@elaraai/east 1.0.3 → 1.0.5

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 (57) hide show
  1. package/README.md +3 -2
  2. package/dist/src/ast_to_ir.coerce_to.spec.js +7 -7
  3. package/dist/src/ast_to_ir.coerce_to.spec.js.map +1 -1
  4. package/dist/src/ast_to_ir.d.ts.map +1 -1
  5. package/dist/src/ast_to_ir.js +13 -68
  6. package/dist/src/ast_to_ir.js.map +1 -1
  7. package/dist/src/builtins.js +2 -2
  8. package/dist/src/builtins.js.map +1 -1
  9. package/dist/src/comparison.js +2 -2
  10. package/dist/src/comparison.js.map +1 -1
  11. package/dist/src/compile.d.ts.map +1 -1
  12. package/dist/src/compile.js +10 -14
  13. package/dist/src/compile.js.map +1 -1
  14. package/dist/src/expr/ast.d.ts.map +1 -1
  15. package/dist/src/expr/ast.js +5 -4
  16. package/dist/src/expr/ast.js.map +1 -1
  17. package/dist/src/expr/asyncfunction.d.ts.map +1 -1
  18. package/dist/src/expr/asyncfunction.js +7 -3
  19. package/dist/src/expr/asyncfunction.js.map +1 -1
  20. package/dist/src/expr/block.d.ts.map +1 -1
  21. package/dist/src/expr/block.js +5 -4
  22. package/dist/src/expr/block.js.map +1 -1
  23. package/dist/src/expr/function.d.ts.map +1 -1
  24. package/dist/src/expr/function.js +7 -3
  25. package/dist/src/expr/function.js.map +1 -1
  26. package/dist/src/expr/matrix.d.ts +7 -8
  27. package/dist/src/expr/matrix.d.ts.map +1 -1
  28. package/dist/src/expr/matrix.js +8 -8
  29. package/dist/src/expr/matrix.js.map +1 -1
  30. package/dist/src/expr/vector.d.ts +8 -9
  31. package/dist/src/expr/vector.d.ts.map +1 -1
  32. package/dist/src/expr/vector.js +9 -9
  33. package/dist/src/expr/vector.js.map +1 -1
  34. package/dist/src/index.d.ts +1 -0
  35. package/dist/src/index.d.ts.map +1 -1
  36. package/dist/src/index.js +1 -0
  37. package/dist/src/index.js.map +1 -1
  38. package/dist/src/internal.d.ts +1 -0
  39. package/dist/src/internal.d.ts.map +1 -1
  40. package/dist/src/internal.js +1 -0
  41. package/dist/src/internal.js.map +1 -1
  42. package/dist/src/location.js +2 -2
  43. package/dist/src/location.js.map +1 -1
  44. package/dist/src/serialization/binary-utils.d.ts.map +1 -1
  45. package/dist/src/serialization/binary-utils.js +29 -42
  46. package/dist/src/serialization/binary-utils.js.map +1 -1
  47. package/dist/src/type_diff.d.ts +218 -0
  48. package/dist/src/type_diff.d.ts.map +1 -0
  49. package/dist/src/type_diff.js +434 -0
  50. package/dist/src/type_diff.js.map +1 -0
  51. package/dist/src/types.d.ts +18 -3
  52. package/dist/src/types.d.ts.map +1 -1
  53. package/dist/src/types.js +31 -5
  54. package/dist/src/types.js.map +1 -1
  55. package/dist/src/types.spec.js +14 -1
  56. package/dist/src/types.spec.js.map +1 -1
  57. package/package.json +1 -1
@@ -0,0 +1,218 @@
1
+ /**
2
+ * Copyright (c) 2025 Elara AI Pty Ltd
3
+ * Dual-licensed under AGPL-3.0 and commercial license. See LICENSE for details.
4
+ */
5
+ import { type EastType, TypeMismatchError } from "./types.js";
6
+ import { type EastTypeValue } from "./type_of_type.js";
7
+ import { type Location } from "./location.js";
8
+ /**
9
+ * One step from a root type down to the location of a {@link TypeDiff}.
10
+ *
11
+ * @remarks
12
+ * A path is read left-to-right from the root. {@link renderTypePath} turns a
13
+ * path into a string like `.binding.patch` or `.write(arg 0)`.
14
+ */
15
+ export type TypePathSegment =
16
+ /** Into struct field `name`. */
17
+ {
18
+ readonly kind: "field";
19
+ readonly name: string;
20
+ }
21
+ /** Into the payload of variant case `name`. */
22
+ | {
23
+ readonly kind: "case";
24
+ readonly name: string;
25
+ }
26
+ /** Into the element of an `Array` / `Set` / `Vector` / `Matrix`. */
27
+ | {
28
+ readonly kind: "element";
29
+ }
30
+ /** Into the key of a `Dict`. */
31
+ | {
32
+ readonly kind: "key";
33
+ }
34
+ /** Into the value of a `Dict` or the referent of a `Ref`. */
35
+ | {
36
+ readonly kind: "value";
37
+ }
38
+ /** Into the function input at position `index`. */
39
+ | {
40
+ readonly kind: "input";
41
+ readonly index: number;
42
+ }
43
+ /** Into the function output. */
44
+ | {
45
+ readonly kind: "output";
46
+ };
47
+ /** A location inside a type, as a sequence of {@link TypePathSegment}s from the root. */
48
+ export type TypePath = readonly TypePathSegment[];
49
+ /**
50
+ * A single point of incompatibility between an `actual` and an `expected` type.
51
+ *
52
+ * @remarks
53
+ * Produced by {@link diffTypes} / {@link diffTypeValues}. Every record carries
54
+ * the {@link TypePath} where the incompatibility was found and the two subtypes
55
+ * at that location. The `kind` discriminant determines how `actual`/`expected`
56
+ * should be read — for the structural kinds they carry the field/case type that
57
+ * is the subject of the diff (see each variant):
58
+ *
59
+ * - `constructor` — different type constructors (e.g. `.Struct` vs `.Integer`,
60
+ * `.Array` vs `.Set`, sync `.Function` where async was required). `actual` and
61
+ * `expected` are the two whole subtypes.
62
+ * - `primitive` — both sides are primitive leaves but differ (e.g. `.Integer` vs
63
+ * `.Float`). `actual`/`expected` are those leaves.
64
+ * - `missing-field` — `expected` is a struct field absent from the actual struct;
65
+ * `actual` is the actual struct (context). `name` is the field.
66
+ * - `extra-field` — `actual` is a struct field absent from the expected struct;
67
+ * `expected` is the expected struct (context). `name` is the field.
68
+ * - `field-order` — both structs have the same shared fields but in a different
69
+ * order (struct field order is significant in East). `actual`/`expected` are
70
+ * the structs; `actualOrder`/`expectedOrder` list the shared names.
71
+ * - `missing-case` — `expected` is a variant case payload required by the
72
+ * expected type but absent from the actual variant; `actual` is the actual
73
+ * variant (context).
74
+ * - `extra-case` — `actual` is a variant case payload present in the actual type
75
+ * but not accepted by the expected variant; `expected` is the expected variant.
76
+ * - `arity` — function input counts differ; `actual`/`expected` are the function
77
+ * types and `actualCount`/`expectedCount` the input counts.
78
+ */
79
+ export type TypeDiff = {
80
+ readonly path: TypePath;
81
+ readonly actual: EastTypeValue;
82
+ readonly expected: EastTypeValue;
83
+ } & ({
84
+ readonly kind: "constructor";
85
+ } | {
86
+ readonly kind: "primitive";
87
+ } | {
88
+ readonly kind: "missing-field";
89
+ readonly name: string;
90
+ } | {
91
+ readonly kind: "extra-field";
92
+ readonly name: string;
93
+ } | {
94
+ readonly kind: "field-order";
95
+ readonly actualOrder: readonly string[];
96
+ readonly expectedOrder: readonly string[];
97
+ } | {
98
+ readonly kind: "missing-case";
99
+ readonly name: string;
100
+ } | {
101
+ readonly kind: "extra-case";
102
+ readonly name: string;
103
+ } | {
104
+ readonly kind: "arity";
105
+ readonly actualCount: number;
106
+ readonly expectedCount: number;
107
+ });
108
+ /** Options for {@link diffTypes} / {@link diffTypeValues}. */
109
+ export interface DiffTypesOptions {
110
+ /** Stop after this many records, to bound work on pathologically large or
111
+ * deeply divergent types. Defaults to 64. */
112
+ readonly maxDiffs?: number;
113
+ }
114
+ /**
115
+ * Compute the structural differences that make `actual` not assignable to
116
+ * `expected`, as a list of {@link TypeDiff} records.
117
+ *
118
+ * @param actual - The type that was supplied.
119
+ * @param expected - The type that was required.
120
+ * @param options - See {@link DiffTypesOptions}.
121
+ * @returns One record per incompatible location. Empty when `actual` is
122
+ * assignable to `expected` (a subtype of it).
123
+ *
124
+ * @remarks
125
+ * The walk follows East's assignability rules — struct fields covariant with a
126
+ * fixed field set, variant width subtyping, contravariant function inputs and
127
+ * covariant outputs, invariant mutable-container contents. Subtrees that are
128
+ * already compatible (including `OptionType<T>` against its `Variant{none, some}`
129
+ * form) are pruned, so the result pinpoints only what actually differs rather
130
+ * than restating the whole type. Use {@link renderTypeDiff} to format the result.
131
+ */
132
+ export declare function diffTypeValues(actual: EastTypeValue, expected: EastTypeValue, options?: DiffTypesOptions): TypeDiff[];
133
+ /**
134
+ * {@link EastType} entry point for {@link diffTypeValues}.
135
+ *
136
+ * @param actual - The type that was supplied.
137
+ * @param expected - The type that was required.
138
+ * @param options - See {@link DiffTypesOptions}.
139
+ * @returns One {@link TypeDiff} per incompatible location; empty when `actual`
140
+ * is assignable to `expected`.
141
+ *
142
+ * @remarks
143
+ * Converts both types with {@link toEastTypeValue} (which interns them, enabling
144
+ * the identity fast paths) and defers to {@link diffTypeValues}.
145
+ */
146
+ export declare function diffTypes(actual: EastType, expected: EastType, options?: DiffTypesOptions): TypeDiff[];
147
+ /**
148
+ * A compact, truncated string for an {@link EastTypeValue}, for use in diff
149
+ * messages.
150
+ *
151
+ * @param type - The type value to summarize.
152
+ * @param maxDepth - How many levels of compound types to expand before
153
+ * collapsing to an ellipsis. Defaults to 1.
154
+ * @param maxMembers - How many struct fields / variant cases to list before
155
+ * `…+N`. Defaults to 4.
156
+ * @returns A one-line summary such as `.Struct {nodes, links, metadata}` or
157
+ * `.Function (.Integer) => .Null`.
158
+ *
159
+ * @remarks
160
+ * The {@link EastTypeValue} sibling of `printTypeSummary`; it stays terse
161
+ * (struct/variant members are listed by name) because the {@link TypePath} on a
162
+ * {@link TypeDiff} already carries the precise location.
163
+ */
164
+ export declare function printTypeValueSummary(type: EastTypeValue, maxDepth?: number, maxMembers?: number): string;
165
+ /**
166
+ * Render a {@link TypePath} as a readable location string.
167
+ *
168
+ * @param path - The path to render.
169
+ * @returns e.g. `.binding.patch`, `.write(arg 0)`, `.items[]`, or `(root)` for
170
+ * the empty path.
171
+ */
172
+ export declare function renderTypePath(path: TypePath): string;
173
+ /** Options for {@link renderTypeDiff}. */
174
+ export interface RenderTypeDiffOptions {
175
+ /** Maximum number of diff lines to show before `…and N more`. Defaults to 6. */
176
+ readonly maxShown?: number;
177
+ /** `maxDepth` passed to {@link printTypeValueSummary}. Defaults to 1. */
178
+ readonly maxDepth?: number;
179
+ /** `maxMembers` passed to {@link printTypeValueSummary}. Defaults to 4. */
180
+ readonly maxMembers?: number;
181
+ }
182
+ /**
183
+ * Format a list of {@link TypeDiff} records as a concise, multi-line message.
184
+ *
185
+ * @param diffs - The diffs from {@link diffTypes} / {@link diffTypeValues}.
186
+ * @param options - See {@link RenderTypeDiffOptions}.
187
+ * @returns A message with one line per location (deepest first), or `""` when
188
+ * there are no diffs.
189
+ *
190
+ * @remarks
191
+ * When every diff is a leaf mismatch sharing the same actual/expected pair (the
192
+ * common case where one wrong inner type shows up at several call sites), a
193
+ * single summary line is emitted ahead of the per-location lines.
194
+ */
195
+ export declare function renderTypeDiff(diffs: readonly TypeDiff[], options?: RenderTypeDiffOptions): string;
196
+ /** Options for {@link typeMismatchError}. */
197
+ export interface TypeMismatchErrorOptions {
198
+ /** Resolved source frames. Takes precedence over `loc_id`. */
199
+ readonly location?: Location[];
200
+ /** A source-map id, resolved into frames via the ambient source map (the one
201
+ * established with `with_source_map` while building IR). Used by callers like
202
+ * `coerce_to` that hold the id but not the map. */
203
+ readonly loc_id?: bigint;
204
+ /** Forwarded to {@link renderTypeDiff} when building the message. */
205
+ readonly render?: RenderTypeDiffOptions;
206
+ }
207
+ /**
208
+ * Build a {@link TypeMismatchError} for an assignability failure, populating its
209
+ * rendered message and structured `diffs`/`actual`/`expected` from
210
+ * {@link diffTypes}, and resolving frames from `loc_id` via the ambient source
211
+ * map when no explicit `location` is given.
212
+ *
213
+ * @remarks
214
+ * The diff is computed here (not in the error class) so {@link types.TypeMismatchError}
215
+ * stays free of a dependency on the diff machinery.
216
+ */
217
+ export declare function typeMismatchError(actual: EastType, expected: EastType, options?: TypeMismatchErrorOptions): TypeMismatchError;
218
+ //# sourceMappingURL=type_diff.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"type_diff.d.ts","sourceRoot":"","sources":["../../src/type_diff.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,434 @@
1
+ /**
2
+ * Copyright (c) 2025 Elara AI Pty Ltd
3
+ * Dual-licensed under AGPL-3.0 and commercial license. See LICENSE for details.
4
+ */
5
+ import { TypeMismatchError } from "./types.js";
6
+ import { toEastTypeValue, isTypeValueEqual, isSubtypeValue } from "./type_of_type.js";
7
+ import { get_current_source_map } from "./location.js";
8
+ const PRIMITIVE_TAGS = new Set([
9
+ "Never", "Null", "Boolean", "Integer", "Float", "String", "DateTime", "Blob",
10
+ ]);
11
+ function flip(v) {
12
+ return v === "co" ? "contra" : v === "contra" ? "co" : "inv";
13
+ }
14
+ // Already-compatible subtrees produce no diff. Reusing the canonical relations
15
+ // means everything East already considers equivalent — Option ≡ Variant{none,some},
16
+ // variant width subtyping, recursive alpha-equivalence — is pruned for free, and
17
+ // we only ever descend into a genuinely incompatible subtree to localize it.
18
+ function compatible(a, b, v) {
19
+ if (v === "inv")
20
+ return isTypeValueEqual(a, b);
21
+ if (v === "co")
22
+ return isSubtypeValue(a, b);
23
+ return isSubtypeValue(b, a);
24
+ }
25
+ function isFunctionLike(t) {
26
+ return t.type === "Function" || t.type === "AsyncFunction";
27
+ }
28
+ // Recursive bodies are compared at most once per id pair (see the assumption
29
+ // set in `walk`); this only bounds malformed/non-terminating input.
30
+ const MAX_PATH_DEPTH = 256;
31
+ function walk(a, b, v, ctx) {
32
+ if (ctx.diffs.length >= ctx.maxDiffs)
33
+ return;
34
+ if (a === b)
35
+ return;
36
+ // Absolute backstop against pathological depth; recursion is bounded by the
37
+ // assumption set below, this only guards malformed input.
38
+ if (ctx.path.length > MAX_PATH_DEPTH)
39
+ return;
40
+ if (compatible(a, b, v))
41
+ return;
42
+ if (a.type === "Recursive" || b.type === "Recursive") {
43
+ const aRef = a.type === "Recursive" && a.value.type === "ref" ? a.value.value : undefined;
44
+ const bRef = b.type === "Recursive" && b.value.type === "ref" ? b.value.value : undefined;
45
+ // Two back-references: equal recursive ids, or a pair we are already
46
+ // unfolding (the coinductive hypothesis), are aligned — not a difference.
47
+ if (aRef !== undefined && bRef !== undefined) {
48
+ if (aRef === bRef || ctx.assumed.has(`${aRef}:${bRef}`))
49
+ return;
50
+ ctx.diffs.push({ kind: "constructor", path: ctx.path.slice(), actual: a, expected: b });
51
+ return;
52
+ }
53
+ // Two wrappers: assume their ids correspond, then compare the bodies once.
54
+ // The assumption makes the paired back-references (handled above) align, so
55
+ // we report only where the unfolded structures actually diverge.
56
+ if (a.type === "Recursive" && a.value.type === "wrapper" && b.type === "Recursive" && b.value.type === "wrapper") {
57
+ const key = `${a.value.value.id}:${b.value.value.id}`;
58
+ if (ctx.assumed.has(key))
59
+ return;
60
+ ctx.assumed.add(key);
61
+ walk(a.value.value.inner, b.value.value.inner, v, ctx);
62
+ return;
63
+ }
64
+ // One wrapper against a concrete type: unfold and compare its head.
65
+ if (a.type === "Recursive" && a.value.type === "wrapper") {
66
+ walk(a.value.value.inner, b, v, ctx);
67
+ return;
68
+ }
69
+ if (b.type === "Recursive" && b.value.type === "wrapper") {
70
+ walk(a, b.value.value.inner, v, ctx);
71
+ return;
72
+ }
73
+ // A bare back-reference against a non-recursive type.
74
+ ctx.diffs.push({ kind: "constructor", path: ctx.path.slice(), actual: a, expected: b });
75
+ return;
76
+ }
77
+ const sameFunctionFamily = isFunctionLike(a) && isFunctionLike(b);
78
+ if (a.type !== b.type && !sameFunctionFamily) {
79
+ const kind = PRIMITIVE_TAGS.has(a.type) && PRIMITIVE_TAGS.has(b.type) ? "primitive" : "constructor";
80
+ ctx.diffs.push({ kind, path: ctx.path.slice(), actual: a, expected: b });
81
+ return;
82
+ }
83
+ switch (a.type) {
84
+ case "Ref": {
85
+ ctx.path.push({ kind: "value" });
86
+ walk(a.value, b.value, "inv", ctx);
87
+ ctx.path.pop();
88
+ return;
89
+ }
90
+ case "Array":
91
+ case "Set":
92
+ case "Vector":
93
+ case "Matrix": {
94
+ ctx.path.push({ kind: "element" });
95
+ walk(a.value, b.value, "inv", ctx);
96
+ ctx.path.pop();
97
+ return;
98
+ }
99
+ case "Dict": {
100
+ const bv = b.value;
101
+ ctx.path.push({ kind: "key" });
102
+ walk(a.value.key, bv.key, "inv", ctx);
103
+ ctx.path.pop();
104
+ ctx.path.push({ kind: "value" });
105
+ walk(a.value.value, bv.value, "inv", ctx);
106
+ ctx.path.pop();
107
+ return;
108
+ }
109
+ case "Struct": {
110
+ diffStruct(a.value, b.value, a, b, v, ctx);
111
+ return;
112
+ }
113
+ case "Variant": {
114
+ diffVariant(a.value, b.value, a, b, v, ctx);
115
+ return;
116
+ }
117
+ case "Function":
118
+ case "AsyncFunction": {
119
+ diffFunction(a, b, v, ctx);
120
+ return;
121
+ }
122
+ // Equal primitives are compatible (returned above) and unequal ones are a
123
+ // head mismatch (handled above), so a primitive cannot reach here. Listed
124
+ // for exhaustiveness (Recursive is already excluded by the branch above).
125
+ case "Never":
126
+ case "Null":
127
+ case "Boolean":
128
+ case "Integer":
129
+ case "Float":
130
+ case "String":
131
+ case "DateTime":
132
+ case "Blob":
133
+ return;
134
+ default:
135
+ throw new Error(`Unhandled type in type diff: ${a.type}`);
136
+ }
137
+ }
138
+ function diffStruct(af, bf, a, b, v, ctx) {
139
+ const aNames = af.map((f) => f.name);
140
+ const bByName = new Map(bf.map((f) => [f.name, f.type]));
141
+ const aSet = new Set(aNames);
142
+ for (const f of bf) {
143
+ if (!aSet.has(f.name))
144
+ ctx.diffs.push({ kind: "missing-field", name: f.name, path: ctx.path.slice(), actual: a, expected: f.type });
145
+ }
146
+ for (const f of af) {
147
+ if (!bByName.has(f.name))
148
+ ctx.diffs.push({ kind: "extra-field", name: f.name, path: ctx.path.slice(), actual: f.type, expected: b });
149
+ }
150
+ const sharedA = aNames.filter((n) => bByName.has(n));
151
+ const sharedB = bf.map((f) => f.name).filter((n) => aSet.has(n));
152
+ if (sharedA.length === sharedB.length && sharedA.some((n, i) => n !== sharedB[i])) {
153
+ ctx.diffs.push({ kind: "field-order", actualOrder: sharedA, expectedOrder: sharedB, path: ctx.path.slice(), actual: a, expected: b });
154
+ }
155
+ for (const f of af) {
156
+ const bt = bByName.get(f.name);
157
+ if (bt === undefined)
158
+ continue;
159
+ ctx.path.push({ kind: "field", name: f.name });
160
+ walk(f.type, bt, v, ctx);
161
+ ctx.path.pop();
162
+ }
163
+ }
164
+ function diffVariant(ac, bc, a, b, v, ctx) {
165
+ const aByName = new Map(ac.map((c) => [c.name, c.type]));
166
+ const bByName = new Map(bc.map((c) => [c.name, c.type]));
167
+ // Under covariance the actual case set must be a subset of the expected one
168
+ // (width subtyping), so a case only in `actual` is the error; under
169
+ // contravariance the requirement flips; invariance demands the sets match.
170
+ if (v === "co" || v === "inv") {
171
+ for (const c of ac) {
172
+ if (!bByName.has(c.name))
173
+ ctx.diffs.push({ kind: "extra-case", name: c.name, path: ctx.path.slice(), actual: c.type, expected: b });
174
+ }
175
+ }
176
+ if (v === "contra" || v === "inv") {
177
+ for (const c of bc) {
178
+ if (!aByName.has(c.name))
179
+ ctx.diffs.push({ kind: "missing-case", name: c.name, path: ctx.path.slice(), actual: a, expected: c.type });
180
+ }
181
+ }
182
+ for (const c of ac) {
183
+ const bt = bByName.get(c.name);
184
+ if (bt === undefined)
185
+ continue;
186
+ ctx.path.push({ kind: "case", name: c.name });
187
+ walk(c.type, bt, v, ctx);
188
+ ctx.path.pop();
189
+ }
190
+ }
191
+ function diffFunction(a, b, v, ctx) {
192
+ const av = a.value;
193
+ const bv = b.value;
194
+ // A sync Function is assignable to an async one but not vice versa.
195
+ if (a.type !== b.type) {
196
+ const syncRequiredButAsyncGiven = (v === "co" && a.type === "AsyncFunction") ||
197
+ (v === "contra" && b.type === "AsyncFunction") ||
198
+ v === "inv";
199
+ if (syncRequiredButAsyncGiven)
200
+ ctx.diffs.push({ kind: "constructor", path: ctx.path.slice(), actual: a, expected: b });
201
+ }
202
+ if (av.inputs.length !== bv.inputs.length) {
203
+ ctx.diffs.push({ kind: "arity", actualCount: av.inputs.length, expectedCount: bv.inputs.length, path: ctx.path.slice(), actual: a, expected: b });
204
+ }
205
+ else {
206
+ for (let i = 0; i < av.inputs.length; i++) {
207
+ ctx.path.push({ kind: "input", index: i });
208
+ walk(av.inputs[i], bv.inputs[i], flip(v), ctx);
209
+ ctx.path.pop();
210
+ }
211
+ }
212
+ ctx.path.push({ kind: "output" });
213
+ walk(av.output, bv.output, v, ctx);
214
+ ctx.path.pop();
215
+ }
216
+ /**
217
+ * Compute the structural differences that make `actual` not assignable to
218
+ * `expected`, as a list of {@link TypeDiff} records.
219
+ *
220
+ * @param actual - The type that was supplied.
221
+ * @param expected - The type that was required.
222
+ * @param options - See {@link DiffTypesOptions}.
223
+ * @returns One record per incompatible location. Empty when `actual` is
224
+ * assignable to `expected` (a subtype of it).
225
+ *
226
+ * @remarks
227
+ * The walk follows East's assignability rules — struct fields covariant with a
228
+ * fixed field set, variant width subtyping, contravariant function inputs and
229
+ * covariant outputs, invariant mutable-container contents. Subtrees that are
230
+ * already compatible (including `OptionType<T>` against its `Variant{none, some}`
231
+ * form) are pruned, so the result pinpoints only what actually differs rather
232
+ * than restating the whole type. Use {@link renderTypeDiff} to format the result.
233
+ */
234
+ export function diffTypeValues(actual, expected, options = {}) {
235
+ const ctx = { diffs: [], path: [], assumed: new Set(), maxDiffs: options.maxDiffs ?? 64 };
236
+ walk(actual, expected, "co", ctx);
237
+ return ctx.diffs;
238
+ }
239
+ /**
240
+ * {@link EastType} entry point for {@link diffTypeValues}.
241
+ *
242
+ * @param actual - The type that was supplied.
243
+ * @param expected - The type that was required.
244
+ * @param options - See {@link DiffTypesOptions}.
245
+ * @returns One {@link TypeDiff} per incompatible location; empty when `actual`
246
+ * is assignable to `expected`.
247
+ *
248
+ * @remarks
249
+ * Converts both types with {@link toEastTypeValue} (which interns them, enabling
250
+ * the identity fast paths) and defers to {@link diffTypeValues}.
251
+ */
252
+ export function diffTypes(actual, expected, options = {}) {
253
+ return diffTypeValues(toEastTypeValue(actual), toEastTypeValue(expected), options);
254
+ }
255
+ /**
256
+ * A compact, truncated string for an {@link EastTypeValue}, for use in diff
257
+ * messages.
258
+ *
259
+ * @param type - The type value to summarize.
260
+ * @param maxDepth - How many levels of compound types to expand before
261
+ * collapsing to an ellipsis. Defaults to 1.
262
+ * @param maxMembers - How many struct fields / variant cases to list before
263
+ * `…+N`. Defaults to 4.
264
+ * @returns A one-line summary such as `.Struct {nodes, links, metadata}` or
265
+ * `.Function (.Integer) => .Null`.
266
+ *
267
+ * @remarks
268
+ * The {@link EastTypeValue} sibling of `printTypeSummary`; it stays terse
269
+ * (struct/variant members are listed by name) because the {@link TypePath} on a
270
+ * {@link TypeDiff} already carries the precise location.
271
+ */
272
+ export function printTypeValueSummary(type, maxDepth = 1, maxMembers = 4) {
273
+ switch (type.type) {
274
+ case "Never": return ".Never";
275
+ case "Null": return ".Null";
276
+ case "Boolean": return ".Boolean";
277
+ case "Integer": return ".Integer";
278
+ case "Float": return ".Float";
279
+ case "String": return ".String";
280
+ case "DateTime": return ".DateTime";
281
+ case "Blob": return ".Blob";
282
+ case "Ref": return maxDepth <= 0 ? ".Ref …" : `.Ref ${printTypeValueSummary(type.value, maxDepth - 1, maxMembers)}`;
283
+ case "Array": return maxDepth <= 0 ? ".Array …" : `.Array ${printTypeValueSummary(type.value, maxDepth - 1, maxMembers)}`;
284
+ case "Set": return maxDepth <= 0 ? ".Set …" : `.Set ${printTypeValueSummary(type.value, maxDepth - 1, maxMembers)}`;
285
+ case "Vector": return maxDepth <= 0 ? ".Vector …" : `.Vector ${printTypeValueSummary(type.value, maxDepth - 1, maxMembers)}`;
286
+ case "Matrix": return maxDepth <= 0 ? ".Matrix …" : `.Matrix ${printTypeValueSummary(type.value, maxDepth - 1, maxMembers)}`;
287
+ case "Dict":
288
+ return maxDepth <= 0
289
+ ? ".Dict …"
290
+ : `.Dict (key=${printTypeValueSummary(type.value.key, maxDepth - 1, maxMembers)}, value=${printTypeValueSummary(type.value.value, maxDepth - 1, maxMembers)})`;
291
+ case "Struct": {
292
+ const names = type.value.map((f) => f.name);
293
+ return `.Struct {${truncateMembers(names, maxMembers)}}`;
294
+ }
295
+ case "Variant": {
296
+ const names = type.value.map((c) => c.name);
297
+ return `.Variant (${truncateMembers(names, maxMembers).split(", ").join(" | ")})`;
298
+ }
299
+ case "Function":
300
+ case "AsyncFunction": {
301
+ const head = type.type === "AsyncFunction" ? ".AsyncFunction" : ".Function";
302
+ if (maxDepth <= 0)
303
+ return `${head} …`;
304
+ const inputs = type.value.inputs.map((i) => printTypeValueSummary(i, maxDepth - 1, maxMembers)).join(", ");
305
+ return `${head} (${inputs}) => ${printTypeValueSummary(type.value.output, maxDepth - 1, maxMembers)}`;
306
+ }
307
+ case "Recursive":
308
+ return type.value.type === "ref"
309
+ ? `.Recursive ref(${type.value.value})`
310
+ : maxDepth <= 0 ? ".Recursive …" : `.Recursive ${printTypeValueSummary(type.value.value.inner, maxDepth, maxMembers)}`;
311
+ default:
312
+ throw new Error(`Unhandled type in type summary: ${type.type}`);
313
+ }
314
+ }
315
+ function truncateMembers(names, maxMembers) {
316
+ if (names.length <= maxMembers)
317
+ return names.join(", ");
318
+ return `${names.slice(0, maxMembers).join(", ")}, …+${names.length - maxMembers}`;
319
+ }
320
+ /**
321
+ * Render a {@link TypePath} as a readable location string.
322
+ *
323
+ * @param path - The path to render.
324
+ * @returns e.g. `.binding.patch`, `.write(arg 0)`, `.items[]`, or `(root)` for
325
+ * the empty path.
326
+ */
327
+ export function renderTypePath(path) {
328
+ if (path.length === 0)
329
+ return "(root)";
330
+ let out = "";
331
+ for (const seg of path) {
332
+ switch (seg.kind) {
333
+ case "field":
334
+ out += `.${seg.name}`;
335
+ break;
336
+ case "case":
337
+ out += `:${seg.name}`;
338
+ break;
339
+ // Collection traversals read as the element/key/value *type* (East
340
+ // collections are homogeneous), not a particular index/entry.
341
+ case "element":
342
+ out += "[element]";
343
+ break;
344
+ case "key":
345
+ out += "[key]";
346
+ break;
347
+ case "value":
348
+ out += "[value]";
349
+ break;
350
+ case "input":
351
+ out += `(arg ${seg.index})`;
352
+ break;
353
+ case "output":
354
+ out += "(ret)";
355
+ break;
356
+ }
357
+ }
358
+ return out;
359
+ }
360
+ function phrase(d, summarize) {
361
+ switch (d.kind) {
362
+ case "constructor":
363
+ case "primitive":
364
+ return `expected ${summarize(d.expected)}, found ${summarize(d.actual)}`;
365
+ case "missing-field":
366
+ return `missing field "${d.name}": ${summarize(d.expected)}`;
367
+ case "extra-field":
368
+ return `unexpected field "${d.name}": ${summarize(d.actual)}`;
369
+ case "field-order":
370
+ return `field order differs (expected ${d.expectedOrder.join(", ")}; found ${d.actualOrder.join(", ")})`;
371
+ case "missing-case":
372
+ return `missing variant case "${d.name}": ${summarize(d.expected)}`;
373
+ case "extra-case":
374
+ return `unexpected variant case "${d.name}": ${summarize(d.actual)}`;
375
+ case "arity":
376
+ return `expected ${d.expectedCount} argument${d.expectedCount === 1 ? "" : "s"}, found ${d.actualCount}`;
377
+ }
378
+ }
379
+ /**
380
+ * Format a list of {@link TypeDiff} records as a concise, multi-line message.
381
+ *
382
+ * @param diffs - The diffs from {@link diffTypes} / {@link diffTypeValues}.
383
+ * @param options - See {@link RenderTypeDiffOptions}.
384
+ * @returns A message with one line per location (deepest first), or `""` when
385
+ * there are no diffs.
386
+ *
387
+ * @remarks
388
+ * When every diff is a leaf mismatch sharing the same actual/expected pair (the
389
+ * common case where one wrong inner type shows up at several call sites), a
390
+ * single summary line is emitted ahead of the per-location lines.
391
+ */
392
+ export function renderTypeDiff(diffs, options = {}) {
393
+ if (diffs.length === 0)
394
+ return "";
395
+ const maxShown = options.maxShown ?? 6;
396
+ const summarize = (t) => printTypeValueSummary(t, options.maxDepth ?? 1, options.maxMembers ?? 4);
397
+ const lines = [];
398
+ const allLeaves = diffs.every((d) => d.kind === "constructor" || d.kind === "primitive");
399
+ if (allLeaves && diffs.length >= 2) {
400
+ const pairs = new Set(diffs.map((d) => `${summarize(d.actual)}${summarize(d.expected)}`));
401
+ if (pairs.size === 1) {
402
+ const d0 = diffs[0];
403
+ lines.push(`all ${diffs.length} differences: found ${summarize(d0.actual)} where ${summarize(d0.expected)} was expected`);
404
+ }
405
+ }
406
+ const sorted = [...diffs].sort((x, y) => y.path.length - x.path.length);
407
+ for (const d of sorted.slice(0, maxShown)) {
408
+ lines.push(`${renderTypePath(d.path)}: ${phrase(d, summarize)}`);
409
+ }
410
+ if (sorted.length > maxShown)
411
+ lines.push(`…and ${sorted.length - maxShown} more`);
412
+ return lines.join("\n");
413
+ }
414
+ /**
415
+ * Build a {@link TypeMismatchError} for an assignability failure, populating its
416
+ * rendered message and structured `diffs`/`actual`/`expected` from
417
+ * {@link diffTypes}, and resolving frames from `loc_id` via the ambient source
418
+ * map when no explicit `location` is given.
419
+ *
420
+ * @remarks
421
+ * The diff is computed here (not in the error class) so {@link types.TypeMismatchError}
422
+ * stays free of a dependency on the diff machinery.
423
+ */
424
+ export function typeMismatchError(actual, expected, options = {}) {
425
+ const diffs = diffTypes(actual, expected);
426
+ const body = renderTypeDiff(diffs, options.render);
427
+ const detail = body.length > 0
428
+ ? body
429
+ : `${printTypeValueSummary(toEastTypeValue(actual))} is not assignable to ${printTypeValueSummary(toEastTypeValue(expected))}`;
430
+ const location = options.location
431
+ ?? (options.loc_id !== undefined ? [...(get_current_source_map()?.resolve(options.loc_id) ?? [])] : []);
432
+ return new TypeMismatchError(`East type mismatch:\n${detail}`, { actual, expected, diffs, location });
433
+ }
434
+ //# sourceMappingURL=type_diff.js.map