core-typed

2022-04-03T15:06:26.081569Z

BTW I had an idea on how to support inference via cljs.core/type in the CLJS checker. How important is this function in CLJS? and are namespaced keywords usually used to identify a type?

rayat 2022-04-03T23:19:07.637929Z

More noob noise for you if you're interested: 1. Are you suggesting to call cljs.c/type from inside core.typed on forms statically? Doesn't that only work "at runtime", as that's when the object whose constructor you're interested in exists?

2022-04-03T23:54:22.832959Z

What I'm speculating about is how to type check something like this:

(def my-specially-tagged-val ^{:type ::my-specially-tagged-val} {:foo 1 :bar 2})
(defn process-thing [thing :- t/Any]
  (case (type thing)
    ::my-specially-tagged-val (+ (:foo thing) (:bar thing))))

2022-04-03T23:55:53.692699Z

How can we know that (:foo thing) is an int when we type check (+ ...)?

🤔 1
2022-04-03T23:57:23.669159Z

Idea: Have a registry that maps :type values to their types.

(t/register-metadata-type ::my-specially-tagged-val '{:foo t/Int, :bar t/Int})

2022-04-03T23:58:01.327669Z

And once we learn, for example (= ::my-specially-tagged-val (type thing)), we update the type environment with thing :- '{:foo t/Int, :bar t/Int}.

2022-04-04T00:00:41.726849Z

Typed Clojure uses an environment of propositions that contain knowledge of the current lexical scope. You can see propositions encoded in the types of predicate, eg., pos-int? learns the 0th arg is an int if it returns true: https://github.com/typedclojure/typedclojure/blob/main/typed/lib.clojure/src/typed/ann/clojure.cljc#L2037-L2044

1
2022-04-04T00:02:57.981669Z

You can extend this propositional logic to reason about "paths" into local variables. Currently, we can reason about the "Class" of a variable, see the type of clojure.core/class. https://github.com/typedclojure/typedclojure/blob/main/typed/lib.clojure/src/typed/ann/clojure.cljc#L1760-L1762

2022-04-04T00:04:02.477079Z

That enables inferring cool stuff like this:

(defn foo [a :- t/Any]
  (when (= Number (class a))
    (inc a)))

2022-04-04T00:04:48.865029Z

Now the idea here is to add a Type path element, so we can reason about clojure.core/type in the same way.

2022-04-04T00:05:28.239099Z

cc/type would look something like [t/Any -> t/Any :object {:id 0 :path [Type]}]

🤔 1
2022-04-03T15:09:25.254639Z

Also thinking about how Typed Clojure might integrate with clj-kondo (JVM). If the checker can be hooked in every time clj-kondo thinks an expression needs linting, I think that should work.

😮 1
2022-04-03T15:10:01.960539Z

that's probably the first step to thinking about Graalvm support--integrate via JVM first.

rayat 2022-04-05T21:48:27.073099Z

Unrelated to the polymorphic ts contextual typing stuff, I'm sure you've seen https://github.com/borkdude/speculative, and I wonder if your spec->types endeavor could help you bootstrap the community types repo thing

👍 1
2022-04-05T23:15:56.245719Z

Yeah it would be interesting to know how useful raw specs are for type checking. For CRUD-style things, it's probably perfectly adequate.

2022-04-05T23:16:47.331619Z

Unfortunately that's exactly the kind of thing that's usually closed source, so it's harder to experiment.

lilactown 2022-04-04T16:52:27.460829Z

something like https://github.com/lilactown/cascade or https://github.com/lilactown/pyramid might be fun to try and type

lilactown 2022-04-04T16:55:13.214659Z

that being said, I did my time in the OCaml and TypeScript mines and came out a better programmer and not excited about writing more types for my libs

👍 1
2022-04-04T17:00:47.505459Z

oh boy, cascade would be a good stress test of how first-class the typings of core ops are.

2022-04-04T17:03:11.412699Z

I'm guessing it would fail miserably 🙂

😄 1
lilactown 2022-04-04T17:30:22.820169Z

my experience typing similar libraries in ts like ramda makes me believe it's just a hard problem 😄

rayat 2022-04-04T18:20:44.064129Z

Hahaha speaking of typing rambda you guys will fucking go nuts over this: https://www.freecodecamp.org/news/typescript-curry-ramda-types-f747e99744ab/ Loops in the type annotation syntax??? Bananas --- Anyways, I love typing libraries so much, no clue why, so I'm down to type either @lilactown, though IDK what Cascade is

2022-04-04T21:58:28.503849Z

Nice, conditional union types has always had me jealous. I almost included something like it for my PhD thesis IIRC because I wanted the expressivity.

❤️ 1
2022-04-04T22:02:57.874219Z

I'm currently playing around with a nutty idea to solve how to check (map (fn [x :- InferThisTypeAutomatically] ..) [..]). I think Flow lang can do this. I don't think TS can, I believe it infers x as any.

2022-04-04T22:30:12.440019Z

Looks like TS added support for this https://www.typescriptlang.org/docs/handbook/2/functions.html#inference

👆🏽 1
2022-04-04T22:33:29.461339Z

apparently called contextual typing https://www.typescriptlang.org/docs/handbook/type-inference.html#contextual-typing

2022-04-04T22:40:54.407119Z

An old spec detailing when contextual typing happens https://github.com/microsoft/TypeScript/blob/main/doc/spec-ARCHIVED.md#423-contextually-typed-expressions

2022-04-04T22:42:45.577359Z

In a typed function call, argument expressions are contextually typed by their corresponding parameter types. ? Seems vague about polymorphic function calls AFAICT.

2022-04-04T22:43:36.613559Z

If anyone knows where the code is that implements this I'd be interested.

🤔 1
rayat 2022-04-03T23:14:36.799239Z

Forgive my ignorance, what is it about graal that makes it so that it needs special consideration for core-typed? What does core-typed do that isn't "regular clojure 'user-space'-y code"? btw I'm very invested in core-typed (or until, as I joke, I stop working at a clj startup lol) in a personal agenda sense but also curious about it - and I'd love to contribute in some way or other someday, if that's something you're into

2022-04-03T23:49:41.923029Z

Good question, the problem is that clojure on graalvm cannot perform macroexpansion.

2022-04-03T23:50:21.914449Z

This (being able to macroexpand forms) was very fundamental to how Typed Clojure worked in its original design. In the last few years, we've added custom rules for particular macros.

2022-04-03T23:50:48.978109Z

So now the door is partially open to graalvm...if we can write enough rules!

2022-04-04T01:00:41.646059Z

@rayatrahman9 in terms of contributing, it might be fun and informative to try and type check one of your favourite libraries or programs (~500 LOC). As you go through, through it, leave yourself a bunch of comments about what sucks. Then you have a list of relevant features to add to Typed Clojure! That's exactly what I'm doing now with malli.

2022-04-04T01:03:34.555469Z

if you pick a popular library, we can take the annotations and rules you generate from the exercise and make that itself a contribution to Typed Clojure.

2022-04-04T01:03:55.117389Z

What do you think?

rayat 2022-04-04T01:27:20.921119Z

1. 😮 I did not know that about graalvm. What's the best resource to learn more about that? I'm new AF to clj in general, but it does seem that macros are a pretty big part of it, no? Curious about how that is developing. I was so floored when I first read about graal like years ago, been out of the loop! 2. I'm very open to your proposal, however, I'm probably the worst person to ask : a. I started clj programming like 5 months ago after joining my current org b. And primarily am working so far in the UI stack in cljs, which I understand is quite far from clj as far as progress/integration with most tooling goes, including typed , right? c. That being said, I'm very comfortable throwing my manager @lilactown under the bus here, i. as (I like to believe) he wants nothing more than to make me happy (with cljs tooling), ii. and is far more experienced 1. in the clj/s OSS community a. and the experiences with/expectations for/frustrations with them that developers have, 2. but also from his own libraries, like #helix, a. which I'm sure I can irritate him enough to eventually fully type annotate 3. and has also had experience with the gradually-typed static type annotation world of TS 3. Are you familiar with Typescript's DefinitelyTyped repository? a. If not, it's a community repo with auto build system set up by M$ for people to share hand-written type annotations for popular libraries whose maintainers don't want to or can't provide them themselves. b. It's an imperfect solution to an imperfect problem, but provides a halfway-decent opt-in way to essentially let people who need them obtain and contribute annotations Regarding making the annotations and rules generated from your proposed exercise contributions to Type Clojure, do you mean something like (3) above?

2022-04-04T01:39:39.037569Z

1. I don't know a lot about Graalvm except that babashka and clj-kondo use it, and they are both excellent tools. I donate to @borkdude so I don't have to know too much about graavlm xD 2. FYI Typed Clojure is both older than clj-kondo/malli/spec/schema and both less mature and more experimental. It effectively has no users AFAIK. Just to say, it might be a while, if ever, if it can be applied to your job. That said, I think it's extremely rewarding to work on, and it's really making strides lately. 3. Yes, my plan is just to shove annotations into the monorepo with Typed Clojure so I have everything in the same place. I'm basically proposing that you write annotations like you would in DefinitelyTyped for library "X", except you also call t/check-ns-clj on the actual library with those annotations (actually, not sure if this is the usual practice with DefinitelyTyped). Now you have annotations that have been self-checked, and some experience under your belt as to the rough edges to Typed Clojure and some idea of which things you think would be the biggest improvements.

😆 1
🤔 1
borkdude 2022-04-03T15:11:03.329649Z

@ambrosebs welcome to elaborate - I can help with both clj-kondo and graalvm

❤️ 1
borkdude 2022-04-03T15:11:42.900459Z

is core typed a static-analysis based type checker or a run-time checker?

2022-04-03T15:11:55.914379Z

static, like eastwood mostly

borkdude 2022-04-03T15:12:18.638559Z

but it does expand macros etc first right?

2022-04-03T15:13:00.716319Z

Kind of, it's starting to look more like clj-kondo now. You can make custom rules for macros. But where clj-kondo gives up if it doesn't recognize the macro, Typed Clojure will expand.

2022-04-03T15:13:20.027459Z

So theoretically with enough rules, it should be compatible with graalvm.

borkdude 2022-04-03T15:13:30.179079Z

clj-kondo has hooks to expand any macro, if you configure it correctly

2022-04-03T15:13:53.969119Z

Ah ok, news to me! It can use the definition of the macro?

borkdude 2022-04-03T15:14:45.338279Z

Not exactly but you can often copy the macro definition literally to the config

2022-04-03T15:15:31.794569Z

Oh ok, we're talking about the same thing. Typed Clojure has hooks in the same way. But it also expands the macro from its definition if there is no hook.

2022-04-03T15:15:39.523249Z

Obviously not possible on graalvm

🤔 1
borkdude 2022-04-03T15:15:47.437159Z

yes, that's why I was asking :)

borkdude 2022-04-03T15:16:23.576539Z

but potentially that can be disabled for native compilation

borkdude 2022-04-03T15:18:31.141219Z

or it could use SCI in native-mode, to try to eval

2022-04-03T15:18:35.290489Z

Yes. My thoughts are, let's try and integrate with clj-kondo JVM first. Try and teach clj-kondo about what Typed Clojure thinks a cache should contain (ie., when to relint a form/file). And hook into clj-kondo's linting at the form-level. ie., if clj-kondo thinks a form needs linting, it also passes it to Typed Clojure.

2022-04-03T15:19:08.612189Z

The former is not super important first up.

2022-04-03T15:19:20.555769Z

Cache integration can wait.

borkdude 2022-04-03T15:19:52.240879Z

What would be interesting for clj-kondo is also directly be able to use typed clojure annotations for its own (super simple) type checker

2022-04-03T15:20:10.329909Z

I agree but there are are major perf implications.

borkdude 2022-04-03T15:20:33.813699Z

E.g. it can lint the following: (assoc (keyword "dude") 1 2 ) as invalid, but it doesn't know the type of everything

2022-04-03T15:20:34.663949Z

I read through your design goals, it didn't seem to fit with Typed Clojure

2022-04-03T15:22:05.086609Z

I had exactly the same thought as you, I'm still mulling it over. Ignore my negative reaction for now, thinking out loud xD

borkdude 2022-04-03T15:23:15.784989Z

@ambrosebs I've got a thing for clojure.spec here (super spartan, but conceptually it works): https://github.com/clj-kondo/inspector You run clojure.spec at runtime, it emits some type info on disk and clj-kondo can start using it for static analysis

🤔 1
borkdude 2022-04-03T15:23:26.598109Z

A similar thing exists for malli

2022-04-03T15:24:28.057949Z

Yes, I saw the same thing in malli a few days ago. I was about to build one for Typed Clojure until I realized there's a large difference in expressibility. Typed Clojure types are very expressive, it's not obvious how to translate.

borkdude 2022-04-03T15:24:41.522719Z

I wish there was one annotation format to rule them all, so then clj-kondo could just support it statically maybe

borkdude 2022-04-03T15:25:13.903469Z

yes, you have to convert from super expressive to a lossy representation, but the cool thing is that you will get some linting for free, even though it's not perfect

2022-04-03T15:25:40.344979Z

It's an alien language.

borkdude 2022-04-03T15:26:12.040599Z

don't worry about assoc, clj-kondo already has built-in types for that :)

2022-04-03T15:26:25.107449Z

hehe, ok what about polymorphism? map/identity?

borkdude 2022-04-03T15:26:39.526819Z

not available right now

borkdude 2022-04-03T15:27:10.501219Z

all that clj-kondo knows about map is that you get back a sequence

borkdude 2022-04-03T15:27:39.116169Z

it's at the other end of the type spectrum: super mundane, but sometimes it catches a silly thing :)

2022-04-03T15:27:58.298299Z

Yes, and that also plays into your perf goals (lint per character).

borkdude 2022-04-03T15:28:29.852319Z

not sure if I understand that right

2022-04-03T15:29:15.445179Z

Type checking with polymorphic types is expensive. If you support polymorphic types in clj-kondo, you'll need to navigate that problem.

2022-04-03T15:29:25.124149Z

If you want quick response times from the linter.

borkdude 2022-04-03T15:29:44.610139Z

yeah

2022-04-03T15:30:42.197129Z

So that's why I'm thinking, let's keep clj-kondo's type system. clj-kondo + Typed Clojure is almost another use case, it's going to be very slow.

borkdude 2022-04-03T15:31:32.646649Z

ok. if you need any help with anything clj-kondo / graalvm, just poke me.

2022-04-03T15:33:23.810149Z

@borkdude is there a way to hook into clj-kondo's linting on a form-level (JVM, not graalvm)? Say, I just want to add an extra log printing the current form, or call my own linter on the form at the same time as clj-kondo. how might I do that?

borkdude 2022-04-03T15:34:08.473049Z

@ambrosebs There is no generic hook for that, but for experimentation, we could add that in a branch and then see if that's useful

2022-04-03T15:34:25.217669Z

Yes please. All I need is the current namespace and the form.

borkdude 2022-04-03T15:35:06.690839Z

clj-kondo lints on a file-by-file basis, so you would get every form from file. clj-kondo walks from in recursive descend fashion

borkdude 2022-04-03T15:35:48.139419Z

forms are rewrite-clj nodes, but there are ways to coerce those into s-expressions

2022-04-03T15:36:06.401529Z

a rewrite-clj node sounds perfect.

borkdude 2022-04-03T15:36:13.111189Z

ah good

borkdude 2022-04-03T15:36:31.504489Z

clj-kondo runs with a fork of rewrite-clj: the nodes are the same, but all the whitespace has already been erased

👍 1
2022-04-03T15:37:44.954959Z

Oh, is this pre or post reader conditional expansion? I might also need the current platform. Or, just separate hooks for clj/cljs.

borkdude 2022-04-03T15:38:42.810579Z

post

2022-04-03T15:39:17.949769Z

Ok cool. Still need the platform to pick the right annotations.

borkdude 2022-04-03T15:39:24.302719Z

yeah that's available

borkdude 2022-04-03T15:39:33.703519Z

let's see where to best put this hook

😁 1
borkdude 2022-04-03T15:45:07.127259Z

OK, try commit 7c6a60fd1b0c3b168e5982afedbfa41c16b90519:

user=> (with-in-str "(+ 1 2 3)" (clj-kondo/run! {:lint ["-"] :core-typed-hook prn}))
{:node <list: (+ 1 2 3)>, :lang :clj}
{:node <token: 1>, :lang :clj}
{:node <token: 2>, :lang :clj}
{:node <token: 3>, :lang :clj}

👍 1
2022-04-03T15:49:59.798339Z

How do I get the current ns, and what's the node->sexpr function called?

borkdude 2022-04-03T15:50:29.877509Z

I'll add the current ns now

👍 1
borkdude 2022-04-03T15:50:59.094229Z

The node->sexpr function is called rewrite-clj.node/sexpr or you can use the internal version: clj-kondo.impl.rewrite-clj.node/sexpr

👍 1
borkdude 2022-04-03T15:52:59.143909Z

4d7bc522b512d773df4a395b6c515a84abb53bf3

2022-04-03T15:54:30.905999Z

Ok that looks good, thanks! wish me luck ;)

borkdude 2022-04-03T15:54:58.166579Z

luck :)

2022-04-03T16:01:31.964389Z

ah, now I have a potential list of type errors I want to pass back to clj-kondo to report on.

borkdude 2022-04-03T16:03:32.003709Z

You can use clj-kondo.impl.findings/reg-finding! for this

👍 1
borkdude 2022-04-03T16:03:55.337409Z

(at this phase, when this goes public, we need to make everything impl a bit more neat)

borkdude 2022-04-03T16:05:31.578169Z

Hmm, right now this won't work for you

borkdude 2022-04-03T16:05:37.565329Z

Let me add something

borkdude 2022-04-03T16:09:14.204589Z

Commit:

fe622fe37ff93c2edfdc22c30c4ac65bdcb9ef20
There is a :reg-finding! key that contains a function that should be called with a map with keys:
row col end-row end-col type message
The location keys are on the metadata of the node. The :type should be configured in .clj-kondo/config.edn and contain a :level

borkdude 2022-04-03T16:10:02.614969Z

And looky here, clj-kondo found a missing required key ;)

2022-04-03T16:10:29.464929Z

Gotta love that!

2022-04-03T16:24:14.828139Z

Could I get an atom that I can store local state in? Stuff like thread bindings for the current ns.

borkdude 2022-04-03T16:25:54.595649Z

clj-kondo already has that local state

borkdude 2022-04-03T16:26:04.361729Z

can't it just give you that?

borkdude 2022-04-03T16:26:22.152529Z

oh thread bindings?

borkdude 2022-04-03T16:27:05.908779Z

I guess that atom can just live in a closed over value of your hook?

2022-04-03T17:10:28.633529Z

Yes, that's what I was thinking. Either let me set my own state at the top level (say call my hook with zero args and assoc the result to :state) or give me an atom.

2022-04-03T17:16:29.107189Z

The former is probably most flexible.

2022-04-03T17:37:23.310339Z

I was thinking a few steps ahead -- I could close over the hook myself (and I'm doing that for the moment), but if the hook was configured in a .edn configuration somewhere, you'd just want to provide a symbol naming the hook I think.

2022-04-03T18:01:08.284239Z

@borkdude do I need to register a new :type for reg-finding! to show up in the :findings entry?

borkdude 2022-04-03T18:05:42.879519Z

yes, it needs to be in your config.edn

borkdude 2022-04-03T18:05:47.386869Z

configured with a :level

👍 1
2022-04-03T20:25:40.133029Z

@borkdude how do I get the rewrite-clj sexpr with line/col metadata? Using clj-kondo.impl.rewrite-clj.node/sexpr seems to give nil metadata.

borkdude 2022-04-03T20:43:12.204059Z

The node contains the row/col as metadata

2022-04-03T20:43:31.880659Z

Thanks!

borkdude 2022-04-03T20:43:36.448579Z

So you need to grab that before you convert it

2022-04-03T20:44:14.257929Z

Hmm how do I do that recursively?

borkdude 2022-04-03T20:48:39.518499Z

That's a good question. In general you can't attach metadata to some values like numbers, etc so converting to a sexpr is lossy anyway. But you might be able to postwalk the thing at make a best attempt. Or only report the top level location for now. It's a tricky problem. This is why clj-kondo :analyze-call hooks operate directly on nodes so you can return more accurate locations to your messages

borkdude 2022-04-03T20:49:20.619269Z

There's also a :macroexpand hook which operates on the s-expr but moves the messages to the outer node location

2022-04-03T20:51:52.887739Z

I'll try postwalk. I just want the equivalent to the tools.reader output.

borkdude 2022-04-03T20:54:08.492599Z

This could maybe be done for the :macroexpand hook too to get better locations. The challenge there is also that the transformed s-expr is converted back into a rewrite-clj node

borkdude 2022-04-03T20:54:40.401779Z

The Clojure reader has imo a good trade-off to only attach locations to seqs

👍 1
2022-04-03T20:56:33.286499Z

Also I think another piece that I can't access is the current file name for *file*.

borkdude 2022-04-03T20:56:54.678819Z

ok, we can pass that too

👍 1
borkdude 2022-04-03T20:57:10.454399Z

but should not be relevant probably, as the reg-finding! already takes care of this

borkdude 2022-04-03T20:57:58.263299Z

PRs welcome against the clj-kondo core-typed-hook branch. I'm done programming for today :)

borkdude 2022-04-03T20:58:10.432739Z

You can probably experiment with this in a local checkout.

2022-04-03T20:58:33.953189Z

sounds good, thanks very much!

borkdude 2022-04-03T21:24:03.593849Z

Just one more thought: if you're more interested in getting the sexprs exactly like tools.reader gives them, maybe it makes more sense to place the hook elsewhere so you can parse the file yourself. I'm not exactly sure where you want to go with this hook.

2022-04-03T22:01:02.153149Z

Yeah I think you're right. Not exactly sure either, my eventual goal is to have something like clj-kondo native but running Typed Clojure. I was hoping this was a good first step, to share as much machinery as I can with clj-kondo. We'll see.

borkdude 2022-04-03T22:01:49.189439Z

Makes sense, let's keep exploring

👍 1
2022-04-03T22:02:27.590099Z

Hehe yes. I looked for a better place for the hook, but I ended up so far up the stack it seemed a little pointless 🙂

borkdude 2022-04-03T22:03:50.933349Z

At the bottom of analyzer you have analyze-input which has the string of the file

borkdude 2022-04-03T22:04:07.387909Z

you could just do your normal tools reader thing there

borkdude 2022-04-03T22:04:25.113829Z

if the rewrite conversion doesn't pan out

borkdude 2022-04-03T22:04:59.320919Z

but we could also make improvements to the rewrite-clj -> sexpr stuff too

2022-04-03T22:07:42.586709Z

Ah, I missed that. This is making me step back to what I think I want to reuse from clj-kondo: • symbol->var resolution, • info about Java classes/methods/fields • participate in existing editor feedback mechanisms

borkdude 2022-04-03T22:10:58.500179Z

right, then probably top level exprs makes sense

2022-04-03T22:14:40.116479Z

Hmm let's say we wanted feedback from both clj-kondo and typed clojure, but as soon as it's ready. I'm guessing if both were computed by the same run! then both would have to finish before feedback right?

2022-04-03T20:42:02.893399Z

@borkdude I think the hook needs to be a little higher in the analyzer stack, Typed Clojure just needs the top-level forms, but where the hook is now seems pass the checker all the subforms too.

borkdude 2022-04-03T20:44:02.267789Z

OK, we can change it to only pass top level forms, or pass a :top-level key so you can filter

2022-04-03T20:44:42.796769Z

I'll try the latter, thanks!

borkdude 2022-04-03T20:45:15.936699Z

There's already a top-level thing floating around which you probably only need to pass to the hook

2022-04-03T20:48:08.100489Z

hmm do you mean the :top-level? in the ctx? I don't think the hook can access it

borkdude 2022-04-03T20:48:20.808429Z

not yet no, but if we pass it, it can

👍 1