@tilua/parser 1.0.0 → 1.0.2

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.js CHANGED
@@ -4395,7 +4395,12 @@ function substitute(t, subst) {
4395
4395
  return t.name && r.kind === "intersection" && !r.name ? { ...r, name: t.name } : r;
4396
4396
  }
4397
4397
  case "genericRef":
4398
- return { kind: "genericRef", name: t.name, typeArguments: t.typeArguments.map((a) => substitute(a, subst)) };
4398
+ return {
4399
+ kind: "genericRef",
4400
+ name: t.name,
4401
+ typeArguments: t.typeArguments.map((a) => substitute(a, subst)),
4402
+ ...t.origin ? { origin: t.origin } : {}
4403
+ };
4399
4404
  case "keyof":
4400
4405
  return { kind: "keyof", target: substitute(t.target, subst) };
4401
4406
  case "templateLiteral":
@@ -4555,6 +4560,7 @@ function optional(t) {
4555
4560
  return union([t, nilType]);
4556
4561
  }
4557
4562
  function widen(t) {
4563
+ if (isAliasNamed(t)) return t;
4558
4564
  switch (t.kind) {
4559
4565
  case "literal":
4560
4566
  return primitive(t.base);
@@ -4594,7 +4600,7 @@ function isAssignable(rawA, rawB) {
4594
4600
  if (expandAlias) {
4595
4601
  if (a.kind === "genericRef" && b.kind !== "genericRef") a = expandAlias(a);
4596
4602
  else if (b.kind === "genericRef" && a.kind !== "genericRef") b = expandAlias(b);
4597
- else if (a.kind === "genericRef" && b.kind === "genericRef" && a.name !== b.name) {
4603
+ else if (a.kind === "genericRef" && b.kind === "genericRef" && !sameRefTarget(a, b)) {
4598
4604
  a = expandAlias(a);
4599
4605
  b = expandAlias(b);
4600
4606
  }
@@ -4704,10 +4710,17 @@ function isAssignableInner(a, b) {
4704
4710
  }
4705
4711
  if (b.kind === "typeParam") return false;
4706
4712
  if (a.kind === "genericRef" || b.kind === "genericRef") {
4707
- return a.kind === "genericRef" && b.kind === "genericRef" && a.name === b.name && a.typeArguments.length === b.typeArguments.length && a.typeArguments.every((x, i) => equalTypes(x, b.typeArguments[i]));
4713
+ return a.kind === "genericRef" && b.kind === "genericRef" && sameRefTarget(a, b) && a.typeArguments.length === b.typeArguments.length && a.typeArguments.every((x, i) => equalTypes(x, b.typeArguments[i]));
4708
4714
  }
4709
4715
  return false;
4710
4716
  }
4717
+ function isAliasNamed(t) {
4718
+ if (t.kind === "object" || t.kind === "intersection") return t.name !== void 0;
4719
+ return t.alias !== void 0;
4720
+ }
4721
+ function sameRefTarget(a, b) {
4722
+ return a.name === b.name && (!a.origin || !b.origin || a.origin === b.origin);
4723
+ }
4711
4724
  function isNoValue(t) {
4712
4725
  return t.kind === "tuple" && t.elements.length === 0;
4713
4726
  }
@@ -5108,6 +5121,7 @@ function mergeObjectMembers(types) {
5108
5121
  var INTERNAL_MEMBERS = /* @__PURE__ */ new Set(["ClassObject", "ParentClass"]);
5109
5122
  var MAX_MISSING = 3;
5110
5123
  var MAX_EXPLANATION = 90;
5124
+ var MAX_TYPE = 120;
5111
5125
  function analyzeTypes(program, scopes, options = {}) {
5112
5126
  return new TypeAnalyzer(program, scopes, options).run();
5113
5127
  }
@@ -5486,11 +5500,15 @@ var TypeAnalyzer = class {
5486
5500
  narrowedTypeOf = /* @__PURE__ */ new Map();
5487
5501
  typeOfTypeNode = /* @__PURE__ */ new Map();
5488
5502
  expectedTypeOf = /* @__PURE__ */ new Map();
5503
+ iteratesValues = /* @__PURE__ */ new Set();
5489
5504
  /** Public: each alias resolved once (generic aliases keep their params as
5490
5505
  * `typeParam` nodes in the body). */
5491
5506
  aliases = new AliasMap();
5492
5507
  /** Uninstantiated alias definitions, for `Name<Args>` instantiation. */
5493
5508
  aliasDefs = /* @__PURE__ */ new Map();
5509
+ /** Put on every ref to one of `aliasDefs`, so a module that imports the
5510
+ * type expands the ref here rather than by its name there. */
5511
+ origin = { expand: (t) => this.expand(t) };
5494
5512
  /** See `resolveClass`. */
5495
5513
  classTypes = /* @__PURE__ */ new WeakMap();
5496
5514
  /** See `instanceType` — one instance type per `class ... end`. */
@@ -5566,6 +5584,7 @@ var TypeAnalyzer = class {
5566
5584
  typeOfTypeNode: this.typeOfTypeNode,
5567
5585
  expectedTypeOf: this.expectedTypeOf,
5568
5586
  aliases: this.resolveDeferredAliases(),
5587
+ iteratesValues: this.iteratesValues,
5569
5588
  diagnostics: this.diagnostics
5570
5589
  };
5571
5590
  }
@@ -5898,7 +5917,8 @@ var TypeAnalyzer = class {
5898
5917
  return {
5899
5918
  kind: "genericRef",
5900
5919
  name: stmt.name.name,
5901
- typeArguments: params.map((p) => typeParam(p.name, p.constraint ? this.resolveType(p.constraint) : void 0))
5920
+ typeArguments: params.map((p) => typeParam(p.name, p.constraint ? this.resolveType(p.constraint) : void 0)),
5921
+ origin: this.origin
5902
5922
  };
5903
5923
  }
5904
5924
  /** The class table: the statics, what it inherits from the class it
@@ -6059,7 +6079,7 @@ var TypeAnalyzer = class {
6059
6079
  if (declared && this.emitDiagnostics && !isAssignable(actual, declared)) {
6060
6080
  this.diagnostics.push({
6061
6081
  node: member.init,
6062
- message: `Type '${formatType(actual)}' is not assignable to type '${formatType(declared)}'` + this.explainMismatch(actual, declared)
6082
+ message: `Type '${briefType(actual)}' is not assignable to type '${briefType(declared)}'` + this.explainMismatch(actual, declared)
6063
6083
  });
6064
6084
  }
6065
6085
  continue;
@@ -6246,7 +6266,7 @@ var TypeAnalyzer = class {
6246
6266
  if (fits) return;
6247
6267
  this.diagnostics.push({
6248
6268
  node: stmt,
6249
- message: `Type '${formatType(actual)}' is not assignable to '${briefType(declared)}'` + this.explainMismatch(actual, declared)
6269
+ message: `Type '${briefType(actual)}' is not assignable to '${briefType(declared)}'` + this.explainMismatch(actual, declared)
6250
6270
  });
6251
6271
  }
6252
6272
  /** A function that declared what it returns but never does. Only a body
@@ -6463,7 +6483,8 @@ var TypeAnalyzer = class {
6463
6483
  return this.expand({
6464
6484
  kind: "genericRef",
6465
6485
  name: node.base,
6466
- typeArguments: node.typeArguments.map((a) => this.resolveType(a))
6486
+ typeArguments: node.typeArguments.map((a) => this.resolveType(a)),
6487
+ origin: this.origin
6467
6488
  });
6468
6489
  }
6469
6490
  const imported = this.importedTypes.get(node.base);
@@ -6476,7 +6497,8 @@ var TypeAnalyzer = class {
6476
6497
  return this.expand({
6477
6498
  kind: "genericRef",
6478
6499
  name,
6479
- typeArguments: node.typeArguments.map((a) => this.resolveType(a))
6500
+ typeArguments: node.typeArguments.map((a) => this.resolveType(a)),
6501
+ origin: this.origin
6480
6502
  });
6481
6503
  }
6482
6504
  return {
@@ -6906,7 +6928,7 @@ var TypeAnalyzer = class {
6906
6928
  if (declared.kind !== "any" && !this.namesNothing(declared) && !this.fitsAnnotation(source, declared, inferred, env)) {
6907
6929
  this.diagnostics.push({
6908
6930
  node: stmt,
6909
- message: `Type '${formatType(inferred)}' is not assignable to '${formatType(declared)}'` + this.explainMismatch(inferred, declared)
6931
+ message: `Type '${briefType(inferred)}' is not assignable to '${briefType(declared)}'` + this.explainMismatch(inferred, declared)
6910
6932
  });
6911
6933
  } else if (declared.kind !== "any") {
6912
6934
  this.reportExcessProperties(source, declared);
@@ -7000,7 +7022,7 @@ var TypeAnalyzer = class {
7000
7022
  if (this.emitDiagnostics && !isAssignable(next, declared) && declared.kind !== "any") {
7001
7023
  this.diagnostics.push({
7002
7024
  node: stmt,
7003
- message: `Type '${formatType(next)}' is not assignable to '${formatType(declared)}'` + this.explainMismatch(next, declared)
7025
+ message: `Type '${briefType(next)}' is not assignable to '${briefType(declared)}'` + this.explainMismatch(next, declared)
7004
7026
  });
7005
7027
  }
7006
7028
  this.setBinding(env, id, narrowTo(declared, next));
@@ -7085,21 +7107,23 @@ var TypeAnalyzer = class {
7085
7107
  const valueId = value.type === "IdentifierPattern" ? this.bindingIdByName(value.name, value) : void 0;
7086
7108
  if (keyId !== void 0 && valueId !== void 0) this.correlateBindings(bodyEnv, [keyId, valueId], rows);
7087
7109
  } else {
7088
- const [keyT, valT] = this.iterationTypes(stmt.iterators[0], iterTypes[0], stmt.variables.length);
7110
+ const [keyT, valT, iteratesValues] = this.iterationTypes(stmt.iterators[0], iterTypes[0], stmt.variables.length);
7089
7111
  stmt.variables.forEach((v, i) => {
7090
7112
  this.bindPattern(v, i === 0 ? keyT : i === 1 ? valT : unknownType, bodyEnv, "widen");
7091
7113
  });
7114
+ if (iteratesValues && stmt.iterators.length === 1) this.iteratesValues.add(stmt);
7092
7115
  }
7093
7116
  this.visitBlock(stmt.body, bodyEnv);
7094
7117
  return;
7095
7118
  }
7096
7119
  case "ReturnStatement": {
7097
7120
  const declared = this.declaredReturns[this.declaredReturns.length - 1];
7098
- if (declared) {
7121
+ const wanted = declared ?? this.contextualReturns[this.contextualReturns.length - 1];
7122
+ if (wanted) {
7099
7123
  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]));
7124
+ this.applyContext(stmt.arguments[0], wanted);
7125
+ } else if (wanted.kind === "tuple" && wanted.isPack) {
7126
+ stmt.arguments.forEach((a, i) => this.applyContext(a, wanted.elements[i]));
7103
7127
  }
7104
7128
  }
7105
7129
  const want = declared?.kind === "tuple" && declared.isPack ? declared.elements.length : 0;
@@ -7328,6 +7352,17 @@ var TypeAnalyzer = class {
7328
7352
  /** What a function expression's unannotated parameters are, from where
7329
7353
  * it is written — see `applyContext`. */
7330
7354
  contextualParams = /* @__PURE__ */ new WeakMap();
7355
+ /** What a function expression written in a typed position returns, from
7356
+ * that position — see `applyContext`. A body with no `return` annotation
7357
+ * would otherwise infer its `return` expressions with nothing wanted of
7358
+ * them, and widen the literals in them: `() => S = function() { return
7359
+ * { Status: true } }` inferred `Status: boolean` and then failed against
7360
+ * `S`. It is a hint, not a contract: only `applyContext` reads it, so a
7361
+ * body that does not match still reports at the assignment, not here. */
7362
+ contextualReturnOf = /* @__PURE__ */ new WeakMap();
7363
+ /** `contextualReturnOf` for each function body being walked, alongside
7364
+ * `declaredReturns`. */
7365
+ contextualReturns = [];
7331
7366
  /** `expected` is the type the surroundings want for `expr`. A function
7332
7367
  * expression written there takes its unannotated parameters' types from
7333
7368
  * it, as in TypeScript: `signal:Connect(function(player) ... end)` knows
@@ -7350,6 +7385,10 @@ var TypeAnalyzer = class {
7350
7385
  const members = expected.kind === "union" ? expected.types : [expected];
7351
7386
  const signatures = members.flatMap((m) => this.overloadsOf(this.expand(m)));
7352
7387
  if (!signatures.length) return;
7388
+ if (!e.func.returnType && !e.func.predicate) {
7389
+ const returns = signatures.map((s) => s.returns).filter((t) => !containsTypeParam(t));
7390
+ if (returns.length) this.contextualReturnOf.set(e.func, union(returns));
7391
+ }
7353
7392
  e.func.params.forEach((p, k) => {
7354
7393
  if (p.typeAnnotation || p.pattern || p.default) return;
7355
7394
  const candidates = [];
@@ -7393,7 +7432,9 @@ var TypeAnalyzer = class {
7393
7432
  return property ? [property.type] : o.indexer ? [o.indexer.value] : [];
7394
7433
  });
7395
7434
  if (types.length) {
7396
- this.applyContext(field.type === "TableFieldShorthand" ? field.name : field.value, union(types));
7435
+ const target = field.type === "TableFieldShorthand" ? field.name : field.value;
7436
+ this.contextArms.set(target, types);
7437
+ this.applyContext(target, union(types));
7397
7438
  }
7398
7439
  }
7399
7440
  }
@@ -7454,11 +7495,13 @@ var TypeAnalyzer = class {
7454
7495
  withVarargs(func, body) {
7455
7496
  this.varargs.push(this.varargElement(func));
7456
7497
  this.declaredReturns.push(func.predicate ? booleanType : func.returnType ? this.resolveType(func.returnType) : void 0);
7498
+ this.contextualReturns.push(this.contextualReturnOf.get(func));
7457
7499
  try {
7458
7500
  return body();
7459
7501
  } finally {
7460
7502
  this.varargs.pop();
7461
7503
  this.declaredReturns.pop();
7504
+ this.contextualReturns.pop();
7462
7505
  }
7463
7506
  }
7464
7507
  visitFunctionBodyInner(func, outerEnv) {
@@ -8100,22 +8143,26 @@ var TypeAnalyzer = class {
8100
8143
  if (ids.length < 2) return;
8101
8144
  this.correlateBindings(env, ids, objects.map((member) => names.map((name) => this.propertyType(member, name))));
8102
8145
  }
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. */
8146
+ /** `(keyType, valueType, iteratesValues)` yielded by a generic-for
8147
+ * iterator. Handles `ipairs`/`pairs`/`next(t)` and Luau generalized
8148
+ * iteration (`for … in t`). `varCount` is how many loop variables were
8149
+ * written. `iteratesValues` reports the one case where the first variable
8150
+ * is bound to the value rather than the key, which is what lowering has
8151
+ * to know to emit the loop; see `TypeAnalysis.iteratesValues`. */
8106
8152
  iterationTypes(iterNode, iterType, varCount) {
8107
8153
  if (iterNode?.type === "CallExpression" && iterNode.callee.type === "Identifier" && iterNode.arguments[0]) {
8108
8154
  const name = iterNode.callee.name;
8109
8155
  const src = this.expand(this.typeOf.get(iterNode.arguments[0]) ?? unknownType);
8110
- if (name === "ipairs") return [numberType, this.elementType(src, 0)];
8156
+ if (name === "ipairs") return [numberType, this.elementType(src, 0), false];
8111
8157
  if (name === "pairs" || name === "next") {
8112
8158
  if (src.kind === "object") {
8113
8159
  return [
8114
8160
  src.indexer?.key ?? stringType,
8115
- src.indexer?.value ?? union([...src.properties.values()].map((p) => p.type))
8161
+ src.indexer?.value ?? union([...src.properties.values()].map((p) => p.type)),
8162
+ false
8116
8163
  ];
8117
8164
  }
8118
- if (src.kind === "array") return [numberType, src.element];
8165
+ if (src.kind === "array") return [numberType, src.element, false];
8119
8166
  }
8120
8167
  }
8121
8168
  const iterator = this.expand(iterType);
@@ -8123,16 +8170,18 @@ var TypeAnalyzer = class {
8123
8170
  const returns = this.expand(iterator.returns);
8124
8171
  const parts = returns.kind === "tuple" ? returns.elements.map((m) => this.expand(m)) : [returns];
8125
8172
  const at = (i) => parts[i] ?? (parts.length === 1 ? parts[0] : unknownType);
8126
- return [at(0), at(1)];
8173
+ return [at(0), at(1), false];
8127
8174
  }
8128
8175
  const t = this.expand(iterType);
8129
- if (t.kind === "array") return varCount >= 2 ? [numberType, t.element] : [t.element, unknownType];
8176
+ if (t.kind === "array") {
8177
+ return varCount >= 2 ? [numberType, t.element, false] : [t.element, unknownType, true];
8178
+ }
8130
8179
  if (t.kind === "object") {
8131
8180
  const k = t.indexer?.key ?? stringType;
8132
8181
  const v = t.indexer?.value ?? union([...t.properties.values()].map((p) => p.type));
8133
- return varCount >= 2 ? [k, v] : [v, unknownType];
8182
+ return varCount >= 2 ? [k, v, false] : [v, unknownType, true];
8134
8183
  }
8135
- return [unknownType, unknownType];
8184
+ return [unknownType, unknownType, false];
8136
8185
  }
8137
8186
  inferReturnType(body, env) {
8138
8187
  const returns = [];
@@ -8275,6 +8324,7 @@ var TypeAnalyzer = class {
8275
8324
  * resolving, which is what breaks re-entry on a recursive alias. */
8276
8325
  expand(t) {
8277
8326
  if (t.kind !== "genericRef") return t;
8327
+ if (t.origin && t.origin !== this.origin) return t.origin.expand(t);
8278
8328
  const def = this.aliasDefs.get(t.name);
8279
8329
  if (!def) {
8280
8330
  const imported = this.importedTypes.get(t.name);
@@ -8578,7 +8628,7 @@ var TypeAnalyzer = class {
8578
8628
  if (!isAssignable(narrow, declared) && !isAssignable(actual, declared)) {
8579
8629
  this.diagnostics.push({
8580
8630
  node: expr,
8581
- message: `Type '${formatType(actual)}' does not satisfy the expected type '${formatType(declared)}'`
8631
+ message: `Type '${briefType(actual)}' does not satisfy the expected type '${briefType(declared)}'` + this.explainMismatch(actual, declared)
8582
8632
  });
8583
8633
  } else {
8584
8634
  this.reportExcessProperties(expr.expression, declared);
@@ -8957,8 +9007,16 @@ var TypeAnalyzer = class {
8957
9007
  * inferred, so this is a lookup rather than a second pass. */
8958
9008
  widenUnlessAsked(value, at) {
8959
9009
  const wanted = this.expectedTypeOf.get(at);
9010
+ if (value.kind === "literal") {
9011
+ const arms = this.contextArms.get(at);
9012
+ if (arms?.some((arm) => this.admitsLiteral(arm, value.base))) return value;
9013
+ }
8960
9014
  return wanted === void 0 ? widen(value) : this.keepContextualLiterals(value, wanted);
8961
9015
  }
9016
+ /** What each arm of the contract wanted of an expression, unmerged — see
9017
+ * `widenUnlessAsked`. Recorded by `applyTableContext` beside the merged
9018
+ * type it hands to `applyContext`. */
9019
+ contextArms = /* @__PURE__ */ new WeakMap();
8962
9020
  inferObject(expr, env, asConst) {
8963
9021
  const entries = [];
8964
9022
  let indexer;
@@ -9667,8 +9725,9 @@ var TypeAnalyzer = class {
9667
9725
  * themselves are then the whole story. */
9668
9726
  explainMismatch(actual, expected) {
9669
9727
  const from = this.expand(actual);
9670
- const to = this.expand(expected);
9671
- if (from.kind !== "object" || to.kind !== "object") return "";
9728
+ if (from.kind !== "object") return "";
9729
+ const to = this.likeliestArm(from, this.expand(expected));
9730
+ if (to === void 0) return "";
9672
9731
  const missing = [];
9673
9732
  for (const [name, property] of to.properties) {
9674
9733
  if (property.optional || from.properties.has(name) || INTERNAL_MEMBERS.has(name)) continue;
@@ -9693,6 +9752,34 @@ var TypeAnalyzer = class {
9693
9752
  }
9694
9753
  return "";
9695
9754
  }
9755
+ /** Which object of a contract the value was reaching for. One object is
9756
+ * itself; among several, a discriminant decides — the arm whose literal
9757
+ * properties the value already agrees with (`Status: true` picks
9758
+ * `StatSuccess` over `StatError`). Failing that, the arm it is closest to
9759
+ * fitting, so the explanation is about the shortest gap rather than an
9760
+ * arbitrary one. `undefined` when there is no object to explain against. */
9761
+ likeliestArm(from, expected) {
9762
+ const arms = this.membersOf(expected).filter((m) => m.kind === "object");
9763
+ if (arms.length <= 1) return arms[0];
9764
+ const gapOf = (arm) => {
9765
+ let gap = 0;
9766
+ for (const [name, property] of arm.properties) {
9767
+ if (INTERNAL_MEMBERS.has(name)) continue;
9768
+ const own = from.properties.get(name);
9769
+ if (!own) {
9770
+ if (!property.optional) gap += 1;
9771
+ continue;
9772
+ }
9773
+ if (isAssignable(own.type, property.type)) continue;
9774
+ gap += this.expand(property.type).kind === "literal" ? 100 : 1;
9775
+ }
9776
+ for (const name of from.properties.keys()) {
9777
+ if (!INTERNAL_MEMBERS.has(name) && !arm.properties.has(name) && !arm.indexer) gap += 1;
9778
+ }
9779
+ return gap;
9780
+ };
9781
+ return arms.reduce((best, arm) => gapOf(arm) < gapOf(best) ? arm : best);
9782
+ }
9696
9783
  bindingIdOf(id) {
9697
9784
  return this.scopes.bindingOf.get(id);
9698
9785
  }
@@ -9739,7 +9826,15 @@ function briefType(t) {
9739
9826
  const shown = t.types.slice(0, 6).map(formatType).join(" | ");
9740
9827
  return `${shown} | ... ${t.types.length - 6} more`;
9741
9828
  }
9742
- return formatType(t);
9829
+ const full = formatType(t);
9830
+ if (full.length <= MAX_TYPE) return full;
9831
+ if (t.kind === "object" && t.properties.size) {
9832
+ const names = [...t.properties.keys()].filter((n) => !INTERNAL_MEMBERS.has(n));
9833
+ const shown = names.slice(0, MAX_MISSING);
9834
+ const rest = names.length - shown.length;
9835
+ return `{ ${shown.join(", ")}${rest > 0 ? `, ... ${rest} more` : ""} }`;
9836
+ }
9837
+ return `${full.slice(0, MAX_TYPE)}...`;
9743
9838
  }
9744
9839
 
9745
9840
  // 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.2",
4
4
  "description": "tilua parser for roblox",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.js",