@tilua/parser 2.0.0 → 2.1.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/README.md CHANGED
@@ -341,9 +341,11 @@ name: T | nil -- must be written, but may be nil
341
341
  Omitting an argument requires `?` (or a default), as in TypeScript — a
342
342
  parameter typed `T | nil` still has to be passed something.
343
343
 
344
- **Optional chaining** — `a?.b` and `a?:m(x)` are nil when `a` is, and then
345
- nothing further along the chain runs, arguments included: `folder?:FindFirstChild("A")?.Name`
346
- is a `string | nil`. The `?` must touch the `.` or `:`; `c ? a : b` stays a
344
+ **Optional chaining** — `a?.b`, `a?.[k]`, `a?.(x)` and `a?:m(x)` are nil when
345
+ `a` is, and then nothing further along the chain runs, arguments and the key
346
+ included: `folder?:FindFirstChild("A")?.Name` is a `string | nil`, and
347
+ `maps?.[key]?.label` reads the key only when `maps` is there. The `?` must
348
+ touch the `.` or `:`; `c ? a : b` stays a
347
349
  ternary. Parentheses end a chain. A chain cannot be assigned to (`a?.b = 1` is
348
350
  an error). A chain that got through narrows what it tested: inside
349
351
  `if (part?.Parent)`, and `if (part?.Name == "Door")`, `part` is not nil.
@@ -743,6 +745,17 @@ non-nil part.
743
745
 
744
746
  Only `nil` and `false` are falsy — `0` and `""` are truthy, unlike JavaScript.
745
747
 
748
+ **What Luau raises on is said first.** An operator no metamethod answers takes
749
+ numbers — `..` a string or a number, `#` a string or a table — so `n * 2` with
750
+ `n` a `number | nil` is an error where it is written rather than where it runs.
751
+ A comparison two types can never settle (`s == "c"` with `s` a `"a" | "b"`) is
752
+ one too; a test against `nil` never is, since a map's value and an array's
753
+ element are read as what they hold. A name holds nothing until its line has
754
+ run, a list is indexed by position (`xs["a"]` is not a read), `readonly T[]`
755
+ rules out writing to the list as well as passing it where a `number[]` is
756
+ wanted, a key written twice in one table is reported, and a parameter with no
757
+ type says so — it is `any`, and `any` turns off every check made of it.
758
+
746
759
  **Types** — unions, intersections, tuples `[A, B]` (with a rest, `[A,
747
760
  ...B[]]`), `keyof`, `T[K]`, conditional types with
748
761
  `infer`, mapped types with `as` remapping, template literal types
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) }]);
@@ -4700,6 +4729,20 @@ function unify(param, arg, vars, out) {
4700
4729
  return;
4701
4730
  }
4702
4731
  }
4732
+ function memberKey(t) {
4733
+ switch (t.kind) {
4734
+ case "literal":
4735
+ return `l${t.base}:${String(t.value)}`;
4736
+ case "primitive":
4737
+ return `p${t.name}`;
4738
+ case "any":
4739
+ case "unknown":
4740
+ case "never":
4741
+ return t.kind;
4742
+ default:
4743
+ return formatType(t);
4744
+ }
4745
+ }
4703
4746
  function union(types) {
4704
4747
  const flat = [];
4705
4748
  for (const t of types) {
@@ -4711,7 +4754,7 @@ function union(types) {
4711
4754
  const seen = /* @__PURE__ */ new Map();
4712
4755
  for (const t of flat) {
4713
4756
  if (t.kind === "never") continue;
4714
- const key = formatType(t);
4757
+ const key = memberKey(t);
4715
4758
  if (!seen.has(key)) seen.set(key, t);
4716
4759
  }
4717
4760
  let members = reduceSubtypes([...seen.values()]);
@@ -4740,7 +4783,7 @@ function intersection(types) {
4740
4783
  const seen = /* @__PURE__ */ new Map();
4741
4784
  for (const t of flat) {
4742
4785
  if (t.kind === "unknown") continue;
4743
- seen.set(formatType(t), t);
4786
+ seen.set(memberKey(t), t);
4744
4787
  }
4745
4788
  const members = [...seen.values()];
4746
4789
  if (members.length === 0) return unknownType;
@@ -4767,9 +4810,9 @@ function widen(t) {
4767
4810
  case "literal":
4768
4811
  return primitive(t.base);
4769
4812
  case "array":
4770
- return arrayOf(widen(t.element));
4813
+ return { ...arrayOf(widen(t.element)), readonly: t.readonly };
4771
4814
  case "tuple":
4772
- return tuple(t.elements.map(widen), t.rest && widen(t.rest));
4815
+ return { ...tuple(t.elements.map(widen), t.rest && widen(t.rest)), readonly: t.readonly };
4773
4816
  case "object": {
4774
4817
  if (t.frozen || t.class) return t;
4775
4818
  const entries = [];
@@ -4867,6 +4910,7 @@ function isAssignableInner(a, b) {
4867
4910
  return b.kind === "primitive" && b.name === "string";
4868
4911
  }
4869
4912
  if (a.kind === "primitive") return b.kind === "primitive" && b.name === a.name;
4913
+ if ((a.kind === "array" || a.kind === "tuple") && a.readonly && (b.kind === "array" || b.kind === "tuple") && !b.readonly) return false;
4870
4914
  if (a.kind === "array") {
4871
4915
  if (b.kind === "array") return isAssignable(a.element, b.element);
4872
4916
  if (b.kind === "tuple") return b.elements.length === 0 && !!b.rest && isAssignable(a.element, b.rest);
@@ -5212,10 +5256,10 @@ function formatTypeUncached(t) {
5212
5256
  case "literal":
5213
5257
  return t.base === "string" ? JSON.stringify(t.value) : String(t.value);
5214
5258
  case "array":
5215
- return `${formatAtom(t.element)}[]`;
5259
+ return `${t.readonly ? "readonly " : ""}${formatAtom(t.element)}[]`;
5216
5260
  case "tuple": {
5217
5261
  const inner = [...t.elements.map(formatType), ...t.rest ? [`...${formatAtom(t.rest)}[]`] : []];
5218
- return `[${inner.join(", ")}]`;
5262
+ return `${t.readonly ? "readonly " : ""}[${inner.join(", ")}]`;
5219
5263
  }
5220
5264
  case "object": {
5221
5265
  if (t.name) {
@@ -5738,7 +5782,7 @@ function expressionLabel(e, depth = 0) {
5738
5782
  case "IndexExpression": {
5739
5783
  const o = expressionLabel(e.object, depth + 1);
5740
5784
  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}]`;
5785
+ return o === void 0 ? void 0 : `${o}${e.optional ? "?." : ""}[${i}]`;
5742
5786
  }
5743
5787
  case "ParenthesizedExpression": {
5744
5788
  const inner = expressionLabel(e.expression, depth + 1);
@@ -5789,7 +5833,8 @@ var TypeAnalyzer = class {
5789
5833
  /** Put on every ref to one of `aliasDefs`, so a module that imports the
5790
5834
  * type expands the ref here rather than by its name there. */
5791
5835
  origin = { expand: (t) => this.expand(t) };
5792
- /** See `resolveClass`. */
5836
+ /** See `resolveClass`. Shared with other analyses over the same libraries
5837
+ * when none of their names is redefined here; see `shareableClasses`. */
5793
5838
  classTypes = /* @__PURE__ */ new WeakMap();
5794
5839
  /** See `instanceType` — one instance type per `class ... end`. */
5795
5840
  instanceTypes = /* @__PURE__ */ new WeakMap();
@@ -5835,6 +5880,11 @@ var TypeAnalyzer = class {
5835
5880
  /** Recursion guard for `preVisitBody`. */
5836
5881
  preVisitDepth = 0;
5837
5882
  run() {
5883
+ const shared = shareableClasses(this.options.libs ?? [], this.program);
5884
+ if (shared) {
5885
+ this.classTypes = shared.classTypes;
5886
+ this.classMembers = shared.classMembers;
5887
+ }
5838
5888
  this.registerAliasDefs(preludeProgram().body, true);
5839
5889
  for (const lib of this.options.libs ?? []) this.registerAliasDefs(lib.body, true);
5840
5890
  this.registerAliasDefs(this.program.body);
@@ -7000,14 +7050,17 @@ var TypeAnalyzer = class {
7000
7050
  return literal(node.value);
7001
7051
  case "TypeLiteralNumber":
7002
7052
  return literal(node.value);
7003
- case "ArrayTypeNode":
7004
- return arrayOf(this.resolveType(node.element));
7053
+ case "ArrayTypeNode": {
7054
+ const made = arrayOf(this.resolveType(node.element));
7055
+ return node.isReadonly ? { ...made, readonly: true } : made;
7056
+ }
7005
7057
  case "TupleTypeNode": {
7006
7058
  const rest = node.rest && this.expand(this.resolveType(node.rest));
7007
- return tuple(
7059
+ const made = tuple(
7008
7060
  node.elements.map((e) => this.resolveType(e)),
7009
7061
  rest ? rest.kind === "array" ? rest.element : rest.kind === "any" ? anyType : unknownType : void 0
7010
7062
  );
7063
+ return node.isReadonly && made.kind === "tuple" ? { ...made, readonly: true } : made;
7011
7064
  }
7012
7065
  case "UnionTypeNode":
7013
7066
  return union(node.types.map((t) => this.resolveType(t)));
@@ -7827,8 +7880,19 @@ var TypeAnalyzer = class {
7827
7880
  if (p.rest) return arrayOf(unknownType);
7828
7881
  if (p.pattern) return this.patternToType(p.pattern, env);
7829
7882
  if (p.default) return widen(this.infer(p.default, env));
7830
- return this.contextualParams.get(p) ?? anyType;
7883
+ const contextual = this.contextualParams.get(p);
7884
+ if (contextual) return contextual;
7885
+ if (this.emitDiagnostics && !receiver && p.name && p.line && !this.untypedParamReported.has(p)) {
7886
+ this.untypedParamReported.add(p);
7887
+ this.diagnostics.push({
7888
+ node: p,
7889
+ message: `Parameter '${p.name}' has no type, so it is 'any': give it one, or a default to read it from`
7890
+ });
7891
+ }
7892
+ return anyType;
7831
7893
  }
7894
+ /** Parameters already spoken about: a body is visited more than once. */
7895
+ untypedParamReported = /* @__PURE__ */ new WeakSet();
7832
7896
  /** What a function expression's unannotated parameters are, from where
7833
7897
  * it is written — see `applyContext`. */
7834
7898
  contextualParams = /* @__PURE__ */ new WeakMap();
@@ -8123,6 +8187,11 @@ var TypeAnalyzer = class {
8123
8187
  * this returns `undefined` and the ordinary pick stands. */
8124
8188
  distributedReturn(fns, argTypes, picked, argsFor) {
8125
8189
  if (fns.length < 2) return void 0;
8190
+ if (argTypes.some((t) => this.expand(t).kind === "any")) {
8191
+ const taken = fns.filter((f) => this.overloadAccepts(f, argsFor(f, argTypes)));
8192
+ if (taken.length < 2) return void 0;
8193
+ return union(taken.map((f) => this.callReturn(f, argsFor(f, argTypes))));
8194
+ }
8126
8195
  const position = argTypes.findIndex((t) => this.expand(t).kind === "union");
8127
8196
  if (position < 0) return void 0;
8128
8197
  const members = this.expand(argTypes[position]).types;
@@ -8171,6 +8240,8 @@ var TypeAnalyzer = class {
8171
8240
  * lone argument cannot be checked against without false errors. */
8172
8241
  boundParams(f) {
8173
8242
  if (!f.typeParams?.length) return f.params.map((p) => p.type);
8243
+ const known = this.boundParamsCache.get(f);
8244
+ if (known) return known;
8174
8245
  const bounds = new Map(f.typeParams.map((name) => [name, anyType]));
8175
8246
  const seen = /* @__PURE__ */ new WeakSet();
8176
8247
  const walk = (value) => {
@@ -8188,8 +8259,11 @@ var TypeAnalyzer = class {
8188
8259
  for (const child of Object.values(value)) walk(child);
8189
8260
  };
8190
8261
  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)));
8262
+ const bound = f.params.map((p) => this.reduceType(substitute(p.type, bounds)));
8263
+ this.boundParamsCache.set(f, bound);
8264
+ return bound;
8192
8265
  }
8266
+ boundParamsCache = /* @__PURE__ */ new WeakMap();
8193
8267
  /** Record what each written argument is expected to be — see
8194
8268
  * `TypeAnalysis.expectedTypeOf`. */
8195
8269
  recordExpected(written, fns, selfOf, argsOf = () => []) {
@@ -8653,7 +8727,7 @@ var TypeAnalyzer = class {
8653
8727
  /** What one `for (const item in source)` hands over each time, and how it
8654
8728
  * walks the source. `pairs`/`ipairs` are read from the table they are
8655
8729
  * given, so a record's keys stay the literals they are. */
8656
- iterationOf(source, sourceType) {
8730
+ iterationOf(source, sourceType, report = true) {
8657
8731
  if (source.type === "CallExpression" && source.callee.type === "Identifier" && source.arguments[0]) {
8658
8732
  const name = source.callee.name;
8659
8733
  const src = this.expand(this.typeOf.get(source.arguments[0]) ?? unknownType);
@@ -8691,7 +8765,14 @@ var TypeAnalyzer = class {
8691
8765
  if (t.kind === "object") {
8692
8766
  return [t.indexer?.value ?? union([...t.properties.values()].map((p) => p.type)), values];
8693
8767
  }
8694
- if (t.kind !== "never" && this.emitDiagnostics) {
8768
+ if (t.kind === "union") {
8769
+ const each = t.types.map((member) => this.iterationOf(source, member, false));
8770
+ const [first] = each;
8771
+ if (first && each.every(([, form]) => form.walks === first[1].walks && form.viaIter === first[1].viaIter)) {
8772
+ return [union(each.map(([item]) => item)), first[1]];
8773
+ }
8774
+ }
8775
+ if (t.kind !== "never" && report && this.emitDiagnostics) {
8695
8776
  this.diagnostics.push({
8696
8777
  node: source,
8697
8778
  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 +9141,14 @@ var TypeAnalyzer = class {
9060
9141
  const names = keys.map(nameOf);
9061
9142
  const others = keys.filter((_, i) => names[i] === void 0);
9062
9143
  let message;
9063
- if (others.length && this.cannotIndex(object, others)) {
9144
+ const listOnly = (t) => {
9145
+ const x = this.expand(t);
9146
+ if (x.kind === "union") return x.types.every(listOnly);
9147
+ return x.kind === "array" || x.kind === "tuple";
9148
+ };
9149
+ if (names.some((name) => name !== void 0) && listOnly(withoutNil(this.expand(object)))) {
9150
+ message = `Type '${briefType(index)}' cannot be used to index type '${shown}'`;
9151
+ } else if (others.length && this.cannotIndex(object, others)) {
9064
9152
  message = `Type '${briefType(index)}' cannot be used to index type '${shown}'`;
9065
9153
  } else if (!others.length && names.every((name) => this.memberMissing(object, name))) {
9066
9154
  message = keys.length === 1 ? `Property '${names[0]}' does not exist on type '${shown}'` : `None of ${briefType(index)} is a property of type '${shown}'`;
@@ -9332,10 +9420,18 @@ var TypeAnalyzer = class {
9332
9420
  switch (expr.operator) {
9333
9421
  case "not":
9334
9422
  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;
9423
+ case "-": {
9424
+ const answered = this.operatorResult(expr, "-", arg, void 0);
9425
+ if (answered) return answered;
9426
+ this.checkOperands(expr, "-", arg);
9427
+ return this.arithmeticOn([arg]);
9428
+ }
9429
+ case "#": {
9430
+ const answered = this.operatorResult(expr, "#", arg, void 0);
9431
+ if (answered) return answered;
9432
+ this.checkOperands(expr, "#", arg);
9433
+ return numberType;
9434
+ }
9339
9435
  }
9340
9436
  return arg;
9341
9437
  }
@@ -9364,16 +9460,21 @@ var TypeAnalyzer = class {
9364
9460
  if (unwrapParens(expr.left).type === "StringLiteral") this.expectedTypeOf.set(unwrapParens(expr.left), r);
9365
9461
  }
9366
9462
  switch (op) {
9367
- case "..":
9368
- return this.operatorResult(expr, op, l, r) ?? stringType;
9463
+ case "..": {
9464
+ const answered = this.operatorResult(expr, op, l, r);
9465
+ if (answered) return answered;
9466
+ this.checkOperands(expr, op, l, r);
9467
+ return stringType;
9468
+ }
9369
9469
  case "<":
9370
9470
  case ">":
9371
9471
  case "<=":
9372
9472
  case ">=":
9373
- this.checkComparison(expr, op, l, r);
9473
+ if (!this.checkComparison(expr, op, l, r)) this.checkOperands(expr, op, l, r);
9374
9474
  return booleanType;
9375
9475
  case "==":
9376
9476
  case "~=":
9477
+ this.checkOverlap(expr, op, l, r);
9377
9478
  return booleanType;
9378
9479
  case "+":
9379
9480
  case "-":
@@ -9381,8 +9482,12 @@ var TypeAnalyzer = class {
9381
9482
  case "/":
9382
9483
  case "//":
9383
9484
  case "%":
9384
- case "^":
9385
- return this.operatorResult(expr, op, l, r) ?? this.arithmeticOn([l, r]);
9485
+ case "^": {
9486
+ const answered = this.operatorResult(expr, op, l, r);
9487
+ if (answered) return answered;
9488
+ this.checkOperands(expr, op, l, r);
9489
+ return this.arithmeticOn([l, r]);
9490
+ }
9386
9491
  }
9387
9492
  return union([l, r]);
9388
9493
  }
@@ -9630,6 +9735,14 @@ var TypeAnalyzer = class {
9630
9735
  name = target.property.name;
9631
9736
  object = target.object;
9632
9737
  } else if (target.type === "IndexExpression") {
9738
+ const list = this.expand(this.infer(target.object, env));
9739
+ if ((list.kind === "array" || list.kind === "tuple") && list.readonly) {
9740
+ this.diagnostics.push({
9741
+ node: target,
9742
+ message: `Cannot assign to an element of '${formatType(list)}': it is read-only`
9743
+ });
9744
+ return;
9745
+ }
9633
9746
  const index = this.expand(this.infer(target.index, env));
9634
9747
  if (index.kind !== "literal" || typeof index.value !== "string") return;
9635
9748
  name = index.value;
@@ -9802,9 +9915,27 @@ var TypeAnalyzer = class {
9802
9915
  if (s.indexer) indexer = mergeIndexer(indexer, s.indexer);
9803
9916
  }
9804
9917
  }
9918
+ this.checkDuplicateKey(expr, field, entries);
9805
9919
  }
9806
9920
  return objectType(entries, indexer, asConst || void 0);
9807
9921
  }
9922
+ /** `{ a: 1, a: 2 }` — the second wins and the first was written for
9923
+ * nothing, which is a typo far more often than a decision. A spread is
9924
+ * not one: overriding what it brought is exactly what it is for. */
9925
+ checkDuplicateKey(expr, field, entries) {
9926
+ if (!this.emitDiagnostics || this.duplicateKeyReported.has(field)) return;
9927
+ const written = field.type === "TableFieldNamed" ? field.key.type === "Identifier" ? field.key.name : field.key.value : field.type === "TableFieldShorthand" ? field.name.name : void 0;
9928
+ if (written === void 0) return;
9929
+ const spreadBefore = expr.fields.slice(0, expr.fields.indexOf(field)).some((f) => f.type === "TableFieldSpread");
9930
+ if (spreadBefore) return;
9931
+ if (entries.filter(([k]) => k === written).length < 2) return;
9932
+ this.duplicateKeyReported.add(field);
9933
+ this.diagnostics.push({
9934
+ node: field.type === "TableFieldNamed" ? field.key : field.name,
9935
+ message: `'${written}' is given twice in this table; only the last one is kept`
9936
+ });
9937
+ }
9938
+ duplicateKeyReported = /* @__PURE__ */ new WeakSet();
9808
9939
  /** A value inferred `as const`, widened back wherever `context` does not
9809
9940
  * ask for a literal: `satisfies`' result type. A property keeps `"circle"`
9810
9941
  * when the contract's property admits string literals, and becomes
@@ -10297,27 +10428,84 @@ var TypeAnalyzer = class {
10297
10428
  arithmeticOn(operands) {
10298
10429
  return operands.some((t) => this.expand(t).kind === "any") ? anyType : numberType;
10299
10430
  }
10431
+ /** `a == b` where nothing `a` can hold is anything `b` can: the answer is
10432
+ * always the same, so the comparison was not the one meant. Only types
10433
+ * that say what they hold take part — `any`, `unknown` and a type
10434
+ * parameter say nothing, and a table compares by identity, so two of them
10435
+ * may well be the same table. */
10436
+ checkOverlap(node, op, l, r) {
10437
+ if (!this.emitDiagnostics) return;
10438
+ const left = this.expand(l);
10439
+ const right = this.expand(r);
10440
+ const judged = (t) => {
10441
+ const parts = t.kind === "union" ? t.types.map((m) => this.expand(m)) : [t];
10442
+ return parts.every((p) => p.kind === "literal" || p.kind === "primitive");
10443
+ };
10444
+ const isNil = (t) => t.kind === "primitive" && t.name === "nil";
10445
+ if (isNil(left) || isNil(right)) return;
10446
+ if (!judged(left) || !judged(right)) return;
10447
+ if (isAssignable(left, right) || isAssignable(right, left)) return;
10448
+ this.diagnostics.push({
10449
+ node,
10450
+ message: `This comparison is always ${op === "==" ? "false" : "true"}: '${formatType(l)}' and '${formatType(r)}' have no value in common`
10451
+ });
10452
+ }
10453
+ /** What an operator takes, where no metamethod answered for it. Luau
10454
+ * raises on the rest — `n * 2` with `n` nil is an error at the line it
10455
+ * runs, and a nil `n` is exactly what `number | nil` says may happen — so
10456
+ * it is said here instead. `any` passes: nothing is known about it. */
10457
+ checkOperands(node, op, left, right) {
10458
+ if (!this.emitDiagnostics) return;
10459
+ const fits = (t) => {
10460
+ const x = this.expand(t);
10461
+ if (x.kind === "any" || x.kind === "never") return true;
10462
+ switch (op) {
10463
+ // `..` joins strings, and Luau writes a number into one.
10464
+ case "..":
10465
+ return isAssignable(x, stringType) || isAssignable(x, numberType);
10466
+ // `#` counts a string's bytes or a table's entries.
10467
+ case "#":
10468
+ return x.kind === "array" || x.kind === "tuple" || x.kind === "object" || isAssignable(x, stringType);
10469
+ default:
10470
+ return isAssignable(x, numberType);
10471
+ }
10472
+ };
10473
+ if (op === "<" || op === ">" || op === "<=" || op === ">=") {
10474
+ const both = (t) => this.expand(t);
10475
+ const [l, r] = [both(left), both(right)];
10476
+ const loose = (t) => t.kind === "any" || t.kind === "never";
10477
+ const alike = (as) => isAssignable(l, as) && isAssignable(r, as);
10478
+ if (loose(l) || loose(r) || alike(numberType) || alike(stringType)) return;
10479
+ } else if (fits(left) && (right === void 0 || fits(right))) {
10480
+ return;
10481
+ }
10482
+ this.diagnostics.push({
10483
+ node,
10484
+ 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)}'`
10485
+ });
10486
+ }
10300
10487
  /** `a < b` between class instances: Luau asks `__lt` (`__le` for `<=`),
10301
10488
  * with the operands swapped for `>` and `>=`, and an instance without
10302
10489
  * one cannot be compared at all. */
10303
10490
  checkComparison(node, op, l, r) {
10304
- if (!this.emitDiagnostics) return;
10491
+ if (!this.emitDiagnostics) return false;
10305
10492
  const left = this.expand(l);
10306
10493
  const right = this.expand(r);
10307
10494
  const instance = (t) => t.kind === "object" && t.class !== void 0;
10308
- if (!instance(left) && !instance(right)) return;
10495
+ if (!instance(left) && !instance(right)) return false;
10309
10496
  const name = op === "<" || op === ">" ? "__lt" : "__le";
10310
10497
  const operands = op === ">" || op === ">=" ? [right, left] : [left, right];
10311
10498
  for (const receiver of operands) {
10312
10499
  const method = receiver.kind === "object" ? receiver.properties.get(name) : void 0;
10313
10500
  if (!method) continue;
10314
- if (this.pickOverload(this.overloadsOf(method.type), operands)) return;
10501
+ if (this.pickOverload(this.overloadsOf(method.type), operands)) return true;
10315
10502
  break;
10316
10503
  }
10317
10504
  this.diagnostics.push({
10318
10505
  node,
10319
10506
  message: `Operator '${op}' cannot be applied to types '${formatType(l)}' and '${formatType(r)}'`
10320
10507
  });
10508
+ return true;
10321
10509
  }
10322
10510
  /** What an operator on a value with metamethods gives: `a + b` calls
10323
10511
  * `__add` on `a`, or failing that on `b` with the operands swapped — the
@@ -10604,7 +10792,56 @@ var TypeAnalyzer = class {
10604
10792
  return this.bindingByDecl.get(node) ?? this.bindingByPos.get(posKey(name, node.line.start, node.column.start));
10605
10793
  }
10606
10794
  };
10607
- function referencedTypeNames(node, out = []) {
10795
+ var libraryNames = /* @__PURE__ */ new WeakMap();
10796
+ function namesDeclaredIn(program) {
10797
+ const known = libraryNames.get(program);
10798
+ if (known) return known;
10799
+ const names = /* @__PURE__ */ new Set();
10800
+ for (const stmt of program.body.statements) {
10801
+ const declaration = stmt.type === "ExportStatement" || stmt.type === "ExportDefaultStatement" ? stmt.declaration : stmt;
10802
+ if (declaration.type === "TypeAliasStatement") names.add(declaration.name.name);
10803
+ else if (declaration.type === "ExportTypeAliasStatement") names.add(declaration.alias.name.name);
10804
+ else if (declaration.type === "DeclareClassStatement") names.add(declaration.name.name);
10805
+ else if (declaration.type === "ClassDeclaration" && declaration.name) names.add(declaration.name.name);
10806
+ else if (stmt.type === "ImportStatement") {
10807
+ for (const s of stmt.specifiers) names.add(s.local.name);
10808
+ if (stmt.defaultImport) names.add(stmt.defaultImport.name);
10809
+ if (stmt.namespaceImport) names.add(stmt.namespaceImport.name);
10810
+ }
10811
+ }
10812
+ libraryNames.set(program, names);
10813
+ return names;
10814
+ }
10815
+ var sharedClassesByLib = /* @__PURE__ */ new WeakMap();
10816
+ function shareableClasses(libs, program) {
10817
+ if (!libs.length) return void 0;
10818
+ const mine = namesDeclaredIn(program);
10819
+ if (mine.size) {
10820
+ for (const lib of libs) {
10821
+ for (const name of namesDeclaredIn(lib)) if (mine.has(name)) return void 0;
10822
+ }
10823
+ }
10824
+ const existing = sharedClassesByLib.get(libs[0]);
10825
+ if (existing) {
10826
+ const same = existing.libs.length === libs.length && existing.libs.every((lib, i) => lib === libs[i]);
10827
+ if (same) return existing;
10828
+ return void 0;
10829
+ }
10830
+ const store = { libs: [...libs], classTypes: /* @__PURE__ */ new WeakMap(), classMembers: /* @__PURE__ */ new WeakMap() };
10831
+ sharedClassesByLib.set(libs[0], store);
10832
+ return store;
10833
+ }
10834
+ var referencedNamesOf = /* @__PURE__ */ new WeakMap();
10835
+ var typeQueryIn = /* @__PURE__ */ new WeakMap();
10836
+ function referencedTypeNames(node, out) {
10837
+ if (out === void 0 && node && typeof node === "object") {
10838
+ const known = referencedNamesOf.get(node);
10839
+ if (known) return known;
10840
+ const found = referencedTypeNames(node, []);
10841
+ referencedNamesOf.set(node, found);
10842
+ return found;
10843
+ }
10844
+ out = out ?? [];
10608
10845
  if (!node || typeof node !== "object") return out;
10609
10846
  if (Array.isArray(node)) {
10610
10847
  for (const item of node) referencedTypeNames(item, out);
@@ -10621,9 +10858,11 @@ function referencedTypeNames(node, out = []) {
10621
10858
  }
10622
10859
  function containsTypeQuery(node) {
10623
10860
  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);
10861
+ const known = typeQueryIn.get(node);
10862
+ if (known !== void 0) return known;
10863
+ const found = Array.isArray(node) ? node.some(containsTypeQuery) : node.type === "TypeofTypeNode" || Object.values(node).some(containsTypeQuery);
10864
+ typeQueryIn.set(node, found);
10865
+ return found;
10627
10866
  }
10628
10867
  var STRING_INTRINSICS = /* @__PURE__ */ new Set(["Uppercase", "Lowercase", "Capitalize", "Uncapitalize"]);
10629
10868
  function briefType(t) {
package/dist/index.d.cts CHANGED
@@ -704,6 +704,9 @@ interface IndexExpression extends BaseNode {
704
704
  type: "IndexExpression";
705
705
  object: Expression;
706
706
  index: Expression;
707
+ /** `object?.[key]` — when `object` is nil, the whole chain this link
708
+ * belongs to is nil and the key is not evaluated. */
709
+ optional?: boolean;
707
710
  }
708
711
  interface CallExpression extends BaseNode {
709
712
  type: "CallExpression";
@@ -902,6 +905,8 @@ interface TableTypeNode extends BaseNode {
902
905
  interface ArrayTypeNode extends BaseNode {
903
906
  type: "ArrayTypeNode";
904
907
  element: TypeNode;
908
+ /** `readonly T[]` — the list may be read, not written to. */
909
+ isReadonly?: boolean;
905
910
  }
906
911
  /** `[number, string]` — a tuple type; `[string, ...number[]]` ends in a rest
907
912
  * of as many more as there are. */
@@ -910,6 +915,8 @@ interface TupleTypeNode extends BaseNode {
910
915
  elements: TypeNode[];
911
916
  /** `...number[]` at the end: the array type the rest of it is. */
912
917
  rest?: TypeNode;
918
+ /** `readonly [A, B]` — see `ArrayTypeNode.isReadonly`. */
919
+ isReadonly?: boolean;
913
920
  }
914
921
  interface FunctionTypeParameter extends BaseNode {
915
922
  type: "FunctionTypeParameter";
@@ -1119,6 +1126,7 @@ interface ArrayType {
1119
1126
  kind: "array";
1120
1127
  element: Type;
1121
1128
  alias?: string;
1129
+ readonly?: boolean;
1122
1130
  }
1123
1131
  /** `[A, B, C]` — fixed length; `[A, ...B[]]` — at least that, then any
1124
1132
  * number of `B`. `rest` is the element type of that tail. A tuple is an
@@ -1128,6 +1136,7 @@ interface TupleType {
1128
1136
  elements: Type[];
1129
1137
  rest?: Type;
1130
1138
  alias?: string;
1139
+ readonly?: boolean;
1131
1140
  }
1132
1141
  interface ObjectProperty {
1133
1142
  type: Type;
package/dist/index.d.ts CHANGED
@@ -704,6 +704,9 @@ interface IndexExpression extends BaseNode {
704
704
  type: "IndexExpression";
705
705
  object: Expression;
706
706
  index: Expression;
707
+ /** `object?.[key]` — when `object` is nil, the whole chain this link
708
+ * belongs to is nil and the key is not evaluated. */
709
+ optional?: boolean;
707
710
  }
708
711
  interface CallExpression extends BaseNode {
709
712
  type: "CallExpression";
@@ -902,6 +905,8 @@ interface TableTypeNode extends BaseNode {
902
905
  interface ArrayTypeNode extends BaseNode {
903
906
  type: "ArrayTypeNode";
904
907
  element: TypeNode;
908
+ /** `readonly T[]` — the list may be read, not written to. */
909
+ isReadonly?: boolean;
905
910
  }
906
911
  /** `[number, string]` — a tuple type; `[string, ...number[]]` ends in a rest
907
912
  * of as many more as there are. */
@@ -910,6 +915,8 @@ interface TupleTypeNode extends BaseNode {
910
915
  elements: TypeNode[];
911
916
  /** `...number[]` at the end: the array type the rest of it is. */
912
917
  rest?: TypeNode;
918
+ /** `readonly [A, B]` — see `ArrayTypeNode.isReadonly`. */
919
+ isReadonly?: boolean;
913
920
  }
914
921
  interface FunctionTypeParameter extends BaseNode {
915
922
  type: "FunctionTypeParameter";
@@ -1119,6 +1126,7 @@ interface ArrayType {
1119
1126
  kind: "array";
1120
1127
  element: Type;
1121
1128
  alias?: string;
1129
+ readonly?: boolean;
1122
1130
  }
1123
1131
  /** `[A, B, C]` — fixed length; `[A, ...B[]]` — at least that, then any
1124
1132
  * number of `B`. `rest` is the element type of that tail. A tuple is an
@@ -1128,6 +1136,7 @@ interface TupleType {
1128
1136
  elements: Type[];
1129
1137
  rest?: Type;
1130
1138
  alias?: string;
1139
+ readonly?: boolean;
1131
1140
  }
1132
1141
  interface ObjectProperty {
1133
1142
  type: Type;
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) }]);
@@ -4595,6 +4624,20 @@ function unify(param, arg, vars, out) {
4595
4624
  return;
4596
4625
  }
4597
4626
  }
4627
+ function memberKey(t) {
4628
+ switch (t.kind) {
4629
+ case "literal":
4630
+ return `l${t.base}:${String(t.value)}`;
4631
+ case "primitive":
4632
+ return `p${t.name}`;
4633
+ case "any":
4634
+ case "unknown":
4635
+ case "never":
4636
+ return t.kind;
4637
+ default:
4638
+ return formatType(t);
4639
+ }
4640
+ }
4598
4641
  function union(types) {
4599
4642
  const flat = [];
4600
4643
  for (const t of types) {
@@ -4606,7 +4649,7 @@ function union(types) {
4606
4649
  const seen = /* @__PURE__ */ new Map();
4607
4650
  for (const t of flat) {
4608
4651
  if (t.kind === "never") continue;
4609
- const key = formatType(t);
4652
+ const key = memberKey(t);
4610
4653
  if (!seen.has(key)) seen.set(key, t);
4611
4654
  }
4612
4655
  let members = reduceSubtypes([...seen.values()]);
@@ -4635,7 +4678,7 @@ function intersection(types) {
4635
4678
  const seen = /* @__PURE__ */ new Map();
4636
4679
  for (const t of flat) {
4637
4680
  if (t.kind === "unknown") continue;
4638
- seen.set(formatType(t), t);
4681
+ seen.set(memberKey(t), t);
4639
4682
  }
4640
4683
  const members = [...seen.values()];
4641
4684
  if (members.length === 0) return unknownType;
@@ -4662,9 +4705,9 @@ function widen(t) {
4662
4705
  case "literal":
4663
4706
  return primitive(t.base);
4664
4707
  case "array":
4665
- return arrayOf(widen(t.element));
4708
+ return { ...arrayOf(widen(t.element)), readonly: t.readonly };
4666
4709
  case "tuple":
4667
- return tuple(t.elements.map(widen), t.rest && widen(t.rest));
4710
+ return { ...tuple(t.elements.map(widen), t.rest && widen(t.rest)), readonly: t.readonly };
4668
4711
  case "object": {
4669
4712
  if (t.frozen || t.class) return t;
4670
4713
  const entries = [];
@@ -4762,6 +4805,7 @@ function isAssignableInner(a, b) {
4762
4805
  return b.kind === "primitive" && b.name === "string";
4763
4806
  }
4764
4807
  if (a.kind === "primitive") return b.kind === "primitive" && b.name === a.name;
4808
+ if ((a.kind === "array" || a.kind === "tuple") && a.readonly && (b.kind === "array" || b.kind === "tuple") && !b.readonly) return false;
4765
4809
  if (a.kind === "array") {
4766
4810
  if (b.kind === "array") return isAssignable(a.element, b.element);
4767
4811
  if (b.kind === "tuple") return b.elements.length === 0 && !!b.rest && isAssignable(a.element, b.rest);
@@ -5107,10 +5151,10 @@ function formatTypeUncached(t) {
5107
5151
  case "literal":
5108
5152
  return t.base === "string" ? JSON.stringify(t.value) : String(t.value);
5109
5153
  case "array":
5110
- return `${formatAtom(t.element)}[]`;
5154
+ return `${t.readonly ? "readonly " : ""}${formatAtom(t.element)}[]`;
5111
5155
  case "tuple": {
5112
5156
  const inner = [...t.elements.map(formatType), ...t.rest ? [`...${formatAtom(t.rest)}[]`] : []];
5113
- return `[${inner.join(", ")}]`;
5157
+ return `${t.readonly ? "readonly " : ""}[${inner.join(", ")}]`;
5114
5158
  }
5115
5159
  case "object": {
5116
5160
  if (t.name) {
@@ -5633,7 +5677,7 @@ function expressionLabel(e, depth = 0) {
5633
5677
  case "IndexExpression": {
5634
5678
  const o = expressionLabel(e.object, depth + 1);
5635
5679
  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}]`;
5680
+ return o === void 0 ? void 0 : `${o}${e.optional ? "?." : ""}[${i}]`;
5637
5681
  }
5638
5682
  case "ParenthesizedExpression": {
5639
5683
  const inner = expressionLabel(e.expression, depth + 1);
@@ -5684,7 +5728,8 @@ var TypeAnalyzer = class {
5684
5728
  /** Put on every ref to one of `aliasDefs`, so a module that imports the
5685
5729
  * type expands the ref here rather than by its name there. */
5686
5730
  origin = { expand: (t) => this.expand(t) };
5687
- /** See `resolveClass`. */
5731
+ /** See `resolveClass`. Shared with other analyses over the same libraries
5732
+ * when none of their names is redefined here; see `shareableClasses`. */
5688
5733
  classTypes = /* @__PURE__ */ new WeakMap();
5689
5734
  /** See `instanceType` — one instance type per `class ... end`. */
5690
5735
  instanceTypes = /* @__PURE__ */ new WeakMap();
@@ -5730,6 +5775,11 @@ var TypeAnalyzer = class {
5730
5775
  /** Recursion guard for `preVisitBody`. */
5731
5776
  preVisitDepth = 0;
5732
5777
  run() {
5778
+ const shared = shareableClasses(this.options.libs ?? [], this.program);
5779
+ if (shared) {
5780
+ this.classTypes = shared.classTypes;
5781
+ this.classMembers = shared.classMembers;
5782
+ }
5733
5783
  this.registerAliasDefs(preludeProgram().body, true);
5734
5784
  for (const lib of this.options.libs ?? []) this.registerAliasDefs(lib.body, true);
5735
5785
  this.registerAliasDefs(this.program.body);
@@ -6895,14 +6945,17 @@ var TypeAnalyzer = class {
6895
6945
  return literal(node.value);
6896
6946
  case "TypeLiteralNumber":
6897
6947
  return literal(node.value);
6898
- case "ArrayTypeNode":
6899
- return arrayOf(this.resolveType(node.element));
6948
+ case "ArrayTypeNode": {
6949
+ const made = arrayOf(this.resolveType(node.element));
6950
+ return node.isReadonly ? { ...made, readonly: true } : made;
6951
+ }
6900
6952
  case "TupleTypeNode": {
6901
6953
  const rest = node.rest && this.expand(this.resolveType(node.rest));
6902
- return tuple(
6954
+ const made = tuple(
6903
6955
  node.elements.map((e) => this.resolveType(e)),
6904
6956
  rest ? rest.kind === "array" ? rest.element : rest.kind === "any" ? anyType : unknownType : void 0
6905
6957
  );
6958
+ return node.isReadonly && made.kind === "tuple" ? { ...made, readonly: true } : made;
6906
6959
  }
6907
6960
  case "UnionTypeNode":
6908
6961
  return union(node.types.map((t) => this.resolveType(t)));
@@ -7722,8 +7775,19 @@ var TypeAnalyzer = class {
7722
7775
  if (p.rest) return arrayOf(unknownType);
7723
7776
  if (p.pattern) return this.patternToType(p.pattern, env);
7724
7777
  if (p.default) return widen(this.infer(p.default, env));
7725
- return this.contextualParams.get(p) ?? anyType;
7778
+ const contextual = this.contextualParams.get(p);
7779
+ if (contextual) return contextual;
7780
+ if (this.emitDiagnostics && !receiver && p.name && p.line && !this.untypedParamReported.has(p)) {
7781
+ this.untypedParamReported.add(p);
7782
+ this.diagnostics.push({
7783
+ node: p,
7784
+ message: `Parameter '${p.name}' has no type, so it is 'any': give it one, or a default to read it from`
7785
+ });
7786
+ }
7787
+ return anyType;
7726
7788
  }
7789
+ /** Parameters already spoken about: a body is visited more than once. */
7790
+ untypedParamReported = /* @__PURE__ */ new WeakSet();
7727
7791
  /** What a function expression's unannotated parameters are, from where
7728
7792
  * it is written — see `applyContext`. */
7729
7793
  contextualParams = /* @__PURE__ */ new WeakMap();
@@ -8018,6 +8082,11 @@ var TypeAnalyzer = class {
8018
8082
  * this returns `undefined` and the ordinary pick stands. */
8019
8083
  distributedReturn(fns, argTypes, picked, argsFor) {
8020
8084
  if (fns.length < 2) return void 0;
8085
+ if (argTypes.some((t) => this.expand(t).kind === "any")) {
8086
+ const taken = fns.filter((f) => this.overloadAccepts(f, argsFor(f, argTypes)));
8087
+ if (taken.length < 2) return void 0;
8088
+ return union(taken.map((f) => this.callReturn(f, argsFor(f, argTypes))));
8089
+ }
8021
8090
  const position = argTypes.findIndex((t) => this.expand(t).kind === "union");
8022
8091
  if (position < 0) return void 0;
8023
8092
  const members = this.expand(argTypes[position]).types;
@@ -8066,6 +8135,8 @@ var TypeAnalyzer = class {
8066
8135
  * lone argument cannot be checked against without false errors. */
8067
8136
  boundParams(f) {
8068
8137
  if (!f.typeParams?.length) return f.params.map((p) => p.type);
8138
+ const known = this.boundParamsCache.get(f);
8139
+ if (known) return known;
8069
8140
  const bounds = new Map(f.typeParams.map((name) => [name, anyType]));
8070
8141
  const seen = /* @__PURE__ */ new WeakSet();
8071
8142
  const walk = (value) => {
@@ -8083,8 +8154,11 @@ var TypeAnalyzer = class {
8083
8154
  for (const child of Object.values(value)) walk(child);
8084
8155
  };
8085
8156
  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)));
8157
+ const bound = f.params.map((p) => this.reduceType(substitute(p.type, bounds)));
8158
+ this.boundParamsCache.set(f, bound);
8159
+ return bound;
8087
8160
  }
8161
+ boundParamsCache = /* @__PURE__ */ new WeakMap();
8088
8162
  /** Record what each written argument is expected to be — see
8089
8163
  * `TypeAnalysis.expectedTypeOf`. */
8090
8164
  recordExpected(written, fns, selfOf, argsOf = () => []) {
@@ -8548,7 +8622,7 @@ var TypeAnalyzer = class {
8548
8622
  /** What one `for (const item in source)` hands over each time, and how it
8549
8623
  * walks the source. `pairs`/`ipairs` are read from the table they are
8550
8624
  * given, so a record's keys stay the literals they are. */
8551
- iterationOf(source, sourceType) {
8625
+ iterationOf(source, sourceType, report = true) {
8552
8626
  if (source.type === "CallExpression" && source.callee.type === "Identifier" && source.arguments[0]) {
8553
8627
  const name = source.callee.name;
8554
8628
  const src = this.expand(this.typeOf.get(source.arguments[0]) ?? unknownType);
@@ -8586,7 +8660,14 @@ var TypeAnalyzer = class {
8586
8660
  if (t.kind === "object") {
8587
8661
  return [t.indexer?.value ?? union([...t.properties.values()].map((p) => p.type)), values];
8588
8662
  }
8589
- if (t.kind !== "never" && this.emitDiagnostics) {
8663
+ if (t.kind === "union") {
8664
+ const each = t.types.map((member) => this.iterationOf(source, member, false));
8665
+ const [first] = each;
8666
+ if (first && each.every(([, form]) => form.walks === first[1].walks && form.viaIter === first[1].viaIter)) {
8667
+ return [union(each.map(([item]) => item)), first[1]];
8668
+ }
8669
+ }
8670
+ if (t.kind !== "never" && report && this.emitDiagnostics) {
8590
8671
  this.diagnostics.push({
8591
8672
  node: source,
8592
8673
  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 +9036,14 @@ var TypeAnalyzer = class {
8955
9036
  const names = keys.map(nameOf);
8956
9037
  const others = keys.filter((_, i) => names[i] === void 0);
8957
9038
  let message;
8958
- if (others.length && this.cannotIndex(object, others)) {
9039
+ const listOnly = (t) => {
9040
+ const x = this.expand(t);
9041
+ if (x.kind === "union") return x.types.every(listOnly);
9042
+ return x.kind === "array" || x.kind === "tuple";
9043
+ };
9044
+ if (names.some((name) => name !== void 0) && listOnly(withoutNil(this.expand(object)))) {
9045
+ message = `Type '${briefType(index)}' cannot be used to index type '${shown}'`;
9046
+ } else if (others.length && this.cannotIndex(object, others)) {
8959
9047
  message = `Type '${briefType(index)}' cannot be used to index type '${shown}'`;
8960
9048
  } else if (!others.length && names.every((name) => this.memberMissing(object, name))) {
8961
9049
  message = keys.length === 1 ? `Property '${names[0]}' does not exist on type '${shown}'` : `None of ${briefType(index)} is a property of type '${shown}'`;
@@ -9227,10 +9315,18 @@ var TypeAnalyzer = class {
9227
9315
  switch (expr.operator) {
9228
9316
  case "not":
9229
9317
  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;
9318
+ case "-": {
9319
+ const answered = this.operatorResult(expr, "-", arg, void 0);
9320
+ if (answered) return answered;
9321
+ this.checkOperands(expr, "-", arg);
9322
+ return this.arithmeticOn([arg]);
9323
+ }
9324
+ case "#": {
9325
+ const answered = this.operatorResult(expr, "#", arg, void 0);
9326
+ if (answered) return answered;
9327
+ this.checkOperands(expr, "#", arg);
9328
+ return numberType;
9329
+ }
9234
9330
  }
9235
9331
  return arg;
9236
9332
  }
@@ -9259,16 +9355,21 @@ var TypeAnalyzer = class {
9259
9355
  if (unwrapParens(expr.left).type === "StringLiteral") this.expectedTypeOf.set(unwrapParens(expr.left), r);
9260
9356
  }
9261
9357
  switch (op) {
9262
- case "..":
9263
- return this.operatorResult(expr, op, l, r) ?? stringType;
9358
+ case "..": {
9359
+ const answered = this.operatorResult(expr, op, l, r);
9360
+ if (answered) return answered;
9361
+ this.checkOperands(expr, op, l, r);
9362
+ return stringType;
9363
+ }
9264
9364
  case "<":
9265
9365
  case ">":
9266
9366
  case "<=":
9267
9367
  case ">=":
9268
- this.checkComparison(expr, op, l, r);
9368
+ if (!this.checkComparison(expr, op, l, r)) this.checkOperands(expr, op, l, r);
9269
9369
  return booleanType;
9270
9370
  case "==":
9271
9371
  case "~=":
9372
+ this.checkOverlap(expr, op, l, r);
9272
9373
  return booleanType;
9273
9374
  case "+":
9274
9375
  case "-":
@@ -9276,8 +9377,12 @@ var TypeAnalyzer = class {
9276
9377
  case "/":
9277
9378
  case "//":
9278
9379
  case "%":
9279
- case "^":
9280
- return this.operatorResult(expr, op, l, r) ?? this.arithmeticOn([l, r]);
9380
+ case "^": {
9381
+ const answered = this.operatorResult(expr, op, l, r);
9382
+ if (answered) return answered;
9383
+ this.checkOperands(expr, op, l, r);
9384
+ return this.arithmeticOn([l, r]);
9385
+ }
9281
9386
  }
9282
9387
  return union([l, r]);
9283
9388
  }
@@ -9525,6 +9630,14 @@ var TypeAnalyzer = class {
9525
9630
  name = target.property.name;
9526
9631
  object = target.object;
9527
9632
  } else if (target.type === "IndexExpression") {
9633
+ const list = this.expand(this.infer(target.object, env));
9634
+ if ((list.kind === "array" || list.kind === "tuple") && list.readonly) {
9635
+ this.diagnostics.push({
9636
+ node: target,
9637
+ message: `Cannot assign to an element of '${formatType(list)}': it is read-only`
9638
+ });
9639
+ return;
9640
+ }
9528
9641
  const index = this.expand(this.infer(target.index, env));
9529
9642
  if (index.kind !== "literal" || typeof index.value !== "string") return;
9530
9643
  name = index.value;
@@ -9697,9 +9810,27 @@ var TypeAnalyzer = class {
9697
9810
  if (s.indexer) indexer = mergeIndexer(indexer, s.indexer);
9698
9811
  }
9699
9812
  }
9813
+ this.checkDuplicateKey(expr, field, entries);
9700
9814
  }
9701
9815
  return objectType(entries, indexer, asConst || void 0);
9702
9816
  }
9817
+ /** `{ a: 1, a: 2 }` — the second wins and the first was written for
9818
+ * nothing, which is a typo far more often than a decision. A spread is
9819
+ * not one: overriding what it brought is exactly what it is for. */
9820
+ checkDuplicateKey(expr, field, entries) {
9821
+ if (!this.emitDiagnostics || this.duplicateKeyReported.has(field)) return;
9822
+ const written = field.type === "TableFieldNamed" ? field.key.type === "Identifier" ? field.key.name : field.key.value : field.type === "TableFieldShorthand" ? field.name.name : void 0;
9823
+ if (written === void 0) return;
9824
+ const spreadBefore = expr.fields.slice(0, expr.fields.indexOf(field)).some((f) => f.type === "TableFieldSpread");
9825
+ if (spreadBefore) return;
9826
+ if (entries.filter(([k]) => k === written).length < 2) return;
9827
+ this.duplicateKeyReported.add(field);
9828
+ this.diagnostics.push({
9829
+ node: field.type === "TableFieldNamed" ? field.key : field.name,
9830
+ message: `'${written}' is given twice in this table; only the last one is kept`
9831
+ });
9832
+ }
9833
+ duplicateKeyReported = /* @__PURE__ */ new WeakSet();
9703
9834
  /** A value inferred `as const`, widened back wherever `context` does not
9704
9835
  * ask for a literal: `satisfies`' result type. A property keeps `"circle"`
9705
9836
  * when the contract's property admits string literals, and becomes
@@ -10192,27 +10323,84 @@ var TypeAnalyzer = class {
10192
10323
  arithmeticOn(operands) {
10193
10324
  return operands.some((t) => this.expand(t).kind === "any") ? anyType : numberType;
10194
10325
  }
10326
+ /** `a == b` where nothing `a` can hold is anything `b` can: the answer is
10327
+ * always the same, so the comparison was not the one meant. Only types
10328
+ * that say what they hold take part — `any`, `unknown` and a type
10329
+ * parameter say nothing, and a table compares by identity, so two of them
10330
+ * may well be the same table. */
10331
+ checkOverlap(node, op, l, r) {
10332
+ if (!this.emitDiagnostics) return;
10333
+ const left = this.expand(l);
10334
+ const right = this.expand(r);
10335
+ const judged = (t) => {
10336
+ const parts = t.kind === "union" ? t.types.map((m) => this.expand(m)) : [t];
10337
+ return parts.every((p) => p.kind === "literal" || p.kind === "primitive");
10338
+ };
10339
+ const isNil = (t) => t.kind === "primitive" && t.name === "nil";
10340
+ if (isNil(left) || isNil(right)) return;
10341
+ if (!judged(left) || !judged(right)) return;
10342
+ if (isAssignable(left, right) || isAssignable(right, left)) return;
10343
+ this.diagnostics.push({
10344
+ node,
10345
+ message: `This comparison is always ${op === "==" ? "false" : "true"}: '${formatType(l)}' and '${formatType(r)}' have no value in common`
10346
+ });
10347
+ }
10348
+ /** What an operator takes, where no metamethod answered for it. Luau
10349
+ * raises on the rest — `n * 2` with `n` nil is an error at the line it
10350
+ * runs, and a nil `n` is exactly what `number | nil` says may happen — so
10351
+ * it is said here instead. `any` passes: nothing is known about it. */
10352
+ checkOperands(node, op, left, right) {
10353
+ if (!this.emitDiagnostics) return;
10354
+ const fits = (t) => {
10355
+ const x = this.expand(t);
10356
+ if (x.kind === "any" || x.kind === "never") return true;
10357
+ switch (op) {
10358
+ // `..` joins strings, and Luau writes a number into one.
10359
+ case "..":
10360
+ return isAssignable(x, stringType) || isAssignable(x, numberType);
10361
+ // `#` counts a string's bytes or a table's entries.
10362
+ case "#":
10363
+ return x.kind === "array" || x.kind === "tuple" || x.kind === "object" || isAssignable(x, stringType);
10364
+ default:
10365
+ return isAssignable(x, numberType);
10366
+ }
10367
+ };
10368
+ if (op === "<" || op === ">" || op === "<=" || op === ">=") {
10369
+ const both = (t) => this.expand(t);
10370
+ const [l, r] = [both(left), both(right)];
10371
+ const loose = (t) => t.kind === "any" || t.kind === "never";
10372
+ const alike = (as) => isAssignable(l, as) && isAssignable(r, as);
10373
+ if (loose(l) || loose(r) || alike(numberType) || alike(stringType)) return;
10374
+ } else if (fits(left) && (right === void 0 || fits(right))) {
10375
+ return;
10376
+ }
10377
+ this.diagnostics.push({
10378
+ node,
10379
+ 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)}'`
10380
+ });
10381
+ }
10195
10382
  /** `a < b` between class instances: Luau asks `__lt` (`__le` for `<=`),
10196
10383
  * with the operands swapped for `>` and `>=`, and an instance without
10197
10384
  * one cannot be compared at all. */
10198
10385
  checkComparison(node, op, l, r) {
10199
- if (!this.emitDiagnostics) return;
10386
+ if (!this.emitDiagnostics) return false;
10200
10387
  const left = this.expand(l);
10201
10388
  const right = this.expand(r);
10202
10389
  const instance = (t) => t.kind === "object" && t.class !== void 0;
10203
- if (!instance(left) && !instance(right)) return;
10390
+ if (!instance(left) && !instance(right)) return false;
10204
10391
  const name = op === "<" || op === ">" ? "__lt" : "__le";
10205
10392
  const operands = op === ">" || op === ">=" ? [right, left] : [left, right];
10206
10393
  for (const receiver of operands) {
10207
10394
  const method = receiver.kind === "object" ? receiver.properties.get(name) : void 0;
10208
10395
  if (!method) continue;
10209
- if (this.pickOverload(this.overloadsOf(method.type), operands)) return;
10396
+ if (this.pickOverload(this.overloadsOf(method.type), operands)) return true;
10210
10397
  break;
10211
10398
  }
10212
10399
  this.diagnostics.push({
10213
10400
  node,
10214
10401
  message: `Operator '${op}' cannot be applied to types '${formatType(l)}' and '${formatType(r)}'`
10215
10402
  });
10403
+ return true;
10216
10404
  }
10217
10405
  /** What an operator on a value with metamethods gives: `a + b` calls
10218
10406
  * `__add` on `a`, or failing that on `b` with the operands swapped — the
@@ -10499,7 +10687,56 @@ var TypeAnalyzer = class {
10499
10687
  return this.bindingByDecl.get(node) ?? this.bindingByPos.get(posKey(name, node.line.start, node.column.start));
10500
10688
  }
10501
10689
  };
10502
- function referencedTypeNames(node, out = []) {
10690
+ var libraryNames = /* @__PURE__ */ new WeakMap();
10691
+ function namesDeclaredIn(program) {
10692
+ const known = libraryNames.get(program);
10693
+ if (known) return known;
10694
+ const names = /* @__PURE__ */ new Set();
10695
+ for (const stmt of program.body.statements) {
10696
+ const declaration = stmt.type === "ExportStatement" || stmt.type === "ExportDefaultStatement" ? stmt.declaration : stmt;
10697
+ if (declaration.type === "TypeAliasStatement") names.add(declaration.name.name);
10698
+ else if (declaration.type === "ExportTypeAliasStatement") names.add(declaration.alias.name.name);
10699
+ else if (declaration.type === "DeclareClassStatement") names.add(declaration.name.name);
10700
+ else if (declaration.type === "ClassDeclaration" && declaration.name) names.add(declaration.name.name);
10701
+ else if (stmt.type === "ImportStatement") {
10702
+ for (const s of stmt.specifiers) names.add(s.local.name);
10703
+ if (stmt.defaultImport) names.add(stmt.defaultImport.name);
10704
+ if (stmt.namespaceImport) names.add(stmt.namespaceImport.name);
10705
+ }
10706
+ }
10707
+ libraryNames.set(program, names);
10708
+ return names;
10709
+ }
10710
+ var sharedClassesByLib = /* @__PURE__ */ new WeakMap();
10711
+ function shareableClasses(libs, program) {
10712
+ if (!libs.length) return void 0;
10713
+ const mine = namesDeclaredIn(program);
10714
+ if (mine.size) {
10715
+ for (const lib of libs) {
10716
+ for (const name of namesDeclaredIn(lib)) if (mine.has(name)) return void 0;
10717
+ }
10718
+ }
10719
+ const existing = sharedClassesByLib.get(libs[0]);
10720
+ if (existing) {
10721
+ const same = existing.libs.length === libs.length && existing.libs.every((lib, i) => lib === libs[i]);
10722
+ if (same) return existing;
10723
+ return void 0;
10724
+ }
10725
+ const store = { libs: [...libs], classTypes: /* @__PURE__ */ new WeakMap(), classMembers: /* @__PURE__ */ new WeakMap() };
10726
+ sharedClassesByLib.set(libs[0], store);
10727
+ return store;
10728
+ }
10729
+ var referencedNamesOf = /* @__PURE__ */ new WeakMap();
10730
+ var typeQueryIn = /* @__PURE__ */ new WeakMap();
10731
+ function referencedTypeNames(node, out) {
10732
+ if (out === void 0 && node && typeof node === "object") {
10733
+ const known = referencedNamesOf.get(node);
10734
+ if (known) return known;
10735
+ const found = referencedTypeNames(node, []);
10736
+ referencedNamesOf.set(node, found);
10737
+ return found;
10738
+ }
10739
+ out = out ?? [];
10503
10740
  if (!node || typeof node !== "object") return out;
10504
10741
  if (Array.isArray(node)) {
10505
10742
  for (const item of node) referencedTypeNames(item, out);
@@ -10516,9 +10753,11 @@ function referencedTypeNames(node, out = []) {
10516
10753
  }
10517
10754
  function containsTypeQuery(node) {
10518
10755
  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);
10756
+ const known = typeQueryIn.get(node);
10757
+ if (known !== void 0) return known;
10758
+ const found = Array.isArray(node) ? node.some(containsTypeQuery) : node.type === "TypeofTypeNode" || Object.values(node).some(containsTypeQuery);
10759
+ typeQueryIn.set(node, found);
10760
+ return found;
10522
10761
  }
10523
10762
  var STRING_INTRINSICS = /* @__PURE__ */ new Set(["Uppercase", "Lowercase", "Capitalize", "Uncapitalize"]);
10524
10763
  function briefType(t) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tilua/parser",
3
- "version": "2.0.0",
3
+ "version": "2.1.0",
4
4
  "description": "tilua parser for roblox",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.js",