luaut-parser 3.1.0 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +339 -14
- package/dist/index.cjs +1620 -128
- package/dist/index.d.cts +226 -13
- package/dist/index.d.ts +226 -13
- package/dist/index.js +1619 -128
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -70,6 +70,7 @@ var Punctuators = [
|
|
|
70
70
|
",",
|
|
71
71
|
".",
|
|
72
72
|
"?",
|
|
73
|
+
"=>",
|
|
73
74
|
"->",
|
|
74
75
|
"&",
|
|
75
76
|
"|",
|
|
@@ -594,6 +595,13 @@ function applyDirectives(directives, diagnostics, lineOf) {
|
|
|
594
595
|
var UNUSED_EXPECT_ERROR = "Unused '@luaut-expect-error' directive";
|
|
595
596
|
|
|
596
597
|
// src/ast/builders.ts
|
|
598
|
+
function bindThis(func, at) {
|
|
599
|
+
bindThisParam(func.params, at);
|
|
600
|
+
func.isMethod = true;
|
|
601
|
+
}
|
|
602
|
+
function bindThisParam(params, at) {
|
|
603
|
+
params.unshift({ type: "FunctionParameter", name: "this", ...spanFrom(at, at) });
|
|
604
|
+
}
|
|
597
605
|
var ParseError = class extends Error {
|
|
598
606
|
constructor(message, line, column) {
|
|
599
607
|
super(`${message} (${line}:${column})`);
|
|
@@ -687,10 +695,13 @@ var Parser = class {
|
|
|
687
695
|
indentation;
|
|
688
696
|
/** Populated in recovery mode. */
|
|
689
697
|
errors = [];
|
|
690
|
-
/** Recovery found a block without its `
|
|
698
|
+
/** Recovery found a block without its `}`. */
|
|
691
699
|
missingEnd = false;
|
|
692
700
|
/** The column of the first token on each line, for `indentation`. */
|
|
693
701
|
lineIndent;
|
|
702
|
+
/** Inside a class body, where `super` means the base class. Outside
|
|
703
|
+
* one it is an ordinary name, so existing code using it still reads. */
|
|
704
|
+
classDepth = 0;
|
|
694
705
|
constructor(tokens, options = {}) {
|
|
695
706
|
this.tokens = tokens;
|
|
696
707
|
this.recover = options.recover ?? false;
|
|
@@ -829,8 +840,12 @@ var Parser = class {
|
|
|
829
840
|
expressionOr(stop) {
|
|
830
841
|
return this.attempt(() => this.parseExpression(), stop, (start, from) => this.errorExpression(start, from));
|
|
831
842
|
}
|
|
843
|
+
/** A comma-separated list of values: a `return`'s, a declaration's, an
|
|
844
|
+
* assignment's. `...xs` spreads an array into it, as in a call's
|
|
845
|
+
* arguments; bare `...` is the vararg pack, as it always was. */
|
|
832
846
|
expressionListOr(stop) {
|
|
833
|
-
const
|
|
847
|
+
const until = () => stop() || this.checkPunctuator(",");
|
|
848
|
+
const item = () => this.checkOperator("...") && this.startsSpread() ? this.parseSpreadArgument(until) : this.expressionOr(until);
|
|
834
849
|
const list = [item()];
|
|
835
850
|
while (this.matchPunctuator(",")) list.push(item());
|
|
836
851
|
return list;
|
|
@@ -998,7 +1013,62 @@ var Parser = class {
|
|
|
998
1013
|
// Block / Statement
|
|
999
1014
|
// ============================================================
|
|
1000
1015
|
isBlockEnd() {
|
|
1001
|
-
return this.isAtEnd() || this.checkKeyword("end") || this.checkKeyword("else") || this.checkKeyword("elseif") || this.checkKeyword("until");
|
|
1016
|
+
return this.isAtEnd() || this.checkPunctuator("}") || this.checkKeyword("end") || this.checkKeyword("else") || this.checkKeyword("elseif") || this.checkKeyword("until");
|
|
1017
|
+
}
|
|
1018
|
+
// ============================================================
|
|
1019
|
+
// Bodies
|
|
1020
|
+
// ------------------------------------------------------------
|
|
1021
|
+
// luaut writes a block in braces — `if (ready) { ... }`, `function f() {
|
|
1022
|
+
// ... }`. The `end` spellings Lua uses are still read, so a file written
|
|
1023
|
+
// in them keeps working while it is being moved over.
|
|
1024
|
+
//
|
|
1025
|
+
// A condition is in parentheses because `f {}` is a call: without them
|
|
1026
|
+
// `if ready { ... }` would be a call of `ready` and then a block. With
|
|
1027
|
+
// them the form is decided by looking past the closing parenthesis, which
|
|
1028
|
+
// is why a parenthesized condition in the older spelling still reads.
|
|
1029
|
+
// ============================================================
|
|
1030
|
+
/** `{ ... }` — a block in braces. */
|
|
1031
|
+
parseBraceBlock() {
|
|
1032
|
+
const brace = this.advance();
|
|
1033
|
+
const body = this.parseBlock(brace);
|
|
1034
|
+
if (this.matchPunctuator("}")) return body;
|
|
1035
|
+
if (!this.recover) this.error(`Expected '}' to close the block opened on line ${brace.line.start}`);
|
|
1036
|
+
this.softError(`Expected '}' to close the block opened on line ${brace.line.start}`);
|
|
1037
|
+
this.missingEnd = true;
|
|
1038
|
+
return body;
|
|
1039
|
+
}
|
|
1040
|
+
/** The `{ ... }` a construct's body is written in. Half-written — the
|
|
1041
|
+
* `{` not typed yet — it is empty and says so, rather than throwing the
|
|
1042
|
+
* whole construct away: what has been written is what an editor answers
|
|
1043
|
+
* from. */
|
|
1044
|
+
parseBracedBody(what) {
|
|
1045
|
+
if (this.checkPunctuator("{")) return this.parseBraceBlock();
|
|
1046
|
+
const at = this.current();
|
|
1047
|
+
if (!this.recover) this.error(`Expected '{' to open the body of '${what}'`);
|
|
1048
|
+
this.softError(`Expected '{' to open the body of '${what}'`);
|
|
1049
|
+
return { type: "Block", statements: [], ...spanFrom(at, at) };
|
|
1050
|
+
}
|
|
1051
|
+
/** Does a `{` follow the parenthesized group starting here? That is what
|
|
1052
|
+
* tells `if (ready) { ... }` from `if (ready) then ... end`. */
|
|
1053
|
+
braceFollowsGroup() {
|
|
1054
|
+
if (!this.checkPunctuator("(")) return false;
|
|
1055
|
+
const closers = { "(": ")", "[": "]", "{": "}" };
|
|
1056
|
+
const stack = [];
|
|
1057
|
+
for (let i = 0; ; i++) {
|
|
1058
|
+
const token = this.peek(i);
|
|
1059
|
+
if (token.type === "EOF") return false;
|
|
1060
|
+
if (token.type === "Punctuator") {
|
|
1061
|
+
const value = String(token.value);
|
|
1062
|
+
if (closers[value]) stack.push(closers[value]);
|
|
1063
|
+
else if (value === stack[stack.length - 1]) {
|
|
1064
|
+
stack.pop();
|
|
1065
|
+
if (!stack.length) {
|
|
1066
|
+
const next = this.peek(i + 1);
|
|
1067
|
+
return next.type === "Punctuator" && next.value === "{";
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1002
1072
|
}
|
|
1003
1073
|
/** `opener` is the token that began the block (`if`, `function`, ...), for
|
|
1004
1074
|
* indentation recovery. */
|
|
@@ -1110,6 +1180,9 @@ var Parser = class {
|
|
|
1110
1180
|
if (t.type === "Identifier" && t.value === "type" && this.peek(1).type === "Identifier") {
|
|
1111
1181
|
return this.parseTypeAliasStatement();
|
|
1112
1182
|
}
|
|
1183
|
+
if (t.type === "Identifier" && t.value === "class" && this.peek(1).type === "Identifier") {
|
|
1184
|
+
return this.parseClassDeclaration();
|
|
1185
|
+
}
|
|
1113
1186
|
if (t.type === "Identifier" && t.value === "declare") {
|
|
1114
1187
|
const p1 = this.peek(1);
|
|
1115
1188
|
if (p1.type === "Identifier" && p1.value === "class" && this.peek(2).type === "Identifier") {
|
|
@@ -1135,6 +1208,7 @@ var Parser = class {
|
|
|
1135
1208
|
name: p.name || void 0,
|
|
1136
1209
|
id: p.name ? nameIdentifier(p.name, p) : void 0,
|
|
1137
1210
|
optional: p.optional,
|
|
1211
|
+
rest: p.rest,
|
|
1138
1212
|
typeAnnotation: p.typeAnnotation ?? { type: "TypeReference", base: "any", typeArguments: [], line: p.line, column: p.column },
|
|
1139
1213
|
line: p.line,
|
|
1140
1214
|
column: p.column
|
|
@@ -1283,7 +1357,7 @@ var Parser = class {
|
|
|
1283
1357
|
this.advance();
|
|
1284
1358
|
if (this.checkIdentifierValue("default")) {
|
|
1285
1359
|
this.advance();
|
|
1286
|
-
const declaration = this.parseExpression(0);
|
|
1360
|
+
const declaration = this.checkIdentifierValue("class") && this.peek(1).type === "Identifier" && !this.punctuatorAt(2, ":") && !this.operatorAt(2, "=") && !this.punctuatorAt(2, "(") ? this.parseClassDeclaration() : this.parseExpression(0);
|
|
1287
1361
|
return { type: "ExportDefaultStatement", declaration, ...spanFrom(start, this.previous()) };
|
|
1288
1362
|
}
|
|
1289
1363
|
if (this.checkIdentifierValue("type") && this.peek(1).type === "Identifier") {
|
|
@@ -1294,6 +1368,10 @@ var Parser = class {
|
|
|
1294
1368
|
const declaration = this.parseVariableDeclaration();
|
|
1295
1369
|
return { type: "ExportStatement", declaration, ...spanFrom(start, this.previous()) };
|
|
1296
1370
|
}
|
|
1371
|
+
if (this.checkIdentifierValue("class") && this.peek(1).type === "Identifier") {
|
|
1372
|
+
const declaration = this.parseClassDeclaration();
|
|
1373
|
+
return { type: "ExportStatement", declaration, ...spanFrom(start, this.previous()) };
|
|
1374
|
+
}
|
|
1297
1375
|
if (this.checkKeyword("function")) {
|
|
1298
1376
|
const declaration = this.parseFunctionStatement(true);
|
|
1299
1377
|
if (declaration.type !== "FunctionDeclaration") this.error("An exported function needs a plain name: 'export function name()'");
|
|
@@ -1322,7 +1400,7 @@ var Parser = class {
|
|
|
1322
1400
|
const source = this.parseModuleSource();
|
|
1323
1401
|
return { type: "ExportAllStatement", source, ...spanFrom(start, this.previous()) };
|
|
1324
1402
|
}
|
|
1325
|
-
this.error("Expected 'const', 'let', 'function', 'type', 'default', '{' or '*' after 'export'");
|
|
1403
|
+
this.error("Expected 'const', 'let', 'function', 'class', 'type', 'default', '{' or '*' after 'export'");
|
|
1326
1404
|
}
|
|
1327
1405
|
// `const x = ...` / `let x, y = ...`.
|
|
1328
1406
|
// luaut has no `local` — `const` bindings are immutable, `let` mutable.
|
|
@@ -1350,63 +1428,63 @@ var Parser = class {
|
|
|
1350
1428
|
parseIfStatement() {
|
|
1351
1429
|
const start = this.current();
|
|
1352
1430
|
this.expectKeyword("if");
|
|
1431
|
+
return this.parseBracedIf(start);
|
|
1432
|
+
}
|
|
1433
|
+
parseBracedIf(start) {
|
|
1353
1434
|
const clauses = [];
|
|
1354
|
-
const
|
|
1355
|
-
const cond = this.expressionOr(untilThen);
|
|
1356
|
-
this.expectKeywordSoft("then");
|
|
1357
|
-
const body = this.parseBlock(start);
|
|
1358
|
-
clauses.push({ type: "IfClause", condition: cond, body, ...spanFrom(cond, this.previous()) });
|
|
1359
|
-
while (this.checkKeyword("elseif")) {
|
|
1435
|
+
const clause = () => {
|
|
1360
1436
|
const clauseStart = this.current();
|
|
1361
|
-
this.
|
|
1362
|
-
const
|
|
1363
|
-
this.
|
|
1364
|
-
const
|
|
1365
|
-
clauses.push({ type: "IfClause", condition
|
|
1366
|
-
}
|
|
1437
|
+
this.expectPunctuator("(");
|
|
1438
|
+
const condition = this.expressionOr(() => this.checkPunctuator(")"));
|
|
1439
|
+
this.expectCloser(")");
|
|
1440
|
+
const body = this.parseBracedBody("if");
|
|
1441
|
+
clauses.push({ type: "IfClause", condition, body, ...spanFrom(clauseStart, this.previous()) });
|
|
1442
|
+
};
|
|
1443
|
+
clause();
|
|
1367
1444
|
let alternate;
|
|
1368
|
-
|
|
1369
|
-
|
|
1445
|
+
while (this.checkKeyword("elseif") || this.checkKeyword("else")) {
|
|
1446
|
+
const isElse = this.checkKeyword("else");
|
|
1447
|
+
this.advance();
|
|
1448
|
+
if (!isElse) {
|
|
1449
|
+
clause();
|
|
1450
|
+
continue;
|
|
1451
|
+
}
|
|
1452
|
+
alternate = this.parseBracedBody("else");
|
|
1453
|
+
break;
|
|
1370
1454
|
}
|
|
1371
|
-
this.expectEnd(start);
|
|
1372
1455
|
return { type: "IfStatement", clauses, alternate, ...spanFrom(start, this.previous()) };
|
|
1373
1456
|
}
|
|
1374
1457
|
parseWhileStatement() {
|
|
1375
1458
|
const start = this.current();
|
|
1376
1459
|
this.expectKeyword("while");
|
|
1377
|
-
|
|
1378
|
-
this.
|
|
1379
|
-
|
|
1380
|
-
this.
|
|
1460
|
+
this.expectPunctuator("(");
|
|
1461
|
+
const condition = this.expressionOr(() => this.checkPunctuator(")"));
|
|
1462
|
+
this.expectCloser(")");
|
|
1463
|
+
const body = this.parseBracedBody("while");
|
|
1381
1464
|
return { type: "WhileStatement", condition, body, ...spanFrom(start, this.previous()) };
|
|
1382
1465
|
}
|
|
1383
1466
|
parseRepeatStatement() {
|
|
1384
1467
|
const start = this.current();
|
|
1385
1468
|
this.expectKeyword("repeat");
|
|
1386
|
-
const body = this.
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
} else {
|
|
1392
|
-
this.softError(`Expected 'until' to close 'repeat' on line ${start.line.start}`);
|
|
1393
|
-
this.missingEnd = true;
|
|
1394
|
-
condition = this.errorExpression(this.current(), this.cursor);
|
|
1395
|
-
}
|
|
1469
|
+
const body = this.parseBracedBody("repeat");
|
|
1470
|
+
this.expectKeyword("until");
|
|
1471
|
+
this.expectPunctuator("(");
|
|
1472
|
+
const condition = this.expressionOr(() => this.checkPunctuator(")"));
|
|
1473
|
+
this.expectCloser(")");
|
|
1396
1474
|
return { type: "RepeatStatement", body, condition, ...spanFrom(start, this.previous()) };
|
|
1397
1475
|
}
|
|
1398
1476
|
parseDoStatement() {
|
|
1399
1477
|
const start = this.current();
|
|
1400
1478
|
this.expectKeyword("do");
|
|
1401
|
-
const body = this.
|
|
1402
|
-
this.expectEnd(start);
|
|
1479
|
+
const body = this.parseBracedBody("do");
|
|
1403
1480
|
return { type: "DoStatement", body, ...spanFrom(start, this.previous()) };
|
|
1404
1481
|
}
|
|
1405
1482
|
parseForStatement() {
|
|
1406
1483
|
const start = this.current();
|
|
1407
1484
|
this.expectKeyword("for");
|
|
1485
|
+
this.expectPunctuator("(");
|
|
1408
1486
|
const first = this.parseBindingTarget(true);
|
|
1409
|
-
const untilDo = () => this.
|
|
1487
|
+
const untilDo = () => this.checkPunctuator(")");
|
|
1410
1488
|
if (first.type === "IdentifierPattern" && this.matchOperator("=")) {
|
|
1411
1489
|
const from = this.expressionOr(() => untilDo() || this.checkPunctuator(","));
|
|
1412
1490
|
this.expectPunctuator(",");
|
|
@@ -1415,9 +1493,7 @@ var Parser = class {
|
|
|
1415
1493
|
if (this.matchPunctuator(",")) {
|
|
1416
1494
|
step = this.expressionOr(untilDo);
|
|
1417
1495
|
}
|
|
1418
|
-
this.
|
|
1419
|
-
const body2 = this.parseBlock(start);
|
|
1420
|
-
this.expectEnd(start);
|
|
1496
|
+
const body2 = this.parseForBody();
|
|
1421
1497
|
return {
|
|
1422
1498
|
type: "NumericForStatement",
|
|
1423
1499
|
variable: this.identifierPatternToTypedIdentifier(first),
|
|
@@ -1434,9 +1510,7 @@ var Parser = class {
|
|
|
1434
1510
|
}
|
|
1435
1511
|
this.expectKeyword("in");
|
|
1436
1512
|
const iterators = this.expressionListOr(untilDo);
|
|
1437
|
-
this.
|
|
1438
|
-
const body = this.parseBlock(start);
|
|
1439
|
-
this.expectEnd(start);
|
|
1513
|
+
const body = this.parseForBody();
|
|
1440
1514
|
return {
|
|
1441
1515
|
type: "GenericForStatement",
|
|
1442
1516
|
variables,
|
|
@@ -1445,6 +1519,10 @@ var Parser = class {
|
|
|
1445
1519
|
...spanFrom(start, this.previous())
|
|
1446
1520
|
};
|
|
1447
1521
|
}
|
|
1522
|
+
parseForBody() {
|
|
1523
|
+
this.expectCloser(")");
|
|
1524
|
+
return this.parseBracedBody("for");
|
|
1525
|
+
}
|
|
1448
1526
|
/** `function name() end` declares `name`; `function a.b() end` and
|
|
1449
1527
|
* `function T:m() end` define a member. */
|
|
1450
1528
|
/** `exported` — the `export` before this `function` has been consumed, so
|
|
@@ -1511,6 +1589,183 @@ var Parser = class {
|
|
|
1511
1589
|
if (!this.recover) throw error;
|
|
1512
1590
|
this.record(error);
|
|
1513
1591
|
}
|
|
1592
|
+
/** `class Name extends Base <members> end`.
|
|
1593
|
+
*
|
|
1594
|
+
* The body is a block like every other in luaut, closed by `end` — not a
|
|
1595
|
+
* brace-delimited list. Members are written the way the same thing is
|
|
1596
|
+
* written outside a class: a field like a field (`x: number`), a method
|
|
1597
|
+
* like a function (`function m() ... end`). */
|
|
1598
|
+
parseClassDeclaration() {
|
|
1599
|
+
const start = this.current();
|
|
1600
|
+
this.advance();
|
|
1601
|
+
const name = this.parseIdentifier();
|
|
1602
|
+
const typeParams = this.checkOperator("<") ? this.parseGenericTypeParameterList() : [];
|
|
1603
|
+
const { superclass, superArguments } = this.parseExtends();
|
|
1604
|
+
const members = this.parseClassBody(start);
|
|
1605
|
+
return {
|
|
1606
|
+
type: "ClassDeclaration",
|
|
1607
|
+
name,
|
|
1608
|
+
typeParams,
|
|
1609
|
+
superclass,
|
|
1610
|
+
superArguments,
|
|
1611
|
+
members,
|
|
1612
|
+
...spanFrom(start, this.previous())
|
|
1613
|
+
};
|
|
1614
|
+
}
|
|
1615
|
+
/** `class ... end` as a value. It may be named — the name is for the class
|
|
1616
|
+
* itself, not for the scope around it — and takes no type parameters,
|
|
1617
|
+
* since nothing could write the arguments. */
|
|
1618
|
+
parseClassExpression() {
|
|
1619
|
+
const start = this.current();
|
|
1620
|
+
this.advance();
|
|
1621
|
+
const name = this.checkType("Identifier") && !this.checkIdentifierValue("extends") && !this.punctuatorAt(1, ":") && !this.operatorAt(1, "=") && !this.punctuatorAt(1, "(") ? this.parseIdentifier() : void 0;
|
|
1622
|
+
if (this.checkOperator("<")) {
|
|
1623
|
+
this.error("A class written as a value takes no type parameters: nothing could write the arguments");
|
|
1624
|
+
}
|
|
1625
|
+
const { superclass, superArguments } = this.parseExtends();
|
|
1626
|
+
const members = this.parseClassBody(start);
|
|
1627
|
+
return { type: "ClassExpression", name, superclass, superArguments, members, ...spanFrom(start, this.previous()) };
|
|
1628
|
+
}
|
|
1629
|
+
/** `extends Base` / `extends Box<number>`. */
|
|
1630
|
+
parseExtends() {
|
|
1631
|
+
if (!this.checkIdentifierValue("extends")) return {};
|
|
1632
|
+
this.advance();
|
|
1633
|
+
const superclass = this.parseIdentifier();
|
|
1634
|
+
let superArguments;
|
|
1635
|
+
if (this.checkOperator("<")) {
|
|
1636
|
+
const written = this.tryTypeArguments();
|
|
1637
|
+
if (written) superArguments = written;
|
|
1638
|
+
}
|
|
1639
|
+
return { superclass, superArguments };
|
|
1640
|
+
}
|
|
1641
|
+
parseClassBody(start) {
|
|
1642
|
+
void start;
|
|
1643
|
+
if (!this.checkPunctuator("{")) this.error("Expected '{' to open the class body");
|
|
1644
|
+
this.advance();
|
|
1645
|
+
const members = [];
|
|
1646
|
+
this.classDepth++;
|
|
1647
|
+
try {
|
|
1648
|
+
while (!this.checkPunctuator("}") && !this.isAtEnd()) {
|
|
1649
|
+
if (this.matchPunctuator(",") || this.matchPunctuator(";")) continue;
|
|
1650
|
+
const member = this.parseClassMember();
|
|
1651
|
+
if (member) members.push(member);
|
|
1652
|
+
}
|
|
1653
|
+
} finally {
|
|
1654
|
+
this.classDepth--;
|
|
1655
|
+
}
|
|
1656
|
+
this.expectCloser("}");
|
|
1657
|
+
return members;
|
|
1658
|
+
}
|
|
1659
|
+
/** `<A, B>` in a type position that is not a call: the arguments a class
|
|
1660
|
+
* extends its base with. */
|
|
1661
|
+
tryTypeArguments() {
|
|
1662
|
+
const saved = this.cursor;
|
|
1663
|
+
try {
|
|
1664
|
+
this.expectOperator("<");
|
|
1665
|
+
const args = [this.parseType()];
|
|
1666
|
+
while (this.matchPunctuator(",")) args.push(this.parseType());
|
|
1667
|
+
this.expectOperator(">");
|
|
1668
|
+
return args;
|
|
1669
|
+
} catch (error) {
|
|
1670
|
+
if (error instanceof ParseError || error instanceof ParseRecover) {
|
|
1671
|
+
this.cursor = saved;
|
|
1672
|
+
return void 0;
|
|
1673
|
+
}
|
|
1674
|
+
throw error;
|
|
1675
|
+
}
|
|
1676
|
+
}
|
|
1677
|
+
parseClassMember() {
|
|
1678
|
+
const start = this.current();
|
|
1679
|
+
const isStatic = this.checkIdentifierValue("static") && !this.punctuatorAt(1, ":") && !this.operatorAt(1, "=");
|
|
1680
|
+
if (isStatic) this.advance();
|
|
1681
|
+
if (this.checkKeyword("function")) {
|
|
1682
|
+
this.advance();
|
|
1683
|
+
const memberName = this.parseIdentifier();
|
|
1684
|
+
const signatures = [];
|
|
1685
|
+
let written = memberName;
|
|
1686
|
+
while (true) {
|
|
1687
|
+
const head = this.parseFunctionHead();
|
|
1688
|
+
if (this.isClassOverloadContinuation(memberName.name, isStatic)) {
|
|
1689
|
+
if (!isStatic) bindThisParam(head.params, start);
|
|
1690
|
+
signatures.push({ ...this.headToSignature(head), name: written });
|
|
1691
|
+
if (isStatic) this.advance();
|
|
1692
|
+
this.expectKeyword("function");
|
|
1693
|
+
written = this.parseIdentifier();
|
|
1694
|
+
continue;
|
|
1695
|
+
}
|
|
1696
|
+
const func = this.headToBody(head, start);
|
|
1697
|
+
if (!isStatic) bindThis(func, start);
|
|
1698
|
+
return {
|
|
1699
|
+
type: "ClassMethod",
|
|
1700
|
+
name: memberName,
|
|
1701
|
+
isStatic,
|
|
1702
|
+
func,
|
|
1703
|
+
signatures: signatures.length ? signatures : void 0,
|
|
1704
|
+
...spanFrom(start, this.previous())
|
|
1705
|
+
};
|
|
1706
|
+
}
|
|
1707
|
+
}
|
|
1708
|
+
if (!isStatic && this.checkIdentifierValue("constructor") && this.punctuatorAt(1, "(")) {
|
|
1709
|
+
this.advance();
|
|
1710
|
+
const head = this.parseFunctionHead();
|
|
1711
|
+
if (head.returnType) this.problem("A constructor has no return type; it always builds the instance");
|
|
1712
|
+
const func = this.headToBody(head, start);
|
|
1713
|
+
bindThis(func, start);
|
|
1714
|
+
return { type: "ClassConstructor", func, ...spanFrom(start, this.previous()) };
|
|
1715
|
+
}
|
|
1716
|
+
if ((this.checkIdentifierValue("get") || this.checkIdentifierValue("set")) && this.peek(1).type === "Identifier" && this.punctuatorAt(2, "(")) {
|
|
1717
|
+
const kind = this.advance().value;
|
|
1718
|
+
const memberName = this.parseIdentifier();
|
|
1719
|
+
const head = this.parseFunctionHead();
|
|
1720
|
+
const func = this.headToBody(head, start);
|
|
1721
|
+
if (!isStatic) bindThis(func, start);
|
|
1722
|
+
const written = func.params.length - (isStatic ? 0 : 1);
|
|
1723
|
+
if (kind === "get" && written > 0) {
|
|
1724
|
+
this.problem("A getter takes no parameters");
|
|
1725
|
+
}
|
|
1726
|
+
if (kind === "set" && written !== 1) {
|
|
1727
|
+
this.problem("A setter takes exactly one parameter: the value being assigned");
|
|
1728
|
+
}
|
|
1729
|
+
return { type: "ClassAccessor", kind, name: memberName, isStatic, func, ...spanFrom(start, this.previous()) };
|
|
1730
|
+
}
|
|
1731
|
+
if (this.checkType("Identifier")) {
|
|
1732
|
+
const memberName = this.parseIdentifier();
|
|
1733
|
+
let typeAnnotation;
|
|
1734
|
+
if (this.matchPunctuator(":")) {
|
|
1735
|
+
typeAnnotation = this.typeOr(() => this.checkOperator("="));
|
|
1736
|
+
}
|
|
1737
|
+
let init;
|
|
1738
|
+
if (this.matchOperator("=")) init = this.expressionOr(() => false);
|
|
1739
|
+
if (!typeAnnotation && !init) {
|
|
1740
|
+
this.error("A class field needs a type ('name: T') or a value ('name = v')");
|
|
1741
|
+
}
|
|
1742
|
+
return { type: "ClassField", name: memberName, isStatic, typeAnnotation, init, ...spanFrom(start, this.previous()) };
|
|
1743
|
+
}
|
|
1744
|
+
if (this.recover) {
|
|
1745
|
+
this.softError("Expected a class member: a field, 'function', 'constructor', 'get' or 'set'");
|
|
1746
|
+
this.advance();
|
|
1747
|
+
return void 0;
|
|
1748
|
+
}
|
|
1749
|
+
this.error("Expected a class member: a field, 'function', 'constructor', 'get' or 'set'");
|
|
1750
|
+
}
|
|
1751
|
+
/** Give a class method its `this`: a real first parameter, the way
|
|
1752
|
+
* `function T:m()` gets a real `self`. Every later pass — scopes, types,
|
|
1753
|
+
* arity, lowering — then sees an ordinary parameter and needs to know
|
|
1754
|
+
* nothing about classes. */
|
|
1755
|
+
/** After a bodyless head inside a class body, does another declaration of
|
|
1756
|
+
* the same member follow? Then the head was an overload signature. */
|
|
1757
|
+
isClassOverloadContinuation(name, isStatic) {
|
|
1758
|
+
const offset = isStatic ? 1 : 0;
|
|
1759
|
+
if (isStatic && !this.checkIdentifierValue("static")) return false;
|
|
1760
|
+
const keyword = this.peek(offset);
|
|
1761
|
+
if (!(keyword.type === "Keyword" && keyword.value === "function")) return false;
|
|
1762
|
+
const named = this.peek(offset + 1);
|
|
1763
|
+
return named.type === "Identifier" && named.value === name;
|
|
1764
|
+
}
|
|
1765
|
+
operatorAt(ahead, value) {
|
|
1766
|
+
const token = this.peek(ahead);
|
|
1767
|
+
return token.type === "Operator" && token.value === value;
|
|
1768
|
+
}
|
|
1514
1769
|
parseFunctionName() {
|
|
1515
1770
|
const start = this.current();
|
|
1516
1771
|
const base = this.parseIdentifier();
|
|
@@ -1604,7 +1859,7 @@ var Parser = class {
|
|
|
1604
1859
|
...spanFrom(start, this.previous())
|
|
1605
1860
|
};
|
|
1606
1861
|
}
|
|
1607
|
-
if (first.type === "CallExpression" || first.type === "MethodCallExpression") {
|
|
1862
|
+
if (first.type === "CallExpression" || first.type === "MethodCallExpression" || first.type === "NewExpression") {
|
|
1608
1863
|
return { type: "CallStatement", expression: first, ...spanFrom(start, this.previous()) };
|
|
1609
1864
|
}
|
|
1610
1865
|
this.error("Unexpected expression statement (expected assignment or call)");
|
|
@@ -1792,12 +2047,20 @@ var Parser = class {
|
|
|
1792
2047
|
if (t.type === "Keyword" && t.value === "if") {
|
|
1793
2048
|
return this.parseIfElseExpression();
|
|
1794
2049
|
}
|
|
2050
|
+
if (t.type === "Identifier" && t.value === "class") {
|
|
2051
|
+
return this.parseClassExpression();
|
|
2052
|
+
}
|
|
1795
2053
|
if (t.type === "Punctuator" && t.value === "{") {
|
|
1796
2054
|
return this.parseTableExpression();
|
|
1797
2055
|
}
|
|
1798
2056
|
if (t.type === "Punctuator" && t.value === "[") {
|
|
1799
2057
|
return this.parseArrayExpression();
|
|
1800
2058
|
}
|
|
2059
|
+
if (this.checkType("Identifier") && this.punctuatorAt(1, "=>")) return this.parseArrow();
|
|
2060
|
+
if (this.checkPunctuator("(") || this.checkOperator("<")) {
|
|
2061
|
+
const arrow = this.tryParse(() => this.parseArrow());
|
|
2062
|
+
if (arrow) return arrow;
|
|
2063
|
+
}
|
|
1801
2064
|
if (t.type === "Identifier" || t.type === "Punctuator" && t.value === "(") {
|
|
1802
2065
|
return this.parsePrefixExpression();
|
|
1803
2066
|
}
|
|
@@ -1811,7 +2074,7 @@ var Parser = class {
|
|
|
1811
2074
|
} else {
|
|
1812
2075
|
let expression;
|
|
1813
2076
|
try {
|
|
1814
|
-
expression = shiftSpans(parseExpressionFromSource(p.raw), p.line, p.column);
|
|
2077
|
+
expression = shiftSpans(parseExpressionFromSource(p.raw, this.classDepth > 0), p.line, p.column);
|
|
1815
2078
|
} catch (e) {
|
|
1816
2079
|
if (!this.recover || !(e instanceof ParseError || e instanceof LexError)) throw e;
|
|
1817
2080
|
const at = token;
|
|
@@ -1849,7 +2112,12 @@ var Parser = class {
|
|
|
1849
2112
|
parsePrefixExpression() {
|
|
1850
2113
|
const start = this.current();
|
|
1851
2114
|
let base;
|
|
1852
|
-
if (this.
|
|
2115
|
+
if (this.startsNew()) {
|
|
2116
|
+
base = this.parseNewExpression();
|
|
2117
|
+
} else if (this.classDepth > 0 && this.checkIdentifierValue("super") && this.startsSuperUse()) {
|
|
2118
|
+
this.advance();
|
|
2119
|
+
base = { type: "SuperExpression", ...spanFrom(start, start) };
|
|
2120
|
+
} else if (this.checkType("Identifier")) {
|
|
1853
2121
|
base = this.parseIdentifier();
|
|
1854
2122
|
} else if (this.matchPunctuator("(")) {
|
|
1855
2123
|
const inner = this.parseExpression();
|
|
@@ -1946,11 +2214,13 @@ var Parser = class {
|
|
|
1946
2214
|
}
|
|
1947
2215
|
}
|
|
1948
2216
|
if (this.startsCallArguments()) {
|
|
2217
|
+
const onNewLine = this.current().line.start > base.line.end;
|
|
1949
2218
|
const args = this.parseCallArguments();
|
|
1950
2219
|
base = {
|
|
1951
2220
|
type: "CallExpression",
|
|
1952
2221
|
callee: base,
|
|
1953
2222
|
arguments: args,
|
|
2223
|
+
argumentsOnNewLine: onNewLine || void 0,
|
|
1954
2224
|
...spanFrom(base, this.previous())
|
|
1955
2225
|
};
|
|
1956
2226
|
continue;
|
|
@@ -1959,6 +2229,52 @@ var Parser = class {
|
|
|
1959
2229
|
}
|
|
1960
2230
|
return base;
|
|
1961
2231
|
}
|
|
2232
|
+
/** `new` is a soft keyword: it starts a construction only when a name
|
|
2233
|
+
* follows it, so a function or field called `new` — `Instance.new(x)`,
|
|
2234
|
+
* and `Vec.new(1)` itself — is untouched. */
|
|
2235
|
+
startsNew() {
|
|
2236
|
+
return this.checkIdentifierValue("new") && this.peek(1).type === "Identifier";
|
|
2237
|
+
}
|
|
2238
|
+
/** `super` on its own means the base class, and only `super(...)` and
|
|
2239
|
+
* `super.member` say anything; anything else is a name that happens to
|
|
2240
|
+
* be spelled that way. */
|
|
2241
|
+
startsSuperUse() {
|
|
2242
|
+
return this.punctuatorAt(1, "(") || this.punctuatorAt(1, ".") && this.peek(2).type === "Identifier";
|
|
2243
|
+
}
|
|
2244
|
+
/** `new Name(args)` / `new Module.Name(args)`. The callee is a name, or a
|
|
2245
|
+
* name reached through a module — never an arbitrary expression, so the
|
|
2246
|
+
* arguments are unambiguously the constructor's. */
|
|
2247
|
+
parseNewExpression() {
|
|
2248
|
+
const start = this.current();
|
|
2249
|
+
this.advance();
|
|
2250
|
+
let callee = this.parseIdentifier();
|
|
2251
|
+
while (this.checkPunctuator(".") && this.peek(1).type === "Identifier") {
|
|
2252
|
+
this.advance();
|
|
2253
|
+
const property = this.parseIdentifier();
|
|
2254
|
+
callee = { type: "MemberExpression", object: callee, property, ...spanFrom(start, property) };
|
|
2255
|
+
}
|
|
2256
|
+
const typeArguments = this.tryCallTypeArguments();
|
|
2257
|
+
if (!this.checkPunctuator("(")) {
|
|
2258
|
+
this.error("Expected '(' after the class being constructed: 'new Name(...)'");
|
|
2259
|
+
}
|
|
2260
|
+
const args = this.parseCallArguments();
|
|
2261
|
+
return { type: "NewExpression", callee, arguments: args, typeArguments, ...spanFrom(start, this.previous()) };
|
|
2262
|
+
}
|
|
2263
|
+
/** After `...`, is there something to spread? Nothing following it means
|
|
2264
|
+
* the vararg pack, which is what `f(...)` has always passed on. */
|
|
2265
|
+
startsSpread() {
|
|
2266
|
+
const next = this.peek(1);
|
|
2267
|
+
if (next.type === "Punctuator") {
|
|
2268
|
+
const value = next.value;
|
|
2269
|
+
return value === "(" || value === "{" || value === "[";
|
|
2270
|
+
}
|
|
2271
|
+
return next.type === "Identifier" || next.type === "Literal" || next.type === "InterpolatedString";
|
|
2272
|
+
}
|
|
2273
|
+
parseSpreadArgument(stop) {
|
|
2274
|
+
const dots = this.advance();
|
|
2275
|
+
const argument = this.expressionOr(stop);
|
|
2276
|
+
return { type: "SpreadElement", argument, ...spanFrom(dots, argument) };
|
|
2277
|
+
}
|
|
1962
2278
|
/** Is the token `ahead` places on the punctuator `value`? */
|
|
1963
2279
|
punctuatorAt(ahead, value) {
|
|
1964
2280
|
const token = this.peek(ahead);
|
|
@@ -2005,7 +2321,7 @@ var Parser = class {
|
|
|
2005
2321
|
while (true) {
|
|
2006
2322
|
if (this.recover && this.onNewLine() && this.startsTableField() && !this.startsMethodCall(1)) break;
|
|
2007
2323
|
const before = this.cursor;
|
|
2008
|
-
const argument = this.expressionOr(stop);
|
|
2324
|
+
const argument = this.checkOperator("...") && this.startsSpread() ? this.parseSpreadArgument(stop) : this.expressionOr(stop);
|
|
2009
2325
|
if (argument.type !== "ErrorExpression" || this.cursor > before || list.length) list.push(argument);
|
|
2010
2326
|
if (this.matchPunctuator(",") && !this.checkPunctuator(")")) continue;
|
|
2011
2327
|
if (!this.recover || this.checkPunctuator(")")) break;
|
|
@@ -2108,8 +2424,12 @@ var Parser = class {
|
|
|
2108
2424
|
while (!this.checkPunctuator("]")) {
|
|
2109
2425
|
if (this.checkOperator("...")) {
|
|
2110
2426
|
const dots = this.advance();
|
|
2111
|
-
|
|
2112
|
-
|
|
2427
|
+
if (this.checkPunctuator("]") || this.checkPunctuator(",")) {
|
|
2428
|
+
elements.push({ type: "VarargExpression", ...spanFrom(dots, dots) });
|
|
2429
|
+
} else {
|
|
2430
|
+
const argument = this.expressionOr(stop);
|
|
2431
|
+
elements.push({ type: "SpreadElement", argument, ...spanFrom(dots, argument) });
|
|
2432
|
+
}
|
|
2113
2433
|
} else {
|
|
2114
2434
|
elements.push(this.expressionOr(stop));
|
|
2115
2435
|
}
|
|
@@ -2298,8 +2618,26 @@ var Parser = class {
|
|
|
2298
2618
|
if (!this.checkPunctuator(")")) {
|
|
2299
2619
|
while (true) {
|
|
2300
2620
|
if (this.checkOperator("...")) {
|
|
2301
|
-
this.advance();
|
|
2621
|
+
const dots = this.advance();
|
|
2302
2622
|
hasVarargs = true;
|
|
2623
|
+
if (this.checkType("Identifier")) {
|
|
2624
|
+
const nameTok = this.expectIdentifier();
|
|
2625
|
+
let typeAnnotation2;
|
|
2626
|
+
if (this.matchPunctuator(":")) {
|
|
2627
|
+
typeAnnotation2 = this.typeOr(() => this.checkPunctuator(")"));
|
|
2628
|
+
}
|
|
2629
|
+
params.push({
|
|
2630
|
+
type: "FunctionParameter",
|
|
2631
|
+
name: nameTok.value,
|
|
2632
|
+
typeAnnotation: typeAnnotation2,
|
|
2633
|
+
rest: true,
|
|
2634
|
+
...spanFrom(dots, this.previous())
|
|
2635
|
+
});
|
|
2636
|
+
if (this.checkPunctuator(",")) {
|
|
2637
|
+
this.problem("A rest parameter is the last one: nothing can follow '...'");
|
|
2638
|
+
}
|
|
2639
|
+
break;
|
|
2640
|
+
}
|
|
2303
2641
|
if (this.matchPunctuator(":")) {
|
|
2304
2642
|
varargTypeAnnotation = this.parseTypeOrTypePackReference();
|
|
2305
2643
|
}
|
|
@@ -2391,10 +2729,64 @@ var Parser = class {
|
|
|
2391
2729
|
}
|
|
2392
2730
|
return void 0;
|
|
2393
2731
|
}
|
|
2732
|
+
/** Run `parse`, and put the parser back where it was if it fails. Used
|
|
2733
|
+
* where two forms start alike and only their end tells them apart: `(a,
|
|
2734
|
+
* b) => a + b` and `(a + b)` both open with a `(`. */
|
|
2735
|
+
tryParse(parse2) {
|
|
2736
|
+
const cursor = this.cursor;
|
|
2737
|
+
const errors = this.errors.length;
|
|
2738
|
+
try {
|
|
2739
|
+
return parse2();
|
|
2740
|
+
} catch (error) {
|
|
2741
|
+
if (!(error instanceof ParseError || error instanceof ParseRecover)) throw error;
|
|
2742
|
+
this.cursor = cursor;
|
|
2743
|
+
this.errors.length = errors;
|
|
2744
|
+
return void 0;
|
|
2745
|
+
}
|
|
2746
|
+
}
|
|
2747
|
+
/** `x => x * 2` — a function, written short. The body is an expression,
|
|
2748
|
+
* which is returned, or a block in braces, as in TypeScript. */
|
|
2749
|
+
parseArrow() {
|
|
2750
|
+
const start = this.current();
|
|
2751
|
+
const head = this.checkType("Identifier") ? (() => {
|
|
2752
|
+
const name = this.expectIdentifier();
|
|
2753
|
+
return {
|
|
2754
|
+
start,
|
|
2755
|
+
generics: [],
|
|
2756
|
+
params: [{ type: "FunctionParameter", name: name.value, ...spanFrom(name, name) }],
|
|
2757
|
+
hasVarargs: false,
|
|
2758
|
+
varargTypeAnnotation: void 0,
|
|
2759
|
+
returnType: void 0,
|
|
2760
|
+
predicate: void 0
|
|
2761
|
+
};
|
|
2762
|
+
})() : this.parseFunctionHead();
|
|
2763
|
+
this.expectPunctuator("=>");
|
|
2764
|
+
const body = this.checkPunctuator("{") ? this.parseBraceBlock() : this.returnOf(this.parseExpression(0));
|
|
2765
|
+
const func = {
|
|
2766
|
+
type: "FunctionBody",
|
|
2767
|
+
generics: head.generics,
|
|
2768
|
+
params: head.params,
|
|
2769
|
+
hasVarargs: head.hasVarargs,
|
|
2770
|
+
varargTypeAnnotation: head.varargTypeAnnotation,
|
|
2771
|
+
returnType: head.returnType,
|
|
2772
|
+
predicate: head.predicate,
|
|
2773
|
+
body,
|
|
2774
|
+
...spanFrom(start, this.previous())
|
|
2775
|
+
};
|
|
2776
|
+
return { type: "FunctionExpression", func, ...spanFrom(start, this.previous()) };
|
|
2777
|
+
}
|
|
2778
|
+
/** A one-expression body: the value is what the function returns. */
|
|
2779
|
+
returnOf(expression) {
|
|
2780
|
+
const statement = {
|
|
2781
|
+
type: "ReturnStatement",
|
|
2782
|
+
arguments: [expression],
|
|
2783
|
+
...spanFrom(expression, expression)
|
|
2784
|
+
};
|
|
2785
|
+
return { type: "Block", statements: [statement], ...spanFrom(expression, expression) };
|
|
2786
|
+
}
|
|
2394
2787
|
parseFunctionBody(opener) {
|
|
2395
2788
|
const head = this.parseFunctionHead();
|
|
2396
|
-
const body = this.
|
|
2397
|
-
this.expectEnd(opener);
|
|
2789
|
+
const body = this.parseStatementBody(opener);
|
|
2398
2790
|
return {
|
|
2399
2791
|
type: "FunctionBody",
|
|
2400
2792
|
generics: head.generics,
|
|
@@ -2419,9 +2811,13 @@ var Parser = class {
|
|
|
2419
2811
|
...spanFrom(head.start, this.previous())
|
|
2420
2812
|
};
|
|
2421
2813
|
}
|
|
2814
|
+
/** A function's statements. */
|
|
2815
|
+
parseStatementBody(opener) {
|
|
2816
|
+
void opener;
|
|
2817
|
+
return this.parseBracedBody("function");
|
|
2818
|
+
}
|
|
2422
2819
|
headToBody(head, opener) {
|
|
2423
|
-
const body = this.
|
|
2424
|
-
this.expectEnd(opener);
|
|
2820
|
+
const body = this.parseStatementBody(opener);
|
|
2425
2821
|
return {
|
|
2426
2822
|
type: "FunctionBody",
|
|
2427
2823
|
generics: head.generics,
|
|
@@ -2645,8 +3041,21 @@ var Parser = class {
|
|
|
2645
3041
|
if (!this.checkPunctuator(")")) {
|
|
2646
3042
|
while (true) {
|
|
2647
3043
|
if (this.checkOperator("...")) {
|
|
2648
|
-
this.advance();
|
|
3044
|
+
const dots = this.advance();
|
|
2649
3045
|
hasVarargs = true;
|
|
3046
|
+
if (this.checkType("Identifier") && this.punctuatorAt(1, ":")) {
|
|
3047
|
+
const nameTok2 = this.expectIdentifier();
|
|
3048
|
+
this.advance();
|
|
3049
|
+
params.push({
|
|
3050
|
+
type: "FunctionTypeParameter",
|
|
3051
|
+
name: nameTok2.value,
|
|
3052
|
+
id: tokenIdentifier(nameTok2),
|
|
3053
|
+
typeAnnotation: this.parseType(),
|
|
3054
|
+
rest: true,
|
|
3055
|
+
...spanFrom(dots, this.previous())
|
|
3056
|
+
});
|
|
3057
|
+
break;
|
|
3058
|
+
}
|
|
2650
3059
|
varargType = this.parseType();
|
|
2651
3060
|
break;
|
|
2652
3061
|
}
|
|
@@ -2684,7 +3093,7 @@ var Parser = class {
|
|
|
2684
3093
|
}
|
|
2685
3094
|
}
|
|
2686
3095
|
this.expectPunctuator(")");
|
|
2687
|
-
if (this.matchPunctuator("->")) {
|
|
3096
|
+
if (this.matchPunctuator("=>") || this.matchPunctuator("->")) {
|
|
2688
3097
|
const predicate = this.tryParseTypePredicate();
|
|
2689
3098
|
const returnType = predicate ? { type: "TypeReference", base: "boolean", typeArguments: [], ...spanFrom(start, this.previous()) } : this.parseTypeOrTypePackReference();
|
|
2690
3099
|
return {
|
|
@@ -2827,8 +3236,22 @@ var Parser = class {
|
|
|
2827
3236
|
optional: optional2,
|
|
2828
3237
|
...spanFrom(propStart, this.previous())
|
|
2829
3238
|
});
|
|
3239
|
+
} else if (this.checkType("Literal") && this.current().kind === "string" && this.peek(1).type === "Punctuator" && (this.peek(1).value === ":" || this.peek(1).value === "?" && this.peek(2).type === "Punctuator" && this.peek(2).value === ":")) {
|
|
3240
|
+
const keyTok = this.advance();
|
|
3241
|
+
const name = String(keyTok.value);
|
|
3242
|
+
const optional2 = this.matchPunctuator("?");
|
|
3243
|
+
this.expectPunctuator(":");
|
|
3244
|
+
const valueType = this.parseType();
|
|
3245
|
+
properties.push({
|
|
3246
|
+
type: "TableTypeProperty",
|
|
3247
|
+
name,
|
|
3248
|
+
key: tokenIdentifier(keyTok),
|
|
3249
|
+
valueType,
|
|
3250
|
+
optional: optional2,
|
|
3251
|
+
...spanFrom(propStart, this.previous())
|
|
3252
|
+
});
|
|
2830
3253
|
} else {
|
|
2831
|
-
this.error(
|
|
3254
|
+
this.error(`Expected object type property ('name: T', '"name": T' or '[K]: V'); use 'T[]' for arrays and '[T, U]' for tuples`);
|
|
2832
3255
|
}
|
|
2833
3256
|
if (this.matchPunctuator(",") || this.matchPunctuator(";")) continue;
|
|
2834
3257
|
break;
|
|
@@ -2914,9 +3337,10 @@ function parseTypeFromSource(raw) {
|
|
|
2914
3337
|
const parser = new Parser(tokenize(raw));
|
|
2915
3338
|
return parser.parseType();
|
|
2916
3339
|
}
|
|
2917
|
-
function parseExpressionFromSource(raw) {
|
|
3340
|
+
function parseExpressionFromSource(raw, inClass = false) {
|
|
2918
3341
|
const tokens = tokenize(raw);
|
|
2919
3342
|
const parser = new Parser(tokens);
|
|
3343
|
+
if (inClass) parser.classDepth = 1;
|
|
2920
3344
|
const expr = parser.parseExpression();
|
|
2921
3345
|
return expr;
|
|
2922
3346
|
}
|
|
@@ -3025,6 +3449,11 @@ var Analyzer = class {
|
|
|
3025
3449
|
hoistFunctions(block, scope) {
|
|
3026
3450
|
for (const statement of block.statements) {
|
|
3027
3451
|
const declaration = statement.type === "ExportStatement" ? statement.declaration : statement;
|
|
3452
|
+
if (declaration.type === "ClassDeclaration") {
|
|
3453
|
+
this.declare(scope, declaration.name.name, "local", declaration.name, true, "class");
|
|
3454
|
+
this.hoisted.set(declaration.name, scope === this.moduleScope ? -1 : this.functionDepth);
|
|
3455
|
+
continue;
|
|
3456
|
+
}
|
|
3028
3457
|
if (declaration.type !== "FunctionDeclaration") continue;
|
|
3029
3458
|
this.declare(scope, declaration.name.name, "local", declaration.name, true, "function");
|
|
3030
3459
|
this.hoisted.set(declaration.name, scope === this.moduleScope ? -1 : this.functionDepth);
|
|
@@ -3037,7 +3466,7 @@ var Analyzer = class {
|
|
|
3037
3466
|
* hoisted, and this does not apply. */
|
|
3038
3467
|
checkUseBeforeDefine(identifier, id) {
|
|
3039
3468
|
const binding = this.bindings.get(id);
|
|
3040
|
-
if (binding.declaredBy !== "function" || this.typeQueryDepth > 0) return;
|
|
3469
|
+
if (binding.declaredBy !== "function" && binding.declaredBy !== "class" || this.typeQueryDepth > 0) return;
|
|
3041
3470
|
const declaration = binding.declarationNode;
|
|
3042
3471
|
const depth = declaration && this.hoisted.get(declaration);
|
|
3043
3472
|
if (depth === void 0 || depth !== this.functionDepth) return;
|
|
@@ -3292,6 +3721,11 @@ var Analyzer = class {
|
|
|
3292
3721
|
this.visitFunctionBody(stmt.func, scope);
|
|
3293
3722
|
return;
|
|
3294
3723
|
}
|
|
3724
|
+
case "ClassDeclaration": {
|
|
3725
|
+
if (!this.hoisted.has(stmt.name)) this.declare(scope, stmt.name.name, "local", stmt.name, true, "class");
|
|
3726
|
+
this.visitClassBody(stmt, scope);
|
|
3727
|
+
return;
|
|
3728
|
+
}
|
|
3295
3729
|
case "FunctionDeclarationStatement": {
|
|
3296
3730
|
if (stmt.target.path.length === 0 && !stmt.target.method) {
|
|
3297
3731
|
this.referenceAsAssignmentTarget(scope, stmt.target.base);
|
|
@@ -3411,7 +3845,8 @@ var Analyzer = class {
|
|
|
3411
3845
|
this.visitStatement(stmt.declaration, scope);
|
|
3412
3846
|
return;
|
|
3413
3847
|
case "ExportDefaultStatement":
|
|
3414
|
-
this.
|
|
3848
|
+
if (stmt.declaration.type === "ClassDeclaration") this.visitStatement(stmt.declaration, scope);
|
|
3849
|
+
else this.visitExpression(stmt.declaration, scope);
|
|
3415
3850
|
return;
|
|
3416
3851
|
case "ExportNamedStatement":
|
|
3417
3852
|
if (!stmt.source) {
|
|
@@ -3459,6 +3894,33 @@ var Analyzer = class {
|
|
|
3459
3894
|
}
|
|
3460
3895
|
/** `<K extends typeof config>` — a constraint is a type like any other,
|
|
3461
3896
|
* and the `typeof` in it reads a value. */
|
|
3897
|
+
/** A class body. Its type parameters live in a scope of their own, and
|
|
3898
|
+
* every member is written inside it — so a method's annotations see `T`,
|
|
3899
|
+
* and everything else sees what the class declaration sees, itself
|
|
3900
|
+
* included. `this` is not declared here: the parser makes it a real first
|
|
3901
|
+
* parameter, so it arrives with the rest of them. */
|
|
3902
|
+
visitClassBody(node, outer) {
|
|
3903
|
+
const scope = node.typeParams?.length ? childScope(outer) : outer;
|
|
3904
|
+
this.visitGenerics(node.typeParams, scope);
|
|
3905
|
+
if (node.superclass) this.reference(outer, node.superclass);
|
|
3906
|
+
for (const argument of node.superArguments ?? []) this.visitType(argument, scope);
|
|
3907
|
+
for (const member of node.members) {
|
|
3908
|
+
switch (member.type) {
|
|
3909
|
+
case "ClassField":
|
|
3910
|
+
this.visitType(member.typeAnnotation, scope);
|
|
3911
|
+
if (member.init) this.visitExpression(member.init, scope);
|
|
3912
|
+
break;
|
|
3913
|
+
case "ClassMethod":
|
|
3914
|
+
for (const signature of member.signatures ?? []) this.visitSignature(signature, scope);
|
|
3915
|
+
this.visitFunctionBody(member.func, scope, member.func.isMethod);
|
|
3916
|
+
break;
|
|
3917
|
+
case "ClassAccessor":
|
|
3918
|
+
case "ClassConstructor":
|
|
3919
|
+
this.visitFunctionBody(member.func, scope, member.func.isMethod);
|
|
3920
|
+
break;
|
|
3921
|
+
}
|
|
3922
|
+
}
|
|
3923
|
+
}
|
|
3462
3924
|
visitGenerics(generics, scope) {
|
|
3463
3925
|
for (const generic of generics ?? []) {
|
|
3464
3926
|
this.visitType(generic.constraint, scope);
|
|
@@ -3541,6 +4003,22 @@ var Analyzer = class {
|
|
|
3541
4003
|
this.visitExpression(expr.callee, scope);
|
|
3542
4004
|
for (const arg of expr.arguments) this.visitExpression(arg, scope);
|
|
3543
4005
|
return;
|
|
4006
|
+
case "NewExpression":
|
|
4007
|
+
this.visitExpression(expr.callee, scope);
|
|
4008
|
+
for (const argument of expr.arguments) this.visitExpression(argument, scope);
|
|
4009
|
+
for (const argument of expr.typeArguments ?? []) this.visitType(argument, scope);
|
|
4010
|
+
return;
|
|
4011
|
+
case "SuperExpression":
|
|
4012
|
+
return;
|
|
4013
|
+
case "SpreadElement":
|
|
4014
|
+
this.visitExpression(expr.argument, scope);
|
|
4015
|
+
return;
|
|
4016
|
+
case "ClassExpression": {
|
|
4017
|
+
const inner = childScope(scope);
|
|
4018
|
+
if (expr.name) this.declare(inner, expr.name.name, "local", expr.name, true, "class");
|
|
4019
|
+
this.visitClassBody(expr, inner);
|
|
4020
|
+
return;
|
|
4021
|
+
}
|
|
3544
4022
|
case "MethodCallExpression":
|
|
3545
4023
|
this.visitExpression(expr.object, scope);
|
|
3546
4024
|
for (const arg of expr.arguments) this.visitExpression(arg, scope);
|
|
@@ -3622,6 +4100,14 @@ function preludeProgram() {
|
|
|
3622
4100
|
function isClassType(t) {
|
|
3623
4101
|
return t.kind === "object" && t.class !== void 0;
|
|
3624
4102
|
}
|
|
4103
|
+
function isClassAssignable(got, want) {
|
|
4104
|
+
if (!got || !got.ancestors.includes(want.name)) return false;
|
|
4105
|
+
const wanted = want.typeArguments?.get(want.name);
|
|
4106
|
+
if (!wanted?.length) return true;
|
|
4107
|
+
const given = got.typeArguments?.get(want.name);
|
|
4108
|
+
if (!given) return true;
|
|
4109
|
+
return wanted.every((w, i) => given[i] !== void 0 && isAssignable(given[i], w));
|
|
4110
|
+
}
|
|
3625
4111
|
function typeParam(name, constraint, isConst) {
|
|
3626
4112
|
return { kind: "typeParam", name, constraint, isConst };
|
|
3627
4113
|
}
|
|
@@ -3673,11 +4159,21 @@ function substitute(t, subst) {
|
|
|
3673
4159
|
case "object": {
|
|
3674
4160
|
const entries = [];
|
|
3675
4161
|
for (const [k, v] of t.properties) entries.push([k, { ...v, type: substitute(v.type, subst) }]);
|
|
3676
|
-
|
|
4162
|
+
const out = objectType(
|
|
3677
4163
|
entries,
|
|
3678
4164
|
t.indexer && { key: substitute(t.indexer.key, subst), value: substitute(t.indexer.value, subst) },
|
|
3679
4165
|
t.frozen
|
|
3680
4166
|
);
|
|
4167
|
+
if (t.name) out.name = t.name;
|
|
4168
|
+
if (t.class) {
|
|
4169
|
+
out.class = {
|
|
4170
|
+
...t.class,
|
|
4171
|
+
typeArguments: t.class.typeArguments && new Map(
|
|
4172
|
+
[...t.class.typeArguments].map(([name, args]) => [name, args.map((a) => substitute(a, subst))])
|
|
4173
|
+
)
|
|
4174
|
+
};
|
|
4175
|
+
}
|
|
4176
|
+
return out;
|
|
3681
4177
|
}
|
|
3682
4178
|
case "function": {
|
|
3683
4179
|
const inner = t.typeParams ? new Map([...subst].filter(([k]) => !t.typeParams.includes(k))) : subst;
|
|
@@ -3744,6 +4240,11 @@ function substitute(t, subst) {
|
|
|
3744
4240
|
return t;
|
|
3745
4241
|
}
|
|
3746
4242
|
}
|
|
4243
|
+
function classArguments(t, name) {
|
|
4244
|
+
if (t.kind === "genericRef") return t.name === name ? t.typeArguments : void 0;
|
|
4245
|
+
if (t.kind === "object") return t.class?.typeArguments?.get(name);
|
|
4246
|
+
return void 0;
|
|
4247
|
+
}
|
|
3747
4248
|
function unify(param, arg, vars, out) {
|
|
3748
4249
|
if (param.kind === "typeParam" && param.constraint && !vars.has(param.name)) {
|
|
3749
4250
|
unify(param.constraint, arg, vars, out);
|
|
@@ -3771,7 +4272,12 @@ function unify(param, arg, vars, out) {
|
|
|
3771
4272
|
}
|
|
3772
4273
|
return;
|
|
3773
4274
|
case "object":
|
|
3774
|
-
if (param.class)
|
|
4275
|
+
if (param.class) {
|
|
4276
|
+
const wanted = param.class.typeArguments?.get(param.class.name);
|
|
4277
|
+
const given = classArguments(arg, param.class.name);
|
|
4278
|
+
if (wanted && given) wanted.forEach((w, i) => given[i] && unify(w, given[i], vars, out));
|
|
4279
|
+
return;
|
|
4280
|
+
}
|
|
3775
4281
|
if (arg.kind === "object" && !arg.class) {
|
|
3776
4282
|
for (const [k, pv] of param.properties) {
|
|
3777
4283
|
const av = arg.properties.get(k);
|
|
@@ -3780,6 +4286,11 @@ function unify(param, arg, vars, out) {
|
|
|
3780
4286
|
if (param.indexer && arg.indexer) unify(param.indexer.value, arg.indexer.value, vars, out);
|
|
3781
4287
|
}
|
|
3782
4288
|
return;
|
|
4289
|
+
case "genericRef": {
|
|
4290
|
+
const given = classArguments(arg, param.name);
|
|
4291
|
+
if (given) param.typeArguments.forEach((p, i) => given[i] && unify(p, given[i], vars, out));
|
|
4292
|
+
return;
|
|
4293
|
+
}
|
|
3783
4294
|
case "union":
|
|
3784
4295
|
for (const m of param.types) unify(m, arg, vars, out);
|
|
3785
4296
|
return;
|
|
@@ -3874,6 +4385,10 @@ var expandAlias;
|
|
|
3874
4385
|
function setAliasExpander(fn2) {
|
|
3875
4386
|
expandAlias = fn2;
|
|
3876
4387
|
}
|
|
4388
|
+
var deferredBound;
|
|
4389
|
+
function setDeferredBound(fn2) {
|
|
4390
|
+
deferredBound = fn2;
|
|
4391
|
+
}
|
|
3877
4392
|
var comparing = [];
|
|
3878
4393
|
function isAssignable(rawA, rawB) {
|
|
3879
4394
|
let a = isNoValue(rawA) ? nilType : rawA;
|
|
@@ -3904,10 +4419,29 @@ function isAssignableInner(a, b) {
|
|
|
3904
4419
|
if (b.kind === "unknown") return true;
|
|
3905
4420
|
if (b.kind === "never") return false;
|
|
3906
4421
|
if (a.kind === "unknown") return false;
|
|
4422
|
+
if (a.kind === "difference" && b.kind === "difference") {
|
|
4423
|
+
return isAssignable(a.base, b.base) && isAssignable(b.excluded, a.excluded);
|
|
4424
|
+
}
|
|
3907
4425
|
if (b.kind === "difference") {
|
|
3908
4426
|
return isAssignable(a, b.base) && !overlaps(a, b.excluded);
|
|
3909
4427
|
}
|
|
3910
|
-
if (a.kind === "difference")
|
|
4428
|
+
if (a.kind === "difference") {
|
|
4429
|
+
if (b.kind === "union" && b.types.some((m) => isAssignable(a, m))) return true;
|
|
4430
|
+
return isAssignable(a.base, b);
|
|
4431
|
+
}
|
|
4432
|
+
if (a.kind === "conditional" || a.kind === "indexedAccess") {
|
|
4433
|
+
if ((b.kind === "conditional" || b.kind === "indexedAccess" || b.kind === "union") && equalTypes(a, b)) {
|
|
4434
|
+
return true;
|
|
4435
|
+
}
|
|
4436
|
+
if (b.kind === "union" && b.types.some((m) => equalTypes(a, m))) return true;
|
|
4437
|
+
const bound = deferredBound?.(a);
|
|
4438
|
+
if (bound && bound !== a) return isAssignable(bound, b);
|
|
4439
|
+
}
|
|
4440
|
+
if (a.kind === "typeParam") {
|
|
4441
|
+
if (b.kind === "typeParam" && a.name === b.name) return true;
|
|
4442
|
+
if (b.kind === "union" && b.types.some((m) => m.kind === "typeParam" && m.name === a.name)) return true;
|
|
4443
|
+
return a.constraint ? isAssignable(a.constraint, b) : false;
|
|
4444
|
+
}
|
|
3911
4445
|
if (a.kind === "union") return a.types.every((t) => isAssignable(t, b));
|
|
3912
4446
|
if (b.kind === "union") return b.types.some((t) => isAssignable(a, t));
|
|
3913
4447
|
if (b.kind === "intersection") return b.types.every((t) => isAssignable(a, t));
|
|
@@ -3941,7 +4475,7 @@ function isAssignableInner(a, b) {
|
|
|
3941
4475
|
}
|
|
3942
4476
|
if (a.kind === "object") {
|
|
3943
4477
|
if (b.kind !== "object") return false;
|
|
3944
|
-
if (b.class) return a.class
|
|
4478
|
+
if (b.class) return isClassAssignable(a.class, b.class);
|
|
3945
4479
|
if (a.class && (b.indexer || b.properties.size === 0)) return false;
|
|
3946
4480
|
for (const [name, bp] of b.properties) {
|
|
3947
4481
|
const ap = a.properties.get(name);
|
|
@@ -3950,7 +4484,7 @@ function isAssignableInner(a, b) {
|
|
|
3950
4484
|
if (a.indexer && isAssignable(a.indexer.value, bp.type)) continue;
|
|
3951
4485
|
return false;
|
|
3952
4486
|
}
|
|
3953
|
-
if (!isAssignable(ap.type, bp.type)) return false;
|
|
4487
|
+
if (!isAssignable(ap.type, bp.optional ? optional(bp.type) : bp.type)) return false;
|
|
3954
4488
|
}
|
|
3955
4489
|
if (b.indexer) {
|
|
3956
4490
|
for (const [name, ap] of a.properties) {
|
|
@@ -3971,10 +4505,6 @@ function isAssignableInner(a, b) {
|
|
|
3971
4505
|
}
|
|
3972
4506
|
return isAssignable(a.returns, b.returns);
|
|
3973
4507
|
}
|
|
3974
|
-
if (a.kind === "typeParam") {
|
|
3975
|
-
if (b.kind === "typeParam" && a.name === b.name) return true;
|
|
3976
|
-
return a.constraint ? isAssignable(a.constraint, b) : false;
|
|
3977
|
-
}
|
|
3978
4508
|
if (b.kind === "typeParam") return false;
|
|
3979
4509
|
if (a.kind === "genericRef" || b.kind === "genericRef") {
|
|
3980
4510
|
return a.kind === "genericRef" && b.kind === "genericRef" && a.name === b.name && a.typeArguments.length === b.typeArguments.length && a.typeArguments.every((x, i) => equalTypes(x, b.typeArguments[i]));
|
|
@@ -4094,7 +4624,9 @@ function containsFreeTypeParam(t, seen, bound) {
|
|
|
4094
4624
|
case "intersection":
|
|
4095
4625
|
return t.types.some((m) => containsTypeParam(m, seen, bound));
|
|
4096
4626
|
case "object":
|
|
4097
|
-
if (t.class)
|
|
4627
|
+
if (t.class) {
|
|
4628
|
+
return [...t.class.typeArguments?.values() ?? []].some((args) => args.some((a) => containsTypeParam(a, seen, bound)));
|
|
4629
|
+
}
|
|
4098
4630
|
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));
|
|
4099
4631
|
case "function": {
|
|
4100
4632
|
const inner = t.typeParams?.length ? /* @__PURE__ */ new Set([...bound, ...t.typeParams]) : bound;
|
|
@@ -4264,7 +4796,10 @@ function formatTypeUncached(t) {
|
|
|
4264
4796
|
return t.isPack ? `(${inner})` : `[${inner}]`;
|
|
4265
4797
|
}
|
|
4266
4798
|
case "object": {
|
|
4267
|
-
if (t.name)
|
|
4799
|
+
if (t.name) {
|
|
4800
|
+
const args = t.class?.typeArguments?.get(t.class.name);
|
|
4801
|
+
return args?.length ? `${t.name}<${args.map(formatType).join(", ")}>` : t.name;
|
|
4802
|
+
}
|
|
4268
4803
|
const props = [...t.properties.entries()].sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([k, v]) => `${v.readonly ? "readonly " : ""}${formatKey(k)}${v.optional ? "?" : ""}: ${formatType(v.type)}`);
|
|
4269
4804
|
if (t.indexer) props.push(`[${formatType(t.indexer.key)}]: ${formatType(t.indexer.value)}`);
|
|
4270
4805
|
return props.length ? `{ ${props.join(", ")} }` : "{}";
|
|
@@ -4283,7 +4818,7 @@ function formatTypeUncached(t) {
|
|
|
4283
4818
|
}).join(", ")}>` : "";
|
|
4284
4819
|
const ps = t.params.map((p) => `${p.name ? p.name + ": " : ""}${formatType(p.type)}`);
|
|
4285
4820
|
if (t.varargs) ps.push(`...${formatType(t.varargs)}`);
|
|
4286
|
-
return `${gen}(${ps.join(", ")})
|
|
4821
|
+
return `${gen}(${ps.join(", ")}) => ${formatPredicate(t) ?? formatType(t.returns)}`;
|
|
4287
4822
|
}
|
|
4288
4823
|
case "typeParam":
|
|
4289
4824
|
return t.name;
|
|
@@ -4327,7 +4862,7 @@ function formatPredicate(t) {
|
|
|
4327
4862
|
}
|
|
4328
4863
|
function formatAtom(t) {
|
|
4329
4864
|
if (t.kind === "intersection" && t.name) return t.name;
|
|
4330
|
-
if (t.kind === "union" || t.kind === "intersection" || t.kind === "function" || t.kind === "difference") {
|
|
4865
|
+
if (t.kind === "union" || t.kind === "intersection" || t.kind === "function" || t.kind === "difference" || t.kind === "conditional") {
|
|
4331
4866
|
return `(${formatType(t)})`;
|
|
4332
4867
|
}
|
|
4333
4868
|
return formatType(t);
|
|
@@ -4431,13 +4966,33 @@ function moduleExports(program, scopes, types, resolveModule) {
|
|
|
4431
4966
|
if (stmt.type === "ExportStatement") {
|
|
4432
4967
|
const declaration = stmt.declaration;
|
|
4433
4968
|
if (declaration.type === "FunctionDeclaration") exportName(declaration.name, declaration.name.name);
|
|
4434
|
-
else
|
|
4969
|
+
else if (declaration.type === "ClassDeclaration") {
|
|
4970
|
+
exportName(declaration.name, declaration.name.name);
|
|
4971
|
+
const instance = types.aliases.get(declaration.name.name);
|
|
4972
|
+
if (instance) {
|
|
4973
|
+
exportedTypes.set(declaration.name.name, {
|
|
4974
|
+
type: instance,
|
|
4975
|
+
params: declaration.typeParams.map((g) => g.name)
|
|
4976
|
+
});
|
|
4977
|
+
}
|
|
4978
|
+
} else for (const target of declaration.names) exportPattern(target);
|
|
4435
4979
|
} else if (stmt.type === "ExportTypeAliasStatement") {
|
|
4436
4980
|
const name = stmt.alias.name.name;
|
|
4437
4981
|
const type = types.aliases.get(name);
|
|
4438
4982
|
if (type) exportedTypes.set(name, { type, params: stmt.alias.generics.map((g) => g.name) });
|
|
4439
4983
|
} else if (stmt.type === "ExportDefaultStatement") {
|
|
4440
|
-
|
|
4984
|
+
if (stmt.declaration.type === "ClassDeclaration") {
|
|
4985
|
+
const id = byDeclaration.get(stmt.declaration.name);
|
|
4986
|
+
defaultType = (id !== void 0 ? types.bindingType.get(id) : void 0) ?? anyType;
|
|
4987
|
+
const instance = types.aliases.get(stmt.declaration.name.name);
|
|
4988
|
+
if (instance) {
|
|
4989
|
+
const exported = { type: instance, params: stmt.declaration.typeParams.map((g) => g.name) };
|
|
4990
|
+
exportedTypes.set(stmt.declaration.name.name, exported);
|
|
4991
|
+
exportedTypes.set("default", exported);
|
|
4992
|
+
}
|
|
4993
|
+
} else {
|
|
4994
|
+
defaultType = types.typeOf.get(stmt.declaration) ?? anyType;
|
|
4995
|
+
}
|
|
4441
4996
|
} else if (stmt.type === "ExportNamedStatement") {
|
|
4442
4997
|
if (stmt.source) {
|
|
4443
4998
|
const from = resolveModule?.(stmt.source.value);
|
|
@@ -4565,6 +5120,51 @@ function keepsLiterals(paramType) {
|
|
|
4565
5120
|
const members = paramType.constraint.kind === "union" ? paramType.constraint.types : [paramType.constraint];
|
|
4566
5121
|
return members.some((m) => m.kind === "literal");
|
|
4567
5122
|
}
|
|
5123
|
+
var CLASS_LINKS = /* @__PURE__ */ new Set(["new", "ClassObject", "ParentClass"]);
|
|
5124
|
+
var CLASS_RESERVED = /* @__PURE__ */ new Set([
|
|
5125
|
+
"new",
|
|
5126
|
+
"ClassObject",
|
|
5127
|
+
"ParentClass",
|
|
5128
|
+
"__init",
|
|
5129
|
+
"__index",
|
|
5130
|
+
"__newindex",
|
|
5131
|
+
"__getters",
|
|
5132
|
+
"__setters",
|
|
5133
|
+
"__dynamic"
|
|
5134
|
+
]);
|
|
5135
|
+
function callsSuper(block) {
|
|
5136
|
+
let found = false;
|
|
5137
|
+
walkNodes(block, (node) => {
|
|
5138
|
+
const record = node;
|
|
5139
|
+
if (record.type === "CallExpression" && record.callee?.type === "SuperExpression") found = true;
|
|
5140
|
+
});
|
|
5141
|
+
return found;
|
|
5142
|
+
}
|
|
5143
|
+
function assignedFields(block) {
|
|
5144
|
+
const names = /* @__PURE__ */ new Set();
|
|
5145
|
+
walkNodes(block, (node) => {
|
|
5146
|
+
const record = node;
|
|
5147
|
+
const targets = record.type === "AssignmentStatement" ? record.targets : record.type === "CompoundAssignmentStatement" ? [record.target] : void 0;
|
|
5148
|
+
for (const target of targets ?? []) {
|
|
5149
|
+
const member = target;
|
|
5150
|
+
if (member.type === "MemberExpression" && member.object?.type === "Identifier" && member.object.name === "this" && member.property?.name) {
|
|
5151
|
+
names.add(member.property.name);
|
|
5152
|
+
}
|
|
5153
|
+
}
|
|
5154
|
+
});
|
|
5155
|
+
return names;
|
|
5156
|
+
}
|
|
5157
|
+
function walkNodes(root, visit) {
|
|
5158
|
+
if (!root || typeof root !== "object") return;
|
|
5159
|
+
if (Array.isArray(root)) {
|
|
5160
|
+
for (const item of root) walkNodes(item, visit);
|
|
5161
|
+
return;
|
|
5162
|
+
}
|
|
5163
|
+
visit(root);
|
|
5164
|
+
for (const [key, value] of Object.entries(root)) {
|
|
5165
|
+
if (key !== "line" && key !== "column" && value && typeof value === "object") walkNodes(value, visit);
|
|
5166
|
+
}
|
|
5167
|
+
}
|
|
4568
5168
|
var AliasMap = class extends Map {
|
|
4569
5169
|
pending = /* @__PURE__ */ new Map();
|
|
4570
5170
|
defer(name, resolve5) {
|
|
@@ -4688,6 +5288,8 @@ var TypeAnalyzer = class {
|
|
|
4688
5288
|
aliasDefs = /* @__PURE__ */ new Map();
|
|
4689
5289
|
/** See `resolveClass`. */
|
|
4690
5290
|
classTypes = /* @__PURE__ */ new WeakMap();
|
|
5291
|
+
/** See `instanceType` — one instance type per `class ... end`. */
|
|
5292
|
+
instanceTypes = /* @__PURE__ */ new WeakMap();
|
|
4691
5293
|
classMembers = /* @__PURE__ */ new WeakMap();
|
|
4692
5294
|
/** Generic parameters currently in lexical scope (alias body / generic fn),
|
|
4693
5295
|
* with their `extends` constraints resolved. */
|
|
@@ -4726,9 +5328,10 @@ var TypeAnalyzer = class {
|
|
|
4726
5328
|
/** Recursion guard for `preVisitBody`. */
|
|
4727
5329
|
preVisitDepth = 0;
|
|
4728
5330
|
run() {
|
|
4729
|
-
this.registerAliasDefs(preludeProgram().body);
|
|
4730
|
-
for (const lib of this.options.libs ?? []) this.registerAliasDefs(lib.body);
|
|
5331
|
+
this.registerAliasDefs(preludeProgram().body, true);
|
|
5332
|
+
for (const lib of this.options.libs ?? []) this.registerAliasDefs(lib.body, true);
|
|
4731
5333
|
this.registerAliasDefs(this.program.body);
|
|
5334
|
+
this.registerNestedClasses();
|
|
4732
5335
|
for (const lib of this.options.libs ?? []) this.harvestDeclares(lib.body);
|
|
4733
5336
|
this.registerImportedTypes();
|
|
4734
5337
|
this.resolveAllAliases();
|
|
@@ -4740,6 +5343,7 @@ var TypeAnalyzer = class {
|
|
|
4740
5343
|
this.bindingType.set(id, t);
|
|
4741
5344
|
}
|
|
4742
5345
|
setAliasExpander((t) => this.expand(t));
|
|
5346
|
+
setDeferredBound((t) => this.deferredBound(t));
|
|
4743
5347
|
try {
|
|
4744
5348
|
const env = /* @__PURE__ */ new Map();
|
|
4745
5349
|
this.visitBlock(this.program.body, env);
|
|
@@ -4747,6 +5351,7 @@ var TypeAnalyzer = class {
|
|
|
4747
5351
|
if (this.options.reportUnknownTypes) this.reportUnknownTypes();
|
|
4748
5352
|
} finally {
|
|
4749
5353
|
setAliasExpander(void 0);
|
|
5354
|
+
setDeferredBound(void 0);
|
|
4750
5355
|
}
|
|
4751
5356
|
return {
|
|
4752
5357
|
typeOf: this.typeOf,
|
|
@@ -4796,6 +5401,11 @@ var TypeAnalyzer = class {
|
|
|
4796
5401
|
this.aliases.set(qualified, exported.type);
|
|
4797
5402
|
}
|
|
4798
5403
|
}
|
|
5404
|
+
const asDefault = stmt.defaultImport && exports.types.get("default");
|
|
5405
|
+
if (stmt.defaultImport && asDefault) {
|
|
5406
|
+
this.importedTypes.set(stmt.defaultImport.name, asDefault);
|
|
5407
|
+
this.aliases.set(stmt.defaultImport.name, asDefault.type);
|
|
5408
|
+
}
|
|
4799
5409
|
for (const s of stmt.specifiers) {
|
|
4800
5410
|
const exported = exports.types.get(s.imported.name);
|
|
4801
5411
|
if (exported) {
|
|
@@ -4805,17 +5415,65 @@ var TypeAnalyzer = class {
|
|
|
4805
5415
|
}
|
|
4806
5416
|
}
|
|
4807
5417
|
}
|
|
4808
|
-
|
|
5418
|
+
/** `layering` is on for the prelude and for definitions files: a second
|
|
5419
|
+
* library that declares an alias already declared *adds* to it, the way a
|
|
5420
|
+
* second `declare` of a table's name does, so `@luaut/roblox` can give
|
|
5421
|
+
* `StringMethods` Luau's `split` without restating Lua's. The file being
|
|
5422
|
+
* analysed is not a layer: its own alias replaces what the libraries
|
|
5423
|
+
* gave, which is how a project opts out of a set. */
|
|
5424
|
+
registerAliasDefs(block, layering = false) {
|
|
4809
5425
|
for (const stmt of block.statements) {
|
|
4810
5426
|
const alias = stmt.type === "TypeAliasStatement" ? stmt : stmt.type === "ExportTypeAliasStatement" ? stmt.alias : void 0;
|
|
4811
|
-
if (alias)
|
|
5427
|
+
if (alias) {
|
|
5428
|
+
const previous = layering ? this.aliasDefs.get(alias.name.name) : void 0;
|
|
5429
|
+
const node = previous && !previous.class ? {
|
|
5430
|
+
type: "IntersectionTypeNode",
|
|
5431
|
+
types: [previous.node, alias.definition],
|
|
5432
|
+
line: alias.definition.line,
|
|
5433
|
+
column: alias.definition.column
|
|
5434
|
+
} : alias.definition;
|
|
5435
|
+
this.aliasDefs.set(alias.name.name, {
|
|
5436
|
+
params: previous && !previous.class && previous.params.length ? previous.params : alias.generics,
|
|
5437
|
+
node
|
|
5438
|
+
});
|
|
5439
|
+
}
|
|
4812
5440
|
if (stmt.type === "DeclareClassStatement") {
|
|
4813
5441
|
this.aliasDefs.set(stmt.name.name, { params: [], node: stmt.body, class: stmt });
|
|
4814
5442
|
}
|
|
5443
|
+
const declaration = stmt.type === "ExportStatement" || stmt.type === "ExportDefaultStatement" ? stmt.declaration : stmt;
|
|
5444
|
+
if (declaration.type === "ClassDeclaration") this.registerClass(declaration);
|
|
4815
5445
|
}
|
|
4816
5446
|
}
|
|
5447
|
+
/** A class written inside a function or a block names a type too — its
|
|
5448
|
+
* own instances', which its methods' `this` is annotated with. Type names
|
|
5449
|
+
* are one namespace here, so it is registered with the rest; only a
|
|
5450
|
+
* second class of the same name would notice. */
|
|
5451
|
+
registerNestedClasses() {
|
|
5452
|
+
walkNodes(this.program.body, (node) => {
|
|
5453
|
+
const record = node;
|
|
5454
|
+
if (record.type !== "ClassDeclaration") return;
|
|
5455
|
+
const declaration = node;
|
|
5456
|
+
if (this.aliasDefs.has(declaration.name.name)) return;
|
|
5457
|
+
this.registerClass(declaration);
|
|
5458
|
+
});
|
|
5459
|
+
}
|
|
5460
|
+
/** A class's name as a type. `node` is a placeholder: `resolveDef` and
|
|
5461
|
+
* `instantiateAlias` both go to the declaration itself. */
|
|
5462
|
+
registerClass(declaration) {
|
|
5463
|
+
this.aliasDefs.set(declaration.name.name, {
|
|
5464
|
+
params: declaration.typeParams,
|
|
5465
|
+
node: {
|
|
5466
|
+
type: "TableTypeNode",
|
|
5467
|
+
properties: [],
|
|
5468
|
+
line: declaration.line,
|
|
5469
|
+
column: declaration.column
|
|
5470
|
+
},
|
|
5471
|
+
runtimeClass: declaration
|
|
5472
|
+
});
|
|
5473
|
+
}
|
|
4817
5474
|
/** A non-generic definition's type. */
|
|
4818
5475
|
resolveDef(def) {
|
|
5476
|
+
if (def.runtimeClass) return this.instanceType(def.runtimeClass);
|
|
4819
5477
|
return def.class ? this.classType(def.class) : this.resolveType(def.node);
|
|
4820
5478
|
}
|
|
4821
5479
|
/** One type per class declaration, so every mention of a class is the same
|
|
@@ -4873,6 +5531,381 @@ var TypeAnalyzer = class {
|
|
|
4873
5531
|
if (this.program.body.statements.includes(stmt)) ownMembers();
|
|
4874
5532
|
return type;
|
|
4875
5533
|
}
|
|
5534
|
+
// ============================================================
|
|
5535
|
+
// `class ... end` — the runtime kind
|
|
5536
|
+
// ------------------------------------------------------------
|
|
5537
|
+
// A declaration says two things at once. Its *name as a type* is
|
|
5538
|
+
// the type of its instances, nominal the way `declare class` is:
|
|
5539
|
+
// only the class and the classes extending it produce one. Its
|
|
5540
|
+
// *name as a value* is the class table — the statics, the class
|
|
5541
|
+
// it extends (`ParentClass`), and the `new` that builds an
|
|
5542
|
+
// instance, which is an ordinary function and can be called as
|
|
5543
|
+
// one. An instance reaches its own class back through
|
|
5544
|
+
// `ClassObject`.
|
|
5545
|
+
//
|
|
5546
|
+
// Members are resolved into one shape, filled in two passes: the
|
|
5547
|
+
// fields first, then the functions. That order is what lets a
|
|
5548
|
+
// method body read `this.x` while the class it belongs to is
|
|
5549
|
+
// still being worked out.
|
|
5550
|
+
// ============================================================
|
|
5551
|
+
classShapes = /* @__PURE__ */ new WeakMap();
|
|
5552
|
+
classValues = /* @__PURE__ */ new WeakMap();
|
|
5553
|
+
/** Identity for a class written as a value, which has no name to be known
|
|
5554
|
+
* by: two of them are different types however alike they look. */
|
|
5555
|
+
classIdentities = /* @__PURE__ */ new WeakMap();
|
|
5556
|
+
classIdentityCount = 0;
|
|
5557
|
+
/** The class whose members are being read, so `super` knows its base. */
|
|
5558
|
+
currentClass;
|
|
5559
|
+
withClass(stmt, fn2) {
|
|
5560
|
+
const previous = this.currentClass;
|
|
5561
|
+
this.currentClass = stmt;
|
|
5562
|
+
try {
|
|
5563
|
+
return fn2();
|
|
5564
|
+
} finally {
|
|
5565
|
+
this.currentClass = previous;
|
|
5566
|
+
}
|
|
5567
|
+
}
|
|
5568
|
+
/** What the class is known by. A declaration is known by its name, the way
|
|
5569
|
+
* a `declare class` is — that is what makes it the same class across
|
|
5570
|
+
* modules. A class written as a value is known by where it is written. */
|
|
5571
|
+
classIdentity(stmt) {
|
|
5572
|
+
if (stmt.type === "ClassDeclaration") return stmt.name.name;
|
|
5573
|
+
let identity = this.classIdentities.get(stmt);
|
|
5574
|
+
if (!identity) {
|
|
5575
|
+
identity = `${stmt.name?.name ?? "class"}@${++this.classIdentityCount}`;
|
|
5576
|
+
this.classIdentities.set(stmt, identity);
|
|
5577
|
+
}
|
|
5578
|
+
return identity;
|
|
5579
|
+
}
|
|
5580
|
+
/** What it is *shown* as. A class written as a value has no name of its
|
|
5581
|
+
* own, so it borrows the one it is being bound to — `const Counter =
|
|
5582
|
+
* class ... end` reads as `Counter` everywhere. */
|
|
5583
|
+
className(stmt) {
|
|
5584
|
+
return stmt.name?.name ?? this.classDisplayNames.get(stmt) ?? "(class)";
|
|
5585
|
+
}
|
|
5586
|
+
classDisplayNames = /* @__PURE__ */ new WeakMap();
|
|
5587
|
+
/** `const Name = class ... end` — the name the class will be known by. */
|
|
5588
|
+
nameClassExpressions(stmt) {
|
|
5589
|
+
stmt.names.forEach((target, i) => {
|
|
5590
|
+
const value = stmt.init[i];
|
|
5591
|
+
if (target.type === "IdentifierPattern" && value?.type === "ClassExpression" && !value.name) {
|
|
5592
|
+
this.classDisplayNames.set(value, target.name);
|
|
5593
|
+
}
|
|
5594
|
+
});
|
|
5595
|
+
}
|
|
5596
|
+
classTypeParams(stmt) {
|
|
5597
|
+
return stmt.type === "ClassDeclaration" ? stmt.typeParams : [];
|
|
5598
|
+
}
|
|
5599
|
+
/** The class a declaration extends, when it is one written in this file.
|
|
5600
|
+
* An imported class is reached through its type and its value instead. */
|
|
5601
|
+
superDecl(stmt) {
|
|
5602
|
+
if (!stmt.superclass) return void 0;
|
|
5603
|
+
const base = this.aliasDefs.get(stmt.superclass.name)?.runtimeClass;
|
|
5604
|
+
return base && base !== stmt && !this.extendsThrough(base, stmt) ? base : void 0;
|
|
5605
|
+
}
|
|
5606
|
+
/** Does `from` reach `target` by `extends`? Guards against a cycle turning
|
|
5607
|
+
* resolution into a loop. */
|
|
5608
|
+
extendsThrough(from, target) {
|
|
5609
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5610
|
+
for (let cls = from; cls && !seen.has(cls); ) {
|
|
5611
|
+
if (cls === target) return true;
|
|
5612
|
+
seen.add(cls);
|
|
5613
|
+
cls = cls.superclass ? this.aliasDefs.get(cls.superclass.name)?.runtimeClass : void 0;
|
|
5614
|
+
}
|
|
5615
|
+
return false;
|
|
5616
|
+
}
|
|
5617
|
+
/** The instance type of what `stmt` extends, with the arguments it was
|
|
5618
|
+
* extended with filled in — a class in this file, or any class type a
|
|
5619
|
+
* name in scope stands for (an imported one). */
|
|
5620
|
+
baseInstance(stmt) {
|
|
5621
|
+
if (!stmt.superclass) return void 0;
|
|
5622
|
+
const written = (stmt.superArguments ?? []).map((argument) => this.resolveType(argument));
|
|
5623
|
+
if (this.aliasDefs.get(stmt.superclass.name)?.runtimeClass) {
|
|
5624
|
+
const local = this.superDecl(stmt);
|
|
5625
|
+
if (!local) return void 0;
|
|
5626
|
+
const base = this.instanceType(local);
|
|
5627
|
+
const params = this.classTypeParams(local);
|
|
5628
|
+
if (!params.length) return base;
|
|
5629
|
+
const applied = substitute(base, this.bindTypeArguments(params, written));
|
|
5630
|
+
return applied.kind === "object" ? applied : void 0;
|
|
5631
|
+
}
|
|
5632
|
+
const imported = this.importedTypes.get(stmt.superclass.name);
|
|
5633
|
+
const named = imported ? this.importedType(imported, stmt.superArguments ?? []) : this.aliases.get(stmt.superclass.name);
|
|
5634
|
+
return named && isClassType(named) ? named : void 0;
|
|
5635
|
+
}
|
|
5636
|
+
/** One instance type per declaration, so every mention of the class is the
|
|
5637
|
+
* same object — the `this` of its own methods included. Members are read
|
|
5638
|
+
* lazily for the reason `declare class` reads them lazily: a class can
|
|
5639
|
+
* name itself, and two classes can name each other. */
|
|
5640
|
+
instanceType(stmt) {
|
|
5641
|
+
const cached = this.instanceTypes.get(stmt);
|
|
5642
|
+
if (cached) return cached;
|
|
5643
|
+
const name = this.className(stmt);
|
|
5644
|
+
const identity = this.classIdentity(stmt);
|
|
5645
|
+
const params = this.classTypeParams(stmt);
|
|
5646
|
+
const type = { kind: "object", name };
|
|
5647
|
+
this.instanceTypes.set(stmt, type);
|
|
5648
|
+
const own = params.map((p) => typeParam(p.name, p.constraint ? this.resolveType(p.constraint) : void 0));
|
|
5649
|
+
const info = () => {
|
|
5650
|
+
const base = this.baseInstance(stmt)?.class;
|
|
5651
|
+
const typeArguments = new Map(base?.typeArguments ?? []);
|
|
5652
|
+
if (own.length) typeArguments.set(identity, own);
|
|
5653
|
+
return {
|
|
5654
|
+
name: identity,
|
|
5655
|
+
superclass: base?.name,
|
|
5656
|
+
ancestors: [identity, ...base?.ancestors ?? []],
|
|
5657
|
+
typeArguments: typeArguments.size ? typeArguments : void 0
|
|
5658
|
+
};
|
|
5659
|
+
};
|
|
5660
|
+
Object.defineProperties(type, {
|
|
5661
|
+
properties: {
|
|
5662
|
+
enumerable: true,
|
|
5663
|
+
get: () => {
|
|
5664
|
+
const shape = this.shapeOf(stmt);
|
|
5665
|
+
const base = this.baseInstance(stmt)?.properties;
|
|
5666
|
+
const members = base?.size ? new Map([...base, ...shape.instance]) : new Map(shape.instance);
|
|
5667
|
+
members.set("ClassObject", { type: this.classValueType(stmt), optional: false, readonly: true });
|
|
5668
|
+
return members;
|
|
5669
|
+
}
|
|
5670
|
+
},
|
|
5671
|
+
class: { enumerable: true, get: info }
|
|
5672
|
+
});
|
|
5673
|
+
return type;
|
|
5674
|
+
}
|
|
5675
|
+
/** `Box<T>` as its own methods see it — a reference, not the object, so
|
|
5676
|
+
* substituting the arguments in does not have to walk the class. */
|
|
5677
|
+
selfTypeOf(stmt) {
|
|
5678
|
+
const params = this.classTypeParams(stmt);
|
|
5679
|
+
if (!params.length || stmt.type !== "ClassDeclaration") return this.instanceType(stmt);
|
|
5680
|
+
return {
|
|
5681
|
+
kind: "genericRef",
|
|
5682
|
+
name: stmt.name.name,
|
|
5683
|
+
typeArguments: params.map((p) => typeParam(p.name, p.constraint ? this.resolveType(p.constraint) : void 0))
|
|
5684
|
+
};
|
|
5685
|
+
}
|
|
5686
|
+
/** The class table: the statics, what it inherits from the class it
|
|
5687
|
+
* extends, `ParentClass`, `ClassObject`, and `new`. */
|
|
5688
|
+
classValueType(stmt) {
|
|
5689
|
+
const cached = this.classValues.get(stmt);
|
|
5690
|
+
if (cached) return cached;
|
|
5691
|
+
const type = objectType([]);
|
|
5692
|
+
this.classValues.set(stmt, type);
|
|
5693
|
+
type.name = `typeof ${this.className(stmt)}`;
|
|
5694
|
+
const shape = this.shapeOf(stmt);
|
|
5695
|
+
const local = this.superDecl(stmt);
|
|
5696
|
+
const parent = local ? this.classValueType(local) : stmt.superclass ? this.classStaticsByNameType(stmt.superclass.name) : void 0;
|
|
5697
|
+
if (parent?.kind === "object") {
|
|
5698
|
+
for (const [key, property] of parent.properties) {
|
|
5699
|
+
if (!CLASS_LINKS.has(key)) type.properties.set(key, property);
|
|
5700
|
+
}
|
|
5701
|
+
}
|
|
5702
|
+
for (const [key, property] of shape.statics) type.properties.set(key, property);
|
|
5703
|
+
const constructor = this.constructorType(stmt);
|
|
5704
|
+
const params = this.classTypeParams(stmt);
|
|
5705
|
+
type.properties.set("new", {
|
|
5706
|
+
type: fn(
|
|
5707
|
+
constructor?.params.filter((p) => p.name !== "this") ?? [],
|
|
5708
|
+
this.selfTypeOf(stmt),
|
|
5709
|
+
constructor?.varargs,
|
|
5710
|
+
params.map((p) => p.name)
|
|
5711
|
+
),
|
|
5712
|
+
optional: false,
|
|
5713
|
+
readonly: true
|
|
5714
|
+
});
|
|
5715
|
+
type.properties.set("ParentClass", { type: parent ?? nilType, optional: false, readonly: true });
|
|
5716
|
+
return type;
|
|
5717
|
+
}
|
|
5718
|
+
/** The value side of a class named by a binding rather than by a
|
|
5719
|
+
* declaration in this file — an imported one. */
|
|
5720
|
+
classStaticsByNameType(name) {
|
|
5721
|
+
const id = this.classBindingByName(name);
|
|
5722
|
+
const declared = id !== void 0 ? this.bindingType.get(id) : void 0;
|
|
5723
|
+
return declared?.kind === "object" ? declared : void 0;
|
|
5724
|
+
}
|
|
5725
|
+
/** The binding a class name stands for, wherever it was declared. */
|
|
5726
|
+
classBindingByName(name) {
|
|
5727
|
+
for (const [id, binding] of this.scopes.bindings) {
|
|
5728
|
+
if (binding.name === name && binding.declaredBy === "class") return id;
|
|
5729
|
+
}
|
|
5730
|
+
return this.scopes.globalsByName.get(name);
|
|
5731
|
+
}
|
|
5732
|
+
/** What `super(...)` takes: the constructor of the class `stmt` extends,
|
|
5733
|
+
* its own skipped. */
|
|
5734
|
+
baseConstructorType(stmt) {
|
|
5735
|
+
return this.constructorType(stmt, /* @__PURE__ */ new Set([stmt]));
|
|
5736
|
+
}
|
|
5737
|
+
/** A class's constructor signature — its own, or the one it inherits. */
|
|
5738
|
+
constructorType(stmt, seen = /* @__PURE__ */ new Set()) {
|
|
5739
|
+
if (seen.has(stmt)) return void 0;
|
|
5740
|
+
seen.add(stmt);
|
|
5741
|
+
const own = this.shapeOf(stmt).ctor;
|
|
5742
|
+
if (own) return own;
|
|
5743
|
+
const local = this.superDecl(stmt);
|
|
5744
|
+
if (local) return this.constructorType(local, seen);
|
|
5745
|
+
const parent = stmt.superclass ? this.classStaticsByNameType(stmt.superclass.name) : void 0;
|
|
5746
|
+
const inherited = parent && this.overloadsOf(this.propertyType(parent, "new"))[0];
|
|
5747
|
+
return inherited;
|
|
5748
|
+
}
|
|
5749
|
+
/** `super` as a value: the base class's instance members with the `this`
|
|
5750
|
+
* slot already filled, because `super.m(a)` passes this instance. */
|
|
5751
|
+
superType(stmt) {
|
|
5752
|
+
const base = this.baseInstance(stmt);
|
|
5753
|
+
if (!base) return anyType;
|
|
5754
|
+
const entries = [];
|
|
5755
|
+
for (const [name, property] of base.properties) {
|
|
5756
|
+
const bound = this.overloadsOf(property.type);
|
|
5757
|
+
entries.push([name, bound.length ? {
|
|
5758
|
+
...property,
|
|
5759
|
+
type: intersection(bound.map((f) => this.takesSelf(f) ? fn(f.params.slice(1), f.returns, f.varargs, f.typeParams) : f))
|
|
5760
|
+
} : property]);
|
|
5761
|
+
}
|
|
5762
|
+
return objectType(entries);
|
|
5763
|
+
}
|
|
5764
|
+
shapeOf(stmt) {
|
|
5765
|
+
const cached = this.classShapes.get(stmt);
|
|
5766
|
+
if (cached) return cached;
|
|
5767
|
+
const shape = { instance: /* @__PURE__ */ new Map(), statics: /* @__PURE__ */ new Map(), filling: true };
|
|
5768
|
+
this.classShapes.set(stmt, shape);
|
|
5769
|
+
const wasEmitting = this.emitDiagnostics;
|
|
5770
|
+
this.emitDiagnostics = false;
|
|
5771
|
+
try {
|
|
5772
|
+
this.withClass(stmt, () => this.withTypeParams(this.classTypeParams(stmt), () => {
|
|
5773
|
+
this.withSelfType(this.selfTypeOf(stmt), () => this.fillShape(stmt, shape));
|
|
5774
|
+
}));
|
|
5775
|
+
} finally {
|
|
5776
|
+
this.emitDiagnostics = wasEmitting;
|
|
5777
|
+
shape.filling = false;
|
|
5778
|
+
}
|
|
5779
|
+
return shape;
|
|
5780
|
+
}
|
|
5781
|
+
fillShape(stmt, shape) {
|
|
5782
|
+
const put = (isStatic, name, property) => {
|
|
5783
|
+
(isStatic ? shape.statics : shape.instance).set(name, property);
|
|
5784
|
+
};
|
|
5785
|
+
for (const member of stmt.members) {
|
|
5786
|
+
if (member.type !== "ClassField") continue;
|
|
5787
|
+
const type = member.typeAnnotation ? this.resolveType(member.typeAnnotation) : member.init ? widen(this.infer(member.init, /* @__PURE__ */ new Map())) : anyType;
|
|
5788
|
+
put(member.isStatic, member.name.name, { type, optional: false });
|
|
5789
|
+
}
|
|
5790
|
+
for (const member of stmt.members) {
|
|
5791
|
+
switch (member.type) {
|
|
5792
|
+
case "ClassField":
|
|
5793
|
+
break;
|
|
5794
|
+
case "ClassMethod": {
|
|
5795
|
+
this.paramsFromSignatures(member.func, member.signatures);
|
|
5796
|
+
const type = member.signatures?.length ? intersection(member.signatures.map((sig) => this.signatureToFnType(sig))) : this.inferFunctionBody(member.func, /* @__PURE__ */ new Map());
|
|
5797
|
+
put(member.isStatic, member.name.name, { type, optional: false });
|
|
5798
|
+
break;
|
|
5799
|
+
}
|
|
5800
|
+
case "ClassAccessor": {
|
|
5801
|
+
const signature = this.inferFunctionBody(member.func, /* @__PURE__ */ new Map());
|
|
5802
|
+
if (signature.kind !== "function") break;
|
|
5803
|
+
const target = member.isStatic ? shape.statics : shape.instance;
|
|
5804
|
+
const existing = target.get(member.name.name);
|
|
5805
|
+
if (member.kind === "get") {
|
|
5806
|
+
put(member.isStatic, member.name.name, {
|
|
5807
|
+
type: signature.returns,
|
|
5808
|
+
optional: false,
|
|
5809
|
+
readonly: existing === void 0 || existing.readonly !== false
|
|
5810
|
+
});
|
|
5811
|
+
} else {
|
|
5812
|
+
put(member.isStatic, member.name.name, {
|
|
5813
|
+
type: existing?.type ?? signature.params[signature.params.length - 1]?.type ?? anyType,
|
|
5814
|
+
optional: false,
|
|
5815
|
+
readonly: false
|
|
5816
|
+
});
|
|
5817
|
+
}
|
|
5818
|
+
break;
|
|
5819
|
+
}
|
|
5820
|
+
case "ClassConstructor": {
|
|
5821
|
+
const signature = this.inferFunctionBody(member.func, /* @__PURE__ */ new Map());
|
|
5822
|
+
if (signature.kind === "function") shape.ctor = signature;
|
|
5823
|
+
break;
|
|
5824
|
+
}
|
|
5825
|
+
}
|
|
5826
|
+
}
|
|
5827
|
+
}
|
|
5828
|
+
/** Bind the class's value, and check what its members say. Shared by the
|
|
5829
|
+
* declaration and the expression forms. */
|
|
5830
|
+
visitClass(stmt, env) {
|
|
5831
|
+
this.checkClassDeclaration(stmt);
|
|
5832
|
+
const value = this.classValueType(stmt);
|
|
5833
|
+
this.withClass(stmt, () => this.withTypeParams(this.classTypeParams(stmt), () => {
|
|
5834
|
+
this.withSelfType(this.selfTypeOf(stmt), () => {
|
|
5835
|
+
for (const member of stmt.members) {
|
|
5836
|
+
if (member.type === "ClassField") {
|
|
5837
|
+
if (!member.init) continue;
|
|
5838
|
+
const declared = member.typeAnnotation ? this.resolveType(member.typeAnnotation) : void 0;
|
|
5839
|
+
if (declared) this.applyContext(member.init, declared);
|
|
5840
|
+
const actual = this.infer(member.init, env);
|
|
5841
|
+
if (declared && this.emitDiagnostics && !isAssignable(actual, declared)) {
|
|
5842
|
+
this.diagnostics.push({
|
|
5843
|
+
node: member.init,
|
|
5844
|
+
message: `Type '${formatType(actual)}' is not assignable to type '${formatType(declared)}'`
|
|
5845
|
+
});
|
|
5846
|
+
}
|
|
5847
|
+
continue;
|
|
5848
|
+
}
|
|
5849
|
+
this.checkParamOrder(member.func.params, member);
|
|
5850
|
+
for (const signature of member.signatures ?? []) {
|
|
5851
|
+
this.checkParamOrder(signature.params, member);
|
|
5852
|
+
}
|
|
5853
|
+
this.visitFunctionBody(member.func, env);
|
|
5854
|
+
}
|
|
5855
|
+
});
|
|
5856
|
+
}));
|
|
5857
|
+
return value;
|
|
5858
|
+
}
|
|
5859
|
+
/** What a class gets wrong, reported where it is written. */
|
|
5860
|
+
checkClassDeclaration(stmt) {
|
|
5861
|
+
if (!this.emitDiagnostics) return;
|
|
5862
|
+
const report = (node, message) => {
|
|
5863
|
+
this.diagnostics.push({ node, message });
|
|
5864
|
+
};
|
|
5865
|
+
const name = this.className(stmt);
|
|
5866
|
+
if (stmt.superclass) {
|
|
5867
|
+
const local = this.aliasDefs.get(stmt.superclass.name)?.runtimeClass;
|
|
5868
|
+
if (local && this.extendsThrough(local, stmt)) {
|
|
5869
|
+
report(stmt.superclass, `'${name}' cannot extend itself`);
|
|
5870
|
+
} else if (!this.baseInstance(stmt)) {
|
|
5871
|
+
const known = this.aliases.has(stmt.superclass.name) || this.importedTypes.has(stmt.superclass.name);
|
|
5872
|
+
report(stmt.superclass, known ? `'${stmt.superclass.name}' is not a class; a class can only extend another class` : `Cannot find class '${stmt.superclass.name}'`);
|
|
5873
|
+
}
|
|
5874
|
+
}
|
|
5875
|
+
for (const member of stmt.members) {
|
|
5876
|
+
if (member.type === "ClassConstructor") continue;
|
|
5877
|
+
if (CLASS_RESERVED.has(member.name.name)) {
|
|
5878
|
+
report(member.name, `'${member.name.name}' is what the compiler calls part of a class; a member cannot be named that`);
|
|
5879
|
+
}
|
|
5880
|
+
}
|
|
5881
|
+
const seen = /* @__PURE__ */ new Map();
|
|
5882
|
+
for (const member of stmt.members) {
|
|
5883
|
+
if (member.type === "ClassConstructor") {
|
|
5884
|
+
if (seen.has("constructor")) report(member, "A class has one constructor");
|
|
5885
|
+
seen.set("constructor", member.type);
|
|
5886
|
+
continue;
|
|
5887
|
+
}
|
|
5888
|
+
const key = `${member.isStatic ? "static " : ""}${member.name.name}`;
|
|
5889
|
+
const before = seen.get(key);
|
|
5890
|
+
const pair = member.type === "ClassAccessor" && before === "ClassAccessor";
|
|
5891
|
+
if (before !== void 0 && !pair) {
|
|
5892
|
+
report(member.name, `'${member.name.name}' is declared twice in class '${name}'`);
|
|
5893
|
+
}
|
|
5894
|
+
seen.set(key, member.type);
|
|
5895
|
+
}
|
|
5896
|
+
const constructor = stmt.members.find((m) => m.type === "ClassConstructor");
|
|
5897
|
+
if (stmt.superclass && this.baseInstance(stmt) && constructor && !callsSuper(constructor.func.body)) {
|
|
5898
|
+
report(constructor, `'${name}' extends '${stmt.superclass.name}', so its constructor must call 'super(...)'`);
|
|
5899
|
+
}
|
|
5900
|
+
const assigned = constructor ? assignedFields(constructor.func.body) : /* @__PURE__ */ new Set();
|
|
5901
|
+
for (const member of stmt.members) {
|
|
5902
|
+
if (member.type !== "ClassField" || member.isStatic || member.init) continue;
|
|
5903
|
+
if (assigned.has(member.name.name)) continue;
|
|
5904
|
+
const type = member.typeAnnotation ? this.withTypeParams(this.classTypeParams(stmt), () => this.resolveType(member.typeAnnotation)) : anyType;
|
|
5905
|
+
if (isAssignable(nilType, type)) continue;
|
|
5906
|
+
report(member.name, `'${member.name.name}' has no value: give it one, assign it in the constructor, or let its type admit nil`);
|
|
5907
|
+
}
|
|
5908
|
+
}
|
|
4876
5909
|
/** `extends` must name a class, and the chain must end. */
|
|
4877
5910
|
checkClass(stmt) {
|
|
4878
5911
|
if (!stmt.superclass || !this.emitDiagnostics) return;
|
|
@@ -5129,6 +6162,7 @@ var TypeAnalyzer = class {
|
|
|
5129
6162
|
instantiateAlias(def, args) {
|
|
5130
6163
|
if (this.instantiationDepth > 20) return unknownType;
|
|
5131
6164
|
const subst = this.bindTypeArguments(def.params, args);
|
|
6165
|
+
if (def.runtimeClass) return substitute(this.instanceType(def.runtimeClass), subst);
|
|
5132
6166
|
this.instantiationDepth++;
|
|
5133
6167
|
try {
|
|
5134
6168
|
const body = this.withTypeParams(def.params, () => this.resolveType(def.node));
|
|
@@ -5273,15 +6307,17 @@ var TypeAnalyzer = class {
|
|
|
5273
6307
|
case "FunctionTypeNode": {
|
|
5274
6308
|
const names = node.generics.map((g) => g.name);
|
|
5275
6309
|
return this.withTypeParams(node.generics, () => {
|
|
5276
|
-
const params = node.params.map((p) => ({
|
|
6310
|
+
const params = node.params.filter((p) => !p.rest).map((p) => ({
|
|
5277
6311
|
name: p.name,
|
|
5278
6312
|
type: p.optional ? optional(this.resolveType(p.typeAnnotation)) : this.resolveType(p.typeAnnotation),
|
|
5279
6313
|
optional: p.optional
|
|
5280
6314
|
}));
|
|
6315
|
+
const restParam = node.params.find((p) => p.rest);
|
|
6316
|
+
const restElement = restParam ? this.resolveType(restParam.typeAnnotation) : void 0;
|
|
5281
6317
|
return this.withTypeParamDefaults(fn(
|
|
5282
6318
|
params,
|
|
5283
6319
|
this.resolveType(node.returnType),
|
|
5284
|
-
node.hasVarargs ? node.varargType ? this.resolveType(node.varargType) : anyType : void 0,
|
|
6320
|
+
restParam ? restElement?.kind === "array" ? restElement.element : unknownType : node.hasVarargs ? node.varargType ? this.resolveType(node.varargType) : anyType : void 0,
|
|
5285
6321
|
names,
|
|
5286
6322
|
this.resolvePredicate(node.predicate, params)
|
|
5287
6323
|
), node.generics);
|
|
@@ -5475,7 +6511,10 @@ var TypeAnalyzer = class {
|
|
|
5475
6511
|
accessType(obj, index) {
|
|
5476
6512
|
if (index.kind === "union") return union(index.types.map((m) => this.accessType(obj, m)));
|
|
5477
6513
|
if (index.kind === "literal" && typeof index.value === "string") {
|
|
5478
|
-
|
|
6514
|
+
const member = this.propertyType(obj, index.value);
|
|
6515
|
+
if (member.kind !== "unknown") return member;
|
|
6516
|
+
const t = this.expand(obj);
|
|
6517
|
+
return t.kind === "object" && !t.class && !t.indexer ? nilType : member;
|
|
5479
6518
|
}
|
|
5480
6519
|
return this.indexedType(obj, index);
|
|
5481
6520
|
}
|
|
@@ -5628,12 +6667,13 @@ var TypeAnalyzer = class {
|
|
|
5628
6667
|
visitStatement(stmt, env) {
|
|
5629
6668
|
switch (stmt.type) {
|
|
5630
6669
|
case "VariableDeclaration": {
|
|
6670
|
+
this.nameClassExpressions(stmt);
|
|
5631
6671
|
stmt.names.forEach((target, i) => {
|
|
5632
6672
|
if (target.type === "IdentifierPattern" && target.typeAnnotation && stmt.init[i]) {
|
|
5633
6673
|
this.applyContext(stmt.init[i], this.resolveType(target.typeAnnotation));
|
|
5634
6674
|
}
|
|
5635
6675
|
});
|
|
5636
|
-
const { types: valueTypes, sources } = this.valueList(stmt.init, env);
|
|
6676
|
+
const { types: valueTypes, sources } = this.valueList(stmt.init, env, stmt.names.length);
|
|
5637
6677
|
stmt.names.forEach((target, i) => {
|
|
5638
6678
|
const inferred = valueTypes[i] ?? (stmt.init.length ? unknownType : nilType);
|
|
5639
6679
|
const source = sources[i];
|
|
@@ -5658,6 +6698,15 @@ var TypeAnalyzer = class {
|
|
|
5658
6698
|
});
|
|
5659
6699
|
return;
|
|
5660
6700
|
}
|
|
6701
|
+
case "ClassDeclaration": {
|
|
6702
|
+
const id = this.bindingIdByName(stmt.name.name, stmt.name);
|
|
6703
|
+
const value = this.visitClass(stmt, env);
|
|
6704
|
+
if (id !== void 0) {
|
|
6705
|
+
this.bindingType.set(id, value);
|
|
6706
|
+
this.setBinding(env, id, value);
|
|
6707
|
+
}
|
|
6708
|
+
return;
|
|
6709
|
+
}
|
|
5661
6710
|
case "FunctionDeclaration": {
|
|
5662
6711
|
this.checkParamOrder(stmt.func.params, stmt);
|
|
5663
6712
|
for (const sig of stmt.signatures ?? []) this.checkParamOrder(sig.params, stmt);
|
|
@@ -5713,7 +6762,7 @@ var TypeAnalyzer = class {
|
|
|
5713
6762
|
if (id !== void 0 && this.annotated.has(id)) this.applyContext(value, this.bindingType.get(id));
|
|
5714
6763
|
}
|
|
5715
6764
|
});
|
|
5716
|
-
const { types: valueTypes, sources } = this.valueList(stmt.values, env);
|
|
6765
|
+
const { types: valueTypes, sources } = this.valueList(stmt.values, env, stmt.targets.length);
|
|
5717
6766
|
stmt.targets.forEach((target, i) => {
|
|
5718
6767
|
const vt = valueTypes[i] ?? unknownType;
|
|
5719
6768
|
const source = sources[i];
|
|
@@ -5821,7 +6870,8 @@ var TypeAnalyzer = class {
|
|
|
5821
6870
|
stmt.arguments.forEach((a, i) => this.applyContext(a, declared.elements[i]));
|
|
5822
6871
|
}
|
|
5823
6872
|
}
|
|
5824
|
-
const
|
|
6873
|
+
const want = declared?.kind === "tuple" && declared.isPack ? declared.elements.length : 0;
|
|
6874
|
+
const { types, sources } = this.valueList(stmt.arguments, env, want);
|
|
5825
6875
|
this.checkReturn(stmt, declared, types, sources, env);
|
|
5826
6876
|
if (this.returnTypes) {
|
|
5827
6877
|
this.returnTypes.push(stmt.arguments.length === 0 ? nilType : types.length === 1 ? types[0] : tuple([...types], true));
|
|
@@ -5832,7 +6882,8 @@ var TypeAnalyzer = class {
|
|
|
5832
6882
|
this.visitStatement(stmt.declaration, env);
|
|
5833
6883
|
return;
|
|
5834
6884
|
case "ExportDefaultStatement":
|
|
5835
|
-
this.
|
|
6885
|
+
if (stmt.declaration.type === "ClassDeclaration") this.visitStatement(stmt.declaration, env);
|
|
6886
|
+
else this.infer(stmt.declaration, env);
|
|
5836
6887
|
return;
|
|
5837
6888
|
case "ExportNamedStatement": {
|
|
5838
6889
|
if (stmt.source) {
|
|
@@ -5911,12 +6962,35 @@ var TypeAnalyzer = class {
|
|
|
5911
6962
|
* `sources` maps each produced value back to the expression it came from
|
|
5912
6963
|
* (undefined for the 2nd and later values of a multi-value call), so the
|
|
5913
6964
|
* caller can still do contextual typing against the written expression. */
|
|
5914
|
-
valueList(exprs, env) {
|
|
6965
|
+
valueList(exprs, env, want = 0) {
|
|
5915
6966
|
const types = [];
|
|
5916
6967
|
const sources = [];
|
|
5917
6968
|
exprs.forEach((e, i) => {
|
|
5918
6969
|
const t = this.infer(e, env);
|
|
5919
6970
|
const last = i === exprs.length - 1;
|
|
6971
|
+
if (e.type === "SpreadElement") {
|
|
6972
|
+
const held = this.typeOf.get(e.argument);
|
|
6973
|
+
const expanded = held && this.expand(held);
|
|
6974
|
+
if (expanded?.kind === "tuple") {
|
|
6975
|
+
for (const element of expanded.elements) {
|
|
6976
|
+
types.push(element);
|
|
6977
|
+
sources.push(e);
|
|
6978
|
+
}
|
|
6979
|
+
return;
|
|
6980
|
+
}
|
|
6981
|
+
do {
|
|
6982
|
+
types.push(t);
|
|
6983
|
+
sources.push(e);
|
|
6984
|
+
} while (last && types.length < want);
|
|
6985
|
+
return;
|
|
6986
|
+
}
|
|
6987
|
+
if (last && e.type === "VarargExpression") {
|
|
6988
|
+
do {
|
|
6989
|
+
types.push(t);
|
|
6990
|
+
sources.push(types.length - 1 === i ? e : void 0);
|
|
6991
|
+
} while (types.length < want);
|
|
6992
|
+
return;
|
|
6993
|
+
}
|
|
5920
6994
|
if (last && t.kind === "tuple" && t.isPack && producesMultipleValues(e)) {
|
|
5921
6995
|
t.elements.forEach((el, j) => {
|
|
5922
6996
|
types.push(el);
|
|
@@ -6005,7 +7079,8 @@ var TypeAnalyzer = class {
|
|
|
6005
7079
|
/** A parameter's type: annotation, else a shape synthesized from a
|
|
6006
7080
|
* destructuring pattern, else inferred from its default, else `any`. */
|
|
6007
7081
|
paramType(p, env) {
|
|
6008
|
-
|
|
7082
|
+
const receiver = p.name === "self" || p.name === "this";
|
|
7083
|
+
if (!p.typeAnnotation && !p.pattern && !p.default && receiver && this.selfType) {
|
|
6009
7084
|
return this.selfType;
|
|
6010
7085
|
}
|
|
6011
7086
|
if (p.typeAnnotation) {
|
|
@@ -6013,6 +7088,7 @@ var TypeAnalyzer = class {
|
|
|
6013
7088
|
if (p.default) this.applyContext(p.default, t);
|
|
6014
7089
|
return p.optional ? optional(t) : t;
|
|
6015
7090
|
}
|
|
7091
|
+
if (p.rest) return arrayOf(unknownType);
|
|
6016
7092
|
if (p.pattern) return this.patternToType(p.pattern, env);
|
|
6017
7093
|
if (p.default) return widen(this.infer(p.default, env));
|
|
6018
7094
|
return this.contextualParams.get(p) ?? anyType;
|
|
@@ -6033,6 +7109,11 @@ var TypeAnalyzer = class {
|
|
|
6033
7109
|
this.expectedTypeOf.set(e, expected);
|
|
6034
7110
|
if (e.type === "ArrayExpression") return this.applyArrayContext(e, expected);
|
|
6035
7111
|
if (e.type === "TableExpression") return this.applyTableContext(e, expected);
|
|
7112
|
+
if (e.type === "BinaryExpression" && (e.operator === "or" || e.operator === "and")) {
|
|
7113
|
+
if (e.operator === "or") this.applyContext(e.left, expected);
|
|
7114
|
+
this.applyContext(e.right, expected);
|
|
7115
|
+
return;
|
|
7116
|
+
}
|
|
6036
7117
|
if (e.type !== "FunctionExpression") return;
|
|
6037
7118
|
const members = expected.kind === "union" ? expected.types : [expected];
|
|
6038
7119
|
const signatures = members.flatMap((m) => this.overloadsOf(this.expand(m)));
|
|
@@ -6058,7 +7139,7 @@ var TypeAnalyzer = class {
|
|
|
6058
7139
|
const target = this.expectedMembers(expected).find((m) => m.kind === "array" || m.kind === "tuple");
|
|
6059
7140
|
if (!target) return;
|
|
6060
7141
|
if (!e.elements.length) {
|
|
6061
|
-
|
|
7142
|
+
this.contextualArrays.set(e, target);
|
|
6062
7143
|
return;
|
|
6063
7144
|
}
|
|
6064
7145
|
e.elements.forEach((element, i) => {
|
|
@@ -6073,13 +7154,15 @@ var TypeAnalyzer = class {
|
|
|
6073
7154
|
const objects = this.expectedMembers(expected).filter((m) => m.kind === "object");
|
|
6074
7155
|
if (!objects.length) return;
|
|
6075
7156
|
for (const field of e.fields) {
|
|
6076
|
-
if (field.type !== "TableFieldNamed") continue;
|
|
6077
|
-
const key = field.key.type === "Identifier" ? field.key.name : field.key.value;
|
|
7157
|
+
if (field.type !== "TableFieldNamed" && field.type !== "TableFieldShorthand") continue;
|
|
7158
|
+
const key = field.type === "TableFieldShorthand" ? field.name.name : field.key.type === "Identifier" ? field.key.name : field.key.value;
|
|
6078
7159
|
const types = objects.flatMap((o) => {
|
|
6079
7160
|
const property = o.properties.get(key);
|
|
6080
7161
|
return property ? [property.type] : o.indexer ? [o.indexer.value] : [];
|
|
6081
7162
|
});
|
|
6082
|
-
if (types.length)
|
|
7163
|
+
if (types.length) {
|
|
7164
|
+
this.applyContext(field.type === "TableFieldShorthand" ? field.name : field.value, union(types));
|
|
7165
|
+
}
|
|
6083
7166
|
}
|
|
6084
7167
|
}
|
|
6085
7168
|
/** The members of an expected type worth matching a literal against:
|
|
@@ -6116,13 +7199,26 @@ var TypeAnalyzer = class {
|
|
|
6116
7199
|
}
|
|
6117
7200
|
return tuple(target.elements.map((el) => el ? leaf(el.value, el.default) : anyType));
|
|
6118
7201
|
}
|
|
7202
|
+
/** What a vararg function's `...` holds, one value at a time.
|
|
7203
|
+
*
|
|
7204
|
+
* Written three ways, and they mean the same call: `...` says nothing,
|
|
7205
|
+
* `...: T` says each value is a `T`, and `...rest: T[]` collects them
|
|
7206
|
+
* into an array the body reads by name. Only the last changes what the
|
|
7207
|
+
* body sees — the signature is the same either way. */
|
|
7208
|
+
varargElement(func) {
|
|
7209
|
+
if (!func.hasVarargs) return void 0;
|
|
7210
|
+
const rest = func.params.find((p) => p.rest);
|
|
7211
|
+
if (!rest) return func.varargTypeAnnotation ? this.resolveType(func.varargTypeAnnotation) : anyType;
|
|
7212
|
+
const declared = rest.typeAnnotation ? this.resolveType(rest.typeAnnotation) : void 0;
|
|
7213
|
+
return declared?.kind === "array" ? declared.element : unknownType;
|
|
7214
|
+
}
|
|
6119
7215
|
/** The type of `...` in each function body being walked. */
|
|
6120
7216
|
varargs = [];
|
|
6121
7217
|
/** What each function body being walked declared it returns. */
|
|
6122
7218
|
declaredReturns = [];
|
|
6123
7219
|
/** Run `body` with `...` and `return` as `func` declares them. */
|
|
6124
7220
|
withVarargs(func, body) {
|
|
6125
|
-
this.varargs.push(
|
|
7221
|
+
this.varargs.push(this.varargElement(func));
|
|
6126
7222
|
this.declaredReturns.push(func.predicate ? booleanType : func.returnType ? this.resolveType(func.returnType) : void 0);
|
|
6127
7223
|
try {
|
|
6128
7224
|
return body();
|
|
@@ -6203,6 +7299,13 @@ var TypeAnalyzer = class {
|
|
|
6203
7299
|
const param = p.type.kind === "typeParam" && p.type.constraint ? { ...p.type, constraint: this.reduceType(p.type.constraint) } : p.type;
|
|
6204
7300
|
unify(p.type, keepsLiterals(param) ? arg : widen(arg), vars, subst);
|
|
6205
7301
|
});
|
|
7302
|
+
if (f.varargs) {
|
|
7303
|
+
const keeps = keepsLiterals(f.varargs);
|
|
7304
|
+
for (let i = f.params.length; i < argTypes.length; i++) {
|
|
7305
|
+
const arg = argTypes[i];
|
|
7306
|
+
if (arg !== void 0) unify(f.varargs, keeps ? arg : widen(arg), vars, subst);
|
|
7307
|
+
}
|
|
7308
|
+
}
|
|
6206
7309
|
for (const name of f.typeParams ?? []) {
|
|
6207
7310
|
if (!subst.has(name)) subst.set(name, f.typeParamDefaults?.[name] ?? unknownType);
|
|
6208
7311
|
}
|
|
@@ -6227,11 +7330,11 @@ var TypeAnalyzer = class {
|
|
|
6227
7330
|
* after every concrete signature has been tried. That ordering is what
|
|
6228
7331
|
* lets `typeof` declare `(v: number) -> "number"` alongside a trailing
|
|
6229
7332
|
* `<T>(v: T) -> string` and still pick the precise one. */
|
|
6230
|
-
pickOverload(fns, argTypes, argsFor) {
|
|
7333
|
+
pickOverload(fns, argTypes, argsFor, spread) {
|
|
6231
7334
|
for (const generic of [false, true]) {
|
|
6232
7335
|
for (const f of fns) {
|
|
6233
7336
|
if ((f.typeParams?.length ?? 0) > 0 !== generic) continue;
|
|
6234
|
-
if (this.overloadAccepts(f, argsFor ? argsFor(f) : argTypes)) return f;
|
|
7337
|
+
if (this.overloadAccepts(f, argsFor ? argsFor(f) : argTypes, spread)) return f;
|
|
6235
7338
|
}
|
|
6236
7339
|
}
|
|
6237
7340
|
return void 0;
|
|
@@ -6266,12 +7369,27 @@ var TypeAnalyzer = class {
|
|
|
6266
7369
|
* own type parameters stand for what the call would infer, so each is
|
|
6267
7370
|
* checked only against its constraint — `<K extends keyof Services>`
|
|
6268
7371
|
* accepts `"Players"` but not `""`. */
|
|
6269
|
-
overloadAccepts(f, argTypes) {
|
|
6270
|
-
|
|
7372
|
+
overloadAccepts(f, argTypes, spread) {
|
|
7373
|
+
const at = (i) => {
|
|
7374
|
+
if (!spread || i < spread.index) return argTypes[i];
|
|
7375
|
+
if (!spread.elements) return argTypes[spread.index];
|
|
7376
|
+
const held = spread.elements[i - spread.index];
|
|
7377
|
+
return held ?? argTypes[i - spread.index + 1 + spread.elements.length - 1];
|
|
7378
|
+
};
|
|
7379
|
+
const written = spread?.elements ? argTypes.length + spread.elements.length - 1 : argTypes.length;
|
|
7380
|
+
if (!f.varargs && (!spread || spread.elements) && written > f.params.length) return false;
|
|
7381
|
+
if (f.varargs && !f.typeParams?.length) {
|
|
7382
|
+
const last = spread && !spread.elements ? Math.max(f.params.length + 1, written) : written;
|
|
7383
|
+
for (let i = f.params.length; i < last; i++) {
|
|
7384
|
+
const arg = at(i);
|
|
7385
|
+
if (arg !== void 0 && !isAssignable(arg, f.varargs)) return false;
|
|
7386
|
+
}
|
|
7387
|
+
}
|
|
6271
7388
|
const params = this.boundParams(f);
|
|
6272
7389
|
return f.params.every((p, i) => {
|
|
6273
|
-
|
|
6274
|
-
|
|
7390
|
+
const arg = at(i);
|
|
7391
|
+
if (arg === void 0) return p.optional === true;
|
|
7392
|
+
return isAssignable(arg, params[i]);
|
|
6275
7393
|
});
|
|
6276
7394
|
}
|
|
6277
7395
|
/** A signature's parameter types as a call site sees them before inference:
|
|
@@ -6301,17 +7419,40 @@ var TypeAnalyzer = class {
|
|
|
6301
7419
|
}
|
|
6302
7420
|
/** Record what each written argument is expected to be — see
|
|
6303
7421
|
* `TypeAnalysis.expectedTypeOf`. */
|
|
6304
|
-
recordExpected(written, fns, selfOf) {
|
|
7422
|
+
recordExpected(written, fns, selfOf, argsOf = () => []) {
|
|
7423
|
+
const paramsOf = /* @__PURE__ */ new Map();
|
|
7424
|
+
for (const f of fns) paramsOf.set(f, this.paramsAsCalled(f, argsOf(f)));
|
|
6305
7425
|
written.forEach((arg, j) => {
|
|
6306
7426
|
const candidates = [];
|
|
6307
7427
|
for (const f of fns) {
|
|
6308
7428
|
const i = j + selfOf(f);
|
|
6309
|
-
const
|
|
7429
|
+
const params = paramsOf.get(f);
|
|
7430
|
+
const param = i < params.length ? params[i] : f.varargs;
|
|
6310
7431
|
if (param) candidates.push(param);
|
|
6311
7432
|
}
|
|
6312
7433
|
if (candidates.length) this.expectedTypeOf.set(arg, union(candidates));
|
|
6313
7434
|
});
|
|
6314
7435
|
}
|
|
7436
|
+
/** The parameters as *this* call makes them read: a type argument the
|
|
7437
|
+
* arguments already written pin down is substituted in, and one nothing
|
|
7438
|
+
* has pinned down yet falls back to its constraint.
|
|
7439
|
+
*
|
|
7440
|
+
* It is what makes the second argument of
|
|
7441
|
+
* `get(page, skill: Extract<Rows, { Page: Page }>["Skills"][number])`
|
|
7442
|
+
* worth completing — with `page` written, `skill` is the skills of that
|
|
7443
|
+
* page, not of every page. */
|
|
7444
|
+
paramsAsCalled(f, argTypes) {
|
|
7445
|
+
const fallback = this.boundParams(f);
|
|
7446
|
+
if (!f.typeParams?.length || !argTypes.length) return fallback;
|
|
7447
|
+
return f.params.map((p, i) => {
|
|
7448
|
+
if (!containsTypeParam(p.type)) return p.type;
|
|
7449
|
+
const subst = this.inferTypeArgs(f, argTypes.map((t, k) => k === i ? void 0 : t));
|
|
7450
|
+
for (const [name, bound] of [...subst]) if (bound.kind === "unknown") subst.delete(name);
|
|
7451
|
+
if (!subst.size) return fallback[i];
|
|
7452
|
+
const applied = this.reduceType(substitute(p.type, subst));
|
|
7453
|
+
return containsTypeParam(applied) ? fallback[i] : applied;
|
|
7454
|
+
});
|
|
7455
|
+
}
|
|
6315
7456
|
/** No signature accepts the call, and the argument count is not the
|
|
6316
7457
|
* problem: say which argument is wrong, the way TypeScript does. */
|
|
6317
7458
|
/** Check what was written against the parameters as this call's own type
|
|
@@ -6322,10 +7463,11 @@ var TypeAnalyzer = class {
|
|
|
6322
7463
|
if (!this.emitDiagnostics || !f.typeParams?.length) return;
|
|
6323
7464
|
const subst = this.inferTypeArgs(f, [...argTypes]);
|
|
6324
7465
|
for (const bound of subst.values()) if (bound.kind === "unknown") return;
|
|
6325
|
-
|
|
7466
|
+
const declaredAt = (i) => i < f.params.length ? f.params[i].type : f.varargs;
|
|
7467
|
+
for (let i = 0; i < Math.max(f.params.length, argTypes.length); i++) {
|
|
6326
7468
|
const arg = argTypes[i];
|
|
6327
|
-
const declared =
|
|
6328
|
-
if (arg === void 0 || !containsTypeParam(declared)) continue;
|
|
7469
|
+
const declared = declaredAt(i);
|
|
7470
|
+
if (arg === void 0 || declared === void 0 || !containsTypeParam(declared)) continue;
|
|
6329
7471
|
const expected = this.reduceType(substitute(declared, subst));
|
|
6330
7472
|
if (containsTypeParam(expected) || expected.kind === "any" || expected.kind === "unknown") continue;
|
|
6331
7473
|
if (isAssignable(arg, expected) || isAssignable(widen(arg), expected)) continue;
|
|
@@ -6346,15 +7488,26 @@ var TypeAnalyzer = class {
|
|
|
6346
7488
|
const args = argsFor(f);
|
|
6347
7489
|
const params = this.boundParams(f);
|
|
6348
7490
|
const self = selfOf(f);
|
|
7491
|
+
const spread = this.spreadOf(written, self);
|
|
6349
7492
|
for (let i = 0; i < f.params.length; i++) {
|
|
6350
|
-
const arg = args[i];
|
|
6351
|
-
if (
|
|
7493
|
+
const arg = spread && i >= spread.index ? this.spreadValue(spread, i) : args[i];
|
|
7494
|
+
if (spread && i > spread.index && !spread.elements) break;
|
|
7495
|
+
if (arg === void 0 || arg.kind === "never" || isAssignable(arg, params[i])) continue;
|
|
6352
7496
|
this.diagnostics.push({
|
|
6353
|
-
node: written[i - self] ?? call,
|
|
7497
|
+
node: (spread && i >= spread.index ? written[spread.index - self] : written[i - self]) ?? call,
|
|
6354
7498
|
message: `Argument of type '${formatType(arg)}' is not assignable to parameter of type '${briefType(params[i])}'`
|
|
6355
7499
|
});
|
|
6356
7500
|
return;
|
|
6357
7501
|
}
|
|
7502
|
+
if (!f.varargs || f.typeParams?.length) return;
|
|
7503
|
+
for (let i = f.params.length; i < args.length; i++) {
|
|
7504
|
+
if (isAssignable(args[i], f.varargs)) continue;
|
|
7505
|
+
this.diagnostics.push({
|
|
7506
|
+
node: written[i - self] ?? call,
|
|
7507
|
+
message: `Argument of type '${formatType(args[i])}' is not assignable to parameter of type '${briefType(f.varargs)}'`
|
|
7508
|
+
});
|
|
7509
|
+
return;
|
|
7510
|
+
}
|
|
6358
7511
|
}
|
|
6359
7512
|
/** A required parameter may not follow an optional one — otherwise the
|
|
6360
7513
|
* optional one could never actually be omitted. Same rule as TypeScript,
|
|
@@ -6363,6 +7516,10 @@ var TypeAnalyzer = class {
|
|
|
6363
7516
|
if (!this.emitDiagnostics) return;
|
|
6364
7517
|
let seenOptional;
|
|
6365
7518
|
for (const p of params) {
|
|
7519
|
+
if (p.rest === true) {
|
|
7520
|
+
this.checkRestType(p, node);
|
|
7521
|
+
continue;
|
|
7522
|
+
}
|
|
6366
7523
|
const isOptional = p.optional === true || p.default !== void 0;
|
|
6367
7524
|
if (isOptional) {
|
|
6368
7525
|
if (seenOptional === void 0) seenOptional = p.name ?? "parameter";
|
|
@@ -6388,8 +7545,10 @@ var TypeAnalyzer = class {
|
|
|
6388
7545
|
* when *no* overload accepts the count, so an overload set still reports
|
|
6389
7546
|
* once, against its first signature. Returns whether the count fits, so
|
|
6390
7547
|
* an argument's type is only complained about when its count is right. */
|
|
6391
|
-
checkArity(node, fns, argCount, selfArgs) {
|
|
7548
|
+
checkArity(node, fns, argCount, selfArgs, spread) {
|
|
6392
7549
|
if (!fns.length) return true;
|
|
7550
|
+
if (spread && !spread.elements) return true;
|
|
7551
|
+
if (spread?.elements) argCount += spread.elements.length - 1;
|
|
6393
7552
|
const fits = fns.some((f) => {
|
|
6394
7553
|
const { min: min2, max: max2 } = this.arityOf(f);
|
|
6395
7554
|
const n = argCount + selfArgs;
|
|
@@ -6432,7 +7591,7 @@ var TypeAnalyzer = class {
|
|
|
6432
7591
|
return type;
|
|
6433
7592
|
};
|
|
6434
7593
|
return record(this.withTypeParams(sig.generics, () => {
|
|
6435
|
-
const params = sig.params.map((p) => ({
|
|
7594
|
+
const params = sig.params.filter((p) => !p.rest).map((p) => ({
|
|
6436
7595
|
name: p.pattern ? void 0 : p.name,
|
|
6437
7596
|
type: this.paramType(p, /* @__PURE__ */ new Map()),
|
|
6438
7597
|
optional: p.optional || p.default !== void 0
|
|
@@ -6440,12 +7599,23 @@ var TypeAnalyzer = class {
|
|
|
6440
7599
|
return fn(
|
|
6441
7600
|
params,
|
|
6442
7601
|
sig.returnType ? this.resolveType(sig.returnType) : sig.predicate ? booleanType : anyType,
|
|
6443
|
-
|
|
7602
|
+
this.varargElement(sig),
|
|
6444
7603
|
names,
|
|
6445
7604
|
this.resolvePredicate(sig.predicate, params)
|
|
6446
7605
|
);
|
|
6447
7606
|
}));
|
|
6448
7607
|
}
|
|
7608
|
+
/** `...rest: T[]` holds every argument from its position on, so its type
|
|
7609
|
+
* is an array of what each one is. */
|
|
7610
|
+
checkRestType(p, node) {
|
|
7611
|
+
if (!this.emitDiagnostics || !p.typeAnnotation) return;
|
|
7612
|
+
const declared = this.resolveType(p.typeAnnotation);
|
|
7613
|
+
if (declared.kind === "array" || declared.kind === "any" || declared.kind === "typeParam") return;
|
|
7614
|
+
this.diagnostics.push({
|
|
7615
|
+
node,
|
|
7616
|
+
message: `A rest parameter holds every argument from its position on, so '${p.name ?? "..."}' is an array: '${formatType(declared)}[]', not '${formatType(declared)}'`
|
|
7617
|
+
});
|
|
7618
|
+
}
|
|
6449
7619
|
/** Turn a parsed `v is T` / `asserts v` annotation into a `TypePredicate`,
|
|
6450
7620
|
* resolving the named parameter to its index. A guard naming a parameter
|
|
6451
7621
|
* the function does not have is dropped rather than mis-narrowing an
|
|
@@ -6463,17 +7633,18 @@ var TypeAnalyzer = class {
|
|
|
6463
7633
|
inferFunctionBody(func, env) {
|
|
6464
7634
|
const names = func.generics.map((g) => g.name);
|
|
6465
7635
|
return this.withTypeParams(func.generics, () => {
|
|
6466
|
-
const params = func.params.
|
|
7636
|
+
const params = func.params.flatMap((p) => {
|
|
6467
7637
|
const type = this.paramType(p, env);
|
|
6468
7638
|
if (!p.pattern) {
|
|
6469
7639
|
const id = this.bindingIdByName(p.name, p);
|
|
6470
7640
|
if (id !== void 0 && !this.bindingType.has(id)) this.bindingType.set(id, type);
|
|
6471
7641
|
}
|
|
6472
|
-
return
|
|
7642
|
+
if (p.rest) return [];
|
|
7643
|
+
return [{
|
|
6473
7644
|
name: p.pattern ? void 0 : p.name,
|
|
6474
7645
|
type,
|
|
6475
7646
|
optional: p.optional || p.default !== void 0
|
|
6476
|
-
};
|
|
7647
|
+
}];
|
|
6477
7648
|
});
|
|
6478
7649
|
const bodyEnv = forkEnv(env);
|
|
6479
7650
|
for (const p of func.params) {
|
|
@@ -6498,7 +7669,7 @@ var TypeAnalyzer = class {
|
|
|
6498
7669
|
return fn(
|
|
6499
7670
|
params,
|
|
6500
7671
|
returns,
|
|
6501
|
-
|
|
7672
|
+
this.varargElement(func),
|
|
6502
7673
|
names,
|
|
6503
7674
|
this.resolvePredicate(func.predicate, params)
|
|
6504
7675
|
);
|
|
@@ -6712,6 +7883,13 @@ var TypeAnalyzer = class {
|
|
|
6712
7883
|
if (src.kind === "array") return [numberType, src.element];
|
|
6713
7884
|
}
|
|
6714
7885
|
}
|
|
7886
|
+
const iterator = this.expand(iterType);
|
|
7887
|
+
if (iterator.kind === "function") {
|
|
7888
|
+
const returns = this.expand(iterator.returns);
|
|
7889
|
+
const parts = returns.kind === "tuple" ? returns.elements.map((m) => this.expand(m)) : [returns];
|
|
7890
|
+
const at = (i) => parts[i] ?? (parts.length === 1 ? parts[0] : unknownType);
|
|
7891
|
+
return [at(0), at(1)];
|
|
7892
|
+
}
|
|
6715
7893
|
const t = this.expand(iterType);
|
|
6716
7894
|
if (t.kind === "array") return varCount >= 2 ? [numberType, t.element] : [t.element, unknownType];
|
|
6717
7895
|
if (t.kind === "object") {
|
|
@@ -6863,7 +8041,21 @@ var TypeAnalyzer = class {
|
|
|
6863
8041
|
expand(t) {
|
|
6864
8042
|
if (t.kind !== "genericRef") return t;
|
|
6865
8043
|
const def = this.aliasDefs.get(t.name);
|
|
6866
|
-
if (!def
|
|
8044
|
+
if (!def) {
|
|
8045
|
+
const imported = this.importedTypes.get(t.name);
|
|
8046
|
+
if (!imported) return t;
|
|
8047
|
+
if (!imported.params.length) return imported.type;
|
|
8048
|
+
const subst = /* @__PURE__ */ new Map();
|
|
8049
|
+
imported.params.forEach((name, i) => subst.set(name, t.typeArguments[i] ?? unknownType));
|
|
8050
|
+
const key2 = `import ${formatType(t)}`;
|
|
8051
|
+
const cached2 = this.expandCache.get(key2);
|
|
8052
|
+
if (cached2) return cached2;
|
|
8053
|
+
this.expandCache.set(key2, t);
|
|
8054
|
+
const applied = substitute(imported.type, subst);
|
|
8055
|
+
this.expandCache.set(key2, applied);
|
|
8056
|
+
return applied;
|
|
8057
|
+
}
|
|
8058
|
+
if (this.resolvingAliases.has(t.name)) return t;
|
|
6867
8059
|
const key = t.typeArguments.length ? formatType(t) : t.name;
|
|
6868
8060
|
const cached = this.expandCache.get(key);
|
|
6869
8061
|
if (cached) return cached;
|
|
@@ -6878,6 +8070,126 @@ var TypeAnalyzer = class {
|
|
|
6878
8070
|
this.resolvingAliases.delete(t.name);
|
|
6879
8071
|
}
|
|
6880
8072
|
}
|
|
8073
|
+
/** `names:filter(f)`, `text:trim()` — the methods arrays and strings have.
|
|
8074
|
+
* They are written in the prelude as `ArrayMethods<T>` and
|
|
8075
|
+
* `StringMethods`, so a file (or a type library) that declares one of
|
|
8076
|
+
* those names again replaces the whole set, and nothing here is a special
|
|
8077
|
+
* case in the analyzer. The build lowers each call to a plain function. */
|
|
8078
|
+
builtInMethod(t, name) {
|
|
8079
|
+
const parts = (t.kind === "union" ? t.types : [t]).map((m) => this.expand(m));
|
|
8080
|
+
const elements = parts.map((m) => m.kind === "array" ? m.element : m.kind === "tuple" ? union(m.elements) : void 0);
|
|
8081
|
+
const element = elements.every((e) => e !== void 0) ? union(elements) : void 0;
|
|
8082
|
+
const isString = (m) => m.kind === "primitive" && m.name === "string" || m.kind === "literal" && m.base === "string" || m.kind === "templateLiteral";
|
|
8083
|
+
const methodTable = element !== void 0 ? "ArrayMethods" : parts.every(isString) ? "StringMethods" : void 0;
|
|
8084
|
+
const def = methodTable === void 0 ? void 0 : this.aliasDefs.get(methodTable);
|
|
8085
|
+
if (!def || def.class) return void 0;
|
|
8086
|
+
const table = this.expand(this.instantiateAlias(def, element !== void 0 ? [element] : []));
|
|
8087
|
+
const layers = table.kind === "intersection" ? table.types.map((m) => this.expand(m)) : [table];
|
|
8088
|
+
for (let i = layers.length - 1; i >= 0; i--) {
|
|
8089
|
+
const part = layers[i];
|
|
8090
|
+
const property = part.kind === "object" ? part.properties.get(name) : void 0;
|
|
8091
|
+
if (property) return property.type;
|
|
8092
|
+
}
|
|
8093
|
+
return void 0;
|
|
8094
|
+
}
|
|
8095
|
+
/** The most a deferred type could turn out to be. A conditional is one of
|
|
8096
|
+
* its branches, and the true branch stands for a member of what was
|
|
8097
|
+
* tested (`T` in `T extends U ? T : never`); an indexed access reads
|
|
8098
|
+
* through the bound of what it indexes. Anything else has no bound worth
|
|
8099
|
+
* giving — `undefined` leaves the comparison as it was. */
|
|
8100
|
+
deferredBound(t, depth = 0) {
|
|
8101
|
+
if (depth > 8) return void 0;
|
|
8102
|
+
switch (t.kind) {
|
|
8103
|
+
case "conditional": {
|
|
8104
|
+
const check = this.reduceType(t.checkType);
|
|
8105
|
+
if (containsTypeParam(check)) return void 0;
|
|
8106
|
+
const subst = /* @__PURE__ */ new Map();
|
|
8107
|
+
if (t.distributeParam) subst.set(t.distributeParam, check);
|
|
8108
|
+
for (const name of t.inferVars ?? []) subst.set(name, unknownType);
|
|
8109
|
+
const branches = [substitute(t.trueType, subst), t.falseType].map((branch) => this.reduceType(branch));
|
|
8110
|
+
if (branches.some((branch) => containsTypeParam(branch))) return void 0;
|
|
8111
|
+
return union(branches);
|
|
8112
|
+
}
|
|
8113
|
+
case "indexedAccess": {
|
|
8114
|
+
const object = this.deferredBound(t.objectType, depth + 1) ?? this.atConstraints(t.objectType);
|
|
8115
|
+
const index = this.atConstraints(this.reduceType(t.indexType));
|
|
8116
|
+
if (!object || !index) return void 0;
|
|
8117
|
+
return this.indexedType(object, index);
|
|
8118
|
+
}
|
|
8119
|
+
default:
|
|
8120
|
+
return void 0;
|
|
8121
|
+
}
|
|
8122
|
+
}
|
|
8123
|
+
/** `t` with every type parameter standing at its constraint: `Map[K]`
|
|
8124
|
+
* where `K extends "a" | "b"` is at most what those two keys hold. A
|
|
8125
|
+
* parameter with no constraint bounds nothing, and says so. */
|
|
8126
|
+
atConstraints(t) {
|
|
8127
|
+
if (!containsTypeParam(t)) return t;
|
|
8128
|
+
const bounds = /* @__PURE__ */ new Map();
|
|
8129
|
+
const seen = /* @__PURE__ */ new WeakSet();
|
|
8130
|
+
let open = false;
|
|
8131
|
+
const walk = (value) => {
|
|
8132
|
+
if (!value || typeof value !== "object" || seen.has(value)) return;
|
|
8133
|
+
seen.add(value);
|
|
8134
|
+
if (value instanceof Map) {
|
|
8135
|
+
value.forEach(walk);
|
|
8136
|
+
return;
|
|
8137
|
+
}
|
|
8138
|
+
const part = value;
|
|
8139
|
+
if (part.kind === "object" && part.class) return;
|
|
8140
|
+
if (part.kind === "typeParam" && typeof part.name === "string") {
|
|
8141
|
+
if (part.constraint && !containsTypeParam(part.constraint)) {
|
|
8142
|
+
bounds.set(part.name, this.reduceType(part.constraint));
|
|
8143
|
+
} else {
|
|
8144
|
+
open = true;
|
|
8145
|
+
}
|
|
8146
|
+
}
|
|
8147
|
+
for (const child of Object.values(value)) walk(child);
|
|
8148
|
+
};
|
|
8149
|
+
walk(t);
|
|
8150
|
+
if (open) return void 0;
|
|
8151
|
+
const applied = this.reduceType(substitute(t, bounds));
|
|
8152
|
+
return containsTypeParam(applied) ? void 0 : applied;
|
|
8153
|
+
}
|
|
8154
|
+
/** What `text["upper"]` reads: a string answers only to its methods, the
|
|
8155
|
+
* way Lua's string metatable does. */
|
|
8156
|
+
stringMember(object, index) {
|
|
8157
|
+
if (index.kind !== "literal" || typeof index.value !== "string") return void 0;
|
|
8158
|
+
const parts = this.stringParts(object);
|
|
8159
|
+
if (!parts) return void 0;
|
|
8160
|
+
const found = parts.map((part) => this.builtInMethod(part, index.value));
|
|
8161
|
+
return found.every((t) => t !== void 0) ? union(found) : void 0;
|
|
8162
|
+
}
|
|
8163
|
+
/** The members of `t` when every one of them is a string — `"a" | "b"` is
|
|
8164
|
+
* as much a string as `string` is. */
|
|
8165
|
+
stringParts(t) {
|
|
8166
|
+
let expanded = this.expand(t);
|
|
8167
|
+
if (expanded.kind === "conditional" || expanded.kind === "indexedAccess") {
|
|
8168
|
+
const bound = this.deferredBound(expanded);
|
|
8169
|
+
if (!bound) return void 0;
|
|
8170
|
+
expanded = this.expand(bound);
|
|
8171
|
+
}
|
|
8172
|
+
const parts = (expanded.kind === "union" ? expanded.types : [expanded]).map((m) => this.expand(m));
|
|
8173
|
+
const isString = (m) => m.kind === "primitive" && m.name === "string" || m.kind === "literal" && m.base === "string" || m.kind === "templateLiteral";
|
|
8174
|
+
return parts.length && parts.every(isString) ? parts : void 0;
|
|
8175
|
+
}
|
|
8176
|
+
/** `text.Sans` or `text["Sans"]`: a string is not a table, and the only
|
|
8177
|
+
* members it has are the ones a type library gave it — so a name that is
|
|
8178
|
+
* not one of them is a mistake worth reporting, rather than the nil Lua
|
|
8179
|
+
* would hand back. */
|
|
8180
|
+
checkStringMember(node, object, key) {
|
|
8181
|
+
if (!this.emitDiagnostics) return;
|
|
8182
|
+
const parts = this.stringParts(object);
|
|
8183
|
+
if (!parts) return;
|
|
8184
|
+
const keys = (key.kind === "union" ? key.types : [key]).map((m) => this.expand(m));
|
|
8185
|
+
if (!keys.length || !keys.every((m) => m.kind === "literal" && typeof m.value === "string")) return;
|
|
8186
|
+
const names = keys.map((m) => String(m.value));
|
|
8187
|
+
if (names.some((name) => parts.some((part) => this.builtInMethod(part, name)))) return;
|
|
8188
|
+
this.diagnostics.push({
|
|
8189
|
+
node,
|
|
8190
|
+
message: names.length === 1 ? `'${names[0]}' does not exist on a string` : `'${briefType(key)}' does not name a member of a string`
|
|
8191
|
+
});
|
|
8192
|
+
}
|
|
6881
8193
|
propertyType(raw, name) {
|
|
6882
8194
|
const t = this.deferredAccess(this.expand(raw));
|
|
6883
8195
|
if (t.kind === "object") {
|
|
@@ -6885,7 +8197,13 @@ var TypeAnalyzer = class {
|
|
|
6885
8197
|
if (p) return p.optional ? optional(p.type) : p.type;
|
|
6886
8198
|
if (t.indexer) return t.indexer.value;
|
|
6887
8199
|
}
|
|
6888
|
-
|
|
8200
|
+
const built = this.builtInMethod(t, name);
|
|
8201
|
+
if (built) return built;
|
|
8202
|
+
if (t.kind === "union") {
|
|
8203
|
+
const built2 = this.builtInMethod(t, name);
|
|
8204
|
+
if (built2) return built2;
|
|
8205
|
+
return union(t.types.map((m) => this.propertyType(m, name)));
|
|
8206
|
+
}
|
|
6889
8207
|
if (t.kind === "intersection") {
|
|
6890
8208
|
const parts = t.types.map((m) => this.propertyType(m, name)).filter((p) => p.kind !== "unknown");
|
|
6891
8209
|
if (parts.length) return intersection(parts);
|
|
@@ -6893,6 +8211,10 @@ var TypeAnalyzer = class {
|
|
|
6893
8211
|
if (t.kind === "typeParam" && t.constraint) return this.propertyType(t.constraint, name);
|
|
6894
8212
|
if (t.kind === "difference") return this.propertyType(t.base, name);
|
|
6895
8213
|
if (t.kind === "any") return anyType;
|
|
8214
|
+
if (t.kind === "conditional" || t.kind === "indexedAccess") {
|
|
8215
|
+
const bound = this.deferredBound(t);
|
|
8216
|
+
if (bound) return this.propertyType(bound, name);
|
|
8217
|
+
}
|
|
6896
8218
|
return unknownType;
|
|
6897
8219
|
}
|
|
6898
8220
|
/** `t[k]`. A statically known string key resolves against the declared
|
|
@@ -6968,6 +8290,20 @@ var TypeAnalyzer = class {
|
|
|
6968
8290
|
// `...` holds what the function declared it takes.
|
|
6969
8291
|
case "VarargExpression":
|
|
6970
8292
|
return this.varargs[this.varargs.length - 1] ?? anyType;
|
|
8293
|
+
// `f(a, ...rest)` — every value the array holds, one after
|
|
8294
|
+
// another. Each of them is an element, so that is what the
|
|
8295
|
+
// parameters it fills are checked against.
|
|
8296
|
+
case "SpreadElement": {
|
|
8297
|
+
const spread = this.infer(expr.argument, env);
|
|
8298
|
+
const element = this.spreadElement(spread);
|
|
8299
|
+
if (element === void 0 && this.emitDiagnostics) {
|
|
8300
|
+
this.diagnostics.push({
|
|
8301
|
+
node: expr,
|
|
8302
|
+
message: `Only an array can be spread, and '${formatType(spread)}' is not one`
|
|
8303
|
+
});
|
|
8304
|
+
}
|
|
8305
|
+
return element ?? anyType;
|
|
8306
|
+
}
|
|
6971
8307
|
// Broken syntax is reported by the parser; nothing more to say.
|
|
6972
8308
|
case "ErrorExpression":
|
|
6973
8309
|
return anyType;
|
|
@@ -7073,6 +8409,7 @@ var TypeAnalyzer = class {
|
|
|
7073
8409
|
const { type: obj, shortCircuits } = this.chainObject(expr, expr.object, env);
|
|
7074
8410
|
const key = this.refKeyOf(expr);
|
|
7075
8411
|
const narrowed = key === void 0 ? void 0 : env.get(key);
|
|
8412
|
+
this.checkStringMember(expr, obj, literal(expr.property.name));
|
|
7076
8413
|
return this.chainResult(expr, narrowed ?? this.propertyType(obj, expr.property.name), shortCircuits);
|
|
7077
8414
|
}
|
|
7078
8415
|
case "IndexExpression": {
|
|
@@ -7080,12 +8417,33 @@ var TypeAnalyzer = class {
|
|
|
7080
8417
|
const idx = this.infer(expr.index, env);
|
|
7081
8418
|
const key = this.refKeyOf(expr);
|
|
7082
8419
|
const narrowed = key === void 0 ? void 0 : env.get(key);
|
|
8420
|
+
this.checkStringMember(expr, obj, this.expand(idx));
|
|
8421
|
+
const member = this.stringMember(obj, this.expand(idx));
|
|
8422
|
+
if (member) return this.chainResult(expr, member, shortCircuits);
|
|
7083
8423
|
return this.chainResult(expr, narrowed ?? this.indexedType(obj, idx), shortCircuits);
|
|
7084
8424
|
}
|
|
7085
8425
|
case "CallExpression": {
|
|
8426
|
+
if (expr.callee.type === "SuperExpression") return this.inferSuperCall(expr, env);
|
|
7086
8427
|
const { type: callee, shortCircuits } = this.chainObject(expr, expr.callee, env);
|
|
7087
8428
|
return this.chainResult(expr, this.inferCall(expr, callee, env), shortCircuits);
|
|
7088
8429
|
}
|
|
8430
|
+
case "NewExpression":
|
|
8431
|
+
return this.inferNew(expr, env);
|
|
8432
|
+
case "ClassExpression":
|
|
8433
|
+
return this.visitClass(expr, env);
|
|
8434
|
+
case "SuperExpression": {
|
|
8435
|
+
const stmt = this.currentClass;
|
|
8436
|
+
if (!stmt?.superclass) {
|
|
8437
|
+
if (this.emitDiagnostics) {
|
|
8438
|
+
this.diagnostics.push({
|
|
8439
|
+
node: expr,
|
|
8440
|
+
message: "'super' is only available inside a class that extends another"
|
|
8441
|
+
});
|
|
8442
|
+
}
|
|
8443
|
+
return anyType;
|
|
8444
|
+
}
|
|
8445
|
+
return this.superType(stmt);
|
|
8446
|
+
}
|
|
7089
8447
|
case "MethodCallExpression": {
|
|
7090
8448
|
const { type: objType, shortCircuits } = this.chainObject(expr, expr.object, env);
|
|
7091
8449
|
return this.chainResult(expr, this.inferMethodCall(expr, objType, env), shortCircuits);
|
|
@@ -7104,16 +8462,56 @@ var TypeAnalyzer = class {
|
|
|
7104
8462
|
}
|
|
7105
8463
|
}
|
|
7106
8464
|
}
|
|
8465
|
+
/** `new Name(args)` is `Name.new(args)` — the same function, and the
|
|
8466
|
+
* same check. Saying so here rather than rewriting the tree keeps the
|
|
8467
|
+
* error messages pointing at what was written. */
|
|
8468
|
+
inferNew(expr, env) {
|
|
8469
|
+
const calleeType = this.infer(expr.callee, env);
|
|
8470
|
+
const constructor = this.propertyType(calleeType, "new");
|
|
8471
|
+
if (!this.overloadsOf(constructor).length && calleeType.kind !== "any") {
|
|
8472
|
+
if (this.emitDiagnostics) {
|
|
8473
|
+
const label = expressionLabel(expr.callee) ?? formatType(calleeType);
|
|
8474
|
+
this.diagnostics.push({ node: expr.callee, message: `'${label}' is not a class; 'new' needs one` });
|
|
8475
|
+
}
|
|
8476
|
+
for (const argument of expr.arguments) this.infer(argument, env);
|
|
8477
|
+
return anyType;
|
|
8478
|
+
}
|
|
8479
|
+
return this.inferCall(expr, constructor, env);
|
|
8480
|
+
}
|
|
8481
|
+
/** `super(...)` — the base constructor, run on the instance being built. */
|
|
8482
|
+
inferSuperCall(expr, env) {
|
|
8483
|
+
const stmt = this.currentClass;
|
|
8484
|
+
const constructor = stmt ? this.baseConstructorType(stmt) : void 0;
|
|
8485
|
+
if (!stmt?.superclass) {
|
|
8486
|
+
if (this.emitDiagnostics) {
|
|
8487
|
+
this.diagnostics.push({
|
|
8488
|
+
node: expr,
|
|
8489
|
+
message: "'super(...)' is only available inside the constructor of a class that extends another"
|
|
8490
|
+
});
|
|
8491
|
+
}
|
|
8492
|
+
for (const argument of expr.arguments) this.infer(argument, env);
|
|
8493
|
+
return nilType;
|
|
8494
|
+
}
|
|
8495
|
+
if (!constructor) {
|
|
8496
|
+
for (const argument of expr.arguments) this.infer(argument, env);
|
|
8497
|
+
return nilType;
|
|
8498
|
+
}
|
|
8499
|
+
const callable = fn(constructor.params.filter((p) => p.name !== "this"), nilType, constructor.varargs);
|
|
8500
|
+
this.inferCall(expr, callable, env);
|
|
8501
|
+
return nilType;
|
|
8502
|
+
}
|
|
7107
8503
|
inferCall(expr, callee, env) {
|
|
8504
|
+
this.checkAmbiguousCall(expr);
|
|
7108
8505
|
const fns = this.overloadsOf(callee);
|
|
7109
8506
|
const explicit = this.explicitTypeArguments(expr, fns);
|
|
7110
8507
|
const expected = this.expectedArguments(expr.arguments, fns, () => 0);
|
|
7111
8508
|
expr.arguments.forEach((a, i) => this.applyContext(a, expected[i]));
|
|
7112
8509
|
const argTypes = expr.arguments.map((a) => this.infer(a, env));
|
|
7113
8510
|
if (fns.length) {
|
|
7114
|
-
this.recordExpected(expr.arguments, fns, () => 0);
|
|
7115
|
-
const
|
|
7116
|
-
const
|
|
8511
|
+
this.recordExpected(expr.arguments, fns, () => 0, () => argTypes);
|
|
8512
|
+
const spread = this.spreadOf(expr.arguments);
|
|
8513
|
+
const arityFits = this.checkArity(expr, fns, argTypes.length, 0, spread);
|
|
8514
|
+
const picked = this.pickOverload(fns, argTypes, void 0, spread);
|
|
7117
8515
|
const distributed = this.distributedReturn(fns, argTypes, picked, (_, args) => args);
|
|
7118
8516
|
if (distributed) return distributed;
|
|
7119
8517
|
if (picked) {
|
|
@@ -7125,6 +8523,21 @@ var TypeAnalyzer = class {
|
|
|
7125
8523
|
}
|
|
7126
8524
|
return callee.kind === "any" ? anyType : unknownType;
|
|
7127
8525
|
}
|
|
8526
|
+
/** A `(` on a line of its own continues the statement above it:
|
|
8527
|
+
*
|
|
8528
|
+
* const value = map[key]
|
|
8529
|
+
* ("text"):upper()
|
|
8530
|
+
*
|
|
8531
|
+
* calls `map[key]`, in luaut as in Lua and in JavaScript. It is almost
|
|
8532
|
+
* never what was meant, and what it does instead is invisible — so say
|
|
8533
|
+
* so, and name the fix. */
|
|
8534
|
+
checkAmbiguousCall(expr) {
|
|
8535
|
+
if (!this.emitDiagnostics || !expr.argumentsOnNewLine) return;
|
|
8536
|
+
this.diagnostics.push({
|
|
8537
|
+
node: expr,
|
|
8538
|
+
message: "This calls the value the line above ends with \u2014 a line break does not end a statement. Write ';' before '(' if a new statement was meant."
|
|
8539
|
+
});
|
|
8540
|
+
}
|
|
7128
8541
|
inferMethodCall(expr, objType, env) {
|
|
7129
8542
|
const fns = this.overloadsOf(this.propertyType(objType, expr.method.name));
|
|
7130
8543
|
const explicit = this.explicitTypeArguments(expr, fns);
|
|
@@ -7134,9 +8547,11 @@ var TypeAnalyzer = class {
|
|
|
7134
8547
|
if (fns.length) {
|
|
7135
8548
|
const withSelf = (f) => this.takesSelf(f) ? [objType, ...argTypes] : argTypes;
|
|
7136
8549
|
const selfOf = (f) => this.takesSelf(f) ? 1 : 0;
|
|
7137
|
-
this.recordExpected(expr.arguments, fns, selfOf);
|
|
7138
|
-
const
|
|
7139
|
-
const
|
|
8550
|
+
this.recordExpected(expr.arguments, fns, selfOf, withSelf);
|
|
8551
|
+
const self0 = this.takesSelf(fns[0]) ? 1 : 0;
|
|
8552
|
+
const spread = this.spreadOf(expr.arguments, self0);
|
|
8553
|
+
const arityFits = this.checkArity(expr, fns, argTypes.length, self0, spread);
|
|
8554
|
+
const picked = this.pickOverload(fns, argTypes, withSelf, spread);
|
|
7140
8555
|
const distributed = this.distributedReturn(
|
|
7141
8556
|
fns,
|
|
7142
8557
|
argTypes,
|
|
@@ -7238,7 +8653,21 @@ var TypeAnalyzer = class {
|
|
|
7238
8653
|
}
|
|
7239
8654
|
}
|
|
7240
8655
|
if (asConst && !hadSpread) return tuple(elems);
|
|
7241
|
-
return arrayOf(elems.length ? union(elems.map((t) =>
|
|
8656
|
+
return arrayOf(elems.length ? union(elems.map((t, i) => {
|
|
8657
|
+
const element = expr.elements[i];
|
|
8658
|
+
return asConst || !element || element.type === "SpreadElement" ? t : this.widenUnlessAsked(t, element);
|
|
8659
|
+
})) : unknownType);
|
|
8660
|
+
}
|
|
8661
|
+
/** A literal written inside a fresh table or array widens — `{ n = 1 }` is
|
|
8662
|
+
* `{ n: number }` — unless the surroundings said a literal belongs there.
|
|
8663
|
+
* `request({ Method: "GET" })` keeps `"GET"` when `Method` is a union of
|
|
8664
|
+
* string literals, exactly as TypeScript's contextual typing does, and
|
|
8665
|
+
* goes on widening to `string` when the parameter only says `string`.
|
|
8666
|
+
* The context was recorded by `applyContext` before the value was
|
|
8667
|
+
* inferred, so this is a lookup rather than a second pass. */
|
|
8668
|
+
widenUnlessAsked(value, at) {
|
|
8669
|
+
const wanted = this.expectedTypeOf.get(at);
|
|
8670
|
+
return wanted === void 0 ? widen(value) : this.keepContextualLiterals(value, wanted);
|
|
7242
8671
|
}
|
|
7243
8672
|
inferObject(expr, env, asConst) {
|
|
7244
8673
|
const entries = [];
|
|
@@ -7246,16 +8675,24 @@ var TypeAnalyzer = class {
|
|
|
7246
8675
|
for (const field of expr.fields) {
|
|
7247
8676
|
if (field.type === "TableFieldNamed") {
|
|
7248
8677
|
const key = field.key.type === "Identifier" ? field.key.name : field.key.value;
|
|
7249
|
-
const v = asConst ? this.inferAsConst(field.value, env) :
|
|
8678
|
+
const v = asConst ? this.inferAsConst(field.value, env) : this.widenUnlessAsked(this.infer(field.value, env), field.value);
|
|
7250
8679
|
entries.push([key, { type: v, optional: false, readonly: asConst }]);
|
|
7251
8680
|
} else if (field.type === "TableFieldShorthand") {
|
|
7252
8681
|
const v = this.infer(field.name, env);
|
|
7253
|
-
entries.push([field.name.name, {
|
|
8682
|
+
entries.push([field.name.name, {
|
|
8683
|
+
type: asConst ? v : this.widenUnlessAsked(v, field.name),
|
|
8684
|
+
optional: false,
|
|
8685
|
+
readonly: asConst
|
|
8686
|
+
}]);
|
|
7254
8687
|
} else if (field.type === "TableFieldComputed") {
|
|
7255
8688
|
const k = this.infer(field.key, env);
|
|
7256
8689
|
const v = this.infer(field.value, env);
|
|
7257
8690
|
if (k.kind === "literal" && typeof k.value === "string") {
|
|
7258
|
-
entries.push([k.value, {
|
|
8691
|
+
entries.push([k.value, {
|
|
8692
|
+
type: asConst ? v : this.widenUnlessAsked(v, field.value),
|
|
8693
|
+
optional: false,
|
|
8694
|
+
readonly: asConst
|
|
8695
|
+
}]);
|
|
7259
8696
|
} else {
|
|
7260
8697
|
indexer = mergeIndexer(indexer, { key: widen(k), value: asConst ? v : widen(v) });
|
|
7261
8698
|
}
|
|
@@ -7763,7 +9200,39 @@ var TypeAnalyzer = class {
|
|
|
7763
9200
|
* out. Every place that has to line arguments up with parameters goes
|
|
7764
9201
|
* through here so the two sides cannot drift apart. */
|
|
7765
9202
|
takesSelf(f) {
|
|
7766
|
-
|
|
9203
|
+
const first = f.params[0]?.name;
|
|
9204
|
+
return first === "self" || first === "this";
|
|
9205
|
+
}
|
|
9206
|
+
/** What one value of a spread array is, or `undefined` when the thing
|
|
9207
|
+
* spread is not a list of values at all. */
|
|
9208
|
+
spreadElement(t) {
|
|
9209
|
+
const spread = this.expand(t);
|
|
9210
|
+
if (spread.kind === "array") return spread.element;
|
|
9211
|
+
if (spread.kind === "tuple") return union(spread.elements);
|
|
9212
|
+
if (spread.kind === "any") return anyType;
|
|
9213
|
+
return void 0;
|
|
9214
|
+
}
|
|
9215
|
+
/** Where a call's arguments stop being one each, and what fills the rest.
|
|
9216
|
+
* From a spread on, every remaining parameter is filled by one of the
|
|
9217
|
+
* array's values — however many that turns out to be, so neither the
|
|
9218
|
+
* count nor the positions after it are known. A *tuple* is the exception:
|
|
9219
|
+
* it holds a known value at each position, and `elements` says which. */
|
|
9220
|
+
spreadOf(args, self = 0) {
|
|
9221
|
+
const index = args.findIndex((a) => a.type === "SpreadElement");
|
|
9222
|
+
if (index < 0) return void 0;
|
|
9223
|
+
const spread = args[index];
|
|
9224
|
+
const held = this.typeOf.get(spread.argument);
|
|
9225
|
+
const expanded = held && this.expand(held);
|
|
9226
|
+
return {
|
|
9227
|
+
index: index + self,
|
|
9228
|
+
element: this.typeOf.get(spread) ?? unknownType,
|
|
9229
|
+
elements: expanded?.kind === "tuple" ? expanded.elements : void 0
|
|
9230
|
+
};
|
|
9231
|
+
}
|
|
9232
|
+
/** One of `spread`'s values, at the position `i` of a call's arguments. */
|
|
9233
|
+
spreadValue(spread, i) {
|
|
9234
|
+
if (!spread.elements) return spread.element;
|
|
9235
|
+
return spread.elements[i - spread.index] ?? neverType;
|
|
7767
9236
|
}
|
|
7768
9237
|
/** A function type as a list of call signatures: a lone function is a
|
|
7769
9238
|
* one-element list, an intersection is the overload set in source order. */
|
|
@@ -7818,6 +9287,8 @@ var TypeAnalyzer = class {
|
|
|
7818
9287
|
type = this.resolveType(statement.valueType);
|
|
7819
9288
|
} else if (statement.type === "FunctionDeclaration") {
|
|
7820
9289
|
type = statement.signatures?.length ? intersection(statement.signatures.map((sig) => this.signatureToFnType(sig))) : this.inferFunctionBody(statement.func, /* @__PURE__ */ new Map());
|
|
9290
|
+
} else if (statement.type === "ClassDeclaration") {
|
|
9291
|
+
type = this.classValueType(statement);
|
|
7821
9292
|
} else if (statement.type === "VariableDeclaration") {
|
|
7822
9293
|
const target = statement.names[index];
|
|
7823
9294
|
if (target.type === "IdentifierPattern" && target.typeAnnotation) {
|
|
@@ -7854,6 +9325,10 @@ var TypeAnalyzer = class {
|
|
|
7854
9325
|
return;
|
|
7855
9326
|
}
|
|
7856
9327
|
const record = node;
|
|
9328
|
+
if (record.type === "ClassDeclaration" && record.name) {
|
|
9329
|
+
const id = this.bindingIdByName(record.name.name, record.name);
|
|
9330
|
+
if (id !== void 0) out.set(id, { statement: node, index: 0 });
|
|
9331
|
+
}
|
|
7857
9332
|
if (record.type === "FunctionDeclaration" && record.name) {
|
|
7858
9333
|
const id = this.bindingIdByName(record.name.name, record.name);
|
|
7859
9334
|
if (id !== void 0) out.set(id, { statement: node, index: 0 });
|
|
@@ -8080,6 +9555,7 @@ function offsetPosition(source, offset) {
|
|
|
8080
9555
|
import { dirname as dirname2, join as join2, resolve as resolve2 } from "path";
|
|
8081
9556
|
function resolveTypeLibraries(config, host = nodeHost) {
|
|
8082
9557
|
const files = [];
|
|
9558
|
+
const lowerings = [];
|
|
8083
9559
|
const problems = [];
|
|
8084
9560
|
const loaded = /* @__PURE__ */ new Set();
|
|
8085
9561
|
const addFile = (file) => {
|
|
@@ -8097,6 +9573,8 @@ function resolveTypeLibraries(config, host = nodeHost) {
|
|
|
8097
9573
|
if (found) addPackage(found.directory, found.file, visiting);
|
|
8098
9574
|
}
|
|
8099
9575
|
addFile(entryFile);
|
|
9576
|
+
const lowering = loweringModule(directory, host, problems, config);
|
|
9577
|
+
if (lowering) lowerings.push(lowering);
|
|
8100
9578
|
};
|
|
8101
9579
|
for (const entry of config.types) {
|
|
8102
9580
|
const relative = entry.startsWith("./") || entry.startsWith("../") || entry.startsWith("/") || /^[A-Za-z]:[\\/]/.test(entry);
|
|
@@ -8123,7 +9601,19 @@ function resolveTypeLibraries(config, host = nodeHost) {
|
|
|
8123
9601
|
});
|
|
8124
9602
|
}
|
|
8125
9603
|
}
|
|
8126
|
-
return { files, problems };
|
|
9604
|
+
return { files, lowerings, problems };
|
|
9605
|
+
}
|
|
9606
|
+
function loweringModule(directory, host, problems, config) {
|
|
9607
|
+
const manifest = readJson(join2(directory, "package.json"), host);
|
|
9608
|
+
const declared = manifest?.luaut?.lowering;
|
|
9609
|
+
if (typeof declared !== "string") return void 0;
|
|
9610
|
+
const from = typeof manifest?.name === "string" ? manifest.name : directory;
|
|
9611
|
+
const file = resolve2(directory, declared);
|
|
9612
|
+
if (host.readFile(file) === void 0) {
|
|
9613
|
+
problems.push({ file: config.path, message: `'${from}' names a lowering module '${declared}', which is not there` });
|
|
9614
|
+
return void 0;
|
|
9615
|
+
}
|
|
9616
|
+
return { file, from };
|
|
8127
9617
|
}
|
|
8128
9618
|
var ENTRY_FILE = "index.d.luaut";
|
|
8129
9619
|
function packageEntry(directory, host) {
|
|
@@ -8358,6 +9848,7 @@ export {
|
|
|
8358
9848
|
resolveModulePath,
|
|
8359
9849
|
resolveTypeLibraries,
|
|
8360
9850
|
setAliasExpander,
|
|
9851
|
+
setDeferredBound,
|
|
8361
9852
|
sourceMapTypes,
|
|
8362
9853
|
stringType,
|
|
8363
9854
|
stripJsonComments,
|