@elaraai/east 1.0.4 → 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.
- package/README.md +3 -2
- package/dist/src/ast_to_ir.coerce_to.spec.js +7 -7
- package/dist/src/ast_to_ir.coerce_to.spec.js.map +1 -1
- package/dist/src/ast_to_ir.d.ts.map +1 -1
- package/dist/src/ast_to_ir.js +13 -68
- package/dist/src/ast_to_ir.js.map +1 -1
- package/dist/src/builtins.js +2 -2
- package/dist/src/builtins.js.map +1 -1
- package/dist/src/comparison.js +2 -2
- package/dist/src/comparison.js.map +1 -1
- package/dist/src/compile.d.ts.map +1 -1
- package/dist/src/compile.js +10 -14
- package/dist/src/compile.js.map +1 -1
- package/dist/src/expr/ast.d.ts.map +1 -1
- package/dist/src/expr/ast.js +5 -4
- package/dist/src/expr/ast.js.map +1 -1
- package/dist/src/expr/asyncfunction.d.ts.map +1 -1
- package/dist/src/expr/asyncfunction.js +7 -3
- package/dist/src/expr/asyncfunction.js.map +1 -1
- package/dist/src/expr/block.d.ts.map +1 -1
- package/dist/src/expr/block.js +5 -4
- package/dist/src/expr/block.js.map +1 -1
- package/dist/src/expr/function.d.ts.map +1 -1
- package/dist/src/expr/function.js +7 -3
- package/dist/src/expr/function.js.map +1 -1
- package/dist/src/expr/matrix.d.ts +7 -8
- package/dist/src/expr/matrix.d.ts.map +1 -1
- package/dist/src/expr/matrix.js +8 -8
- package/dist/src/expr/matrix.js.map +1 -1
- package/dist/src/expr/vector.d.ts +8 -9
- package/dist/src/expr/vector.d.ts.map +1 -1
- package/dist/src/expr/vector.js +9 -9
- package/dist/src/expr/vector.js.map +1 -1
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +1 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/internal.d.ts +1 -0
- package/dist/src/internal.d.ts.map +1 -1
- package/dist/src/internal.js +1 -0
- package/dist/src/internal.js.map +1 -1
- package/dist/src/location.js +2 -2
- package/dist/src/location.js.map +1 -1
- package/dist/src/serialization/binary-utils.d.ts.map +1 -1
- package/dist/src/serialization/binary-utils.js +29 -42
- package/dist/src/serialization/binary-utils.js.map +1 -1
- package/dist/src/type_diff.d.ts +218 -0
- package/dist/src/type_diff.d.ts.map +1 -0
- package/dist/src/type_diff.js +434 -0
- package/dist/src/type_diff.js.map +1 -0
- package/dist/src/types.d.ts +18 -3
- package/dist/src/types.d.ts.map +1 -1
- package/dist/src/types.js +31 -5
- package/dist/src/types.js.map +1 -1
- package/dist/src/types.spec.js +14 -1
- package/dist/src/types.spec.js.map +1 -1
- 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
|