@tilua/parser 2.4.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,6 +2022,14 @@ var Parser = class {
2007
2022
  parseExpressionStatement() {
2008
2023
  const start = this.current();
2009
2024
  if (this.checkPunctuator("{") || this.checkPunctuator("[")) {
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()) };
2010
2033
  const target = this.checkPunctuator("{") ? this.parseObjectPattern(true) : this.parseArrayPattern(true);
2011
2034
  return this.parseAssignmentRest(start, target);
2012
2035
  }
@@ -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);
@@ -4159,6 +4185,11 @@ var Analyzer = class {
4159
4185
  case "DeclareClassStatement":
4160
4186
  this.visitType(stmt.body, scope);
4161
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;
4162
4193
  case "TypeAliasStatement":
4163
4194
  case "ExportTypeAliasStatement":
4164
4195
  this.visitGenerics(
@@ -4424,6 +4455,101 @@ type Mutable<T> = { -readonly [K in keyof T]: T[K] }
4424
4455
  type Pick<T, K> = { [P in K]: T[P] }
4425
4456
  type Omit<T, K> = Pick<T, Exclude<keyof T, K>>
4426
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
+ }
4427
4553
  `;
4428
4554
  var prelude;
4429
4555
  function preludeProgram() {
@@ -5789,6 +5915,10 @@ var TypeAnalyzer = class {
5789
5915
  typeOfTypeNode = /* @__PURE__ */ new Map();
5790
5916
  expectedTypeOf = /* @__PURE__ */ new Map();
5791
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();
5792
5922
  /** Public: each alias resolved once (generic aliases keep their params as
5793
5923
  * `typeParam` nodes in the body). */
5794
5924
  aliases = new LazyMap();
@@ -5855,6 +5985,9 @@ var TypeAnalyzer = class {
5855
5985
  this.registerNestedClasses();
5856
5986
  this.registerNestedAliases();
5857
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" });
5858
5991
  this.registerImportedTypes();
5859
5992
  this.resolveAllAliases();
5860
5993
  this.harvestDeclares(this.program.body, true);
@@ -5886,6 +6019,8 @@ var TypeAnalyzer = class {
5886
6019
  expectedTypeOf: this.expectedTypeOf,
5887
6020
  aliases: this.resolveDeferredAliases(),
5888
6021
  loops: this.loops,
6022
+ methodSources: this.methodSources,
6023
+ metatableMembers: (type) => this.metatableMembers(type),
5889
6024
  diagnostics: this.diagnostics
5890
6025
  };
5891
6026
  }
@@ -5943,8 +6078,7 @@ var TypeAnalyzer = class {
5943
6078
  }
5944
6079
  /** `layering` is on for the prelude and for definitions files: a second
5945
6080
  * library that declares an alias already declared *adds* to it, the way a
5946
- * second `declare` of a table's name does, so `@tilua-types/roblox` can give
5947
- * `StringMethods` Luau's `split` without restating Lua's. The file being
6081
+ * second `declare` of a table's name does. The file being
5948
6082
  * analysed is not a layer: its own alias replaces what the libraries
5949
6083
  * gave, which is how a project opts out of a set. */
5950
6084
  registerAliasDefs(block, layering = false) {
@@ -6862,7 +6996,7 @@ var TypeAnalyzer = class {
6862
6996
  if (record.type === "TypeReference" && typeof record.base === "string") {
6863
6997
  const name = typeof record.namespace === "string" ? `${record.namespace}.${record.base}` : record.base;
6864
6998
  const resolved = this.typeOfTypeNode.get(node);
6865
- 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]);
6866
7000
  const at = node;
6867
7001
  const key = `${at.line.start}:${at.column.start}`;
6868
7002
  if (unresolved && !reported.has(key)) {
@@ -6980,7 +7114,7 @@ var TypeAnalyzer = class {
6980
7114
  const tp = this.lookupTypeParam(node.base);
6981
7115
  if (tp) return typeParam(tp.name, tp.constraint, tp.isConst);
6982
7116
  if (node.typeArguments.length === 1 && !this.aliasDefs.has(node.base)) {
6983
- const intrinsic = this.applyStringIntrinsic(
7117
+ const intrinsic = this.applyIntrinsic(
6984
7118
  node.base,
6985
7119
  this.resolveType(node.typeArguments[0])
6986
7120
  );
@@ -7187,7 +7321,7 @@ var TypeAnalyzer = class {
7187
7321
  return difference(this.reduceType(t.base), this.reduceType(t.excluded));
7188
7322
  case "genericRef": {
7189
7323
  if (t.typeArguments.length !== 1 || this.aliasDefs.has(t.name)) return t;
7190
- return this.applyStringIntrinsic(t.name, t.typeArguments[0]) ?? t;
7324
+ return this.applyIntrinsic(t.name, t.typeArguments[0]) ?? t;
7191
7325
  }
7192
7326
  case "conditional":
7193
7327
  return this.reduceConditional(t);
@@ -7300,6 +7434,41 @@ var TypeAnalyzer = class {
7300
7434
  }
7301
7435
  return union(combos.map((c) => literal(c)));
7302
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
+ }
7303
7472
  /** TypeScript's four string intrinsics. They cannot be written in tilua —
7304
7473
  * there is no character-level type arithmetic — so the analyzer supplies
7305
7474
  * them, and only them, as named type functions. */
@@ -8981,28 +9150,122 @@ var TypeAnalyzer = class {
8981
9150
  this.resolvingAliases.delete(t.name);
8982
9151
  }
8983
9152
  }
8984
- /** `names:filter(f)`, `text:trim()` — the methods arrays and strings have.
8985
- * They are written in the prelude as `ArrayMethods<T>` and
8986
- * `StringMethods`, so a file (or a type library) that declares one of
8987
- * those names again replaces the whole set, and nothing here is a special
8988
- * case in the analyzer. The build lowers each call to a plain function. */
8989
- builtInMethod(t, name) {
8990
- const parts = (t.kind === "union" ? t.types : [t]).map((m) => this.expand(m));
8991
- const elements = parts.map((m) => m.kind === "array" ? m.element : m.kind === "tuple" ? union(m.elements) : void 0);
8992
- const element = elements.every((e) => e !== void 0) ? union(elements) : void 0;
8993
- const isString = (m) => m.kind === "primitive" && m.name === "string" || m.kind === "literal" && m.base === "string" || m.kind === "templateLiteral";
8994
- const methodTable = element !== void 0 ? "ArrayMethods" : parts.every(isString) ? "StringMethods" : void 0;
8995
- const def = methodTable === void 0 ? void 0 : this.aliasDefs.get(methodTable);
8996
- if (!def || def.class) return void 0;
8997
- const table = this.expand(this.instantiateAlias(def, element !== void 0 ? [element] : []));
8998
- const layers = table.kind === "intersection" ? table.types.map((m) => this.expand(m)) : [table];
8999
- for (let i = layers.length - 1; i >= 0; i--) {
9000
- const part = layers[i];
9001
- const property = part.kind === "object" ? part.properties.get(name) : void 0;
9002
- 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 };
9003
9242
  }
9004
9243
  return void 0;
9005
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
+ }
9006
9269
  /** The most a deferred type could turn out to be. A conditional is one of
9007
9270
  * its branches, and the true branch stands for a member of what was
9008
9271
  * tested (`T` in `T extends U ? T : never`); an indexed access reads
@@ -9068,7 +9331,7 @@ var TypeAnalyzer = class {
9068
9331
  if (index.kind !== "literal" || typeof index.value !== "string") return void 0;
9069
9332
  const parts = this.stringParts(object);
9070
9333
  if (!parts) return void 0;
9071
- const found = parts.map((part) => this.builtInMethod(part, index.value));
9334
+ const found = parts.map((part) => this.metatableMethod(part, index.value));
9072
9335
  return found.every((t) => t !== void 0) ? union(found) : void 0;
9073
9336
  }
9074
9337
  /** The members of `t` when every one of them is a string — `"a" | "b"` is
@@ -9089,13 +9352,13 @@ var TypeAnalyzer = class {
9089
9352
  * not one of them is a mistake worth reporting, rather than the nil Lua
9090
9353
  * would hand back. */
9091
9354
  checkStringMember(node, object, key) {
9092
- if (!this.emitDiagnostics || !this.aliasDefs.has("StringMethods")) return;
9355
+ if (!this.emitDiagnostics || !this.hasMetatable(stringType)) return;
9093
9356
  const parts = this.stringParts(object);
9094
9357
  if (!parts) return;
9095
9358
  const keys = (key.kind === "union" ? key.types : [key]).map((m) => this.expand(m));
9096
9359
  if (!keys.length || !keys.every((m) => m.kind === "literal" && typeof m.value === "string")) return;
9097
9360
  const names = keys.map((m) => String(m.value));
9098
- 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;
9099
9362
  this.diagnostics.push({
9100
9363
  node,
9101
9364
  message: names.length === 1 ? `'${names[0]}' does not exist on a string` : `'${briefType(key)}' does not name a member of a string`
@@ -9127,24 +9390,24 @@ var TypeAnalyzer = class {
9127
9390
  switch (part.kind) {
9128
9391
  case "primitive":
9129
9392
  if (part.name === "nil") continue;
9130
- if (part.name === "string" && !this.aliasDefs.has("StringMethods")) return false;
9131
- 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;
9132
9395
  break;
9133
9396
  case "literal":
9134
9397
  case "templateLiteral":
9135
9398
  if (part.kind === "templateLiteral" || part.base === "string") {
9136
- if (!this.aliasDefs.has("StringMethods")) return false;
9399
+ if (!this.hasMetatable(part)) return false;
9137
9400
  }
9138
- if (this.builtInMethod(part, name)) continue;
9401
+ if (this.metatableMethod(part, name)) continue;
9139
9402
  break;
9140
9403
  case "array":
9141
9404
  case "tuple":
9142
- if (!this.aliasDefs.has("ArrayMethods")) return false;
9143
- if (this.builtInMethod(part, name)) continue;
9405
+ if (!this.hasMetatable(part)) return false;
9406
+ if (this.metatableMethod(part, name)) continue;
9144
9407
  break;
9145
9408
  case "object":
9146
9409
  if (part.properties.has(name)) continue;
9147
- 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;
9148
9411
  break;
9149
9412
  default:
9150
9413
  return false;
@@ -9279,10 +9542,10 @@ var TypeAnalyzer = class {
9279
9542
  if (p) return p.optional ? optional(p.type) : p.type;
9280
9543
  if (t.indexer) return t.indexer.value;
9281
9544
  }
9282
- const built = this.builtInMethod(t, name);
9545
+ const built = this.metatableMethod(t, name);
9283
9546
  if (built) return built;
9284
9547
  if (t.kind === "union") {
9285
- const built2 = this.builtInMethod(t, name);
9548
+ const built2 = this.metatableMethod(t, name);
9286
9549
  if (built2) return built2;
9287
9550
  return union(t.types.map((m) => this.propertyType(m, name)));
9288
9551
  }
@@ -9683,7 +9946,9 @@ var TypeAnalyzer = class {
9683
9946
  this.checkPrivateMember(expr.method, objType, expr.method.name);
9684
9947
  this.checkStringMember(expr.method, objType, literal(expr.method.name));
9685
9948
  this.checkMissingMember(expr.method, objType, expr.method.name);
9686
- 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);
9687
9952
  const united = this.unionSignatures(method);
9688
9953
  const fns = united ?? this.overloadsOf(method);
9689
9954
  const explicit = this.explicitTypeArguments(expr, fns);
@@ -10925,6 +11190,8 @@ function containsTypeQuery(node) {
10925
11190
  return found;
10926
11191
  }
10927
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]);
10928
11195
  function briefType(t) {
10929
11196
  if (t.kind === "union" && t.types.length > 8) {
10930
11197
  const shown = t.types.slice(0, 6).map(formatType).join(" | ");
@@ -11132,7 +11399,8 @@ function resolveTypeLibraries(config, host = nodeHost) {
11132
11399
  }
11133
11400
  addFile(entryFile);
11134
11401
  const lowering = loweringModule(directory, host, problems, config);
11135
- if (lowering) lowerings.push(lowering);
11402
+ const library = files.findIndex((file) => pathKey(file) === pathKey(entryFile));
11403
+ if (lowering) lowerings.push({ ...lowering, library });
11136
11404
  };
11137
11405
  for (const entry of config.types) {
11138
11406
  const relative = entry.startsWith("./") || entry.startsWith("../") || entry.startsWith("/") || /^[A-Za-z]:[\\/]/.test(entry);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tilua/parser",
3
- "version": "2.4.0",
3
+ "version": "3.0.0",
4
4
  "description": "tilua parser for roblox",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.js",