@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/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 '${formatType(actual)}' is not assignable to type '${formatType(declared)}'` + this.explainMismatch(actual, declared)
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 '${formatType(actual)}' is not assignable to '${briefType(declared)}'` + this.explainMismatch(actual, declared)
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 '${formatType(inferred)}' is not assignable to '${formatType(declared)}'` + this.explainMismatch(inferred, declared)
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 '${formatType(next)}' is not assignable to '${formatType(declared)}'` + this.explainMismatch(next, declared)
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
- if (declared) {
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], declared);
7204
- } else if (declared.kind === "tuple" && declared.isPack) {
7205
- stmt.arguments.forEach((a, i) => this.applyContext(a, declared.elements[i]));
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
- this.applyContext(field.type === "TableFieldShorthand" ? field.name : field.value, union(types));
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 iterator. Handles
8207
- * `ipairs`/`pairs`/`next(t)` and Luau generalized iteration (`for … in t`).
8208
- * `varCount` is how many loop variables were written. */
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") return varCount >= 2 ? [numberType, t.element] : [t.element, unknownType];
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 '${formatType(actual)}' does not satisfy the expected type '${formatType(declared)}'`
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
- const to = this.expand(expected);
9774
- if (from.kind !== "object" || to.kind !== "object") return "";
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
- return formatType(t);
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 '${formatType(actual)}' is not assignable to type '${formatType(declared)}'` + this.explainMismatch(actual, declared)
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 '${formatType(actual)}' is not assignable to '${briefType(declared)}'` + this.explainMismatch(actual, declared)
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 '${formatType(inferred)}' is not assignable to '${formatType(declared)}'` + this.explainMismatch(inferred, declared)
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 '${formatType(next)}' is not assignable to '${formatType(declared)}'` + this.explainMismatch(next, declared)
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
- if (declared) {
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], declared);
7101
- } else if (declared.kind === "tuple" && declared.isPack) {
7102
- stmt.arguments.forEach((a, i) => this.applyContext(a, declared.elements[i]));
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
- this.applyContext(field.type === "TableFieldShorthand" ? field.name : field.value, union(types));
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 iterator. Handles
8104
- * `ipairs`/`pairs`/`next(t)` and Luau generalized iteration (`for … in t`).
8105
- * `varCount` is how many loop variables were written. */
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") return varCount >= 2 ? [numberType, t.element] : [t.element, unknownType];
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 '${formatType(actual)}' does not satisfy the expected type '${formatType(declared)}'`
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
- const to = this.expand(expected);
9671
- if (from.kind !== "object" || to.kind !== "object") return "";
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
- return formatType(t);
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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tilua/parser",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "tilua parser for roblox",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.js",