@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.cjs CHANGED
@@ -2503,6 +2503,21 @@ var Parser = class {
2503
2503
  base = { type: "MemberExpression", object: base, property: prop, optional: true, ...spanFrom(base, prop) };
2504
2504
  continue;
2505
2505
  }
2506
+ if (punct === "." && this.punctuatorAt(2, "[")) {
2507
+ this.advance();
2508
+ this.advance();
2509
+ this.expectPunctuator("[");
2510
+ const index = this.inBrackets(() => this.parseExpression());
2511
+ this.expectPunctuator("]");
2512
+ base = {
2513
+ type: "IndexExpression",
2514
+ object: base,
2515
+ index,
2516
+ optional: true,
2517
+ ...spanFrom(base, this.previous())
2518
+ };
2519
+ continue;
2520
+ }
2506
2521
  if (punct === "." && this.punctuatorAt(2, "(")) {
2507
2522
  this.advance();
2508
2523
  this.advance();
@@ -3256,8 +3271,12 @@ var Parser = class {
3256
3271
  * `name?: T` for an optional property or parameter. */
3257
3272
  parseSuffixType() {
3258
3273
  if (this.checkIdentifierValue("readonly")) {
3274
+ const start = this.current();
3259
3275
  this.advance();
3260
- return this.parseSuffixType();
3276
+ const t2 = this.parseSuffixType();
3277
+ if (t2.type === "ArrayTypeNode" || t2.type === "TupleTypeNode") return { ...t2, isReadonly: true };
3278
+ this.problemAt(start, "'readonly' belongs on an array or a tuple: a property is marked where it is written");
3279
+ return t2;
3261
3280
  }
3262
3281
  let t = this.parsePrimaryType();
3263
3282
  while (true) {
@@ -3808,31 +3827,34 @@ var Analyzer = class {
3808
3827
  * can call it, but a call straight in the block before the declaration
3809
3828
  * finds nothing there yet. At a module's top level the whole function is
3810
3829
  * hoisted, and this does not apply. */
3811
- checkUseBeforeDefine(identifier, id) {
3830
+ checkUseBeforeDefine(identifier, id, at = this.functionDepth) {
3812
3831
  const binding = this.bindings.get(id);
3813
- if (binding.declaredBy !== "function" && binding.declaredBy !== "class" || this.typeQueryDepth > 0) return;
3832
+ if (this.typeQueryDepth > 0) return;
3814
3833
  const declaration = binding.declarationNode;
3815
- const depth = declaration && this.hoisted.get(declaration);
3816
- if (depth === void 0 || depth !== this.functionDepth) return;
3834
+ if (!declaration) return;
3835
+ const hoisted = binding.declaredBy === "function" || binding.declaredBy === "class";
3836
+ const depth = hoisted ? this.hoisted.get(declaration) : this.declaredDepth.get(id);
3837
+ const readable = hoisted || binding.kind === "local";
3838
+ if (!readable || depth === void 0 || depth !== at) return;
3817
3839
  const before = identifier.line.start < declaration.line.start || identifier.line.start === declaration.line.start && identifier.column.start < declaration.column.start;
3818
3840
  if (!before) return;
3819
3841
  this.diagnostics.push({
3820
3842
  node: identifier,
3821
- message: `'${binding.name}' is used before its definition: inside a function, a function declared further down is only there once its declaration has run`,
3843
+ 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`,
3822
3844
  kind: "use-before-define"
3823
3845
  });
3824
3846
  }
3825
3847
  noteDeferred(identifier, scope, id, assignment) {
3826
3848
  if (this.functionDepth === 0 && this.typeQueryDepth === 0) return;
3827
3849
  if (this.bindings.get(id).kind !== "global") return;
3828
- this.deferredGlobals.push({ node: identifier, scope, assignment });
3850
+ this.deferredGlobals.push({ node: identifier, scope, assignment, depth: this.functionDepth });
3829
3851
  }
3830
3852
  /** Point each deferred read of a global at the declaration of that name
3831
3853
  * that turned up later — in the module, or in any block around the code
3832
3854
  * that reads it. Such code runs after the declaration has: a closure
3833
3855
  * written inside a value reads the name the value is bound to. */
3834
3856
  resolveForwardReferences() {
3835
- for (const { node, scope, assignment } of this.deferredGlobals) {
3857
+ for (const { node, scope, assignment, depth } of this.deferredGlobals) {
3836
3858
  const localId = this.lookup(scope, node.name);
3837
3859
  const globalId = this.bindingOf.get(node);
3838
3860
  if (localId === void 0 || globalId === void 0 || localId === globalId) continue;
@@ -3847,7 +3869,10 @@ var Analyzer = class {
3847
3869
  this.bindingOf.set(node, localId);
3848
3870
  this.bindings.get(localId).references.push(node);
3849
3871
  if (assignment) this.checkConstAssign(localId, node);
3850
- else if (this.typeQueryDepth === 0) this.checkTypeOnly(localId, node);
3872
+ else if (this.typeQueryDepth === 0) {
3873
+ this.checkTypeOnly(localId, node);
3874
+ this.checkUseBeforeDefine(node, localId, depth);
3875
+ }
3851
3876
  }
3852
3877
  }
3853
3878
  /** Every read of a global nothing declares. A global assigned somewhere
@@ -3878,9 +3903,13 @@ var Analyzer = class {
3878
3903
  }
3879
3904
  const id = this.nextId++;
3880
3905
  this.bindings.set(id, { id, name, kind, declarationNode: node, references: [], isConst, declaredBy });
3906
+ this.declaredDepth.set(id, this.functionDepth);
3881
3907
  scope.declarations.set(name, id);
3882
3908
  return id;
3883
3909
  }
3910
+ /** Which function body each name was declared in. A read of a `const` from
3911
+ * the same body, but above it, is a read of nothing. */
3912
+ declaredDepth = /* @__PURE__ */ new Map();
3884
3913
  resolve(scope, name) {
3885
3914
  for (let s = scope; s; s = s.parent) {
3886
3915
  const id = s.declarations.get(name);
@@ -4533,9 +4562,9 @@ function substitute(t, subst) {
4533
4562
  case "typeParam":
4534
4563
  return subst.get(t.name) ?? t;
4535
4564
  case "array":
4536
- return arrayOf(substitute(t.element, subst));
4565
+ return { ...arrayOf(substitute(t.element, subst)), readonly: t.readonly };
4537
4566
  case "tuple":
4538
- return tuple(t.elements.map((e) => substitute(e, subst)), t.rest && substitute(t.rest, subst));
4567
+ return { ...tuple(t.elements.map((e) => substitute(e, subst)), t.rest && substitute(t.rest, subst)), readonly: t.readonly };
4539
4568
  case "object": {
4540
4569
  const entries = [];
4541
4570
  for (const [k, v] of t.properties) entries.push([k, { ...v, type: substitute(v.type, subst) }]);
@@ -4641,14 +4670,17 @@ function classArguments(t, name) {
4641
4670
  if (t.kind === "object") return t.class?.typeArguments?.get(name);
4642
4671
  return void 0;
4643
4672
  }
4644
- function unify(param, arg, vars, out) {
4673
+ function unify(param, arg, vars, out, how = "union") {
4645
4674
  if (param.kind === "typeParam" && param.constraint && !vars.has(param.name)) {
4646
- unify(param.constraint, arg, vars, out);
4675
+ unify(param.constraint, arg, vars, out, how);
4647
4676
  return;
4648
4677
  }
4649
4678
  if (param.kind === "typeParam" && vars.has(param.name)) {
4650
4679
  const prev = out.get(param.name);
4651
- out.set(param.name, prev ? union([prev, arg]) : arg);
4680
+ if (!prev) out.set(param.name, arg);
4681
+ else if (isAssignable(arg, prev)) {
4682
+ } else if (isAssignable(prev, arg)) out.set(param.name, arg);
4683
+ else if (how === "union") out.set(param.name, union([prev, arg]));
4652
4684
  return;
4653
4685
  }
4654
4686
  if (arg.kind === "any" || arg.kind === "never") return;
@@ -4700,6 +4732,20 @@ function unify(param, arg, vars, out) {
4700
4732
  return;
4701
4733
  }
4702
4734
  }
4735
+ function memberKey(t) {
4736
+ switch (t.kind) {
4737
+ case "literal":
4738
+ return `l${t.base}:${String(t.value)}`;
4739
+ case "primitive":
4740
+ return `p${t.name}`;
4741
+ case "any":
4742
+ case "unknown":
4743
+ case "never":
4744
+ return t.kind;
4745
+ default:
4746
+ return formatType(t);
4747
+ }
4748
+ }
4703
4749
  function union(types) {
4704
4750
  const flat = [];
4705
4751
  for (const t of types) {
@@ -4711,7 +4757,7 @@ function union(types) {
4711
4757
  const seen = /* @__PURE__ */ new Map();
4712
4758
  for (const t of flat) {
4713
4759
  if (t.kind === "never") continue;
4714
- const key = formatType(t);
4760
+ const key = memberKey(t);
4715
4761
  if (!seen.has(key)) seen.set(key, t);
4716
4762
  }
4717
4763
  let members = reduceSubtypes([...seen.values()]);
@@ -4740,7 +4786,7 @@ function intersection(types) {
4740
4786
  const seen = /* @__PURE__ */ new Map();
4741
4787
  for (const t of flat) {
4742
4788
  if (t.kind === "unknown") continue;
4743
- seen.set(formatType(t), t);
4789
+ seen.set(memberKey(t), t);
4744
4790
  }
4745
4791
  const members = [...seen.values()];
4746
4792
  if (members.length === 0) return unknownType;
@@ -4767,9 +4813,9 @@ function widen(t) {
4767
4813
  case "literal":
4768
4814
  return primitive(t.base);
4769
4815
  case "array":
4770
- return arrayOf(widen(t.element));
4816
+ return { ...arrayOf(widen(t.element)), readonly: t.readonly };
4771
4817
  case "tuple":
4772
- return tuple(t.elements.map(widen), t.rest && widen(t.rest));
4818
+ return { ...tuple(t.elements.map(widen), t.rest && widen(t.rest)), readonly: t.readonly };
4773
4819
  case "object": {
4774
4820
  if (t.frozen || t.class) return t;
4775
4821
  const entries = [];
@@ -4867,6 +4913,7 @@ function isAssignableInner(a, b) {
4867
4913
  return b.kind === "primitive" && b.name === "string";
4868
4914
  }
4869
4915
  if (a.kind === "primitive") return b.kind === "primitive" && b.name === a.name;
4916
+ if ((a.kind === "array" || a.kind === "tuple") && a.readonly && (b.kind === "array" || b.kind === "tuple") && !b.readonly) return false;
4870
4917
  if (a.kind === "array") {
4871
4918
  if (b.kind === "array") return isAssignable(a.element, b.element);
4872
4919
  if (b.kind === "tuple") return b.elements.length === 0 && !!b.rest && isAssignable(a.element, b.rest);
@@ -5212,10 +5259,10 @@ function formatTypeUncached(t) {
5212
5259
  case "literal":
5213
5260
  return t.base === "string" ? JSON.stringify(t.value) : String(t.value);
5214
5261
  case "array":
5215
- return `${formatAtom(t.element)}[]`;
5262
+ return `${t.readonly ? "readonly " : ""}${formatAtom(t.element)}[]`;
5216
5263
  case "tuple": {
5217
5264
  const inner = [...t.elements.map(formatType), ...t.rest ? [`...${formatAtom(t.rest)}[]`] : []];
5218
- return `[${inner.join(", ")}]`;
5265
+ return `${t.readonly ? "readonly " : ""}[${inner.join(", ")}]`;
5219
5266
  }
5220
5267
  case "object": {
5221
5268
  if (t.name) {
@@ -5738,7 +5785,7 @@ function expressionLabel(e, depth = 0) {
5738
5785
  case "IndexExpression": {
5739
5786
  const o = expressionLabel(e.object, depth + 1);
5740
5787
  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 : "...";
5741
- return o === void 0 ? void 0 : `${o}[${i}]`;
5788
+ return o === void 0 ? void 0 : `${o}${e.optional ? "?." : ""}[${i}]`;
5742
5789
  }
5743
5790
  case "ParenthesizedExpression": {
5744
5791
  const inner = expressionLabel(e.expression, depth + 1);
@@ -5789,7 +5836,8 @@ var TypeAnalyzer = class {
5789
5836
  /** Put on every ref to one of `aliasDefs`, so a module that imports the
5790
5837
  * type expands the ref here rather than by its name there. */
5791
5838
  origin = { expand: (t) => this.expand(t) };
5792
- /** See `resolveClass`. */
5839
+ /** See `resolveClass`. Shared with other analyses over the same libraries
5840
+ * when none of their names is redefined here; see `shareableClasses`. */
5793
5841
  classTypes = /* @__PURE__ */ new WeakMap();
5794
5842
  /** See `instanceType` — one instance type per `class ... end`. */
5795
5843
  instanceTypes = /* @__PURE__ */ new WeakMap();
@@ -5835,6 +5883,11 @@ var TypeAnalyzer = class {
5835
5883
  /** Recursion guard for `preVisitBody`. */
5836
5884
  preVisitDepth = 0;
5837
5885
  run() {
5886
+ const shared = shareableClasses(this.options.libs ?? [], this.program);
5887
+ if (shared) {
5888
+ this.classTypes = shared.classTypes;
5889
+ this.classMembers = shared.classMembers;
5890
+ }
5838
5891
  this.registerAliasDefs(preludeProgram().body, true);
5839
5892
  for (const lib of this.options.libs ?? []) this.registerAliasDefs(lib.body, true);
5840
5893
  this.registerAliasDefs(this.program.body);
@@ -7000,14 +7053,17 @@ var TypeAnalyzer = class {
7000
7053
  return literal(node.value);
7001
7054
  case "TypeLiteralNumber":
7002
7055
  return literal(node.value);
7003
- case "ArrayTypeNode":
7004
- return arrayOf(this.resolveType(node.element));
7056
+ case "ArrayTypeNode": {
7057
+ const made = arrayOf(this.resolveType(node.element));
7058
+ return node.isReadonly ? { ...made, readonly: true } : made;
7059
+ }
7005
7060
  case "TupleTypeNode": {
7006
7061
  const rest = node.rest && this.expand(this.resolveType(node.rest));
7007
- return tuple(
7062
+ const made = tuple(
7008
7063
  node.elements.map((e) => this.resolveType(e)),
7009
7064
  rest ? rest.kind === "array" ? rest.element : rest.kind === "any" ? anyType : unknownType : void 0
7010
7065
  );
7066
+ return node.isReadonly && made.kind === "tuple" ? { ...made, readonly: true } : made;
7011
7067
  }
7012
7068
  case "UnionTypeNode":
7013
7069
  return union(node.types.map((t) => this.resolveType(t)));
@@ -7809,8 +7865,10 @@ var TypeAnalyzer = class {
7809
7865
  // --------------------------------------------------------
7810
7866
  // Functions
7811
7867
  // --------------------------------------------------------
7812
- visitFunctionBody(func, outerEnv) {
7813
- this.withTypeParams(func.generics, () => this.visitFunctionBodyInner(func, outerEnv));
7868
+ /** `returns`, when given, collects the type of each `return` the walk
7869
+ * reads — see `inferFunctionBody`. */
7870
+ visitFunctionBody(func, outerEnv, returns) {
7871
+ this.withTypeParams(func.generics, () => this.visitFunctionBodyInner(func, outerEnv, returns));
7814
7872
  }
7815
7873
  /** A parameter's type: annotation, else a shape synthesized from a
7816
7874
  * destructuring pattern, else inferred from its default, else `any`. */
@@ -7827,8 +7885,19 @@ var TypeAnalyzer = class {
7827
7885
  if (p.rest) return arrayOf(unknownType);
7828
7886
  if (p.pattern) return this.patternToType(p.pattern, env);
7829
7887
  if (p.default) return widen(this.infer(p.default, env));
7830
- return this.contextualParams.get(p) ?? anyType;
7888
+ const contextual = this.contextualParams.get(p);
7889
+ if (contextual) return contextual;
7890
+ if (this.emitDiagnostics && !receiver && p.name && p.line && !this.untypedParamReported.has(p)) {
7891
+ this.untypedParamReported.add(p);
7892
+ this.diagnostics.push({
7893
+ node: p,
7894
+ message: `Parameter '${p.name}' has no type, so it is 'any': give it one, or a default to read it from`
7895
+ });
7896
+ }
7897
+ return anyType;
7831
7898
  }
7899
+ /** Parameters already spoken about: a body is visited more than once. */
7900
+ untypedParamReported = /* @__PURE__ */ new WeakSet();
7832
7901
  /** What a function expression's unannotated parameters are, from where
7833
7902
  * it is written — see `applyContext`. */
7834
7903
  contextualParams = /* @__PURE__ */ new WeakMap();
@@ -7848,6 +7917,53 @@ var TypeAnalyzer = class {
7848
7917
  * it, as in TypeScript: `signal:Connect(function(player) ... end)` knows
7849
7918
  * `player` from `Connect`'s callback type. Anything else is inferred as
7850
7919
  * usual. */
7920
+ /** The arguments of a call, in two passes where one is needed.
7921
+ *
7922
+ * A function written at the call site with a parameter nothing annotates
7923
+ * reads that parameter from the signature — and where the signature says
7924
+ * `(v: T) => U`, `T` is only known once the other arguments have been
7925
+ * read. So such an argument is read once quietly, to get a shape to pick
7926
+ * a signature with, and then again with `T` in hand: `map(xs, v => v + 1)`
7927
+ * has its `v` a number by then, and `U` follows from what the body
7928
+ * answers. Everything else is read once, as it always was. */
7929
+ inferArguments(written, fns, selfOf, extra, env) {
7930
+ const expected = this.expectedArguments(written, fns, selfOf);
7931
+ written.forEach((a, i) => this.applyContext(a, expected[i]));
7932
+ const waits = written.map((a, i) => this.waitsOnInference(a, expected[i]));
7933
+ if (!waits.some(Boolean)) return written.map((a) => this.infer(a, env));
7934
+ const argTypes = written.map((a, i) => waits[i] ? this.quietly(() => this.infer(a, env)) : this.infer(a, env));
7935
+ const picked = this.pickOverload(fns, argTypes, (f) => extra(f, argTypes), this.spreadOf(written, selfOf(fns[0])));
7936
+ const subst = picked?.typeParams?.length ? this.inferTypeArgs(picked, extra(picked, argTypes)) : void 0;
7937
+ written.forEach((a, i) => {
7938
+ if (!waits[i]) return;
7939
+ if (picked && subst) {
7940
+ const self = selfOf(picked);
7941
+ const declared = i + self < picked.params.length ? picked.params[i + self].type : picked.varargs;
7942
+ if (declared) this.applyContext(a, this.reduceType(substitute(declared, subst)));
7943
+ }
7944
+ argTypes[i] = this.infer(a, env);
7945
+ });
7946
+ return argTypes;
7947
+ }
7948
+ /** Is this argument a function whose own type waits on the call's? */
7949
+ waitsOnInference(arg, expected) {
7950
+ if (!expected || !containsTypeParam(expected)) return false;
7951
+ let e = arg;
7952
+ while (e.type === "ParenthesizedExpression") e = e.expression;
7953
+ if (e.type !== "FunctionExpression") return false;
7954
+ return e.func.params.some((p) => !p.typeAnnotation && !p.default && !p.pattern && p.name !== "self" && p.name !== "this");
7955
+ }
7956
+ /** Read something without saying anything about it: a first pass whose
7957
+ * only purpose is a shape to go on. */
7958
+ quietly(read) {
7959
+ const was = this.emitDiagnostics;
7960
+ this.emitDiagnostics = false;
7961
+ try {
7962
+ return read();
7963
+ } finally {
7964
+ this.emitDiagnostics = was;
7965
+ }
7966
+ }
7851
7967
  applyContext(expr, expected) {
7852
7968
  let e = expr;
7853
7969
  while (e.type === "ParenthesizedExpression") e = e.expression;
@@ -7996,7 +8112,7 @@ var TypeAnalyzer = class {
7996
8112
  this.contextualReturns.pop();
7997
8113
  }
7998
8114
  }
7999
- visitFunctionBodyInner(func, outerEnv) {
8115
+ visitFunctionBodyInner(func, outerEnv, returns) {
8000
8116
  const env = forkEnv(outerEnv);
8001
8117
  for (const p of func.params) {
8002
8118
  if (p.pattern) {
@@ -8013,7 +8129,7 @@ var TypeAnalyzer = class {
8013
8129
  if (p.typeAnnotation) this.annotated.add(id);
8014
8130
  }
8015
8131
  }
8016
- this.withVarargs(func, () => this.collectReturns(void 0, () => {
8132
+ this.withVarargs(func, () => this.collectReturns(returns, () => {
8017
8133
  this.visitBlock(func.body, env);
8018
8134
  this.checkReturnsAtAll(func, this.declaredReturns[this.declaredReturns.length - 1]);
8019
8135
  }));
@@ -8067,7 +8183,7 @@ var TypeAnalyzer = class {
8067
8183
  if (arg === void 0) return;
8068
8184
  const param = p.type.kind === "typeParam" && p.type.constraint ? { ...p.type, constraint: this.reduceType(p.type.constraint) } : p.type;
8069
8185
  const preserve = keepsLiterals(param) || param.kind !== "typeParam" && containsTypeParam(param);
8070
- unify(p.type, preserve ? arg : widen(arg), vars, subst);
8186
+ unify(p.type, preserve ? arg : widen(arg), vars, subst, "first");
8071
8187
  });
8072
8188
  if (f.varargs && f.restIsWhole) {
8073
8189
  const rest = argTypes.slice(f.params.length).map((a) => a === void 0 ? unknownType : widen(a));
@@ -8123,6 +8239,11 @@ var TypeAnalyzer = class {
8123
8239
  * this returns `undefined` and the ordinary pick stands. */
8124
8240
  distributedReturn(fns, argTypes, picked, argsFor) {
8125
8241
  if (fns.length < 2) return void 0;
8242
+ if (argTypes.some((t) => this.expand(t).kind === "any")) {
8243
+ const taken = fns.filter((f) => this.overloadAccepts(f, argsFor(f, argTypes)));
8244
+ if (taken.length < 2) return void 0;
8245
+ return union(taken.map((f) => this.callReturn(f, argsFor(f, argTypes))));
8246
+ }
8126
8247
  const position = argTypes.findIndex((t) => this.expand(t).kind === "union");
8127
8248
  if (position < 0) return void 0;
8128
8249
  const members = this.expand(argTypes[position]).types;
@@ -8171,6 +8292,8 @@ var TypeAnalyzer = class {
8171
8292
  * lone argument cannot be checked against without false errors. */
8172
8293
  boundParams(f) {
8173
8294
  if (!f.typeParams?.length) return f.params.map((p) => p.type);
8295
+ const known = this.boundParamsCache.get(f);
8296
+ if (known) return known;
8174
8297
  const bounds = new Map(f.typeParams.map((name) => [name, anyType]));
8175
8298
  const seen = /* @__PURE__ */ new WeakSet();
8176
8299
  const walk = (value) => {
@@ -8188,8 +8311,11 @@ var TypeAnalyzer = class {
8188
8311
  for (const child of Object.values(value)) walk(child);
8189
8312
  };
8190
8313
  for (const p of f.params) if (containsTypeParam(p.type)) walk(p.type);
8191
- return f.params.map((p) => this.reduceType(substitute(p.type, bounds)));
8314
+ const bound = f.params.map((p) => this.reduceType(substitute(p.type, bounds)));
8315
+ this.boundParamsCache.set(f, bound);
8316
+ return bound;
8192
8317
  }
8318
+ boundParamsCache = /* @__PURE__ */ new WeakMap();
8193
8319
  /** Record what each written argument is expected to be — see
8194
8320
  * `TypeAnalysis.expectedTypeOf`. */
8195
8321
  recordExpected(written, fns, selfOf, argsOf = () => []) {
@@ -8408,7 +8534,12 @@ var TypeAnalyzer = class {
8408
8534
  asserts: node.asserts
8409
8535
  };
8410
8536
  }
8411
- inferFunctionBody(func, env) {
8537
+ /** `visited`, when given, is what the real walk of the body just
8538
+ * collected from its `return`s: a function expression is walked before
8539
+ * its type is asked for, so there is nothing left to guess, and walking
8540
+ * the body a second time only to read them made every callback nested in
8541
+ * another cost twice its parent's. */
8542
+ inferFunctionBody(func, env, visited) {
8412
8543
  const names = func.generics.map((g) => g.name);
8413
8544
  return this.withTypeParams(func.generics, () => {
8414
8545
  const params = func.params.flatMap((p) => {
@@ -8437,6 +8568,8 @@ var TypeAnalyzer = class {
8437
8568
  returns = this.resolveType(func.returnType);
8438
8569
  } else if (func.predicate) {
8439
8570
  returns = booleanType;
8571
+ } else if (visited) {
8572
+ returns = visited.length ? union([...visited]) : this.withVarargs(func, () => this.silently(() => this.inferReturnType(func.body, bodyEnv)));
8440
8573
  } else {
8441
8574
  const collected = [];
8442
8575
  returns = this.withVarargs(func, () => this.silently(() => {
@@ -8653,7 +8786,7 @@ var TypeAnalyzer = class {
8653
8786
  /** What one `for (const item in source)` hands over each time, and how it
8654
8787
  * walks the source. `pairs`/`ipairs` are read from the table they are
8655
8788
  * given, so a record's keys stay the literals they are. */
8656
- iterationOf(source, sourceType) {
8789
+ iterationOf(source, sourceType, report = true) {
8657
8790
  if (source.type === "CallExpression" && source.callee.type === "Identifier" && source.arguments[0]) {
8658
8791
  const name = source.callee.name;
8659
8792
  const src = this.expand(this.typeOf.get(source.arguments[0]) ?? unknownType);
@@ -8691,7 +8824,14 @@ var TypeAnalyzer = class {
8691
8824
  if (t.kind === "object") {
8692
8825
  return [t.indexer?.value ?? union([...t.properties.values()].map((p) => p.type)), values];
8693
8826
  }
8694
- if (t.kind !== "never" && this.emitDiagnostics) {
8827
+ if (t.kind === "union") {
8828
+ const each = t.types.map((member) => this.iterationOf(source, member, false));
8829
+ const [first] = each;
8830
+ if (first && each.every(([, form]) => form.walks === first[1].walks && form.viaIter === first[1].viaIter)) {
8831
+ return [union(each.map(([item]) => item)), first[1]];
8832
+ }
8833
+ }
8834
+ if (t.kind !== "never" && report && this.emitDiagnostics) {
8695
8835
  this.diagnostics.push({
8696
8836
  node: source,
8697
8837
  message: `Cannot loop over '${formatType(sourceType)}': a loop walks an array, a table, an iterator function or an iteration such as 'pairs(t)'`
@@ -9060,7 +9200,14 @@ var TypeAnalyzer = class {
9060
9200
  const names = keys.map(nameOf);
9061
9201
  const others = keys.filter((_, i) => names[i] === void 0);
9062
9202
  let message;
9063
- if (others.length && this.cannotIndex(object, others)) {
9203
+ const listOnly = (t) => {
9204
+ const x = this.expand(t);
9205
+ if (x.kind === "union") return x.types.every(listOnly);
9206
+ return x.kind === "array" || x.kind === "tuple";
9207
+ };
9208
+ if (names.some((name) => name !== void 0) && listOnly(withoutNil(this.expand(object)))) {
9209
+ message = `Type '${briefType(index)}' cannot be used to index type '${shown}'`;
9210
+ } else if (others.length && this.cannotIndex(object, others)) {
9064
9211
  message = `Type '${briefType(index)}' cannot be used to index type '${shown}'`;
9065
9212
  } else if (!others.length && names.every((name) => this.memberMissing(object, name))) {
9066
9213
  message = keys.length === 1 ? `Property '${names[0]}' does not exist on type '${shown}'` : `None of ${briefType(index)} is a property of type '${shown}'`;
@@ -9295,10 +9442,12 @@ var TypeAnalyzer = class {
9295
9442
  return this.inferArray(expr, env, false);
9296
9443
  case "TableExpression":
9297
9444
  return this.inferObject(expr, env, false);
9298
- case "FunctionExpression":
9445
+ case "FunctionExpression": {
9299
9446
  this.checkParamOrder(expr.func.params, expr);
9300
- this.visitFunctionBody(expr.func, env);
9301
- return this.inferFunctionBody(expr.func, env);
9447
+ const returned = [];
9448
+ this.visitFunctionBody(expr.func, env, returned);
9449
+ return this.inferFunctionBody(expr.func, env, returned);
9450
+ }
9302
9451
  case "ParenthesizedExpression":
9303
9452
  return this.infer(expr.expression, env);
9304
9453
  case "TypeAssertionExpression": {
@@ -9332,10 +9481,18 @@ var TypeAnalyzer = class {
9332
9481
  switch (expr.operator) {
9333
9482
  case "not":
9334
9483
  return booleanType;
9335
- case "-":
9336
- return this.operatorResult(expr, "-", arg, void 0) ?? this.arithmeticOn([arg]);
9337
- case "#":
9338
- return this.operatorResult(expr, "#", arg, void 0) ?? numberType;
9484
+ case "-": {
9485
+ const answered = this.operatorResult(expr, "-", arg, void 0);
9486
+ if (answered) return answered;
9487
+ this.checkOperands(expr, "-", arg);
9488
+ return this.arithmeticOn([arg]);
9489
+ }
9490
+ case "#": {
9491
+ const answered = this.operatorResult(expr, "#", arg, void 0);
9492
+ if (answered) return answered;
9493
+ this.checkOperands(expr, "#", arg);
9494
+ return numberType;
9495
+ }
9339
9496
  }
9340
9497
  return arg;
9341
9498
  }
@@ -9364,16 +9521,21 @@ var TypeAnalyzer = class {
9364
9521
  if (unwrapParens(expr.left).type === "StringLiteral") this.expectedTypeOf.set(unwrapParens(expr.left), r);
9365
9522
  }
9366
9523
  switch (op) {
9367
- case "..":
9368
- return this.operatorResult(expr, op, l, r) ?? stringType;
9524
+ case "..": {
9525
+ const answered = this.operatorResult(expr, op, l, r);
9526
+ if (answered) return answered;
9527
+ this.checkOperands(expr, op, l, r);
9528
+ return stringType;
9529
+ }
9369
9530
  case "<":
9370
9531
  case ">":
9371
9532
  case "<=":
9372
9533
  case ">=":
9373
- this.checkComparison(expr, op, l, r);
9534
+ if (!this.checkComparison(expr, op, l, r)) this.checkOperands(expr, op, l, r);
9374
9535
  return booleanType;
9375
9536
  case "==":
9376
9537
  case "~=":
9538
+ this.checkOverlap(expr, op, l, r);
9377
9539
  return booleanType;
9378
9540
  case "+":
9379
9541
  case "-":
@@ -9381,8 +9543,12 @@ var TypeAnalyzer = class {
9381
9543
  case "/":
9382
9544
  case "//":
9383
9545
  case "%":
9384
- case "^":
9385
- return this.operatorResult(expr, op, l, r) ?? this.arithmeticOn([l, r]);
9546
+ case "^": {
9547
+ const answered = this.operatorResult(expr, op, l, r);
9548
+ if (answered) return answered;
9549
+ this.checkOperands(expr, op, l, r);
9550
+ return this.arithmeticOn([l, r]);
9551
+ }
9386
9552
  }
9387
9553
  return union([l, r]);
9388
9554
  }
@@ -9485,9 +9651,7 @@ var TypeAnalyzer = class {
9485
9651
  const united = this.unionSignatures(callee);
9486
9652
  const fns = united ?? this.overloadsOf(callee);
9487
9653
  const explicit = this.explicitTypeArguments(expr, fns);
9488
- const expected = this.expectedArguments(expr.arguments, fns, () => 0);
9489
- expr.arguments.forEach((a, i) => this.applyContext(a, expected[i]));
9490
- const argTypes = expr.arguments.map((a) => this.infer(a, env));
9654
+ const argTypes = this.inferArguments(expr.arguments, fns, () => 0, (_, args) => args, env);
9491
9655
  if (fns.length) {
9492
9656
  this.recordExpected(expr.arguments, fns, () => 0, () => argTypes);
9493
9657
  const spread = this.spreadOf(expr.arguments);
@@ -9552,9 +9716,13 @@ var TypeAnalyzer = class {
9552
9716
  const united = this.unionSignatures(method);
9553
9717
  const fns = united ?? this.overloadsOf(method);
9554
9718
  const explicit = this.explicitTypeArguments(expr, fns);
9555
- const expected = this.expectedArguments(expr.arguments, fns, (f) => this.takesSelf(f) ? 1 : 0);
9556
- expr.arguments.forEach((a, i) => this.applyContext(a, expected[i]));
9557
- const argTypes = expr.arguments.map((a) => this.infer(a, env));
9719
+ const argTypes = this.inferArguments(
9720
+ expr.arguments,
9721
+ fns,
9722
+ (f) => this.takesSelf(f) ? 1 : 0,
9723
+ (f, args) => this.takesSelf(f) ? [objType, ...args] : args,
9724
+ env
9725
+ );
9558
9726
  if (fns.length) {
9559
9727
  const withSelf = (f) => this.takesSelf(f) ? [objType, ...argTypes] : argTypes;
9560
9728
  const selfOf = (f) => this.takesSelf(f) ? 1 : 0;
@@ -9630,6 +9798,14 @@ var TypeAnalyzer = class {
9630
9798
  name = target.property.name;
9631
9799
  object = target.object;
9632
9800
  } else if (target.type === "IndexExpression") {
9801
+ const list = this.expand(this.infer(target.object, env));
9802
+ if ((list.kind === "array" || list.kind === "tuple") && list.readonly) {
9803
+ this.diagnostics.push({
9804
+ node: target,
9805
+ message: `Cannot assign to an element of '${formatType(list)}': it is read-only`
9806
+ });
9807
+ return;
9808
+ }
9633
9809
  const index = this.expand(this.infer(target.index, env));
9634
9810
  if (index.kind !== "literal" || typeof index.value !== "string") return;
9635
9811
  name = index.value;
@@ -9802,9 +9978,27 @@ var TypeAnalyzer = class {
9802
9978
  if (s.indexer) indexer = mergeIndexer(indexer, s.indexer);
9803
9979
  }
9804
9980
  }
9981
+ this.checkDuplicateKey(expr, field, entries);
9805
9982
  }
9806
9983
  return objectType(entries, indexer, asConst || void 0);
9807
9984
  }
9985
+ /** `{ a: 1, a: 2 }` — the second wins and the first was written for
9986
+ * nothing, which is a typo far more often than a decision. A spread is
9987
+ * not one: overriding what it brought is exactly what it is for. */
9988
+ checkDuplicateKey(expr, field, entries) {
9989
+ if (!this.emitDiagnostics || this.duplicateKeyReported.has(field)) return;
9990
+ const written = field.type === "TableFieldNamed" ? field.key.type === "Identifier" ? field.key.name : field.key.value : field.type === "TableFieldShorthand" ? field.name.name : void 0;
9991
+ if (written === void 0) return;
9992
+ const spreadBefore = expr.fields.slice(0, expr.fields.indexOf(field)).some((f) => f.type === "TableFieldSpread");
9993
+ if (spreadBefore) return;
9994
+ if (entries.filter(([k]) => k === written).length < 2) return;
9995
+ this.duplicateKeyReported.add(field);
9996
+ this.diagnostics.push({
9997
+ node: field.type === "TableFieldNamed" ? field.key : field.name,
9998
+ message: `'${written}' is given twice in this table; only the last one is kept`
9999
+ });
10000
+ }
10001
+ duplicateKeyReported = /* @__PURE__ */ new WeakSet();
9808
10002
  /** A value inferred `as const`, widened back wherever `context` does not
9809
10003
  * ask for a literal: `satisfies`' result type. A property keeps `"circle"`
9810
10004
  * when the contract's property admits string literals, and becomes
@@ -10297,27 +10491,84 @@ var TypeAnalyzer = class {
10297
10491
  arithmeticOn(operands) {
10298
10492
  return operands.some((t) => this.expand(t).kind === "any") ? anyType : numberType;
10299
10493
  }
10494
+ /** `a == b` where nothing `a` can hold is anything `b` can: the answer is
10495
+ * always the same, so the comparison was not the one meant. Only types
10496
+ * that say what they hold take part — `any`, `unknown` and a type
10497
+ * parameter say nothing, and a table compares by identity, so two of them
10498
+ * may well be the same table. */
10499
+ checkOverlap(node, op, l, r) {
10500
+ if (!this.emitDiagnostics) return;
10501
+ const left = this.expand(l);
10502
+ const right = this.expand(r);
10503
+ const judged = (t) => {
10504
+ const parts = t.kind === "union" ? t.types.map((m) => this.expand(m)) : [t];
10505
+ return parts.every((p) => p.kind === "literal" || p.kind === "primitive");
10506
+ };
10507
+ const isNil = (t) => t.kind === "primitive" && t.name === "nil";
10508
+ if (isNil(left) || isNil(right)) return;
10509
+ if (!judged(left) || !judged(right)) return;
10510
+ if (isAssignable(left, right) || isAssignable(right, left)) return;
10511
+ this.diagnostics.push({
10512
+ node,
10513
+ message: `This comparison is always ${op === "==" ? "false" : "true"}: '${formatType(l)}' and '${formatType(r)}' have no value in common`
10514
+ });
10515
+ }
10516
+ /** What an operator takes, where no metamethod answered for it. Luau
10517
+ * raises on the rest — `n * 2` with `n` nil is an error at the line it
10518
+ * runs, and a nil `n` is exactly what `number | nil` says may happen — so
10519
+ * it is said here instead. `any` passes: nothing is known about it. */
10520
+ checkOperands(node, op, left, right) {
10521
+ if (!this.emitDiagnostics) return;
10522
+ const fits = (t) => {
10523
+ const x = this.expand(t);
10524
+ if (x.kind === "any" || x.kind === "never") return true;
10525
+ switch (op) {
10526
+ // `..` joins strings, and Luau writes a number into one.
10527
+ case "..":
10528
+ return isAssignable(x, stringType) || isAssignable(x, numberType);
10529
+ // `#` counts a string's bytes or a table's entries.
10530
+ case "#":
10531
+ return x.kind === "array" || x.kind === "tuple" || x.kind === "object" || isAssignable(x, stringType);
10532
+ default:
10533
+ return isAssignable(x, numberType);
10534
+ }
10535
+ };
10536
+ if (op === "<" || op === ">" || op === "<=" || op === ">=") {
10537
+ const both = (t) => this.expand(t);
10538
+ const [l, r] = [both(left), both(right)];
10539
+ const loose = (t) => t.kind === "any" || t.kind === "never";
10540
+ const alike = (as) => isAssignable(l, as) && isAssignable(r, as);
10541
+ if (loose(l) || loose(r) || alike(numberType) || alike(stringType)) return;
10542
+ } else if (fits(left) && (right === void 0 || fits(right))) {
10543
+ return;
10544
+ }
10545
+ this.diagnostics.push({
10546
+ node,
10547
+ 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)}'`
10548
+ });
10549
+ }
10300
10550
  /** `a < b` between class instances: Luau asks `__lt` (`__le` for `<=`),
10301
10551
  * with the operands swapped for `>` and `>=`, and an instance without
10302
10552
  * one cannot be compared at all. */
10303
10553
  checkComparison(node, op, l, r) {
10304
- if (!this.emitDiagnostics) return;
10554
+ if (!this.emitDiagnostics) return false;
10305
10555
  const left = this.expand(l);
10306
10556
  const right = this.expand(r);
10307
10557
  const instance = (t) => t.kind === "object" && t.class !== void 0;
10308
- if (!instance(left) && !instance(right)) return;
10558
+ if (!instance(left) && !instance(right)) return false;
10309
10559
  const name = op === "<" || op === ">" ? "__lt" : "__le";
10310
10560
  const operands = op === ">" || op === ">=" ? [right, left] : [left, right];
10311
10561
  for (const receiver of operands) {
10312
10562
  const method = receiver.kind === "object" ? receiver.properties.get(name) : void 0;
10313
10563
  if (!method) continue;
10314
- if (this.pickOverload(this.overloadsOf(method.type), operands)) return;
10564
+ if (this.pickOverload(this.overloadsOf(method.type), operands)) return true;
10315
10565
  break;
10316
10566
  }
10317
10567
  this.diagnostics.push({
10318
10568
  node,
10319
10569
  message: `Operator '${op}' cannot be applied to types '${formatType(l)}' and '${formatType(r)}'`
10320
10570
  });
10571
+ return true;
10321
10572
  }
10322
10573
  /** What an operator on a value with metamethods gives: `a + b` calls
10323
10574
  * `__add` on `a`, or failing that on `b` with the operands swapped — the
@@ -10604,7 +10855,56 @@ var TypeAnalyzer = class {
10604
10855
  return this.bindingByDecl.get(node) ?? this.bindingByPos.get(posKey(name, node.line.start, node.column.start));
10605
10856
  }
10606
10857
  };
10607
- function referencedTypeNames(node, out = []) {
10858
+ var libraryNames = /* @__PURE__ */ new WeakMap();
10859
+ function namesDeclaredIn(program) {
10860
+ const known = libraryNames.get(program);
10861
+ if (known) return known;
10862
+ const names = /* @__PURE__ */ new Set();
10863
+ for (const stmt of program.body.statements) {
10864
+ const declaration = stmt.type === "ExportStatement" || stmt.type === "ExportDefaultStatement" ? stmt.declaration : stmt;
10865
+ if (declaration.type === "TypeAliasStatement") names.add(declaration.name.name);
10866
+ else if (declaration.type === "ExportTypeAliasStatement") names.add(declaration.alias.name.name);
10867
+ else if (declaration.type === "DeclareClassStatement") names.add(declaration.name.name);
10868
+ else if (declaration.type === "ClassDeclaration" && declaration.name) names.add(declaration.name.name);
10869
+ else if (stmt.type === "ImportStatement") {
10870
+ for (const s of stmt.specifiers) names.add(s.local.name);
10871
+ if (stmt.defaultImport) names.add(stmt.defaultImport.name);
10872
+ if (stmt.namespaceImport) names.add(stmt.namespaceImport.name);
10873
+ }
10874
+ }
10875
+ libraryNames.set(program, names);
10876
+ return names;
10877
+ }
10878
+ var sharedClassesByLib = /* @__PURE__ */ new WeakMap();
10879
+ function shareableClasses(libs, program) {
10880
+ if (!libs.length) return void 0;
10881
+ const mine = namesDeclaredIn(program);
10882
+ if (mine.size) {
10883
+ for (const lib of libs) {
10884
+ for (const name of namesDeclaredIn(lib)) if (mine.has(name)) return void 0;
10885
+ }
10886
+ }
10887
+ const existing = sharedClassesByLib.get(libs[0]);
10888
+ if (existing) {
10889
+ const same = existing.libs.length === libs.length && existing.libs.every((lib, i) => lib === libs[i]);
10890
+ if (same) return existing;
10891
+ return void 0;
10892
+ }
10893
+ const store = { libs: [...libs], classTypes: /* @__PURE__ */ new WeakMap(), classMembers: /* @__PURE__ */ new WeakMap() };
10894
+ sharedClassesByLib.set(libs[0], store);
10895
+ return store;
10896
+ }
10897
+ var referencedNamesOf = /* @__PURE__ */ new WeakMap();
10898
+ var typeQueryIn = /* @__PURE__ */ new WeakMap();
10899
+ function referencedTypeNames(node, out) {
10900
+ if (out === void 0 && node && typeof node === "object") {
10901
+ const known = referencedNamesOf.get(node);
10902
+ if (known) return known;
10903
+ const found = referencedTypeNames(node, []);
10904
+ referencedNamesOf.set(node, found);
10905
+ return found;
10906
+ }
10907
+ out = out ?? [];
10608
10908
  if (!node || typeof node !== "object") return out;
10609
10909
  if (Array.isArray(node)) {
10610
10910
  for (const item of node) referencedTypeNames(item, out);
@@ -10621,9 +10921,11 @@ function referencedTypeNames(node, out = []) {
10621
10921
  }
10622
10922
  function containsTypeQuery(node) {
10623
10923
  if (!node || typeof node !== "object") return false;
10624
- if (Array.isArray(node)) return node.some(containsTypeQuery);
10625
- if (node.type === "TypeofTypeNode") return true;
10626
- return Object.values(node).some(containsTypeQuery);
10924
+ const known = typeQueryIn.get(node);
10925
+ if (known !== void 0) return known;
10926
+ const found = Array.isArray(node) ? node.some(containsTypeQuery) : node.type === "TypeofTypeNode" || Object.values(node).some(containsTypeQuery);
10927
+ typeQueryIn.set(node, found);
10928
+ return found;
10627
10929
  }
10628
10930
  var STRING_INTRINSICS = /* @__PURE__ */ new Set(["Uppercase", "Lowercase", "Capitalize", "Uncapitalize"]);
10629
10931
  function briefType(t) {