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