@tilua/parser 2.0.0 → 2.2.0

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
@@ -2398,6 +2398,21 @@ var Parser = class {
2398
2398
  base = { type: "MemberExpression", object: base, property: prop, optional: true, ...spanFrom(base, prop) };
2399
2399
  continue;
2400
2400
  }
2401
+ if (punct === "." && this.punctuatorAt(2, "[")) {
2402
+ this.advance();
2403
+ this.advance();
2404
+ this.expectPunctuator("[");
2405
+ const index = this.inBrackets(() => this.parseExpression());
2406
+ this.expectPunctuator("]");
2407
+ base = {
2408
+ type: "IndexExpression",
2409
+ object: base,
2410
+ index,
2411
+ optional: true,
2412
+ ...spanFrom(base, this.previous())
2413
+ };
2414
+ continue;
2415
+ }
2401
2416
  if (punct === "." && this.punctuatorAt(2, "(")) {
2402
2417
  this.advance();
2403
2418
  this.advance();
@@ -3151,8 +3166,12 @@ var Parser = class {
3151
3166
  * `name?: T` for an optional property or parameter. */
3152
3167
  parseSuffixType() {
3153
3168
  if (this.checkIdentifierValue("readonly")) {
3169
+ const start = this.current();
3154
3170
  this.advance();
3155
- return this.parseSuffixType();
3171
+ const t2 = this.parseSuffixType();
3172
+ if (t2.type === "ArrayTypeNode" || t2.type === "TupleTypeNode") return { ...t2, isReadonly: true };
3173
+ this.problemAt(start, "'readonly' belongs on an array or a tuple: a property is marked where it is written");
3174
+ return t2;
3156
3175
  }
3157
3176
  let t = this.parsePrimaryType();
3158
3177
  while (true) {
@@ -3703,31 +3722,34 @@ var Analyzer = class {
3703
3722
  * can call it, but a call straight in the block before the declaration
3704
3723
  * finds nothing there yet. At a module's top level the whole function is
3705
3724
  * hoisted, and this does not apply. */
3706
- checkUseBeforeDefine(identifier, id) {
3725
+ checkUseBeforeDefine(identifier, id, at = this.functionDepth) {
3707
3726
  const binding = this.bindings.get(id);
3708
- if (binding.declaredBy !== "function" && binding.declaredBy !== "class" || this.typeQueryDepth > 0) return;
3727
+ if (this.typeQueryDepth > 0) return;
3709
3728
  const declaration = binding.declarationNode;
3710
- const depth = declaration && this.hoisted.get(declaration);
3711
- if (depth === void 0 || depth !== this.functionDepth) return;
3729
+ if (!declaration) return;
3730
+ const hoisted = binding.declaredBy === "function" || binding.declaredBy === "class";
3731
+ const depth = hoisted ? this.hoisted.get(declaration) : this.declaredDepth.get(id);
3732
+ const readable = hoisted || binding.kind === "local";
3733
+ if (!readable || depth === void 0 || depth !== at) return;
3712
3734
  const before = identifier.line.start < declaration.line.start || identifier.line.start === declaration.line.start && identifier.column.start < declaration.column.start;
3713
3735
  if (!before) return;
3714
3736
  this.diagnostics.push({
3715
3737
  node: identifier,
3716
- message: `'${binding.name}' is used before its definition: inside a function, a function declared further down is only there once its declaration has run`,
3738
+ message: hoisted ? `'${binding.name}' is used before its definition: inside a function, a function declared further down is only there once its declaration has run` : `'${binding.name}' is used before its declaration, and holds nothing until that line has run`,
3717
3739
  kind: "use-before-define"
3718
3740
  });
3719
3741
  }
3720
3742
  noteDeferred(identifier, scope, id, assignment) {
3721
3743
  if (this.functionDepth === 0 && this.typeQueryDepth === 0) return;
3722
3744
  if (this.bindings.get(id).kind !== "global") return;
3723
- this.deferredGlobals.push({ node: identifier, scope, assignment });
3745
+ this.deferredGlobals.push({ node: identifier, scope, assignment, depth: this.functionDepth });
3724
3746
  }
3725
3747
  /** Point each deferred read of a global at the declaration of that name
3726
3748
  * that turned up later — in the module, or in any block around the code
3727
3749
  * that reads it. Such code runs after the declaration has: a closure
3728
3750
  * written inside a value reads the name the value is bound to. */
3729
3751
  resolveForwardReferences() {
3730
- for (const { node, scope, assignment } of this.deferredGlobals) {
3752
+ for (const { node, scope, assignment, depth } of this.deferredGlobals) {
3731
3753
  const localId = this.lookup(scope, node.name);
3732
3754
  const globalId = this.bindingOf.get(node);
3733
3755
  if (localId === void 0 || globalId === void 0 || localId === globalId) continue;
@@ -3742,7 +3764,10 @@ var Analyzer = class {
3742
3764
  this.bindingOf.set(node, localId);
3743
3765
  this.bindings.get(localId).references.push(node);
3744
3766
  if (assignment) this.checkConstAssign(localId, node);
3745
- else if (this.typeQueryDepth === 0) this.checkTypeOnly(localId, node);
3767
+ else if (this.typeQueryDepth === 0) {
3768
+ this.checkTypeOnly(localId, node);
3769
+ this.checkUseBeforeDefine(node, localId, depth);
3770
+ }
3746
3771
  }
3747
3772
  }
3748
3773
  /** Every read of a global nothing declares. A global assigned somewhere
@@ -3773,9 +3798,13 @@ var Analyzer = class {
3773
3798
  }
3774
3799
  const id = this.nextId++;
3775
3800
  this.bindings.set(id, { id, name, kind, declarationNode: node, references: [], isConst, declaredBy });
3801
+ this.declaredDepth.set(id, this.functionDepth);
3776
3802
  scope.declarations.set(name, id);
3777
3803
  return id;
3778
3804
  }
3805
+ /** Which function body each name was declared in. A read of a `const` from
3806
+ * the same body, but above it, is a read of nothing. */
3807
+ declaredDepth = /* @__PURE__ */ new Map();
3779
3808
  resolve(scope, name) {
3780
3809
  for (let s = scope; s; s = s.parent) {
3781
3810
  const id = s.declarations.get(name);
@@ -4428,9 +4457,9 @@ function substitute(t, subst) {
4428
4457
  case "typeParam":
4429
4458
  return subst.get(t.name) ?? t;
4430
4459
  case "array":
4431
- return arrayOf(substitute(t.element, subst));
4460
+ return { ...arrayOf(substitute(t.element, subst)), readonly: t.readonly };
4432
4461
  case "tuple":
4433
- return tuple(t.elements.map((e) => substitute(e, subst)), t.rest && substitute(t.rest, subst));
4462
+ return { ...tuple(t.elements.map((e) => substitute(e, subst)), t.rest && substitute(t.rest, subst)), readonly: t.readonly };
4434
4463
  case "object": {
4435
4464
  const entries = [];
4436
4465
  for (const [k, v] of t.properties) entries.push([k, { ...v, type: substitute(v.type, subst) }]);
@@ -4536,14 +4565,17 @@ function classArguments(t, name) {
4536
4565
  if (t.kind === "object") return t.class?.typeArguments?.get(name);
4537
4566
  return void 0;
4538
4567
  }
4539
- function unify(param, arg, vars, out) {
4568
+ function unify(param, arg, vars, out, how = "union") {
4540
4569
  if (param.kind === "typeParam" && param.constraint && !vars.has(param.name)) {
4541
- unify(param.constraint, arg, vars, out);
4570
+ unify(param.constraint, arg, vars, out, how);
4542
4571
  return;
4543
4572
  }
4544
4573
  if (param.kind === "typeParam" && vars.has(param.name)) {
4545
4574
  const prev = out.get(param.name);
4546
- out.set(param.name, prev ? union([prev, arg]) : arg);
4575
+ if (!prev) out.set(param.name, arg);
4576
+ else if (isAssignable(arg, prev)) {
4577
+ } else if (isAssignable(prev, arg)) out.set(param.name, arg);
4578
+ else if (how === "union") out.set(param.name, union([prev, arg]));
4547
4579
  return;
4548
4580
  }
4549
4581
  if (arg.kind === "any" || arg.kind === "never") return;
@@ -4595,6 +4627,20 @@ function unify(param, arg, vars, out) {
4595
4627
  return;
4596
4628
  }
4597
4629
  }
4630
+ function memberKey(t) {
4631
+ switch (t.kind) {
4632
+ case "literal":
4633
+ return `l${t.base}:${String(t.value)}`;
4634
+ case "primitive":
4635
+ return `p${t.name}`;
4636
+ case "any":
4637
+ case "unknown":
4638
+ case "never":
4639
+ return t.kind;
4640
+ default:
4641
+ return formatType(t);
4642
+ }
4643
+ }
4598
4644
  function union(types) {
4599
4645
  const flat = [];
4600
4646
  for (const t of types) {
@@ -4606,7 +4652,7 @@ function union(types) {
4606
4652
  const seen = /* @__PURE__ */ new Map();
4607
4653
  for (const t of flat) {
4608
4654
  if (t.kind === "never") continue;
4609
- const key = formatType(t);
4655
+ const key = memberKey(t);
4610
4656
  if (!seen.has(key)) seen.set(key, t);
4611
4657
  }
4612
4658
  let members = reduceSubtypes([...seen.values()]);
@@ -4635,7 +4681,7 @@ function intersection(types) {
4635
4681
  const seen = /* @__PURE__ */ new Map();
4636
4682
  for (const t of flat) {
4637
4683
  if (t.kind === "unknown") continue;
4638
- seen.set(formatType(t), t);
4684
+ seen.set(memberKey(t), t);
4639
4685
  }
4640
4686
  const members = [...seen.values()];
4641
4687
  if (members.length === 0) return unknownType;
@@ -4662,9 +4708,9 @@ function widen(t) {
4662
4708
  case "literal":
4663
4709
  return primitive(t.base);
4664
4710
  case "array":
4665
- return arrayOf(widen(t.element));
4711
+ return { ...arrayOf(widen(t.element)), readonly: t.readonly };
4666
4712
  case "tuple":
4667
- return tuple(t.elements.map(widen), t.rest && widen(t.rest));
4713
+ return { ...tuple(t.elements.map(widen), t.rest && widen(t.rest)), readonly: t.readonly };
4668
4714
  case "object": {
4669
4715
  if (t.frozen || t.class) return t;
4670
4716
  const entries = [];
@@ -4762,6 +4808,7 @@ function isAssignableInner(a, b) {
4762
4808
  return b.kind === "primitive" && b.name === "string";
4763
4809
  }
4764
4810
  if (a.kind === "primitive") return b.kind === "primitive" && b.name === a.name;
4811
+ if ((a.kind === "array" || a.kind === "tuple") && a.readonly && (b.kind === "array" || b.kind === "tuple") && !b.readonly) return false;
4765
4812
  if (a.kind === "array") {
4766
4813
  if (b.kind === "array") return isAssignable(a.element, b.element);
4767
4814
  if (b.kind === "tuple") return b.elements.length === 0 && !!b.rest && isAssignable(a.element, b.rest);
@@ -5107,10 +5154,10 @@ function formatTypeUncached(t) {
5107
5154
  case "literal":
5108
5155
  return t.base === "string" ? JSON.stringify(t.value) : String(t.value);
5109
5156
  case "array":
5110
- return `${formatAtom(t.element)}[]`;
5157
+ return `${t.readonly ? "readonly " : ""}${formatAtom(t.element)}[]`;
5111
5158
  case "tuple": {
5112
5159
  const inner = [...t.elements.map(formatType), ...t.rest ? [`...${formatAtom(t.rest)}[]`] : []];
5113
- return `[${inner.join(", ")}]`;
5160
+ return `${t.readonly ? "readonly " : ""}[${inner.join(", ")}]`;
5114
5161
  }
5115
5162
  case "object": {
5116
5163
  if (t.name) {
@@ -5633,7 +5680,7 @@ function expressionLabel(e, depth = 0) {
5633
5680
  case "IndexExpression": {
5634
5681
  const o = expressionLabel(e.object, depth + 1);
5635
5682
  const i = e.index.type === "StringLiteral" ? JSON.stringify(e.index.value) : e.index.type === "NumberLiteral" ? e.index.raw : e.index.type === "Identifier" ? e.index.name : "...";
5636
- return o === void 0 ? void 0 : `${o}[${i}]`;
5683
+ return o === void 0 ? void 0 : `${o}${e.optional ? "?." : ""}[${i}]`;
5637
5684
  }
5638
5685
  case "ParenthesizedExpression": {
5639
5686
  const inner = expressionLabel(e.expression, depth + 1);
@@ -5684,7 +5731,8 @@ var TypeAnalyzer = class {
5684
5731
  /** Put on every ref to one of `aliasDefs`, so a module that imports the
5685
5732
  * type expands the ref here rather than by its name there. */
5686
5733
  origin = { expand: (t) => this.expand(t) };
5687
- /** See `resolveClass`. */
5734
+ /** See `resolveClass`. Shared with other analyses over the same libraries
5735
+ * when none of their names is redefined here; see `shareableClasses`. */
5688
5736
  classTypes = /* @__PURE__ */ new WeakMap();
5689
5737
  /** See `instanceType` — one instance type per `class ... end`. */
5690
5738
  instanceTypes = /* @__PURE__ */ new WeakMap();
@@ -5730,6 +5778,11 @@ var TypeAnalyzer = class {
5730
5778
  /** Recursion guard for `preVisitBody`. */
5731
5779
  preVisitDepth = 0;
5732
5780
  run() {
5781
+ const shared = shareableClasses(this.options.libs ?? [], this.program);
5782
+ if (shared) {
5783
+ this.classTypes = shared.classTypes;
5784
+ this.classMembers = shared.classMembers;
5785
+ }
5733
5786
  this.registerAliasDefs(preludeProgram().body, true);
5734
5787
  for (const lib of this.options.libs ?? []) this.registerAliasDefs(lib.body, true);
5735
5788
  this.registerAliasDefs(this.program.body);
@@ -6895,14 +6948,17 @@ var TypeAnalyzer = class {
6895
6948
  return literal(node.value);
6896
6949
  case "TypeLiteralNumber":
6897
6950
  return literal(node.value);
6898
- case "ArrayTypeNode":
6899
- return arrayOf(this.resolveType(node.element));
6951
+ case "ArrayTypeNode": {
6952
+ const made = arrayOf(this.resolveType(node.element));
6953
+ return node.isReadonly ? { ...made, readonly: true } : made;
6954
+ }
6900
6955
  case "TupleTypeNode": {
6901
6956
  const rest = node.rest && this.expand(this.resolveType(node.rest));
6902
- return tuple(
6957
+ const made = tuple(
6903
6958
  node.elements.map((e) => this.resolveType(e)),
6904
6959
  rest ? rest.kind === "array" ? rest.element : rest.kind === "any" ? anyType : unknownType : void 0
6905
6960
  );
6961
+ return node.isReadonly && made.kind === "tuple" ? { ...made, readonly: true } : made;
6906
6962
  }
6907
6963
  case "UnionTypeNode":
6908
6964
  return union(node.types.map((t) => this.resolveType(t)));
@@ -7704,8 +7760,10 @@ var TypeAnalyzer = class {
7704
7760
  // --------------------------------------------------------
7705
7761
  // Functions
7706
7762
  // --------------------------------------------------------
7707
- visitFunctionBody(func, outerEnv) {
7708
- this.withTypeParams(func.generics, () => this.visitFunctionBodyInner(func, outerEnv));
7763
+ /** `returns`, when given, collects the type of each `return` the walk
7764
+ * reads — see `inferFunctionBody`. */
7765
+ visitFunctionBody(func, outerEnv, returns) {
7766
+ this.withTypeParams(func.generics, () => this.visitFunctionBodyInner(func, outerEnv, returns));
7709
7767
  }
7710
7768
  /** A parameter's type: annotation, else a shape synthesized from a
7711
7769
  * destructuring pattern, else inferred from its default, else `any`. */
@@ -7722,8 +7780,19 @@ var TypeAnalyzer = class {
7722
7780
  if (p.rest) return arrayOf(unknownType);
7723
7781
  if (p.pattern) return this.patternToType(p.pattern, env);
7724
7782
  if (p.default) return widen(this.infer(p.default, env));
7725
- return this.contextualParams.get(p) ?? anyType;
7783
+ const contextual = this.contextualParams.get(p);
7784
+ if (contextual) return contextual;
7785
+ if (this.emitDiagnostics && !receiver && p.name && p.line && !this.untypedParamReported.has(p)) {
7786
+ this.untypedParamReported.add(p);
7787
+ this.diagnostics.push({
7788
+ node: p,
7789
+ message: `Parameter '${p.name}' has no type, so it is 'any': give it one, or a default to read it from`
7790
+ });
7791
+ }
7792
+ return anyType;
7726
7793
  }
7794
+ /** Parameters already spoken about: a body is visited more than once. */
7795
+ untypedParamReported = /* @__PURE__ */ new WeakSet();
7727
7796
  /** What a function expression's unannotated parameters are, from where
7728
7797
  * it is written — see `applyContext`. */
7729
7798
  contextualParams = /* @__PURE__ */ new WeakMap();
@@ -7743,6 +7812,53 @@ var TypeAnalyzer = class {
7743
7812
  * it, as in TypeScript: `signal:Connect(function(player) ... end)` knows
7744
7813
  * `player` from `Connect`'s callback type. Anything else is inferred as
7745
7814
  * usual. */
7815
+ /** The arguments of a call, in two passes where one is needed.
7816
+ *
7817
+ * A function written at the call site with a parameter nothing annotates
7818
+ * reads that parameter from the signature — and where the signature says
7819
+ * `(v: T) => U`, `T` is only known once the other arguments have been
7820
+ * read. So such an argument is read once quietly, to get a shape to pick
7821
+ * a signature with, and then again with `T` in hand: `map(xs, v => v + 1)`
7822
+ * has its `v` a number by then, and `U` follows from what the body
7823
+ * answers. Everything else is read once, as it always was. */
7824
+ inferArguments(written, fns, selfOf, extra, env) {
7825
+ const expected = this.expectedArguments(written, fns, selfOf);
7826
+ written.forEach((a, i) => this.applyContext(a, expected[i]));
7827
+ const waits = written.map((a, i) => this.waitsOnInference(a, expected[i]));
7828
+ if (!waits.some(Boolean)) return written.map((a) => this.infer(a, env));
7829
+ const argTypes = written.map((a, i) => waits[i] ? this.quietly(() => this.infer(a, env)) : this.infer(a, env));
7830
+ const picked = this.pickOverload(fns, argTypes, (f) => extra(f, argTypes), this.spreadOf(written, selfOf(fns[0])));
7831
+ const subst = picked?.typeParams?.length ? this.inferTypeArgs(picked, extra(picked, argTypes)) : void 0;
7832
+ written.forEach((a, i) => {
7833
+ if (!waits[i]) return;
7834
+ if (picked && subst) {
7835
+ const self = selfOf(picked);
7836
+ const declared = i + self < picked.params.length ? picked.params[i + self].type : picked.varargs;
7837
+ if (declared) this.applyContext(a, this.reduceType(substitute(declared, subst)));
7838
+ }
7839
+ argTypes[i] = this.infer(a, env);
7840
+ });
7841
+ return argTypes;
7842
+ }
7843
+ /** Is this argument a function whose own type waits on the call's? */
7844
+ waitsOnInference(arg, expected) {
7845
+ if (!expected || !containsTypeParam(expected)) return false;
7846
+ let e = arg;
7847
+ while (e.type === "ParenthesizedExpression") e = e.expression;
7848
+ if (e.type !== "FunctionExpression") return false;
7849
+ return e.func.params.some((p) => !p.typeAnnotation && !p.default && !p.pattern && p.name !== "self" && p.name !== "this");
7850
+ }
7851
+ /** Read something without saying anything about it: a first pass whose
7852
+ * only purpose is a shape to go on. */
7853
+ quietly(read) {
7854
+ const was = this.emitDiagnostics;
7855
+ this.emitDiagnostics = false;
7856
+ try {
7857
+ return read();
7858
+ } finally {
7859
+ this.emitDiagnostics = was;
7860
+ }
7861
+ }
7746
7862
  applyContext(expr, expected) {
7747
7863
  let e = expr;
7748
7864
  while (e.type === "ParenthesizedExpression") e = e.expression;
@@ -7891,7 +8007,7 @@ var TypeAnalyzer = class {
7891
8007
  this.contextualReturns.pop();
7892
8008
  }
7893
8009
  }
7894
- visitFunctionBodyInner(func, outerEnv) {
8010
+ visitFunctionBodyInner(func, outerEnv, returns) {
7895
8011
  const env = forkEnv(outerEnv);
7896
8012
  for (const p of func.params) {
7897
8013
  if (p.pattern) {
@@ -7908,7 +8024,7 @@ var TypeAnalyzer = class {
7908
8024
  if (p.typeAnnotation) this.annotated.add(id);
7909
8025
  }
7910
8026
  }
7911
- this.withVarargs(func, () => this.collectReturns(void 0, () => {
8027
+ this.withVarargs(func, () => this.collectReturns(returns, () => {
7912
8028
  this.visitBlock(func.body, env);
7913
8029
  this.checkReturnsAtAll(func, this.declaredReturns[this.declaredReturns.length - 1]);
7914
8030
  }));
@@ -7962,7 +8078,7 @@ var TypeAnalyzer = class {
7962
8078
  if (arg === void 0) return;
7963
8079
  const param = p.type.kind === "typeParam" && p.type.constraint ? { ...p.type, constraint: this.reduceType(p.type.constraint) } : p.type;
7964
8080
  const preserve = keepsLiterals(param) || param.kind !== "typeParam" && containsTypeParam(param);
7965
- unify(p.type, preserve ? arg : widen(arg), vars, subst);
8081
+ unify(p.type, preserve ? arg : widen(arg), vars, subst, "first");
7966
8082
  });
7967
8083
  if (f.varargs && f.restIsWhole) {
7968
8084
  const rest = argTypes.slice(f.params.length).map((a) => a === void 0 ? unknownType : widen(a));
@@ -8018,6 +8134,11 @@ var TypeAnalyzer = class {
8018
8134
  * this returns `undefined` and the ordinary pick stands. */
8019
8135
  distributedReturn(fns, argTypes, picked, argsFor) {
8020
8136
  if (fns.length < 2) return void 0;
8137
+ if (argTypes.some((t) => this.expand(t).kind === "any")) {
8138
+ const taken = fns.filter((f) => this.overloadAccepts(f, argsFor(f, argTypes)));
8139
+ if (taken.length < 2) return void 0;
8140
+ return union(taken.map((f) => this.callReturn(f, argsFor(f, argTypes))));
8141
+ }
8021
8142
  const position = argTypes.findIndex((t) => this.expand(t).kind === "union");
8022
8143
  if (position < 0) return void 0;
8023
8144
  const members = this.expand(argTypes[position]).types;
@@ -8066,6 +8187,8 @@ var TypeAnalyzer = class {
8066
8187
  * lone argument cannot be checked against without false errors. */
8067
8188
  boundParams(f) {
8068
8189
  if (!f.typeParams?.length) return f.params.map((p) => p.type);
8190
+ const known = this.boundParamsCache.get(f);
8191
+ if (known) return known;
8069
8192
  const bounds = new Map(f.typeParams.map((name) => [name, anyType]));
8070
8193
  const seen = /* @__PURE__ */ new WeakSet();
8071
8194
  const walk = (value) => {
@@ -8083,8 +8206,11 @@ var TypeAnalyzer = class {
8083
8206
  for (const child of Object.values(value)) walk(child);
8084
8207
  };
8085
8208
  for (const p of f.params) if (containsTypeParam(p.type)) walk(p.type);
8086
- return f.params.map((p) => this.reduceType(substitute(p.type, bounds)));
8209
+ const bound = f.params.map((p) => this.reduceType(substitute(p.type, bounds)));
8210
+ this.boundParamsCache.set(f, bound);
8211
+ return bound;
8087
8212
  }
8213
+ boundParamsCache = /* @__PURE__ */ new WeakMap();
8088
8214
  /** Record what each written argument is expected to be — see
8089
8215
  * `TypeAnalysis.expectedTypeOf`. */
8090
8216
  recordExpected(written, fns, selfOf, argsOf = () => []) {
@@ -8303,7 +8429,12 @@ var TypeAnalyzer = class {
8303
8429
  asserts: node.asserts
8304
8430
  };
8305
8431
  }
8306
- inferFunctionBody(func, env) {
8432
+ /** `visited`, when given, is what the real walk of the body just
8433
+ * collected from its `return`s: a function expression is walked before
8434
+ * its type is asked for, so there is nothing left to guess, and walking
8435
+ * the body a second time only to read them made every callback nested in
8436
+ * another cost twice its parent's. */
8437
+ inferFunctionBody(func, env, visited) {
8307
8438
  const names = func.generics.map((g) => g.name);
8308
8439
  return this.withTypeParams(func.generics, () => {
8309
8440
  const params = func.params.flatMap((p) => {
@@ -8332,6 +8463,8 @@ var TypeAnalyzer = class {
8332
8463
  returns = this.resolveType(func.returnType);
8333
8464
  } else if (func.predicate) {
8334
8465
  returns = booleanType;
8466
+ } else if (visited) {
8467
+ returns = visited.length ? union([...visited]) : this.withVarargs(func, () => this.silently(() => this.inferReturnType(func.body, bodyEnv)));
8335
8468
  } else {
8336
8469
  const collected = [];
8337
8470
  returns = this.withVarargs(func, () => this.silently(() => {
@@ -8548,7 +8681,7 @@ var TypeAnalyzer = class {
8548
8681
  /** What one `for (const item in source)` hands over each time, and how it
8549
8682
  * walks the source. `pairs`/`ipairs` are read from the table they are
8550
8683
  * given, so a record's keys stay the literals they are. */
8551
- iterationOf(source, sourceType) {
8684
+ iterationOf(source, sourceType, report = true) {
8552
8685
  if (source.type === "CallExpression" && source.callee.type === "Identifier" && source.arguments[0]) {
8553
8686
  const name = source.callee.name;
8554
8687
  const src = this.expand(this.typeOf.get(source.arguments[0]) ?? unknownType);
@@ -8586,7 +8719,14 @@ var TypeAnalyzer = class {
8586
8719
  if (t.kind === "object") {
8587
8720
  return [t.indexer?.value ?? union([...t.properties.values()].map((p) => p.type)), values];
8588
8721
  }
8589
- if (t.kind !== "never" && this.emitDiagnostics) {
8722
+ if (t.kind === "union") {
8723
+ const each = t.types.map((member) => this.iterationOf(source, member, false));
8724
+ const [first] = each;
8725
+ if (first && each.every(([, form]) => form.walks === first[1].walks && form.viaIter === first[1].viaIter)) {
8726
+ return [union(each.map(([item]) => item)), first[1]];
8727
+ }
8728
+ }
8729
+ if (t.kind !== "never" && report && this.emitDiagnostics) {
8590
8730
  this.diagnostics.push({
8591
8731
  node: source,
8592
8732
  message: `Cannot loop over '${formatType(sourceType)}': a loop walks an array, a table, an iterator function or an iteration such as 'pairs(t)'`
@@ -8955,7 +9095,14 @@ var TypeAnalyzer = class {
8955
9095
  const names = keys.map(nameOf);
8956
9096
  const others = keys.filter((_, i) => names[i] === void 0);
8957
9097
  let message;
8958
- if (others.length && this.cannotIndex(object, others)) {
9098
+ const listOnly = (t) => {
9099
+ const x = this.expand(t);
9100
+ if (x.kind === "union") return x.types.every(listOnly);
9101
+ return x.kind === "array" || x.kind === "tuple";
9102
+ };
9103
+ if (names.some((name) => name !== void 0) && listOnly(withoutNil(this.expand(object)))) {
9104
+ message = `Type '${briefType(index)}' cannot be used to index type '${shown}'`;
9105
+ } else if (others.length && this.cannotIndex(object, others)) {
8959
9106
  message = `Type '${briefType(index)}' cannot be used to index type '${shown}'`;
8960
9107
  } else if (!others.length && names.every((name) => this.memberMissing(object, name))) {
8961
9108
  message = keys.length === 1 ? `Property '${names[0]}' does not exist on type '${shown}'` : `None of ${briefType(index)} is a property of type '${shown}'`;
@@ -9190,10 +9337,12 @@ var TypeAnalyzer = class {
9190
9337
  return this.inferArray(expr, env, false);
9191
9338
  case "TableExpression":
9192
9339
  return this.inferObject(expr, env, false);
9193
- case "FunctionExpression":
9340
+ case "FunctionExpression": {
9194
9341
  this.checkParamOrder(expr.func.params, expr);
9195
- this.visitFunctionBody(expr.func, env);
9196
- return this.inferFunctionBody(expr.func, env);
9342
+ const returned = [];
9343
+ this.visitFunctionBody(expr.func, env, returned);
9344
+ return this.inferFunctionBody(expr.func, env, returned);
9345
+ }
9197
9346
  case "ParenthesizedExpression":
9198
9347
  return this.infer(expr.expression, env);
9199
9348
  case "TypeAssertionExpression": {
@@ -9227,10 +9376,18 @@ var TypeAnalyzer = class {
9227
9376
  switch (expr.operator) {
9228
9377
  case "not":
9229
9378
  return booleanType;
9230
- case "-":
9231
- return this.operatorResult(expr, "-", arg, void 0) ?? this.arithmeticOn([arg]);
9232
- case "#":
9233
- return this.operatorResult(expr, "#", arg, void 0) ?? numberType;
9379
+ case "-": {
9380
+ const answered = this.operatorResult(expr, "-", arg, void 0);
9381
+ if (answered) return answered;
9382
+ this.checkOperands(expr, "-", arg);
9383
+ return this.arithmeticOn([arg]);
9384
+ }
9385
+ case "#": {
9386
+ const answered = this.operatorResult(expr, "#", arg, void 0);
9387
+ if (answered) return answered;
9388
+ this.checkOperands(expr, "#", arg);
9389
+ return numberType;
9390
+ }
9234
9391
  }
9235
9392
  return arg;
9236
9393
  }
@@ -9259,16 +9416,21 @@ var TypeAnalyzer = class {
9259
9416
  if (unwrapParens(expr.left).type === "StringLiteral") this.expectedTypeOf.set(unwrapParens(expr.left), r);
9260
9417
  }
9261
9418
  switch (op) {
9262
- case "..":
9263
- return this.operatorResult(expr, op, l, r) ?? stringType;
9419
+ case "..": {
9420
+ const answered = this.operatorResult(expr, op, l, r);
9421
+ if (answered) return answered;
9422
+ this.checkOperands(expr, op, l, r);
9423
+ return stringType;
9424
+ }
9264
9425
  case "<":
9265
9426
  case ">":
9266
9427
  case "<=":
9267
9428
  case ">=":
9268
- this.checkComparison(expr, op, l, r);
9429
+ if (!this.checkComparison(expr, op, l, r)) this.checkOperands(expr, op, l, r);
9269
9430
  return booleanType;
9270
9431
  case "==":
9271
9432
  case "~=":
9433
+ this.checkOverlap(expr, op, l, r);
9272
9434
  return booleanType;
9273
9435
  case "+":
9274
9436
  case "-":
@@ -9276,8 +9438,12 @@ var TypeAnalyzer = class {
9276
9438
  case "/":
9277
9439
  case "//":
9278
9440
  case "%":
9279
- case "^":
9280
- return this.operatorResult(expr, op, l, r) ?? this.arithmeticOn([l, r]);
9441
+ case "^": {
9442
+ const answered = this.operatorResult(expr, op, l, r);
9443
+ if (answered) return answered;
9444
+ this.checkOperands(expr, op, l, r);
9445
+ return this.arithmeticOn([l, r]);
9446
+ }
9281
9447
  }
9282
9448
  return union([l, r]);
9283
9449
  }
@@ -9380,9 +9546,7 @@ var TypeAnalyzer = class {
9380
9546
  const united = this.unionSignatures(callee);
9381
9547
  const fns = united ?? this.overloadsOf(callee);
9382
9548
  const explicit = this.explicitTypeArguments(expr, fns);
9383
- const expected = this.expectedArguments(expr.arguments, fns, () => 0);
9384
- expr.arguments.forEach((a, i) => this.applyContext(a, expected[i]));
9385
- const argTypes = expr.arguments.map((a) => this.infer(a, env));
9549
+ const argTypes = this.inferArguments(expr.arguments, fns, () => 0, (_, args) => args, env);
9386
9550
  if (fns.length) {
9387
9551
  this.recordExpected(expr.arguments, fns, () => 0, () => argTypes);
9388
9552
  const spread = this.spreadOf(expr.arguments);
@@ -9447,9 +9611,13 @@ var TypeAnalyzer = class {
9447
9611
  const united = this.unionSignatures(method);
9448
9612
  const fns = united ?? this.overloadsOf(method);
9449
9613
  const explicit = this.explicitTypeArguments(expr, fns);
9450
- const expected = this.expectedArguments(expr.arguments, fns, (f) => this.takesSelf(f) ? 1 : 0);
9451
- expr.arguments.forEach((a, i) => this.applyContext(a, expected[i]));
9452
- const argTypes = expr.arguments.map((a) => this.infer(a, env));
9614
+ const argTypes = this.inferArguments(
9615
+ expr.arguments,
9616
+ fns,
9617
+ (f) => this.takesSelf(f) ? 1 : 0,
9618
+ (f, args) => this.takesSelf(f) ? [objType, ...args] : args,
9619
+ env
9620
+ );
9453
9621
  if (fns.length) {
9454
9622
  const withSelf = (f) => this.takesSelf(f) ? [objType, ...argTypes] : argTypes;
9455
9623
  const selfOf = (f) => this.takesSelf(f) ? 1 : 0;
@@ -9525,6 +9693,14 @@ var TypeAnalyzer = class {
9525
9693
  name = target.property.name;
9526
9694
  object = target.object;
9527
9695
  } else if (target.type === "IndexExpression") {
9696
+ const list = this.expand(this.infer(target.object, env));
9697
+ if ((list.kind === "array" || list.kind === "tuple") && list.readonly) {
9698
+ this.diagnostics.push({
9699
+ node: target,
9700
+ message: `Cannot assign to an element of '${formatType(list)}': it is read-only`
9701
+ });
9702
+ return;
9703
+ }
9528
9704
  const index = this.expand(this.infer(target.index, env));
9529
9705
  if (index.kind !== "literal" || typeof index.value !== "string") return;
9530
9706
  name = index.value;
@@ -9697,9 +9873,27 @@ var TypeAnalyzer = class {
9697
9873
  if (s.indexer) indexer = mergeIndexer(indexer, s.indexer);
9698
9874
  }
9699
9875
  }
9876
+ this.checkDuplicateKey(expr, field, entries);
9700
9877
  }
9701
9878
  return objectType(entries, indexer, asConst || void 0);
9702
9879
  }
9880
+ /** `{ a: 1, a: 2 }` — the second wins and the first was written for
9881
+ * nothing, which is a typo far more often than a decision. A spread is
9882
+ * not one: overriding what it brought is exactly what it is for. */
9883
+ checkDuplicateKey(expr, field, entries) {
9884
+ if (!this.emitDiagnostics || this.duplicateKeyReported.has(field)) return;
9885
+ const written = field.type === "TableFieldNamed" ? field.key.type === "Identifier" ? field.key.name : field.key.value : field.type === "TableFieldShorthand" ? field.name.name : void 0;
9886
+ if (written === void 0) return;
9887
+ const spreadBefore = expr.fields.slice(0, expr.fields.indexOf(field)).some((f) => f.type === "TableFieldSpread");
9888
+ if (spreadBefore) return;
9889
+ if (entries.filter(([k]) => k === written).length < 2) return;
9890
+ this.duplicateKeyReported.add(field);
9891
+ this.diagnostics.push({
9892
+ node: field.type === "TableFieldNamed" ? field.key : field.name,
9893
+ message: `'${written}' is given twice in this table; only the last one is kept`
9894
+ });
9895
+ }
9896
+ duplicateKeyReported = /* @__PURE__ */ new WeakSet();
9703
9897
  /** A value inferred `as const`, widened back wherever `context` does not
9704
9898
  * ask for a literal: `satisfies`' result type. A property keeps `"circle"`
9705
9899
  * when the contract's property admits string literals, and becomes
@@ -10192,27 +10386,84 @@ var TypeAnalyzer = class {
10192
10386
  arithmeticOn(operands) {
10193
10387
  return operands.some((t) => this.expand(t).kind === "any") ? anyType : numberType;
10194
10388
  }
10389
+ /** `a == b` where nothing `a` can hold is anything `b` can: the answer is
10390
+ * always the same, so the comparison was not the one meant. Only types
10391
+ * that say what they hold take part — `any`, `unknown` and a type
10392
+ * parameter say nothing, and a table compares by identity, so two of them
10393
+ * may well be the same table. */
10394
+ checkOverlap(node, op, l, r) {
10395
+ if (!this.emitDiagnostics) return;
10396
+ const left = this.expand(l);
10397
+ const right = this.expand(r);
10398
+ const judged = (t) => {
10399
+ const parts = t.kind === "union" ? t.types.map((m) => this.expand(m)) : [t];
10400
+ return parts.every((p) => p.kind === "literal" || p.kind === "primitive");
10401
+ };
10402
+ const isNil = (t) => t.kind === "primitive" && t.name === "nil";
10403
+ if (isNil(left) || isNil(right)) return;
10404
+ if (!judged(left) || !judged(right)) return;
10405
+ if (isAssignable(left, right) || isAssignable(right, left)) return;
10406
+ this.diagnostics.push({
10407
+ node,
10408
+ message: `This comparison is always ${op === "==" ? "false" : "true"}: '${formatType(l)}' and '${formatType(r)}' have no value in common`
10409
+ });
10410
+ }
10411
+ /** What an operator takes, where no metamethod answered for it. Luau
10412
+ * raises on the rest — `n * 2` with `n` nil is an error at the line it
10413
+ * runs, and a nil `n` is exactly what `number | nil` says may happen — so
10414
+ * it is said here instead. `any` passes: nothing is known about it. */
10415
+ checkOperands(node, op, left, right) {
10416
+ if (!this.emitDiagnostics) return;
10417
+ const fits = (t) => {
10418
+ const x = this.expand(t);
10419
+ if (x.kind === "any" || x.kind === "never") return true;
10420
+ switch (op) {
10421
+ // `..` joins strings, and Luau writes a number into one.
10422
+ case "..":
10423
+ return isAssignable(x, stringType) || isAssignable(x, numberType);
10424
+ // `#` counts a string's bytes or a table's entries.
10425
+ case "#":
10426
+ return x.kind === "array" || x.kind === "tuple" || x.kind === "object" || isAssignable(x, stringType);
10427
+ default:
10428
+ return isAssignable(x, numberType);
10429
+ }
10430
+ };
10431
+ if (op === "<" || op === ">" || op === "<=" || op === ">=") {
10432
+ const both = (t) => this.expand(t);
10433
+ const [l, r] = [both(left), both(right)];
10434
+ const loose = (t) => t.kind === "any" || t.kind === "never";
10435
+ const alike = (as) => isAssignable(l, as) && isAssignable(r, as);
10436
+ if (loose(l) || loose(r) || alike(numberType) || alike(stringType)) return;
10437
+ } else if (fits(left) && (right === void 0 || fits(right))) {
10438
+ return;
10439
+ }
10440
+ this.diagnostics.push({
10441
+ node,
10442
+ message: right === void 0 ? `Operator '${op}' cannot be applied to type '${formatType(left)}'` : `Operator '${op}' cannot be applied to types '${formatType(left)}' and '${formatType(right)}'`
10443
+ });
10444
+ }
10195
10445
  /** `a < b` between class instances: Luau asks `__lt` (`__le` for `<=`),
10196
10446
  * with the operands swapped for `>` and `>=`, and an instance without
10197
10447
  * one cannot be compared at all. */
10198
10448
  checkComparison(node, op, l, r) {
10199
- if (!this.emitDiagnostics) return;
10449
+ if (!this.emitDiagnostics) return false;
10200
10450
  const left = this.expand(l);
10201
10451
  const right = this.expand(r);
10202
10452
  const instance = (t) => t.kind === "object" && t.class !== void 0;
10203
- if (!instance(left) && !instance(right)) return;
10453
+ if (!instance(left) && !instance(right)) return false;
10204
10454
  const name = op === "<" || op === ">" ? "__lt" : "__le";
10205
10455
  const operands = op === ">" || op === ">=" ? [right, left] : [left, right];
10206
10456
  for (const receiver of operands) {
10207
10457
  const method = receiver.kind === "object" ? receiver.properties.get(name) : void 0;
10208
10458
  if (!method) continue;
10209
- if (this.pickOverload(this.overloadsOf(method.type), operands)) return;
10459
+ if (this.pickOverload(this.overloadsOf(method.type), operands)) return true;
10210
10460
  break;
10211
10461
  }
10212
10462
  this.diagnostics.push({
10213
10463
  node,
10214
10464
  message: `Operator '${op}' cannot be applied to types '${formatType(l)}' and '${formatType(r)}'`
10215
10465
  });
10466
+ return true;
10216
10467
  }
10217
10468
  /** What an operator on a value with metamethods gives: `a + b` calls
10218
10469
  * `__add` on `a`, or failing that on `b` with the operands swapped — the
@@ -10499,7 +10750,56 @@ var TypeAnalyzer = class {
10499
10750
  return this.bindingByDecl.get(node) ?? this.bindingByPos.get(posKey(name, node.line.start, node.column.start));
10500
10751
  }
10501
10752
  };
10502
- function referencedTypeNames(node, out = []) {
10753
+ var libraryNames = /* @__PURE__ */ new WeakMap();
10754
+ function namesDeclaredIn(program) {
10755
+ const known = libraryNames.get(program);
10756
+ if (known) return known;
10757
+ const names = /* @__PURE__ */ new Set();
10758
+ for (const stmt of program.body.statements) {
10759
+ const declaration = stmt.type === "ExportStatement" || stmt.type === "ExportDefaultStatement" ? stmt.declaration : stmt;
10760
+ if (declaration.type === "TypeAliasStatement") names.add(declaration.name.name);
10761
+ else if (declaration.type === "ExportTypeAliasStatement") names.add(declaration.alias.name.name);
10762
+ else if (declaration.type === "DeclareClassStatement") names.add(declaration.name.name);
10763
+ else if (declaration.type === "ClassDeclaration" && declaration.name) names.add(declaration.name.name);
10764
+ else if (stmt.type === "ImportStatement") {
10765
+ for (const s of stmt.specifiers) names.add(s.local.name);
10766
+ if (stmt.defaultImport) names.add(stmt.defaultImport.name);
10767
+ if (stmt.namespaceImport) names.add(stmt.namespaceImport.name);
10768
+ }
10769
+ }
10770
+ libraryNames.set(program, names);
10771
+ return names;
10772
+ }
10773
+ var sharedClassesByLib = /* @__PURE__ */ new WeakMap();
10774
+ function shareableClasses(libs, program) {
10775
+ if (!libs.length) return void 0;
10776
+ const mine = namesDeclaredIn(program);
10777
+ if (mine.size) {
10778
+ for (const lib of libs) {
10779
+ for (const name of namesDeclaredIn(lib)) if (mine.has(name)) return void 0;
10780
+ }
10781
+ }
10782
+ const existing = sharedClassesByLib.get(libs[0]);
10783
+ if (existing) {
10784
+ const same = existing.libs.length === libs.length && existing.libs.every((lib, i) => lib === libs[i]);
10785
+ if (same) return existing;
10786
+ return void 0;
10787
+ }
10788
+ const store = { libs: [...libs], classTypes: /* @__PURE__ */ new WeakMap(), classMembers: /* @__PURE__ */ new WeakMap() };
10789
+ sharedClassesByLib.set(libs[0], store);
10790
+ return store;
10791
+ }
10792
+ var referencedNamesOf = /* @__PURE__ */ new WeakMap();
10793
+ var typeQueryIn = /* @__PURE__ */ new WeakMap();
10794
+ function referencedTypeNames(node, out) {
10795
+ if (out === void 0 && node && typeof node === "object") {
10796
+ const known = referencedNamesOf.get(node);
10797
+ if (known) return known;
10798
+ const found = referencedTypeNames(node, []);
10799
+ referencedNamesOf.set(node, found);
10800
+ return found;
10801
+ }
10802
+ out = out ?? [];
10503
10803
  if (!node || typeof node !== "object") return out;
10504
10804
  if (Array.isArray(node)) {
10505
10805
  for (const item of node) referencedTypeNames(item, out);
@@ -10516,9 +10816,11 @@ function referencedTypeNames(node, out = []) {
10516
10816
  }
10517
10817
  function containsTypeQuery(node) {
10518
10818
  if (!node || typeof node !== "object") return false;
10519
- if (Array.isArray(node)) return node.some(containsTypeQuery);
10520
- if (node.type === "TypeofTypeNode") return true;
10521
- return Object.values(node).some(containsTypeQuery);
10819
+ const known = typeQueryIn.get(node);
10820
+ if (known !== void 0) return known;
10821
+ const found = Array.isArray(node) ? node.some(containsTypeQuery) : node.type === "TypeofTypeNode" || Object.values(node).some(containsTypeQuery);
10822
+ typeQueryIn.set(node, found);
10823
+ return found;
10522
10824
  }
10523
10825
  var STRING_INTRINSICS = /* @__PURE__ */ new Set(["Uppercase", "Lowercase", "Capitalize", "Uncapitalize"]);
10524
10826
  function briefType(t) {