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.
- checksums.yaml +4 -4
- data/AGENTS.md +164 -0
- data/CHANGELOG.md +215 -1
- data/README.md +17 -12
- data/docs/interop.md +208 -0
- data/docs/json.md +163 -0
- data/docs/lsp.md +105 -0
- data/docs/stdlib.md +69 -0
- data/docs/syntax.md +458 -0
- data/docs/testing.md +70 -0
- data/lib/jade/api.rb +244 -0
- data/lib/jade/cli/check.rb +97 -0
- data/lib/jade/cli/q.rb +47 -1
- data/lib/jade/cli.rb +4 -2
- data/lib/jade/codegen/emitter.rb +11 -2
- data/lib/jade/codegen/function_call.rb +1 -1
- data/lib/jade/codegen/helpers.rb +6 -0
- data/lib/jade/codegen/inlines.rb +18 -0
- data/lib/jade/codegen/{port_decoder.rb → port_codec.rb} +27 -15
- data/lib/jade/codegen.rb +2 -2
- data/lib/jade/debug.rb +59 -0
- data/lib/jade/decode.rb +332 -212
- data/lib/jade/diagnostics/renderer.rb +16 -5
- data/lib/jade/frontend/fixity_fixer.rb +3 -2
- data/lib/jade/frontend/forward_declaration/interop_import_declaration.rb +13 -3
- data/lib/jade/frontend/pattern_analysis/matrix.rb +57 -23
- data/lib/jade/frontend/semantic_analysis/constructor_reference.rb +33 -7
- data/lib/jade/frontend/semantic_analysis/error/constructor_not_found.rb +20 -5
- data/lib/jade/frontend/semantic_analysis/error/variable_not_found.rb +9 -2
- data/lib/jade/frontend/semantic_analysis/member_access.rb +11 -1
- data/lib/jade/frontend/type_checking/constraints/deriving/decodable.rb +35 -32
- data/lib/jade/frontend/type_checking/constraints/deriving/encodable.rb +32 -50
- data/lib/jade/frontend/type_checking/constraints/deriving/eq.rb +51 -175
- data/lib/jade/frontend/type_checking/constraints/deriving/helpers.rb +133 -0
- data/lib/jade/frontend/type_checking/constraints/deriving/show.rb +186 -0
- data/lib/jade/frontend/type_checking/constraints/deriving.rb +2 -1
- data/lib/jade/frontend/type_checking/error/port_not_encodable.rb +38 -0
- data/lib/jade/frontend/type_checking/port_resolution.rb +64 -16
- data/lib/jade/interop/boundary.rb +2 -3
- data/lib/jade/interop/runtime.rb +10 -2
- data/lib/jade/lsp/converters.rb +2 -15
- data/lib/jade/lsp/snippets.rb +21 -3
- data/lib/jade/parsing/error.rb +19 -0
- data/lib/jade/runtime.rb +1 -0
- data/lib/jade/signature.rb +47 -0
- data/lib/jade/stdlib/calendar.rb +20 -19
- data/lib/jade/stdlib/clock.rb +7 -13
- data/lib/jade/stdlib/debug.rb +12 -0
- data/lib/jade/stdlib/decimal.rb +3 -16
- data/lib/jade/stdlib/decode.rb +57 -12
- data/lib/jade/stdlib/encode.rb +26 -0
- data/lib/jade/stdlib/intrinsics.rb +12 -1
- data/lib/jade/stdlib/result.rb +1 -1
- data/lib/jade/stdlib/show.rb +40 -0
- data/lib/jade/stdlib/text.rb +131 -0
- data/lib/jade/stdlib.rb +5 -2
- data/lib/jade/symbol/interop_function.rb +3 -1
- data/lib/jade/symbol.rb +2 -2
- data/lib/jade/task.rb +2 -3
- data/lib/jade/version.rb +1 -1
- metadata +19 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f1bbeb934a5bbbeb19cfeddeb18f59798c37380a34d514110f42e07dab206525
|
|
4
|
+
data.tar.gz: 1f68a227a953fa57f875274f409bf93aa7269bbadfc4b3abc7634ea1d6b283c2
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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.
|
|
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
|
|
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
|
|
160
|
-
|
|
159
|
+
interface Labelled(a) with
|
|
160
|
+
label : a -> String
|
|
161
161
|
end
|
|
162
162
|
|
|
163
163
|
|
|
164
|
-
implements
|
|
165
|
-
|
|
164
|
+
implements Labelled(Person) with
|
|
165
|
+
label: (p) -> { p.name ++ " (" ++ String.from_int(p.age) ++ ")" }
|
|
166
166
|
end
|
|
167
167
|
|
|
168
168
|
|
|
169
|
-
def
|
|
170
|
-
|
|
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
|
|
361
|
-
compile inline rather than through a runtime
|
|
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`
|
|
378
|
-
|
|
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
|