@tilua/parser 2.3.0 → 3.0.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
@@ -734,7 +734,8 @@ var DECLARATION_STATEMENTS = /* @__PURE__ */ new Set([
734
734
  "ExportNamedStatement",
735
735
  "ExportAllStatement",
736
736
  "DeclareStatement",
737
- "DeclareClassStatement"
737
+ "DeclareClassStatement",
738
+ "DeclareMetatableStatement"
738
739
  ]);
739
740
  function spanFrom(start, end) {
740
741
  return {
@@ -1352,6 +1353,9 @@ var Parser = class {
1352
1353
  if (p1.type === "Identifier" && p1.value === "class" && this.peek(2).type === "Identifier") {
1353
1354
  return this.parseDeclareClassStatement();
1354
1355
  }
1356
+ if (p1.type === "Identifier" && p1.value === "metatable" && !this.punctuatorAt(2, ":")) {
1357
+ return this.parseDeclareMetatableStatement();
1358
+ }
1355
1359
  if (p1.type === "Identifier" || p1.type === "Keyword" && p1.value === "function") {
1356
1360
  return this.parseDeclareStatement();
1357
1361
  }
@@ -1404,6 +1408,17 @@ var Parser = class {
1404
1408
  }
1405
1409
  return tokenIdentifier(first);
1406
1410
  }
1411
+ // `declare metatable<T> T[]: { __index: { ... } }`
1412
+ parseDeclareMetatableStatement() {
1413
+ const start = this.current();
1414
+ this.advance();
1415
+ this.advance();
1416
+ const generics = this.checkOperator("<") ? this.parseGenericTypeParameterList() : [];
1417
+ const target = this.parseType();
1418
+ this.expectPunctuator(":");
1419
+ const metatable = this.parseType();
1420
+ return { type: "DeclareMetatableStatement", generics, target, metatable, ...spanFrom(start, this.previous()) };
1421
+ }
1407
1422
  // `declare class Name extends Base { member: T, ... }`
1408
1423
  parseDeclareClassStatement() {
1409
1424
  const start = this.current();
@@ -2112,7 +2127,15 @@ var Parser = class {
2112
2127
  parseExpressionStatement() {
2113
2128
  const start = this.current();
2114
2129
  if (this.checkPunctuator("{") || this.checkPunctuator("[")) {
2115
- const target = this.checkPunctuator("{") ? this.parseObjectPattern() : this.parseArrayPattern();
2130
+ const call = this.tryParse(() => {
2131
+ const errors = this.errors.length;
2132
+ const expression2 = this.parseAtom();
2133
+ if (expression2.type !== "MethodCallExpression" && expression2.type !== "CallExpression") throw new ParseRecover("not a call");
2134
+ if (this.errors.length > errors) throw new ParseRecover("not a call");
2135
+ return expression2;
2136
+ });
2137
+ if (call) return { type: "CallStatement", expression: call, ...spanFrom(start, this.previous()) };
2138
+ const target = this.checkPunctuator("{") ? this.parseObjectPattern(true) : this.parseArrayPattern(true);
2116
2139
  return this.parseAssignmentRest(start, target);
2117
2140
  }
2118
2141
  const statementStart = this.cursor;
@@ -2433,11 +2456,9 @@ var Parser = class {
2433
2456
  if (t.type === "Identifier" && t.value === "class") {
2434
2457
  return this.parseClassExpression();
2435
2458
  }
2436
- if (t.type === "Punctuator" && t.value === "{") {
2437
- return this.parseTableExpression();
2438
- }
2439
- if (t.type === "Punctuator" && t.value === "[") {
2440
- return this.parseArrayExpression();
2459
+ if (t.type === "Punctuator" && (t.value === "{" || t.value === "[")) {
2460
+ const literal2 = t.value === "{" ? this.parseTableExpression() : this.parseArrayExpression();
2461
+ return this.checkPunctuator(":") && this.startsMethodCall() ? this.parseSuffixes(literal2) : literal2;
2441
2462
  }
2442
2463
  if (this.checkType("Identifier") && this.punctuatorAt(1, "=>")) return this.parseArrow();
2443
2464
  if (this.checkPunctuator("(") || this.checkOperator("<")) {
@@ -2514,6 +2535,11 @@ var Parser = class {
2514
2535
  } else {
2515
2536
  this.error("Expected identifier or '('");
2516
2537
  }
2538
+ return this.parseSuffixes(base);
2539
+ }
2540
+ /** What follows a prefix expression's base: `.name`, `[key]`, `:m(...)`,
2541
+ * a call, and their optional forms. */
2542
+ parseSuffixes(base) {
2517
2543
  while (true) {
2518
2544
  if (this.checkPunctuator("?") && this.touchesNext()) {
2519
2545
  const next = this.peek(1);
@@ -2667,8 +2693,8 @@ var Parser = class {
2667
2693
  /** An assignment target after the first — Luau's `a, b = ...`, read only
2668
2694
  * to be reported: a prefix expression or a destructuring pattern. */
2669
2695
  parseAssignTarget() {
2670
- if (this.checkPunctuator("{")) return this.parseObjectPattern();
2671
- if (this.checkPunctuator("[")) return this.parseArrayPattern();
2696
+ if (this.checkPunctuator("{")) return this.parseObjectPattern(true);
2697
+ if (this.checkPunctuator("[")) return this.parseArrayPattern(true);
2672
2698
  return this.parsePrefixExpression();
2673
2699
  }
2674
2700
  /** Does a call's argument list start here? Lua's three forms: `(`, a
@@ -2847,13 +2873,17 @@ var Parser = class {
2847
2873
  /** Parses a binding target. When `topLevel`, also consumes a trailing
2848
2874
  * `<attr>` list (identifier only) and a `: Type` annotation — these are
2849
2875
  * only valid at the outermost level of a `local` / parameter binding,
2850
- * never nested inside another pattern. */
2851
- parseBindingTarget(topLevel) {
2876
+ * never nested inside another pattern. When `assign`, the pattern is a
2877
+ * destructuring assignment's (`[t[i], t[j]] = ...`), and a leaf is any
2878
+ * place a value can be assigned to: a name, a member or an index. */
2879
+ parseBindingTarget(topLevel, assign = false) {
2852
2880
  let target;
2853
2881
  if (this.checkPunctuator("{")) {
2854
- target = this.parseObjectPattern();
2882
+ target = this.parseObjectPattern(assign);
2855
2883
  } else if (this.checkPunctuator("[")) {
2856
- target = this.parseArrayPattern();
2884
+ target = this.parseArrayPattern(assign);
2885
+ } else if (assign) {
2886
+ return this.parseAssignPatternLeaf();
2857
2887
  } else {
2858
2888
  const nameTok = this.expectIdentifier();
2859
2889
  let attributes;
@@ -2875,10 +2905,25 @@ var Parser = class {
2875
2905
  }
2876
2906
  return target;
2877
2907
  }
2878
- parseObjectPattern() {
2879
- return this.inBrackets(() => this.parseObjectPatternInner());
2908
+ /** A leaf of a destructuring assignment: `a`, `a.b` or `a[i]`. */
2909
+ parseAssignPatternLeaf() {
2910
+ const expression = this.parsePrefixExpression();
2911
+ if (expression.type === "Identifier") {
2912
+ return { type: "IdentifierPattern", name: expression.name, line: expression.line, column: expression.column };
2913
+ }
2914
+ if (expression.type === "MemberExpression" || expression.type === "IndexExpression") {
2915
+ this.rejectOptionalTarget(expression);
2916
+ return expression;
2917
+ }
2918
+ const err = new ParseError("Only a name, a member or an index can be assigned to", expression.line.start, expression.column.start);
2919
+ if (!this.recover) throw err;
2920
+ this.record(err);
2921
+ return { type: "IdentifierPattern", name: "", line: expression.line, column: expression.column };
2922
+ }
2923
+ parseObjectPattern(assign = false) {
2924
+ return this.inBrackets(() => this.parseObjectPatternInner(assign));
2880
2925
  }
2881
- parseObjectPatternInner() {
2926
+ parseObjectPatternInner(assign) {
2882
2927
  const start = this.current();
2883
2928
  this.expectPunctuator("{");
2884
2929
  const properties = [];
@@ -2886,7 +2931,7 @@ var Parser = class {
2886
2931
  while (!this.checkPunctuator("}")) {
2887
2932
  if (this.checkOperator("...")) {
2888
2933
  this.advance();
2889
- rest = this.parseBindingTarget(false);
2934
+ rest = this.parseBindingTarget(false, assign);
2890
2935
  break;
2891
2936
  }
2892
2937
  const propStart = this.current();
@@ -2899,17 +2944,17 @@ var Parser = class {
2899
2944
  key = this.parseExpression();
2900
2945
  this.expectPunctuator("]");
2901
2946
  this.expectPunctuator(":");
2902
- value = this.parseBindingTarget(false);
2947
+ value = this.parseBindingTarget(false, assign);
2903
2948
  } else if (this.checkType("Literal") && this.current().kind === "string") {
2904
2949
  const t = this.advance();
2905
2950
  key = { type: "StringLiteral", value: t.value, raw: t.raw, ...spanFrom(t, t) };
2906
2951
  this.expectPunctuator(":");
2907
- value = this.parseBindingTarget(false);
2952
+ value = this.parseBindingTarget(false, assign);
2908
2953
  } else {
2909
2954
  const nameTok = this.expectIdentifier();
2910
2955
  key = { type: "Identifier", name: nameTok.value, ...spanFrom(nameTok, nameTok) };
2911
2956
  if (this.matchPunctuator(":")) {
2912
- value = this.parseBindingTarget(false);
2957
+ value = this.parseBindingTarget(false, assign);
2913
2958
  } else {
2914
2959
  shorthand = true;
2915
2960
  value = { type: "IdentifierPattern", name: nameTok.value, ...spanFrom(nameTok, nameTok) };
@@ -2932,10 +2977,10 @@ var Parser = class {
2932
2977
  this.expectPunctuator("}");
2933
2978
  return { type: "ObjectPattern", properties, rest, ...spanFrom(start, this.previous()) };
2934
2979
  }
2935
- parseArrayPattern() {
2936
- return this.inBrackets(() => this.parseArrayPatternInner());
2980
+ parseArrayPattern(assign = false) {
2981
+ return this.inBrackets(() => this.parseArrayPatternInner(assign));
2937
2982
  }
2938
- parseArrayPatternInner() {
2983
+ parseArrayPatternInner(assign) {
2939
2984
  const start = this.current();
2940
2985
  this.expectPunctuator("[");
2941
2986
  const elements = [];
@@ -2943,7 +2988,7 @@ var Parser = class {
2943
2988
  while (!this.checkPunctuator("]")) {
2944
2989
  if (this.checkOperator("...")) {
2945
2990
  this.advance();
2946
- rest = this.parseBindingTarget(false);
2991
+ rest = this.parseBindingTarget(false, assign);
2947
2992
  break;
2948
2993
  }
2949
2994
  if (this.checkPunctuator(",")) {
@@ -2952,7 +2997,7 @@ var Parser = class {
2952
2997
  continue;
2953
2998
  }
2954
2999
  const elStart = this.current();
2955
- const value = this.parseBindingTarget(false);
3000
+ const value = this.parseBindingTarget(false, assign);
2956
3001
  let def;
2957
3002
  if (this.matchOperator("=")) def = this.parseExpression();
2958
3003
  elements.push({ type: "ArrayPatternElement", value, default: def, ...spanFrom(elStart, this.previous()) });
@@ -4089,6 +4134,10 @@ var Analyzer = class {
4089
4134
  this.checkConstAssign(id, t);
4090
4135
  return;
4091
4136
  }
4137
+ case "MemberExpression":
4138
+ case "IndexExpression":
4139
+ this.visitExpression(t, scope);
4140
+ return;
4092
4141
  case "ObjectPattern":
4093
4142
  for (const p of t.properties) {
4094
4143
  if (p.computed) this.visitExpression(p.key, scope);
@@ -4124,7 +4173,9 @@ var Analyzer = class {
4124
4173
  switch (stmt.type) {
4125
4174
  case "VariableDeclaration": {
4126
4175
  if (stmt.init) this.visitExpression(stmt.init, scope);
4127
- this.visitType(stmt.name.typeAnnotation, scope);
4176
+ if (stmt.name.type !== "MemberExpression" && stmt.name.type !== "IndexExpression") {
4177
+ this.visitType(stmt.name.typeAnnotation, scope);
4178
+ }
4128
4179
  this.declarePattern(scope, stmt.name, "local", scope, stmt.kind === "const");
4129
4180
  return;
4130
4181
  }
@@ -4239,6 +4290,11 @@ var Analyzer = class {
4239
4290
  case "DeclareClassStatement":
4240
4291
  this.visitType(stmt.body, scope);
4241
4292
  return;
4293
+ case "DeclareMetatableStatement":
4294
+ this.visitGenerics(stmt.generics, scope);
4295
+ this.visitType(stmt.target, scope);
4296
+ this.visitType(stmt.metatable, scope);
4297
+ return;
4242
4298
  case "TypeAliasStatement":
4243
4299
  case "ExportTypeAliasStatement":
4244
4300
  this.visitGenerics(
@@ -4504,6 +4560,101 @@ type Mutable<T> = { -readonly [K in keyof T]: T[K] }
4504
4560
  type Pick<T, K> = { [P in K]: T[P] }
4505
4561
  type Omit<T, K> = Pick<T, Exclude<keyof T, K>>
4506
4562
  type Record<K, V> = { [P in K]: V }
4563
+
4564
+ -- ============================================================
4565
+ -- The metatables of the language's own values
4566
+ -- ============================================================
4567
+
4568
+ -- A string's methods. The first group is Lua's string library, which a
4569
+ -- string really answers to; where Lua answers several values, tilua answers
4570
+ -- one, as the global functions do (\`find\` is an array, \`gsub\` the new
4571
+ -- string). The second group is written the way JavaScript writes it.
4572
+ --
4573
+ -- Positions are Lua's: \`sub\` and \`slice\` count from 1, and \`indexOf\` is
4574
+ -- \`nil\` when the text is not there.
4575
+ declare metatable string: {
4576
+ __index: {
4577
+ upper: (self: string) => string,
4578
+ lower: (self: string) => string,
4579
+ len: (self: string) => number,
4580
+ sub: (self: string, i: number, j?: number) => string,
4581
+ rep: (self: string, n: number, separator?: string) => string,
4582
+ reverse: (self: string) => string,
4583
+ format: (self: string, ...args: unknown[]) => string,
4584
+ byte: ((self: string, i?: number) => number) & ((self: string, i: number, j: number) => number[]),
4585
+ find: (self: string, pattern: string, init?: number, plain?: boolean) => [number, number, ...string[]] | nil,
4586
+ match: (self: string, pattern: string, init?: number) => [...string[]] | nil,
4587
+ gmatch: (self: string, pattern: string) => () => [string, ...string[]] | nil,
4588
+ gsub: (self: string, pattern: string, replacement: unknown, n?: number) => string,
4589
+
4590
+ trim: (self: string) => string,
4591
+ trimStart: (self: string) => string,
4592
+ trimEnd: (self: string) => string,
4593
+ startsWith: (self: string, text: string) => boolean,
4594
+ endsWith: (self: string, text: string) => boolean,
4595
+ includes: (self: string, text: string) => boolean,
4596
+ indexOf: (self: string, text: string, start?: number) => number | nil,
4597
+ slice: (self: string, start?: number, stop?: number) => string,
4598
+ replace: (self: string, text: string, replacement: string) => string,
4599
+ replaceAll: (self: string, text: string, replacement: string) => string,
4600
+ padStart: (self: string, length: number, padding?: string) => string,
4601
+ padEnd: (self: string, length: number, padding?: string) => string,
4602
+ },
4603
+ }
4604
+
4605
+ -- An array's methods. Lua gives an array no metatable: the compiler lowers
4606
+ -- each call to a plain function, so they work on any array, one a Lua
4607
+ -- library made included.
4608
+ --
4609
+ -- Indices are Lua's: the first element is 1, and \`indexOf\` / \`findIndex\`
4610
+ -- answer \`nil\` rather than JavaScript's -1. \`sort\` takes Lua's comparator
4611
+ -- (true when \`a\` comes first). \`push\`, \`pop\`, \`shift\`, \`unshift\`,
4612
+ -- \`sort\` and \`reverse\` change the array; everything else returns a new one.
4613
+ declare metatable<T> T[]: {
4614
+ __index: {
4615
+ find: (self: T[], test: (value: T, index: number) => boolean) => T | nil,
4616
+ findIndex: (self: T[], test: (value: T, index: number) => boolean) => number | nil,
4617
+ filter: (self: T[], test: (value: T, index: number) => boolean) => T[],
4618
+ map: <U>(self: T[], transform: (value: T, index: number) => U) => U[],
4619
+ forEach: (self: T[], visit: (value: T, index: number) => nil) => nil,
4620
+ some: (self: T[], test: (value: T, index: number) => boolean) => boolean,
4621
+ every: (self: T[], test: (value: T, index: number) => boolean) => boolean,
4622
+ reduce: <U>(self: T[], step: (total: U, value: T, index: number) => U, initial: U) => U,
4623
+ includes: (self: T[], value: T) => boolean,
4624
+ indexOf: (self: T[], value: T, start?: number) => number | nil,
4625
+ join: (self: T[], separator?: string) => string,
4626
+ concat: (self: T[], ...others: T[][]) => T[],
4627
+ slice: (self: T[], start?: number, stop?: number) => T[],
4628
+ flat: (self: T[]) => T[],
4629
+ reverse: (self: T[]) => T[],
4630
+ sort: (self: T[], compare?: (a: T, b: T) => boolean) => T[],
4631
+ push: (self: T[], ...args: T[]) => number,
4632
+ pop: (self: T[]) => T | nil,
4633
+ shift: (self: T[]) => T | nil,
4634
+ unshift: (self: T[], ...args: T[]) => number,
4635
+ },
4636
+ }
4637
+
4638
+ -- A table's methods. Unlike JavaScript's \`Object.keys\`, they answer a
4639
+ -- tuple, in the order the table's type wrote its members:
4640
+ --
4641
+ -- const point = { x: 1, y: "up" }
4642
+ -- point:keys() -- ["x", "y"]
4643
+ -- point:values() -- [number, string]
4644
+ -- point:entries() -- [["x", number], ["y", string]]
4645
+ --
4646
+ -- A Lua table has no order of its own, so the compiler hands its runtime the
4647
+ -- keys as the type lists them. An optional member keeps its place, holding
4648
+ -- \`nil\` when it is missing; an indexer's keys follow the named ones. A
4649
+ -- member of the table's own with one of these names is what a call reaches,
4650
+ -- and a class instance, which has a metatable of its own, has none of them.
4651
+ declare metatable<T extends {}> T: {
4652
+ __index: {
4653
+ keys: (self: T) => ObjectKeys<T>,
4654
+ values: (self: T) => ObjectValues<T>,
4655
+ entries: (self: T) => ObjectEntries<T>,
4656
+ },
4657
+ }
4507
4658
  `;
4508
4659
  var prelude;
4509
4660
  function preludeProgram() {
@@ -5869,6 +6020,10 @@ var TypeAnalyzer = class {
5869
6020
  typeOfTypeNode = /* @__PURE__ */ new Map();
5870
6021
  expectedTypeOf = /* @__PURE__ */ new Map();
5871
6022
  loops = /* @__PURE__ */ new Map();
6023
+ /** Every `declare metatable`, libraries first, in declaration order. */
6024
+ metatables = [];
6025
+ metatableCache = /* @__PURE__ */ new WeakMap();
6026
+ methodSources = /* @__PURE__ */ new Map();
5872
6027
  /** Public: each alias resolved once (generic aliases keep their params as
5873
6028
  * `typeParam` nodes in the body). */
5874
6029
  aliases = new LazyMap();
@@ -5935,6 +6090,9 @@ var TypeAnalyzer = class {
5935
6090
  this.registerNestedClasses();
5936
6091
  this.registerNestedAliases();
5937
6092
  for (const lib of this.options.libs ?? []) this.harvestDeclares(lib.body);
6093
+ this.harvestMetatables(preludeProgram().body, { origin: "language" });
6094
+ (this.options.libs ?? []).forEach((lib, i) => this.harvestMetatables(lib.body, { origin: "library", library: i }));
6095
+ this.harvestMetatables(this.program.body, { origin: "program" });
5938
6096
  this.registerImportedTypes();
5939
6097
  this.resolveAllAliases();
5940
6098
  this.harvestDeclares(this.program.body, true);
@@ -5966,6 +6124,8 @@ var TypeAnalyzer = class {
5966
6124
  expectedTypeOf: this.expectedTypeOf,
5967
6125
  aliases: this.resolveDeferredAliases(),
5968
6126
  loops: this.loops,
6127
+ methodSources: this.methodSources,
6128
+ metatableMembers: (type) => this.metatableMembers(type),
5969
6129
  diagnostics: this.diagnostics
5970
6130
  };
5971
6131
  }
@@ -6023,8 +6183,7 @@ var TypeAnalyzer = class {
6023
6183
  }
6024
6184
  /** `layering` is on for the prelude and for definitions files: a second
6025
6185
  * library that declares an alias already declared *adds* to it, the way a
6026
- * second `declare` of a table's name does, so `@tilua-types/roblox` can give
6027
- * `StringMethods` Luau's `split` without restating Lua's. The file being
6186
+ * second `declare` of a table's name does. The file being
6028
6187
  * analysed is not a layer: its own alias replaces what the libraries
6029
6188
  * gave, which is how a project opts out of a set. */
6030
6189
  registerAliasDefs(block, layering = false) {
@@ -6541,6 +6700,14 @@ var TypeAnalyzer = class {
6541
6700
  report(stmt.superclass, known ? `'${stmt.superclass.name}' is not a class; a class can only extend another class` : `Cannot find class '${stmt.superclass.name}'`);
6542
6701
  }
6543
6702
  }
6703
+ this.withClass(stmt, () => this.withTypeParams(this.classTypeParams(stmt), () => {
6704
+ this.withSelfType(this.selfTypeOf(stmt), () => {
6705
+ for (const member of stmt.members) {
6706
+ if (member.type !== "ClassMethod" || !member.signatures) continue;
6707
+ for (const signature of member.signatures) this.checkOverload(member.func, signature);
6708
+ }
6709
+ });
6710
+ }));
6544
6711
  for (const member of stmt.members) {
6545
6712
  if (member.type === "ClassConstructor") continue;
6546
6713
  if (CLASS_RESERVED.has(member.name.name)) {
@@ -6934,7 +7101,7 @@ var TypeAnalyzer = class {
6934
7101
  if (record.type === "TypeReference" && typeof record.base === "string") {
6935
7102
  const name = typeof record.namespace === "string" ? `${record.namespace}.${record.base}` : record.base;
6936
7103
  const resolved = this.typeOfTypeNode.get(node);
6937
- const unresolved = resolved?.kind === "genericRef" && resolved.name === name && !this.aliasDefs.has(name) && !this.importedTypes.has(name) && this.options.libTypes?.[name] === void 0 && !STRING_INTRINSICS.has(name) && !this.importedNames().has(name.split(".")[0]);
7104
+ const unresolved = resolved?.kind === "genericRef" && resolved.name === name && !this.aliasDefs.has(name) && !this.importedTypes.has(name) && this.options.libTypes?.[name] === void 0 && !INTRINSICS.has(name) && !this.importedNames().has(name.split(".")[0]);
6938
7105
  const at = node;
6939
7106
  const key = `${at.line.start}:${at.column.start}`;
6940
7107
  if (unresolved && !reported.has(key)) {
@@ -7052,7 +7219,7 @@ var TypeAnalyzer = class {
7052
7219
  const tp = this.lookupTypeParam(node.base);
7053
7220
  if (tp) return typeParam(tp.name, tp.constraint, tp.isConst);
7054
7221
  if (node.typeArguments.length === 1 && !this.aliasDefs.has(node.base)) {
7055
- const intrinsic = this.applyStringIntrinsic(
7222
+ const intrinsic = this.applyIntrinsic(
7056
7223
  node.base,
7057
7224
  this.resolveType(node.typeArguments[0])
7058
7225
  );
@@ -7259,7 +7426,7 @@ var TypeAnalyzer = class {
7259
7426
  return difference(this.reduceType(t.base), this.reduceType(t.excluded));
7260
7427
  case "genericRef": {
7261
7428
  if (t.typeArguments.length !== 1 || this.aliasDefs.has(t.name)) return t;
7262
- return this.applyStringIntrinsic(t.name, t.typeArguments[0]) ?? t;
7429
+ return this.applyIntrinsic(t.name, t.typeArguments[0]) ?? t;
7263
7430
  }
7264
7431
  case "conditional":
7265
7432
  return this.reduceConditional(t);
@@ -7372,6 +7539,41 @@ var TypeAnalyzer = class {
7372
7539
  }
7373
7540
  return union(combos.map((c) => literal(c)));
7374
7541
  }
7542
+ /** A type function the analyzer supplies, since tilua cannot write it. */
7543
+ applyIntrinsic(name, arg) {
7544
+ return this.applyStringIntrinsic(name, arg) ?? this.applyObjectIntrinsic(name, arg);
7545
+ }
7546
+ /** `ObjectKeys<T>`, `ObjectValues<T>`, `ObjectEntries<T>`: an object's
7547
+ * members as a tuple, in the order they were written — `{ a: 1, b: "x" }`
7548
+ * has keys `["a", "b"]`, values `[number, string]` and entries
7549
+ * `[["a", number], ["b", string]]`. TypeScript answers `string[]` for
7550
+ * `Object.keys`, since its objects have no order; tilua's lowering hands
7551
+ * the runtime the written order, so the tuple is what runs. An indexer's
7552
+ * keys follow the named ones, as the tuple's rest. */
7553
+ applyObjectIntrinsic(name, arg) {
7554
+ if (!OBJECT_INTRINSICS.has(name)) return void 0;
7555
+ const t = this.expand(this.reduceType(arg));
7556
+ if (containsTypeParam(t)) return void 0;
7557
+ if (t.kind === "union") return union(t.types.map((m) => this.applyObjectIntrinsic(name, m) ?? unknownType));
7558
+ const parts = t.kind === "intersection" ? t.types.map((m) => this.expand(m)) : [t];
7559
+ if (!parts.every((m) => m.kind === "object" && !m.class)) return unknownType;
7560
+ const properties = /* @__PURE__ */ new Map();
7561
+ let indexer;
7562
+ for (const part of parts) {
7563
+ for (const [key, property] of part.properties) properties.set(key, property);
7564
+ indexer ??= part.indexer;
7565
+ }
7566
+ const entries = [...properties].map(([key, p]) => [literal(key), p.optional ? optional(p.type) : p.type]);
7567
+ const listed = (elements, rest) => !elements.length && rest ? arrayOf(rest) : tuple(elements, rest);
7568
+ switch (name) {
7569
+ case "ObjectKeys":
7570
+ return listed(entries.map(([key]) => key), indexer?.key);
7571
+ case "ObjectValues":
7572
+ return listed(entries.map(([, value]) => value), indexer?.value);
7573
+ default:
7574
+ return listed(entries.map((entry) => tuple([...entry])), indexer && tuple([indexer.key, indexer.value]));
7575
+ }
7576
+ }
7375
7577
  /** TypeScript's four string intrinsics. They cannot be written in tilua —
7376
7578
  * there is no character-level type arithmetic — so the analyzer supplies
7377
7579
  * them, and only them, as named type functions. */
@@ -8047,7 +8249,7 @@ var TypeAnalyzer = class {
8047
8249
  /** Synthesize a type from a destructuring pattern used without an
8048
8250
  * annotation (`function f({ a, b = 1 })`). */
8049
8251
  patternToType(target, env) {
8050
- const leaf = (v, def) => v.type !== "IdentifierPattern" ? this.patternToType(v, env) : v.typeAnnotation ? this.resolveType(v.typeAnnotation) : def ? widen(this.infer(def, env)) : anyType;
8252
+ const leaf = (v, def) => v.type === "ObjectPattern" || v.type === "ArrayPattern" ? this.patternToType(v, env) : v.type !== "IdentifierPattern" ? anyType : v.typeAnnotation ? this.resolveType(v.typeAnnotation) : def ? widen(this.infer(def, env)) : anyType;
8051
8253
  if (target.type === "ObjectPattern") {
8052
8254
  const entries = [];
8053
8255
  for (const p of target.properties) {
@@ -8453,6 +8655,7 @@ var TypeAnalyzer = class {
8453
8655
  * can actually be. An annotation, a pattern or a default still wins. */
8454
8656
  paramsFromSignatures(func, signatures) {
8455
8657
  if (!signatures?.length) return;
8658
+ for (const signature of signatures) this.checkOverload(func, signature);
8456
8659
  const resolved = signatures.map((sig) => this.signatureToFnType(sig));
8457
8660
  func.params.forEach((param, i) => {
8458
8661
  if (param.typeAnnotation || param.pattern || param.default) return;
@@ -8466,6 +8669,64 @@ var TypeAnalyzer = class {
8466
8669
  if (candidates.length) this.contextualParams.set(param, union(candidates));
8467
8670
  });
8468
8671
  }
8672
+ /** The implementation is what runs for every signature of an overload
8673
+ * set, so each signature must be one it can serve: no parameter it does
8674
+ * not have, no fewer arguments than it requires, no argument type it does
8675
+ * not accept, and a return it can give. Stricter than TypeScript, which
8676
+ * lets a signature name parameters the implementation never reads. */
8677
+ checkOverload(func, signature) {
8678
+ if (!this.emitDiagnostics) return;
8679
+ const node = signature;
8680
+ const report = (message) => {
8681
+ this.diagnostics.push({ node, message });
8682
+ };
8683
+ const own = (params) => params.filter((p, i) => !p.rest && !(i === 0 && !p.typeAnnotation && (p.name === "self" || p.name === "this")));
8684
+ const implementation = own(func.params);
8685
+ const offered = own(signature.params);
8686
+ const implementationRest = func.params.some((p) => p.rest);
8687
+ const signatureRest = signature.params.some((p) => p.rest);
8688
+ const label = (p) => p.pattern ? "a destructured parameter" : `'${p.name}'`;
8689
+ if (!implementationRest) {
8690
+ const extra = offered[implementation.length];
8691
+ if (extra) {
8692
+ report(`This overload takes parameter ${label(extra)}, which the implementation does not have: it takes ${implementation.length} parameter${implementation.length === 1 ? "" : "s"}`);
8693
+ return;
8694
+ }
8695
+ if (signatureRest) {
8696
+ report("This overload takes a rest parameter, which the implementation does not have");
8697
+ return;
8698
+ }
8699
+ }
8700
+ if (!signatureRest) {
8701
+ let required = 0;
8702
+ implementation.forEach((p, i) => {
8703
+ if (!p.optional && p.default === void 0) required = i + 1;
8704
+ });
8705
+ if (offered.length < required) {
8706
+ report(`The implementation requires ${required} argument${required === 1 ? "" : "s"}, and this overload passes at most ${offered.length}`);
8707
+ return;
8708
+ }
8709
+ }
8710
+ if (signature.generics.length || func.generics.length) return;
8711
+ for (let i = 0; i < offered.length && i < implementation.length; i++) {
8712
+ const target = implementation[i];
8713
+ if (!target.typeAnnotation && target.default === void 0) continue;
8714
+ let accepted = this.paramType(target, /* @__PURE__ */ new Map());
8715
+ if (target.default !== void 0) accepted = optional(accepted);
8716
+ const given = this.paramType(offered[i], /* @__PURE__ */ new Map());
8717
+ if (!isAssignable(given, accepted)) {
8718
+ report(`Parameter ${label(offered[i])} of this overload is '${briefType(given)}', which the implementation's '${briefType(accepted)}' does not accept`);
8719
+ return;
8720
+ }
8721
+ }
8722
+ if (signature.returnType && func.returnType) {
8723
+ const promised = this.resolveType(signature.returnType);
8724
+ const returned = this.resolveType(func.returnType);
8725
+ if (!isAssignable(promised, returned) && !isAssignable(returned, promised)) {
8726
+ report(`This overload returns '${briefType(promised)}', which the implementation's '${briefType(returned)}' cannot be`);
8727
+ }
8728
+ }
8729
+ }
8469
8730
  signatureToFnType(sig) {
8470
8731
  const names = sig.generics.map((g) => g.name);
8471
8732
  const record = (type) => {
@@ -8890,6 +9151,12 @@ var TypeAnalyzer = class {
8890
9151
  }
8891
9152
  return;
8892
9153
  }
9154
+ case "MemberExpression":
9155
+ case "IndexExpression":
9156
+ this.infer(target, env);
9157
+ this.checkReadonlyAssign(target, env);
9158
+ this.assignToRef(target, widen(valueType), env);
9159
+ return;
8893
9160
  case "ObjectPattern": {
8894
9161
  for (const p of target.properties) {
8895
9162
  const key = !p.computed && p.key.type === "Identifier" ? p.key.name : !p.computed && p.key.type === "StringLiteral" ? p.key.value : void 0;
@@ -8988,28 +9255,122 @@ var TypeAnalyzer = class {
8988
9255
  this.resolvingAliases.delete(t.name);
8989
9256
  }
8990
9257
  }
8991
- /** `names:filter(f)`, `text:trim()` — the methods arrays and strings have.
8992
- * They are written in the prelude as `ArrayMethods<T>` and
8993
- * `StringMethods`, so a file (or a type library) that declares one of
8994
- * those names again replaces the whole set, and nothing here is a special
8995
- * case in the analyzer. The build lowers each call to a plain function. */
8996
- builtInMethod(t, name) {
8997
- const parts = (t.kind === "union" ? t.types : [t]).map((m) => this.expand(m));
8998
- const elements = parts.map((m) => m.kind === "array" ? m.element : m.kind === "tuple" ? union(m.elements) : void 0);
8999
- const element = elements.every((e) => e !== void 0) ? union(elements) : void 0;
9000
- const isString = (m) => m.kind === "primitive" && m.name === "string" || m.kind === "literal" && m.base === "string" || m.kind === "templateLiteral";
9001
- const methodTable = element !== void 0 ? "ArrayMethods" : parts.every(isString) ? "StringMethods" : void 0;
9002
- const def = methodTable === void 0 ? void 0 : this.aliasDefs.get(methodTable);
9003
- if (!def || def.class) return void 0;
9004
- const table = this.expand(this.instantiateAlias(def, element !== void 0 ? [element] : []));
9005
- const layers = table.kind === "intersection" ? table.types.map((m) => this.expand(m)) : [table];
9006
- for (let i = layers.length - 1; i >= 0; i--) {
9007
- const part = layers[i];
9008
- const property = part.kind === "object" ? part.properties.get(name) : void 0;
9009
- if (property) return property.type;
9258
+ // --------------------------------------------------------
9259
+ // Metatables
9260
+ // --------------------------------------------------------
9261
+ harvestMetatables(block, source) {
9262
+ for (const stmt of block.statements) {
9263
+ if (stmt.type === "DeclareMetatableStatement") this.metatables.push({ node: stmt, source });
9264
+ }
9265
+ }
9266
+ resolvedMetatable(entry) {
9267
+ entry.resolved ??= this.withTypeParams(entry.node.generics, () => ({
9268
+ target: this.resolveType(entry.node.target),
9269
+ metatable: this.resolveType(entry.node.metatable)
9270
+ }));
9271
+ return entry.resolved;
9272
+ }
9273
+ /** Does a value (`whole`, whose union members are `parts`) have the
9274
+ * metatable `entry` declares — and if so how specifically, and what its
9275
+ * type parameters stand for there. Three kinds of target:
9276
+ *
9277
+ * - `<T> T[]`: every array and tuple, `T` what they hold;
9278
+ * - `<T extends C> T`: every value that is a `C` other than a class
9279
+ * instance, which has a metatable of its own; `T` the value itself;
9280
+ * - a type with no parameters (`string`): every value that is one.
9281
+ *
9282
+ * A bare parameter matches the most, so it is the least specific: an
9283
+ * array that is also a `{}` takes an array's metatable. */
9284
+ matchMetatable(entry, whole, parts) {
9285
+ if (!parts.length || parts.some((m) => m.kind === "any" || m.kind === "unknown" || m.kind === "never")) return void 0;
9286
+ const { target } = this.resolvedMetatable(entry);
9287
+ if (target.kind === "array" && target.element.kind === "typeParam") {
9288
+ const elements = parts.map((m) => m.kind === "array" ? m.element : m.kind === "tuple" ? union(tupleMembers(m)) : void 0);
9289
+ if (!elements.every((e) => e !== void 0)) return void 0;
9290
+ return { specificity: 2, bindings: /* @__PURE__ */ new Map([[target.element.name, union(elements)]]) };
9291
+ }
9292
+ if (target.kind === "typeParam") {
9293
+ const constraint = target.constraint;
9294
+ if (parts.some((m) => m.kind === "object" && m.class)) return void 0;
9295
+ if (constraint && !parts.every((m) => isAssignable(m, constraint))) return void 0;
9296
+ return { specificity: 1, bindings: /* @__PURE__ */ new Map([[target.name, whole]]) };
9297
+ }
9298
+ if (containsTypeParam(target)) return void 0;
9299
+ return parts.every((m) => isAssignable(m, target)) ? { specificity: 2, bindings: /* @__PURE__ */ new Map() } : void 0;
9300
+ }
9301
+ /** The metatables `t` has: every declaration of the most specific target
9302
+ * it matches, instantiated for it, in declaration order — a later one
9303
+ * adds to an earlier, the way a library layers on another. A type
9304
+ * parameter is read at its constraint. */
9305
+ metatablesOf(t) {
9306
+ if (!this.metatables.length) return [];
9307
+ const cached = this.metatableCache.get(t);
9308
+ if (cached) return cached;
9309
+ const expanded = this.expand(t);
9310
+ const parts = (expanded.kind === "union" ? expanded.types : [expanded]).map((m) => this.expand(m)).map((m) => m.kind === "typeParam" && m.constraint ? this.expand(m.constraint) : m);
9311
+ const whole = union(parts);
9312
+ const matches = this.metatables.flatMap((entry) => {
9313
+ const match = this.matchMetatable(entry, whole, parts);
9314
+ return match ? [{ entry, ...match }] : [];
9315
+ });
9316
+ const best = Math.max(0, ...matches.map((m) => m.specificity));
9317
+ const found = matches.filter((m) => m.specificity === best).map(({ entry, bindings }) => ({
9318
+ type: this.expand(this.reduceType(substitute(this.resolvedMetatable(entry).metatable, bindings))),
9319
+ source: entry.source
9320
+ }));
9321
+ this.metatableCache.set(t, found);
9322
+ return found;
9323
+ }
9324
+ /** The `__index` tables of `t`'s metatables, each with the library that
9325
+ * declared it, in declaration order. */
9326
+ metatableIndexes(t) {
9327
+ return this.metatablesOf(t).flatMap(({ type, source }) => {
9328
+ const parts = type.kind === "intersection" ? type.types.map((m) => this.expand(m)) : [type];
9329
+ return parts.flatMap((part) => {
9330
+ const index = part.kind === "object" ? part.properties.get("__index") : void 0;
9331
+ if (!index) return [];
9332
+ const table = this.expand(index.type);
9333
+ return (table.kind === "intersection" ? table.types.map((m) => this.expand(m)) : [table]).map((m) => ({ table: m, source }));
9334
+ });
9335
+ });
9336
+ }
9337
+ /** `name` read through `t`'s metatable: `text:upper()` is `upper` in the
9338
+ * string metatable's `__index`. The last declaration to give the name
9339
+ * wins. The language's own metatables are declared in the prelude, in
9340
+ * tilua, like any library's; a library adds to them the same way. */
9341
+ metatableMember(t, name) {
9342
+ const indexes = this.metatableIndexes(t);
9343
+ for (let i = indexes.length - 1; i >= 0; i--) {
9344
+ const { table, source } = indexes[i];
9345
+ const property = table.kind === "object" ? table.properties.get(name) : void 0;
9346
+ if (property) return { type: property.type, source };
9010
9347
  }
9011
9348
  return void 0;
9012
9349
  }
9350
+ metatableMethod(t, name) {
9351
+ return this.metatableMember(t, name)?.type;
9352
+ }
9353
+ hasMetatable(t) {
9354
+ return this.metatablesOf(t).length > 0;
9355
+ }
9356
+ metatableMembers(t) {
9357
+ const members = /* @__PURE__ */ new Map();
9358
+ for (const { table } of this.metatableIndexes(t)) {
9359
+ if (table.kind !== "object") continue;
9360
+ for (const [name, property] of table.properties) members.set(name, property.type);
9361
+ }
9362
+ return members;
9363
+ }
9364
+ /** `text:upper()`, `scores:keys()`: the method a value's metatable gives
9365
+ * it, when the value has no member of that name of its own. Called with
9366
+ * `:`, it answers before an object's indexer, since a metatable's
9367
+ * `__index` is only read for a key the table does not hold. */
9368
+ receiverMetatableMember(objType, name) {
9369
+ const t = this.expand(objType);
9370
+ const parts = (t.kind === "union" ? t.types : [t]).map((m) => this.expand(m));
9371
+ if (parts.some((m) => m.kind === "object" && m.properties.has(name))) return void 0;
9372
+ return this.metatableMember(t, name);
9373
+ }
9013
9374
  /** The most a deferred type could turn out to be. A conditional is one of
9014
9375
  * its branches, and the true branch stands for a member of what was
9015
9376
  * tested (`T` in `T extends U ? T : never`); an indexed access reads
@@ -9075,7 +9436,7 @@ var TypeAnalyzer = class {
9075
9436
  if (index.kind !== "literal" || typeof index.value !== "string") return void 0;
9076
9437
  const parts = this.stringParts(object);
9077
9438
  if (!parts) return void 0;
9078
- const found = parts.map((part) => this.builtInMethod(part, index.value));
9439
+ const found = parts.map((part) => this.metatableMethod(part, index.value));
9079
9440
  return found.every((t) => t !== void 0) ? union(found) : void 0;
9080
9441
  }
9081
9442
  /** The members of `t` when every one of them is a string — `"a" | "b"` is
@@ -9096,13 +9457,13 @@ var TypeAnalyzer = class {
9096
9457
  * not one of them is a mistake worth reporting, rather than the nil Lua
9097
9458
  * would hand back. */
9098
9459
  checkStringMember(node, object, key) {
9099
- if (!this.emitDiagnostics || !this.aliasDefs.has("StringMethods")) return;
9460
+ if (!this.emitDiagnostics || !this.hasMetatable(stringType)) return;
9100
9461
  const parts = this.stringParts(object);
9101
9462
  if (!parts) return;
9102
9463
  const keys = (key.kind === "union" ? key.types : [key]).map((m) => this.expand(m));
9103
9464
  if (!keys.length || !keys.every((m) => m.kind === "literal" && typeof m.value === "string")) return;
9104
9465
  const names = keys.map((m) => String(m.value));
9105
- if (names.some((name) => parts.some((part) => this.builtInMethod(part, name)))) return;
9466
+ if (names.some((name) => parts.some((part) => this.metatableMethod(part, name)))) return;
9106
9467
  this.diagnostics.push({
9107
9468
  node,
9108
9469
  message: names.length === 1 ? `'${names[0]}' does not exist on a string` : `'${briefType(key)}' does not name a member of a string`
@@ -9134,24 +9495,24 @@ var TypeAnalyzer = class {
9134
9495
  switch (part.kind) {
9135
9496
  case "primitive":
9136
9497
  if (part.name === "nil") continue;
9137
- if (part.name === "string" && !this.aliasDefs.has("StringMethods")) return false;
9138
- if (part.name === "string" && this.builtInMethod(part, name)) continue;
9498
+ if (part.name === "string" && !this.hasMetatable(part)) return false;
9499
+ if (part.name === "string" && this.metatableMethod(part, name)) continue;
9139
9500
  break;
9140
9501
  case "literal":
9141
9502
  case "templateLiteral":
9142
9503
  if (part.kind === "templateLiteral" || part.base === "string") {
9143
- if (!this.aliasDefs.has("StringMethods")) return false;
9504
+ if (!this.hasMetatable(part)) return false;
9144
9505
  }
9145
- if (this.builtInMethod(part, name)) continue;
9506
+ if (this.metatableMethod(part, name)) continue;
9146
9507
  break;
9147
9508
  case "array":
9148
9509
  case "tuple":
9149
- if (!this.aliasDefs.has("ArrayMethods")) return false;
9150
- if (this.builtInMethod(part, name)) continue;
9510
+ if (!this.hasMetatable(part)) return false;
9511
+ if (this.metatableMethod(part, name)) continue;
9151
9512
  break;
9152
9513
  case "object":
9153
9514
  if (part.properties.has(name)) continue;
9154
- if (part.indexer || part.properties.size === 0 && !part.class || this.builtInMethod(part, name)) return false;
9515
+ if (part.indexer || part.properties.size === 0 && !part.class || this.metatableMethod(part, name)) return false;
9155
9516
  break;
9156
9517
  default:
9157
9518
  return false;
@@ -9286,10 +9647,10 @@ var TypeAnalyzer = class {
9286
9647
  if (p) return p.optional ? optional(p.type) : p.type;
9287
9648
  if (t.indexer) return t.indexer.value;
9288
9649
  }
9289
- const built = this.builtInMethod(t, name);
9650
+ const built = this.metatableMethod(t, name);
9290
9651
  if (built) return built;
9291
9652
  if (t.kind === "union") {
9292
- const built2 = this.builtInMethod(t, name);
9653
+ const built2 = this.metatableMethod(t, name);
9293
9654
  if (built2) return built2;
9294
9655
  return union(t.types.map((m) => this.propertyType(m, name)));
9295
9656
  }
@@ -9690,7 +10051,9 @@ var TypeAnalyzer = class {
9690
10051
  this.checkPrivateMember(expr.method, objType, expr.method.name);
9691
10052
  this.checkStringMember(expr.method, objType, literal(expr.method.name));
9692
10053
  this.checkMissingMember(expr.method, objType, expr.method.name);
9693
- const method = this.propertyType(objType, expr.method.name);
10054
+ const fromMetatable = this.receiverMetatableMember(objType, expr.method.name);
10055
+ if (fromMetatable) this.methodSources.set(expr, fromMetatable.source);
10056
+ const method = fromMetatable?.type ?? this.propertyType(objType, expr.method.name);
9694
10057
  const united = this.unionSignatures(method);
9695
10058
  const fns = united ?? this.overloadsOf(method);
9696
10059
  const explicit = this.explicitTypeArguments(expr, fns);
@@ -10932,6 +11295,8 @@ function containsTypeQuery(node) {
10932
11295
  return found;
10933
11296
  }
10934
11297
  var STRING_INTRINSICS = /* @__PURE__ */ new Set(["Uppercase", "Lowercase", "Capitalize", "Uncapitalize"]);
11298
+ var OBJECT_INTRINSICS = /* @__PURE__ */ new Set(["ObjectKeys", "ObjectValues", "ObjectEntries"]);
11299
+ var INTRINSICS = /* @__PURE__ */ new Set([...STRING_INTRINSICS, ...OBJECT_INTRINSICS]);
10935
11300
  function briefType(t) {
10936
11301
  if (t.kind === "union" && t.types.length > 8) {
10937
11302
  const shown = t.types.slice(0, 6).map(formatType).join(" | ");
@@ -11139,7 +11504,8 @@ function resolveTypeLibraries(config, host = nodeHost) {
11139
11504
  }
11140
11505
  addFile(entryFile);
11141
11506
  const lowering = loweringModule(directory, host, problems, config);
11142
- if (lowering) lowerings.push(lowering);
11507
+ const library = files.findIndex((file) => pathKey(file) === pathKey(entryFile));
11508
+ if (lowering) lowerings.push({ ...lowering, library });
11143
11509
  };
11144
11510
  for (const entry of config.types) {
11145
11511
  const relative = entry.startsWith("./") || entry.startsWith("../") || entry.startsWith("/") || /^[A-Za-z]:[\\/]/.test(entry);