@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.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,6 +2127,14 @@ var Parser = class {
|
|
|
2112
2127
|
parseExpressionStatement() {
|
|
2113
2128
|
const start = this.current();
|
|
2114
2129
|
if (this.checkPunctuator("{") || this.checkPunctuator("[")) {
|
|
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()) };
|
|
2115
2138
|
const target = this.checkPunctuator("{") ? this.parseObjectPattern(true) : this.parseArrayPattern(true);
|
|
2116
2139
|
return this.parseAssignmentRest(start, target);
|
|
2117
2140
|
}
|
|
@@ -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);
|
|
@@ -3867,6 +3893,10 @@ var Analyzer = class {
|
|
|
3867
3893
|
moduleScope = this.globalScope;
|
|
3868
3894
|
/** How many function bodies enclose the walk. */
|
|
3869
3895
|
functionDepth = 0;
|
|
3896
|
+
/** How many loops enclose the walk inside the nearest function body. A
|
|
3897
|
+
* function starts again at 0: `continue` in `xs:map(x => ...)` has no
|
|
3898
|
+
* loop to go on with, whatever loop the call is written in. */
|
|
3899
|
+
loopDepth = 0;
|
|
3870
3900
|
/** Function declarations already declared by their block's hoisting, with
|
|
3871
3901
|
* the function depth of that block. */
|
|
3872
3902
|
hoisted = /* @__PURE__ */ new Map();
|
|
@@ -4219,11 +4249,11 @@ var Analyzer = class {
|
|
|
4219
4249
|
return;
|
|
4220
4250
|
case "WhileStatement":
|
|
4221
4251
|
this.visitExpression(stmt.condition, scope);
|
|
4222
|
-
this.visitBlockInNewScope(stmt.body, scope);
|
|
4252
|
+
this.inLoop(() => this.visitBlockInNewScope(stmt.body, scope));
|
|
4223
4253
|
return;
|
|
4224
4254
|
case "RepeatStatement": {
|
|
4225
4255
|
const bodyScope = childScope(scope);
|
|
4226
|
-
this.visitBlock(stmt.body, bodyScope);
|
|
4256
|
+
this.inLoop(() => this.visitBlock(stmt.body, bodyScope));
|
|
4227
4257
|
this.visitExpression(stmt.condition, bodyScope);
|
|
4228
4258
|
return;
|
|
4229
4259
|
}
|
|
@@ -4241,14 +4271,14 @@ var Analyzer = class {
|
|
|
4241
4271
|
if (stmt.step) this.visitExpression(stmt.step, scope);
|
|
4242
4272
|
const bodyScope = childScope(scope);
|
|
4243
4273
|
this.declare(bodyScope, stmt.variable.name, "for-numeric", stmt.variable);
|
|
4244
|
-
this.visitBlock(stmt.body, bodyScope);
|
|
4274
|
+
this.inLoop(() => this.visitBlock(stmt.body, bodyScope));
|
|
4245
4275
|
return;
|
|
4246
4276
|
}
|
|
4247
4277
|
case "GenericForStatement": {
|
|
4248
4278
|
this.visitExpression(stmt.iterator, scope);
|
|
4249
4279
|
const bodyScope = childScope(scope);
|
|
4250
4280
|
this.declarePattern(bodyScope, stmt.variable, "for-generic", scope, stmt.kind === "const");
|
|
4251
|
-
this.visitBlock(stmt.body, bodyScope);
|
|
4281
|
+
this.inLoop(() => this.visitBlock(stmt.body, bodyScope));
|
|
4252
4282
|
return;
|
|
4253
4283
|
}
|
|
4254
4284
|
case "ReturnStatement":
|
|
@@ -4256,6 +4286,15 @@ var Analyzer = class {
|
|
|
4256
4286
|
return;
|
|
4257
4287
|
case "BreakStatement":
|
|
4258
4288
|
case "ContinueStatement":
|
|
4289
|
+
if (this.loopDepth === 0) {
|
|
4290
|
+
const word = stmt.type === "BreakStatement" ? "break" : "continue";
|
|
4291
|
+
this.diagnostics.push({
|
|
4292
|
+
node: stmt,
|
|
4293
|
+
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`,
|
|
4294
|
+
kind: "outside-loop"
|
|
4295
|
+
});
|
|
4296
|
+
}
|
|
4297
|
+
return;
|
|
4259
4298
|
case "ErrorStatement":
|
|
4260
4299
|
return;
|
|
4261
4300
|
case "DeclareStatement":
|
|
@@ -4264,6 +4303,11 @@ var Analyzer = class {
|
|
|
4264
4303
|
case "DeclareClassStatement":
|
|
4265
4304
|
this.visitType(stmt.body, scope);
|
|
4266
4305
|
return;
|
|
4306
|
+
case "DeclareMetatableStatement":
|
|
4307
|
+
this.visitGenerics(stmt.generics, scope);
|
|
4308
|
+
this.visitType(stmt.target, scope);
|
|
4309
|
+
this.visitType(stmt.metatable, scope);
|
|
4310
|
+
return;
|
|
4267
4311
|
case "TypeAliasStatement":
|
|
4268
4312
|
case "ExportTypeAliasStatement":
|
|
4269
4313
|
this.visitGenerics(
|
|
@@ -4306,6 +4350,15 @@ var Analyzer = class {
|
|
|
4306
4350
|
return;
|
|
4307
4351
|
}
|
|
4308
4352
|
}
|
|
4353
|
+
/** Visit a loop's body, where `break` and `continue` have a loop. */
|
|
4354
|
+
inLoop(visit) {
|
|
4355
|
+
this.loopDepth++;
|
|
4356
|
+
try {
|
|
4357
|
+
visit();
|
|
4358
|
+
} finally {
|
|
4359
|
+
this.loopDepth--;
|
|
4360
|
+
}
|
|
4361
|
+
}
|
|
4309
4362
|
// ---------------- functions ----------------
|
|
4310
4363
|
visitFunctionBody(func, outerScope, isMethod = false) {
|
|
4311
4364
|
const fnScope = childScope(outerScope);
|
|
@@ -4321,11 +4374,14 @@ var Analyzer = class {
|
|
|
4321
4374
|
}
|
|
4322
4375
|
});
|
|
4323
4376
|
this.visitType(func.returnType, fnScope);
|
|
4377
|
+
const loops = this.loopDepth;
|
|
4324
4378
|
this.functionDepth++;
|
|
4379
|
+
this.loopDepth = 0;
|
|
4325
4380
|
try {
|
|
4326
4381
|
this.visitBlock(func.body, fnScope);
|
|
4327
4382
|
} finally {
|
|
4328
4383
|
this.functionDepth--;
|
|
4384
|
+
this.loopDepth = loops;
|
|
4329
4385
|
}
|
|
4330
4386
|
}
|
|
4331
4387
|
/** An overload signature: no body and no bindings, but its types can hold
|
|
@@ -4529,6 +4585,104 @@ type Mutable<T> = { -readonly [K in keyof T]: T[K] }
|
|
|
4529
4585
|
type Pick<T, K> = { [P in K]: T[P] }
|
|
4530
4586
|
type Omit<T, K> = Pick<T, Exclude<keyof T, K>>
|
|
4531
4587
|
type Record<K, V> = { [P in K]: V }
|
|
4588
|
+
|
|
4589
|
+
-- ============================================================
|
|
4590
|
+
-- The metatables of the language's own values
|
|
4591
|
+
-- ============================================================
|
|
4592
|
+
|
|
4593
|
+
-- A string's methods. The first group is Lua's string library, which a
|
|
4594
|
+
-- string really answers to; where Lua answers several values, tilua answers
|
|
4595
|
+
-- one, as the global functions do (\`find\` is an array, \`gsub\` the new
|
|
4596
|
+
-- string). The second group is written the way JavaScript writes it.
|
|
4597
|
+
--
|
|
4598
|
+
-- Positions are Lua's: \`sub\` and \`slice\` count from 1, and \`indexOf\` is
|
|
4599
|
+
-- \`nil\` when the text is not there.
|
|
4600
|
+
declare metatable string: {
|
|
4601
|
+
__index: {
|
|
4602
|
+
upper: (self: string) => string,
|
|
4603
|
+
lower: (self: string) => string,
|
|
4604
|
+
len: (self: string) => number,
|
|
4605
|
+
sub: (self: string, i: number, j?: number) => string,
|
|
4606
|
+
rep: (self: string, n: number, separator?: string) => string,
|
|
4607
|
+
reverse: (self: string) => string,
|
|
4608
|
+
format: (self: string, ...args: unknown[]) => string,
|
|
4609
|
+
byte: ((self: string, i?: number) => number) & ((self: string, i: number, j: number) => number[]),
|
|
4610
|
+
find: (self: string, pattern: string, init?: number, plain?: boolean) => [number, number, ...string[]] | nil,
|
|
4611
|
+
match: (self: string, pattern: string, init?: number) => [...string[]] | nil,
|
|
4612
|
+
gmatch: (self: string, pattern: string) => () => [string, ...string[]] | nil,
|
|
4613
|
+
gsub: (self: string, pattern: string, replacement: unknown, n?: number) => string,
|
|
4614
|
+
|
|
4615
|
+
trim: (self: string) => string,
|
|
4616
|
+
trimStart: (self: string) => string,
|
|
4617
|
+
trimEnd: (self: string) => string,
|
|
4618
|
+
startsWith: (self: string, text: string) => boolean,
|
|
4619
|
+
endsWith: (self: string, text: string) => boolean,
|
|
4620
|
+
includes: (self: string, text: string) => boolean,
|
|
4621
|
+
indexOf: (self: string, text: string, start?: number) => number | nil,
|
|
4622
|
+
slice: (self: string, start?: number, stop?: number) => string,
|
|
4623
|
+
replace: (self: string, text: string, replacement: string) => string,
|
|
4624
|
+
replaceAll: (self: string, text: string, replacement: string) => string,
|
|
4625
|
+
padStart: (self: string, length: number, padding?: string) => string,
|
|
4626
|
+
padEnd: (self: string, length: number, padding?: string) => string,
|
|
4627
|
+
},
|
|
4628
|
+
}
|
|
4629
|
+
|
|
4630
|
+
-- An array's methods. Lua gives an array no metatable: the compiler lowers
|
|
4631
|
+
-- each call to a plain function, so they work on any array, one a Lua
|
|
4632
|
+
-- library made included.
|
|
4633
|
+
--
|
|
4634
|
+
-- Indices are Lua's: the first element is 1, and \`indexOf\` / \`findIndex\`
|
|
4635
|
+
-- answer \`nil\` rather than JavaScript's -1. \`sort\` takes Lua's comparator
|
|
4636
|
+
-- (true when \`a\` comes first). \`push\`, \`pop\`, \`shift\`, \`unshift\`,
|
|
4637
|
+
-- \`sort\` and \`reverse\` change the array; everything else returns a new one.
|
|
4638
|
+
-- A \`filter\` whose test is a guard \u2014 \`v is S\`, or \`v => v ~= nil\` \u2014
|
|
4639
|
+
-- answers an array of what it let through.
|
|
4640
|
+
declare metatable<T> T[]: {
|
|
4641
|
+
__index: {
|
|
4642
|
+
find: (self: T[], test: (value: T, index: number) => boolean) => T | nil,
|
|
4643
|
+
findIndex: (self: T[], test: (value: T, index: number) => boolean) => number | nil,
|
|
4644
|
+
filter: (<S extends T>(self: T[], test: (value: T, index: number) => value is S) => S[])
|
|
4645
|
+
& ((self: T[], test: (value: T, index: number) => boolean) => T[]),
|
|
4646
|
+
map: <U>(self: T[], transform: (value: T, index: number) => U) => U[],
|
|
4647
|
+
forEach: (self: T[], visit: (value: T, index: number) => nil) => nil,
|
|
4648
|
+
some: (self: T[], test: (value: T, index: number) => boolean) => boolean,
|
|
4649
|
+
every: (self: T[], test: (value: T, index: number) => boolean) => boolean,
|
|
4650
|
+
reduce: <U>(self: T[], step: (total: U, value: T, index: number) => U, initial: U) => U,
|
|
4651
|
+
includes: (self: T[], value: T) => boolean,
|
|
4652
|
+
indexOf: (self: T[], value: T, start?: number) => number | nil,
|
|
4653
|
+
join: (self: T[], separator?: string) => string,
|
|
4654
|
+
concat: (self: T[], ...others: T[][]) => T[],
|
|
4655
|
+
slice: (self: T[], start?: number, stop?: number) => T[],
|
|
4656
|
+
flat: (self: T[]) => T[],
|
|
4657
|
+
reverse: (self: T[]) => T[],
|
|
4658
|
+
sort: (self: T[], compare?: (a: T, b: T) => boolean) => T[],
|
|
4659
|
+
push: (self: T[], ...args: T[]) => number,
|
|
4660
|
+
pop: (self: T[]) => T | nil,
|
|
4661
|
+
shift: (self: T[]) => T | nil,
|
|
4662
|
+
unshift: (self: T[], ...args: T[]) => number,
|
|
4663
|
+
},
|
|
4664
|
+
}
|
|
4665
|
+
|
|
4666
|
+
-- A table's methods. Unlike JavaScript's \`Object.keys\`, they answer a
|
|
4667
|
+
-- tuple, in the order the table's type wrote its members:
|
|
4668
|
+
--
|
|
4669
|
+
-- const point = { x: 1, y: "up" }
|
|
4670
|
+
-- point:keys() -- ["x", "y"]
|
|
4671
|
+
-- point:values() -- [number, string]
|
|
4672
|
+
-- point:entries() -- [["x", number], ["y", string]]
|
|
4673
|
+
--
|
|
4674
|
+
-- A Lua table has no order of its own, so the compiler hands its runtime the
|
|
4675
|
+
-- keys as the type lists them. An optional member keeps its place, holding
|
|
4676
|
+
-- \`nil\` when it is missing; an indexer's keys follow the named ones. A
|
|
4677
|
+
-- member of the table's own with one of these names is what a call reaches,
|
|
4678
|
+
-- and a class instance, which has a metatable of its own, has none of them.
|
|
4679
|
+
declare metatable<T extends {}> T: {
|
|
4680
|
+
__index: {
|
|
4681
|
+
keys: (self: T) => ObjectKeys<T>,
|
|
4682
|
+
values: (self: T) => ObjectValues<T>,
|
|
4683
|
+
entries: (self: T) => ObjectEntries<T>,
|
|
4684
|
+
},
|
|
4685
|
+
}
|
|
4532
4686
|
`;
|
|
4533
4687
|
var prelude;
|
|
4534
4688
|
function preludeProgram() {
|
|
@@ -4627,8 +4781,14 @@ function substitute(t, subst) {
|
|
|
4627
4781
|
if (subst.size === 0) return t;
|
|
4628
4782
|
if (!containsTypeParam(t)) return t;
|
|
4629
4783
|
switch (t.kind) {
|
|
4630
|
-
case "typeParam":
|
|
4631
|
-
|
|
4784
|
+
case "typeParam": {
|
|
4785
|
+
const bound = subst.get(t.name);
|
|
4786
|
+
if (bound) return bound;
|
|
4787
|
+
if (t.constraint && containsTypeParam(t.constraint)) {
|
|
4788
|
+
return { ...t, constraint: substitute(t.constraint, new Map([...subst, [t.name, t]])) };
|
|
4789
|
+
}
|
|
4790
|
+
return t;
|
|
4791
|
+
}
|
|
4632
4792
|
case "array":
|
|
4633
4793
|
return { ...arrayOf(substitute(t.element, subst)), readonly: t.readonly };
|
|
4634
4794
|
case "tuple":
|
|
@@ -4773,6 +4933,10 @@ function unify(param, arg, vars, out, how = "union") {
|
|
|
4773
4933
|
if (arg.kind === "function") {
|
|
4774
4934
|
param.params.forEach((p, i) => arg.params[i] && unify(p.type, arg.params[i].type, vars, out));
|
|
4775
4935
|
unify(param.returns, arg.returns, vars, out);
|
|
4936
|
+
const wanted = param.predicate, given = arg.predicate;
|
|
4937
|
+
if (wanted?.type && given?.type && wanted.param === given.param && !wanted.asserts && !given.asserts) {
|
|
4938
|
+
unify(wanted.type, given.type, vars, out);
|
|
4939
|
+
}
|
|
4776
4940
|
}
|
|
4777
4941
|
return;
|
|
4778
4942
|
case "object":
|
|
@@ -5028,6 +5192,11 @@ function isAssignableInner(a, b) {
|
|
|
5028
5192
|
for (let i = 0; i < n; i++) {
|
|
5029
5193
|
if (!isAssignable(a.params[i].type, b.params[i].type) && !isAssignable(b.params[i].type, a.params[i].type)) return false;
|
|
5030
5194
|
}
|
|
5195
|
+
const wanted = b.predicate;
|
|
5196
|
+
if (wanted?.type && !wanted.asserts) {
|
|
5197
|
+
const given = a.predicate;
|
|
5198
|
+
if (!given?.type || given.asserts || given.param !== wanted.param || !isAssignable(given.type, wanted.type)) return false;
|
|
5199
|
+
}
|
|
5031
5200
|
return isAssignable(a.returns, b.returns);
|
|
5032
5201
|
}
|
|
5033
5202
|
if (b.kind === "typeParam") return false;
|
|
@@ -5162,7 +5331,7 @@ function containsFreeTypeParam(t, seen, bound) {
|
|
|
5162
5331
|
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));
|
|
5163
5332
|
case "function": {
|
|
5164
5333
|
const inner = t.typeParams?.length ? /* @__PURE__ */ new Set([...bound, ...t.typeParams]) : bound;
|
|
5165
|
-
return t.params.some((p) => containsTypeParam(p.type, seen, inner)) || !!t.varargs && containsTypeParam(t.varargs, seen, inner) || containsTypeParam(t.returns, seen, inner);
|
|
5334
|
+
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);
|
|
5166
5335
|
}
|
|
5167
5336
|
case "genericRef":
|
|
5168
5337
|
return t.typeArguments.some((a) => containsTypeParam(a, seen, bound));
|
|
@@ -5587,9 +5756,14 @@ function isFreshLiteralExpr(e) {
|
|
|
5587
5756
|
case "StringLiteral":
|
|
5588
5757
|
case "BooleanLiteral":
|
|
5589
5758
|
case "InterpolatedStringExpression":
|
|
5759
|
+
return true;
|
|
5760
|
+
// A table or array literal widened the literals written in it as it
|
|
5761
|
+
// was read (`widenUnlessAsked`), and keeps what it read from
|
|
5762
|
+
// elsewhere — `{ key: name }`, `[...names]` — so there is nothing
|
|
5763
|
+
// left for the binding to widen.
|
|
5590
5764
|
case "TableExpression":
|
|
5591
5765
|
case "ArrayExpression":
|
|
5592
|
-
return
|
|
5766
|
+
return false;
|
|
5593
5767
|
case "ParenthesizedExpression":
|
|
5594
5768
|
return isFreshLiteralExpr(e.expression);
|
|
5595
5769
|
case "UnaryExpression":
|
|
@@ -5604,6 +5778,30 @@ function isFreshLiteralExpr(e) {
|
|
|
5604
5778
|
return false;
|
|
5605
5779
|
}
|
|
5606
5780
|
}
|
|
5781
|
+
function overloadTier(f) {
|
|
5782
|
+
const takesGuard = f.params.some((p) => p.type.kind === "function" && p.type.predicate?.type && !p.type.predicate.asserts);
|
|
5783
|
+
if (takesGuard) return 0;
|
|
5784
|
+
return (f.typeParams?.length ?? 0) > 0 ? 2 : 1;
|
|
5785
|
+
}
|
|
5786
|
+
function writesLiteral(e) {
|
|
5787
|
+
switch (e.type) {
|
|
5788
|
+
case "NumberLiteral":
|
|
5789
|
+
case "StringLiteral":
|
|
5790
|
+
case "BooleanLiteral":
|
|
5791
|
+
case "InterpolatedStringExpression":
|
|
5792
|
+
return true;
|
|
5793
|
+
case "ParenthesizedExpression":
|
|
5794
|
+
return writesLiteral(e.expression);
|
|
5795
|
+
case "UnaryExpression":
|
|
5796
|
+
return writesLiteral(e.argument);
|
|
5797
|
+
case "IfElseExpression":
|
|
5798
|
+
return e.clauses.some((c) => writesLiteral(c.body)) || writesLiteral(e.alternate);
|
|
5799
|
+
case "BinaryExpression":
|
|
5800
|
+
return (e.operator === "or" || e.operator === "and") && (writesLiteral(e.left) || writesLiteral(e.right));
|
|
5801
|
+
default:
|
|
5802
|
+
return false;
|
|
5803
|
+
}
|
|
5804
|
+
}
|
|
5607
5805
|
function collectInferNames(node) {
|
|
5608
5806
|
const out = [];
|
|
5609
5807
|
const walk = (n) => {
|
|
@@ -5894,6 +6092,10 @@ var TypeAnalyzer = class {
|
|
|
5894
6092
|
typeOfTypeNode = /* @__PURE__ */ new Map();
|
|
5895
6093
|
expectedTypeOf = /* @__PURE__ */ new Map();
|
|
5896
6094
|
loops = /* @__PURE__ */ new Map();
|
|
6095
|
+
/** Every `declare metatable`, libraries first, in declaration order. */
|
|
6096
|
+
metatables = [];
|
|
6097
|
+
metatableCache = /* @__PURE__ */ new WeakMap();
|
|
6098
|
+
methodSources = /* @__PURE__ */ new Map();
|
|
5897
6099
|
/** Public: each alias resolved once (generic aliases keep their params as
|
|
5898
6100
|
* `typeParam` nodes in the body). */
|
|
5899
6101
|
aliases = new LazyMap();
|
|
@@ -5960,6 +6162,9 @@ var TypeAnalyzer = class {
|
|
|
5960
6162
|
this.registerNestedClasses();
|
|
5961
6163
|
this.registerNestedAliases();
|
|
5962
6164
|
for (const lib of this.options.libs ?? []) this.harvestDeclares(lib.body);
|
|
6165
|
+
this.harvestMetatables(preludeProgram().body, { origin: "language" });
|
|
6166
|
+
(this.options.libs ?? []).forEach((lib, i) => this.harvestMetatables(lib.body, { origin: "library", library: i }));
|
|
6167
|
+
this.harvestMetatables(this.program.body, { origin: "program" });
|
|
5963
6168
|
this.registerImportedTypes();
|
|
5964
6169
|
this.resolveAllAliases();
|
|
5965
6170
|
this.harvestDeclares(this.program.body, true);
|
|
@@ -5991,6 +6196,8 @@ var TypeAnalyzer = class {
|
|
|
5991
6196
|
expectedTypeOf: this.expectedTypeOf,
|
|
5992
6197
|
aliases: this.resolveDeferredAliases(),
|
|
5993
6198
|
loops: this.loops,
|
|
6199
|
+
methodSources: this.methodSources,
|
|
6200
|
+
metatableMembers: (type) => this.metatableMembers(type),
|
|
5994
6201
|
diagnostics: this.diagnostics
|
|
5995
6202
|
};
|
|
5996
6203
|
}
|
|
@@ -6048,8 +6255,7 @@ var TypeAnalyzer = class {
|
|
|
6048
6255
|
}
|
|
6049
6256
|
/** `layering` is on for the prelude and for definitions files: a second
|
|
6050
6257
|
* library that declares an alias already declared *adds* to it, the way a
|
|
6051
|
-
* second `declare` of a table's name does
|
|
6052
|
-
* `StringMethods` Luau's `split` without restating Lua's. The file being
|
|
6258
|
+
* second `declare` of a table's name does. The file being
|
|
6053
6259
|
* analysed is not a layer: its own alias replaces what the libraries
|
|
6054
6260
|
* gave, which is how a project opts out of a set. */
|
|
6055
6261
|
registerAliasDefs(block, layering = false) {
|
|
@@ -6967,7 +7173,7 @@ var TypeAnalyzer = class {
|
|
|
6967
7173
|
if (record.type === "TypeReference" && typeof record.base === "string") {
|
|
6968
7174
|
const name = typeof record.namespace === "string" ? `${record.namespace}.${record.base}` : record.base;
|
|
6969
7175
|
const resolved = this.typeOfTypeNode.get(node);
|
|
6970
|
-
const unresolved = resolved?.kind === "genericRef" && resolved.name === name && !this.aliasDefs.has(name) && !this.importedTypes.has(name) && this.options.libTypes?.[name] === void 0 && !
|
|
7176
|
+
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]);
|
|
6971
7177
|
const at = node;
|
|
6972
7178
|
const key = `${at.line.start}:${at.column.start}`;
|
|
6973
7179
|
if (unresolved && !reported.has(key)) {
|
|
@@ -7085,7 +7291,7 @@ var TypeAnalyzer = class {
|
|
|
7085
7291
|
const tp = this.lookupTypeParam(node.base);
|
|
7086
7292
|
if (tp) return typeParam(tp.name, tp.constraint, tp.isConst);
|
|
7087
7293
|
if (node.typeArguments.length === 1 && !this.aliasDefs.has(node.base)) {
|
|
7088
|
-
const intrinsic = this.
|
|
7294
|
+
const intrinsic = this.applyIntrinsic(
|
|
7089
7295
|
node.base,
|
|
7090
7296
|
this.resolveType(node.typeArguments[0])
|
|
7091
7297
|
);
|
|
@@ -7292,7 +7498,7 @@ var TypeAnalyzer = class {
|
|
|
7292
7498
|
return difference(this.reduceType(t.base), this.reduceType(t.excluded));
|
|
7293
7499
|
case "genericRef": {
|
|
7294
7500
|
if (t.typeArguments.length !== 1 || this.aliasDefs.has(t.name)) return t;
|
|
7295
|
-
return this.
|
|
7501
|
+
return this.applyIntrinsic(t.name, t.typeArguments[0]) ?? t;
|
|
7296
7502
|
}
|
|
7297
7503
|
case "conditional":
|
|
7298
7504
|
return this.reduceConditional(t);
|
|
@@ -7405,6 +7611,41 @@ var TypeAnalyzer = class {
|
|
|
7405
7611
|
}
|
|
7406
7612
|
return union(combos.map((c) => literal(c)));
|
|
7407
7613
|
}
|
|
7614
|
+
/** A type function the analyzer supplies, since tilua cannot write it. */
|
|
7615
|
+
applyIntrinsic(name, arg) {
|
|
7616
|
+
return this.applyStringIntrinsic(name, arg) ?? this.applyObjectIntrinsic(name, arg);
|
|
7617
|
+
}
|
|
7618
|
+
/** `ObjectKeys<T>`, `ObjectValues<T>`, `ObjectEntries<T>`: an object's
|
|
7619
|
+
* members as a tuple, in the order they were written — `{ a: 1, b: "x" }`
|
|
7620
|
+
* has keys `["a", "b"]`, values `[number, string]` and entries
|
|
7621
|
+
* `[["a", number], ["b", string]]`. TypeScript answers `string[]` for
|
|
7622
|
+
* `Object.keys`, since its objects have no order; tilua's lowering hands
|
|
7623
|
+
* the runtime the written order, so the tuple is what runs. An indexer's
|
|
7624
|
+
* keys follow the named ones, as the tuple's rest. */
|
|
7625
|
+
applyObjectIntrinsic(name, arg) {
|
|
7626
|
+
if (!OBJECT_INTRINSICS.has(name)) return void 0;
|
|
7627
|
+
const t = this.expand(this.reduceType(arg));
|
|
7628
|
+
if (containsTypeParam(t)) return void 0;
|
|
7629
|
+
if (t.kind === "union") return union(t.types.map((m) => this.applyObjectIntrinsic(name, m) ?? unknownType));
|
|
7630
|
+
const parts = t.kind === "intersection" ? t.types.map((m) => this.expand(m)) : [t];
|
|
7631
|
+
if (!parts.every((m) => m.kind === "object" && !m.class)) return unknownType;
|
|
7632
|
+
const properties = /* @__PURE__ */ new Map();
|
|
7633
|
+
let indexer;
|
|
7634
|
+
for (const part of parts) {
|
|
7635
|
+
for (const [key, property] of part.properties) properties.set(key, property);
|
|
7636
|
+
indexer ??= part.indexer;
|
|
7637
|
+
}
|
|
7638
|
+
const entries = [...properties].map(([key, p]) => [literal(key), p.optional ? optional(p.type) : p.type]);
|
|
7639
|
+
const listed = (elements, rest) => !elements.length && rest ? arrayOf(rest) : tuple(elements, rest);
|
|
7640
|
+
switch (name) {
|
|
7641
|
+
case "ObjectKeys":
|
|
7642
|
+
return listed(entries.map(([key]) => key), indexer?.key);
|
|
7643
|
+
case "ObjectValues":
|
|
7644
|
+
return listed(entries.map(([, value]) => value), indexer?.value);
|
|
7645
|
+
default:
|
|
7646
|
+
return listed(entries.map((entry) => tuple([...entry])), indexer && tuple([indexer.key, indexer.value]));
|
|
7647
|
+
}
|
|
7648
|
+
}
|
|
7408
7649
|
/** TypeScript's four string intrinsics. They cannot be written in tilua —
|
|
7409
7650
|
* there is no character-level type arithmetic — so the analyzer supplies
|
|
7410
7651
|
* them, and only them, as named type functions. */
|
|
@@ -7943,7 +8184,9 @@ var TypeAnalyzer = class {
|
|
|
7943
8184
|
const waits = written.map((a, i) => this.waitsOnInference(a, expected[i]));
|
|
7944
8185
|
if (!waits.some(Boolean)) return written.map((a) => this.infer(a, env));
|
|
7945
8186
|
const argTypes = written.map((a, i) => waits[i] ? this.quietly(() => this.infer(a, env)) : this.infer(a, env));
|
|
7946
|
-
const
|
|
8187
|
+
const spread = this.spreadOf(written, selfOf(fns[0]));
|
|
8188
|
+
const provisional = argTypes.map((t, i) => waits[i] ? anyType : t);
|
|
8189
|
+
const picked = this.pickOverload(fns, argTypes, (f) => extra(f, argTypes), spread) ?? this.pickOverload(fns, provisional, (f) => extra(f, provisional), spread);
|
|
7947
8190
|
const subst = picked?.typeParams?.length ? this.inferTypeArgs(picked, extra(picked, argTypes)) : void 0;
|
|
7948
8191
|
written.forEach((a, i) => {
|
|
7949
8192
|
if (!waits[i]) return;
|
|
@@ -8134,6 +8377,16 @@ var TypeAnalyzer = class {
|
|
|
8134
8377
|
}
|
|
8135
8378
|
const id = this.bindingIdByName(p.name, p);
|
|
8136
8379
|
const t = this.paramType(p, env);
|
|
8380
|
+
if (p.default && p.typeAnnotation) {
|
|
8381
|
+
const given = this.infer(p.default, env);
|
|
8382
|
+
const declared = this.resolveType(p.typeAnnotation);
|
|
8383
|
+
if (this.emitDiagnostics && !isAssignable(given, declared)) {
|
|
8384
|
+
this.diagnostics.push({
|
|
8385
|
+
node: p.default,
|
|
8386
|
+
message: `Type '${briefType(given)}' is not assignable to '${briefType(declared)}'` + this.explainMismatch(given, declared)
|
|
8387
|
+
});
|
|
8388
|
+
}
|
|
8389
|
+
}
|
|
8137
8390
|
if (id !== void 0) {
|
|
8138
8391
|
this.bindingType.set(id, t);
|
|
8139
8392
|
this.setBinding(env, id, t);
|
|
@@ -8231,9 +8484,9 @@ var TypeAnalyzer = class {
|
|
|
8231
8484
|
* lets `typeof` declare `(v: number) -> "number"` alongside a trailing
|
|
8232
8485
|
* `<T>(v: T) -> string` and still pick the precise one. */
|
|
8233
8486
|
pickOverload(fns, argTypes, argsFor, spread) {
|
|
8234
|
-
for (const
|
|
8487
|
+
for (const tier of [0, 1, 2]) {
|
|
8235
8488
|
for (const f of fns) {
|
|
8236
|
-
if ((f
|
|
8489
|
+
if (overloadTier(f) !== tier) continue;
|
|
8237
8490
|
if (this.overloadAccepts(f, argsFor ? argsFor(f) : argTypes, spread)) return f;
|
|
8238
8491
|
}
|
|
8239
8492
|
}
|
|
@@ -8259,7 +8512,7 @@ var TypeAnalyzer = class {
|
|
|
8259
8512
|
if (position < 0) return void 0;
|
|
8260
8513
|
const members = this.expand(argTypes[position]).types;
|
|
8261
8514
|
if (members.length > 32) return void 0;
|
|
8262
|
-
const rank = (f) => (
|
|
8515
|
+
const rank = (f) => overloadTier(f) * fns.length + fns.indexOf(f);
|
|
8263
8516
|
const limit = picked ? rank(picked) : Infinity;
|
|
8264
8517
|
const results = [];
|
|
8265
8518
|
for (const member of members) {
|
|
@@ -8652,10 +8905,38 @@ var TypeAnalyzer = class {
|
|
|
8652
8905
|
returns,
|
|
8653
8906
|
void 0,
|
|
8654
8907
|
names,
|
|
8655
|
-
this.resolvePredicate(func.predicate, params)
|
|
8908
|
+
func.predicate || func.returnType ? this.resolvePredicate(func.predicate, params) : this.inferPredicate(func, params, returns, bodyEnv)
|
|
8656
8909
|
), func.params);
|
|
8657
8910
|
});
|
|
8658
8911
|
}
|
|
8912
|
+
/** `v => v ~= nil` is a guard without saying so, as TypeScript reads it:
|
|
8913
|
+
* a body that is one `return` of a boolean which, true, narrows a
|
|
8914
|
+
* parameter — and, false, leaves it exactly what that excludes. Then
|
|
8915
|
+
* `list:filter(v => v ~= nil)` knows what it kept. */
|
|
8916
|
+
inferPredicate(func, params, returns, env) {
|
|
8917
|
+
const only = func.body.statements.length === 1 ? func.body.statements[0] : void 0;
|
|
8918
|
+
if (only?.type !== "ReturnStatement" || !only.argument) return void 0;
|
|
8919
|
+
if (returns.kind === "any" || returns.kind === "never" || !isAssignable(returns, booleanType)) return void 0;
|
|
8920
|
+
const cond = only.argument;
|
|
8921
|
+
for (let i = 0; i < params.length; i++) {
|
|
8922
|
+
const p = func.params[i];
|
|
8923
|
+
if (p.pattern || p.rest) continue;
|
|
8924
|
+
const id = this.bindingIdByName(p.name, p);
|
|
8925
|
+
if (id === void 0) continue;
|
|
8926
|
+
const declared = params[i].type;
|
|
8927
|
+
const key = bindKey(id);
|
|
8928
|
+
const t = forkEnv(env);
|
|
8929
|
+
const f = forkEnv(env);
|
|
8930
|
+
this.silently(() => this.applyNarrowing(cond, env, t, f));
|
|
8931
|
+
const yes = t.get(key);
|
|
8932
|
+
if (!yes || equalTypes(yes, declared) || !isAssignable(yes, declared)) continue;
|
|
8933
|
+
const no = this.expand(f.get(key) ?? declared);
|
|
8934
|
+
const rest = no.kind === "union" ? no.types : [no];
|
|
8935
|
+
if (rest.some((m) => m.kind !== "never" && isAssignable(m, yes))) continue;
|
|
8936
|
+
return { param: i, type: yes, asserts: false };
|
|
8937
|
+
}
|
|
8938
|
+
return void 0;
|
|
8939
|
+
}
|
|
8659
8940
|
/** Where the return types of the function being walked are collected, so
|
|
8660
8941
|
* each is read where it is written — inside the branch that narrowed it —
|
|
8661
8942
|
* rather than in whatever state the body ends in. */
|
|
@@ -9086,28 +9367,122 @@ var TypeAnalyzer = class {
|
|
|
9086
9367
|
this.resolvingAliases.delete(t.name);
|
|
9087
9368
|
}
|
|
9088
9369
|
}
|
|
9089
|
-
|
|
9090
|
-
|
|
9091
|
-
|
|
9092
|
-
|
|
9093
|
-
|
|
9094
|
-
|
|
9095
|
-
|
|
9096
|
-
|
|
9097
|
-
|
|
9098
|
-
|
|
9099
|
-
|
|
9100
|
-
|
|
9101
|
-
|
|
9102
|
-
|
|
9103
|
-
|
|
9104
|
-
|
|
9105
|
-
|
|
9106
|
-
|
|
9107
|
-
|
|
9370
|
+
// --------------------------------------------------------
|
|
9371
|
+
// Metatables
|
|
9372
|
+
// --------------------------------------------------------
|
|
9373
|
+
harvestMetatables(block, source) {
|
|
9374
|
+
for (const stmt of block.statements) {
|
|
9375
|
+
if (stmt.type === "DeclareMetatableStatement") this.metatables.push({ node: stmt, source });
|
|
9376
|
+
}
|
|
9377
|
+
}
|
|
9378
|
+
resolvedMetatable(entry) {
|
|
9379
|
+
entry.resolved ??= this.withTypeParams(entry.node.generics, () => ({
|
|
9380
|
+
target: this.resolveType(entry.node.target),
|
|
9381
|
+
metatable: this.resolveType(entry.node.metatable)
|
|
9382
|
+
}));
|
|
9383
|
+
return entry.resolved;
|
|
9384
|
+
}
|
|
9385
|
+
/** Does a value (`whole`, whose union members are `parts`) have the
|
|
9386
|
+
* metatable `entry` declares — and if so how specifically, and what its
|
|
9387
|
+
* type parameters stand for there. Three kinds of target:
|
|
9388
|
+
*
|
|
9389
|
+
* - `<T> T[]`: every array and tuple, `T` what they hold;
|
|
9390
|
+
* - `<T extends C> T`: every value that is a `C` other than a class
|
|
9391
|
+
* instance, which has a metatable of its own; `T` the value itself;
|
|
9392
|
+
* - a type with no parameters (`string`): every value that is one.
|
|
9393
|
+
*
|
|
9394
|
+
* A bare parameter matches the most, so it is the least specific: an
|
|
9395
|
+
* array that is also a `{}` takes an array's metatable. */
|
|
9396
|
+
matchMetatable(entry, whole, parts) {
|
|
9397
|
+
if (!parts.length || parts.some((m) => m.kind === "any" || m.kind === "unknown" || m.kind === "never")) return void 0;
|
|
9398
|
+
const { target } = this.resolvedMetatable(entry);
|
|
9399
|
+
if (target.kind === "array" && target.element.kind === "typeParam") {
|
|
9400
|
+
const elements = parts.map((m) => m.kind === "array" ? m.element : m.kind === "tuple" ? union(tupleMembers(m)) : void 0);
|
|
9401
|
+
if (!elements.every((e) => e !== void 0)) return void 0;
|
|
9402
|
+
return { specificity: 2, bindings: /* @__PURE__ */ new Map([[target.element.name, union(elements)]]) };
|
|
9403
|
+
}
|
|
9404
|
+
if (target.kind === "typeParam") {
|
|
9405
|
+
const constraint = target.constraint;
|
|
9406
|
+
if (parts.some((m) => m.kind === "object" && m.class)) return void 0;
|
|
9407
|
+
if (constraint && !parts.every((m) => isAssignable(m, constraint))) return void 0;
|
|
9408
|
+
return { specificity: 1, bindings: /* @__PURE__ */ new Map([[target.name, whole]]) };
|
|
9409
|
+
}
|
|
9410
|
+
if (containsTypeParam(target)) return void 0;
|
|
9411
|
+
return parts.every((m) => isAssignable(m, target)) ? { specificity: 2, bindings: /* @__PURE__ */ new Map() } : void 0;
|
|
9412
|
+
}
|
|
9413
|
+
/** The metatables `t` has: every declaration of the most specific target
|
|
9414
|
+
* it matches, instantiated for it, in declaration order — a later one
|
|
9415
|
+
* adds to an earlier, the way a library layers on another. A type
|
|
9416
|
+
* parameter is read at its constraint. */
|
|
9417
|
+
metatablesOf(t) {
|
|
9418
|
+
if (!this.metatables.length) return [];
|
|
9419
|
+
const cached = this.metatableCache.get(t);
|
|
9420
|
+
if (cached) return cached;
|
|
9421
|
+
const expanded = this.expand(t);
|
|
9422
|
+
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);
|
|
9423
|
+
const whole = union(parts);
|
|
9424
|
+
const matches = this.metatables.flatMap((entry) => {
|
|
9425
|
+
const match = this.matchMetatable(entry, whole, parts);
|
|
9426
|
+
return match ? [{ entry, ...match }] : [];
|
|
9427
|
+
});
|
|
9428
|
+
const best = Math.max(0, ...matches.map((m) => m.specificity));
|
|
9429
|
+
const found = matches.filter((m) => m.specificity === best).map(({ entry, bindings }) => ({
|
|
9430
|
+
type: this.expand(this.reduceType(substitute(this.resolvedMetatable(entry).metatable, bindings))),
|
|
9431
|
+
source: entry.source
|
|
9432
|
+
}));
|
|
9433
|
+
this.metatableCache.set(t, found);
|
|
9434
|
+
return found;
|
|
9435
|
+
}
|
|
9436
|
+
/** The `__index` tables of `t`'s metatables, each with the library that
|
|
9437
|
+
* declared it, in declaration order. */
|
|
9438
|
+
metatableIndexes(t) {
|
|
9439
|
+
return this.metatablesOf(t).flatMap(({ type, source }) => {
|
|
9440
|
+
const parts = type.kind === "intersection" ? type.types.map((m) => this.expand(m)) : [type];
|
|
9441
|
+
return parts.flatMap((part) => {
|
|
9442
|
+
const index = part.kind === "object" ? part.properties.get("__index") : void 0;
|
|
9443
|
+
if (!index) return [];
|
|
9444
|
+
const table = this.expand(index.type);
|
|
9445
|
+
return (table.kind === "intersection" ? table.types.map((m) => this.expand(m)) : [table]).map((m) => ({ table: m, source }));
|
|
9446
|
+
});
|
|
9447
|
+
});
|
|
9448
|
+
}
|
|
9449
|
+
/** `name` read through `t`'s metatable: `text:upper()` is `upper` in the
|
|
9450
|
+
* string metatable's `__index`. The last declaration to give the name
|
|
9451
|
+
* wins. The language's own metatables are declared in the prelude, in
|
|
9452
|
+
* tilua, like any library's; a library adds to them the same way. */
|
|
9453
|
+
metatableMember(t, name) {
|
|
9454
|
+
const indexes = this.metatableIndexes(t);
|
|
9455
|
+
for (let i = indexes.length - 1; i >= 0; i--) {
|
|
9456
|
+
const { table, source } = indexes[i];
|
|
9457
|
+
const property = table.kind === "object" ? table.properties.get(name) : void 0;
|
|
9458
|
+
if (property) return { type: property.type, source };
|
|
9108
9459
|
}
|
|
9109
9460
|
return void 0;
|
|
9110
9461
|
}
|
|
9462
|
+
metatableMethod(t, name) {
|
|
9463
|
+
return this.metatableMember(t, name)?.type;
|
|
9464
|
+
}
|
|
9465
|
+
hasMetatable(t) {
|
|
9466
|
+
return this.metatablesOf(t).length > 0;
|
|
9467
|
+
}
|
|
9468
|
+
metatableMembers(t) {
|
|
9469
|
+
const members = /* @__PURE__ */ new Map();
|
|
9470
|
+
for (const { table } of this.metatableIndexes(t)) {
|
|
9471
|
+
if (table.kind !== "object") continue;
|
|
9472
|
+
for (const [name, property] of table.properties) members.set(name, property.type);
|
|
9473
|
+
}
|
|
9474
|
+
return members;
|
|
9475
|
+
}
|
|
9476
|
+
/** `text:upper()`, `scores:keys()`: the method a value's metatable gives
|
|
9477
|
+
* it, when the value has no member of that name of its own. Called with
|
|
9478
|
+
* `:`, it answers before an object's indexer, since a metatable's
|
|
9479
|
+
* `__index` is only read for a key the table does not hold. */
|
|
9480
|
+
receiverMetatableMember(objType, name) {
|
|
9481
|
+
const t = this.expand(objType);
|
|
9482
|
+
const parts = (t.kind === "union" ? t.types : [t]).map((m) => this.expand(m));
|
|
9483
|
+
if (parts.some((m) => m.kind === "object" && m.properties.has(name))) return void 0;
|
|
9484
|
+
return this.metatableMember(t, name);
|
|
9485
|
+
}
|
|
9111
9486
|
/** The most a deferred type could turn out to be. A conditional is one of
|
|
9112
9487
|
* its branches, and the true branch stands for a member of what was
|
|
9113
9488
|
* tested (`T` in `T extends U ? T : never`); an indexed access reads
|
|
@@ -9173,7 +9548,7 @@ var TypeAnalyzer = class {
|
|
|
9173
9548
|
if (index.kind !== "literal" || typeof index.value !== "string") return void 0;
|
|
9174
9549
|
const parts = this.stringParts(object);
|
|
9175
9550
|
if (!parts) return void 0;
|
|
9176
|
-
const found = parts.map((part) => this.
|
|
9551
|
+
const found = parts.map((part) => this.metatableMethod(part, index.value));
|
|
9177
9552
|
return found.every((t) => t !== void 0) ? union(found) : void 0;
|
|
9178
9553
|
}
|
|
9179
9554
|
/** The members of `t` when every one of them is a string — `"a" | "b"` is
|
|
@@ -9194,13 +9569,13 @@ var TypeAnalyzer = class {
|
|
|
9194
9569
|
* not one of them is a mistake worth reporting, rather than the nil Lua
|
|
9195
9570
|
* would hand back. */
|
|
9196
9571
|
checkStringMember(node, object, key) {
|
|
9197
|
-
if (!this.emitDiagnostics || !this.
|
|
9572
|
+
if (!this.emitDiagnostics || !this.hasMetatable(stringType)) return;
|
|
9198
9573
|
const parts = this.stringParts(object);
|
|
9199
9574
|
if (!parts) return;
|
|
9200
9575
|
const keys = (key.kind === "union" ? key.types : [key]).map((m) => this.expand(m));
|
|
9201
9576
|
if (!keys.length || !keys.every((m) => m.kind === "literal" && typeof m.value === "string")) return;
|
|
9202
9577
|
const names = keys.map((m) => String(m.value));
|
|
9203
|
-
if (names.some((name) => parts.some((part) => this.
|
|
9578
|
+
if (names.some((name) => parts.some((part) => this.metatableMethod(part, name)))) return;
|
|
9204
9579
|
this.diagnostics.push({
|
|
9205
9580
|
node,
|
|
9206
9581
|
message: names.length === 1 ? `'${names[0]}' does not exist on a string` : `'${briefType(key)}' does not name a member of a string`
|
|
@@ -9232,24 +9607,24 @@ var TypeAnalyzer = class {
|
|
|
9232
9607
|
switch (part.kind) {
|
|
9233
9608
|
case "primitive":
|
|
9234
9609
|
if (part.name === "nil") continue;
|
|
9235
|
-
if (part.name === "string" && !this.
|
|
9236
|
-
if (part.name === "string" && this.
|
|
9610
|
+
if (part.name === "string" && !this.hasMetatable(part)) return false;
|
|
9611
|
+
if (part.name === "string" && this.metatableMethod(part, name)) continue;
|
|
9237
9612
|
break;
|
|
9238
9613
|
case "literal":
|
|
9239
9614
|
case "templateLiteral":
|
|
9240
9615
|
if (part.kind === "templateLiteral" || part.base === "string") {
|
|
9241
|
-
if (!this.
|
|
9616
|
+
if (!this.hasMetatable(part)) return false;
|
|
9242
9617
|
}
|
|
9243
|
-
if (this.
|
|
9618
|
+
if (this.metatableMethod(part, name)) continue;
|
|
9244
9619
|
break;
|
|
9245
9620
|
case "array":
|
|
9246
9621
|
case "tuple":
|
|
9247
|
-
if (!this.
|
|
9248
|
-
if (this.
|
|
9622
|
+
if (!this.hasMetatable(part)) return false;
|
|
9623
|
+
if (this.metatableMethod(part, name)) continue;
|
|
9249
9624
|
break;
|
|
9250
9625
|
case "object":
|
|
9251
9626
|
if (part.properties.has(name)) continue;
|
|
9252
|
-
if (part.indexer || part.properties.size === 0 && !part.class || this.
|
|
9627
|
+
if (part.indexer || part.properties.size === 0 && !part.class || this.metatableMethod(part, name)) return false;
|
|
9253
9628
|
break;
|
|
9254
9629
|
default:
|
|
9255
9630
|
return false;
|
|
@@ -9384,10 +9759,10 @@ var TypeAnalyzer = class {
|
|
|
9384
9759
|
if (p) return p.optional ? optional(p.type) : p.type;
|
|
9385
9760
|
if (t.indexer) return t.indexer.value;
|
|
9386
9761
|
}
|
|
9387
|
-
const built = this.
|
|
9762
|
+
const built = this.metatableMethod(t, name);
|
|
9388
9763
|
if (built) return built;
|
|
9389
9764
|
if (t.kind === "union") {
|
|
9390
|
-
const built2 = this.
|
|
9765
|
+
const built2 = this.metatableMethod(t, name);
|
|
9391
9766
|
if (built2) return built2;
|
|
9392
9767
|
return union(t.types.map((m) => this.propertyType(m, name)));
|
|
9393
9768
|
}
|
|
@@ -9683,6 +10058,27 @@ var TypeAnalyzer = class {
|
|
|
9683
10058
|
elseEnv = whenFalse;
|
|
9684
10059
|
}
|
|
9685
10060
|
branches.push(this.infer(expr.alternate, elseEnv));
|
|
10061
|
+
const bodies = [...expr.clauses.map((c) => c.body), expr.alternate].map(unwrapParens);
|
|
10062
|
+
const isEmpty = (e) => e.type === "TableExpression" && e.fields.length === 0 || e.type === "ArrayExpression" && e.elements.length === 0;
|
|
10063
|
+
const others = union(branches.filter((_, i) => !isEmpty(bodies[i])));
|
|
10064
|
+
const parts = (others.kind === "union" ? others.types : [others]).map((m) => this.expand(m)).filter((m) => !(m.kind === "primitive" && m.name === "nil"));
|
|
10065
|
+
const fits = (e) => {
|
|
10066
|
+
if (!parts.length) return void 0;
|
|
10067
|
+
if (e.type === "ArrayExpression") {
|
|
10068
|
+
if (parts.every((m) => m.kind === "array")) return others;
|
|
10069
|
+
return parts.every((m) => m.kind === "array" || m.kind === "tuple") ? tuple([]) : void 0;
|
|
10070
|
+
}
|
|
10071
|
+
return parts.every((m) => m.kind === "object" && !m.class && [...m.properties.values()].every((prop) => prop.optional)) ? others : void 0;
|
|
10072
|
+
};
|
|
10073
|
+
if (bodies.some(isEmpty) && others.kind !== "never" && others.kind !== "any" && bodies.every((body) => !isEmpty(body) || fits(body))) {
|
|
10074
|
+
const typed = bodies.map((body, i) => {
|
|
10075
|
+
if (!isEmpty(body)) return branches[i];
|
|
10076
|
+
const t = fits(body);
|
|
10077
|
+
this.typeOf.set(body, t);
|
|
10078
|
+
return t;
|
|
10079
|
+
});
|
|
10080
|
+
return union(typed);
|
|
10081
|
+
}
|
|
9686
10082
|
return union(branches);
|
|
9687
10083
|
}
|
|
9688
10084
|
}
|
|
@@ -9788,7 +10184,9 @@ var TypeAnalyzer = class {
|
|
|
9788
10184
|
this.checkPrivateMember(expr.method, objType, expr.method.name);
|
|
9789
10185
|
this.checkStringMember(expr.method, objType, literal(expr.method.name));
|
|
9790
10186
|
this.checkMissingMember(expr.method, objType, expr.method.name);
|
|
9791
|
-
const
|
|
10187
|
+
const fromMetatable = this.receiverMetatableMember(objType, expr.method.name);
|
|
10188
|
+
if (fromMetatable) this.methodSources.set(expr, fromMetatable.source);
|
|
10189
|
+
const method = fromMetatable?.type ?? this.propertyType(objType, expr.method.name);
|
|
9792
10190
|
const united = this.unionSignatures(method);
|
|
9793
10191
|
const fns = united ?? this.overloadsOf(method);
|
|
9794
10192
|
const explicit = this.explicitTypeArguments(expr, fns);
|
|
@@ -9987,23 +10385,87 @@ var TypeAnalyzer = class {
|
|
|
9987
10385
|
if (wantedTuple && !asConst) return this.inferTupleLiteral(expr, env, wantedTuple);
|
|
9988
10386
|
const elems = [];
|
|
9989
10387
|
let hadSpread = false;
|
|
10388
|
+
const written = [];
|
|
9990
10389
|
for (const el of expr.elements) {
|
|
9991
10390
|
if (el.type === "SpreadElement") {
|
|
9992
10391
|
hadSpread = true;
|
|
9993
|
-
const
|
|
9994
|
-
|
|
9995
|
-
|
|
9996
|
-
else elems.push(unknownType);
|
|
10392
|
+
const spread = this.infer(el.argument, env);
|
|
10393
|
+
written.push({ spread: true, type: spread });
|
|
10394
|
+
elems.push(...this.spreadElements(spread));
|
|
9997
10395
|
} else {
|
|
9998
|
-
|
|
10396
|
+
const t = asConst ? this.inferAsConst(el, env) : this.infer(el, env);
|
|
10397
|
+
written.push({ spread: false, type: asConst ? t : this.widenUnlessAsked(t, el) });
|
|
10398
|
+
elems.push(t);
|
|
9999
10399
|
}
|
|
10000
10400
|
}
|
|
10001
10401
|
if (asConst && !hadSpread) return tuple(elems);
|
|
10402
|
+
if (hadSpread) {
|
|
10403
|
+
const tuples = this.spreadTuples(written);
|
|
10404
|
+
if (tuples) return union(tuples.map((elements) => tuple(elements)));
|
|
10405
|
+
}
|
|
10002
10406
|
return arrayOf(elems.length ? union(elems.map((t, i) => {
|
|
10003
10407
|
const element = expr.elements[i];
|
|
10004
10408
|
return asConst || !element || element.type === "SpreadElement" ? t : this.widenUnlessAsked(t, element);
|
|
10005
10409
|
})) : unknownType);
|
|
10006
10410
|
}
|
|
10411
|
+
/** The tuples an array literal with spreads is, when every spread is a
|
|
10412
|
+
* tuple of known length or a union of them: one per way the unions go.
|
|
10413
|
+
* `undefined` when a spread's length is not known (an array, a tuple
|
|
10414
|
+
* with a rest), or the ways multiply past what is worth listing. */
|
|
10415
|
+
spreadTuples(written) {
|
|
10416
|
+
const MAX = 32;
|
|
10417
|
+
let variants = [[]];
|
|
10418
|
+
for (const { spread, type } of written) {
|
|
10419
|
+
if (!spread) {
|
|
10420
|
+
variants = variants.map((v) => [...v, type]);
|
|
10421
|
+
continue;
|
|
10422
|
+
}
|
|
10423
|
+
const options = this.fixedTuples(type);
|
|
10424
|
+
if (!options) return void 0;
|
|
10425
|
+
const next = [];
|
|
10426
|
+
for (const v of variants) for (const option of options) next.push([...v, ...option]);
|
|
10427
|
+
if (next.length > MAX) return void 0;
|
|
10428
|
+
variants = next;
|
|
10429
|
+
}
|
|
10430
|
+
return variants;
|
|
10431
|
+
}
|
|
10432
|
+
/** A tuple of known length's members, or a union of such tuples' — each
|
|
10433
|
+
* a way the value can be. `nil` in a union spreads nothing. */
|
|
10434
|
+
fixedTuples(t, depth = 0) {
|
|
10435
|
+
if (depth > 8) return void 0;
|
|
10436
|
+
const held = this.expand(t);
|
|
10437
|
+
if (held.kind === "tuple") return held.rest ? void 0 : [held.elements];
|
|
10438
|
+
if (held.kind !== "union") return void 0;
|
|
10439
|
+
const out = [];
|
|
10440
|
+
for (const member of held.types) {
|
|
10441
|
+
if (member.kind === "primitive" && member.name === "nil") continue;
|
|
10442
|
+
const options = this.fixedTuples(member, depth + 1);
|
|
10443
|
+
if (!options) return void 0;
|
|
10444
|
+
out.push(...options);
|
|
10445
|
+
}
|
|
10446
|
+
return out.length ? out : void 0;
|
|
10447
|
+
}
|
|
10448
|
+
/** What `...xs` puts into an array: an array's element, a tuple's members,
|
|
10449
|
+
* and for a union each member's — `...(host ? extra : [])` adds `extra`'s
|
|
10450
|
+
* elements and the empty tuple nothing. Anything else, `unknown`. */
|
|
10451
|
+
spreadElements(t, depth = 0) {
|
|
10452
|
+
const held = this.expand(t);
|
|
10453
|
+
if (depth > 8) return [unknownType];
|
|
10454
|
+
switch (held.kind) {
|
|
10455
|
+
case "array":
|
|
10456
|
+
return [held.element];
|
|
10457
|
+
case "tuple":
|
|
10458
|
+
return tupleMembers(held);
|
|
10459
|
+
case "union":
|
|
10460
|
+
return held.types.filter((m) => !(m.kind === "primitive" && m.name === "nil")).flatMap((m) => this.spreadElements(m, depth + 1));
|
|
10461
|
+
case "typeParam":
|
|
10462
|
+
return held.constraint ? this.spreadElements(held.constraint, depth + 1) : [unknownType];
|
|
10463
|
+
case "any":
|
|
10464
|
+
return [anyType];
|
|
10465
|
+
default:
|
|
10466
|
+
return [unknownType];
|
|
10467
|
+
}
|
|
10468
|
+
}
|
|
10007
10469
|
/** `[a, b, ...more]` where a tuple is wanted: the places it names are the
|
|
10008
10470
|
* tuple's, and what comes after them — more elements, or a spread — is
|
|
10009
10471
|
* its rest. */
|
|
@@ -10014,10 +10476,7 @@ var TypeAnalyzer = class {
|
|
|
10014
10476
|
expr.elements.forEach((element, i) => {
|
|
10015
10477
|
if (element.type === "SpreadElement") {
|
|
10016
10478
|
spread = true;
|
|
10017
|
-
|
|
10018
|
-
if (held.kind === "array") tail.push(held.element);
|
|
10019
|
-
else if (held.kind === "tuple") tail.push(...tupleMembers(held));
|
|
10020
|
-
else tail.push(unknownType);
|
|
10479
|
+
tail.push(...this.spreadElements(this.infer(element.argument, env)));
|
|
10021
10480
|
return;
|
|
10022
10481
|
}
|
|
10023
10482
|
const t = this.widenUnlessAsked(this.infer(element, env), element);
|
|
@@ -10034,6 +10493,7 @@ var TypeAnalyzer = class {
|
|
|
10034
10493
|
* The context was recorded by `applyContext` before the value was
|
|
10035
10494
|
* inferred, so this is a lookup rather than a second pass. */
|
|
10036
10495
|
widenUnlessAsked(value, at) {
|
|
10496
|
+
if (!writesLiteral(at)) return value;
|
|
10037
10497
|
const wanted = this.expectedTypeOf.get(at);
|
|
10038
10498
|
if (value.kind === "literal") {
|
|
10039
10499
|
const arms = this.contextArms.get(at);
|
|
@@ -11030,6 +11490,8 @@ function containsTypeQuery(node) {
|
|
|
11030
11490
|
return found;
|
|
11031
11491
|
}
|
|
11032
11492
|
var STRING_INTRINSICS = /* @__PURE__ */ new Set(["Uppercase", "Lowercase", "Capitalize", "Uncapitalize"]);
|
|
11493
|
+
var OBJECT_INTRINSICS = /* @__PURE__ */ new Set(["ObjectKeys", "ObjectValues", "ObjectEntries"]);
|
|
11494
|
+
var INTRINSICS = /* @__PURE__ */ new Set([...STRING_INTRINSICS, ...OBJECT_INTRINSICS]);
|
|
11033
11495
|
function briefType(t) {
|
|
11034
11496
|
if (t.kind === "union" && t.types.length > 8) {
|
|
11035
11497
|
const shown = t.types.slice(0, 6).map(formatType).join(" | ");
|
|
@@ -11237,7 +11699,8 @@ function resolveTypeLibraries(config, host = nodeHost) {
|
|
|
11237
11699
|
}
|
|
11238
11700
|
addFile(entryFile);
|
|
11239
11701
|
const lowering = loweringModule(directory, host, problems, config);
|
|
11240
|
-
|
|
11702
|
+
const library = files.findIndex((file) => pathKey(file) === pathKey(entryFile));
|
|
11703
|
+
if (lowering) lowerings.push({ ...lowering, library });
|
|
11241
11704
|
};
|
|
11242
11705
|
for (const entry of config.types) {
|
|
11243
11706
|
const relative = entry.startsWith("./") || entry.startsWith("../") || entry.startsWith("/") || /^[A-Za-z]:[\\/]/.test(entry);
|