@velarscript/compiler 0.11.1 → 0.12.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/dist/analyzer.d.ts +26 -19
- package/dist/analyzer.d.ts.map +1 -1
- package/dist/analyzer.js +110 -192
- package/dist/analyzer.js.map +1 -1
- package/dist/application-package-host.d.ts +1 -7
- package/dist/application-package-host.d.ts.map +1 -1
- package/dist/application-package-host.js +1 -1
- package/dist/ast.d.ts +24 -5
- package/dist/ast.d.ts.map +1 -1
- package/dist/ast.js +3 -0
- package/dist/ast.js.map +1 -1
- package/dist/core-vocabulary.d.ts +2 -2
- package/dist/emitter.d.ts +7 -0
- package/dist/emitter.d.ts.map +1 -1
- package/dist/emitter.js +56 -0
- package/dist/emitter.js.map +1 -1
- package/dist/extension.d.ts +2 -0
- package/dist/extension.d.ts.map +1 -1
- package/dist/formatter.js +11 -0
- package/dist/formatter.js.map +1 -1
- package/dist/framework-host.d.ts +0 -1
- package/dist/framework-host.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/lexer.d.ts.map +1 -1
- package/dist/lexer.js +17 -10
- package/dist/lexer.js.map +1 -1
- package/dist/parser.d.ts +15 -0
- package/dist/parser.d.ts.map +1 -1
- package/dist/parser.js +73 -20
- package/dist/parser.js.map +1 -1
- package/dist/semantic.d.ts.map +1 -1
- package/dist/semantic.js +3 -0
- package/dist/semantic.js.map +1 -1
- package/dist/token.d.ts +8 -1
- package/dist/token.d.ts.map +1 -1
- package/dist/token.js.map +1 -1
- package/package.json +1 -1
package/dist/analyzer.js
CHANGED
|
@@ -145,16 +145,15 @@ const builtinTypeNames = new Set(["string", "number", "bool", "null", "unknown",
|
|
|
145
145
|
const asyncResultAnnotationMessage = "An async result annotation in a declaration names the resolved value; write '-> T', not '-> Promise<T>'";
|
|
146
146
|
const memberNarrowingPrefix = "\u0000member:";
|
|
147
147
|
/**
|
|
148
|
-
*
|
|
149
|
-
*
|
|
150
|
-
* `@dispose` is
|
|
148
|
+
* The emitted member behind a class's `@dispose:` block. The key is not a
|
|
149
|
+
* source-shaped identifier, so no author member can collide with it:
|
|
150
|
+
* `@dispose` is compiler-owned, not part of the author's namespace.
|
|
151
151
|
*/
|
|
152
152
|
export const disposeMemberKey = "__velar:dispose";
|
|
153
153
|
/**
|
|
154
|
-
*
|
|
155
|
-
*
|
|
156
|
-
*
|
|
157
|
-
* author call can reach it.
|
|
154
|
+
* The emitted member behind a class's `@iterate:` block, under the same kind of
|
|
155
|
+
* key and for the same reason: `@iterate` is compiler-owned, so no author
|
|
156
|
+
* member can answer it by accident and no author call can reach it.
|
|
158
157
|
*/
|
|
159
158
|
export const iterateMemberKey = "__velar:iterate";
|
|
160
159
|
/** What each bound admits, written the way a rejected call needs to hear it. */
|
|
@@ -593,8 +592,6 @@ export class Analyzer {
|
|
|
593
592
|
inferredFunctionResultTypes = new Map();
|
|
594
593
|
finalizeFunctionResultInference;
|
|
595
594
|
logicalConditionNarrowings = new Map();
|
|
596
|
-
collectionInferenceGroups = new WeakMap();
|
|
597
|
-
collectionInferenceTypes = new WeakMap();
|
|
598
595
|
privateFields = new Map();
|
|
599
596
|
privateGetters = new Map();
|
|
600
597
|
privateMethods = new Map();
|
|
@@ -1418,6 +1415,10 @@ export class Analyzer {
|
|
|
1418
1415
|
return instantiated;
|
|
1419
1416
|
return this.extensionFieldsOf(identity);
|
|
1420
1417
|
}
|
|
1418
|
+
enumValuesOf(identity) {
|
|
1419
|
+
const values = this.enums.get(identity)?.members;
|
|
1420
|
+
return values ? [...values] : null;
|
|
1421
|
+
}
|
|
1421
1422
|
/**
|
|
1422
1423
|
* D55 rule 121: an instantiation's field table is the declaration's fields
|
|
1423
1424
|
* with the arguments substituted, registered under the instantiation's own
|
|
@@ -1534,11 +1535,10 @@ export class Analyzer {
|
|
|
1534
1535
|
if (!info)
|
|
1535
1536
|
return null;
|
|
1536
1537
|
const built = genericApplicationType(info.identity, info.name, application.arguments.map(resolveArgument), type.readonlyView === true);
|
|
1537
|
-
// One object per instantiation, not one per resolution.
|
|
1538
|
-
//
|
|
1539
|
-
//
|
|
1540
|
-
//
|
|
1541
|
-
// and recur without end.
|
|
1538
|
+
// One object per instantiation, not one per resolution. A recursive
|
|
1539
|
+
// generic re-enters resolution, so handing back a fresh equal object each
|
|
1540
|
+
// time would defeat any traversal that memoizes by type identity and recur
|
|
1541
|
+
// without end.
|
|
1542
1542
|
const key = `${built.identity}${built.readonlyView ? "\u0000readonly" : ""}`;
|
|
1543
1543
|
const cached = this.canonicalGenericApplications.get(key);
|
|
1544
1544
|
if (cached)
|
|
@@ -2583,9 +2583,6 @@ export class Analyzer {
|
|
|
2583
2583
|
}
|
|
2584
2584
|
const annotated = statement.type ? this.resolveAnnotation(statement.type) : null;
|
|
2585
2585
|
const annotationValid = statement.type ? this.validateTypeReference(statement.type) : true;
|
|
2586
|
-
const aliasedBinding = !annotated && statement.initializer.kind === "IdentifierExpression"
|
|
2587
|
-
? this.lookup(statement.initializer.name)
|
|
2588
|
-
: null;
|
|
2589
2586
|
const actual = this.inferExpression(statement.initializer, annotationValid ? annotated ?? unknownType : invalidType);
|
|
2590
2587
|
// D44 rule 71: an unannotated alias of an assignment-established fact
|
|
2591
2588
|
// declares the source's domain and re-establishes the fact below, so
|
|
@@ -2599,6 +2596,7 @@ export class Analyzer {
|
|
|
2599
2596
|
const contract = annotationValid ? annotated ?? inferredStorage : invalidType;
|
|
2600
2597
|
if (annotationValid)
|
|
2601
2598
|
this.requireAssignable(actual, declared, statement.initializer.span);
|
|
2599
|
+
this.requireSettledCollectionElement(statement.initializer, declared, annotated !== null);
|
|
2602
2600
|
this.declarePattern(statement.pattern, statement.binding === "let", declared, contract);
|
|
2603
2601
|
// D51 rule 101: an alias of an owned handle — or a closure over one —
|
|
2604
2602
|
// is the same resource under a second name, so it inherits the
|
|
@@ -2620,14 +2618,6 @@ export class Analyzer {
|
|
|
2620
2618
|
if (binding?.span.start === statement.pattern.span.start && binding.span.end === statement.pattern.span.end) {
|
|
2621
2619
|
if (this.expandAliases(actual).kind === "promise")
|
|
2622
2620
|
this.promiseInitializerBindings.add(binding);
|
|
2623
|
-
if (!annotated) {
|
|
2624
|
-
const aliasedGroup = aliasedBinding ? this.collectionInferenceGroups.get(aliasedBinding) : null;
|
|
2625
|
-
if (aliasedGroup)
|
|
2626
|
-
this.joinCollectionInference(statement.pattern.name, binding, aliasedGroup);
|
|
2627
|
-
else if (this.isFreshUnresolvedCollection(statement.initializer, declared)) {
|
|
2628
|
-
this.joinCollectionInference(statement.pattern.name, binding, this.createCollectionInference(declared));
|
|
2629
|
-
}
|
|
2630
|
-
}
|
|
2631
2621
|
}
|
|
2632
2622
|
}
|
|
2633
2623
|
break;
|
|
@@ -4069,12 +4059,11 @@ export class Analyzer {
|
|
|
4069
4059
|
return this.inferredFunctionResultSeeds.get(this.iterationResultKey(block)) ?? inferredResultPlaceholderType;
|
|
4070
4060
|
}
|
|
4071
4061
|
/**
|
|
4072
|
-
*
|
|
4062
|
+
* `@iterate:` answers the compiler's question "what does
|
|
4073
4063
|
* iterating you mean?" with a collection the language already iterates. It
|
|
4074
|
-
* shares `@dispose:`'s path
|
|
4075
|
-
* contract, not a method
|
|
4076
|
-
*
|
|
4077
|
-
* consumers reads it synchronously.
|
|
4064
|
+
* shares `@dispose:`'s compiler-name path, then supplies its own role: it is
|
|
4065
|
+
* a contract, not a method, it produces a value, and it may not `await`,
|
|
4066
|
+
* because every one of the eight consumers reads it synchronously.
|
|
4078
4067
|
*/
|
|
4079
4068
|
analyzeClassIterate(statement, block, baseName) {
|
|
4080
4069
|
this.enterScope();
|
|
@@ -4545,9 +4534,12 @@ export class Analyzer {
|
|
|
4545
4534
|
this.declareBinding("self", false, selfType, statement.span, true);
|
|
4546
4535
|
}
|
|
4547
4536
|
for (const parameter of statement.parameters) {
|
|
4548
|
-
const
|
|
4549
|
-
|
|
4550
|
-
|
|
4537
|
+
const contextualType = !parameter.type && parameter.defaultValue
|
|
4538
|
+
? this.contextualFunctionParameterDefault(statement, parameter)
|
|
4539
|
+
: null;
|
|
4540
|
+
const type = contextualType ?? this.resolveAnnotation(parameter.type);
|
|
4541
|
+
const valid = contextualType !== null || (parameter.type ? this.validateTypeReference(parameter.type) : true);
|
|
4542
|
+
if (parameter.defaultValue && valid && contextualType === null) {
|
|
4551
4543
|
this.requireAssignable(this.inferParameterDefault(parameter.defaultValue, type), type, parameter.defaultValue.span);
|
|
4552
4544
|
}
|
|
4553
4545
|
const declared = valid ? type : invalidType;
|
|
@@ -4590,6 +4582,15 @@ export class Analyzer {
|
|
|
4590
4582
|
this.typeParameterFrames.pop();
|
|
4591
4583
|
this.constructorDepth = outerConstructorDepth;
|
|
4592
4584
|
}
|
|
4585
|
+
/**
|
|
4586
|
+
* A target-owned declaration may use an ordinary default expression as a
|
|
4587
|
+
* typed input descriptor. The target analyzes that expression and returns
|
|
4588
|
+
* the value type visible inside the body; Core owns only the function-scope
|
|
4589
|
+
* plumbing and never learns the descriptor vocabulary.
|
|
4590
|
+
*/
|
|
4591
|
+
contextualFunctionParameterDefault(_statement, _parameter) {
|
|
4592
|
+
return null;
|
|
4593
|
+
}
|
|
4593
4594
|
analyzeBlock(statements, narrowed = new Map()) {
|
|
4594
4595
|
const visible = this.visibleBindings();
|
|
4595
4596
|
this.enterScope();
|
|
@@ -4833,7 +4834,6 @@ export class Analyzer {
|
|
|
4833
4834
|
storageBinding.type = rebound;
|
|
4834
4835
|
targetBinding.storageType = rebound;
|
|
4835
4836
|
targetBinding.type = rebound;
|
|
4836
|
-
this.rebindCollectionInference(statement.target.kind === "IdentifierExpression" ? statement.target.name : "", targetBinding, statement.value, valueType);
|
|
4837
4837
|
}
|
|
4838
4838
|
// D44 rule 71: the assignment establishes the right-hand side's type
|
|
4839
4839
|
// as the location's fact (`x = maybeNull()` establishes nothing —
|
|
@@ -5273,6 +5273,31 @@ export class Analyzer {
|
|
|
5273
5273
|
this.requireAssignable(operand, numberType, expression.operand.span);
|
|
5274
5274
|
return numberType;
|
|
5275
5275
|
}
|
|
5276
|
+
case "RequiredExpression": {
|
|
5277
|
+
// D86 rule 212: `value!` answers "absent here is a bug", so it takes
|
|
5278
|
+
// `T?` to `T` and has nothing to say about a value that already holds
|
|
5279
|
+
// one. The contextual type is the optional of what the consumer wants,
|
|
5280
|
+
// since the unwrap is what removes the question.
|
|
5281
|
+
const value = this.inferExpression(expression.value, optionalOf(this.expandAliases(contextualType)));
|
|
5282
|
+
if (isInvalidType(value))
|
|
5283
|
+
return invalidType;
|
|
5284
|
+
const resolved = this.expandAliases(value);
|
|
5285
|
+
if (resolved.kind === "any")
|
|
5286
|
+
return value;
|
|
5287
|
+
if (resolved.kind === "optional")
|
|
5288
|
+
return resolved.inner;
|
|
5289
|
+
const message = resolved.kind === "unknown"
|
|
5290
|
+
? `'!' unwraps an optional, and ${describeType(value)} is not one; validate it with 'is' or 'parse' before reading it`
|
|
5291
|
+
: resolved.kind === "promise"
|
|
5292
|
+
// The same mistake `try` guards against: the unwrap reached the
|
|
5293
|
+
// Promise rather than the value it resolves to.
|
|
5294
|
+
? `'!' unwraps an optional, and this expression is ${describeType(value)}; write '(await ...)!' so the unwrap reaches the resolved value`
|
|
5295
|
+
: `'!' unwraps an optional, and this value is already ${describeType(value)}; remove the '!'`;
|
|
5296
|
+
this.diagnostics.push(diagnostic("VEL4040", message, expression.span, ...(resolved.kind === "unknown" || resolved.kind === "promise"
|
|
5297
|
+
? []
|
|
5298
|
+
: [mechanicalFix({ start: expression.span.end - 1, end: expression.span.end }, "", "Remove the redundant '!'")])));
|
|
5299
|
+
return value;
|
|
5300
|
+
}
|
|
5276
5301
|
case "TryExpression": {
|
|
5277
5302
|
// D39 item 51: an expected failure is an optional. The inner
|
|
5278
5303
|
// expression is checked against the non-optional shape of whatever the
|
|
@@ -7507,11 +7532,7 @@ export class Analyzer {
|
|
|
7507
7532
|
this.collectionCalls.set(member.span.end, "listAppend");
|
|
7508
7533
|
const argument = argumentAt(0);
|
|
7509
7534
|
const value = inferArgument(0, object.element);
|
|
7510
|
-
if (argument
|
|
7511
|
-
if (!this.refineCollectionInference(object, { kind: "list", element: value }))
|
|
7512
|
-
this.requireAssignable(value, object.element, argument.span);
|
|
7513
|
-
}
|
|
7514
|
-
else if (argument)
|
|
7535
|
+
if (argument)
|
|
7515
7536
|
this.requireAssignable(value, object.element, argument.span);
|
|
7516
7537
|
requireCount(1);
|
|
7517
7538
|
return nullType;
|
|
@@ -7520,11 +7541,7 @@ export class Analyzer {
|
|
|
7520
7541
|
this.collectionCalls.set(member.span.end, "listExtend");
|
|
7521
7542
|
const argument = argumentAt(0);
|
|
7522
7543
|
const source = argument ? this.expandAliases(inferArgument(0)) : unknownType;
|
|
7523
|
-
|
|
7524
|
-
if (argument && source.kind === "list" && object.element.kind === "unknown") {
|
|
7525
|
-
inferred = this.refineCollectionInference(object, source);
|
|
7526
|
-
}
|
|
7527
|
-
if (argument && !inferred)
|
|
7544
|
+
if (argument)
|
|
7528
7545
|
this.requireAssignable(source, object, argument.span);
|
|
7529
7546
|
requireCount(1);
|
|
7530
7547
|
return nullType;
|
|
@@ -7536,11 +7553,7 @@ export class Analyzer {
|
|
|
7536
7553
|
this.requireAssignable(inferArgument(0, numberType), numberType, indexArgument.span);
|
|
7537
7554
|
const argument = argumentAt(1);
|
|
7538
7555
|
const value = inferArgument(1, object.element);
|
|
7539
|
-
if (argument
|
|
7540
|
-
if (!this.refineCollectionInference(object, { kind: "list", element: value }))
|
|
7541
|
-
this.requireAssignable(value, object.element, argument.span);
|
|
7542
|
-
}
|
|
7543
|
-
else if (argument)
|
|
7556
|
+
if (argument)
|
|
7544
7557
|
this.requireAssignable(value, object.element, argument.span);
|
|
7545
7558
|
requireCount(2);
|
|
7546
7559
|
return nullType;
|
|
@@ -7690,11 +7703,12 @@ export class Analyzer {
|
|
|
7690
7703
|
this.collectionCalls.set(member.span.end, "mapSet");
|
|
7691
7704
|
const keyArgument = argumentAt(0);
|
|
7692
7705
|
const valueArgument = argumentAt(1);
|
|
7693
|
-
|
|
7694
|
-
|
|
7695
|
-
|
|
7696
|
-
|
|
7697
|
-
|
|
7706
|
+
// D85 rule 211: the receiver's declared key and value types are the
|
|
7707
|
+
// contextual types of this call, exactly as `List.append` passes its
|
|
7708
|
+
// element type. Without them an empty `[]`, an arrow, or any other
|
|
7709
|
+
// value that reads its shape from context arrives as `unknown`.
|
|
7710
|
+
const key = inferArgument(0, object.key);
|
|
7711
|
+
const value = inferArgument(1, object.value);
|
|
7698
7712
|
if (keyArgument)
|
|
7699
7713
|
this.requireAssignable(key, object.key, keyArgument.span);
|
|
7700
7714
|
if (valueArgument)
|
|
@@ -7706,11 +7720,7 @@ export class Analyzer {
|
|
|
7706
7720
|
this.collectionCalls.set(member.span.end, "mapUpdate");
|
|
7707
7721
|
const argument = argumentAt(0);
|
|
7708
7722
|
const source = argument ? this.expandAliases(inferArgument(0)) : unknownType;
|
|
7709
|
-
|
|
7710
|
-
if (argument && source.kind === "map" && object.key.kind === "unknown" && object.value.kind === "unknown") {
|
|
7711
|
-
inferred = this.refineCollectionInference(object, source);
|
|
7712
|
-
}
|
|
7713
|
-
if (argument && !inferred)
|
|
7723
|
+
if (argument)
|
|
7714
7724
|
this.requireAssignable(source, object, argument.span);
|
|
7715
7725
|
requireCount(1);
|
|
7716
7726
|
return nullType;
|
|
@@ -7818,25 +7828,17 @@ export class Analyzer {
|
|
|
7818
7828
|
const argument = argumentAt(0);
|
|
7819
7829
|
const value = inferArgument(0, object.element);
|
|
7820
7830
|
requireCount(1);
|
|
7821
|
-
if (argument
|
|
7822
|
-
if (!this.refineCollectionInference(object, { kind: "set", element: value }))
|
|
7823
|
-
this.requireAssignable(value, object.element, argument.span);
|
|
7824
|
-
}
|
|
7825
|
-
else if (argument)
|
|
7831
|
+
if (argument)
|
|
7826
7832
|
this.requireAssignable(value, object.element, argument.span);
|
|
7827
7833
|
return nullType;
|
|
7828
7834
|
}
|
|
7829
7835
|
if (member.property === "update") {
|
|
7830
7836
|
this.collectionCalls.set(member.span.end, "setUpdate");
|
|
7831
7837
|
const argument = argumentAt(0);
|
|
7838
|
+
const accepted = { kind: "union", members: [object, { kind: "list", element: object.element }] };
|
|
7832
7839
|
const source = argument ? this.expandAliases(inferArgument(0)) : unknownType;
|
|
7833
|
-
|
|
7834
|
-
|
|
7835
|
-
inferred = this.refineCollectionInference(object, { kind: "set", element: source.element });
|
|
7836
|
-
}
|
|
7837
|
-
if (argument && !inferred) {
|
|
7838
|
-
this.requireAssignable(source, { kind: "union", members: [object, { kind: "list", element: object.element }] }, argument.span);
|
|
7839
|
-
}
|
|
7840
|
+
if (argument)
|
|
7841
|
+
this.requireAssignable(source, accepted, argument.span);
|
|
7840
7842
|
requireCount(1);
|
|
7841
7843
|
return nullType;
|
|
7842
7844
|
}
|
|
@@ -9388,10 +9390,8 @@ export class Analyzer {
|
|
|
9388
9390
|
return null;
|
|
9389
9391
|
}
|
|
9390
9392
|
requireAssignable(actual, expected, valueSpan) {
|
|
9391
|
-
if (this.contextuallyAssignable(actual, expected, valueSpan))
|
|
9392
|
-
this.freezeEscapedCollectionInference(actual, expected);
|
|
9393
|
+
if (this.contextuallyAssignable(actual, expected, valueSpan))
|
|
9393
9394
|
return;
|
|
9394
|
-
}
|
|
9395
9395
|
const expandedActual = this.expandAliases(actual);
|
|
9396
9396
|
const expandedExpected = this.expandAliases(expected);
|
|
9397
9397
|
const expectedCore = expandedExpected.kind === "optional" ? this.expandAliases(expandedExpected.inner) : expandedExpected;
|
|
@@ -9526,64 +9526,6 @@ export class Analyzer {
|
|
|
9526
9526
|
}
|
|
9527
9527
|
return null;
|
|
9528
9528
|
}
|
|
9529
|
-
freezeEscapedCollectionInference(actual, expected, seen = new WeakMap()) {
|
|
9530
|
-
const inference = this.collectionInferenceTypes.get(actual);
|
|
9531
|
-
const expectedInference = this.collectionInferenceTypes.get(expected);
|
|
9532
|
-
if (inference && actual !== expected && !expectedInference)
|
|
9533
|
-
inference.open = false;
|
|
9534
|
-
const expandedActual = this.expandAliases(actual);
|
|
9535
|
-
const expandedExpected = this.expandAliases(expected);
|
|
9536
|
-
const expectedSeen = seen.get(expandedActual) ?? new WeakSet();
|
|
9537
|
-
if (expectedSeen.has(expandedExpected))
|
|
9538
|
-
return;
|
|
9539
|
-
expectedSeen.add(expandedExpected);
|
|
9540
|
-
seen.set(expandedActual, expectedSeen);
|
|
9541
|
-
if (expandedExpected.kind === "optional") {
|
|
9542
|
-
if (expandedActual.kind === "optional")
|
|
9543
|
-
this.freezeEscapedCollectionInference(expandedActual.inner, expandedExpected.inner, seen);
|
|
9544
|
-
else if (expandedActual.kind !== "null")
|
|
9545
|
-
this.freezeEscapedCollectionInference(expandedActual, expandedExpected.inner, seen);
|
|
9546
|
-
return;
|
|
9547
|
-
}
|
|
9548
|
-
if (expandedExpected.kind === "union") {
|
|
9549
|
-
const target = expandedExpected.members.find((member) => isAssignable(expandedActual, member, this));
|
|
9550
|
-
if (target)
|
|
9551
|
-
this.freezeEscapedCollectionInference(expandedActual, target, seen);
|
|
9552
|
-
return;
|
|
9553
|
-
}
|
|
9554
|
-
if (expandedActual.kind === "union") {
|
|
9555
|
-
for (const member of expandedActual.members)
|
|
9556
|
-
this.freezeEscapedCollectionInference(member, expandedExpected, seen);
|
|
9557
|
-
return;
|
|
9558
|
-
}
|
|
9559
|
-
if ((expandedActual.kind === "list" && expandedExpected.kind === "list")
|
|
9560
|
-
|| (expandedActual.kind === "set" && expandedExpected.kind === "set")) {
|
|
9561
|
-
this.freezeEscapedCollectionInference(expandedActual.element, expandedExpected.element, seen);
|
|
9562
|
-
return;
|
|
9563
|
-
}
|
|
9564
|
-
if (expandedActual.kind === "map" && expandedExpected.kind === "map") {
|
|
9565
|
-
this.freezeEscapedCollectionInference(expandedActual.key, expandedExpected.key, seen);
|
|
9566
|
-
this.freezeEscapedCollectionInference(expandedActual.value, expandedExpected.value, seen);
|
|
9567
|
-
return;
|
|
9568
|
-
}
|
|
9569
|
-
if (expandedActual.kind === "record" && expandedExpected.kind === "record") {
|
|
9570
|
-
this.freezeEscapedCollectionInference(expandedActual.value, expandedExpected.value, seen);
|
|
9571
|
-
return;
|
|
9572
|
-
}
|
|
9573
|
-
const actualFields = expandedActual.kind === "object" ? expandedActual.fields
|
|
9574
|
-
: expandedActual.kind === "named" ? this.fieldsOf(expandedActual.identity ?? expandedActual.name)
|
|
9575
|
-
: null;
|
|
9576
|
-
const expectedFields = expandedExpected.kind === "object" ? expandedExpected.fields
|
|
9577
|
-
: expandedExpected.kind === "named" ? this.fieldsOf(expandedExpected.identity ?? expandedExpected.name)
|
|
9578
|
-
: null;
|
|
9579
|
-
if (actualFields && expectedFields) {
|
|
9580
|
-
for (const [name, expectedField] of expectedFields) {
|
|
9581
|
-
const actualField = actualFields.get(name);
|
|
9582
|
-
if (actualField)
|
|
9583
|
-
this.freezeEscapedCollectionInference(actualField, expectedField, seen);
|
|
9584
|
-
}
|
|
9585
|
-
}
|
|
9586
|
-
}
|
|
9587
9529
|
contextuallyAssignable(actual, expected, valueSpan) {
|
|
9588
9530
|
if (isAssignable(actual, expected, this))
|
|
9589
9531
|
return true;
|
|
@@ -10956,6 +10898,26 @@ export class Analyzer {
|
|
|
10956
10898
|
narrowingFrame: null,
|
|
10957
10899
|
} : null;
|
|
10958
10900
|
}
|
|
10901
|
+
/**
|
|
10902
|
+
* D85 rule 207: an empty collection's element type must be settled where the
|
|
10903
|
+
* collection is written — by an annotation, a contextual type, or the
|
|
10904
|
+
* constructor's own arguments. Nothing infers it from a later mutation, so a
|
|
10905
|
+
* binding left with no source is reported at the construction rather than
|
|
10906
|
+
* kept as `unknown` for a following line to fill in.
|
|
10907
|
+
*/
|
|
10908
|
+
requireSettledCollectionElement(initializer, declared, annotated) {
|
|
10909
|
+
if (annotated)
|
|
10910
|
+
return;
|
|
10911
|
+
const type = this.expandAliases(declared);
|
|
10912
|
+
if (!this.isFreshUnresolvedCollection(initializer, type))
|
|
10913
|
+
return;
|
|
10914
|
+
const [spelling, holds, example] = type.kind === "list"
|
|
10915
|
+
? ["[]", "what the List holds", "let items: List<string> = []"]
|
|
10916
|
+
: type.kind === "set"
|
|
10917
|
+
? ["Set()", "what the Set holds", "const tags: Set<string> = Set()"]
|
|
10918
|
+
: ["Map()", "what the Map holds", "const users: Map<string, User> = Map()"];
|
|
10919
|
+
this.diagnostics.push(diagnostic("VEL4039", `Empty '${spelling}' requires an explicit type; nothing at this position says ${holds} — write '${example}'`, initializer.span));
|
|
10920
|
+
}
|
|
10959
10921
|
isFreshUnresolvedCollection(expression, type) {
|
|
10960
10922
|
const unresolved = type.kind === "list" ? type.element.kind === "unknown"
|
|
10961
10923
|
: type.kind === "set" ? type.element.kind === "unknown"
|
|
@@ -10963,71 +10925,20 @@ export class Analyzer {
|
|
|
10963
10925
|
: false;
|
|
10964
10926
|
if (!unresolved)
|
|
10965
10927
|
return false;
|
|
10928
|
+
// Only a genuinely empty construction is unsettled. A populated one whose
|
|
10929
|
+
// items happen to be `unknown` (`[value]` over an unchecked boundary
|
|
10930
|
+
// value) says what it holds; the element type is simply that.
|
|
10966
10931
|
if (expression.kind === "ListExpression")
|
|
10967
|
-
return
|
|
10932
|
+
return expression.elements.length === 0;
|
|
10968
10933
|
return expression.kind === "CallExpression"
|
|
10934
|
+
&& expression.arguments.length === 0
|
|
10935
|
+
// `Set<string>()` already told the author where the element type goes
|
|
10936
|
+
// (VEL2031); reporting the missing one here would name the same mistake
|
|
10937
|
+
// twice and contradict the fix the first report offers.
|
|
10938
|
+
&& expression.typeArgumentsRemoved !== true
|
|
10969
10939
|
&& expression.callee.kind === "IdentifierExpression"
|
|
10970
10940
|
&& (expression.callee.name === "Map" || expression.callee.name === "Set");
|
|
10971
10941
|
}
|
|
10972
|
-
joinCollectionInference(name, binding, group) {
|
|
10973
|
-
group.bindings.set(binding, name);
|
|
10974
|
-
this.collectionInferenceGroups.set(binding, group);
|
|
10975
|
-
}
|
|
10976
|
-
createCollectionInference(type) {
|
|
10977
|
-
const group = { type, bindings: new Map(), open: true };
|
|
10978
|
-
this.collectionInferenceTypes.set(type, group);
|
|
10979
|
-
return group;
|
|
10980
|
-
}
|
|
10981
|
-
refineCollectionInference(current, next) {
|
|
10982
|
-
const group = this.collectionInferenceTypes.get(current);
|
|
10983
|
-
if (!group?.open)
|
|
10984
|
-
return false;
|
|
10985
|
-
if (group.type.kind === "list" && next.kind === "list") {
|
|
10986
|
-
group.type.element = next.element;
|
|
10987
|
-
}
|
|
10988
|
-
else if (group.type.kind === "set" && next.kind === "set") {
|
|
10989
|
-
group.type.element = next.element;
|
|
10990
|
-
}
|
|
10991
|
-
else if (group.type.kind === "map" && next.kind === "map") {
|
|
10992
|
-
group.type.key = next.key;
|
|
10993
|
-
group.type.value = next.value;
|
|
10994
|
-
}
|
|
10995
|
-
else
|
|
10996
|
-
return false;
|
|
10997
|
-
for (const [member, name] of group.bindings) {
|
|
10998
|
-
this.recordSemanticBinding(`${member.span.start}:${name}`, group.type);
|
|
10999
|
-
}
|
|
11000
|
-
return true;
|
|
11001
|
-
}
|
|
11002
|
-
rebindCollectionInference(name, binding, value, valueType) {
|
|
11003
|
-
const previous = this.collectionInferenceGroups.get(binding);
|
|
11004
|
-
if (!previous)
|
|
11005
|
-
return;
|
|
11006
|
-
const aliasedBinding = value.kind === "IdentifierExpression" ? this.lookup(value.name) : null;
|
|
11007
|
-
const aliasedGroup = aliasedBinding ? this.collectionInferenceGroups.get(aliasedBinding) : null;
|
|
11008
|
-
if (previous === aliasedGroup && aliasedGroup)
|
|
11009
|
-
return;
|
|
11010
|
-
previous?.bindings.delete(binding);
|
|
11011
|
-
this.collectionInferenceGroups.delete(binding);
|
|
11012
|
-
if (aliasedGroup) {
|
|
11013
|
-
binding.type = aliasedGroup.type;
|
|
11014
|
-
binding.declaredType = aliasedGroup.type;
|
|
11015
|
-
binding.storageType = aliasedGroup.type;
|
|
11016
|
-
this.joinCollectionInference(name, binding, aliasedGroup);
|
|
11017
|
-
}
|
|
11018
|
-
else if (this.isFreshUnresolvedCollection(value, valueType)) {
|
|
11019
|
-
binding.type = valueType;
|
|
11020
|
-
binding.declaredType = valueType;
|
|
11021
|
-
binding.storageType = valueType;
|
|
11022
|
-
this.joinCollectionInference(name, binding, this.createCollectionInference(valueType));
|
|
11023
|
-
}
|
|
11024
|
-
else {
|
|
11025
|
-
binding.type = valueType;
|
|
11026
|
-
binding.declaredType = valueType;
|
|
11027
|
-
binding.storageType = valueType;
|
|
11028
|
-
}
|
|
11029
|
-
this.recordSemanticBinding(`${binding.span.start}:${name}`, binding.type);
|
|
11030
|
-
}
|
|
11031
10942
|
declareBinding(name, mutable, type, declarationSpan, internal = false, declaredType = type, importSource) {
|
|
11032
10943
|
this.pendingScopeDeclarations.at(-1)?.delete(name);
|
|
11033
10944
|
if (!internal) {
|
|
@@ -11981,6 +11892,13 @@ export class Analyzer {
|
|
|
11981
11892
|
}
|
|
11982
11893
|
runtimeCheckedType(input, checked) {
|
|
11983
11894
|
const source = this.expandAliases(input);
|
|
11895
|
+
// D85 rule 210: `unknown` is the one checked domain that proves nothing,
|
|
11896
|
+
// so a check against it leaves the subject's own type alone. Without this
|
|
11897
|
+
// a membership probe against a container whose element or key type is
|
|
11898
|
+
// `unknown` replaced a `string` subject with `unknown`, which is a
|
|
11899
|
+
// widening — every later read of it then failed for the wrong reason.
|
|
11900
|
+
if (this.expandAliases(checked).kind === "unknown")
|
|
11901
|
+
return source;
|
|
11984
11902
|
const candidates = source.kind === "union" ? source.members
|
|
11985
11903
|
: source.kind === "optional" ? [source.inner, nullType]
|
|
11986
11904
|
: [source];
|