@tilua/parser 1.0.0 → 1.0.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/README.md +702 -702
- package/dist/index.cjs +99 -24
- package/dist/index.d.cts +7 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +99 -24
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -5211,6 +5211,7 @@ function mergeObjectMembers(types) {
|
|
|
5211
5211
|
var INTERNAL_MEMBERS = /* @__PURE__ */ new Set(["ClassObject", "ParentClass"]);
|
|
5212
5212
|
var MAX_MISSING = 3;
|
|
5213
5213
|
var MAX_EXPLANATION = 90;
|
|
5214
|
+
var MAX_TYPE = 120;
|
|
5214
5215
|
function analyzeTypes(program, scopes, options = {}) {
|
|
5215
5216
|
return new TypeAnalyzer(program, scopes, options).run();
|
|
5216
5217
|
}
|
|
@@ -5589,6 +5590,7 @@ var TypeAnalyzer = class {
|
|
|
5589
5590
|
narrowedTypeOf = /* @__PURE__ */ new Map();
|
|
5590
5591
|
typeOfTypeNode = /* @__PURE__ */ new Map();
|
|
5591
5592
|
expectedTypeOf = /* @__PURE__ */ new Map();
|
|
5593
|
+
iteratesValues = /* @__PURE__ */ new Set();
|
|
5592
5594
|
/** Public: each alias resolved once (generic aliases keep their params as
|
|
5593
5595
|
* `typeParam` nodes in the body). */
|
|
5594
5596
|
aliases = new AliasMap();
|
|
@@ -5669,6 +5671,7 @@ var TypeAnalyzer = class {
|
|
|
5669
5671
|
typeOfTypeNode: this.typeOfTypeNode,
|
|
5670
5672
|
expectedTypeOf: this.expectedTypeOf,
|
|
5671
5673
|
aliases: this.resolveDeferredAliases(),
|
|
5674
|
+
iteratesValues: this.iteratesValues,
|
|
5672
5675
|
diagnostics: this.diagnostics
|
|
5673
5676
|
};
|
|
5674
5677
|
}
|
|
@@ -6162,7 +6165,7 @@ var TypeAnalyzer = class {
|
|
|
6162
6165
|
if (declared && this.emitDiagnostics && !isAssignable(actual, declared)) {
|
|
6163
6166
|
this.diagnostics.push({
|
|
6164
6167
|
node: member.init,
|
|
6165
|
-
message: `Type '${
|
|
6168
|
+
message: `Type '${briefType(actual)}' is not assignable to type '${briefType(declared)}'` + this.explainMismatch(actual, declared)
|
|
6166
6169
|
});
|
|
6167
6170
|
}
|
|
6168
6171
|
continue;
|
|
@@ -6349,7 +6352,7 @@ var TypeAnalyzer = class {
|
|
|
6349
6352
|
if (fits) return;
|
|
6350
6353
|
this.diagnostics.push({
|
|
6351
6354
|
node: stmt,
|
|
6352
|
-
message: `Type '${
|
|
6355
|
+
message: `Type '${briefType(actual)}' is not assignable to '${briefType(declared)}'` + this.explainMismatch(actual, declared)
|
|
6353
6356
|
});
|
|
6354
6357
|
}
|
|
6355
6358
|
/** A function that declared what it returns but never does. Only a body
|
|
@@ -7009,7 +7012,7 @@ var TypeAnalyzer = class {
|
|
|
7009
7012
|
if (declared.kind !== "any" && !this.namesNothing(declared) && !this.fitsAnnotation(source, declared, inferred, env)) {
|
|
7010
7013
|
this.diagnostics.push({
|
|
7011
7014
|
node: stmt,
|
|
7012
|
-
message: `Type '${
|
|
7015
|
+
message: `Type '${briefType(inferred)}' is not assignable to '${briefType(declared)}'` + this.explainMismatch(inferred, declared)
|
|
7013
7016
|
});
|
|
7014
7017
|
} else if (declared.kind !== "any") {
|
|
7015
7018
|
this.reportExcessProperties(source, declared);
|
|
@@ -7103,7 +7106,7 @@ var TypeAnalyzer = class {
|
|
|
7103
7106
|
if (this.emitDiagnostics && !isAssignable(next, declared) && declared.kind !== "any") {
|
|
7104
7107
|
this.diagnostics.push({
|
|
7105
7108
|
node: stmt,
|
|
7106
|
-
message: `Type '${
|
|
7109
|
+
message: `Type '${briefType(next)}' is not assignable to '${briefType(declared)}'` + this.explainMismatch(next, declared)
|
|
7107
7110
|
});
|
|
7108
7111
|
}
|
|
7109
7112
|
this.setBinding(env, id, narrowTo(declared, next));
|
|
@@ -7188,21 +7191,23 @@ var TypeAnalyzer = class {
|
|
|
7188
7191
|
const valueId = value.type === "IdentifierPattern" ? this.bindingIdByName(value.name, value) : void 0;
|
|
7189
7192
|
if (keyId !== void 0 && valueId !== void 0) this.correlateBindings(bodyEnv, [keyId, valueId], rows);
|
|
7190
7193
|
} else {
|
|
7191
|
-
const [keyT, valT] = this.iterationTypes(stmt.iterators[0], iterTypes[0], stmt.variables.length);
|
|
7194
|
+
const [keyT, valT, iteratesValues] = this.iterationTypes(stmt.iterators[0], iterTypes[0], stmt.variables.length);
|
|
7192
7195
|
stmt.variables.forEach((v, i) => {
|
|
7193
7196
|
this.bindPattern(v, i === 0 ? keyT : i === 1 ? valT : unknownType, bodyEnv, "widen");
|
|
7194
7197
|
});
|
|
7198
|
+
if (iteratesValues && stmt.iterators.length === 1) this.iteratesValues.add(stmt);
|
|
7195
7199
|
}
|
|
7196
7200
|
this.visitBlock(stmt.body, bodyEnv);
|
|
7197
7201
|
return;
|
|
7198
7202
|
}
|
|
7199
7203
|
case "ReturnStatement": {
|
|
7200
7204
|
const declared = this.declaredReturns[this.declaredReturns.length - 1];
|
|
7201
|
-
|
|
7205
|
+
const wanted = declared ?? this.contextualReturns[this.contextualReturns.length - 1];
|
|
7206
|
+
if (wanted) {
|
|
7202
7207
|
if (stmt.arguments.length === 1) {
|
|
7203
|
-
this.applyContext(stmt.arguments[0],
|
|
7204
|
-
} else if (
|
|
7205
|
-
stmt.arguments.forEach((a, i) => this.applyContext(a,
|
|
7208
|
+
this.applyContext(stmt.arguments[0], wanted);
|
|
7209
|
+
} else if (wanted.kind === "tuple" && wanted.isPack) {
|
|
7210
|
+
stmt.arguments.forEach((a, i) => this.applyContext(a, wanted.elements[i]));
|
|
7206
7211
|
}
|
|
7207
7212
|
}
|
|
7208
7213
|
const want = declared?.kind === "tuple" && declared.isPack ? declared.elements.length : 0;
|
|
@@ -7431,6 +7436,17 @@ var TypeAnalyzer = class {
|
|
|
7431
7436
|
/** What a function expression's unannotated parameters are, from where
|
|
7432
7437
|
* it is written — see `applyContext`. */
|
|
7433
7438
|
contextualParams = /* @__PURE__ */ new WeakMap();
|
|
7439
|
+
/** What a function expression written in a typed position returns, from
|
|
7440
|
+
* that position — see `applyContext`. A body with no `return` annotation
|
|
7441
|
+
* would otherwise infer its `return` expressions with nothing wanted of
|
|
7442
|
+
* them, and widen the literals in them: `() => S = function() { return
|
|
7443
|
+
* { Status: true } }` inferred `Status: boolean` and then failed against
|
|
7444
|
+
* `S`. It is a hint, not a contract: only `applyContext` reads it, so a
|
|
7445
|
+
* body that does not match still reports at the assignment, not here. */
|
|
7446
|
+
contextualReturnOf = /* @__PURE__ */ new WeakMap();
|
|
7447
|
+
/** `contextualReturnOf` for each function body being walked, alongside
|
|
7448
|
+
* `declaredReturns`. */
|
|
7449
|
+
contextualReturns = [];
|
|
7434
7450
|
/** `expected` is the type the surroundings want for `expr`. A function
|
|
7435
7451
|
* expression written there takes its unannotated parameters' types from
|
|
7436
7452
|
* it, as in TypeScript: `signal:Connect(function(player) ... end)` knows
|
|
@@ -7453,6 +7469,10 @@ var TypeAnalyzer = class {
|
|
|
7453
7469
|
const members = expected.kind === "union" ? expected.types : [expected];
|
|
7454
7470
|
const signatures = members.flatMap((m) => this.overloadsOf(this.expand(m)));
|
|
7455
7471
|
if (!signatures.length) return;
|
|
7472
|
+
if (!e.func.returnType && !e.func.predicate) {
|
|
7473
|
+
const returns = signatures.map((s) => s.returns).filter((t) => !containsTypeParam(t));
|
|
7474
|
+
if (returns.length) this.contextualReturnOf.set(e.func, union(returns));
|
|
7475
|
+
}
|
|
7456
7476
|
e.func.params.forEach((p, k) => {
|
|
7457
7477
|
if (p.typeAnnotation || p.pattern || p.default) return;
|
|
7458
7478
|
const candidates = [];
|
|
@@ -7496,7 +7516,9 @@ var TypeAnalyzer = class {
|
|
|
7496
7516
|
return property ? [property.type] : o.indexer ? [o.indexer.value] : [];
|
|
7497
7517
|
});
|
|
7498
7518
|
if (types.length) {
|
|
7499
|
-
|
|
7519
|
+
const target = field.type === "TableFieldShorthand" ? field.name : field.value;
|
|
7520
|
+
this.contextArms.set(target, types);
|
|
7521
|
+
this.applyContext(target, union(types));
|
|
7500
7522
|
}
|
|
7501
7523
|
}
|
|
7502
7524
|
}
|
|
@@ -7557,11 +7579,13 @@ var TypeAnalyzer = class {
|
|
|
7557
7579
|
withVarargs(func, body) {
|
|
7558
7580
|
this.varargs.push(this.varargElement(func));
|
|
7559
7581
|
this.declaredReturns.push(func.predicate ? booleanType : func.returnType ? this.resolveType(func.returnType) : void 0);
|
|
7582
|
+
this.contextualReturns.push(this.contextualReturnOf.get(func));
|
|
7560
7583
|
try {
|
|
7561
7584
|
return body();
|
|
7562
7585
|
} finally {
|
|
7563
7586
|
this.varargs.pop();
|
|
7564
7587
|
this.declaredReturns.pop();
|
|
7588
|
+
this.contextualReturns.pop();
|
|
7565
7589
|
}
|
|
7566
7590
|
}
|
|
7567
7591
|
visitFunctionBodyInner(func, outerEnv) {
|
|
@@ -8203,22 +8227,26 @@ var TypeAnalyzer = class {
|
|
|
8203
8227
|
if (ids.length < 2) return;
|
|
8204
8228
|
this.correlateBindings(env, ids, objects.map((member) => names.map((name) => this.propertyType(member, name))));
|
|
8205
8229
|
}
|
|
8206
|
-
/** `(keyType, valueType)` yielded by a generic-for
|
|
8207
|
-
* `ipairs`/`pairs`/`next(t)` and Luau generalized
|
|
8208
|
-
* `varCount` is how many loop variables were
|
|
8230
|
+
/** `(keyType, valueType, iteratesValues)` yielded by a generic-for
|
|
8231
|
+
* iterator. Handles `ipairs`/`pairs`/`next(t)` and Luau generalized
|
|
8232
|
+
* iteration (`for … in t`). `varCount` is how many loop variables were
|
|
8233
|
+
* written. `iteratesValues` reports the one case where the first variable
|
|
8234
|
+
* is bound to the value rather than the key, which is what lowering has
|
|
8235
|
+
* to know to emit the loop; see `TypeAnalysis.iteratesValues`. */
|
|
8209
8236
|
iterationTypes(iterNode, iterType, varCount) {
|
|
8210
8237
|
if (iterNode?.type === "CallExpression" && iterNode.callee.type === "Identifier" && iterNode.arguments[0]) {
|
|
8211
8238
|
const name = iterNode.callee.name;
|
|
8212
8239
|
const src = this.expand(this.typeOf.get(iterNode.arguments[0]) ?? unknownType);
|
|
8213
|
-
if (name === "ipairs") return [numberType, this.elementType(src, 0)];
|
|
8240
|
+
if (name === "ipairs") return [numberType, this.elementType(src, 0), false];
|
|
8214
8241
|
if (name === "pairs" || name === "next") {
|
|
8215
8242
|
if (src.kind === "object") {
|
|
8216
8243
|
return [
|
|
8217
8244
|
src.indexer?.key ?? stringType,
|
|
8218
|
-
src.indexer?.value ?? union([...src.properties.values()].map((p) => p.type))
|
|
8245
|
+
src.indexer?.value ?? union([...src.properties.values()].map((p) => p.type)),
|
|
8246
|
+
false
|
|
8219
8247
|
];
|
|
8220
8248
|
}
|
|
8221
|
-
if (src.kind === "array") return [numberType, src.element];
|
|
8249
|
+
if (src.kind === "array") return [numberType, src.element, false];
|
|
8222
8250
|
}
|
|
8223
8251
|
}
|
|
8224
8252
|
const iterator = this.expand(iterType);
|
|
@@ -8226,16 +8254,18 @@ var TypeAnalyzer = class {
|
|
|
8226
8254
|
const returns = this.expand(iterator.returns);
|
|
8227
8255
|
const parts = returns.kind === "tuple" ? returns.elements.map((m) => this.expand(m)) : [returns];
|
|
8228
8256
|
const at = (i) => parts[i] ?? (parts.length === 1 ? parts[0] : unknownType);
|
|
8229
|
-
return [at(0), at(1)];
|
|
8257
|
+
return [at(0), at(1), false];
|
|
8230
8258
|
}
|
|
8231
8259
|
const t = this.expand(iterType);
|
|
8232
|
-
if (t.kind === "array")
|
|
8260
|
+
if (t.kind === "array") {
|
|
8261
|
+
return varCount >= 2 ? [numberType, t.element, false] : [t.element, unknownType, true];
|
|
8262
|
+
}
|
|
8233
8263
|
if (t.kind === "object") {
|
|
8234
8264
|
const k = t.indexer?.key ?? stringType;
|
|
8235
8265
|
const v = t.indexer?.value ?? union([...t.properties.values()].map((p) => p.type));
|
|
8236
|
-
return varCount >= 2 ? [k, v] : [v, unknownType];
|
|
8266
|
+
return varCount >= 2 ? [k, v, false] : [v, unknownType, true];
|
|
8237
8267
|
}
|
|
8238
|
-
return [unknownType, unknownType];
|
|
8268
|
+
return [unknownType, unknownType, false];
|
|
8239
8269
|
}
|
|
8240
8270
|
inferReturnType(body, env) {
|
|
8241
8271
|
const returns = [];
|
|
@@ -8681,7 +8711,7 @@ var TypeAnalyzer = class {
|
|
|
8681
8711
|
if (!isAssignable(narrow, declared) && !isAssignable(actual, declared)) {
|
|
8682
8712
|
this.diagnostics.push({
|
|
8683
8713
|
node: expr,
|
|
8684
|
-
message: `Type '${
|
|
8714
|
+
message: `Type '${briefType(actual)}' does not satisfy the expected type '${briefType(declared)}'` + this.explainMismatch(actual, declared)
|
|
8685
8715
|
});
|
|
8686
8716
|
} else {
|
|
8687
8717
|
this.reportExcessProperties(expr.expression, declared);
|
|
@@ -9060,8 +9090,16 @@ var TypeAnalyzer = class {
|
|
|
9060
9090
|
* inferred, so this is a lookup rather than a second pass. */
|
|
9061
9091
|
widenUnlessAsked(value, at) {
|
|
9062
9092
|
const wanted = this.expectedTypeOf.get(at);
|
|
9093
|
+
if (value.kind === "literal") {
|
|
9094
|
+
const arms = this.contextArms.get(at);
|
|
9095
|
+
if (arms?.some((arm) => this.admitsLiteral(arm, value.base))) return value;
|
|
9096
|
+
}
|
|
9063
9097
|
return wanted === void 0 ? widen(value) : this.keepContextualLiterals(value, wanted);
|
|
9064
9098
|
}
|
|
9099
|
+
/** What each arm of the contract wanted of an expression, unmerged — see
|
|
9100
|
+
* `widenUnlessAsked`. Recorded by `applyTableContext` beside the merged
|
|
9101
|
+
* type it hands to `applyContext`. */
|
|
9102
|
+
contextArms = /* @__PURE__ */ new WeakMap();
|
|
9065
9103
|
inferObject(expr, env, asConst) {
|
|
9066
9104
|
const entries = [];
|
|
9067
9105
|
let indexer;
|
|
@@ -9770,8 +9808,9 @@ var TypeAnalyzer = class {
|
|
|
9770
9808
|
* themselves are then the whole story. */
|
|
9771
9809
|
explainMismatch(actual, expected) {
|
|
9772
9810
|
const from = this.expand(actual);
|
|
9773
|
-
|
|
9774
|
-
|
|
9811
|
+
if (from.kind !== "object") return "";
|
|
9812
|
+
const to = this.likeliestArm(from, this.expand(expected));
|
|
9813
|
+
if (to === void 0) return "";
|
|
9775
9814
|
const missing = [];
|
|
9776
9815
|
for (const [name, property] of to.properties) {
|
|
9777
9816
|
if (property.optional || from.properties.has(name) || INTERNAL_MEMBERS.has(name)) continue;
|
|
@@ -9796,6 +9835,34 @@ var TypeAnalyzer = class {
|
|
|
9796
9835
|
}
|
|
9797
9836
|
return "";
|
|
9798
9837
|
}
|
|
9838
|
+
/** Which object of a contract the value was reaching for. One object is
|
|
9839
|
+
* itself; among several, a discriminant decides — the arm whose literal
|
|
9840
|
+
* properties the value already agrees with (`Status: true` picks
|
|
9841
|
+
* `StatSuccess` over `StatError`). Failing that, the arm it is closest to
|
|
9842
|
+
* fitting, so the explanation is about the shortest gap rather than an
|
|
9843
|
+
* arbitrary one. `undefined` when there is no object to explain against. */
|
|
9844
|
+
likeliestArm(from, expected) {
|
|
9845
|
+
const arms = this.membersOf(expected).filter((m) => m.kind === "object");
|
|
9846
|
+
if (arms.length <= 1) return arms[0];
|
|
9847
|
+
const gapOf = (arm) => {
|
|
9848
|
+
let gap = 0;
|
|
9849
|
+
for (const [name, property] of arm.properties) {
|
|
9850
|
+
if (INTERNAL_MEMBERS.has(name)) continue;
|
|
9851
|
+
const own = from.properties.get(name);
|
|
9852
|
+
if (!own) {
|
|
9853
|
+
if (!property.optional) gap += 1;
|
|
9854
|
+
continue;
|
|
9855
|
+
}
|
|
9856
|
+
if (isAssignable(own.type, property.type)) continue;
|
|
9857
|
+
gap += this.expand(property.type).kind === "literal" ? 100 : 1;
|
|
9858
|
+
}
|
|
9859
|
+
for (const name of from.properties.keys()) {
|
|
9860
|
+
if (!INTERNAL_MEMBERS.has(name) && !arm.properties.has(name) && !arm.indexer) gap += 1;
|
|
9861
|
+
}
|
|
9862
|
+
return gap;
|
|
9863
|
+
};
|
|
9864
|
+
return arms.reduce((best, arm) => gapOf(arm) < gapOf(best) ? arm : best);
|
|
9865
|
+
}
|
|
9799
9866
|
bindingIdOf(id) {
|
|
9800
9867
|
return this.scopes.bindingOf.get(id);
|
|
9801
9868
|
}
|
|
@@ -9842,7 +9909,15 @@ function briefType(t) {
|
|
|
9842
9909
|
const shown = t.types.slice(0, 6).map(formatType).join(" | ");
|
|
9843
9910
|
return `${shown} | ... ${t.types.length - 6} more`;
|
|
9844
9911
|
}
|
|
9845
|
-
|
|
9912
|
+
const full = formatType(t);
|
|
9913
|
+
if (full.length <= MAX_TYPE) return full;
|
|
9914
|
+
if (t.kind === "object" && t.properties.size) {
|
|
9915
|
+
const names = [...t.properties.keys()].filter((n) => !INTERNAL_MEMBERS.has(n));
|
|
9916
|
+
const shown = names.slice(0, MAX_MISSING);
|
|
9917
|
+
const rest = names.length - shown.length;
|
|
9918
|
+
return `{ ${shown.join(", ")}${rest > 0 ? `, ... ${rest} more` : ""} }`;
|
|
9919
|
+
}
|
|
9920
|
+
return `${full.slice(0, MAX_TYPE)}...`;
|
|
9846
9921
|
}
|
|
9847
9922
|
|
|
9848
9923
|
// src/project/host.ts
|
package/dist/index.d.cts
CHANGED
|
@@ -1405,6 +1405,13 @@ interface TypeAnalysis {
|
|
|
1405
1405
|
/** Top-level type aliases, resolved — and the type names this module
|
|
1406
1406
|
* imports, so tooling treats both alike. */
|
|
1407
1407
|
readonly aliases: Map<string, Type>;
|
|
1408
|
+
/** The `for x in t` loops that walk a table directly, where tilua's one
|
|
1409
|
+
* variable takes the *value* — not an iterator such as `pairs(t)` or
|
|
1410
|
+
* `string.gmatch(s, p)`, whose variables are bound as written. Luau hands
|
|
1411
|
+
* the key over first, so lowering these needs a key variable in front.
|
|
1412
|
+
* Deciding that here keeps it next to the types the same choice produces:
|
|
1413
|
+
* the variable's type and the code bound to it cannot drift apart. */
|
|
1414
|
+
readonly iteratesValues: ReadonlySet<GenericForStatement>;
|
|
1408
1415
|
readonly diagnostics: TypeDiagnostic[];
|
|
1409
1416
|
}
|
|
1410
1417
|
/** A type a module exports: the resolved type, plus the parameter names of a
|
package/dist/index.d.ts
CHANGED
|
@@ -1405,6 +1405,13 @@ interface TypeAnalysis {
|
|
|
1405
1405
|
/** Top-level type aliases, resolved — and the type names this module
|
|
1406
1406
|
* imports, so tooling treats both alike. */
|
|
1407
1407
|
readonly aliases: Map<string, Type>;
|
|
1408
|
+
/** The `for x in t` loops that walk a table directly, where tilua's one
|
|
1409
|
+
* variable takes the *value* — not an iterator such as `pairs(t)` or
|
|
1410
|
+
* `string.gmatch(s, p)`, whose variables are bound as written. Luau hands
|
|
1411
|
+
* the key over first, so lowering these needs a key variable in front.
|
|
1412
|
+
* Deciding that here keeps it next to the types the same choice produces:
|
|
1413
|
+
* the variable's type and the code bound to it cannot drift apart. */
|
|
1414
|
+
readonly iteratesValues: ReadonlySet<GenericForStatement>;
|
|
1408
1415
|
readonly diagnostics: TypeDiagnostic[];
|
|
1409
1416
|
}
|
|
1410
1417
|
/** A type a module exports: the resolved type, plus the parameter names of a
|
package/dist/index.js
CHANGED
|
@@ -5108,6 +5108,7 @@ function mergeObjectMembers(types) {
|
|
|
5108
5108
|
var INTERNAL_MEMBERS = /* @__PURE__ */ new Set(["ClassObject", "ParentClass"]);
|
|
5109
5109
|
var MAX_MISSING = 3;
|
|
5110
5110
|
var MAX_EXPLANATION = 90;
|
|
5111
|
+
var MAX_TYPE = 120;
|
|
5111
5112
|
function analyzeTypes(program, scopes, options = {}) {
|
|
5112
5113
|
return new TypeAnalyzer(program, scopes, options).run();
|
|
5113
5114
|
}
|
|
@@ -5486,6 +5487,7 @@ var TypeAnalyzer = class {
|
|
|
5486
5487
|
narrowedTypeOf = /* @__PURE__ */ new Map();
|
|
5487
5488
|
typeOfTypeNode = /* @__PURE__ */ new Map();
|
|
5488
5489
|
expectedTypeOf = /* @__PURE__ */ new Map();
|
|
5490
|
+
iteratesValues = /* @__PURE__ */ new Set();
|
|
5489
5491
|
/** Public: each alias resolved once (generic aliases keep their params as
|
|
5490
5492
|
* `typeParam` nodes in the body). */
|
|
5491
5493
|
aliases = new AliasMap();
|
|
@@ -5566,6 +5568,7 @@ var TypeAnalyzer = class {
|
|
|
5566
5568
|
typeOfTypeNode: this.typeOfTypeNode,
|
|
5567
5569
|
expectedTypeOf: this.expectedTypeOf,
|
|
5568
5570
|
aliases: this.resolveDeferredAliases(),
|
|
5571
|
+
iteratesValues: this.iteratesValues,
|
|
5569
5572
|
diagnostics: this.diagnostics
|
|
5570
5573
|
};
|
|
5571
5574
|
}
|
|
@@ -6059,7 +6062,7 @@ var TypeAnalyzer = class {
|
|
|
6059
6062
|
if (declared && this.emitDiagnostics && !isAssignable(actual, declared)) {
|
|
6060
6063
|
this.diagnostics.push({
|
|
6061
6064
|
node: member.init,
|
|
6062
|
-
message: `Type '${
|
|
6065
|
+
message: `Type '${briefType(actual)}' is not assignable to type '${briefType(declared)}'` + this.explainMismatch(actual, declared)
|
|
6063
6066
|
});
|
|
6064
6067
|
}
|
|
6065
6068
|
continue;
|
|
@@ -6246,7 +6249,7 @@ var TypeAnalyzer = class {
|
|
|
6246
6249
|
if (fits) return;
|
|
6247
6250
|
this.diagnostics.push({
|
|
6248
6251
|
node: stmt,
|
|
6249
|
-
message: `Type '${
|
|
6252
|
+
message: `Type '${briefType(actual)}' is not assignable to '${briefType(declared)}'` + this.explainMismatch(actual, declared)
|
|
6250
6253
|
});
|
|
6251
6254
|
}
|
|
6252
6255
|
/** A function that declared what it returns but never does. Only a body
|
|
@@ -6906,7 +6909,7 @@ var TypeAnalyzer = class {
|
|
|
6906
6909
|
if (declared.kind !== "any" && !this.namesNothing(declared) && !this.fitsAnnotation(source, declared, inferred, env)) {
|
|
6907
6910
|
this.diagnostics.push({
|
|
6908
6911
|
node: stmt,
|
|
6909
|
-
message: `Type '${
|
|
6912
|
+
message: `Type '${briefType(inferred)}' is not assignable to '${briefType(declared)}'` + this.explainMismatch(inferred, declared)
|
|
6910
6913
|
});
|
|
6911
6914
|
} else if (declared.kind !== "any") {
|
|
6912
6915
|
this.reportExcessProperties(source, declared);
|
|
@@ -7000,7 +7003,7 @@ var TypeAnalyzer = class {
|
|
|
7000
7003
|
if (this.emitDiagnostics && !isAssignable(next, declared) && declared.kind !== "any") {
|
|
7001
7004
|
this.diagnostics.push({
|
|
7002
7005
|
node: stmt,
|
|
7003
|
-
message: `Type '${
|
|
7006
|
+
message: `Type '${briefType(next)}' is not assignable to '${briefType(declared)}'` + this.explainMismatch(next, declared)
|
|
7004
7007
|
});
|
|
7005
7008
|
}
|
|
7006
7009
|
this.setBinding(env, id, narrowTo(declared, next));
|
|
@@ -7085,21 +7088,23 @@ var TypeAnalyzer = class {
|
|
|
7085
7088
|
const valueId = value.type === "IdentifierPattern" ? this.bindingIdByName(value.name, value) : void 0;
|
|
7086
7089
|
if (keyId !== void 0 && valueId !== void 0) this.correlateBindings(bodyEnv, [keyId, valueId], rows);
|
|
7087
7090
|
} else {
|
|
7088
|
-
const [keyT, valT] = this.iterationTypes(stmt.iterators[0], iterTypes[0], stmt.variables.length);
|
|
7091
|
+
const [keyT, valT, iteratesValues] = this.iterationTypes(stmt.iterators[0], iterTypes[0], stmt.variables.length);
|
|
7089
7092
|
stmt.variables.forEach((v, i) => {
|
|
7090
7093
|
this.bindPattern(v, i === 0 ? keyT : i === 1 ? valT : unknownType, bodyEnv, "widen");
|
|
7091
7094
|
});
|
|
7095
|
+
if (iteratesValues && stmt.iterators.length === 1) this.iteratesValues.add(stmt);
|
|
7092
7096
|
}
|
|
7093
7097
|
this.visitBlock(stmt.body, bodyEnv);
|
|
7094
7098
|
return;
|
|
7095
7099
|
}
|
|
7096
7100
|
case "ReturnStatement": {
|
|
7097
7101
|
const declared = this.declaredReturns[this.declaredReturns.length - 1];
|
|
7098
|
-
|
|
7102
|
+
const wanted = declared ?? this.contextualReturns[this.contextualReturns.length - 1];
|
|
7103
|
+
if (wanted) {
|
|
7099
7104
|
if (stmt.arguments.length === 1) {
|
|
7100
|
-
this.applyContext(stmt.arguments[0],
|
|
7101
|
-
} else if (
|
|
7102
|
-
stmt.arguments.forEach((a, i) => this.applyContext(a,
|
|
7105
|
+
this.applyContext(stmt.arguments[0], wanted);
|
|
7106
|
+
} else if (wanted.kind === "tuple" && wanted.isPack) {
|
|
7107
|
+
stmt.arguments.forEach((a, i) => this.applyContext(a, wanted.elements[i]));
|
|
7103
7108
|
}
|
|
7104
7109
|
}
|
|
7105
7110
|
const want = declared?.kind === "tuple" && declared.isPack ? declared.elements.length : 0;
|
|
@@ -7328,6 +7333,17 @@ var TypeAnalyzer = class {
|
|
|
7328
7333
|
/** What a function expression's unannotated parameters are, from where
|
|
7329
7334
|
* it is written — see `applyContext`. */
|
|
7330
7335
|
contextualParams = /* @__PURE__ */ new WeakMap();
|
|
7336
|
+
/** What a function expression written in a typed position returns, from
|
|
7337
|
+
* that position — see `applyContext`. A body with no `return` annotation
|
|
7338
|
+
* would otherwise infer its `return` expressions with nothing wanted of
|
|
7339
|
+
* them, and widen the literals in them: `() => S = function() { return
|
|
7340
|
+
* { Status: true } }` inferred `Status: boolean` and then failed against
|
|
7341
|
+
* `S`. It is a hint, not a contract: only `applyContext` reads it, so a
|
|
7342
|
+
* body that does not match still reports at the assignment, not here. */
|
|
7343
|
+
contextualReturnOf = /* @__PURE__ */ new WeakMap();
|
|
7344
|
+
/** `contextualReturnOf` for each function body being walked, alongside
|
|
7345
|
+
* `declaredReturns`. */
|
|
7346
|
+
contextualReturns = [];
|
|
7331
7347
|
/** `expected` is the type the surroundings want for `expr`. A function
|
|
7332
7348
|
* expression written there takes its unannotated parameters' types from
|
|
7333
7349
|
* it, as in TypeScript: `signal:Connect(function(player) ... end)` knows
|
|
@@ -7350,6 +7366,10 @@ var TypeAnalyzer = class {
|
|
|
7350
7366
|
const members = expected.kind === "union" ? expected.types : [expected];
|
|
7351
7367
|
const signatures = members.flatMap((m) => this.overloadsOf(this.expand(m)));
|
|
7352
7368
|
if (!signatures.length) return;
|
|
7369
|
+
if (!e.func.returnType && !e.func.predicate) {
|
|
7370
|
+
const returns = signatures.map((s) => s.returns).filter((t) => !containsTypeParam(t));
|
|
7371
|
+
if (returns.length) this.contextualReturnOf.set(e.func, union(returns));
|
|
7372
|
+
}
|
|
7353
7373
|
e.func.params.forEach((p, k) => {
|
|
7354
7374
|
if (p.typeAnnotation || p.pattern || p.default) return;
|
|
7355
7375
|
const candidates = [];
|
|
@@ -7393,7 +7413,9 @@ var TypeAnalyzer = class {
|
|
|
7393
7413
|
return property ? [property.type] : o.indexer ? [o.indexer.value] : [];
|
|
7394
7414
|
});
|
|
7395
7415
|
if (types.length) {
|
|
7396
|
-
|
|
7416
|
+
const target = field.type === "TableFieldShorthand" ? field.name : field.value;
|
|
7417
|
+
this.contextArms.set(target, types);
|
|
7418
|
+
this.applyContext(target, union(types));
|
|
7397
7419
|
}
|
|
7398
7420
|
}
|
|
7399
7421
|
}
|
|
@@ -7454,11 +7476,13 @@ var TypeAnalyzer = class {
|
|
|
7454
7476
|
withVarargs(func, body) {
|
|
7455
7477
|
this.varargs.push(this.varargElement(func));
|
|
7456
7478
|
this.declaredReturns.push(func.predicate ? booleanType : func.returnType ? this.resolveType(func.returnType) : void 0);
|
|
7479
|
+
this.contextualReturns.push(this.contextualReturnOf.get(func));
|
|
7457
7480
|
try {
|
|
7458
7481
|
return body();
|
|
7459
7482
|
} finally {
|
|
7460
7483
|
this.varargs.pop();
|
|
7461
7484
|
this.declaredReturns.pop();
|
|
7485
|
+
this.contextualReturns.pop();
|
|
7462
7486
|
}
|
|
7463
7487
|
}
|
|
7464
7488
|
visitFunctionBodyInner(func, outerEnv) {
|
|
@@ -8100,22 +8124,26 @@ var TypeAnalyzer = class {
|
|
|
8100
8124
|
if (ids.length < 2) return;
|
|
8101
8125
|
this.correlateBindings(env, ids, objects.map((member) => names.map((name) => this.propertyType(member, name))));
|
|
8102
8126
|
}
|
|
8103
|
-
/** `(keyType, valueType)` yielded by a generic-for
|
|
8104
|
-
* `ipairs`/`pairs`/`next(t)` and Luau generalized
|
|
8105
|
-
* `varCount` is how many loop variables were
|
|
8127
|
+
/** `(keyType, valueType, iteratesValues)` yielded by a generic-for
|
|
8128
|
+
* iterator. Handles `ipairs`/`pairs`/`next(t)` and Luau generalized
|
|
8129
|
+
* iteration (`for … in t`). `varCount` is how many loop variables were
|
|
8130
|
+
* written. `iteratesValues` reports the one case where the first variable
|
|
8131
|
+
* is bound to the value rather than the key, which is what lowering has
|
|
8132
|
+
* to know to emit the loop; see `TypeAnalysis.iteratesValues`. */
|
|
8106
8133
|
iterationTypes(iterNode, iterType, varCount) {
|
|
8107
8134
|
if (iterNode?.type === "CallExpression" && iterNode.callee.type === "Identifier" && iterNode.arguments[0]) {
|
|
8108
8135
|
const name = iterNode.callee.name;
|
|
8109
8136
|
const src = this.expand(this.typeOf.get(iterNode.arguments[0]) ?? unknownType);
|
|
8110
|
-
if (name === "ipairs") return [numberType, this.elementType(src, 0)];
|
|
8137
|
+
if (name === "ipairs") return [numberType, this.elementType(src, 0), false];
|
|
8111
8138
|
if (name === "pairs" || name === "next") {
|
|
8112
8139
|
if (src.kind === "object") {
|
|
8113
8140
|
return [
|
|
8114
8141
|
src.indexer?.key ?? stringType,
|
|
8115
|
-
src.indexer?.value ?? union([...src.properties.values()].map((p) => p.type))
|
|
8142
|
+
src.indexer?.value ?? union([...src.properties.values()].map((p) => p.type)),
|
|
8143
|
+
false
|
|
8116
8144
|
];
|
|
8117
8145
|
}
|
|
8118
|
-
if (src.kind === "array") return [numberType, src.element];
|
|
8146
|
+
if (src.kind === "array") return [numberType, src.element, false];
|
|
8119
8147
|
}
|
|
8120
8148
|
}
|
|
8121
8149
|
const iterator = this.expand(iterType);
|
|
@@ -8123,16 +8151,18 @@ var TypeAnalyzer = class {
|
|
|
8123
8151
|
const returns = this.expand(iterator.returns);
|
|
8124
8152
|
const parts = returns.kind === "tuple" ? returns.elements.map((m) => this.expand(m)) : [returns];
|
|
8125
8153
|
const at = (i) => parts[i] ?? (parts.length === 1 ? parts[0] : unknownType);
|
|
8126
|
-
return [at(0), at(1)];
|
|
8154
|
+
return [at(0), at(1), false];
|
|
8127
8155
|
}
|
|
8128
8156
|
const t = this.expand(iterType);
|
|
8129
|
-
if (t.kind === "array")
|
|
8157
|
+
if (t.kind === "array") {
|
|
8158
|
+
return varCount >= 2 ? [numberType, t.element, false] : [t.element, unknownType, true];
|
|
8159
|
+
}
|
|
8130
8160
|
if (t.kind === "object") {
|
|
8131
8161
|
const k = t.indexer?.key ?? stringType;
|
|
8132
8162
|
const v = t.indexer?.value ?? union([...t.properties.values()].map((p) => p.type));
|
|
8133
|
-
return varCount >= 2 ? [k, v] : [v, unknownType];
|
|
8163
|
+
return varCount >= 2 ? [k, v, false] : [v, unknownType, true];
|
|
8134
8164
|
}
|
|
8135
|
-
return [unknownType, unknownType];
|
|
8165
|
+
return [unknownType, unknownType, false];
|
|
8136
8166
|
}
|
|
8137
8167
|
inferReturnType(body, env) {
|
|
8138
8168
|
const returns = [];
|
|
@@ -8578,7 +8608,7 @@ var TypeAnalyzer = class {
|
|
|
8578
8608
|
if (!isAssignable(narrow, declared) && !isAssignable(actual, declared)) {
|
|
8579
8609
|
this.diagnostics.push({
|
|
8580
8610
|
node: expr,
|
|
8581
|
-
message: `Type '${
|
|
8611
|
+
message: `Type '${briefType(actual)}' does not satisfy the expected type '${briefType(declared)}'` + this.explainMismatch(actual, declared)
|
|
8582
8612
|
});
|
|
8583
8613
|
} else {
|
|
8584
8614
|
this.reportExcessProperties(expr.expression, declared);
|
|
@@ -8957,8 +8987,16 @@ var TypeAnalyzer = class {
|
|
|
8957
8987
|
* inferred, so this is a lookup rather than a second pass. */
|
|
8958
8988
|
widenUnlessAsked(value, at) {
|
|
8959
8989
|
const wanted = this.expectedTypeOf.get(at);
|
|
8990
|
+
if (value.kind === "literal") {
|
|
8991
|
+
const arms = this.contextArms.get(at);
|
|
8992
|
+
if (arms?.some((arm) => this.admitsLiteral(arm, value.base))) return value;
|
|
8993
|
+
}
|
|
8960
8994
|
return wanted === void 0 ? widen(value) : this.keepContextualLiterals(value, wanted);
|
|
8961
8995
|
}
|
|
8996
|
+
/** What each arm of the contract wanted of an expression, unmerged — see
|
|
8997
|
+
* `widenUnlessAsked`. Recorded by `applyTableContext` beside the merged
|
|
8998
|
+
* type it hands to `applyContext`. */
|
|
8999
|
+
contextArms = /* @__PURE__ */ new WeakMap();
|
|
8962
9000
|
inferObject(expr, env, asConst) {
|
|
8963
9001
|
const entries = [];
|
|
8964
9002
|
let indexer;
|
|
@@ -9667,8 +9705,9 @@ var TypeAnalyzer = class {
|
|
|
9667
9705
|
* themselves are then the whole story. */
|
|
9668
9706
|
explainMismatch(actual, expected) {
|
|
9669
9707
|
const from = this.expand(actual);
|
|
9670
|
-
|
|
9671
|
-
|
|
9708
|
+
if (from.kind !== "object") return "";
|
|
9709
|
+
const to = this.likeliestArm(from, this.expand(expected));
|
|
9710
|
+
if (to === void 0) return "";
|
|
9672
9711
|
const missing = [];
|
|
9673
9712
|
for (const [name, property] of to.properties) {
|
|
9674
9713
|
if (property.optional || from.properties.has(name) || INTERNAL_MEMBERS.has(name)) continue;
|
|
@@ -9693,6 +9732,34 @@ var TypeAnalyzer = class {
|
|
|
9693
9732
|
}
|
|
9694
9733
|
return "";
|
|
9695
9734
|
}
|
|
9735
|
+
/** Which object of a contract the value was reaching for. One object is
|
|
9736
|
+
* itself; among several, a discriminant decides — the arm whose literal
|
|
9737
|
+
* properties the value already agrees with (`Status: true` picks
|
|
9738
|
+
* `StatSuccess` over `StatError`). Failing that, the arm it is closest to
|
|
9739
|
+
* fitting, so the explanation is about the shortest gap rather than an
|
|
9740
|
+
* arbitrary one. `undefined` when there is no object to explain against. */
|
|
9741
|
+
likeliestArm(from, expected) {
|
|
9742
|
+
const arms = this.membersOf(expected).filter((m) => m.kind === "object");
|
|
9743
|
+
if (arms.length <= 1) return arms[0];
|
|
9744
|
+
const gapOf = (arm) => {
|
|
9745
|
+
let gap = 0;
|
|
9746
|
+
for (const [name, property] of arm.properties) {
|
|
9747
|
+
if (INTERNAL_MEMBERS.has(name)) continue;
|
|
9748
|
+
const own = from.properties.get(name);
|
|
9749
|
+
if (!own) {
|
|
9750
|
+
if (!property.optional) gap += 1;
|
|
9751
|
+
continue;
|
|
9752
|
+
}
|
|
9753
|
+
if (isAssignable(own.type, property.type)) continue;
|
|
9754
|
+
gap += this.expand(property.type).kind === "literal" ? 100 : 1;
|
|
9755
|
+
}
|
|
9756
|
+
for (const name of from.properties.keys()) {
|
|
9757
|
+
if (!INTERNAL_MEMBERS.has(name) && !arm.properties.has(name) && !arm.indexer) gap += 1;
|
|
9758
|
+
}
|
|
9759
|
+
return gap;
|
|
9760
|
+
};
|
|
9761
|
+
return arms.reduce((best, arm) => gapOf(arm) < gapOf(best) ? arm : best);
|
|
9762
|
+
}
|
|
9696
9763
|
bindingIdOf(id) {
|
|
9697
9764
|
return this.scopes.bindingOf.get(id);
|
|
9698
9765
|
}
|
|
@@ -9739,7 +9806,15 @@ function briefType(t) {
|
|
|
9739
9806
|
const shown = t.types.slice(0, 6).map(formatType).join(" | ");
|
|
9740
9807
|
return `${shown} | ... ${t.types.length - 6} more`;
|
|
9741
9808
|
}
|
|
9742
|
-
|
|
9809
|
+
const full = formatType(t);
|
|
9810
|
+
if (full.length <= MAX_TYPE) return full;
|
|
9811
|
+
if (t.kind === "object" && t.properties.size) {
|
|
9812
|
+
const names = [...t.properties.keys()].filter((n) => !INTERNAL_MEMBERS.has(n));
|
|
9813
|
+
const shown = names.slice(0, MAX_MISSING);
|
|
9814
|
+
const rest = names.length - shown.length;
|
|
9815
|
+
return `{ ${shown.join(", ")}${rest > 0 ? `, ... ${rest} more` : ""} }`;
|
|
9816
|
+
}
|
|
9817
|
+
return `${full.slice(0, MAX_TYPE)}...`;
|
|
9743
9818
|
}
|
|
9744
9819
|
|
|
9745
9820
|
// src/project/host.ts
|