@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/README.md +45 -23
- package/dist/index.cjs +312 -44
- package/dist/index.d.cts +70 -22
- package/dist/index.d.ts +70 -22
- package/dist/index.js +312 -44
- package/package.json +1 -1
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
|
-
|
|
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
|
|
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 && !
|
|
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.
|
|
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.
|
|
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
|
-
|
|
8985
|
-
|
|
8986
|
-
|
|
8987
|
-
|
|
8988
|
-
|
|
8989
|
-
|
|
8990
|
-
|
|
8991
|
-
|
|
8992
|
-
|
|
8993
|
-
|
|
8994
|
-
|
|
8995
|
-
|
|
8996
|
-
|
|
8997
|
-
|
|
8998
|
-
|
|
8999
|
-
|
|
9000
|
-
|
|
9001
|
-
|
|
9002
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
9131
|
-
if (part.name === "string" && this.
|
|
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.
|
|
9399
|
+
if (!this.hasMetatable(part)) return false;
|
|
9137
9400
|
}
|
|
9138
|
-
if (this.
|
|
9401
|
+
if (this.metatableMethod(part, name)) continue;
|
|
9139
9402
|
break;
|
|
9140
9403
|
case "array":
|
|
9141
9404
|
case "tuple":
|
|
9142
|
-
if (!this.
|
|
9143
|
-
if (this.
|
|
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.
|
|
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.
|
|
9545
|
+
const built = this.metatableMethod(t, name);
|
|
9283
9546
|
if (built) return built;
|
|
9284
9547
|
if (t.kind === "union") {
|
|
9285
|
-
const built2 = this.
|
|
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
|
|
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
|
-
|
|
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);
|