jade-lang 0.3.1 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. data/AGENTS.md +164 -0
  3. data/CHANGELOG.md +215 -1
  4. data/README.md +17 -12
  5. data/docs/interop.md +208 -0
  6. data/docs/json.md +163 -0
  7. data/docs/lsp.md +105 -0
  8. data/docs/stdlib.md +69 -0
  9. data/docs/syntax.md +458 -0
  10. data/docs/testing.md +70 -0
  11. data/lib/jade/api.rb +244 -0
  12. data/lib/jade/cli/check.rb +97 -0
  13. data/lib/jade/cli/q.rb +47 -1
  14. data/lib/jade/cli.rb +4 -2
  15. data/lib/jade/codegen/emitter.rb +11 -2
  16. data/lib/jade/codegen/function_call.rb +1 -1
  17. data/lib/jade/codegen/helpers.rb +6 -0
  18. data/lib/jade/codegen/inlines.rb +18 -0
  19. data/lib/jade/codegen/{port_decoder.rb → port_codec.rb} +27 -15
  20. data/lib/jade/codegen.rb +2 -2
  21. data/lib/jade/debug.rb +59 -0
  22. data/lib/jade/decode.rb +332 -212
  23. data/lib/jade/diagnostics/renderer.rb +16 -5
  24. data/lib/jade/frontend/fixity_fixer.rb +3 -2
  25. data/lib/jade/frontend/forward_declaration/interop_import_declaration.rb +13 -3
  26. data/lib/jade/frontend/pattern_analysis/matrix.rb +57 -23
  27. data/lib/jade/frontend/semantic_analysis/constructor_reference.rb +33 -7
  28. data/lib/jade/frontend/semantic_analysis/error/constructor_not_found.rb +20 -5
  29. data/lib/jade/frontend/semantic_analysis/error/variable_not_found.rb +9 -2
  30. data/lib/jade/frontend/semantic_analysis/member_access.rb +11 -1
  31. data/lib/jade/frontend/type_checking/constraints/deriving/decodable.rb +35 -32
  32. data/lib/jade/frontend/type_checking/constraints/deriving/encodable.rb +32 -50
  33. data/lib/jade/frontend/type_checking/constraints/deriving/eq.rb +51 -175
  34. data/lib/jade/frontend/type_checking/constraints/deriving/helpers.rb +133 -0
  35. data/lib/jade/frontend/type_checking/constraints/deriving/show.rb +186 -0
  36. data/lib/jade/frontend/type_checking/constraints/deriving.rb +2 -1
  37. data/lib/jade/frontend/type_checking/error/port_not_encodable.rb +38 -0
  38. data/lib/jade/frontend/type_checking/port_resolution.rb +64 -16
  39. data/lib/jade/interop/boundary.rb +2 -3
  40. data/lib/jade/interop/runtime.rb +10 -2
  41. data/lib/jade/lsp/converters.rb +2 -15
  42. data/lib/jade/lsp/snippets.rb +21 -3
  43. data/lib/jade/parsing/error.rb +19 -0
  44. data/lib/jade/runtime.rb +1 -0
  45. data/lib/jade/signature.rb +47 -0
  46. data/lib/jade/stdlib/calendar.rb +20 -19
  47. data/lib/jade/stdlib/clock.rb +7 -13
  48. data/lib/jade/stdlib/debug.rb +12 -0
  49. data/lib/jade/stdlib/decimal.rb +3 -16
  50. data/lib/jade/stdlib/decode.rb +57 -12
  51. data/lib/jade/stdlib/encode.rb +26 -0
  52. data/lib/jade/stdlib/intrinsics.rb +12 -1
  53. data/lib/jade/stdlib/result.rb +1 -1
  54. data/lib/jade/stdlib/show.rb +40 -0
  55. data/lib/jade/stdlib/text.rb +131 -0
  56. data/lib/jade/stdlib.rb +5 -2
  57. data/lib/jade/symbol/interop_function.rb +3 -1
  58. data/lib/jade/symbol.rb +2 -2
  59. data/lib/jade/task.rb +2 -3
  60. data/lib/jade/version.rb +1 -1
  61. metadata +19 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bb2b101369a0d671ca8f2aa020e7113a49f22e700491e5d14f7e574f62ee17ec
4
- data.tar.gz: 7e017dbe893887f9e7ac3dfcb5c434474085771f5062ee509cf8e9ee9682ddd6
3
+ metadata.gz: f1bbeb934a5bbbeb19cfeddeb18f59798c37380a34d514110f42e07dab206525
4
+ data.tar.gz: 1f68a227a953fa57f875274f409bf93aa7269bbadfc4b3abc7634ea1d6b283c2
5
5
  SHA512:
6
- metadata.gz: 45578bee3ec52655fbba6a0cd7a053a6ad65c9981760edd86e2966fd671958a63fc08fb0120b58fda73298c584773a8e6a4c92eb71b2c11d79228bb88a905e16
7
- data.tar.gz: 698a60f7e747a8042e32f5da24b8c6c322d25a470e3a65f0ed6dfeb61a89c67cc6394ab993115c8c42e9305e75db9b5445f7b5a42a74974832a2c5cd268fe209
6
+ metadata.gz: 87f7c7e84b193ee2dba6e989f493ca36cf4bc6a58ba7d4c38519aedf5052908263cea802e917b11555396edce72c1ae4af5ff687676f13e123d37858c9643e8f
7
+ data.tar.gz: 0d2f3e20d4e1863a567310355a04bc7eb0fb73c49b629db3f0f6162ce2a009ddf821420558962e35052f0a5859d035b196483ead990659f0c73700ad97b4abfb
data/AGENTS.md ADDED
@@ -0,0 +1,164 @@
1
+ # Writing Jade
2
+
3
+ Idiom and gotchas. This file is deliberately not a function list — that's
4
+ what `jade q api` is for, and a copy here would rot.
5
+
6
+ ## Ask the compiler, not the source
7
+
8
+ | Question | Command |
9
+ |---|---|
10
+ | Does this function exist? What does it take? | `jade q api List` / `jade q api List.fold` |
11
+ | Which module has a `fold`? | `jade q find fold` |
12
+ | How is a lambda / `implements` block written? | `jade q syntax lambda` |
13
+ | Did what I just wrote compile? | `jade check path/to/file.jd` |
14
+
15
+ **Grepping the stdlib does not work, and fails quietly.** Stdlib modules are
16
+ written two ways — Jade in a heredoc (`Maybe`, `Result`, `Decimal`) and a Ruby
17
+ DSL (`List`, `String`, `Dict`, `Decode`) — so half of them never spell `def`,
18
+ and none contains the string `List.map` you would search for. Extension
19
+ modules (`Sql.*`) live in a gem that isn't in the tree at all. A grep that
20
+ comes back empty tells you nothing.
21
+
22
+ Inside a project, `jade q api` covers the stdlib, your own modules, and any
23
+ extension gem's, each tagged with an `origin`.
24
+
25
+ ## Reach for the combinator
26
+
27
+ A `case` that just unwraps and rewraps is the long way round.
28
+
29
+ | Instead of | Write |
30
+ |---|---|
31
+ | `case m in Just(x) then f(x) in Nothing then Nothing end` | `Maybe.map(m, f)` |
32
+ | `case m in Just(x) then x in Nothing then d end` | `Maybe.with_default(m, d)` |
33
+ | `case m in Just(x) then f(x) in Nothing then Nothing end` where `f` returns `Maybe` | `Maybe.and_then(m, f)` |
34
+ | `case r in Ok(x) then Ok(f(x)) in Err(e) then Err(e) end` | `Result.map(r, f)` |
35
+ | `case r in Ok(x) then x in Err(_) then d end` | `Result.with_default(r, d)` |
36
+ | a hand-written `==` | nothing — `Eq` derives |
37
+ | a hand-written decoder for a struct | nothing — `Decode.from_json` derives it |
38
+
39
+ These read best piped:
40
+
41
+ ```jade
42
+ def label(m: Maybe(User)) -> String
43
+ m
44
+ |> Maybe.map((u) -> { u.name })
45
+ |> Maybe.with_default("anonymous")
46
+ end
47
+ ```
48
+
49
+ `case` earns its place when you're actually distinguishing variants — the
50
+ `Result` arm that reports a different error, the union with five constructors.
51
+ Write one `in` branch per variant rather than an `else` fallback; `else` is for
52
+ matching literals, where exhaustiveness isn't available.
53
+
54
+ **`map` changes the element type.** `Maybe.map : (Maybe(a), (a) -> b) ->
55
+ Maybe(b)`. If you were told otherwise, that was hover, which reports a
56
+ collapsed `(a) -> a` for the stdlib functions backing an interface — a known
57
+ bug, see `~/vault/claude/jade/bugs/hover-collapses-stdlib-type-vars.md`.
58
+ `jade q api` reads the declaration and is right.
59
+
60
+ ## Imports
61
+
62
+ Auto-imported, no `import` needed: **`Basics`, `Maybe`, `Tuple`, `List`,
63
+ `Char`, `String`, `Result`, `Task`, `Bytes`**.
64
+
65
+ Everything else needs an explicit `import`: **`Dict`, `Set`, `Decode`,
66
+ `Decode.Params`, `Encode`, `Calendar`, `Clock`, `Decimal`, `Show`, `Debug`**.
67
+ `Show.show(x)` without `import Show` is "I cannot find a `Show.show`
68
+ variable" — the function exists, the import doesn't.
69
+
70
+ `import Dict` gets you the module. To name its *type* unqualified you have to
71
+ ask for it:
72
+
73
+ ```jade
74
+ import Dict exposing (Dict) -- then: def go -> Dict(String, Int)
75
+ import Dict -- then: def go -> Dict.Dict(String, Int)
76
+ ```
77
+
78
+ **Zero-argument entries are values, not calls.** `Dict.empty`, `Decode.bool`,
79
+ `Encode.null` — no parentheses. `Dict.empty()` is a compile error.
80
+
81
+ ## Interfaces
82
+
83
+ The built-ins live in `Basics`: `Eq`, `Comparable`, `Appendable`, `Mappable`,
84
+ `Chainable`, `Numeric`. Plus `Show` (in `Show`), `Decodable` (in `Decode`),
85
+ `Encodable` (in `Encode`).
86
+
87
+ Declare and implement:
88
+
89
+ ```jade
90
+ interface Sized(a) with
91
+ size : a -> Int
92
+ end
93
+
94
+
95
+ implements Sized(Basket) with
96
+ size: (b) -> { List.length(b.items) }
97
+ end
98
+ ```
99
+
100
+ The right-hand side is an inline lambda or a function reference
101
+ (`size: basket_size`).
102
+
103
+ Two things to keep straight:
104
+
105
+ - **The interface parameter is the constructor, not the applied type.**
106
+ `Mappable f` has `map : f(a), (a -> b) -> f(b)` — `f` is `Maybe`, not
107
+ `Maybe(a)`. Getting this wrong is why the impl target reads
108
+ `Mappable(Maybe(a))`.
109
+ - **`jade q api` tells you what already has an instance.** `jade q api
110
+ Decode.Decodable` lists every implementing type under `implemented_by`;
111
+ a struct's entry lists what it `implements`. Check before writing one.
112
+
113
+ ### What derives, what doesn't
114
+
115
+ Verified against the compiler:
116
+
117
+ | | Structs / unions |
118
+ |---|---|
119
+ | `Eq` (`==`) | derives |
120
+ | `Show` | derives |
121
+ | `Encodable` / `Decodable` | derives |
122
+ | `Comparable` (`<`, `List.sort`) | **does not derive** |
123
+
124
+ `List.sort` on a list of structs is `No implementation of Basics.Comparable
125
+ for Point`. Write the instance, or sort by a projection with
126
+ `List.sort_by`.
127
+
128
+ ## Encoding and decoding
129
+
130
+ `Encode.encode(value)` derives the encoder from the value's type;
131
+ `Decode.from_json(json)` derives the decoder from the **return type**. A
132
+ struct round-trips with neither written by hand:
133
+
134
+ ```jade
135
+ def parse(json: String) -> Result(User, DecodeError)
136
+ Decode.from_json(json)
137
+ end
138
+ ```
139
+
140
+ Derivation reaches through structural types, so anything built from encodable
141
+ parts is encodable: `List(a)`/`Set(a)` → array, `Maybe(a)` → the value or
142
+ `null`, tuples → positional array, `Dict(k, v)` → array of `[k, v]` pairs (a
143
+ JSON object only admits string keys), a struct → object keyed by field name, a
144
+ union whose variants take no arguments → the variant name in snake_case.
145
+
146
+ Outside that list — a union carrying arguments, say — write
147
+ `implements Encodable(T)` / `Decodable(T)` yourself; nothing but you knows the
148
+ shape it should take.
149
+
150
+ Reach for the explicit combinators (`Decode.field`, `Decode.required`,
151
+ `Decode.index`) when the JSON doesn't match the struct one-to-one. Use
152
+ `Decode.Params` for PATCH-style input, where a missing field means "don't
153
+ touch" rather than an error.
154
+
155
+ Full treatment: [docs/json.md](docs/json.md).
156
+
157
+ ## Where else to look
158
+
159
+ - [docs/syntax.md](docs/syntax.md) — the language, form by form.
160
+ - [examples/](examples/) — nine files, compiled and asserted by
161
+ `spec/examples_spec.rb`, so they're verified idiom rather than samples that
162
+ drifted. `interfaces.jd` and `pattern_matching.jd` earn their keep.
163
+ - [docs/interop.md](docs/interop.md) — the Ruby boundary, ports, `uses`.
164
+ - [docs/stdlib.md](docs/stdlib.md) — what each module is for.
data/CHANGELOG.md CHANGED
@@ -6,6 +6,219 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.5.0]
10
+
11
+ ### Added
12
+
13
+ - **`jade check [FILE...]`** type-checks and prints diagnostics without
14
+ generating anything — the same front end the language server runs, exiting 1
15
+ if there were errors. With no arguments it checks every `.jd` under the
16
+ source root. Closes the loop for editors, CI, and anything that just edited a
17
+ file and wants to know whether it invented a function.
18
+ - **`jade q api [MODULE|NAME]`** and **`jade q find TERM`** report the public
19
+ surface of everything a module can call — signatures, interface constraints,
20
+ a struct's fields, a type's variants and what it implements, an interface and
21
+ what implements it — read out of the registry rather than the source. Source
22
+ is the wrong thing to read: stdlib modules are written two ways (Jade in a
23
+ heredoc, a Ruby DSL), so grepping `lib/jade/stdlib/` finds neither `List.map`
24
+ nor half the modules' functions at all, and an extension gem's modules aren't
25
+ in your tree to grep. Inside a project the listing spans the stdlib, the
26
+ project's own modules and any extension gem's, each tagged with its `origin`;
27
+ without a `jade.json` it falls back to the stdlib, so the query still answers
28
+ from any directory. A module that won't compile is reported under `skipped`
29
+ rather than silently missing.
30
+ - **`jade q syntax [FORM]`** reports how a form is written — `lambda` is
31
+ `(args) -> { body }`, plus `def`, `type`, `struct`, `case`, `if`, `module`,
32
+ `import`, `interface`, `implements`, `uses`. Signatures answer "does this
33
+ function exist and what does it take", never "how is a lambda spelled", and
34
+ that second question has its own wrong answers. It serves the corpus the
35
+ editor already offers as completions, so the two can't drift; needs no
36
+ project and reads no files.
37
+
38
+ - **`AGENTS.md`** — idiom and gotchas for writing Jade: reach for `Maybe.map` /
39
+ `with_default` over a `case` that only unwraps and rewraps, which stdlib
40
+ modules are auto-imported and which need an `import`, that zero-argument
41
+ entries are values and `Dict.empty()` is a compile error, what derives (`Eq`,
42
+ `Show`, `Encodable`, `Decodable`) and what doesn't (`Comparable`), and how
43
+ interfaces and encode/decode fit together. Deliberately not a function list —
44
+ that's `jade q api`, and a copy would rot. Every claim in it was checked
45
+ against the compiler.
46
+ - The gem now ships `docs/` and `AGENTS.md`, so a project that installs
47
+ jade-lang gets them instead of README links pointing at nothing.
48
+
49
+ ### Changed
50
+
51
+ - **Decoding a row returned from a port is roughly eight times cheaper.**
52
+ A derived decoder was a composition of combinators rebuilt and walked
53
+ for every row; a profile put 88% of the time inside the interpreter and
54
+ 70 allocations on a four-field row. Deriving now emits one node
55
+ carrying every key, its decoder and the constructor, applied once
56
+ rather than curried through a closure per field; the interpreter
57
+ returns values unwrapped, with failures on a sentinel, so nothing
58
+ allocates a `Result` per node; and each descriptor decodes itself
59
+ instead of being matched out of a twenty-branch `case`. A four-field
60
+ row went 9.58 to 1.19 µs and 70 allocations to 5.
61
+ - **`Decimal`, `Clock.Instant` and `Calendar.Date` read their text form
62
+ in Ruby** rather than parsing it through Jade-level combinators, which
63
+ cost a fresh decoder, two `Maybe`s and a tuple for every value. The
64
+ text forms are unchanged, so JSON output and existing ports are
65
+ unaffected. One input changes meaning: a trailing exponent marker
66
+ (`"825e-4e"`) used to read as `825e-4`, because splitting on every
67
+ `"e"` dropped the empty trailing piece, and is rejected now.
68
+ - **Encoding a value back out to Ruby is cheaper by the same route.** A
69
+ derived record encoder built a pair per field and folded the list back
70
+ into a hash, with an intrinsic lookup for both per field per value; it
71
+ builds the hash directly. `Encodable(Instant)` writes its string in
72
+ Ruby next to the reader that parses it. Handing a four-field struct
73
+ back to a Ruby caller cost 2.26 µs/row over the internal form and now
74
+ costs 0.17 — what a caller waits for and what `Internal` measures have
75
+ converged.
76
+ - **`Calendar.from_rata_die` is closed form.** It walked a year at a time
77
+ to find the year, then December backwards to find the month, allocating
78
+ a `Month` and a tuple per step through two twelve-branch `case`
79
+ statements. It is reached from `Clock.to_iso`, `Clock.on_date` and
80
+ `Calendar.add`, so this is not only an interop win — any Jade code
81
+ doing date arithmetic pays it.
82
+ - The `case` completion snippet offers one `in` branch per variant instead of
83
+ an `else` fallback. `else` is for matching literals, where exhaustiveness
84
+ isn't available — not the default shape of a `case`.
85
+ - **A missing qualified name now suggests the one you meant.** `List.fold_left`
86
+ answers ``help: did you mean `List.fold`?`` instead of a bare "not found" —
87
+ the candidates are the module's exposed values, so the suggestion is drawn
88
+ from what actually exists. Suggestions go through the alias the module was
89
+ imported under (`L.post`, not `Ledger.post`), since that's what the call site
90
+ can say. Local variables, types and constructors already did this;
91
+ module-qualified access was the gap.
92
+
93
+ ### Fixed
94
+
95
+ - **Operator precedence was lost inside an infix chain's operands.**
96
+ `(1 + 2 * 3) + 0` was 9, `(10 - 4 / 2) + 0` was 3, and
97
+ `id(1 + 2 * 3) + 0` was 9. Shunting-yard ran on a chain's own operators
98
+ but treated every operand as an atom and returned it unfixed, so a
99
+ grouping, a call argument, a lambda body, a ternary branch or a record
100
+ value sitting in a chain kept the shape the parser built — left to
101
+ right, no precedence. Standing alone the same expression was fine,
102
+ which is why `1 + 2 * 3` and `(1 + 2 * 3)` both gave 7 and nothing
103
+ caught it. **This changes generated output for code that hit it**: a
104
+ project that commits generated artifacts and CI-checks them for drift
105
+ will see a diff, and the new bytes are the correct arithmetic.
106
+ - **`Clock.on_date` reported the previous day for pre-1970 instants.**
107
+ `Clock.floor_div` subtracted one from the quotient for a negative
108
+ dividend with a remainder — the right correction when `/` truncates
109
+ toward zero, which Jade's doesn't; it already floors, so it floored
110
+ twice. `at_time` was unaffected, so `to_iso(-1)` read
111
+ `"1969-12-30T23:59:59Z"`: the time right, the date a day out. Exact
112
+ midnight was always correct, because the double correction only applies
113
+ when there is a remainder.
114
+ - **Anonymous records decoded from the same shape now compare equal.**
115
+ The derived decoder built its constructor from a bare `Data.define`,
116
+ which mints a fresh class each time the decoder is built, so
117
+ `{ x: 1, y: "a" }` decoded twice compared false. It resolves through
118
+ the same interned registry record literals already used. Note this does
119
+ not unify decoded records with *literals* in the general case —
120
+ literals hoist to a per-module constant, so two modules with the same
121
+ shape still get different classes.
122
+ - **The tuple decoders no longer build their constructor with
123
+ `Method#curry`.** A curried `Method` built once and called on every
124
+ decode corrupts the heap under GC compaction — a near-null "try to mark
125
+ T_NONE object" crash after enough rows. It is the pattern the
126
+ constructor-curry regression spec exists to prevent; that spec reads
127
+ generated code, so the stdlib's own copy was invisible to it.
128
+ - **`Result.on_error` can change the error type**, as its signature has always
129
+ claimed. The `Ok` arm handed back the input rather than rebuilding it, which
130
+ unified the outgoing error type with the incoming one, so
131
+ `Result(Int, String) -> Result(Int, Int)` did not compile. Recovering into a
132
+ different error type is the reason the function takes `e -> Result(a, f)`.
133
+ - **A signature no longer renders two distinct type variables as one.** Hover
134
+ reported `Maybe.map : (Maybe(a), (a) -> a) -> Maybe(a)` — a function unable
135
+ to change the element type, which is a different function from the one that
136
+ exists. Variables are identified by an id but print as a name, and nothing
137
+ upstream keeps names distinct; rendering now re-letters a clash instead of
138
+ emitting the same name twice. Declared names are kept where they don't
139
+ collide, so annotated signatures read as written.
140
+
141
+ - **`docs/stdlib.md` named nine functions that don't exist.** `Char.is_digit`,
142
+ `is_alpha`, `is_alpha_num`, `is_upper`, `is_lower` were renamed to `digit?`,
143
+ `alpha?`, `alpha_numeric?`, `upper?`, `lower?` when predicates took the `?`
144
+ suffix and the doc never followed; `String.contains` is `contains?`,
145
+ `Dict.member` is `member?`, `Tuple.map_first` / `map_second` were never
146
+ implemented, and `Decode.at` doesn't exist (`Decode.index` does). Every
147
+ qualified name in `docs/stdlib.md` and `AGENTS.md` now checks out against the
148
+ registry.
149
+
150
+ ## [0.4.0]
151
+
152
+ ### Changed
153
+
154
+ - **Port arguments are encoded on the way out**, the mirror of the return value
155
+ being decoded on the way back. A port declared `Instant, Int -> Task(Instant,
156
+ Never)` used to hand Ruby a live `Jade::Clock::Instant` while demanding an ISO
157
+ string back; both sides are now the wire form. **Every argument type needs an
158
+ `Encodable` instance** — a port taking one without it is a compile error
159
+ (``Port `shift` cannot encode argument 1 (`Shape`): no Encodable instance``)
160
+ rather than a value leaking Jade's internals into Ruby. Declare the argument
161
+ as `Decode.Value` to opt out and pass it untouched. A port whose arguments are
162
+ primitives is unaffected; one taking a struct, a `Clock`/`Calendar`/`Decimal`
163
+ type, or a type variable now receives encoded data, so its Ruby side needs
164
+ updating.
165
+
166
+ ### Added
167
+
168
+ - `Show`, an interface rendering a value the way Jade writes it: `show(B(2))`
169
+ is `"B(2)"`, `show(Point(3, 4))` is `"Point { x: 3, y: 4 }"`, a list shows
170
+ through its elements. Instances ship for the primitives and derive for
171
+ unions, structs and records — including a variant that mixes a type
172
+ parameter with a concrete type. A function shows as `<function>` rather than
173
+ refusing. `Never` has an instance too, so an annotated `Result(a, Never)` —
174
+ what a port-free task returns — is showable; it raises rather than printing
175
+ a plausible string, since it has no values to render.
176
+ - `Debug.log(label, value)` prints `label: value` to stderr and returns the
177
+ value untouched, so it drops into a pipeline without changing it.
178
+ - `Encodable` and `Decodable` derive for tuples, `Dict` and `Set`, from their
179
+ elements' instances — the same way `List` and `Maybe` already did. The
180
+ combinators (`Encode.tuple`, `Decode.dict`, …) existed all along; nothing
181
+ resolved them, so a `(String, Int)` port argument or a `Task(Dict(k, v), e)`
182
+ arm was rejected for want of an instance. A tuple crosses as a positional
183
+ array, a dict as `[key, value]` pairs, a set as an array that drops
184
+ duplicates on the way back. Adds the two functions the set instances needed,
185
+ `Encode.set` and `Decode.set`.
186
+ - `Encodable` and `Decodable` instances for `Decode.Value`, both the identity,
187
+ plus the `Decode.value` and `Encode.value` functions behind them. `Value` is
188
+ the un-decoded value, and it opted out of conversion only as a whole arm or
189
+ argument: `List(Value)`, `Maybe(Value)`, and a struct with a `Value` field
190
+ had no instance to derive from and were rejected at the boundary.
191
+ - A parse error inside a `uses`, `interface`, or `implements` block where the
192
+ entries are separated by newlines instead of commas says so, instead of only
193
+ reporting the next entry as an unexpected token.
194
+
195
+ ### Fixed
196
+
197
+ - `Eq` derives for a union whose variants carry concrete types. `B(1) == B(1)`
198
+ over `type Box = B(Int) | Empty` took the compiler down with a
199
+ `NoMatchingPatternError`; `Just(1) == Just(1)` only worked because `Maybe`'s
200
+ payload is a type parameter. Variants mixing a parameter with a concrete
201
+ type derive too.
202
+ - Diagnostic source excerpts are sliced by byte offset. Spans and line starts
203
+ are the lexer's byte offsets, but the excerpt was cut and measured in
204
+ characters, so one multi-byte glyph anywhere earlier in the file shifted every
205
+ later excerpt and its caret — `def notify_channel(…)` printed as
206
+ `f notify_channel(…)`.
207
+ - Exhaustiveness checking no longer collapses when the scrutinee is a tuple.
208
+ `case (a, b)` over two `Maybe`s reported `Missing cases: (_, _)` with all four
209
+ arms present, and the `else` that silenced it hid genuinely missing cases from
210
+ then on. Two bugs: a tuple's element types were replaced with fresh type
211
+ variables, and specializing on a constructor dropped the types of every column
212
+ after it. A constructor the first column never mentions is now answered from
213
+ the rows that would have covered it, which is also what keeps a recursive type
214
+ from expanding forever — the recursion guard that used to do that job is gone,
215
+ and with it the false "exhaustive" verdicts it caused on any second column.
216
+ - The missing-constructor diagnostic tells you which `exposing` list is missing
217
+ the `(..)`. When the defining module already exposes `Route(..)` and it's the
218
+ importer's `import Routes exposing (Route)` that brought in the type alone, it
219
+ no longer sends you to the wrong file. It also fires for a variant whose name
220
+ differs from its type's (`Home` of `Route`), which previously got no hint.
221
+
9
222
  ## [0.3.1]
10
223
 
11
224
  ### Fixed
@@ -115,7 +328,8 @@ Initial release.
115
328
  - `jade` CLI dispatcher: `fmt`, `lsp`, `q`.
116
329
  - Language server and headless query interface for editor/agent tooling.
117
330
 
118
- [Unreleased]: https://github.com/agustinrhcp/jade/compare/v0.3.1...HEAD
331
+ [Unreleased]: https://github.com/agustinrhcp/jade/compare/v0.4.0...HEAD
332
+ [0.4.0]: https://github.com/agustinrhcp/jade/compare/v0.3.1...v0.4.0
119
333
  [0.3.1]: https://github.com/agustinrhcp/jade/compare/v0.3.0...v0.3.1
120
334
  [0.3.0]: https://github.com/agustinrhcp/jade/compare/v0.2.0...v0.3.0
121
335
  [0.2.0]: https://github.com/agustinrhcp/jade/compare/v0.1.1...v0.2.0
data/README.md CHANGED
@@ -148,7 +148,7 @@ at compile time, no annotation needed. You can define your own, with
148
148
  implementations dispatched by type:
149
149
 
150
150
  ```jade
151
- module Shows exposing (describe)
151
+ module Labels exposing (person_label)
152
152
 
153
153
  struct Person = {
154
154
  name: String,
@@ -156,18 +156,18 @@ struct Person = {
156
156
  }
157
157
 
158
158
 
159
- interface Show(a) with
160
- show : a -> String
159
+ interface Labelled(a) with
160
+ label : a -> String
161
161
  end
162
162
 
163
163
 
164
- implements Show(Person) with
165
- show: (p) -> { p.name ++ " (" ++ String.from_int(p.age) ++ ")" }
164
+ implements Labelled(Person) with
165
+ label: (p) -> { p.name ++ " (" ++ String.from_int(p.age) ++ ")" }
166
166
  end
167
167
 
168
168
 
169
- def describe(p: Person) -> String
170
- show(p)
169
+ def person_label(p: Person) -> String
170
+ label(p)
171
171
  end
172
172
  ```
173
173
 
@@ -333,7 +333,9 @@ Worst case: you wrote Ruby with a nicer authoring layer for a while.
333
333
 
334
334
  There's a language server — type errors, inferred types, and jump-to-definition
335
335
  in any editor that speaks LSP. For tools that don't, `jade q` answers the same
336
- questions as one-shot JSON (hover, definition, references, symbols).
336
+ questions as one-shot JSON (hover, definition, references, symbols), `jade q
337
+ api` reports the stdlib's signatures, and `jade check` type-checks a file and
338
+ prints what's wrong.
337
339
 
338
340
  In our experience coding agents like Claude Code and Cursor handle Jade well:
339
341
  the syntax is close enough to the ML family (Elm, OCaml, Haskell) that models
@@ -346,9 +348,11 @@ for us so far.
346
348
  A single `jade` binary fronts the toolchain:
347
349
 
348
350
  ```
351
+ jade check [file...] # type-check; exits 1 on errors, generates nothing
349
352
  jade fmt [-i|-c] [file] # format .jd source (stdin or file)
350
353
  jade lsp # language server over stdio (hover, defn, refs, diagnostics)
351
354
  jade q hover FILE:L:C # headless JSON queries — hover/symbols/defn/refs
355
+ jade q api List.fold # stdlib signatures, read from the registry
352
356
  ```
353
357
 
354
358
  `jade fmt` is deterministic and idempotent; wire it into your editor or a
@@ -357,8 +361,9 @@ pre-commit hook.
357
361
  ## Standard library
358
362
 
359
363
  `Basics`, `String`, `Char`, `List`, `Dict`, `Set`, `Tuple`, `Maybe`, `Result`,
360
- `Task`, `Decode`, `Encode`, `Bytes`, `Calendar`, `Clock`, `Decimal`. Stdlib operations
361
- compile inline rather than through a runtime dispatch layer.
364
+ `Task`, `Decode`, `Encode`, `Bytes`, `Calendar`, `Clock`, `Decimal`, `Show`,
365
+ `Debug`. Stdlib operations compile inline rather than through a runtime
366
+ dispatch layer.
362
367
 
363
368
  ## Docs
364
369
 
@@ -374,8 +379,8 @@ compile inline rather than through a runtime dispatch layer.
374
379
 
375
380
  Early and experimental — being tried out on small projects.
376
381
 
377
- **In progress:** `Comparable` / `Show` derivation for user types, partial
378
- record types in signatures, a stable REPL.
382
+ **In progress:** `Comparable` derivation for user types, partial record types
383
+ in signatures, a stable REPL.
379
384
 
380
385
  **Not great for:** throwaway scripts, libraries you ship to other Ruby
381
386
  projects (they'd inherit the dependency), and performance-critical hot paths