@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.js CHANGED
@@ -629,7 +629,8 @@ var DECLARATION_STATEMENTS = /* @__PURE__ */ new Set([
629
629
  "ExportNamedStatement",
630
630
  "ExportAllStatement",
631
631
  "DeclareStatement",
632
- "DeclareClassStatement"
632
+ "DeclareClassStatement",
633
+ "DeclareMetatableStatement"
633
634
  ]);
634
635
  function spanFrom(start, end) {
635
636
  return {
@@ -1247,6 +1248,9 @@ var Parser = class {
1247
1248
  if (p1.type === "Identifier" && p1.value === "class" && this.peek(2).type === "Identifier") {
1248
1249
  return this.parseDeclareClassStatement();
1249
1250
  }
1251
+ if (p1.type === "Identifier" && p1.value === "metatable" && !this.punctuatorAt(2, ":")) {
1252
+ return this.parseDeclareMetatableStatement();
1253
+ }
1250
1254
  if (p1.type === "Identifier" || p1.type === "Keyword" && p1.value === "function") {
1251
1255
  return this.parseDeclareStatement();
1252
1256
  }
@@ -1299,6 +1303,17 @@ var Parser = class {
1299
1303
  }
1300
1304
  return tokenIdentifier(first);
1301
1305
  }
1306
+ // `declare metatable<T> T[]: { __index: { ... } }`
1307
+ parseDeclareMetatableStatement() {
1308
+ const start = this.current();
1309
+ this.advance();
1310
+ this.advance();
1311
+ const generics = this.checkOperator("<") ? this.parseGenericTypeParameterList() : [];
1312
+ const target = this.parseType();
1313
+ this.expectPunctuator(":");
1314
+ const metatable = this.parseType();
1315
+ return { type: "DeclareMetatableStatement", generics, target, metatable, ...spanFrom(start, this.previous()) };
1316
+ }
1302
1317
  // `declare class Name extends Base { member: T, ... }`
1303
1318
  parseDeclareClassStatement() {
1304
1319
  const start = this.current();
@@ -2007,7 +2022,15 @@ var Parser = class {
2007
2022
  parseExpressionStatement() {
2008
2023
  const start = this.current();
2009
2024
  if (this.checkPunctuator("{") || this.checkPunctuator("[")) {
2010
- const target = this.checkPunctuator("{") ? this.parseObjectPattern() : this.parseArrayPattern();
2025
+ const call = this.tryParse(() => {
2026
+ const errors = this.errors.length;
2027
+ const expression2 = this.parseAtom();
2028
+ if (expression2.type !== "MethodCallExpression" && expression2.type !== "CallExpression") throw new ParseRecover("not a call");
2029
+ if (this.errors.length > errors) throw new ParseRecover("not a call");
2030
+ return expression2;
2031
+ });
2032
+ if (call) return { type: "CallStatement", expression: call, ...spanFrom(start, this.previous()) };
2033
+ const target = this.checkPunctuator("{") ? this.parseObjectPattern(true) : this.parseArrayPattern(true);
2011
2034
  return this.parseAssignmentRest(start, target);
2012
2035
  }
2013
2036
  const statementStart = this.cursor;
@@ -2328,11 +2351,9 @@ var Parser = class {
2328
2351
  if (t.type === "Identifier" && t.value === "class") {
2329
2352
  return this.parseClassExpression();
2330
2353
  }
2331
- if (t.type === "Punctuator" && t.value === "{") {
2332
- return this.parseTableExpression();
2333
- }
2334
- if (t.type === "Punctuator" && t.value === "[") {
2335
- return this.parseArrayExpression();
2354
+ if (t.type === "Punctuator" && (t.value === "{" || t.value === "[")) {
2355
+ const literal2 = t.value === "{" ? this.parseTableExpression() : this.parseArrayExpression();
2356
+ return this.checkPunctuator(":") && this.startsMethodCall() ? this.parseSuffixes(literal2) : literal2;
2336
2357
  }
2337
2358
  if (this.checkType("Identifier") && this.punctuatorAt(1, "=>")) return this.parseArrow();
2338
2359
  if (this.checkPunctuator("(") || this.checkOperator("<")) {
@@ -2409,6 +2430,11 @@ var Parser = class {
2409
2430
  } else {
2410
2431
  this.error("Expected identifier or '('");
2411
2432
  }
2433
+ return this.parseSuffixes(base);
2434
+ }
2435
+ /** What follows a prefix expression's base: `.name`, `[key]`, `:m(...)`,
2436
+ * a call, and their optional forms. */
2437
+ parseSuffixes(base) {
2412
2438
  while (true) {
2413
2439
  if (this.checkPunctuator("?") && this.touchesNext()) {
2414
2440
  const next = this.peek(1);
@@ -2562,8 +2588,8 @@ var Parser = class {
2562
2588
  /** An assignment target after the first — Luau's `a, b = ...`, read only
2563
2589
  * to be reported: a prefix expression or a destructuring pattern. */
2564
2590
  parseAssignTarget() {
2565
- if (this.checkPunctuator("{")) return this.parseObjectPattern();
2566
- if (this.checkPunctuator("[")) return this.parseArrayPattern();
2591
+ if (this.checkPunctuator("{")) return this.parseObjectPattern(true);
2592
+ if (this.checkPunctuator("[")) return this.parseArrayPattern(true);
2567
2593
  return this.parsePrefixExpression();
2568
2594
  }
2569
2595
  /** Does a call's argument list start here? Lua's three forms: `(`, a
@@ -2742,13 +2768,17 @@ var Parser = class {
2742
2768
  /** Parses a binding target. When `topLevel`, also consumes a trailing
2743
2769
  * `<attr>` list (identifier only) and a `: Type` annotation — these are
2744
2770
  * only valid at the outermost level of a `local` / parameter binding,
2745
- * never nested inside another pattern. */
2746
- parseBindingTarget(topLevel) {
2771
+ * never nested inside another pattern. When `assign`, the pattern is a
2772
+ * destructuring assignment's (`[t[i], t[j]] = ...`), and a leaf is any
2773
+ * place a value can be assigned to: a name, a member or an index. */
2774
+ parseBindingTarget(topLevel, assign = false) {
2747
2775
  let target;
2748
2776
  if (this.checkPunctuator("{")) {
2749
- target = this.parseObjectPattern();
2777
+ target = this.parseObjectPattern(assign);
2750
2778
  } else if (this.checkPunctuator("[")) {
2751
- target = this.parseArrayPattern();
2779
+ target = this.parseArrayPattern(assign);
2780
+ } else if (assign) {
2781
+ return this.parseAssignPatternLeaf();
2752
2782
  } else {
2753
2783
  const nameTok = this.expectIdentifier();
2754
2784
  let attributes;
@@ -2770,10 +2800,25 @@ var Parser = class {
2770
2800
  }
2771
2801
  return target;
2772
2802
  }
2773
- parseObjectPattern() {
2774
- return this.inBrackets(() => this.parseObjectPatternInner());
2803
+ /** A leaf of a destructuring assignment: `a`, `a.b` or `a[i]`. */
2804
+ parseAssignPatternLeaf() {
2805
+ const expression = this.parsePrefixExpression();
2806
+ if (expression.type === "Identifier") {
2807
+ return { type: "IdentifierPattern", name: expression.name, line: expression.line, column: expression.column };
2808
+ }
2809
+ if (expression.type === "MemberExpression" || expression.type === "IndexExpression") {
2810
+ this.rejectOptionalTarget(expression);
2811
+ return expression;
2812
+ }
2813
+ const err = new ParseError("Only a name, a member or an index can be assigned to", expression.line.start, expression.column.start);
2814
+ if (!this.recover) throw err;
2815
+ this.record(err);
2816
+ return { type: "IdentifierPattern", name: "", line: expression.line, column: expression.column };
2817
+ }
2818
+ parseObjectPattern(assign = false) {
2819
+ return this.inBrackets(() => this.parseObjectPatternInner(assign));
2775
2820
  }
2776
- parseObjectPatternInner() {
2821
+ parseObjectPatternInner(assign) {
2777
2822
  const start = this.current();
2778
2823
  this.expectPunctuator("{");
2779
2824
  const properties = [];
@@ -2781,7 +2826,7 @@ var Parser = class {
2781
2826
  while (!this.checkPunctuator("}")) {
2782
2827
  if (this.checkOperator("...")) {
2783
2828
  this.advance();
2784
- rest = this.parseBindingTarget(false);
2829
+ rest = this.parseBindingTarget(false, assign);
2785
2830
  break;
2786
2831
  }
2787
2832
  const propStart = this.current();
@@ -2794,17 +2839,17 @@ var Parser = class {
2794
2839
  key = this.parseExpression();
2795
2840
  this.expectPunctuator("]");
2796
2841
  this.expectPunctuator(":");
2797
- value = this.parseBindingTarget(false);
2842
+ value = this.parseBindingTarget(false, assign);
2798
2843
  } else if (this.checkType("Literal") && this.current().kind === "string") {
2799
2844
  const t = this.advance();
2800
2845
  key = { type: "StringLiteral", value: t.value, raw: t.raw, ...spanFrom(t, t) };
2801
2846
  this.expectPunctuator(":");
2802
- value = this.parseBindingTarget(false);
2847
+ value = this.parseBindingTarget(false, assign);
2803
2848
  } else {
2804
2849
  const nameTok = this.expectIdentifier();
2805
2850
  key = { type: "Identifier", name: nameTok.value, ...spanFrom(nameTok, nameTok) };
2806
2851
  if (this.matchPunctuator(":")) {
2807
- value = this.parseBindingTarget(false);
2852
+ value = this.parseBindingTarget(false, assign);
2808
2853
  } else {
2809
2854
  shorthand = true;
2810
2855
  value = { type: "IdentifierPattern", name: nameTok.value, ...spanFrom(nameTok, nameTok) };
@@ -2827,10 +2872,10 @@ var Parser = class {
2827
2872
  this.expectPunctuator("}");
2828
2873
  return { type: "ObjectPattern", properties, rest, ...spanFrom(start, this.previous()) };
2829
2874
  }
2830
- parseArrayPattern() {
2831
- return this.inBrackets(() => this.parseArrayPatternInner());
2875
+ parseArrayPattern(assign = false) {
2876
+ return this.inBrackets(() => this.parseArrayPatternInner(assign));
2832
2877
  }
2833
- parseArrayPatternInner() {
2878
+ parseArrayPatternInner(assign) {
2834
2879
  const start = this.current();
2835
2880
  this.expectPunctuator("[");
2836
2881
  const elements = [];
@@ -2838,7 +2883,7 @@ var Parser = class {
2838
2883
  while (!this.checkPunctuator("]")) {
2839
2884
  if (this.checkOperator("...")) {
2840
2885
  this.advance();
2841
- rest = this.parseBindingTarget(false);
2886
+ rest = this.parseBindingTarget(false, assign);
2842
2887
  break;
2843
2888
  }
2844
2889
  if (this.checkPunctuator(",")) {
@@ -2847,7 +2892,7 @@ var Parser = class {
2847
2892
  continue;
2848
2893
  }
2849
2894
  const elStart = this.current();
2850
- const value = this.parseBindingTarget(false);
2895
+ const value = this.parseBindingTarget(false, assign);
2851
2896
  let def;
2852
2897
  if (this.matchOperator("=")) def = this.parseExpression();
2853
2898
  elements.push({ type: "ArrayPatternElement", value, default: def, ...spanFrom(elStart, this.previous()) });
@@ -3984,6 +4029,10 @@ var Analyzer = class {
3984
4029
  this.checkConstAssign(id, t);
3985
4030
  return;
3986
4031
  }
4032
+ case "MemberExpression":
4033
+ case "IndexExpression":
4034
+ this.visitExpression(t, scope);
4035
+ return;
3987
4036
  case "ObjectPattern":
3988
4037
  for (const p of t.properties) {
3989
4038
  if (p.computed) this.visitExpression(p.key, scope);
@@ -4019,7 +4068,9 @@ var Analyzer = class {
4019
4068
  switch (stmt.type) {
4020
4069
  case "VariableDeclaration": {
4021
4070
  if (stmt.init) this.visitExpression(stmt.init, scope);
4022
- this.visitType(stmt.name.typeAnnotation, scope);
4071
+ if (stmt.name.type !== "MemberExpression" && stmt.name.type !== "IndexExpression") {
4072
+ this.visitType(stmt.name.typeAnnotation, scope);
4073
+ }
4023
4074
  this.declarePattern(scope, stmt.name, "local", scope, stmt.kind === "const");
4024
4075
  return;
4025
4076
  }
@@ -4134,6 +4185,11 @@ var Analyzer = class {
4134
4185
  case "DeclareClassStatement":
4135
4186
  this.visitType(stmt.body, scope);
4136
4187
  return;
4188
+ case "DeclareMetatableStatement":
4189
+ this.visitGenerics(stmt.generics, scope);
4190
+ this.visitType(stmt.target, scope);
4191
+ this.visitType(stmt.metatable, scope);
4192
+ return;
4137
4193
  case "TypeAliasStatement":
4138
4194
  case "ExportTypeAliasStatement":
4139
4195
  this.visitGenerics(
@@ -4399,6 +4455,101 @@ type Mutable<T> = { -readonly [K in keyof T]: T[K] }
4399
4455
  type Pick<T, K> = { [P in K]: T[P] }
4400
4456
  type Omit<T, K> = Pick<T, Exclude<keyof T, K>>
4401
4457
  type Record<K, V> = { [P in K]: V }
4458
+
4459
+ -- ============================================================
4460
+ -- The metatables of the language's own values
4461
+ -- ============================================================
4462
+
4463
+ -- A string's methods. The first group is Lua's string library, which a
4464
+ -- string really answers to; where Lua answers several values, tilua answers
4465
+ -- one, as the global functions do (\`find\` is an array, \`gsub\` the new
4466
+ -- string). The second group is written the way JavaScript writes it.
4467
+ --
4468
+ -- Positions are Lua's: \`sub\` and \`slice\` count from 1, and \`indexOf\` is
4469
+ -- \`nil\` when the text is not there.
4470
+ declare metatable string: {
4471
+ __index: {
4472
+ upper: (self: string) => string,
4473
+ lower: (self: string) => string,
4474
+ len: (self: string) => number,
4475
+ sub: (self: string, i: number, j?: number) => string,
4476
+ rep: (self: string, n: number, separator?: string) => string,
4477
+ reverse: (self: string) => string,
4478
+ format: (self: string, ...args: unknown[]) => string,
4479
+ byte: ((self: string, i?: number) => number) & ((self: string, i: number, j: number) => number[]),
4480
+ find: (self: string, pattern: string, init?: number, plain?: boolean) => [number, number, ...string[]] | nil,
4481
+ match: (self: string, pattern: string, init?: number) => [...string[]] | nil,
4482
+ gmatch: (self: string, pattern: string) => () => [string, ...string[]] | nil,
4483
+ gsub: (self: string, pattern: string, replacement: unknown, n?: number) => string,
4484
+
4485
+ trim: (self: string) => string,
4486
+ trimStart: (self: string) => string,
4487
+ trimEnd: (self: string) => string,
4488
+ startsWith: (self: string, text: string) => boolean,
4489
+ endsWith: (self: string, text: string) => boolean,
4490
+ includes: (self: string, text: string) => boolean,
4491
+ indexOf: (self: string, text: string, start?: number) => number | nil,
4492
+ slice: (self: string, start?: number, stop?: number) => string,
4493
+ replace: (self: string, text: string, replacement: string) => string,
4494
+ replaceAll: (self: string, text: string, replacement: string) => string,
4495
+ padStart: (self: string, length: number, padding?: string) => string,
4496
+ padEnd: (self: string, length: number, padding?: string) => string,
4497
+ },
4498
+ }
4499
+
4500
+ -- An array's methods. Lua gives an array no metatable: the compiler lowers
4501
+ -- each call to a plain function, so they work on any array, one a Lua
4502
+ -- library made included.
4503
+ --
4504
+ -- Indices are Lua's: the first element is 1, and \`indexOf\` / \`findIndex\`
4505
+ -- answer \`nil\` rather than JavaScript's -1. \`sort\` takes Lua's comparator
4506
+ -- (true when \`a\` comes first). \`push\`, \`pop\`, \`shift\`, \`unshift\`,
4507
+ -- \`sort\` and \`reverse\` change the array; everything else returns a new one.
4508
+ declare metatable<T> T[]: {
4509
+ __index: {
4510
+ find: (self: T[], test: (value: T, index: number) => boolean) => T | nil,
4511
+ findIndex: (self: T[], test: (value: T, index: number) => boolean) => number | nil,
4512
+ filter: (self: T[], test: (value: T, index: number) => boolean) => T[],
4513
+ map: <U>(self: T[], transform: (value: T, index: number) => U) => U[],
4514
+ forEach: (self: T[], visit: (value: T, index: number) => nil) => nil,
4515
+ some: (self: T[], test: (value: T, index: number) => boolean) => boolean,
4516
+ every: (self: T[], test: (value: T, index: number) => boolean) => boolean,
4517
+ reduce: <U>(self: T[], step: (total: U, value: T, index: number) => U, initial: U) => U,
4518
+ includes: (self: T[], value: T) => boolean,
4519
+ indexOf: (self: T[], value: T, start?: number) => number | nil,
4520
+ join: (self: T[], separator?: string) => string,
4521
+ concat: (self: T[], ...others: T[][]) => T[],
4522
+ slice: (self: T[], start?: number, stop?: number) => T[],
4523
+ flat: (self: T[]) => T[],
4524
+ reverse: (self: T[]) => T[],
4525
+ sort: (self: T[], compare?: (a: T, b: T) => boolean) => T[],
4526
+ push: (self: T[], ...args: T[]) => number,
4527
+ pop: (self: T[]) => T | nil,
4528
+ shift: (self: T[]) => T | nil,
4529
+ unshift: (self: T[], ...args: T[]) => number,
4530
+ },
4531
+ }
4532
+
4533
+ -- A table's methods. Unlike JavaScript's \`Object.keys\`, they answer a
4534
+ -- tuple, in the order the table's type wrote its members:
4535
+ --
4536
+ -- const point = { x: 1, y: "up" }
4537
+ -- point:keys() -- ["x", "y"]
4538
+ -- point:values() -- [number, string]
4539
+ -- point:entries() -- [["x", number], ["y", string]]
4540
+ --
4541
+ -- A Lua table has no order of its own, so the compiler hands its runtime the
4542
+ -- keys as the type lists them. An optional member keeps its place, holding
4543
+ -- \`nil\` when it is missing; an indexer's keys follow the named ones. A
4544
+ -- member of the table's own with one of these names is what a call reaches,
4545
+ -- and a class instance, which has a metatable of its own, has none of them.
4546
+ declare metatable<T extends {}> T: {
4547
+ __index: {
4548
+ keys: (self: T) => ObjectKeys<T>,
4549
+ values: (self: T) => ObjectValues<T>,
4550
+ entries: (self: T) => ObjectEntries<T>,
4551
+ },
4552
+ }
4402
4553
  `;
4403
4554
  var prelude;
4404
4555
  function preludeProgram() {
@@ -5764,6 +5915,10 @@ var TypeAnalyzer = class {
5764
5915
  typeOfTypeNode = /* @__PURE__ */ new Map();
5765
5916
  expectedTypeOf = /* @__PURE__ */ new Map();
5766
5917
  loops = /* @__PURE__ */ new Map();
5918
+ /** Every `declare metatable`, libraries first, in declaration order. */
5919
+ metatables = [];
5920
+ metatableCache = /* @__PURE__ */ new WeakMap();
5921
+ methodSources = /* @__PURE__ */ new Map();
5767
5922
  /** Public: each alias resolved once (generic aliases keep their params as
5768
5923
  * `typeParam` nodes in the body). */
5769
5924
  aliases = new LazyMap();
@@ -5830,6 +5985,9 @@ var TypeAnalyzer = class {
5830
5985
  this.registerNestedClasses();
5831
5986
  this.registerNestedAliases();
5832
5987
  for (const lib of this.options.libs ?? []) this.harvestDeclares(lib.body);
5988
+ this.harvestMetatables(preludeProgram().body, { origin: "language" });
5989
+ (this.options.libs ?? []).forEach((lib, i) => this.harvestMetatables(lib.body, { origin: "library", library: i }));
5990
+ this.harvestMetatables(this.program.body, { origin: "program" });
5833
5991
  this.registerImportedTypes();
5834
5992
  this.resolveAllAliases();
5835
5993
  this.harvestDeclares(this.program.body, true);
@@ -5861,6 +6019,8 @@ var TypeAnalyzer = class {
5861
6019
  expectedTypeOf: this.expectedTypeOf,
5862
6020
  aliases: this.resolveDeferredAliases(),
5863
6021
  loops: this.loops,
6022
+ methodSources: this.methodSources,
6023
+ metatableMembers: (type) => this.metatableMembers(type),
5864
6024
  diagnostics: this.diagnostics
5865
6025
  };
5866
6026
  }
@@ -5918,8 +6078,7 @@ var TypeAnalyzer = class {
5918
6078
  }
5919
6079
  /** `layering` is on for the prelude and for definitions files: a second
5920
6080
  * library that declares an alias already declared *adds* to it, the way a
5921
- * second `declare` of a table's name does, so `@tilua-types/roblox` can give
5922
- * `StringMethods` Luau's `split` without restating Lua's. The file being
6081
+ * second `declare` of a table's name does. The file being
5923
6082
  * analysed is not a layer: its own alias replaces what the libraries
5924
6083
  * gave, which is how a project opts out of a set. */
5925
6084
  registerAliasDefs(block, layering = false) {
@@ -6436,6 +6595,14 @@ var TypeAnalyzer = class {
6436
6595
  report(stmt.superclass, known ? `'${stmt.superclass.name}' is not a class; a class can only extend another class` : `Cannot find class '${stmt.superclass.name}'`);
6437
6596
  }
6438
6597
  }
6598
+ this.withClass(stmt, () => this.withTypeParams(this.classTypeParams(stmt), () => {
6599
+ this.withSelfType(this.selfTypeOf(stmt), () => {
6600
+ for (const member of stmt.members) {
6601
+ if (member.type !== "ClassMethod" || !member.signatures) continue;
6602
+ for (const signature of member.signatures) this.checkOverload(member.func, signature);
6603
+ }
6604
+ });
6605
+ }));
6439
6606
  for (const member of stmt.members) {
6440
6607
  if (member.type === "ClassConstructor") continue;
6441
6608
  if (CLASS_RESERVED.has(member.name.name)) {
@@ -6829,7 +6996,7 @@ var TypeAnalyzer = class {
6829
6996
  if (record.type === "TypeReference" && typeof record.base === "string") {
6830
6997
  const name = typeof record.namespace === "string" ? `${record.namespace}.${record.base}` : record.base;
6831
6998
  const resolved = this.typeOfTypeNode.get(node);
6832
- 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]);
6999
+ 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]);
6833
7000
  const at = node;
6834
7001
  const key = `${at.line.start}:${at.column.start}`;
6835
7002
  if (unresolved && !reported.has(key)) {
@@ -6947,7 +7114,7 @@ var TypeAnalyzer = class {
6947
7114
  const tp = this.lookupTypeParam(node.base);
6948
7115
  if (tp) return typeParam(tp.name, tp.constraint, tp.isConst);
6949
7116
  if (node.typeArguments.length === 1 && !this.aliasDefs.has(node.base)) {
6950
- const intrinsic = this.applyStringIntrinsic(
7117
+ const intrinsic = this.applyIntrinsic(
6951
7118
  node.base,
6952
7119
  this.resolveType(node.typeArguments[0])
6953
7120
  );
@@ -7154,7 +7321,7 @@ var TypeAnalyzer = class {
7154
7321
  return difference(this.reduceType(t.base), this.reduceType(t.excluded));
7155
7322
  case "genericRef": {
7156
7323
  if (t.typeArguments.length !== 1 || this.aliasDefs.has(t.name)) return t;
7157
- return this.applyStringIntrinsic(t.name, t.typeArguments[0]) ?? t;
7324
+ return this.applyIntrinsic(t.name, t.typeArguments[0]) ?? t;
7158
7325
  }
7159
7326
  case "conditional":
7160
7327
  return this.reduceConditional(t);
@@ -7267,6 +7434,41 @@ var TypeAnalyzer = class {
7267
7434
  }
7268
7435
  return union(combos.map((c) => literal(c)));
7269
7436
  }
7437
+ /** A type function the analyzer supplies, since tilua cannot write it. */
7438
+ applyIntrinsic(name, arg) {
7439
+ return this.applyStringIntrinsic(name, arg) ?? this.applyObjectIntrinsic(name, arg);
7440
+ }
7441
+ /** `ObjectKeys<T>`, `ObjectValues<T>`, `ObjectEntries<T>`: an object's
7442
+ * members as a tuple, in the order they were written — `{ a: 1, b: "x" }`
7443
+ * has keys `["a", "b"]`, values `[number, string]` and entries
7444
+ * `[["a", number], ["b", string]]`. TypeScript answers `string[]` for
7445
+ * `Object.keys`, since its objects have no order; tilua's lowering hands
7446
+ * the runtime the written order, so the tuple is what runs. An indexer's
7447
+ * keys follow the named ones, as the tuple's rest. */
7448
+ applyObjectIntrinsic(name, arg) {
7449
+ if (!OBJECT_INTRINSICS.has(name)) return void 0;
7450
+ const t = this.expand(this.reduceType(arg));
7451
+ if (containsTypeParam(t)) return void 0;
7452
+ if (t.kind === "union") return union(t.types.map((m) => this.applyObjectIntrinsic(name, m) ?? unknownType));
7453
+ const parts = t.kind === "intersection" ? t.types.map((m) => this.expand(m)) : [t];
7454
+ if (!parts.every((m) => m.kind === "object" && !m.class)) return unknownType;
7455
+ const properties = /* @__PURE__ */ new Map();
7456
+ let indexer;
7457
+ for (const part of parts) {
7458
+ for (const [key, property] of part.properties) properties.set(key, property);
7459
+ indexer ??= part.indexer;
7460
+ }
7461
+ const entries = [...properties].map(([key, p]) => [literal(key), p.optional ? optional(p.type) : p.type]);
7462
+ const listed = (elements, rest) => !elements.length && rest ? arrayOf(rest) : tuple(elements, rest);
7463
+ switch (name) {
7464
+ case "ObjectKeys":
7465
+ return listed(entries.map(([key]) => key), indexer?.key);
7466
+ case "ObjectValues":
7467
+ return listed(entries.map(([, value]) => value), indexer?.value);
7468
+ default:
7469
+ return listed(entries.map((entry) => tuple([...entry])), indexer && tuple([indexer.key, indexer.value]));
7470
+ }
7471
+ }
7270
7472
  /** TypeScript's four string intrinsics. They cannot be written in tilua —
7271
7473
  * there is no character-level type arithmetic — so the analyzer supplies
7272
7474
  * them, and only them, as named type functions. */
@@ -7942,7 +8144,7 @@ var TypeAnalyzer = class {
7942
8144
  /** Synthesize a type from a destructuring pattern used without an
7943
8145
  * annotation (`function f({ a, b = 1 })`). */
7944
8146
  patternToType(target, env) {
7945
- const leaf = (v, def) => v.type !== "IdentifierPattern" ? this.patternToType(v, env) : v.typeAnnotation ? this.resolveType(v.typeAnnotation) : def ? widen(this.infer(def, env)) : anyType;
8147
+ 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;
7946
8148
  if (target.type === "ObjectPattern") {
7947
8149
  const entries = [];
7948
8150
  for (const p of target.properties) {
@@ -8348,6 +8550,7 @@ var TypeAnalyzer = class {
8348
8550
  * can actually be. An annotation, a pattern or a default still wins. */
8349
8551
  paramsFromSignatures(func, signatures) {
8350
8552
  if (!signatures?.length) return;
8553
+ for (const signature of signatures) this.checkOverload(func, signature);
8351
8554
  const resolved = signatures.map((sig) => this.signatureToFnType(sig));
8352
8555
  func.params.forEach((param, i) => {
8353
8556
  if (param.typeAnnotation || param.pattern || param.default) return;
@@ -8361,6 +8564,64 @@ var TypeAnalyzer = class {
8361
8564
  if (candidates.length) this.contextualParams.set(param, union(candidates));
8362
8565
  });
8363
8566
  }
8567
+ /** The implementation is what runs for every signature of an overload
8568
+ * set, so each signature must be one it can serve: no parameter it does
8569
+ * not have, no fewer arguments than it requires, no argument type it does
8570
+ * not accept, and a return it can give. Stricter than TypeScript, which
8571
+ * lets a signature name parameters the implementation never reads. */
8572
+ checkOverload(func, signature) {
8573
+ if (!this.emitDiagnostics) return;
8574
+ const node = signature;
8575
+ const report = (message) => {
8576
+ this.diagnostics.push({ node, message });
8577
+ };
8578
+ const own = (params) => params.filter((p, i) => !p.rest && !(i === 0 && !p.typeAnnotation && (p.name === "self" || p.name === "this")));
8579
+ const implementation = own(func.params);
8580
+ const offered = own(signature.params);
8581
+ const implementationRest = func.params.some((p) => p.rest);
8582
+ const signatureRest = signature.params.some((p) => p.rest);
8583
+ const label = (p) => p.pattern ? "a destructured parameter" : `'${p.name}'`;
8584
+ if (!implementationRest) {
8585
+ const extra = offered[implementation.length];
8586
+ if (extra) {
8587
+ report(`This overload takes parameter ${label(extra)}, which the implementation does not have: it takes ${implementation.length} parameter${implementation.length === 1 ? "" : "s"}`);
8588
+ return;
8589
+ }
8590
+ if (signatureRest) {
8591
+ report("This overload takes a rest parameter, which the implementation does not have");
8592
+ return;
8593
+ }
8594
+ }
8595
+ if (!signatureRest) {
8596
+ let required = 0;
8597
+ implementation.forEach((p, i) => {
8598
+ if (!p.optional && p.default === void 0) required = i + 1;
8599
+ });
8600
+ if (offered.length < required) {
8601
+ report(`The implementation requires ${required} argument${required === 1 ? "" : "s"}, and this overload passes at most ${offered.length}`);
8602
+ return;
8603
+ }
8604
+ }
8605
+ if (signature.generics.length || func.generics.length) return;
8606
+ for (let i = 0; i < offered.length && i < implementation.length; i++) {
8607
+ const target = implementation[i];
8608
+ if (!target.typeAnnotation && target.default === void 0) continue;
8609
+ let accepted = this.paramType(target, /* @__PURE__ */ new Map());
8610
+ if (target.default !== void 0) accepted = optional(accepted);
8611
+ const given = this.paramType(offered[i], /* @__PURE__ */ new Map());
8612
+ if (!isAssignable(given, accepted)) {
8613
+ report(`Parameter ${label(offered[i])} of this overload is '${briefType(given)}', which the implementation's '${briefType(accepted)}' does not accept`);
8614
+ return;
8615
+ }
8616
+ }
8617
+ if (signature.returnType && func.returnType) {
8618
+ const promised = this.resolveType(signature.returnType);
8619
+ const returned = this.resolveType(func.returnType);
8620
+ if (!isAssignable(promised, returned) && !isAssignable(returned, promised)) {
8621
+ report(`This overload returns '${briefType(promised)}', which the implementation's '${briefType(returned)}' cannot be`);
8622
+ }
8623
+ }
8624
+ }
8364
8625
  signatureToFnType(sig) {
8365
8626
  const names = sig.generics.map((g) => g.name);
8366
8627
  const record = (type) => {
@@ -8785,6 +9046,12 @@ var TypeAnalyzer = class {
8785
9046
  }
8786
9047
  return;
8787
9048
  }
9049
+ case "MemberExpression":
9050
+ case "IndexExpression":
9051
+ this.infer(target, env);
9052
+ this.checkReadonlyAssign(target, env);
9053
+ this.assignToRef(target, widen(valueType), env);
9054
+ return;
8788
9055
  case "ObjectPattern": {
8789
9056
  for (const p of target.properties) {
8790
9057
  const key = !p.computed && p.key.type === "Identifier" ? p.key.name : !p.computed && p.key.type === "StringLiteral" ? p.key.value : void 0;
@@ -8883,28 +9150,122 @@ var TypeAnalyzer = class {
8883
9150
  this.resolvingAliases.delete(t.name);
8884
9151
  }
8885
9152
  }
8886
- /** `names:filter(f)`, `text:trim()` — the methods arrays and strings have.
8887
- * They are written in the prelude as `ArrayMethods<T>` and
8888
- * `StringMethods`, so a file (or a type library) that declares one of
8889
- * those names again replaces the whole set, and nothing here is a special
8890
- * case in the analyzer. The build lowers each call to a plain function. */
8891
- builtInMethod(t, name) {
8892
- const parts = (t.kind === "union" ? t.types : [t]).map((m) => this.expand(m));
8893
- const elements = parts.map((m) => m.kind === "array" ? m.element : m.kind === "tuple" ? union(m.elements) : void 0);
8894
- const element = elements.every((e) => e !== void 0) ? union(elements) : void 0;
8895
- const isString = (m) => m.kind === "primitive" && m.name === "string" || m.kind === "literal" && m.base === "string" || m.kind === "templateLiteral";
8896
- const methodTable = element !== void 0 ? "ArrayMethods" : parts.every(isString) ? "StringMethods" : void 0;
8897
- const def = methodTable === void 0 ? void 0 : this.aliasDefs.get(methodTable);
8898
- if (!def || def.class) return void 0;
8899
- const table = this.expand(this.instantiateAlias(def, element !== void 0 ? [element] : []));
8900
- const layers = table.kind === "intersection" ? table.types.map((m) => this.expand(m)) : [table];
8901
- for (let i = layers.length - 1; i >= 0; i--) {
8902
- const part = layers[i];
8903
- const property = part.kind === "object" ? part.properties.get(name) : void 0;
8904
- if (property) return property.type;
9153
+ // --------------------------------------------------------
9154
+ // Metatables
9155
+ // --------------------------------------------------------
9156
+ harvestMetatables(block, source) {
9157
+ for (const stmt of block.statements) {
9158
+ if (stmt.type === "DeclareMetatableStatement") this.metatables.push({ node: stmt, source });
9159
+ }
9160
+ }
9161
+ resolvedMetatable(entry) {
9162
+ entry.resolved ??= this.withTypeParams(entry.node.generics, () => ({
9163
+ target: this.resolveType(entry.node.target),
9164
+ metatable: this.resolveType(entry.node.metatable)
9165
+ }));
9166
+ return entry.resolved;
9167
+ }
9168
+ /** Does a value (`whole`, whose union members are `parts`) have the
9169
+ * metatable `entry` declares — and if so how specifically, and what its
9170
+ * type parameters stand for there. Three kinds of target:
9171
+ *
9172
+ * - `<T> T[]`: every array and tuple, `T` what they hold;
9173
+ * - `<T extends C> T`: every value that is a `C` other than a class
9174
+ * instance, which has a metatable of its own; `T` the value itself;
9175
+ * - a type with no parameters (`string`): every value that is one.
9176
+ *
9177
+ * A bare parameter matches the most, so it is the least specific: an
9178
+ * array that is also a `{}` takes an array's metatable. */
9179
+ matchMetatable(entry, whole, parts) {
9180
+ if (!parts.length || parts.some((m) => m.kind === "any" || m.kind === "unknown" || m.kind === "never")) return void 0;
9181
+ const { target } = this.resolvedMetatable(entry);
9182
+ if (target.kind === "array" && target.element.kind === "typeParam") {
9183
+ const elements = parts.map((m) => m.kind === "array" ? m.element : m.kind === "tuple" ? union(tupleMembers(m)) : void 0);
9184
+ if (!elements.every((e) => e !== void 0)) return void 0;
9185
+ return { specificity: 2, bindings: /* @__PURE__ */ new Map([[target.element.name, union(elements)]]) };
9186
+ }
9187
+ if (target.kind === "typeParam") {
9188
+ const constraint = target.constraint;
9189
+ if (parts.some((m) => m.kind === "object" && m.class)) return void 0;
9190
+ if (constraint && !parts.every((m) => isAssignable(m, constraint))) return void 0;
9191
+ return { specificity: 1, bindings: /* @__PURE__ */ new Map([[target.name, whole]]) };
9192
+ }
9193
+ if (containsTypeParam(target)) return void 0;
9194
+ return parts.every((m) => isAssignable(m, target)) ? { specificity: 2, bindings: /* @__PURE__ */ new Map() } : void 0;
9195
+ }
9196
+ /** The metatables `t` has: every declaration of the most specific target
9197
+ * it matches, instantiated for it, in declaration order — a later one
9198
+ * adds to an earlier, the way a library layers on another. A type
9199
+ * parameter is read at its constraint. */
9200
+ metatablesOf(t) {
9201
+ if (!this.metatables.length) return [];
9202
+ const cached = this.metatableCache.get(t);
9203
+ if (cached) return cached;
9204
+ const expanded = this.expand(t);
9205
+ 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);
9206
+ const whole = union(parts);
9207
+ const matches = this.metatables.flatMap((entry) => {
9208
+ const match = this.matchMetatable(entry, whole, parts);
9209
+ return match ? [{ entry, ...match }] : [];
9210
+ });
9211
+ const best = Math.max(0, ...matches.map((m) => m.specificity));
9212
+ const found = matches.filter((m) => m.specificity === best).map(({ entry, bindings }) => ({
9213
+ type: this.expand(this.reduceType(substitute(this.resolvedMetatable(entry).metatable, bindings))),
9214
+ source: entry.source
9215
+ }));
9216
+ this.metatableCache.set(t, found);
9217
+ return found;
9218
+ }
9219
+ /** The `__index` tables of `t`'s metatables, each with the library that
9220
+ * declared it, in declaration order. */
9221
+ metatableIndexes(t) {
9222
+ return this.metatablesOf(t).flatMap(({ type, source }) => {
9223
+ const parts = type.kind === "intersection" ? type.types.map((m) => this.expand(m)) : [type];
9224
+ return parts.flatMap((part) => {
9225
+ const index = part.kind === "object" ? part.properties.get("__index") : void 0;
9226
+ if (!index) return [];
9227
+ const table = this.expand(index.type);
9228
+ return (table.kind === "intersection" ? table.types.map((m) => this.expand(m)) : [table]).map((m) => ({ table: m, source }));
9229
+ });
9230
+ });
9231
+ }
9232
+ /** `name` read through `t`'s metatable: `text:upper()` is `upper` in the
9233
+ * string metatable's `__index`. The last declaration to give the name
9234
+ * wins. The language's own metatables are declared in the prelude, in
9235
+ * tilua, like any library's; a library adds to them the same way. */
9236
+ metatableMember(t, name) {
9237
+ const indexes = this.metatableIndexes(t);
9238
+ for (let i = indexes.length - 1; i >= 0; i--) {
9239
+ const { table, source } = indexes[i];
9240
+ const property = table.kind === "object" ? table.properties.get(name) : void 0;
9241
+ if (property) return { type: property.type, source };
8905
9242
  }
8906
9243
  return void 0;
8907
9244
  }
9245
+ metatableMethod(t, name) {
9246
+ return this.metatableMember(t, name)?.type;
9247
+ }
9248
+ hasMetatable(t) {
9249
+ return this.metatablesOf(t).length > 0;
9250
+ }
9251
+ metatableMembers(t) {
9252
+ const members = /* @__PURE__ */ new Map();
9253
+ for (const { table } of this.metatableIndexes(t)) {
9254
+ if (table.kind !== "object") continue;
9255
+ for (const [name, property] of table.properties) members.set(name, property.type);
9256
+ }
9257
+ return members;
9258
+ }
9259
+ /** `text:upper()`, `scores:keys()`: the method a value's metatable gives
9260
+ * it, when the value has no member of that name of its own. Called with
9261
+ * `:`, it answers before an object's indexer, since a metatable's
9262
+ * `__index` is only read for a key the table does not hold. */
9263
+ receiverMetatableMember(objType, name) {
9264
+ const t = this.expand(objType);
9265
+ const parts = (t.kind === "union" ? t.types : [t]).map((m) => this.expand(m));
9266
+ if (parts.some((m) => m.kind === "object" && m.properties.has(name))) return void 0;
9267
+ return this.metatableMember(t, name);
9268
+ }
8908
9269
  /** The most a deferred type could turn out to be. A conditional is one of
8909
9270
  * its branches, and the true branch stands for a member of what was
8910
9271
  * tested (`T` in `T extends U ? T : never`); an indexed access reads
@@ -8970,7 +9331,7 @@ var TypeAnalyzer = class {
8970
9331
  if (index.kind !== "literal" || typeof index.value !== "string") return void 0;
8971
9332
  const parts = this.stringParts(object);
8972
9333
  if (!parts) return void 0;
8973
- const found = parts.map((part) => this.builtInMethod(part, index.value));
9334
+ const found = parts.map((part) => this.metatableMethod(part, index.value));
8974
9335
  return found.every((t) => t !== void 0) ? union(found) : void 0;
8975
9336
  }
8976
9337
  /** The members of `t` when every one of them is a string — `"a" | "b"` is
@@ -8991,13 +9352,13 @@ var TypeAnalyzer = class {
8991
9352
  * not one of them is a mistake worth reporting, rather than the nil Lua
8992
9353
  * would hand back. */
8993
9354
  checkStringMember(node, object, key) {
8994
- if (!this.emitDiagnostics || !this.aliasDefs.has("StringMethods")) return;
9355
+ if (!this.emitDiagnostics || !this.hasMetatable(stringType)) return;
8995
9356
  const parts = this.stringParts(object);
8996
9357
  if (!parts) return;
8997
9358
  const keys = (key.kind === "union" ? key.types : [key]).map((m) => this.expand(m));
8998
9359
  if (!keys.length || !keys.every((m) => m.kind === "literal" && typeof m.value === "string")) return;
8999
9360
  const names = keys.map((m) => String(m.value));
9000
- if (names.some((name) => parts.some((part) => this.builtInMethod(part, name)))) return;
9361
+ if (names.some((name) => parts.some((part) => this.metatableMethod(part, name)))) return;
9001
9362
  this.diagnostics.push({
9002
9363
  node,
9003
9364
  message: names.length === 1 ? `'${names[0]}' does not exist on a string` : `'${briefType(key)}' does not name a member of a string`
@@ -9029,24 +9390,24 @@ var TypeAnalyzer = class {
9029
9390
  switch (part.kind) {
9030
9391
  case "primitive":
9031
9392
  if (part.name === "nil") continue;
9032
- if (part.name === "string" && !this.aliasDefs.has("StringMethods")) return false;
9033
- if (part.name === "string" && this.builtInMethod(part, name)) continue;
9393
+ if (part.name === "string" && !this.hasMetatable(part)) return false;
9394
+ if (part.name === "string" && this.metatableMethod(part, name)) continue;
9034
9395
  break;
9035
9396
  case "literal":
9036
9397
  case "templateLiteral":
9037
9398
  if (part.kind === "templateLiteral" || part.base === "string") {
9038
- if (!this.aliasDefs.has("StringMethods")) return false;
9399
+ if (!this.hasMetatable(part)) return false;
9039
9400
  }
9040
- if (this.builtInMethod(part, name)) continue;
9401
+ if (this.metatableMethod(part, name)) continue;
9041
9402
  break;
9042
9403
  case "array":
9043
9404
  case "tuple":
9044
- if (!this.aliasDefs.has("ArrayMethods")) return false;
9045
- if (this.builtInMethod(part, name)) continue;
9405
+ if (!this.hasMetatable(part)) return false;
9406
+ if (this.metatableMethod(part, name)) continue;
9046
9407
  break;
9047
9408
  case "object":
9048
9409
  if (part.properties.has(name)) continue;
9049
- if (part.indexer || part.properties.size === 0 && !part.class || this.builtInMethod(part, name)) return false;
9410
+ if (part.indexer || part.properties.size === 0 && !part.class || this.metatableMethod(part, name)) return false;
9050
9411
  break;
9051
9412
  default:
9052
9413
  return false;
@@ -9181,10 +9542,10 @@ var TypeAnalyzer = class {
9181
9542
  if (p) return p.optional ? optional(p.type) : p.type;
9182
9543
  if (t.indexer) return t.indexer.value;
9183
9544
  }
9184
- const built = this.builtInMethod(t, name);
9545
+ const built = this.metatableMethod(t, name);
9185
9546
  if (built) return built;
9186
9547
  if (t.kind === "union") {
9187
- const built2 = this.builtInMethod(t, name);
9548
+ const built2 = this.metatableMethod(t, name);
9188
9549
  if (built2) return built2;
9189
9550
  return union(t.types.map((m) => this.propertyType(m, name)));
9190
9551
  }
@@ -9585,7 +9946,9 @@ var TypeAnalyzer = class {
9585
9946
  this.checkPrivateMember(expr.method, objType, expr.method.name);
9586
9947
  this.checkStringMember(expr.method, objType, literal(expr.method.name));
9587
9948
  this.checkMissingMember(expr.method, objType, expr.method.name);
9588
- const method = this.propertyType(objType, expr.method.name);
9949
+ const fromMetatable = this.receiverMetatableMember(objType, expr.method.name);
9950
+ if (fromMetatable) this.methodSources.set(expr, fromMetatable.source);
9951
+ const method = fromMetatable?.type ?? this.propertyType(objType, expr.method.name);
9589
9952
  const united = this.unionSignatures(method);
9590
9953
  const fns = united ?? this.overloadsOf(method);
9591
9954
  const explicit = this.explicitTypeArguments(expr, fns);
@@ -10827,6 +11190,8 @@ function containsTypeQuery(node) {
10827
11190
  return found;
10828
11191
  }
10829
11192
  var STRING_INTRINSICS = /* @__PURE__ */ new Set(["Uppercase", "Lowercase", "Capitalize", "Uncapitalize"]);
11193
+ var OBJECT_INTRINSICS = /* @__PURE__ */ new Set(["ObjectKeys", "ObjectValues", "ObjectEntries"]);
11194
+ var INTRINSICS = /* @__PURE__ */ new Set([...STRING_INTRINSICS, ...OBJECT_INTRINSICS]);
10830
11195
  function briefType(t) {
10831
11196
  if (t.kind === "union" && t.types.length > 8) {
10832
11197
  const shown = t.types.slice(0, 6).map(formatType).join(" | ");
@@ -11034,7 +11399,8 @@ function resolveTypeLibraries(config, host = nodeHost) {
11034
11399
  }
11035
11400
  addFile(entryFile);
11036
11401
  const lowering = loweringModule(directory, host, problems, config);
11037
- if (lowering) lowerings.push(lowering);
11402
+ const library = files.findIndex((file) => pathKey(file) === pathKey(entryFile));
11403
+ if (lowering) lowerings.push({ ...lowering, library });
11038
11404
  };
11039
11405
  for (const entry of config.types) {
11040
11406
  const relative = entry.startsWith("./") || entry.startsWith("../") || entry.startsWith("/") || /^[A-Za-z]:[\\/]/.test(entry);