@velarscript/compiler 0.10.1
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/LICENSE +201 -0
- package/README.md +70 -0
- package/dist/analyzer.d.ts +1175 -0
- package/dist/analyzer.d.ts.map +1 -0
- package/dist/analyzer.js +12130 -0
- package/dist/analyzer.js.map +1 -0
- package/dist/application-package-host.d.ts +28 -0
- package/dist/application-package-host.d.ts.map +1 -0
- package/dist/application-package-host.js +2 -0
- package/dist/application-package-host.js.map +1 -0
- package/dist/ast.d.ts +853 -0
- package/dist/ast.d.ts.map +1 -0
- package/dist/ast.js +245 -0
- package/dist/ast.js.map +1 -0
- package/dist/class-runtime.d.ts +6 -0
- package/dist/class-runtime.d.ts.map +1 -0
- package/dist/class-runtime.js +45 -0
- package/dist/class-runtime.js.map +1 -0
- package/dist/collection-lowering-runtime.d.ts +7 -0
- package/dist/collection-lowering-runtime.d.ts.map +1 -0
- package/dist/collection-lowering-runtime.js +952 -0
- package/dist/collection-lowering-runtime.js.map +1 -0
- package/dist/collection-runtime.d.ts +16 -0
- package/dist/collection-runtime.d.ts.map +1 -0
- package/dist/collection-runtime.js +231 -0
- package/dist/collection-runtime.js.map +1 -0
- package/dist/core-vocabulary.d.ts +141 -0
- package/dist/core-vocabulary.d.ts.map +1 -0
- package/dist/core-vocabulary.js +88 -0
- package/dist/core-vocabulary.js.map +1 -0
- package/dist/diagnostic.d.ts +42 -0
- package/dist/diagnostic.d.ts.map +1 -0
- package/dist/diagnostic.js +66 -0
- package/dist/diagnostic.js.map +1 -0
- package/dist/embedded-javascript.d.ts +74 -0
- package/dist/embedded-javascript.d.ts.map +1 -0
- package/dist/embedded-javascript.js +341 -0
- package/dist/embedded-javascript.js.map +1 -0
- package/dist/embedded-module.d.ts +11 -0
- package/dist/embedded-module.d.ts.map +1 -0
- package/dist/embedded-module.js +67 -0
- package/dist/embedded-module.js.map +1 -0
- package/dist/embedded-source.d.ts +19 -0
- package/dist/embedded-source.d.ts.map +1 -0
- package/dist/embedded-source.js +61 -0
- package/dist/embedded-source.js.map +1 -0
- package/dist/emitter.d.ts +188 -0
- package/dist/emitter.d.ts.map +1 -0
- package/dist/emitter.js +3254 -0
- package/dist/emitter.js.map +1 -0
- package/dist/error-runtime.d.ts +17 -0
- package/dist/error-runtime.d.ts.map +1 -0
- package/dist/error-runtime.js +109 -0
- package/dist/error-runtime.js.map +1 -0
- package/dist/extension.d.ts +373 -0
- package/dist/extension.d.ts.map +1 -0
- package/dist/extension.js +25 -0
- package/dist/extension.js.map +1 -0
- package/dist/formatter.d.ts +13 -0
- package/dist/formatter.d.ts.map +1 -0
- package/dist/formatter.js +1440 -0
- package/dist/formatter.js.map +1 -0
- package/dist/framework-host.d.ts +87 -0
- package/dist/framework-host.d.ts.map +1 -0
- package/dist/framework-host.js +2 -0
- package/dist/framework-host.js.map +1 -0
- package/dist/index.d.ts +75 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +932 -0
- package/dist/index.js.map +1 -0
- package/dist/interpolated-string.d.ts +47 -0
- package/dist/interpolated-string.d.ts.map +1 -0
- package/dist/interpolated-string.js +344 -0
- package/dist/interpolated-string.js.map +1 -0
- package/dist/json-runtime.d.ts +2 -0
- package/dist/json-runtime.d.ts.map +1 -0
- package/dist/json-runtime.js +193 -0
- package/dist/json-runtime.js.map +1 -0
- package/dist/language-guidance.d.ts +31 -0
- package/dist/language-guidance.d.ts.map +1 -0
- package/dist/language-guidance.js +187 -0
- package/dist/language-guidance.js.map +1 -0
- package/dist/lexer.d.ts +85 -0
- package/dist/lexer.d.ts.map +1 -0
- package/dist/lexer.js +1004 -0
- package/dist/lexer.js.map +1 -0
- package/dist/limits.d.ts +2 -0
- package/dist/limits.d.ts.map +1 -0
- package/dist/limits.js +2 -0
- package/dist/limits.js.map +1 -0
- package/dist/mechanical-fix.d.ts +26 -0
- package/dist/mechanical-fix.d.ts.map +1 -0
- package/dist/mechanical-fix.js +61 -0
- package/dist/mechanical-fix.js.map +1 -0
- package/dist/narrowing-runtime.d.ts +5 -0
- package/dist/narrowing-runtime.d.ts.map +1 -0
- package/dist/narrowing-runtime.js +27 -0
- package/dist/narrowing-runtime.js.map +1 -0
- package/dist/number-runtime.d.ts +3 -0
- package/dist/number-runtime.d.ts.map +1 -0
- package/dist/number-runtime.js +37 -0
- package/dist/number-runtime.js.map +1 -0
- package/dist/parser.d.ts +245 -0
- package/dist/parser.d.ts.map +1 -0
- package/dist/parser.js +3582 -0
- package/dist/parser.js.map +1 -0
- package/dist/primitive-runtime.d.ts +4 -0
- package/dist/primitive-runtime.d.ts.map +1 -0
- package/dist/primitive-runtime.js +39 -0
- package/dist/primitive-runtime.js.map +1 -0
- package/dist/promise-runtime.d.ts +6 -0
- package/dist/promise-runtime.d.ts.map +1 -0
- package/dist/promise-runtime.js +86 -0
- package/dist/promise-runtime.js.map +1 -0
- package/dist/reactive-bridge-runtime.d.ts +7 -0
- package/dist/reactive-bridge-runtime.d.ts.map +1 -0
- package/dist/reactive-bridge-runtime.js +112 -0
- package/dist/reactive-bridge-runtime.js.map +1 -0
- package/dist/runtime-abi.d.ts +24 -0
- package/dist/runtime-abi.d.ts.map +1 -0
- package/dist/runtime-abi.js +24 -0
- package/dist/runtime-abi.js.map +1 -0
- package/dist/semantic.d.ts +142 -0
- package/dist/semantic.d.ts.map +1 -0
- package/dist/semantic.js +845 -0
- package/dist/semantic.js.map +1 -0
- package/dist/source-names.d.ts +32 -0
- package/dist/source-names.d.ts.map +1 -0
- package/dist/source-names.js +101 -0
- package/dist/source-names.js.map +1 -0
- package/dist/source.d.ts +19 -0
- package/dist/source.d.ts.map +1 -0
- package/dist/source.js +64 -0
- package/dist/source.js.map +1 -0
- package/dist/text-runtime.d.ts +2 -0
- package/dist/text-runtime.d.ts.map +1 -0
- package/dist/text-runtime.js +285 -0
- package/dist/text-runtime.js.map +1 -0
- package/dist/token.d.ts +19 -0
- package/dist/token.d.ts.map +1 -0
- package/dist/token.js +51 -0
- package/dist/token.js.map +1 -0
- package/dist/type-registry-runtime.d.ts +7 -0
- package/dist/type-registry-runtime.d.ts.map +1 -0
- package/dist/type-registry-runtime.js +51 -0
- package/dist/type-registry-runtime.js.map +1 -0
- package/dist/type-validation-runtime.d.ts +17 -0
- package/dist/type-validation-runtime.d.ts.map +1 -0
- package/dist/type-validation-runtime.js +150 -0
- package/dist/type-validation-runtime.js.map +1 -0
- package/dist/types.d.ts +356 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +1170 -0
- package/dist/types.js.map +1 -0
- package/dist/utf8-runtime.d.ts +7 -0
- package/dist/utf8-runtime.d.ts.map +1 -0
- package/dist/utf8-runtime.js +43 -0
- package/dist/utf8-runtime.js.map +1 -0
- package/package.json +48 -0
package/dist/types.js
ADDED
|
@@ -0,0 +1,1170 @@
|
|
|
1
|
+
/** Canonical identities for Core's cross-runtime binary storage types. */
|
|
2
|
+
export const VELAR_BYTES_TYPE_IDENTITY = "velar/binary#type:Bytes";
|
|
3
|
+
export const VELAR_UINT8_BUFFER_TYPE_IDENTITY = "velar/binary#type:UInt8Buffer";
|
|
4
|
+
export const VELAR_UINT16_BUFFER_TYPE_IDENTITY = "velar/binary#type:UInt16Buffer";
|
|
5
|
+
export const VELAR_UINT32_BUFFER_TYPE_IDENTITY = "velar/binary#type:UInt32Buffer";
|
|
6
|
+
export const VELAR_FLOAT32_BUFFER_TYPE_IDENTITY = "velar/binary#type:Float32Buffer";
|
|
7
|
+
/**
|
|
8
|
+
* Binary storage stays nominal even though its JavaScript representation is a
|
|
9
|
+
* typed array. This keeps Buffer's accidental surface out of source while
|
|
10
|
+
* giving the analyzer and emitter one exact fast-path discriminator.
|
|
11
|
+
*/
|
|
12
|
+
export function binaryStorageKind(type) {
|
|
13
|
+
if (type.kind !== "named")
|
|
14
|
+
return null;
|
|
15
|
+
if (type.identity === VELAR_BYTES_TYPE_IDENTITY)
|
|
16
|
+
return "bytes";
|
|
17
|
+
if (type.identity === VELAR_UINT8_BUFFER_TYPE_IDENTITY)
|
|
18
|
+
return "uint8";
|
|
19
|
+
if (type.identity === VELAR_UINT16_BUFFER_TYPE_IDENTITY)
|
|
20
|
+
return "uint16";
|
|
21
|
+
if (type.identity === VELAR_UINT32_BUFFER_TYPE_IDENTITY)
|
|
22
|
+
return "uint32";
|
|
23
|
+
if (type.identity === VELAR_FLOAT32_BUFFER_TYPE_IDENTITY)
|
|
24
|
+
return "float32";
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
export const unknownType = { kind: "unknown" };
|
|
28
|
+
export const invalidType = Object.freeze({ kind: "unknown" });
|
|
29
|
+
export const anyType = { kind: "any" };
|
|
30
|
+
/** The declared parameter domain of the built-in `str`; see `isTextConvertibleType`. */
|
|
31
|
+
export const textConvertibleType = Object.freeze({ kind: "any", textConvertible: true });
|
|
32
|
+
export const nullType = { kind: "null" };
|
|
33
|
+
export const stringType = { kind: "string" };
|
|
34
|
+
export const numberType = { kind: "number" };
|
|
35
|
+
export const boolType = { kind: "bool" };
|
|
36
|
+
/**
|
|
37
|
+
* D41 item 61: the complete, closed bound vocabulary. A bound is a name the
|
|
38
|
+
* compiler owns; users cannot define one, and there is no syntax for combining
|
|
39
|
+
* two — D51 rule 110: not because the three form a containment chain (they do
|
|
40
|
+
* not: a Web text-shaped value satisfies Text and is refused by Data), but
|
|
41
|
+
* because no real function demands two at once. The grant table below is the
|
|
42
|
+
* whole definition; nothing computes a relation between two bounds.
|
|
43
|
+
*/
|
|
44
|
+
export const typeParameterBoundNames = Object.freeze(["Comparable", "Text", "Data"]);
|
|
45
|
+
/**
|
|
46
|
+
* The 4x3 capability-grant table (D41 item 61). Every check reads this
|
|
47
|
+
* constant; nothing computes a relation between two bounds, so the rule
|
|
48
|
+
* "bounds have no subtyping" holds literally. The overlaps below are grants,
|
|
49
|
+
* not type containment (D51 rule 110).
|
|
50
|
+
*/
|
|
51
|
+
const boundCapabilityGrants = Object.freeze({
|
|
52
|
+
Data: Object.freeze({ text: false, order: false, data: true }),
|
|
53
|
+
Text: Object.freeze({ text: true, order: false, data: true }),
|
|
54
|
+
Comparable: Object.freeze({ text: true, order: true, data: true }),
|
|
55
|
+
});
|
|
56
|
+
export function isTypeParameterBound(name) {
|
|
57
|
+
return typeParameterBoundNames.includes(name);
|
|
58
|
+
}
|
|
59
|
+
/** Reads the grant table. An unbounded parameter (`null`) grants nothing. */
|
|
60
|
+
export function boundGrants(bound, capability) {
|
|
61
|
+
return bound ? boundCapabilityGrants[bound][capability] : false;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* A callable carrying `actual` bounds may stand where `expected` bounds are
|
|
65
|
+
* declared only when it demands no capability the target does not promise —
|
|
66
|
+
* decided by the same grant table, one capability at a time.
|
|
67
|
+
*/
|
|
68
|
+
export function boundAccepts(actual, expected) {
|
|
69
|
+
if (actual === expected)
|
|
70
|
+
return true;
|
|
71
|
+
return ["text", "order", "data"].every((capability) => !boundGrants(actual, capability) || boundGrants(expected, capability));
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* The text-conversion whitelist (charter section 14): values whose text form is
|
|
75
|
+
* total and hook-free. This is the single authority behind both the direct
|
|
76
|
+
* `str(value)` / f-string check and the assignability of the `textConvertible`
|
|
77
|
+
* parameter domain, so `str` used as a value cannot admit anything a direct
|
|
78
|
+
* call rejects.
|
|
79
|
+
*/
|
|
80
|
+
export function isTextConvertibleType(type, environment) {
|
|
81
|
+
const expanded = environment.expandTypeAliases?.(type) ?? type;
|
|
82
|
+
if (isInvalidType(expanded))
|
|
83
|
+
return true;
|
|
84
|
+
switch (expanded.kind) {
|
|
85
|
+
case "string":
|
|
86
|
+
case "number":
|
|
87
|
+
case "bool":
|
|
88
|
+
case "null":
|
|
89
|
+
case "enum":
|
|
90
|
+
case "enumMember":
|
|
91
|
+
return true;
|
|
92
|
+
case "optional":
|
|
93
|
+
return isTextConvertibleType(expanded.inner, environment);
|
|
94
|
+
case "union":
|
|
95
|
+
return expanded.members.every((member) => isTextConvertibleType(member, environment));
|
|
96
|
+
// D41 item 61: a `Text`-bounded parameter promises every value it can hold
|
|
97
|
+
// is already in this whitelist, so the body may interpolate it.
|
|
98
|
+
case "parameter":
|
|
99
|
+
return boundGrants(environment.boundOf?.(expanded) ?? null, "text");
|
|
100
|
+
default:
|
|
101
|
+
return environment.extensionTextForm?.(expanded) === true;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
export function resolveTypeReference(reference, extension) {
|
|
105
|
+
return typeFromSyntax(reference.syntax, extension);
|
|
106
|
+
}
|
|
107
|
+
export function typeFromSyntax(syntax, extension) {
|
|
108
|
+
const nested = (value) => typeFromSyntax(value, extension);
|
|
109
|
+
const owned = extension?.(syntax, nested);
|
|
110
|
+
if (owned)
|
|
111
|
+
return owned;
|
|
112
|
+
switch (syntax.kind) {
|
|
113
|
+
case "NamedTypeSyntax":
|
|
114
|
+
switch (syntax.name) {
|
|
115
|
+
case "string": return stringType;
|
|
116
|
+
case "number": return numberType;
|
|
117
|
+
case "bool": return boolType;
|
|
118
|
+
case "null": return nullType;
|
|
119
|
+
case "unknown": return unknownType;
|
|
120
|
+
case "any": return anyType;
|
|
121
|
+
case "Promise": return { kind: "promise", value: nullType };
|
|
122
|
+
case "Function": return { kind: "function", parameters: [], requiredParameters: 0, result: nullType };
|
|
123
|
+
default: return { kind: "named", name: syntax.name };
|
|
124
|
+
}
|
|
125
|
+
case "EnumMemberTypeSyntax":
|
|
126
|
+
return { kind: "enumMember", name: syntax.enumName, identity: syntax.enumName, member: syntax.member };
|
|
127
|
+
case "GenericTypeSyntax": {
|
|
128
|
+
const arguments_ = syntax.arguments.map(nested);
|
|
129
|
+
if (syntax.name === "List")
|
|
130
|
+
return { kind: "list", element: arguments_[0] ?? unknownType };
|
|
131
|
+
if (syntax.name === "Set")
|
|
132
|
+
return { kind: "set", element: arguments_[0] ?? unknownType };
|
|
133
|
+
if (syntax.name === "Map")
|
|
134
|
+
return { kind: "map", key: arguments_[0] ?? unknownType, value: arguments_[1] ?? unknownType };
|
|
135
|
+
if (syntax.name === "Record")
|
|
136
|
+
return { kind: "record", value: arguments_[0] ?? unknownType };
|
|
137
|
+
if (syntax.name === "Promise")
|
|
138
|
+
return { kind: "promise", value: arguments_[0] ?? unknownType };
|
|
139
|
+
if (syntax.name === "Type")
|
|
140
|
+
return { kind: "runtimeType", value: arguments_[0] ?? unknownType };
|
|
141
|
+
if (syntax.name === "Function") {
|
|
142
|
+
const result = arguments_.at(-1) ?? nullType;
|
|
143
|
+
const parameters = arguments_.slice(0, -1);
|
|
144
|
+
return { kind: "function", parameters, requiredParameters: parameters.length, result };
|
|
145
|
+
}
|
|
146
|
+
// D55 rule 121: a name core does not own is either an extension family
|
|
147
|
+
// (claimed above) or a user generic record. The arguments ride along
|
|
148
|
+
// unresolved so the one stage that knows the declarations — the analyzer,
|
|
149
|
+
// or a module interface being built — can canonicalize the application;
|
|
150
|
+
// until then the display name stays exactly the source text it always was.
|
|
151
|
+
return { kind: "named", name: formatTypeSyntax(syntax), application: { declaration: syntax.name, name: syntax.name, arguments: arguments_ } };
|
|
152
|
+
}
|
|
153
|
+
case "ReadonlyTypeSyntax":
|
|
154
|
+
return readonlyViewOf(nested(syntax.inner));
|
|
155
|
+
case "OptionalTypeSyntax":
|
|
156
|
+
return optionalOf(nested(syntax.inner));
|
|
157
|
+
case "UnionTypeSyntax":
|
|
158
|
+
return unionOf(syntax.members.map(nested));
|
|
159
|
+
case "FunctionTypeSyntax": {
|
|
160
|
+
const fixed = syntax.parameters.filter((parameter) => !parameter.rest);
|
|
161
|
+
const rest = syntax.parameters.find((parameter) => parameter.rest);
|
|
162
|
+
return {
|
|
163
|
+
kind: "function",
|
|
164
|
+
parameters: fixed.map((parameter) => nested(parameter.type)),
|
|
165
|
+
...(fixed.some((parameter) => parameter.name) ? { parameterNames: fixed.map((parameter) => parameter.name ?? "") } : {}),
|
|
166
|
+
requiredParameters: fixed.filter((parameter) => !parameter.optional).length,
|
|
167
|
+
...(rest ? { rest: nested(rest.type) } : {}),
|
|
168
|
+
result: nested(syntax.result),
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* The canonical identity of one instantiation. D55 rule 121 puts the arguments
|
|
175
|
+
* in the identity string rather than adding a field `typeIdentity` would have
|
|
176
|
+
* to learn, so `Box<string>` and `Box<number>` are two identities and
|
|
177
|
+
* `typeIdentity`'s `named` branch is unchanged. Arguments are keyed by their
|
|
178
|
+
* own identities, which is what makes `Box<Id>` and `Box<string>` one type when
|
|
179
|
+
* `Id` is an alias of `string`.
|
|
180
|
+
*/
|
|
181
|
+
export function genericApplicationIdentity(declaration, arguments_) {
|
|
182
|
+
return `${declaration}<${arguments_.map((argument) => semanticTypeIdentity(argument)).join(",")}>`;
|
|
183
|
+
}
|
|
184
|
+
/** The display text of one instantiation — `Box<string>`. */
|
|
185
|
+
export function genericApplicationName(name, arguments_) {
|
|
186
|
+
return `${name}<${arguments_.map(describeType).join(", ")}>`;
|
|
187
|
+
}
|
|
188
|
+
/** The one constructor for a resolved application, so identity and text never diverge. */
|
|
189
|
+
export function genericApplicationType(declaration, name, arguments_, readonlyView = false) {
|
|
190
|
+
return {
|
|
191
|
+
kind: "named",
|
|
192
|
+
name: genericApplicationName(name, arguments_),
|
|
193
|
+
identity: genericApplicationIdentity(declaration, arguments_),
|
|
194
|
+
application: { declaration, name, arguments: arguments_ },
|
|
195
|
+
...(readonlyView ? { readonlyView: true } : {}),
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Rebuilds a type with `map` applied to each type it directly contains. The
|
|
200
|
+
* nested positions are exactly the ones `substituteTypeParameters` walks, kept
|
|
201
|
+
* in one place so a traversal added by a caller cannot miss one of them.
|
|
202
|
+
*/
|
|
203
|
+
export function mapNestedTypes(type, map) {
|
|
204
|
+
switch (type.kind) {
|
|
205
|
+
case "optional":
|
|
206
|
+
return optionalOf(map(type.inner));
|
|
207
|
+
case "list":
|
|
208
|
+
case "set":
|
|
209
|
+
return { ...type, element: map(type.element) };
|
|
210
|
+
case "map":
|
|
211
|
+
return { ...type, key: map(type.key), value: map(type.value) };
|
|
212
|
+
case "record":
|
|
213
|
+
return { ...type, value: map(type.value) };
|
|
214
|
+
case "promise":
|
|
215
|
+
return { kind: "promise", value: map(type.value) };
|
|
216
|
+
case "runtimeType":
|
|
217
|
+
return { kind: "runtimeType", value: map(type.value) };
|
|
218
|
+
case "typeObject":
|
|
219
|
+
return type.value ? { ...type, value: map(type.value) } : type;
|
|
220
|
+
case "object":
|
|
221
|
+
return { ...type, fields: new Map([...type.fields].map(([name, value]) => [name, map(value)])) };
|
|
222
|
+
case "named":
|
|
223
|
+
return type.application
|
|
224
|
+
? { ...type, application: { ...type.application, arguments: type.application.arguments.map(map) } }
|
|
225
|
+
: type;
|
|
226
|
+
case "extension":
|
|
227
|
+
return {
|
|
228
|
+
...type,
|
|
229
|
+
properties: new Map([...type.properties].map(([name, value]) => [name, map(value)])),
|
|
230
|
+
arguments: type.arguments.map(map),
|
|
231
|
+
};
|
|
232
|
+
case "function":
|
|
233
|
+
case "action":
|
|
234
|
+
case "intrinsic":
|
|
235
|
+
return {
|
|
236
|
+
...type,
|
|
237
|
+
parameters: type.parameters.map(map),
|
|
238
|
+
...(type.rest ? { rest: map(type.rest) } : {}),
|
|
239
|
+
result: map(type.result),
|
|
240
|
+
};
|
|
241
|
+
case "union":
|
|
242
|
+
return unionOf(type.members.map(map));
|
|
243
|
+
default:
|
|
244
|
+
return type;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
export function formatTypeReference(reference) {
|
|
248
|
+
return formatTypeSyntax(reference.syntax);
|
|
249
|
+
}
|
|
250
|
+
export function formatTypeSyntax(syntax) {
|
|
251
|
+
switch (syntax.kind) {
|
|
252
|
+
case "NamedTypeSyntax": return syntax.name;
|
|
253
|
+
case "EnumMemberTypeSyntax": return `${syntax.enumName}.${syntax.member}`;
|
|
254
|
+
case "GenericTypeSyntax": return `${syntax.name}<${syntax.arguments.map(formatTypeSyntax).join(", ")}>`;
|
|
255
|
+
case "ReadonlyTypeSyntax": return `readonly ${syntax.inner.kind === "UnionTypeSyntax" || syntax.inner.kind === "FunctionTypeSyntax" ? `(${formatTypeSyntax(syntax.inner)})` : formatTypeSyntax(syntax.inner)}`;
|
|
256
|
+
case "OptionalTypeSyntax": return `${syntax.inner.kind === "UnionTypeSyntax" || syntax.inner.kind === "FunctionTypeSyntax" ? `(${formatTypeSyntax(syntax.inner)})` : formatTypeSyntax(syntax.inner)}?`;
|
|
257
|
+
case "UnionTypeSyntax": return syntax.members.map(formatTypeSyntax).join(" | ");
|
|
258
|
+
case "FunctionTypeSyntax": return `(${syntax.parameters.map((parameter) => `${parameter.rest ? "..." : ""}${parameter.name ? `${parameter.name}${parameter.optional ? "?" : ""}: ` : ""}${formatTypeSyntax(parameter.type)}`).join(", ")}) -> ${formatTypeSyntax(syntax.result)}`;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
export function isReadonlyView(type) {
|
|
262
|
+
return (type.kind === "list" || type.kind === "set" || type.kind === "map" || type.kind === "record"
|
|
263
|
+
|| type.kind === "object" || type.kind === "named")
|
|
264
|
+
&& type.readonlyView === true;
|
|
265
|
+
}
|
|
266
|
+
export function readonlyViewOf(type) {
|
|
267
|
+
if (type.kind === "optional")
|
|
268
|
+
return optionalOf(readonlyViewOf(type.inner));
|
|
269
|
+
if (type.kind === "union")
|
|
270
|
+
return unionOf(type.members.map(readonlyViewOf));
|
|
271
|
+
if (type.kind === "list" || type.kind === "set" || type.kind === "map" || type.kind === "record"
|
|
272
|
+
|| type.kind === "object" || type.kind === "named") {
|
|
273
|
+
return type.readonlyView ? type : { ...type, readonlyView: true };
|
|
274
|
+
}
|
|
275
|
+
return type;
|
|
276
|
+
}
|
|
277
|
+
export function mutableViewOf(type) {
|
|
278
|
+
if (type.kind === "optional")
|
|
279
|
+
return optionalOf(mutableViewOf(type.inner));
|
|
280
|
+
if (type.kind === "union")
|
|
281
|
+
return unionOf(type.members.map(mutableViewOf));
|
|
282
|
+
if (type.kind === "list")
|
|
283
|
+
return { kind: "list", element: type.element };
|
|
284
|
+
if (type.kind === "set")
|
|
285
|
+
return { kind: "set", element: type.element };
|
|
286
|
+
if (type.kind === "map")
|
|
287
|
+
return { kind: "map", key: type.key, value: type.value };
|
|
288
|
+
if (type.kind === "record")
|
|
289
|
+
return { kind: "record", value: type.value };
|
|
290
|
+
if (type.kind === "object")
|
|
291
|
+
return { kind: "object", fields: type.fields, ...(type.readonlyFields ? { readonlyFields: type.readonlyFields } : {}), ...(type.optionalFields ? { optionalFields: type.optionalFields } : {}) };
|
|
292
|
+
if (type.kind === "named")
|
|
293
|
+
return { kind: "named", name: type.name, ...(type.identity ? { identity: type.identity } : {}), ...(type.application ? { application: type.application } : {}) };
|
|
294
|
+
return type;
|
|
295
|
+
}
|
|
296
|
+
export function optionalOf(type) {
|
|
297
|
+
if (isInvalidType(type)) {
|
|
298
|
+
return invalidType;
|
|
299
|
+
}
|
|
300
|
+
if (type.kind === "optional") {
|
|
301
|
+
return type;
|
|
302
|
+
}
|
|
303
|
+
if (type.kind === "null") {
|
|
304
|
+
return nullType;
|
|
305
|
+
}
|
|
306
|
+
return { kind: "optional", inner: type };
|
|
307
|
+
}
|
|
308
|
+
export function nonOptional(type) {
|
|
309
|
+
return type.kind === "optional" ? type.inner : type;
|
|
310
|
+
}
|
|
311
|
+
export function unionOf(types) {
|
|
312
|
+
const members = [];
|
|
313
|
+
let nullable = false;
|
|
314
|
+
const add = (type) => {
|
|
315
|
+
if (isInvalidType(type))
|
|
316
|
+
return false;
|
|
317
|
+
if (type.kind === "union")
|
|
318
|
+
return type.members.every(add);
|
|
319
|
+
if (type.kind === "optional") {
|
|
320
|
+
nullable = true;
|
|
321
|
+
return add(type.inner);
|
|
322
|
+
}
|
|
323
|
+
if (type.kind === "null") {
|
|
324
|
+
nullable = true;
|
|
325
|
+
return true;
|
|
326
|
+
}
|
|
327
|
+
if (!members.some((member) => sameType(member, type)))
|
|
328
|
+
members.push(type);
|
|
329
|
+
return true;
|
|
330
|
+
};
|
|
331
|
+
if (!types.every(add))
|
|
332
|
+
return invalidType;
|
|
333
|
+
const value = members.length === 0 ? unknownType : members.length === 1 ? members[0] : { kind: "union", members };
|
|
334
|
+
return nullable ? members.length === 0 ? nullType : optionalOf(value) : value;
|
|
335
|
+
}
|
|
336
|
+
export function mergeTypes(left, right) {
|
|
337
|
+
if (isInvalidType(left) || isInvalidType(right)) {
|
|
338
|
+
return invalidType;
|
|
339
|
+
}
|
|
340
|
+
if (left.kind === "unknown" && !left.restricted) {
|
|
341
|
+
return right;
|
|
342
|
+
}
|
|
343
|
+
if (right.kind === "unknown" && !right.restricted) {
|
|
344
|
+
return left;
|
|
345
|
+
}
|
|
346
|
+
if (sameType(left, right)) {
|
|
347
|
+
return left;
|
|
348
|
+
}
|
|
349
|
+
if ((isReadonlyView(left) || isReadonlyView(right))
|
|
350
|
+
&& sameType(mutableViewOf(left), mutableViewOf(right))) {
|
|
351
|
+
return readonlyViewOf(isReadonlyView(left) ? left : right);
|
|
352
|
+
}
|
|
353
|
+
if (left.kind === "optional" && sameType(left.inner, right)) {
|
|
354
|
+
return left;
|
|
355
|
+
}
|
|
356
|
+
if (right.kind === "optional" && sameType(right.inner, left)) {
|
|
357
|
+
return right;
|
|
358
|
+
}
|
|
359
|
+
if (left.kind === "null") {
|
|
360
|
+
return optionalOf(right);
|
|
361
|
+
}
|
|
362
|
+
if (right.kind === "null") {
|
|
363
|
+
return optionalOf(left);
|
|
364
|
+
}
|
|
365
|
+
return unionOf([left, right]);
|
|
366
|
+
}
|
|
367
|
+
export function resolvedAsyncType(type) {
|
|
368
|
+
if (type.kind === "promise")
|
|
369
|
+
return resolvedAsyncType(type.value);
|
|
370
|
+
if (type.kind === "optional")
|
|
371
|
+
return optionalOf(resolvedAsyncType(type.inner));
|
|
372
|
+
if (type.kind === "union")
|
|
373
|
+
return unionOf(type.members.map(resolvedAsyncType));
|
|
374
|
+
return type;
|
|
375
|
+
}
|
|
376
|
+
export function sameType(left, right) {
|
|
377
|
+
return semanticTypeIdentity(left) === semanticTypeIdentity(right);
|
|
378
|
+
}
|
|
379
|
+
// Parameter labels are editor/call-site metadata, not part of the runtime
|
|
380
|
+
// callable domain. Override implementations may keep local names while every
|
|
381
|
+
// declaration still exposes its own checked named-argument surface.
|
|
382
|
+
export function sameTypeIgnoringCallableParameterNames(left, right) {
|
|
383
|
+
return typeIdentity(left, false) === typeIdentity(right, false);
|
|
384
|
+
}
|
|
385
|
+
function runtimeTypeValue(type) {
|
|
386
|
+
if (type.kind === "runtimeType")
|
|
387
|
+
return type.value;
|
|
388
|
+
if (type.kind === "typeObject")
|
|
389
|
+
return type.value ?? { kind: "named", name: type.name };
|
|
390
|
+
if (type.kind === "enumObject")
|
|
391
|
+
return { kind: "enum", name: type.name, identity: type.identity };
|
|
392
|
+
return null;
|
|
393
|
+
}
|
|
394
|
+
export function isAssignable(actual, expected, environment, seen = new Set()) {
|
|
395
|
+
if (isInvalidType(actual) || isInvalidType(expected)) {
|
|
396
|
+
return true;
|
|
397
|
+
}
|
|
398
|
+
// The text-conversion domain is narrower than `any` and must be decided
|
|
399
|
+
// before the `any` shortcut, so `str` passed as a value keeps the whitelist
|
|
400
|
+
// at every indirect call site (`const c = str`, `values.map(str)`).
|
|
401
|
+
if (expected.kind === "any" && expected.textConvertible) {
|
|
402
|
+
return isTextConvertibleType(actual, environment);
|
|
403
|
+
}
|
|
404
|
+
if (actual.kind === "any" || expected.kind === "any") {
|
|
405
|
+
return true;
|
|
406
|
+
}
|
|
407
|
+
if (expected.kind === "unknown") {
|
|
408
|
+
return !expected.restricted;
|
|
409
|
+
}
|
|
410
|
+
if (actual.kind === "unknown") {
|
|
411
|
+
return false;
|
|
412
|
+
}
|
|
413
|
+
if (sameType(actual, expected)) {
|
|
414
|
+
return true;
|
|
415
|
+
}
|
|
416
|
+
const pair = `${semanticTypeIdentity(actual)}\u0000${semanticTypeIdentity(expected)}`;
|
|
417
|
+
if (seen.has(pair))
|
|
418
|
+
return true;
|
|
419
|
+
seen.add(pair);
|
|
420
|
+
if (actual.kind === "union") {
|
|
421
|
+
return actual.members.every((member) => isAssignable(member, expected, environment, new Set(seen)));
|
|
422
|
+
}
|
|
423
|
+
if (actual.kind === "optional") {
|
|
424
|
+
return isAssignable(nullType, expected, environment, new Set(seen))
|
|
425
|
+
&& isAssignable(actual.inner, expected, environment, new Set(seen));
|
|
426
|
+
}
|
|
427
|
+
if (expected.kind === "optional") {
|
|
428
|
+
return actual.kind === "null" || isAssignable(actual, expected.inner, environment, seen);
|
|
429
|
+
}
|
|
430
|
+
if (expected.kind === "union") {
|
|
431
|
+
return expected.members.some((member) => isAssignable(actual, member, environment, new Set(seen)));
|
|
432
|
+
}
|
|
433
|
+
if (isReadonlyView(actual) && !isReadonlyView(expected)) {
|
|
434
|
+
return false;
|
|
435
|
+
}
|
|
436
|
+
if (actual.kind === "parameter" && expected.kind === "parameter") {
|
|
437
|
+
return actual.index === expected.index;
|
|
438
|
+
}
|
|
439
|
+
if (actual.kind === "enum" && expected.kind === "string") {
|
|
440
|
+
return true;
|
|
441
|
+
}
|
|
442
|
+
if (actual.kind === "enumMember") {
|
|
443
|
+
if (expected.kind === "enumMember") {
|
|
444
|
+
return actual.identity === expected.identity && actual.member === expected.member;
|
|
445
|
+
}
|
|
446
|
+
if (expected.kind === "enum")
|
|
447
|
+
return actual.identity === expected.identity;
|
|
448
|
+
if (expected.kind === "string")
|
|
449
|
+
return true;
|
|
450
|
+
}
|
|
451
|
+
if (expected.kind === "runtimeType") {
|
|
452
|
+
const value = runtimeTypeValue(actual);
|
|
453
|
+
return value !== null && isAssignable(value, expected.value, environment, seen);
|
|
454
|
+
}
|
|
455
|
+
if (actual.kind === "list" && expected.kind === "list") {
|
|
456
|
+
if (expected.readonlyView) {
|
|
457
|
+
return isAssignable(readonlyViewOf(actual.element), readonlyViewOf(expected.element), environment, new Set(seen));
|
|
458
|
+
}
|
|
459
|
+
return invariant(actual.element, expected.element, environment, seen);
|
|
460
|
+
}
|
|
461
|
+
if (actual.kind === "set" && expected.kind === "set") {
|
|
462
|
+
if (expected.readonlyView) {
|
|
463
|
+
return isAssignable(readonlyViewOf(actual.element), readonlyViewOf(expected.element), environment, new Set(seen));
|
|
464
|
+
}
|
|
465
|
+
return invariant(actual.element, expected.element, environment, seen);
|
|
466
|
+
}
|
|
467
|
+
if (actual.kind === "map" && expected.kind === "map") {
|
|
468
|
+
if (expected.readonlyView) {
|
|
469
|
+
return isAssignable(readonlyViewOf(actual.key), readonlyViewOf(expected.key), environment, new Set(seen))
|
|
470
|
+
&& isAssignable(readonlyViewOf(actual.value), readonlyViewOf(expected.value), environment, new Set(seen));
|
|
471
|
+
}
|
|
472
|
+
return invariant(actual.key, expected.key, environment, seen)
|
|
473
|
+
&& invariant(actual.value, expected.value, environment, seen);
|
|
474
|
+
}
|
|
475
|
+
if (actual.kind === "record" && expected.kind === "record") {
|
|
476
|
+
if (expected.readonlyView) {
|
|
477
|
+
return isAssignable(readonlyViewOf(actual.value), readonlyViewOf(expected.value), environment, new Set(seen));
|
|
478
|
+
}
|
|
479
|
+
return invariant(actual.value, expected.value, environment, seen);
|
|
480
|
+
}
|
|
481
|
+
if (actual.kind === "object" && expected.kind === "record") {
|
|
482
|
+
if (expected.readonlyView) {
|
|
483
|
+
return [...actual.fields.values()].every((field) => isAssignable(readonlyViewOf(field), readonlyViewOf(expected.value), environment, new Set(seen)));
|
|
484
|
+
}
|
|
485
|
+
if (actual.readonlyFields && actual.readonlyFields.size > 0)
|
|
486
|
+
return false;
|
|
487
|
+
return [...actual.fields.values()].every((field) => invariant(field, expected.value, environment, seen));
|
|
488
|
+
}
|
|
489
|
+
if (actual.kind === "promise" && expected.kind === "promise") {
|
|
490
|
+
return isAssignable(actual.value, expected.value, environment, seen);
|
|
491
|
+
}
|
|
492
|
+
if (actual.kind === "object" && expected.kind === "named") {
|
|
493
|
+
if (environment.isPrimitiveType(expected.name))
|
|
494
|
+
return false;
|
|
495
|
+
const fields = environment.fieldsOf(expected.identity ?? expected.name);
|
|
496
|
+
const expectedReadonly = expected.readonlyView ? new Set(fields?.keys()) : environment.readonlyFieldsOf?.(expected.identity ?? expected.name) ?? undefined;
|
|
497
|
+
return fields ? objectFieldsAssignable(actual.fields, fields, environment, seen, actual.readonlyView ? new Set(actual.fields.keys()) : actual.readonlyFields, expectedReadonly, actual.optionalFields) : false;
|
|
498
|
+
}
|
|
499
|
+
if (actual.kind === "named" && expected.kind === "object") {
|
|
500
|
+
if (environment.isPrimitiveType(actual.name))
|
|
501
|
+
return false;
|
|
502
|
+
const fields = environment.fieldsOf(actual.identity ?? actual.name);
|
|
503
|
+
const actualReadonly = actual.readonlyView ? new Set(fields?.keys()) : environment.readonlyFieldsOf?.(actual.identity ?? actual.name) ?? undefined;
|
|
504
|
+
const expectedReadonly = expected.readonlyView ? new Set(expected.fields.keys()) : expected.readonlyFields;
|
|
505
|
+
return fields ? objectFieldsAssignable(fields, expected.fields, environment, seen, actualReadonly, expectedReadonly, undefined, expected.optionalFields) : false;
|
|
506
|
+
}
|
|
507
|
+
if (actual.kind === "named" && expected.kind === "named") {
|
|
508
|
+
const actualPrimitive = environment.isPrimitiveType(actual.name);
|
|
509
|
+
const expectedPrimitive = environment.isPrimitiveType(expected.name);
|
|
510
|
+
if (actualPrimitive || expectedPrimitive) {
|
|
511
|
+
return actualPrimitive && expectedPrimitive && environment.isPrimitiveSubtype(actual.name, expected.name);
|
|
512
|
+
}
|
|
513
|
+
const actualFields = environment.fieldsOf(actual.identity ?? actual.name);
|
|
514
|
+
const expectedFields = environment.fieldsOf(expected.identity ?? expected.name);
|
|
515
|
+
return actualFields !== null && expectedFields !== null
|
|
516
|
+
? objectFieldsAssignable(actualFields, expectedFields, environment, seen, actual.readonlyView ? new Set(actualFields.keys()) : environment.readonlyFieldsOf?.(actual.identity ?? actual.name) ?? undefined, expected.readonlyView ? new Set(expectedFields.keys()) : environment.readonlyFieldsOf?.(expected.identity ?? expected.name) ?? undefined)
|
|
517
|
+
: false;
|
|
518
|
+
}
|
|
519
|
+
if (actual.kind === "class" && expected.kind === "class") {
|
|
520
|
+
return environment.isSubclassOf(actual.identity ?? actual.name, expected.identity ?? expected.name);
|
|
521
|
+
}
|
|
522
|
+
if (actual.kind === "object" && expected.kind === "object") {
|
|
523
|
+
return objectFieldsAssignable(actual.fields, expected.fields, environment, seen, actual.readonlyView ? new Set(actual.fields.keys()) : actual.readonlyFields, expected.readonlyView ? new Set(expected.fields.keys()) : expected.readonlyFields, actual.optionalFields, expected.optionalFields);
|
|
524
|
+
}
|
|
525
|
+
if ((actual.kind === "function" || actual.kind === "action" || actual.kind === "intrinsic") && (expected.kind === "function" || expected.kind === "action" || expected.kind === "intrinsic")) {
|
|
526
|
+
const actualTypeParameters = actual.typeParameterNames?.length ?? 0;
|
|
527
|
+
const expectedTypeParameters = expected.typeParameterNames?.length ?? 0;
|
|
528
|
+
if (actualTypeParameters !== expectedTypeParameters) {
|
|
529
|
+
if (expectedTypeParameters !== 0)
|
|
530
|
+
return false;
|
|
531
|
+
// D41 item 61 check site 2: the first-class path erases the parameters
|
|
532
|
+
// silently, so an unsatisfied bound has to make the value unassignable
|
|
533
|
+
// here; the analyzer wrapper turns the same fact into a directed message.
|
|
534
|
+
const violations = [];
|
|
535
|
+
const concrete = instantiateGenericCallable(actual, expected, environment, violations);
|
|
536
|
+
return violations.length === 0 && isAssignable(concrete, expected, environment, seen);
|
|
537
|
+
}
|
|
538
|
+
if (actualTypeParameters > 0 && !typeParameterBoundsAccept(actual, expected))
|
|
539
|
+
return false;
|
|
540
|
+
return callableInputsAssignable(actual, expected, environment, seen)
|
|
541
|
+
&& isAssignable(actual.result, expected.result, environment, seen);
|
|
542
|
+
}
|
|
543
|
+
if (actual.kind === "extension" && expected.kind === "extension") {
|
|
544
|
+
return environment.isExtensionTypeAssignable?.(actual, expected, (nestedActual, nestedExpected) => isAssignable(nestedActual, nestedExpected, environment, new Set(seen))) ?? false;
|
|
545
|
+
}
|
|
546
|
+
return false;
|
|
547
|
+
}
|
|
548
|
+
export function semanticTypeIdentity(type) {
|
|
549
|
+
return typeIdentity(type);
|
|
550
|
+
}
|
|
551
|
+
export const analysisTypeIdentity = semanticTypeIdentity;
|
|
552
|
+
function typeIdentity(type, includeCallableParameterNames = true) {
|
|
553
|
+
const nested = (value) => typeIdentity(value, includeCallableParameterNames);
|
|
554
|
+
switch (type.kind) {
|
|
555
|
+
case "unknown":
|
|
556
|
+
return identityNode("unknown", [isInvalidType(type) ? "diagnosed" : type.restricted ? "restricted" : ""]);
|
|
557
|
+
case "any":
|
|
558
|
+
// The text-conversion domain is a distinct contract from `any`; sharing
|
|
559
|
+
// an identity would let `sameType` short-circuit the whitelist.
|
|
560
|
+
return identityNode("any", [type.textConvertible ? "text" : ""]);
|
|
561
|
+
case "null":
|
|
562
|
+
case "string":
|
|
563
|
+
case "number":
|
|
564
|
+
case "bool":
|
|
565
|
+
return identityNode(type.kind);
|
|
566
|
+
case "class":
|
|
567
|
+
return identityNode("class", [type.identity ?? type.name]);
|
|
568
|
+
case "classConstructor":
|
|
569
|
+
return identityNode("class-constructor", [type.identity ?? type.name]);
|
|
570
|
+
case "named":
|
|
571
|
+
return identityNode("named", [type.readonlyView ? "readonly" : "", type.identity ?? type.name]);
|
|
572
|
+
case "parameter":
|
|
573
|
+
// De Bruijn-style: the identity encodes only the index so that (T) -> T
|
|
574
|
+
// and (U) -> U from any two declarations are the same type.
|
|
575
|
+
return identityNode("parameter", [String(type.index)]);
|
|
576
|
+
case "enum":
|
|
577
|
+
case "enumMember":
|
|
578
|
+
case "enumObject":
|
|
579
|
+
return identityNode(type.kind, [type.identity, ...(type.kind === "enumMember" ? [type.member] : [])]);
|
|
580
|
+
case "typeObject":
|
|
581
|
+
return identityNode("type-object", [type.name]);
|
|
582
|
+
case "runtimeType":
|
|
583
|
+
return identityNode("runtime-type", [nested(type.value)]);
|
|
584
|
+
case "optional":
|
|
585
|
+
return identityNode("optional", [nested(type.inner)]);
|
|
586
|
+
case "list":
|
|
587
|
+
return identityNode("list", [type.readonlyView ? "readonly" : "", nested(type.element)]);
|
|
588
|
+
case "set":
|
|
589
|
+
return identityNode("set", [type.readonlyView ? "readonly" : "", nested(type.element)]);
|
|
590
|
+
case "map":
|
|
591
|
+
return identityNode("map", [type.readonlyView ? "readonly" : "", nested(type.key), nested(type.value)]);
|
|
592
|
+
case "record":
|
|
593
|
+
return identityNode("record", [type.readonlyView ? "readonly" : "", nested(type.value)]);
|
|
594
|
+
case "promise":
|
|
595
|
+
return identityNode("promise", [nested(type.value)]);
|
|
596
|
+
case "object":
|
|
597
|
+
return identityNode("object", [type.readonlyView ? "readonly" : "", ...[...type.fields]
|
|
598
|
+
.map(([name, value]) => [name, nested(value)])
|
|
599
|
+
.sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0)
|
|
600
|
+
.map(([name, value]) => identityNode("field", [
|
|
601
|
+
type.readonlyFields?.has(name) ? "readonly" : "",
|
|
602
|
+
type.optionalFields?.has(name) ? "optional" : "",
|
|
603
|
+
name,
|
|
604
|
+
value,
|
|
605
|
+
]))]);
|
|
606
|
+
case "function":
|
|
607
|
+
case "action":
|
|
608
|
+
case "intrinsic":
|
|
609
|
+
return identityNode(type.kind, [
|
|
610
|
+
type.kind === "intrinsic" ? type.name : "",
|
|
611
|
+
type.typeParameterNames?.length ? String(type.typeParameterNames.length) : "",
|
|
612
|
+
// D41 item 61 risk 1: the bound cannot live in the `parameter` kind's
|
|
613
|
+
// identity without breaking its De Bruijn contract, so the callable
|
|
614
|
+
// carries it — otherwise `<T: Text>(T) -> T` and `<U>(U) -> U` would
|
|
615
|
+
// share one identity and assignment between them would go unchecked.
|
|
616
|
+
type.typeParameterBounds?.some((bound) => bound !== null)
|
|
617
|
+
? identityNode("bounds", type.typeParameterBounds.map((bound) => bound ?? ""))
|
|
618
|
+
: "",
|
|
619
|
+
identityNode("parameter-names", includeCallableParameterNames ? type.parameterNames ?? [] : []),
|
|
620
|
+
String(type.requiredParameters),
|
|
621
|
+
identityNode("parameters", type.parameters.map(nested)),
|
|
622
|
+
type.rest ? nested(type.rest) : "",
|
|
623
|
+
nested(type.result),
|
|
624
|
+
]);
|
|
625
|
+
case "extension":
|
|
626
|
+
return identityNode("extension", [type.extensionId, type.family, type.role, type.nominal ?? "", ...[...type.properties]
|
|
627
|
+
.map(([name, value]) => [name, nested(value)])
|
|
628
|
+
.sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0)
|
|
629
|
+
.map(([name, value]) => identityNode("property", [name, value])),
|
|
630
|
+
identityNode("required-properties", [...type.requiredProperties].sort()),
|
|
631
|
+
identityNode("arguments", type.arguments.map(nested)),
|
|
632
|
+
identityNode("metadata", Object.entries(type.metadata ?? {}).sort(([left], [right]) => left.localeCompare(right)).map(([name, value]) => identityNode("entry", [name, value]))),]);
|
|
633
|
+
case "union":
|
|
634
|
+
return identityNode("union", type.members.map(nested).sort());
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
function identityNode(kind, parts = []) {
|
|
638
|
+
return `${kind.length}:${kind}${parts.map((part) => `${part.length}:${part}`).join("")}`;
|
|
639
|
+
}
|
|
640
|
+
export function isInvalidType(type) {
|
|
641
|
+
return type === invalidType;
|
|
642
|
+
}
|
|
643
|
+
export function describeType(type) {
|
|
644
|
+
switch (type.kind) {
|
|
645
|
+
case "any":
|
|
646
|
+
return type.textConvertible ? "string | number | bool | enum | null" : "any";
|
|
647
|
+
case "unknown":
|
|
648
|
+
case "null":
|
|
649
|
+
case "string":
|
|
650
|
+
case "number":
|
|
651
|
+
case "bool":
|
|
652
|
+
return type.kind;
|
|
653
|
+
case "optional":
|
|
654
|
+
return `${["function", "action", "intrinsic", "union"].includes(type.inner.kind) ? `(${describeType(type.inner)})` : describeType(type.inner)}?`;
|
|
655
|
+
case "list":
|
|
656
|
+
return `${type.readonlyView ? "readonly " : ""}List<${describeType(type.element)}>`;
|
|
657
|
+
case "set":
|
|
658
|
+
return `${type.readonlyView ? "readonly " : ""}Set<${describeType(type.element)}>`;
|
|
659
|
+
case "map":
|
|
660
|
+
return `${type.readonlyView ? "readonly " : ""}Map<${describeType(type.key)}, ${describeType(type.value)}>`;
|
|
661
|
+
case "record":
|
|
662
|
+
return `${type.readonlyView ? "readonly " : ""}Record<${describeType(type.value)}>`;
|
|
663
|
+
case "promise":
|
|
664
|
+
return `Promise<${describeType(type.value)}>`;
|
|
665
|
+
case "runtimeType":
|
|
666
|
+
return `Type<${describeType(type.value)}>`;
|
|
667
|
+
case "object":
|
|
668
|
+
return `${type.readonlyView ? "readonly " : ""}{ ${[...type.fields].map(([name, value]) => `${type.readonlyFields?.has(name) ? "readonly " : ""}${name}${type.optionalFields?.has(name) ? "?" : ""}: ${describeType(value)}`).join(", ")} }`;
|
|
669
|
+
case "named":
|
|
670
|
+
return `${type.readonlyView ? "readonly " : ""}${type.name}`;
|
|
671
|
+
case "parameter":
|
|
672
|
+
case "class":
|
|
673
|
+
case "enum":
|
|
674
|
+
return type.name;
|
|
675
|
+
case "enumMember":
|
|
676
|
+
return `${type.name}.${type.member}`;
|
|
677
|
+
case "typeObject":
|
|
678
|
+
case "classConstructor":
|
|
679
|
+
return type.name;
|
|
680
|
+
case "enumObject":
|
|
681
|
+
return `enum ${type.name}`;
|
|
682
|
+
case "extension": {
|
|
683
|
+
if (type.display.kind === "named")
|
|
684
|
+
return type.display.name;
|
|
685
|
+
if (type.display.kind === "constructor")
|
|
686
|
+
return `${type.display.prefix} ${type.display.name}`;
|
|
687
|
+
const display = type.display;
|
|
688
|
+
const properties = [...type.properties]
|
|
689
|
+
.filter(([name, value]) => {
|
|
690
|
+
if (type.requiredProperties.has(name))
|
|
691
|
+
return true;
|
|
692
|
+
const hidden = display.hiddenOptionalProperties?.get(name);
|
|
693
|
+
return hidden === undefined || describeType(value) !== hidden;
|
|
694
|
+
})
|
|
695
|
+
.map(([name, value]) => `${name}${type.requiredProperties.has(name) ? "" : "?"}: ${describeType(value)}`);
|
|
696
|
+
if (properties.length === 0 && type.arguments.length === 0)
|
|
697
|
+
return display.name;
|
|
698
|
+
return `${display.name}<(${properties.join(", ")}) -> ${display.result}${type.arguments.map((argument) => `, ${describeType(argument)}`).join("")}>`;
|
|
699
|
+
}
|
|
700
|
+
case "function":
|
|
701
|
+
case "action":
|
|
702
|
+
case "intrinsic":
|
|
703
|
+
return `${type.kind === "action" ? "action " : ""}${type.typeParameterNames?.length ? `<${type.typeParameterNames.join(", ")}>` : ""}(${[
|
|
704
|
+
...type.parameters.map((parameter, index) => {
|
|
705
|
+
const described = describeType(parameter);
|
|
706
|
+
const labeled = type.parameterNames?.[index] ? `${type.parameterNames[index]}: ${described}` : described;
|
|
707
|
+
return index >= type.requiredParameters
|
|
708
|
+
? `${labeled} = default`
|
|
709
|
+
: labeled;
|
|
710
|
+
}),
|
|
711
|
+
...(type.rest ? [`...${describeType(type.rest)}`] : []),
|
|
712
|
+
].join(", ")}) -> ${describeType(type.result)}`;
|
|
713
|
+
case "union":
|
|
714
|
+
return type.members.map(describeType).join(" | ");
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
function invariant(actual, expected, environment, seen) {
|
|
718
|
+
return isAssignable(actual, expected, environment, new Set(seen))
|
|
719
|
+
&& isAssignable(expected, actual, environment, new Set(seen));
|
|
720
|
+
}
|
|
721
|
+
export function typeContainsParameter(type, matches = () => true) {
|
|
722
|
+
switch (type.kind) {
|
|
723
|
+
case "parameter":
|
|
724
|
+
return matches(type);
|
|
725
|
+
case "optional":
|
|
726
|
+
return typeContainsParameter(type.inner, matches);
|
|
727
|
+
case "list":
|
|
728
|
+
case "set":
|
|
729
|
+
return typeContainsParameter(type.element, matches);
|
|
730
|
+
case "map":
|
|
731
|
+
return typeContainsParameter(type.key, matches) || typeContainsParameter(type.value, matches);
|
|
732
|
+
case "record":
|
|
733
|
+
return typeContainsParameter(type.value, matches);
|
|
734
|
+
case "promise":
|
|
735
|
+
case "runtimeType":
|
|
736
|
+
return typeContainsParameter(type.value, matches);
|
|
737
|
+
case "object":
|
|
738
|
+
return [...type.fields.values()].some((field) => typeContainsParameter(field, matches));
|
|
739
|
+
// D55 rule 121: `Box<T>` mentions T as surely as `List<T>` does, so every
|
|
740
|
+
// rule phrased over "does this type still mention a parameter" — erasure
|
|
741
|
+
// refusals, generic-callable unification — sees it without being told.
|
|
742
|
+
case "named":
|
|
743
|
+
return (type.application?.arguments ?? []).some((argument) => typeContainsParameter(argument, matches));
|
|
744
|
+
case "extension":
|
|
745
|
+
return [...type.properties.values(), ...type.arguments].some((value) => typeContainsParameter(value, matches));
|
|
746
|
+
case "function":
|
|
747
|
+
case "action":
|
|
748
|
+
case "intrinsic":
|
|
749
|
+
// A generic callable owns its parameter indexes; they are not free here.
|
|
750
|
+
if (type.typeParameterNames?.length)
|
|
751
|
+
return false;
|
|
752
|
+
return type.parameters.some((parameter) => typeContainsParameter(parameter, matches))
|
|
753
|
+
|| (type.rest ? typeContainsParameter(type.rest, matches) : false)
|
|
754
|
+
|| typeContainsParameter(type.result, matches);
|
|
755
|
+
case "union":
|
|
756
|
+
return type.members.some((member) => typeContainsParameter(member, matches));
|
|
757
|
+
default:
|
|
758
|
+
return false;
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
// Mirrors the recursive positions that emitTypeCheck actually inspects. A
|
|
762
|
+
// Type<T> value carries a compiler-known checker, but the checker object itself
|
|
763
|
+
// has no runtime identity for T, so accepting it in one of these positions
|
|
764
|
+
// would otherwise emit a predicate that can never be sound.
|
|
765
|
+
export function typeContainsRuntimeTypeCheck(type) {
|
|
766
|
+
switch (type.kind) {
|
|
767
|
+
case "runtimeType":
|
|
768
|
+
return true;
|
|
769
|
+
case "optional":
|
|
770
|
+
return typeContainsRuntimeTypeCheck(type.inner);
|
|
771
|
+
case "list":
|
|
772
|
+
case "set":
|
|
773
|
+
return typeContainsRuntimeTypeCheck(type.element);
|
|
774
|
+
case "map":
|
|
775
|
+
return typeContainsRuntimeTypeCheck(type.key) || typeContainsRuntimeTypeCheck(type.value);
|
|
776
|
+
case "record":
|
|
777
|
+
return typeContainsRuntimeTypeCheck(type.value);
|
|
778
|
+
case "object":
|
|
779
|
+
return [...type.fields.values()].some(typeContainsRuntimeTypeCheck);
|
|
780
|
+
// D55 rule 124: `Box<Type<User>>` puts the carrier in a runtime-validated
|
|
781
|
+
// field just as `type Holder: t: Type<User>` does, and is refused by the
|
|
782
|
+
// same VEL4022 at the position that wrote it — no new mechanism.
|
|
783
|
+
case "named":
|
|
784
|
+
return (type.application?.arguments ?? []).some(typeContainsRuntimeTypeCheck);
|
|
785
|
+
case "union":
|
|
786
|
+
return type.members.some(typeContainsRuntimeTypeCheck);
|
|
787
|
+
default:
|
|
788
|
+
// Promise and callable checks intentionally validate only their runtime
|
|
789
|
+
// carrier, just as they already do for every other erased inner type.
|
|
790
|
+
return false;
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
export function substituteTypeParameters(type, bindings) {
|
|
794
|
+
switch (type.kind) {
|
|
795
|
+
case "parameter":
|
|
796
|
+
return bindings[type.index] ?? unknownType;
|
|
797
|
+
case "optional":
|
|
798
|
+
return optionalOf(substituteTypeParameters(type.inner, bindings));
|
|
799
|
+
case "list":
|
|
800
|
+
case "set": {
|
|
801
|
+
const element = substituteTypeParameters(type.element, bindings);
|
|
802
|
+
return element === type.element ? type : { ...type, element };
|
|
803
|
+
}
|
|
804
|
+
case "map": {
|
|
805
|
+
const key = substituteTypeParameters(type.key, bindings);
|
|
806
|
+
const value = substituteTypeParameters(type.value, bindings);
|
|
807
|
+
return key === type.key && value === type.value ? type : { ...type, key, value };
|
|
808
|
+
}
|
|
809
|
+
case "record": {
|
|
810
|
+
const value = substituteTypeParameters(type.value, bindings);
|
|
811
|
+
return value === type.value ? type : { ...type, value };
|
|
812
|
+
}
|
|
813
|
+
case "promise": {
|
|
814
|
+
const value = substituteTypeParameters(type.value, bindings);
|
|
815
|
+
return value === type.value ? type : { kind: "promise", value };
|
|
816
|
+
}
|
|
817
|
+
case "runtimeType": {
|
|
818
|
+
const value = substituteTypeParameters(type.value, bindings);
|
|
819
|
+
return value === type.value ? type : { kind: "runtimeType", value };
|
|
820
|
+
}
|
|
821
|
+
case "object":
|
|
822
|
+
return { ...type, fields: new Map([...type.fields].map(([name, value]) => [name, substituteTypeParameters(value, bindings)])) };
|
|
823
|
+
// D55 rule 121: substituting inside an application rebuilds it through the
|
|
824
|
+
// one constructor, so the identity a generic `def` produces for `Box<T>`
|
|
825
|
+
// with `T := string` is the identity the analyzer registered for a written
|
|
826
|
+
// `Box<string>`. Two spellings of one instantiation cannot drift apart.
|
|
827
|
+
case "named": {
|
|
828
|
+
if (!type.application)
|
|
829
|
+
return type;
|
|
830
|
+
const arguments_ = type.application.arguments.map((argument) => substituteTypeParameters(argument, bindings));
|
|
831
|
+
if (arguments_.every((argument, index) => argument === type.application.arguments[index]))
|
|
832
|
+
return type;
|
|
833
|
+
return genericApplicationType(type.application.declaration, type.application.name, arguments_, type.readonlyView === true);
|
|
834
|
+
}
|
|
835
|
+
case "extension":
|
|
836
|
+
return {
|
|
837
|
+
...type,
|
|
838
|
+
properties: new Map([...type.properties].map(([name, value]) => [name, substituteTypeParameters(value, bindings)])),
|
|
839
|
+
arguments: type.arguments.map((argument) => substituteTypeParameters(argument, bindings)),
|
|
840
|
+
};
|
|
841
|
+
case "function":
|
|
842
|
+
case "action":
|
|
843
|
+
case "intrinsic":
|
|
844
|
+
if (type.typeParameterNames?.length)
|
|
845
|
+
return type;
|
|
846
|
+
return {
|
|
847
|
+
...type,
|
|
848
|
+
parameters: type.parameters.map((parameter) => substituteTypeParameters(parameter, bindings)),
|
|
849
|
+
...(type.rest ? { rest: substituteTypeParameters(type.rest, bindings) } : {}),
|
|
850
|
+
result: substituteTypeParameters(type.result, bindings),
|
|
851
|
+
};
|
|
852
|
+
case "union":
|
|
853
|
+
return unionOf(type.members.map((member) => substituteTypeParameters(member, bindings)));
|
|
854
|
+
default:
|
|
855
|
+
return type;
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
export function bindNamedTypeParameters(type, parameters) {
|
|
859
|
+
switch (type.kind) {
|
|
860
|
+
case "named": {
|
|
861
|
+
const bound = !type.identity ? parameters.get(type.name) : undefined;
|
|
862
|
+
if (bound)
|
|
863
|
+
return bound;
|
|
864
|
+
// D55 rule 121: `Box<T>` inside a signature binds T exactly as a bare `T`
|
|
865
|
+
// does; without this the argument stayed a free name and the interface
|
|
866
|
+
// published a different type than the body was checked against.
|
|
867
|
+
return type.application
|
|
868
|
+
? { ...type, application: { ...type.application, arguments: type.application.arguments.map((argument) => bindNamedTypeParameters(argument, parameters)) } }
|
|
869
|
+
: type;
|
|
870
|
+
}
|
|
871
|
+
case "optional":
|
|
872
|
+
return optionalOf(bindNamedTypeParameters(type.inner, parameters));
|
|
873
|
+
case "list":
|
|
874
|
+
case "set": {
|
|
875
|
+
const element = bindNamedTypeParameters(type.element, parameters);
|
|
876
|
+
return element === type.element ? type : { ...type, element };
|
|
877
|
+
}
|
|
878
|
+
case "map":
|
|
879
|
+
return { ...type, key: bindNamedTypeParameters(type.key, parameters), value: bindNamedTypeParameters(type.value, parameters) };
|
|
880
|
+
case "record":
|
|
881
|
+
return { ...type, value: bindNamedTypeParameters(type.value, parameters) };
|
|
882
|
+
case "promise":
|
|
883
|
+
return { kind: "promise", value: bindNamedTypeParameters(type.value, parameters) };
|
|
884
|
+
case "runtimeType":
|
|
885
|
+
return { kind: "runtimeType", value: bindNamedTypeParameters(type.value, parameters) };
|
|
886
|
+
case "object":
|
|
887
|
+
return { ...type, fields: new Map([...type.fields].map(([name, value]) => [name, bindNamedTypeParameters(value, parameters)])) };
|
|
888
|
+
case "extension":
|
|
889
|
+
return {
|
|
890
|
+
...type,
|
|
891
|
+
properties: new Map([...type.properties].map(([name, value]) => [name, bindNamedTypeParameters(value, parameters)])),
|
|
892
|
+
arguments: type.arguments.map((argument) => bindNamedTypeParameters(argument, parameters)),
|
|
893
|
+
};
|
|
894
|
+
case "function":
|
|
895
|
+
case "action":
|
|
896
|
+
case "intrinsic":
|
|
897
|
+
if (type.typeParameterNames?.length)
|
|
898
|
+
return type;
|
|
899
|
+
return {
|
|
900
|
+
...type,
|
|
901
|
+
parameters: type.parameters.map((parameter) => bindNamedTypeParameters(parameter, parameters)),
|
|
902
|
+
...(type.rest ? { rest: bindNamedTypeParameters(type.rest, parameters) } : {}),
|
|
903
|
+
result: bindNamedTypeParameters(type.result, parameters),
|
|
904
|
+
};
|
|
905
|
+
case "union":
|
|
906
|
+
return { kind: "union", members: type.members.map((member) => bindNamedTypeParameters(member, parameters)) };
|
|
907
|
+
default:
|
|
908
|
+
return type;
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
/**
|
|
912
|
+
* D51 item NEW-D3: `unknown` never *binds* a parameter — merging it would erase
|
|
913
|
+
* every concrete actual at the other positions — but the site must still be
|
|
914
|
+
* remembered. A bounded parameter that only `unknown` ever reached is solved to
|
|
915
|
+
* `unknown`, which satisfies no bound; dropping the site silently let `unknown`
|
|
916
|
+
* through every bound and on into the implicit-conversion the bound forbids.
|
|
917
|
+
* Callers that check bounds pass this sink; the rest may ignore it.
|
|
918
|
+
*/
|
|
919
|
+
export function unifyTypeParameters(pattern, actual, bindings, fieldsOf = () => null, unknownParameters) {
|
|
920
|
+
unifyInto(pattern, actual, bindings, fieldsOf, unknownParameters);
|
|
921
|
+
}
|
|
922
|
+
function unifyInto(pattern, actual, bindings, fieldsOf, unknownParameters) {
|
|
923
|
+
const unifyTypeParameters = (nextPattern, nextActual) => unifyInto(nextPattern, nextActual, bindings, fieldsOf, unknownParameters);
|
|
924
|
+
if (isInvalidType(actual))
|
|
925
|
+
return;
|
|
926
|
+
if (pattern.kind === "parameter") {
|
|
927
|
+
if (actual.kind === "unknown") {
|
|
928
|
+
unknownParameters?.add(pattern.index);
|
|
929
|
+
return;
|
|
930
|
+
}
|
|
931
|
+
const existing = bindings[pattern.index];
|
|
932
|
+
bindings[pattern.index] = existing ? mergeTypes(existing, actual) : actual;
|
|
933
|
+
return;
|
|
934
|
+
}
|
|
935
|
+
if (pattern.kind === "optional") {
|
|
936
|
+
if (actual.kind === "null")
|
|
937
|
+
return;
|
|
938
|
+
if (actual.kind === "optional")
|
|
939
|
+
return unifyTypeParameters(pattern.inner, actual.inner);
|
|
940
|
+
if (actual.kind === "union") {
|
|
941
|
+
const remaining = actual.members.filter((member) => member.kind !== "null");
|
|
942
|
+
if (remaining.length === 0)
|
|
943
|
+
return;
|
|
944
|
+
return unifyTypeParameters(pattern.inner, unionOf(remaining));
|
|
945
|
+
}
|
|
946
|
+
return unifyTypeParameters(pattern.inner, actual);
|
|
947
|
+
}
|
|
948
|
+
if (pattern.kind === "union") {
|
|
949
|
+
const concrete = pattern.members.filter((member) => !typeContainsParameter(member));
|
|
950
|
+
const actualMembers = actual.kind === "union" ? actual.members : [actual];
|
|
951
|
+
const remaining = actualMembers.filter((member) => !concrete.some((covered) => sameType(covered, member)));
|
|
952
|
+
if (remaining.length === 0)
|
|
953
|
+
return;
|
|
954
|
+
for (const member of pattern.members) {
|
|
955
|
+
if (typeContainsParameter(member))
|
|
956
|
+
unifyTypeParameters(member, unionOf(remaining));
|
|
957
|
+
}
|
|
958
|
+
return;
|
|
959
|
+
}
|
|
960
|
+
if ((pattern.kind === "list" && actual.kind === "list") || (pattern.kind === "set" && actual.kind === "set")) {
|
|
961
|
+
return unifyTypeParameters(pattern.element, actual.element);
|
|
962
|
+
}
|
|
963
|
+
if (pattern.kind === "map" && actual.kind === "map") {
|
|
964
|
+
unifyTypeParameters(pattern.key, actual.key);
|
|
965
|
+
unifyTypeParameters(pattern.value, actual.value);
|
|
966
|
+
return;
|
|
967
|
+
}
|
|
968
|
+
if (pattern.kind === "record" && actual.kind === "record") {
|
|
969
|
+
return unifyTypeParameters(pattern.value, actual.value);
|
|
970
|
+
}
|
|
971
|
+
if (pattern.kind === "promise" && actual.kind === "promise") {
|
|
972
|
+
return unifyTypeParameters(pattern.value, actual.value);
|
|
973
|
+
}
|
|
974
|
+
if (pattern.kind === "runtimeType") {
|
|
975
|
+
const value = runtimeTypeValue(actual);
|
|
976
|
+
if (value)
|
|
977
|
+
return unifyTypeParameters(pattern.value, value);
|
|
978
|
+
}
|
|
979
|
+
// D55 rule 121: `def unwrap<T>(box: Box<T>) -> T` solves T from the applied
|
|
980
|
+
// argument. Two applications unify only when they apply the same declaration,
|
|
981
|
+
// which is the nominal rule records already follow.
|
|
982
|
+
if (pattern.kind === "named" && pattern.application) {
|
|
983
|
+
if (actual.kind === "named" && actual.application
|
|
984
|
+
&& pattern.application.declaration === actual.application.declaration) {
|
|
985
|
+
for (let index = 0; index < pattern.application.arguments.length; index += 1) {
|
|
986
|
+
const provided = actual.application.arguments[index];
|
|
987
|
+
if (provided)
|
|
988
|
+
unifyTypeParameters(pattern.application.arguments[index], provided);
|
|
989
|
+
}
|
|
990
|
+
return;
|
|
991
|
+
}
|
|
992
|
+
// A record literal is the argument a call most often actually passes, and
|
|
993
|
+
// it carries no application to pair with. The instantiation's own field
|
|
994
|
+
// table still names where each parameter stands, so the literal's fields
|
|
995
|
+
// solve them the same way an `object` pattern's do.
|
|
996
|
+
const fields = actual.kind === "object" ? actual.fields : null;
|
|
997
|
+
const declared = pattern.identity ? fieldsOf(pattern.identity) : null;
|
|
998
|
+
if (!fields || !declared)
|
|
999
|
+
return;
|
|
1000
|
+
for (const [name, field] of declared) {
|
|
1001
|
+
const provided = fields.get(name);
|
|
1002
|
+
if (provided)
|
|
1003
|
+
unifyTypeParameters(field, provided);
|
|
1004
|
+
}
|
|
1005
|
+
return;
|
|
1006
|
+
}
|
|
1007
|
+
if (pattern.kind === "object") {
|
|
1008
|
+
const fields = actual.kind === "object" ? actual.fields
|
|
1009
|
+
: actual.kind === "named" ? fieldsOf(actual.identity ?? actual.name)
|
|
1010
|
+
: null;
|
|
1011
|
+
if (!fields)
|
|
1012
|
+
return;
|
|
1013
|
+
for (const [name, field] of pattern.fields) {
|
|
1014
|
+
const provided = fields.get(name);
|
|
1015
|
+
if (provided)
|
|
1016
|
+
unifyTypeParameters(field, provided);
|
|
1017
|
+
}
|
|
1018
|
+
return;
|
|
1019
|
+
}
|
|
1020
|
+
if (pattern.kind === "extension" && actual.kind === "extension"
|
|
1021
|
+
&& pattern.extensionId === actual.extensionId && pattern.family === actual.family) {
|
|
1022
|
+
for (const [name, property] of pattern.properties) {
|
|
1023
|
+
const provided = actual.properties.get(name);
|
|
1024
|
+
if (provided)
|
|
1025
|
+
unifyTypeParameters(property, provided);
|
|
1026
|
+
}
|
|
1027
|
+
for (let index = 0; index < pattern.arguments.length; index += 1) {
|
|
1028
|
+
const provided = actual.arguments[index];
|
|
1029
|
+
if (provided)
|
|
1030
|
+
unifyTypeParameters(pattern.arguments[index], provided);
|
|
1031
|
+
}
|
|
1032
|
+
return;
|
|
1033
|
+
}
|
|
1034
|
+
if ((pattern.kind === "function" || pattern.kind === "action" || pattern.kind === "intrinsic")
|
|
1035
|
+
&& (actual.kind === "function" || actual.kind === "action" || actual.kind === "intrinsic")) {
|
|
1036
|
+
// A generic callable value is sealed: its parameter indexes belong to it.
|
|
1037
|
+
if (actual.typeParameterNames?.length)
|
|
1038
|
+
return;
|
|
1039
|
+
for (let index = 0; index < pattern.parameters.length; index += 1) {
|
|
1040
|
+
const provided = actual.parameters[index] ?? actual.rest;
|
|
1041
|
+
if (provided)
|
|
1042
|
+
unifyTypeParameters(pattern.parameters[index], provided);
|
|
1043
|
+
}
|
|
1044
|
+
if (pattern.rest && actual.rest)
|
|
1045
|
+
unifyTypeParameters(pattern.rest, actual.rest);
|
|
1046
|
+
unifyTypeParameters(pattern.result, actual.result);
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
/** Every declared bound of `actual` must be promised by `expected`'s. */
|
|
1050
|
+
function typeParameterBoundsAccept(actual, expected) {
|
|
1051
|
+
const count = actual.typeParameterNames?.length ?? 0;
|
|
1052
|
+
for (let index = 0; index < count; index += 1) {
|
|
1053
|
+
if (!boundAccepts(actual.typeParameterBounds?.[index] ?? null, expected.typeParameterBounds?.[index] ?? null))
|
|
1054
|
+
return false;
|
|
1055
|
+
}
|
|
1056
|
+
return true;
|
|
1057
|
+
}
|
|
1058
|
+
/**
|
|
1059
|
+
* Reports every solved binding its declared bound rejects. `satisfiesBound` is
|
|
1060
|
+
* the environment's decision procedure, so the three predicates that answer a
|
|
1061
|
+
* bound live in exactly one place (the analyzer).
|
|
1062
|
+
*/
|
|
1063
|
+
export function collectGenericBoundViolations(callable, bindings, satisfiesBound, unknownParameters) {
|
|
1064
|
+
return collectTypeArgumentBoundViolations(callable.typeParameterNames, callable.typeParameterBounds, bindings, satisfiesBound, unknownParameters);
|
|
1065
|
+
}
|
|
1066
|
+
/**
|
|
1067
|
+
* D55 rule 124: the same judgment for a declaration that is not a callable —
|
|
1068
|
+
* `type Box<T: Data>` applied to an argument. The callable form above is now a
|
|
1069
|
+
* thin caller, so a bound is decided in exactly one place no matter which
|
|
1070
|
+
* declaration form carries it.
|
|
1071
|
+
*/
|
|
1072
|
+
export function collectTypeArgumentBoundViolations(names, bounds, bindings, satisfiesBound, unknownParameters) {
|
|
1073
|
+
if (!bounds?.some((bound) => bound !== null))
|
|
1074
|
+
return [];
|
|
1075
|
+
const violations = [];
|
|
1076
|
+
for (let index = 0; index < bounds.length; index += 1) {
|
|
1077
|
+
const bound = bounds[index];
|
|
1078
|
+
if (!bound)
|
|
1079
|
+
continue;
|
|
1080
|
+
// A parameter no argument mentioned substitutes `unknown`, which satisfies
|
|
1081
|
+
// nothing; checking it would report every call that leaves a parameter
|
|
1082
|
+
// open. A parameter an `unknown` argument *did* reach is a different fact:
|
|
1083
|
+
// it is solved, and solved to the one type no bound admits (NEW-D3).
|
|
1084
|
+
const solved = bindings[index] ?? (unknownParameters?.has(index) ? unknownType : null);
|
|
1085
|
+
if (solved == null)
|
|
1086
|
+
continue;
|
|
1087
|
+
if (!satisfiesBound(solved, bound)) {
|
|
1088
|
+
violations.push({ index, name: names?.[index] ?? `#${index + 1}`, bound, solved });
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
return violations;
|
|
1092
|
+
}
|
|
1093
|
+
export function instantiateGenericCallable(actual, expected, environment, violations) {
|
|
1094
|
+
const bindings = Array.from({ length: actual.typeParameterNames?.length ?? 0 }, () => null);
|
|
1095
|
+
const unknownParameters = new Set();
|
|
1096
|
+
const fieldsOf = (identity) => environment.fieldsOf(identity);
|
|
1097
|
+
for (let index = 0; index < actual.parameters.length; index += 1) {
|
|
1098
|
+
const provided = expected.parameters[index] ?? expected.rest;
|
|
1099
|
+
if (provided)
|
|
1100
|
+
unifyTypeParameters(actual.parameters[index], provided, bindings, fieldsOf, unknownParameters);
|
|
1101
|
+
}
|
|
1102
|
+
if (actual.rest) {
|
|
1103
|
+
for (let index = actual.parameters.length; index < expected.parameters.length; index += 1) {
|
|
1104
|
+
unifyTypeParameters(actual.rest, expected.parameters[index], bindings, fieldsOf, unknownParameters);
|
|
1105
|
+
}
|
|
1106
|
+
if (expected.rest)
|
|
1107
|
+
unifyTypeParameters(actual.rest, expected.rest, bindings, fieldsOf, unknownParameters);
|
|
1108
|
+
}
|
|
1109
|
+
// The result position is deliberately outside the `unknown` sink: an expected
|
|
1110
|
+
// result of `unknown` says "the consumer accepts anything", not "the callee
|
|
1111
|
+
// is handed an unvalidated value". Only the input positions can force a
|
|
1112
|
+
// bounded parameter to be `unknown` inside the body.
|
|
1113
|
+
unifyTypeParameters(actual.result, expected.result, bindings, fieldsOf);
|
|
1114
|
+
if (violations && environment.satisfiesBound) {
|
|
1115
|
+
const decide = environment.satisfiesBound.bind(environment);
|
|
1116
|
+
violations.push(...collectGenericBoundViolations(actual, bindings, decide, unknownParameters));
|
|
1117
|
+
}
|
|
1118
|
+
const { typeParameterNames: _erased, typeParameterBounds: _erasedBounds, ...base } = actual;
|
|
1119
|
+
return {
|
|
1120
|
+
...base,
|
|
1121
|
+
parameters: actual.parameters.map((parameter) => substituteTypeParameters(parameter, bindings)),
|
|
1122
|
+
...(actual.rest ? { rest: substituteTypeParameters(actual.rest, bindings) } : {}),
|
|
1123
|
+
result: substituteTypeParameters(actual.result, bindings),
|
|
1124
|
+
};
|
|
1125
|
+
}
|
|
1126
|
+
function callableInputsAssignable(actual, expected, environment, seen) {
|
|
1127
|
+
if (actual.requiredParameters > expected.requiredParameters)
|
|
1128
|
+
return false;
|
|
1129
|
+
if (!actual.rest && (expected.rest || actual.parameters.length < expected.parameters.length))
|
|
1130
|
+
return false;
|
|
1131
|
+
for (let index = 0; index < expected.parameters.length; index += 1) {
|
|
1132
|
+
const accepted = actual.parameters[index] ?? actual.rest;
|
|
1133
|
+
if (!accepted || !isAssignable(expected.parameters[index], accepted, environment, new Set(seen)))
|
|
1134
|
+
return false;
|
|
1135
|
+
}
|
|
1136
|
+
if (expected.rest) {
|
|
1137
|
+
if (!actual.rest)
|
|
1138
|
+
return false;
|
|
1139
|
+
for (let index = expected.parameters.length; index < actual.parameters.length; index += 1) {
|
|
1140
|
+
if (!isAssignable(expected.rest, actual.parameters[index], environment, new Set(seen)))
|
|
1141
|
+
return false;
|
|
1142
|
+
}
|
|
1143
|
+
if (!isAssignable(expected.rest, actual.rest, environment, new Set(seen)))
|
|
1144
|
+
return false;
|
|
1145
|
+
}
|
|
1146
|
+
return true;
|
|
1147
|
+
}
|
|
1148
|
+
function objectFieldsAssignable(actual, expected, environment, seen, actualReadonly = undefined, expectedReadonly = undefined, actualOptional = undefined, expectedOptional = undefined) {
|
|
1149
|
+
for (const [name, expectedType] of expected) {
|
|
1150
|
+
const actualType = actual.get(name);
|
|
1151
|
+
if (!actualType) {
|
|
1152
|
+
if (expectedType.kind === "optional" || expectedOptional?.has(name))
|
|
1153
|
+
continue;
|
|
1154
|
+
return false;
|
|
1155
|
+
}
|
|
1156
|
+
if (actualOptional?.has(name) && expectedType.kind !== "optional" && !expectedOptional?.has(name))
|
|
1157
|
+
return false;
|
|
1158
|
+
if (expectedReadonly?.has(name)) {
|
|
1159
|
+
if (!isAssignable(actualType, expectedType, environment, new Set(seen)))
|
|
1160
|
+
return false;
|
|
1161
|
+
}
|
|
1162
|
+
else if (actualReadonly?.has(name) || !invariant(actualType, expectedType, environment, seen))
|
|
1163
|
+
return false;
|
|
1164
|
+
}
|
|
1165
|
+
return true;
|
|
1166
|
+
}
|
|
1167
|
+
function writableFieldsAssignable(actual, expected, environment, seen) {
|
|
1168
|
+
return objectFieldsAssignable(actual, expected, environment, seen);
|
|
1169
|
+
}
|
|
1170
|
+
//# sourceMappingURL=types.js.map
|