@tilua/parser 2.4.0 → 3.0.1
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 +529 -66
- package/dist/index.d.cts +71 -23
- package/dist/index.d.ts +71 -23
- package/dist/index.js +529 -66
- 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);
|
|
@@ -3762,6 +3788,10 @@ var Analyzer = class {
|
|
|
3762
3788
|
moduleScope = this.globalScope;
|
|
3763
3789
|
/** How many function bodies enclose the walk. */
|
|
3764
3790
|
functionDepth = 0;
|
|
3791
|
+
/** How many loops enclose the walk inside the nearest function body. A
|
|
3792
|
+
* function starts again at 0: `continue` in `xs:map(x => ...)` has no
|
|
3793
|
+
* loop to go on with, whatever loop the call is written in. */
|
|
3794
|
+
loopDepth = 0;
|
|
3765
3795
|
/** Function declarations already declared by their block's hoisting, with
|
|
3766
3796
|
* the function depth of that block. */
|
|
3767
3797
|
hoisted = /* @__PURE__ */ new Map();
|
|
@@ -4114,11 +4144,11 @@ var Analyzer = class {
|
|
|
4114
4144
|
return;
|
|
4115
4145
|
case "WhileStatement":
|
|
4116
4146
|
this.visitExpression(stmt.condition, scope);
|
|
4117
|
-
this.visitBlockInNewScope(stmt.body, scope);
|
|
4147
|
+
this.inLoop(() => this.visitBlockInNewScope(stmt.body, scope));
|
|
4118
4148
|
return;
|
|
4119
4149
|
case "RepeatStatement": {
|
|
4120
4150
|
const bodyScope = childScope(scope);
|
|
4121
|
-
this.visitBlock(stmt.body, bodyScope);
|
|
4151
|
+
this.inLoop(() => this.visitBlock(stmt.body, bodyScope));
|
|
4122
4152
|
this.visitExpression(stmt.condition, bodyScope);
|
|
4123
4153
|
return;
|
|
4124
4154
|
}
|
|
@@ -4136,14 +4166,14 @@ var Analyzer = class {
|
|
|
4136
4166
|
if (stmt.step) this.visitExpression(stmt.step, scope);
|
|
4137
4167
|
const bodyScope = childScope(scope);
|
|
4138
4168
|
this.declare(bodyScope, stmt.variable.name, "for-numeric", stmt.variable);
|
|
4139
|
-
this.visitBlock(stmt.body, bodyScope);
|
|
4169
|
+
this.inLoop(() => this.visitBlock(stmt.body, bodyScope));
|
|
4140
4170
|
return;
|
|
4141
4171
|
}
|
|
4142
4172
|
case "GenericForStatement": {
|
|
4143
4173
|
this.visitExpression(stmt.iterator, scope);
|
|
4144
4174
|
const bodyScope = childScope(scope);
|
|
4145
4175
|
this.declarePattern(bodyScope, stmt.variable, "for-generic", scope, stmt.kind === "const");
|
|
4146
|
-
this.visitBlock(stmt.body, bodyScope);
|
|
4176
|
+
this.inLoop(() => this.visitBlock(stmt.body, bodyScope));
|
|
4147
4177
|
return;
|
|
4148
4178
|
}
|
|
4149
4179
|
case "ReturnStatement":
|
|
@@ -4151,6 +4181,15 @@ var Analyzer = class {
|
|
|
4151
4181
|
return;
|
|
4152
4182
|
case "BreakStatement":
|
|
4153
4183
|
case "ContinueStatement":
|
|
4184
|
+
if (this.loopDepth === 0) {
|
|
4185
|
+
const word = stmt.type === "BreakStatement" ? "break" : "continue";
|
|
4186
|
+
this.diagnostics.push({
|
|
4187
|
+
node: stmt,
|
|
4188
|
+
message: this.functionDepth > 0 ? `'${word}' is not inside a loop of this function; to skip a value in a callback, 'return' from it` : `'${word}' is not inside a loop`,
|
|
4189
|
+
kind: "outside-loop"
|
|
4190
|
+
});
|
|
4191
|
+
}
|
|
4192
|
+
return;
|
|
4154
4193
|
case "ErrorStatement":
|
|
4155
4194
|
return;
|
|
4156
4195
|
case "DeclareStatement":
|
|
@@ -4159,6 +4198,11 @@ var Analyzer = class {
|
|
|
4159
4198
|
case "DeclareClassStatement":
|
|
4160
4199
|
this.visitType(stmt.body, scope);
|
|
4161
4200
|
return;
|
|
4201
|
+
case "DeclareMetatableStatement":
|
|
4202
|
+
this.visitGenerics(stmt.generics, scope);
|
|
4203
|
+
this.visitType(stmt.target, scope);
|
|
4204
|
+
this.visitType(stmt.metatable, scope);
|
|
4205
|
+
return;
|
|
4162
4206
|
case "TypeAliasStatement":
|
|
4163
4207
|
case "ExportTypeAliasStatement":
|
|
4164
4208
|
this.visitGenerics(
|
|
@@ -4201,6 +4245,15 @@ var Analyzer = class {
|
|
|
4201
4245
|
return;
|
|
4202
4246
|
}
|
|
4203
4247
|
}
|
|
4248
|
+
/** Visit a loop's body, where `break` and `continue` have a loop. */
|
|
4249
|
+
inLoop(visit) {
|
|
4250
|
+
this.loopDepth++;
|
|
4251
|
+
try {
|
|
4252
|
+
visit();
|
|
4253
|
+
} finally {
|
|
4254
|
+
this.loopDepth--;
|
|
4255
|
+
}
|
|
4256
|
+
}
|
|
4204
4257
|
// ---------------- functions ----------------
|
|
4205
4258
|
visitFunctionBody(func, outerScope, isMethod = false) {
|
|
4206
4259
|
const fnScope = childScope(outerScope);
|
|
@@ -4216,11 +4269,14 @@ var Analyzer = class {
|
|
|
4216
4269
|
}
|
|
4217
4270
|
});
|
|
4218
4271
|
this.visitType(func.returnType, fnScope);
|
|
4272
|
+
const loops = this.loopDepth;
|
|
4219
4273
|
this.functionDepth++;
|
|
4274
|
+
this.loopDepth = 0;
|
|
4220
4275
|
try {
|
|
4221
4276
|
this.visitBlock(func.body, fnScope);
|
|
4222
4277
|
} finally {
|
|
4223
4278
|
this.functionDepth--;
|
|
4279
|
+
this.loopDepth = loops;
|
|
4224
4280
|
}
|
|
4225
4281
|
}
|
|
4226
4282
|
/** An overload signature: no body and no bindings, but its types can hold
|
|
@@ -4424,6 +4480,104 @@ type Mutable<T> = { -readonly [K in keyof T]: T[K] }
|
|
|
4424
4480
|
type Pick<T, K> = { [P in K]: T[P] }
|
|
4425
4481
|
type Omit<T, K> = Pick<T, Exclude<keyof T, K>>
|
|
4426
4482
|
type Record<K, V> = { [P in K]: V }
|
|
4483
|
+
|
|
4484
|
+
-- ============================================================
|
|
4485
|
+
-- The metatables of the language's own values
|
|
4486
|
+
-- ============================================================
|
|
4487
|
+
|
|
4488
|
+
-- A string's methods. The first group is Lua's string library, which a
|
|
4489
|
+
-- string really answers to; where Lua answers several values, tilua answers
|
|
4490
|
+
-- one, as the global functions do (\`find\` is an array, \`gsub\` the new
|
|
4491
|
+
-- string). The second group is written the way JavaScript writes it.
|
|
4492
|
+
--
|
|
4493
|
+
-- Positions are Lua's: \`sub\` and \`slice\` count from 1, and \`indexOf\` is
|
|
4494
|
+
-- \`nil\` when the text is not there.
|
|
4495
|
+
declare metatable string: {
|
|
4496
|
+
__index: {
|
|
4497
|
+
upper: (self: string) => string,
|
|
4498
|
+
lower: (self: string) => string,
|
|
4499
|
+
len: (self: string) => number,
|
|
4500
|
+
sub: (self: string, i: number, j?: number) => string,
|
|
4501
|
+
rep: (self: string, n: number, separator?: string) => string,
|
|
4502
|
+
reverse: (self: string) => string,
|
|
4503
|
+
format: (self: string, ...args: unknown[]) => string,
|
|
4504
|
+
byte: ((self: string, i?: number) => number) & ((self: string, i: number, j: number) => number[]),
|
|
4505
|
+
find: (self: string, pattern: string, init?: number, plain?: boolean) => [number, number, ...string[]] | nil,
|
|
4506
|
+
match: (self: string, pattern: string, init?: number) => [...string[]] | nil,
|
|
4507
|
+
gmatch: (self: string, pattern: string) => () => [string, ...string[]] | nil,
|
|
4508
|
+
gsub: (self: string, pattern: string, replacement: unknown, n?: number) => string,
|
|
4509
|
+
|
|
4510
|
+
trim: (self: string) => string,
|
|
4511
|
+
trimStart: (self: string) => string,
|
|
4512
|
+
trimEnd: (self: string) => string,
|
|
4513
|
+
startsWith: (self: string, text: string) => boolean,
|
|
4514
|
+
endsWith: (self: string, text: string) => boolean,
|
|
4515
|
+
includes: (self: string, text: string) => boolean,
|
|
4516
|
+
indexOf: (self: string, text: string, start?: number) => number | nil,
|
|
4517
|
+
slice: (self: string, start?: number, stop?: number) => string,
|
|
4518
|
+
replace: (self: string, text: string, replacement: string) => string,
|
|
4519
|
+
replaceAll: (self: string, text: string, replacement: string) => string,
|
|
4520
|
+
padStart: (self: string, length: number, padding?: string) => string,
|
|
4521
|
+
padEnd: (self: string, length: number, padding?: string) => string,
|
|
4522
|
+
},
|
|
4523
|
+
}
|
|
4524
|
+
|
|
4525
|
+
-- An array's methods. Lua gives an array no metatable: the compiler lowers
|
|
4526
|
+
-- each call to a plain function, so they work on any array, one a Lua
|
|
4527
|
+
-- library made included.
|
|
4528
|
+
--
|
|
4529
|
+
-- Indices are Lua's: the first element is 1, and \`indexOf\` / \`findIndex\`
|
|
4530
|
+
-- answer \`nil\` rather than JavaScript's -1. \`sort\` takes Lua's comparator
|
|
4531
|
+
-- (true when \`a\` comes first). \`push\`, \`pop\`, \`shift\`, \`unshift\`,
|
|
4532
|
+
-- \`sort\` and \`reverse\` change the array; everything else returns a new one.
|
|
4533
|
+
-- A \`filter\` whose test is a guard \u2014 \`v is S\`, or \`v => v ~= nil\` \u2014
|
|
4534
|
+
-- answers an array of what it let through.
|
|
4535
|
+
declare metatable<T> T[]: {
|
|
4536
|
+
__index: {
|
|
4537
|
+
find: (self: T[], test: (value: T, index: number) => boolean) => T | nil,
|
|
4538
|
+
findIndex: (self: T[], test: (value: T, index: number) => boolean) => number | nil,
|
|
4539
|
+
filter: (<S extends T>(self: T[], test: (value: T, index: number) => value is S) => S[])
|
|
4540
|
+
& ((self: T[], test: (value: T, index: number) => boolean) => T[]),
|
|
4541
|
+
map: <U>(self: T[], transform: (value: T, index: number) => U) => U[],
|
|
4542
|
+
forEach: (self: T[], visit: (value: T, index: number) => nil) => nil,
|
|
4543
|
+
some: (self: T[], test: (value: T, index: number) => boolean) => boolean,
|
|
4544
|
+
every: (self: T[], test: (value: T, index: number) => boolean) => boolean,
|
|
4545
|
+
reduce: <U>(self: T[], step: (total: U, value: T, index: number) => U, initial: U) => U,
|
|
4546
|
+
includes: (self: T[], value: T) => boolean,
|
|
4547
|
+
indexOf: (self: T[], value: T, start?: number) => number | nil,
|
|
4548
|
+
join: (self: T[], separator?: string) => string,
|
|
4549
|
+
concat: (self: T[], ...others: T[][]) => T[],
|
|
4550
|
+
slice: (self: T[], start?: number, stop?: number) => T[],
|
|
4551
|
+
flat: (self: T[]) => T[],
|
|
4552
|
+
reverse: (self: T[]) => T[],
|
|
4553
|
+
sort: (self: T[], compare?: (a: T, b: T) => boolean) => T[],
|
|
4554
|
+
push: (self: T[], ...args: T[]) => number,
|
|
4555
|
+
pop: (self: T[]) => T | nil,
|
|
4556
|
+
shift: (self: T[]) => T | nil,
|
|
4557
|
+
unshift: (self: T[], ...args: T[]) => number,
|
|
4558
|
+
},
|
|
4559
|
+
}
|
|
4560
|
+
|
|
4561
|
+
-- A table's methods. Unlike JavaScript's \`Object.keys\`, they answer a
|
|
4562
|
+
-- tuple, in the order the table's type wrote its members:
|
|
4563
|
+
--
|
|
4564
|
+
-- const point = { x: 1, y: "up" }
|
|
4565
|
+
-- point:keys() -- ["x", "y"]
|
|
4566
|
+
-- point:values() -- [number, string]
|
|
4567
|
+
-- point:entries() -- [["x", number], ["y", string]]
|
|
4568
|
+
--
|
|
4569
|
+
-- A Lua table has no order of its own, so the compiler hands its runtime the
|
|
4570
|
+
-- keys as the type lists them. An optional member keeps its place, holding
|
|
4571
|
+
-- \`nil\` when it is missing; an indexer's keys follow the named ones. A
|
|
4572
|
+
-- member of the table's own with one of these names is what a call reaches,
|
|
4573
|
+
-- and a class instance, which has a metatable of its own, has none of them.
|
|
4574
|
+
declare metatable<T extends {}> T: {
|
|
4575
|
+
__index: {
|
|
4576
|
+
keys: (self: T) => ObjectKeys<T>,
|
|
4577
|
+
values: (self: T) => ObjectValues<T>,
|
|
4578
|
+
entries: (self: T) => ObjectEntries<T>,
|
|
4579
|
+
},
|
|
4580
|
+
}
|
|
4427
4581
|
`;
|
|
4428
4582
|
var prelude;
|
|
4429
4583
|
function preludeProgram() {
|
|
@@ -4522,8 +4676,14 @@ function substitute(t, subst) {
|
|
|
4522
4676
|
if (subst.size === 0) return t;
|
|
4523
4677
|
if (!containsTypeParam(t)) return t;
|
|
4524
4678
|
switch (t.kind) {
|
|
4525
|
-
case "typeParam":
|
|
4526
|
-
|
|
4679
|
+
case "typeParam": {
|
|
4680
|
+
const bound = subst.get(t.name);
|
|
4681
|
+
if (bound) return bound;
|
|
4682
|
+
if (t.constraint && containsTypeParam(t.constraint)) {
|
|
4683
|
+
return { ...t, constraint: substitute(t.constraint, new Map([...subst, [t.name, t]])) };
|
|
4684
|
+
}
|
|
4685
|
+
return t;
|
|
4686
|
+
}
|
|
4527
4687
|
case "array":
|
|
4528
4688
|
return { ...arrayOf(substitute(t.element, subst)), readonly: t.readonly };
|
|
4529
4689
|
case "tuple":
|
|
@@ -4668,6 +4828,10 @@ function unify(param, arg, vars, out, how = "union") {
|
|
|
4668
4828
|
if (arg.kind === "function") {
|
|
4669
4829
|
param.params.forEach((p, i) => arg.params[i] && unify(p.type, arg.params[i].type, vars, out));
|
|
4670
4830
|
unify(param.returns, arg.returns, vars, out);
|
|
4831
|
+
const wanted = param.predicate, given = arg.predicate;
|
|
4832
|
+
if (wanted?.type && given?.type && wanted.param === given.param && !wanted.asserts && !given.asserts) {
|
|
4833
|
+
unify(wanted.type, given.type, vars, out);
|
|
4834
|
+
}
|
|
4671
4835
|
}
|
|
4672
4836
|
return;
|
|
4673
4837
|
case "object":
|
|
@@ -4923,6 +5087,11 @@ function isAssignableInner(a, b) {
|
|
|
4923
5087
|
for (let i = 0; i < n; i++) {
|
|
4924
5088
|
if (!isAssignable(a.params[i].type, b.params[i].type) && !isAssignable(b.params[i].type, a.params[i].type)) return false;
|
|
4925
5089
|
}
|
|
5090
|
+
const wanted = b.predicate;
|
|
5091
|
+
if (wanted?.type && !wanted.asserts) {
|
|
5092
|
+
const given = a.predicate;
|
|
5093
|
+
if (!given?.type || given.asserts || given.param !== wanted.param || !isAssignable(given.type, wanted.type)) return false;
|
|
5094
|
+
}
|
|
4926
5095
|
return isAssignable(a.returns, b.returns);
|
|
4927
5096
|
}
|
|
4928
5097
|
if (b.kind === "typeParam") return false;
|
|
@@ -5057,7 +5226,7 @@ function containsFreeTypeParam(t, seen, bound) {
|
|
|
5057
5226
|
return [...t.properties.values()].some((v) => containsTypeParam(v.type, seen, bound)) || !!t.indexer && (containsTypeParam(t.indexer.key, seen, bound) || containsTypeParam(t.indexer.value, seen, bound));
|
|
5058
5227
|
case "function": {
|
|
5059
5228
|
const inner = t.typeParams?.length ? /* @__PURE__ */ new Set([...bound, ...t.typeParams]) : bound;
|
|
5060
|
-
return t.params.some((p) => containsTypeParam(p.type, seen, inner)) || !!t.varargs && containsTypeParam(t.varargs, seen, inner) || containsTypeParam(t.returns, seen, inner);
|
|
5229
|
+
return t.params.some((p) => containsTypeParam(p.type, seen, inner)) || !!t.varargs && containsTypeParam(t.varargs, seen, inner) || containsTypeParam(t.returns, seen, inner) || !!t.predicate?.type && containsTypeParam(t.predicate.type, seen, inner);
|
|
5061
5230
|
}
|
|
5062
5231
|
case "genericRef":
|
|
5063
5232
|
return t.typeArguments.some((a) => containsTypeParam(a, seen, bound));
|
|
@@ -5482,9 +5651,14 @@ function isFreshLiteralExpr(e) {
|
|
|
5482
5651
|
case "StringLiteral":
|
|
5483
5652
|
case "BooleanLiteral":
|
|
5484
5653
|
case "InterpolatedStringExpression":
|
|
5654
|
+
return true;
|
|
5655
|
+
// A table or array literal widened the literals written in it as it
|
|
5656
|
+
// was read (`widenUnlessAsked`), and keeps what it read from
|
|
5657
|
+
// elsewhere — `{ key: name }`, `[...names]` — so there is nothing
|
|
5658
|
+
// left for the binding to widen.
|
|
5485
5659
|
case "TableExpression":
|
|
5486
5660
|
case "ArrayExpression":
|
|
5487
|
-
return
|
|
5661
|
+
return false;
|
|
5488
5662
|
case "ParenthesizedExpression":
|
|
5489
5663
|
return isFreshLiteralExpr(e.expression);
|
|
5490
5664
|
case "UnaryExpression":
|
|
@@ -5499,6 +5673,30 @@ function isFreshLiteralExpr(e) {
|
|
|
5499
5673
|
return false;
|
|
5500
5674
|
}
|
|
5501
5675
|
}
|
|
5676
|
+
function overloadTier(f) {
|
|
5677
|
+
const takesGuard = f.params.some((p) => p.type.kind === "function" && p.type.predicate?.type && !p.type.predicate.asserts);
|
|
5678
|
+
if (takesGuard) return 0;
|
|
5679
|
+
return (f.typeParams?.length ?? 0) > 0 ? 2 : 1;
|
|
5680
|
+
}
|
|
5681
|
+
function writesLiteral(e) {
|
|
5682
|
+
switch (e.type) {
|
|
5683
|
+
case "NumberLiteral":
|
|
5684
|
+
case "StringLiteral":
|
|
5685
|
+
case "BooleanLiteral":
|
|
5686
|
+
case "InterpolatedStringExpression":
|
|
5687
|
+
return true;
|
|
5688
|
+
case "ParenthesizedExpression":
|
|
5689
|
+
return writesLiteral(e.expression);
|
|
5690
|
+
case "UnaryExpression":
|
|
5691
|
+
return writesLiteral(e.argument);
|
|
5692
|
+
case "IfElseExpression":
|
|
5693
|
+
return e.clauses.some((c) => writesLiteral(c.body)) || writesLiteral(e.alternate);
|
|
5694
|
+
case "BinaryExpression":
|
|
5695
|
+
return (e.operator === "or" || e.operator === "and") && (writesLiteral(e.left) || writesLiteral(e.right));
|
|
5696
|
+
default:
|
|
5697
|
+
return false;
|
|
5698
|
+
}
|
|
5699
|
+
}
|
|
5502
5700
|
function collectInferNames(node) {
|
|
5503
5701
|
const out = [];
|
|
5504
5702
|
const walk = (n) => {
|
|
@@ -5789,6 +5987,10 @@ var TypeAnalyzer = class {
|
|
|
5789
5987
|
typeOfTypeNode = /* @__PURE__ */ new Map();
|
|
5790
5988
|
expectedTypeOf = /* @__PURE__ */ new Map();
|
|
5791
5989
|
loops = /* @__PURE__ */ new Map();
|
|
5990
|
+
/** Every `declare metatable`, libraries first, in declaration order. */
|
|
5991
|
+
metatables = [];
|
|
5992
|
+
metatableCache = /* @__PURE__ */ new WeakMap();
|
|
5993
|
+
methodSources = /* @__PURE__ */ new Map();
|
|
5792
5994
|
/** Public: each alias resolved once (generic aliases keep their params as
|
|
5793
5995
|
* `typeParam` nodes in the body). */
|
|
5794
5996
|
aliases = new LazyMap();
|
|
@@ -5855,6 +6057,9 @@ var TypeAnalyzer = class {
|
|
|
5855
6057
|
this.registerNestedClasses();
|
|
5856
6058
|
this.registerNestedAliases();
|
|
5857
6059
|
for (const lib of this.options.libs ?? []) this.harvestDeclares(lib.body);
|
|
6060
|
+
this.harvestMetatables(preludeProgram().body, { origin: "language" });
|
|
6061
|
+
(this.options.libs ?? []).forEach((lib, i) => this.harvestMetatables(lib.body, { origin: "library", library: i }));
|
|
6062
|
+
this.harvestMetatables(this.program.body, { origin: "program" });
|
|
5858
6063
|
this.registerImportedTypes();
|
|
5859
6064
|
this.resolveAllAliases();
|
|
5860
6065
|
this.harvestDeclares(this.program.body, true);
|
|
@@ -5886,6 +6091,8 @@ var TypeAnalyzer = class {
|
|
|
5886
6091
|
expectedTypeOf: this.expectedTypeOf,
|
|
5887
6092
|
aliases: this.resolveDeferredAliases(),
|
|
5888
6093
|
loops: this.loops,
|
|
6094
|
+
methodSources: this.methodSources,
|
|
6095
|
+
metatableMembers: (type) => this.metatableMembers(type),
|
|
5889
6096
|
diagnostics: this.diagnostics
|
|
5890
6097
|
};
|
|
5891
6098
|
}
|
|
@@ -5943,8 +6150,7 @@ var TypeAnalyzer = class {
|
|
|
5943
6150
|
}
|
|
5944
6151
|
/** `layering` is on for the prelude and for definitions files: a second
|
|
5945
6152
|
* 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
|
|
6153
|
+
* second `declare` of a table's name does. The file being
|
|
5948
6154
|
* analysed is not a layer: its own alias replaces what the libraries
|
|
5949
6155
|
* gave, which is how a project opts out of a set. */
|
|
5950
6156
|
registerAliasDefs(block, layering = false) {
|
|
@@ -6862,7 +7068,7 @@ var TypeAnalyzer = class {
|
|
|
6862
7068
|
if (record.type === "TypeReference" && typeof record.base === "string") {
|
|
6863
7069
|
const name = typeof record.namespace === "string" ? `${record.namespace}.${record.base}` : record.base;
|
|
6864
7070
|
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 && !
|
|
7071
|
+
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
7072
|
const at = node;
|
|
6867
7073
|
const key = `${at.line.start}:${at.column.start}`;
|
|
6868
7074
|
if (unresolved && !reported.has(key)) {
|
|
@@ -6980,7 +7186,7 @@ var TypeAnalyzer = class {
|
|
|
6980
7186
|
const tp = this.lookupTypeParam(node.base);
|
|
6981
7187
|
if (tp) return typeParam(tp.name, tp.constraint, tp.isConst);
|
|
6982
7188
|
if (node.typeArguments.length === 1 && !this.aliasDefs.has(node.base)) {
|
|
6983
|
-
const intrinsic = this.
|
|
7189
|
+
const intrinsic = this.applyIntrinsic(
|
|
6984
7190
|
node.base,
|
|
6985
7191
|
this.resolveType(node.typeArguments[0])
|
|
6986
7192
|
);
|
|
@@ -7187,7 +7393,7 @@ var TypeAnalyzer = class {
|
|
|
7187
7393
|
return difference(this.reduceType(t.base), this.reduceType(t.excluded));
|
|
7188
7394
|
case "genericRef": {
|
|
7189
7395
|
if (t.typeArguments.length !== 1 || this.aliasDefs.has(t.name)) return t;
|
|
7190
|
-
return this.
|
|
7396
|
+
return this.applyIntrinsic(t.name, t.typeArguments[0]) ?? t;
|
|
7191
7397
|
}
|
|
7192
7398
|
case "conditional":
|
|
7193
7399
|
return this.reduceConditional(t);
|
|
@@ -7300,6 +7506,41 @@ var TypeAnalyzer = class {
|
|
|
7300
7506
|
}
|
|
7301
7507
|
return union(combos.map((c) => literal(c)));
|
|
7302
7508
|
}
|
|
7509
|
+
/** A type function the analyzer supplies, since tilua cannot write it. */
|
|
7510
|
+
applyIntrinsic(name, arg) {
|
|
7511
|
+
return this.applyStringIntrinsic(name, arg) ?? this.applyObjectIntrinsic(name, arg);
|
|
7512
|
+
}
|
|
7513
|
+
/** `ObjectKeys<T>`, `ObjectValues<T>`, `ObjectEntries<T>`: an object's
|
|
7514
|
+
* members as a tuple, in the order they were written — `{ a: 1, b: "x" }`
|
|
7515
|
+
* has keys `["a", "b"]`, values `[number, string]` and entries
|
|
7516
|
+
* `[["a", number], ["b", string]]`. TypeScript answers `string[]` for
|
|
7517
|
+
* `Object.keys`, since its objects have no order; tilua's lowering hands
|
|
7518
|
+
* the runtime the written order, so the tuple is what runs. An indexer's
|
|
7519
|
+
* keys follow the named ones, as the tuple's rest. */
|
|
7520
|
+
applyObjectIntrinsic(name, arg) {
|
|
7521
|
+
if (!OBJECT_INTRINSICS.has(name)) return void 0;
|
|
7522
|
+
const t = this.expand(this.reduceType(arg));
|
|
7523
|
+
if (containsTypeParam(t)) return void 0;
|
|
7524
|
+
if (t.kind === "union") return union(t.types.map((m) => this.applyObjectIntrinsic(name, m) ?? unknownType));
|
|
7525
|
+
const parts = t.kind === "intersection" ? t.types.map((m) => this.expand(m)) : [t];
|
|
7526
|
+
if (!parts.every((m) => m.kind === "object" && !m.class)) return unknownType;
|
|
7527
|
+
const properties = /* @__PURE__ */ new Map();
|
|
7528
|
+
let indexer;
|
|
7529
|
+
for (const part of parts) {
|
|
7530
|
+
for (const [key, property] of part.properties) properties.set(key, property);
|
|
7531
|
+
indexer ??= part.indexer;
|
|
7532
|
+
}
|
|
7533
|
+
const entries = [...properties].map(([key, p]) => [literal(key), p.optional ? optional(p.type) : p.type]);
|
|
7534
|
+
const listed = (elements, rest) => !elements.length && rest ? arrayOf(rest) : tuple(elements, rest);
|
|
7535
|
+
switch (name) {
|
|
7536
|
+
case "ObjectKeys":
|
|
7537
|
+
return listed(entries.map(([key]) => key), indexer?.key);
|
|
7538
|
+
case "ObjectValues":
|
|
7539
|
+
return listed(entries.map(([, value]) => value), indexer?.value);
|
|
7540
|
+
default:
|
|
7541
|
+
return listed(entries.map((entry) => tuple([...entry])), indexer && tuple([indexer.key, indexer.value]));
|
|
7542
|
+
}
|
|
7543
|
+
}
|
|
7303
7544
|
/** TypeScript's four string intrinsics. They cannot be written in tilua —
|
|
7304
7545
|
* there is no character-level type arithmetic — so the analyzer supplies
|
|
7305
7546
|
* them, and only them, as named type functions. */
|
|
@@ -7838,7 +8079,9 @@ var TypeAnalyzer = class {
|
|
|
7838
8079
|
const waits = written.map((a, i) => this.waitsOnInference(a, expected[i]));
|
|
7839
8080
|
if (!waits.some(Boolean)) return written.map((a) => this.infer(a, env));
|
|
7840
8081
|
const argTypes = written.map((a, i) => waits[i] ? this.quietly(() => this.infer(a, env)) : this.infer(a, env));
|
|
7841
|
-
const
|
|
8082
|
+
const spread = this.spreadOf(written, selfOf(fns[0]));
|
|
8083
|
+
const provisional = argTypes.map((t, i) => waits[i] ? anyType : t);
|
|
8084
|
+
const picked = this.pickOverload(fns, argTypes, (f) => extra(f, argTypes), spread) ?? this.pickOverload(fns, provisional, (f) => extra(f, provisional), spread);
|
|
7842
8085
|
const subst = picked?.typeParams?.length ? this.inferTypeArgs(picked, extra(picked, argTypes)) : void 0;
|
|
7843
8086
|
written.forEach((a, i) => {
|
|
7844
8087
|
if (!waits[i]) return;
|
|
@@ -8029,6 +8272,16 @@ var TypeAnalyzer = class {
|
|
|
8029
8272
|
}
|
|
8030
8273
|
const id = this.bindingIdByName(p.name, p);
|
|
8031
8274
|
const t = this.paramType(p, env);
|
|
8275
|
+
if (p.default && p.typeAnnotation) {
|
|
8276
|
+
const given = this.infer(p.default, env);
|
|
8277
|
+
const declared = this.resolveType(p.typeAnnotation);
|
|
8278
|
+
if (this.emitDiagnostics && !isAssignable(given, declared)) {
|
|
8279
|
+
this.diagnostics.push({
|
|
8280
|
+
node: p.default,
|
|
8281
|
+
message: `Type '${briefType(given)}' is not assignable to '${briefType(declared)}'` + this.explainMismatch(given, declared)
|
|
8282
|
+
});
|
|
8283
|
+
}
|
|
8284
|
+
}
|
|
8032
8285
|
if (id !== void 0) {
|
|
8033
8286
|
this.bindingType.set(id, t);
|
|
8034
8287
|
this.setBinding(env, id, t);
|
|
@@ -8126,9 +8379,9 @@ var TypeAnalyzer = class {
|
|
|
8126
8379
|
* lets `typeof` declare `(v: number) -> "number"` alongside a trailing
|
|
8127
8380
|
* `<T>(v: T) -> string` and still pick the precise one. */
|
|
8128
8381
|
pickOverload(fns, argTypes, argsFor, spread) {
|
|
8129
|
-
for (const
|
|
8382
|
+
for (const tier of [0, 1, 2]) {
|
|
8130
8383
|
for (const f of fns) {
|
|
8131
|
-
if ((f
|
|
8384
|
+
if (overloadTier(f) !== tier) continue;
|
|
8132
8385
|
if (this.overloadAccepts(f, argsFor ? argsFor(f) : argTypes, spread)) return f;
|
|
8133
8386
|
}
|
|
8134
8387
|
}
|
|
@@ -8154,7 +8407,7 @@ var TypeAnalyzer = class {
|
|
|
8154
8407
|
if (position < 0) return void 0;
|
|
8155
8408
|
const members = this.expand(argTypes[position]).types;
|
|
8156
8409
|
if (members.length > 32) return void 0;
|
|
8157
|
-
const rank = (f) => (
|
|
8410
|
+
const rank = (f) => overloadTier(f) * fns.length + fns.indexOf(f);
|
|
8158
8411
|
const limit = picked ? rank(picked) : Infinity;
|
|
8159
8412
|
const results = [];
|
|
8160
8413
|
for (const member of members) {
|
|
@@ -8547,10 +8800,38 @@ var TypeAnalyzer = class {
|
|
|
8547
8800
|
returns,
|
|
8548
8801
|
void 0,
|
|
8549
8802
|
names,
|
|
8550
|
-
this.resolvePredicate(func.predicate, params)
|
|
8803
|
+
func.predicate || func.returnType ? this.resolvePredicate(func.predicate, params) : this.inferPredicate(func, params, returns, bodyEnv)
|
|
8551
8804
|
), func.params);
|
|
8552
8805
|
});
|
|
8553
8806
|
}
|
|
8807
|
+
/** `v => v ~= nil` is a guard without saying so, as TypeScript reads it:
|
|
8808
|
+
* a body that is one `return` of a boolean which, true, narrows a
|
|
8809
|
+
* parameter — and, false, leaves it exactly what that excludes. Then
|
|
8810
|
+
* `list:filter(v => v ~= nil)` knows what it kept. */
|
|
8811
|
+
inferPredicate(func, params, returns, env) {
|
|
8812
|
+
const only = func.body.statements.length === 1 ? func.body.statements[0] : void 0;
|
|
8813
|
+
if (only?.type !== "ReturnStatement" || !only.argument) return void 0;
|
|
8814
|
+
if (returns.kind === "any" || returns.kind === "never" || !isAssignable(returns, booleanType)) return void 0;
|
|
8815
|
+
const cond = only.argument;
|
|
8816
|
+
for (let i = 0; i < params.length; i++) {
|
|
8817
|
+
const p = func.params[i];
|
|
8818
|
+
if (p.pattern || p.rest) continue;
|
|
8819
|
+
const id = this.bindingIdByName(p.name, p);
|
|
8820
|
+
if (id === void 0) continue;
|
|
8821
|
+
const declared = params[i].type;
|
|
8822
|
+
const key = bindKey(id);
|
|
8823
|
+
const t = forkEnv(env);
|
|
8824
|
+
const f = forkEnv(env);
|
|
8825
|
+
this.silently(() => this.applyNarrowing(cond, env, t, f));
|
|
8826
|
+
const yes = t.get(key);
|
|
8827
|
+
if (!yes || equalTypes(yes, declared) || !isAssignable(yes, declared)) continue;
|
|
8828
|
+
const no = this.expand(f.get(key) ?? declared);
|
|
8829
|
+
const rest = no.kind === "union" ? no.types : [no];
|
|
8830
|
+
if (rest.some((m) => m.kind !== "never" && isAssignable(m, yes))) continue;
|
|
8831
|
+
return { param: i, type: yes, asserts: false };
|
|
8832
|
+
}
|
|
8833
|
+
return void 0;
|
|
8834
|
+
}
|
|
8554
8835
|
/** Where the return types of the function being walked are collected, so
|
|
8555
8836
|
* each is read where it is written — inside the branch that narrowed it —
|
|
8556
8837
|
* rather than in whatever state the body ends in. */
|
|
@@ -8981,28 +9262,122 @@ var TypeAnalyzer = class {
|
|
|
8981
9262
|
this.resolvingAliases.delete(t.name);
|
|
8982
9263
|
}
|
|
8983
9264
|
}
|
|
8984
|
-
|
|
8985
|
-
|
|
8986
|
-
|
|
8987
|
-
|
|
8988
|
-
|
|
8989
|
-
|
|
8990
|
-
|
|
8991
|
-
|
|
8992
|
-
|
|
8993
|
-
|
|
8994
|
-
|
|
8995
|
-
|
|
8996
|
-
|
|
8997
|
-
|
|
8998
|
-
|
|
8999
|
-
|
|
9000
|
-
|
|
9001
|
-
|
|
9002
|
-
|
|
9265
|
+
// --------------------------------------------------------
|
|
9266
|
+
// Metatables
|
|
9267
|
+
// --------------------------------------------------------
|
|
9268
|
+
harvestMetatables(block, source) {
|
|
9269
|
+
for (const stmt of block.statements) {
|
|
9270
|
+
if (stmt.type === "DeclareMetatableStatement") this.metatables.push({ node: stmt, source });
|
|
9271
|
+
}
|
|
9272
|
+
}
|
|
9273
|
+
resolvedMetatable(entry) {
|
|
9274
|
+
entry.resolved ??= this.withTypeParams(entry.node.generics, () => ({
|
|
9275
|
+
target: this.resolveType(entry.node.target),
|
|
9276
|
+
metatable: this.resolveType(entry.node.metatable)
|
|
9277
|
+
}));
|
|
9278
|
+
return entry.resolved;
|
|
9279
|
+
}
|
|
9280
|
+
/** Does a value (`whole`, whose union members are `parts`) have the
|
|
9281
|
+
* metatable `entry` declares — and if so how specifically, and what its
|
|
9282
|
+
* type parameters stand for there. Three kinds of target:
|
|
9283
|
+
*
|
|
9284
|
+
* - `<T> T[]`: every array and tuple, `T` what they hold;
|
|
9285
|
+
* - `<T extends C> T`: every value that is a `C` other than a class
|
|
9286
|
+
* instance, which has a metatable of its own; `T` the value itself;
|
|
9287
|
+
* - a type with no parameters (`string`): every value that is one.
|
|
9288
|
+
*
|
|
9289
|
+
* A bare parameter matches the most, so it is the least specific: an
|
|
9290
|
+
* array that is also a `{}` takes an array's metatable. */
|
|
9291
|
+
matchMetatable(entry, whole, parts) {
|
|
9292
|
+
if (!parts.length || parts.some((m) => m.kind === "any" || m.kind === "unknown" || m.kind === "never")) return void 0;
|
|
9293
|
+
const { target } = this.resolvedMetatable(entry);
|
|
9294
|
+
if (target.kind === "array" && target.element.kind === "typeParam") {
|
|
9295
|
+
const elements = parts.map((m) => m.kind === "array" ? m.element : m.kind === "tuple" ? union(tupleMembers(m)) : void 0);
|
|
9296
|
+
if (!elements.every((e) => e !== void 0)) return void 0;
|
|
9297
|
+
return { specificity: 2, bindings: /* @__PURE__ */ new Map([[target.element.name, union(elements)]]) };
|
|
9298
|
+
}
|
|
9299
|
+
if (target.kind === "typeParam") {
|
|
9300
|
+
const constraint = target.constraint;
|
|
9301
|
+
if (parts.some((m) => m.kind === "object" && m.class)) return void 0;
|
|
9302
|
+
if (constraint && !parts.every((m) => isAssignable(m, constraint))) return void 0;
|
|
9303
|
+
return { specificity: 1, bindings: /* @__PURE__ */ new Map([[target.name, whole]]) };
|
|
9304
|
+
}
|
|
9305
|
+
if (containsTypeParam(target)) return void 0;
|
|
9306
|
+
return parts.every((m) => isAssignable(m, target)) ? { specificity: 2, bindings: /* @__PURE__ */ new Map() } : void 0;
|
|
9307
|
+
}
|
|
9308
|
+
/** The metatables `t` has: every declaration of the most specific target
|
|
9309
|
+
* it matches, instantiated for it, in declaration order — a later one
|
|
9310
|
+
* adds to an earlier, the way a library layers on another. A type
|
|
9311
|
+
* parameter is read at its constraint. */
|
|
9312
|
+
metatablesOf(t) {
|
|
9313
|
+
if (!this.metatables.length) return [];
|
|
9314
|
+
const cached = this.metatableCache.get(t);
|
|
9315
|
+
if (cached) return cached;
|
|
9316
|
+
const expanded = this.expand(t);
|
|
9317
|
+
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);
|
|
9318
|
+
const whole = union(parts);
|
|
9319
|
+
const matches = this.metatables.flatMap((entry) => {
|
|
9320
|
+
const match = this.matchMetatable(entry, whole, parts);
|
|
9321
|
+
return match ? [{ entry, ...match }] : [];
|
|
9322
|
+
});
|
|
9323
|
+
const best = Math.max(0, ...matches.map((m) => m.specificity));
|
|
9324
|
+
const found = matches.filter((m) => m.specificity === best).map(({ entry, bindings }) => ({
|
|
9325
|
+
type: this.expand(this.reduceType(substitute(this.resolvedMetatable(entry).metatable, bindings))),
|
|
9326
|
+
source: entry.source
|
|
9327
|
+
}));
|
|
9328
|
+
this.metatableCache.set(t, found);
|
|
9329
|
+
return found;
|
|
9330
|
+
}
|
|
9331
|
+
/** The `__index` tables of `t`'s metatables, each with the library that
|
|
9332
|
+
* declared it, in declaration order. */
|
|
9333
|
+
metatableIndexes(t) {
|
|
9334
|
+
return this.metatablesOf(t).flatMap(({ type, source }) => {
|
|
9335
|
+
const parts = type.kind === "intersection" ? type.types.map((m) => this.expand(m)) : [type];
|
|
9336
|
+
return parts.flatMap((part) => {
|
|
9337
|
+
const index = part.kind === "object" ? part.properties.get("__index") : void 0;
|
|
9338
|
+
if (!index) return [];
|
|
9339
|
+
const table = this.expand(index.type);
|
|
9340
|
+
return (table.kind === "intersection" ? table.types.map((m) => this.expand(m)) : [table]).map((m) => ({ table: m, source }));
|
|
9341
|
+
});
|
|
9342
|
+
});
|
|
9343
|
+
}
|
|
9344
|
+
/** `name` read through `t`'s metatable: `text:upper()` is `upper` in the
|
|
9345
|
+
* string metatable's `__index`. The last declaration to give the name
|
|
9346
|
+
* wins. The language's own metatables are declared in the prelude, in
|
|
9347
|
+
* tilua, like any library's; a library adds to them the same way. */
|
|
9348
|
+
metatableMember(t, name) {
|
|
9349
|
+
const indexes = this.metatableIndexes(t);
|
|
9350
|
+
for (let i = indexes.length - 1; i >= 0; i--) {
|
|
9351
|
+
const { table, source } = indexes[i];
|
|
9352
|
+
const property = table.kind === "object" ? table.properties.get(name) : void 0;
|
|
9353
|
+
if (property) return { type: property.type, source };
|
|
9003
9354
|
}
|
|
9004
9355
|
return void 0;
|
|
9005
9356
|
}
|
|
9357
|
+
metatableMethod(t, name) {
|
|
9358
|
+
return this.metatableMember(t, name)?.type;
|
|
9359
|
+
}
|
|
9360
|
+
hasMetatable(t) {
|
|
9361
|
+
return this.metatablesOf(t).length > 0;
|
|
9362
|
+
}
|
|
9363
|
+
metatableMembers(t) {
|
|
9364
|
+
const members = /* @__PURE__ */ new Map();
|
|
9365
|
+
for (const { table } of this.metatableIndexes(t)) {
|
|
9366
|
+
if (table.kind !== "object") continue;
|
|
9367
|
+
for (const [name, property] of table.properties) members.set(name, property.type);
|
|
9368
|
+
}
|
|
9369
|
+
return members;
|
|
9370
|
+
}
|
|
9371
|
+
/** `text:upper()`, `scores:keys()`: the method a value's metatable gives
|
|
9372
|
+
* it, when the value has no member of that name of its own. Called with
|
|
9373
|
+
* `:`, it answers before an object's indexer, since a metatable's
|
|
9374
|
+
* `__index` is only read for a key the table does not hold. */
|
|
9375
|
+
receiverMetatableMember(objType, name) {
|
|
9376
|
+
const t = this.expand(objType);
|
|
9377
|
+
const parts = (t.kind === "union" ? t.types : [t]).map((m) => this.expand(m));
|
|
9378
|
+
if (parts.some((m) => m.kind === "object" && m.properties.has(name))) return void 0;
|
|
9379
|
+
return this.metatableMember(t, name);
|
|
9380
|
+
}
|
|
9006
9381
|
/** The most a deferred type could turn out to be. A conditional is one of
|
|
9007
9382
|
* its branches, and the true branch stands for a member of what was
|
|
9008
9383
|
* tested (`T` in `T extends U ? T : never`); an indexed access reads
|
|
@@ -9068,7 +9443,7 @@ var TypeAnalyzer = class {
|
|
|
9068
9443
|
if (index.kind !== "literal" || typeof index.value !== "string") return void 0;
|
|
9069
9444
|
const parts = this.stringParts(object);
|
|
9070
9445
|
if (!parts) return void 0;
|
|
9071
|
-
const found = parts.map((part) => this.
|
|
9446
|
+
const found = parts.map((part) => this.metatableMethod(part, index.value));
|
|
9072
9447
|
return found.every((t) => t !== void 0) ? union(found) : void 0;
|
|
9073
9448
|
}
|
|
9074
9449
|
/** The members of `t` when every one of them is a string — `"a" | "b"` is
|
|
@@ -9089,13 +9464,13 @@ var TypeAnalyzer = class {
|
|
|
9089
9464
|
* not one of them is a mistake worth reporting, rather than the nil Lua
|
|
9090
9465
|
* would hand back. */
|
|
9091
9466
|
checkStringMember(node, object, key) {
|
|
9092
|
-
if (!this.emitDiagnostics || !this.
|
|
9467
|
+
if (!this.emitDiagnostics || !this.hasMetatable(stringType)) return;
|
|
9093
9468
|
const parts = this.stringParts(object);
|
|
9094
9469
|
if (!parts) return;
|
|
9095
9470
|
const keys = (key.kind === "union" ? key.types : [key]).map((m) => this.expand(m));
|
|
9096
9471
|
if (!keys.length || !keys.every((m) => m.kind === "literal" && typeof m.value === "string")) return;
|
|
9097
9472
|
const names = keys.map((m) => String(m.value));
|
|
9098
|
-
if (names.some((name) => parts.some((part) => this.
|
|
9473
|
+
if (names.some((name) => parts.some((part) => this.metatableMethod(part, name)))) return;
|
|
9099
9474
|
this.diagnostics.push({
|
|
9100
9475
|
node,
|
|
9101
9476
|
message: names.length === 1 ? `'${names[0]}' does not exist on a string` : `'${briefType(key)}' does not name a member of a string`
|
|
@@ -9127,24 +9502,24 @@ var TypeAnalyzer = class {
|
|
|
9127
9502
|
switch (part.kind) {
|
|
9128
9503
|
case "primitive":
|
|
9129
9504
|
if (part.name === "nil") continue;
|
|
9130
|
-
if (part.name === "string" && !this.
|
|
9131
|
-
if (part.name === "string" && this.
|
|
9505
|
+
if (part.name === "string" && !this.hasMetatable(part)) return false;
|
|
9506
|
+
if (part.name === "string" && this.metatableMethod(part, name)) continue;
|
|
9132
9507
|
break;
|
|
9133
9508
|
case "literal":
|
|
9134
9509
|
case "templateLiteral":
|
|
9135
9510
|
if (part.kind === "templateLiteral" || part.base === "string") {
|
|
9136
|
-
if (!this.
|
|
9511
|
+
if (!this.hasMetatable(part)) return false;
|
|
9137
9512
|
}
|
|
9138
|
-
if (this.
|
|
9513
|
+
if (this.metatableMethod(part, name)) continue;
|
|
9139
9514
|
break;
|
|
9140
9515
|
case "array":
|
|
9141
9516
|
case "tuple":
|
|
9142
|
-
if (!this.
|
|
9143
|
-
if (this.
|
|
9517
|
+
if (!this.hasMetatable(part)) return false;
|
|
9518
|
+
if (this.metatableMethod(part, name)) continue;
|
|
9144
9519
|
break;
|
|
9145
9520
|
case "object":
|
|
9146
9521
|
if (part.properties.has(name)) continue;
|
|
9147
|
-
if (part.indexer || part.properties.size === 0 && !part.class || this.
|
|
9522
|
+
if (part.indexer || part.properties.size === 0 && !part.class || this.metatableMethod(part, name)) return false;
|
|
9148
9523
|
break;
|
|
9149
9524
|
default:
|
|
9150
9525
|
return false;
|
|
@@ -9279,10 +9654,10 @@ var TypeAnalyzer = class {
|
|
|
9279
9654
|
if (p) return p.optional ? optional(p.type) : p.type;
|
|
9280
9655
|
if (t.indexer) return t.indexer.value;
|
|
9281
9656
|
}
|
|
9282
|
-
const built = this.
|
|
9657
|
+
const built = this.metatableMethod(t, name);
|
|
9283
9658
|
if (built) return built;
|
|
9284
9659
|
if (t.kind === "union") {
|
|
9285
|
-
const built2 = this.
|
|
9660
|
+
const built2 = this.metatableMethod(t, name);
|
|
9286
9661
|
if (built2) return built2;
|
|
9287
9662
|
return union(t.types.map((m) => this.propertyType(m, name)));
|
|
9288
9663
|
}
|
|
@@ -9578,6 +9953,27 @@ var TypeAnalyzer = class {
|
|
|
9578
9953
|
elseEnv = whenFalse;
|
|
9579
9954
|
}
|
|
9580
9955
|
branches.push(this.infer(expr.alternate, elseEnv));
|
|
9956
|
+
const bodies = [...expr.clauses.map((c) => c.body), expr.alternate].map(unwrapParens);
|
|
9957
|
+
const isEmpty = (e) => e.type === "TableExpression" && e.fields.length === 0 || e.type === "ArrayExpression" && e.elements.length === 0;
|
|
9958
|
+
const others = union(branches.filter((_, i) => !isEmpty(bodies[i])));
|
|
9959
|
+
const parts = (others.kind === "union" ? others.types : [others]).map((m) => this.expand(m)).filter((m) => !(m.kind === "primitive" && m.name === "nil"));
|
|
9960
|
+
const fits = (e) => {
|
|
9961
|
+
if (!parts.length) return void 0;
|
|
9962
|
+
if (e.type === "ArrayExpression") {
|
|
9963
|
+
if (parts.every((m) => m.kind === "array")) return others;
|
|
9964
|
+
return parts.every((m) => m.kind === "array" || m.kind === "tuple") ? tuple([]) : void 0;
|
|
9965
|
+
}
|
|
9966
|
+
return parts.every((m) => m.kind === "object" && !m.class && [...m.properties.values()].every((prop) => prop.optional)) ? others : void 0;
|
|
9967
|
+
};
|
|
9968
|
+
if (bodies.some(isEmpty) && others.kind !== "never" && others.kind !== "any" && bodies.every((body) => !isEmpty(body) || fits(body))) {
|
|
9969
|
+
const typed = bodies.map((body, i) => {
|
|
9970
|
+
if (!isEmpty(body)) return branches[i];
|
|
9971
|
+
const t = fits(body);
|
|
9972
|
+
this.typeOf.set(body, t);
|
|
9973
|
+
return t;
|
|
9974
|
+
});
|
|
9975
|
+
return union(typed);
|
|
9976
|
+
}
|
|
9581
9977
|
return union(branches);
|
|
9582
9978
|
}
|
|
9583
9979
|
}
|
|
@@ -9683,7 +10079,9 @@ var TypeAnalyzer = class {
|
|
|
9683
10079
|
this.checkPrivateMember(expr.method, objType, expr.method.name);
|
|
9684
10080
|
this.checkStringMember(expr.method, objType, literal(expr.method.name));
|
|
9685
10081
|
this.checkMissingMember(expr.method, objType, expr.method.name);
|
|
9686
|
-
const
|
|
10082
|
+
const fromMetatable = this.receiverMetatableMember(objType, expr.method.name);
|
|
10083
|
+
if (fromMetatable) this.methodSources.set(expr, fromMetatable.source);
|
|
10084
|
+
const method = fromMetatable?.type ?? this.propertyType(objType, expr.method.name);
|
|
9687
10085
|
const united = this.unionSignatures(method);
|
|
9688
10086
|
const fns = united ?? this.overloadsOf(method);
|
|
9689
10087
|
const explicit = this.explicitTypeArguments(expr, fns);
|
|
@@ -9882,23 +10280,87 @@ var TypeAnalyzer = class {
|
|
|
9882
10280
|
if (wantedTuple && !asConst) return this.inferTupleLiteral(expr, env, wantedTuple);
|
|
9883
10281
|
const elems = [];
|
|
9884
10282
|
let hadSpread = false;
|
|
10283
|
+
const written = [];
|
|
9885
10284
|
for (const el of expr.elements) {
|
|
9886
10285
|
if (el.type === "SpreadElement") {
|
|
9887
10286
|
hadSpread = true;
|
|
9888
|
-
const
|
|
9889
|
-
|
|
9890
|
-
|
|
9891
|
-
else elems.push(unknownType);
|
|
10287
|
+
const spread = this.infer(el.argument, env);
|
|
10288
|
+
written.push({ spread: true, type: spread });
|
|
10289
|
+
elems.push(...this.spreadElements(spread));
|
|
9892
10290
|
} else {
|
|
9893
|
-
|
|
10291
|
+
const t = asConst ? this.inferAsConst(el, env) : this.infer(el, env);
|
|
10292
|
+
written.push({ spread: false, type: asConst ? t : this.widenUnlessAsked(t, el) });
|
|
10293
|
+
elems.push(t);
|
|
9894
10294
|
}
|
|
9895
10295
|
}
|
|
9896
10296
|
if (asConst && !hadSpread) return tuple(elems);
|
|
10297
|
+
if (hadSpread) {
|
|
10298
|
+
const tuples = this.spreadTuples(written);
|
|
10299
|
+
if (tuples) return union(tuples.map((elements) => tuple(elements)));
|
|
10300
|
+
}
|
|
9897
10301
|
return arrayOf(elems.length ? union(elems.map((t, i) => {
|
|
9898
10302
|
const element = expr.elements[i];
|
|
9899
10303
|
return asConst || !element || element.type === "SpreadElement" ? t : this.widenUnlessAsked(t, element);
|
|
9900
10304
|
})) : unknownType);
|
|
9901
10305
|
}
|
|
10306
|
+
/** The tuples an array literal with spreads is, when every spread is a
|
|
10307
|
+
* tuple of known length or a union of them: one per way the unions go.
|
|
10308
|
+
* `undefined` when a spread's length is not known (an array, a tuple
|
|
10309
|
+
* with a rest), or the ways multiply past what is worth listing. */
|
|
10310
|
+
spreadTuples(written) {
|
|
10311
|
+
const MAX = 32;
|
|
10312
|
+
let variants = [[]];
|
|
10313
|
+
for (const { spread, type } of written) {
|
|
10314
|
+
if (!spread) {
|
|
10315
|
+
variants = variants.map((v) => [...v, type]);
|
|
10316
|
+
continue;
|
|
10317
|
+
}
|
|
10318
|
+
const options = this.fixedTuples(type);
|
|
10319
|
+
if (!options) return void 0;
|
|
10320
|
+
const next = [];
|
|
10321
|
+
for (const v of variants) for (const option of options) next.push([...v, ...option]);
|
|
10322
|
+
if (next.length > MAX) return void 0;
|
|
10323
|
+
variants = next;
|
|
10324
|
+
}
|
|
10325
|
+
return variants;
|
|
10326
|
+
}
|
|
10327
|
+
/** A tuple of known length's members, or a union of such tuples' — each
|
|
10328
|
+
* a way the value can be. `nil` in a union spreads nothing. */
|
|
10329
|
+
fixedTuples(t, depth = 0) {
|
|
10330
|
+
if (depth > 8) return void 0;
|
|
10331
|
+
const held = this.expand(t);
|
|
10332
|
+
if (held.kind === "tuple") return held.rest ? void 0 : [held.elements];
|
|
10333
|
+
if (held.kind !== "union") return void 0;
|
|
10334
|
+
const out = [];
|
|
10335
|
+
for (const member of held.types) {
|
|
10336
|
+
if (member.kind === "primitive" && member.name === "nil") continue;
|
|
10337
|
+
const options = this.fixedTuples(member, depth + 1);
|
|
10338
|
+
if (!options) return void 0;
|
|
10339
|
+
out.push(...options);
|
|
10340
|
+
}
|
|
10341
|
+
return out.length ? out : void 0;
|
|
10342
|
+
}
|
|
10343
|
+
/** What `...xs` puts into an array: an array's element, a tuple's members,
|
|
10344
|
+
* and for a union each member's — `...(host ? extra : [])` adds `extra`'s
|
|
10345
|
+
* elements and the empty tuple nothing. Anything else, `unknown`. */
|
|
10346
|
+
spreadElements(t, depth = 0) {
|
|
10347
|
+
const held = this.expand(t);
|
|
10348
|
+
if (depth > 8) return [unknownType];
|
|
10349
|
+
switch (held.kind) {
|
|
10350
|
+
case "array":
|
|
10351
|
+
return [held.element];
|
|
10352
|
+
case "tuple":
|
|
10353
|
+
return tupleMembers(held);
|
|
10354
|
+
case "union":
|
|
10355
|
+
return held.types.filter((m) => !(m.kind === "primitive" && m.name === "nil")).flatMap((m) => this.spreadElements(m, depth + 1));
|
|
10356
|
+
case "typeParam":
|
|
10357
|
+
return held.constraint ? this.spreadElements(held.constraint, depth + 1) : [unknownType];
|
|
10358
|
+
case "any":
|
|
10359
|
+
return [anyType];
|
|
10360
|
+
default:
|
|
10361
|
+
return [unknownType];
|
|
10362
|
+
}
|
|
10363
|
+
}
|
|
9902
10364
|
/** `[a, b, ...more]` where a tuple is wanted: the places it names are the
|
|
9903
10365
|
* tuple's, and what comes after them — more elements, or a spread — is
|
|
9904
10366
|
* its rest. */
|
|
@@ -9909,10 +10371,7 @@ var TypeAnalyzer = class {
|
|
|
9909
10371
|
expr.elements.forEach((element, i) => {
|
|
9910
10372
|
if (element.type === "SpreadElement") {
|
|
9911
10373
|
spread = true;
|
|
9912
|
-
|
|
9913
|
-
if (held.kind === "array") tail.push(held.element);
|
|
9914
|
-
else if (held.kind === "tuple") tail.push(...tupleMembers(held));
|
|
9915
|
-
else tail.push(unknownType);
|
|
10374
|
+
tail.push(...this.spreadElements(this.infer(element.argument, env)));
|
|
9916
10375
|
return;
|
|
9917
10376
|
}
|
|
9918
10377
|
const t = this.widenUnlessAsked(this.infer(element, env), element);
|
|
@@ -9929,6 +10388,7 @@ var TypeAnalyzer = class {
|
|
|
9929
10388
|
* The context was recorded by `applyContext` before the value was
|
|
9930
10389
|
* inferred, so this is a lookup rather than a second pass. */
|
|
9931
10390
|
widenUnlessAsked(value, at) {
|
|
10391
|
+
if (!writesLiteral(at)) return value;
|
|
9932
10392
|
const wanted = this.expectedTypeOf.get(at);
|
|
9933
10393
|
if (value.kind === "literal") {
|
|
9934
10394
|
const arms = this.contextArms.get(at);
|
|
@@ -10925,6 +11385,8 @@ function containsTypeQuery(node) {
|
|
|
10925
11385
|
return found;
|
|
10926
11386
|
}
|
|
10927
11387
|
var STRING_INTRINSICS = /* @__PURE__ */ new Set(["Uppercase", "Lowercase", "Capitalize", "Uncapitalize"]);
|
|
11388
|
+
var OBJECT_INTRINSICS = /* @__PURE__ */ new Set(["ObjectKeys", "ObjectValues", "ObjectEntries"]);
|
|
11389
|
+
var INTRINSICS = /* @__PURE__ */ new Set([...STRING_INTRINSICS, ...OBJECT_INTRINSICS]);
|
|
10928
11390
|
function briefType(t) {
|
|
10929
11391
|
if (t.kind === "union" && t.types.length > 8) {
|
|
10930
11392
|
const shown = t.types.slice(0, 6).map(formatType).join(" | ");
|
|
@@ -11132,7 +11594,8 @@ function resolveTypeLibraries(config, host = nodeHost) {
|
|
|
11132
11594
|
}
|
|
11133
11595
|
addFile(entryFile);
|
|
11134
11596
|
const lowering = loweringModule(directory, host, problems, config);
|
|
11135
|
-
|
|
11597
|
+
const library = files.findIndex((file) => pathKey(file) === pathKey(entryFile));
|
|
11598
|
+
if (lowering) lowerings.push({ ...lowering, library });
|
|
11136
11599
|
};
|
|
11137
11600
|
for (const entry of config.types) {
|
|
11138
11601
|
const relative = entry.startsWith("./") || entry.startsWith("../") || entry.startsWith("/") || /^[A-Za-z]:[\\/]/.test(entry);
|