luaut-parser 4.0.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 +296 -20
- package/dist/index.cjs +1535 -121
- package/dist/index.d.cts +143 -20
- package/dist/index.d.ts +143 -20
- package/dist/index.js +1534 -121
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -80,6 +80,7 @@ __export(index_exports, {
|
|
|
80
80
|
resolveModulePath: () => resolveModulePath,
|
|
81
81
|
resolveTypeLibraries: () => resolveTypeLibraries,
|
|
82
82
|
setAliasExpander: () => setAliasExpander,
|
|
83
|
+
setDeferredBound: () => setDeferredBound,
|
|
83
84
|
sourceMapTypes: () => sourceMapTypes,
|
|
84
85
|
stringType: () => stringType,
|
|
85
86
|
stripJsonComments: () => stripJsonComments,
|
|
@@ -168,6 +169,7 @@ var Punctuators = [
|
|
|
168
169
|
",",
|
|
169
170
|
".",
|
|
170
171
|
"?",
|
|
172
|
+
"=>",
|
|
171
173
|
"->",
|
|
172
174
|
"&",
|
|
173
175
|
"|",
|
|
@@ -692,6 +694,13 @@ function applyDirectives(directives, diagnostics, lineOf) {
|
|
|
692
694
|
var UNUSED_EXPECT_ERROR = "Unused '@luaut-expect-error' directive";
|
|
693
695
|
|
|
694
696
|
// src/ast/builders.ts
|
|
697
|
+
function bindThis(func, at) {
|
|
698
|
+
bindThisParam(func.params, at);
|
|
699
|
+
func.isMethod = true;
|
|
700
|
+
}
|
|
701
|
+
function bindThisParam(params, at) {
|
|
702
|
+
params.unshift({ type: "FunctionParameter", name: "this", ...spanFrom(at, at) });
|
|
703
|
+
}
|
|
695
704
|
var ParseError = class extends Error {
|
|
696
705
|
constructor(message, line, column) {
|
|
697
706
|
super(`${message} (${line}:${column})`);
|
|
@@ -785,10 +794,13 @@ var Parser = class {
|
|
|
785
794
|
indentation;
|
|
786
795
|
/** Populated in recovery mode. */
|
|
787
796
|
errors = [];
|
|
788
|
-
/** Recovery found a block without its `
|
|
797
|
+
/** Recovery found a block without its `}`. */
|
|
789
798
|
missingEnd = false;
|
|
790
799
|
/** The column of the first token on each line, for `indentation`. */
|
|
791
800
|
lineIndent;
|
|
801
|
+
/** Inside a class body, where `super` means the base class. Outside
|
|
802
|
+
* one it is an ordinary name, so existing code using it still reads. */
|
|
803
|
+
classDepth = 0;
|
|
792
804
|
constructor(tokens, options = {}) {
|
|
793
805
|
this.tokens = tokens;
|
|
794
806
|
this.recover = options.recover ?? false;
|
|
@@ -927,8 +939,12 @@ var Parser = class {
|
|
|
927
939
|
expressionOr(stop) {
|
|
928
940
|
return this.attempt(() => this.parseExpression(), stop, (start, from) => this.errorExpression(start, from));
|
|
929
941
|
}
|
|
942
|
+
/** A comma-separated list of values: a `return`'s, a declaration's, an
|
|
943
|
+
* assignment's. `...xs` spreads an array into it, as in a call's
|
|
944
|
+
* arguments; bare `...` is the vararg pack, as it always was. */
|
|
930
945
|
expressionListOr(stop) {
|
|
931
|
-
const
|
|
946
|
+
const until = () => stop() || this.checkPunctuator(",");
|
|
947
|
+
const item = () => this.checkOperator("...") && this.startsSpread() ? this.parseSpreadArgument(until) : this.expressionOr(until);
|
|
932
948
|
const list = [item()];
|
|
933
949
|
while (this.matchPunctuator(",")) list.push(item());
|
|
934
950
|
return list;
|
|
@@ -1096,7 +1112,62 @@ var Parser = class {
|
|
|
1096
1112
|
// Block / Statement
|
|
1097
1113
|
// ============================================================
|
|
1098
1114
|
isBlockEnd() {
|
|
1099
|
-
return this.isAtEnd() || this.checkKeyword("end") || this.checkKeyword("else") || this.checkKeyword("elseif") || this.checkKeyword("until");
|
|
1115
|
+
return this.isAtEnd() || this.checkPunctuator("}") || this.checkKeyword("end") || this.checkKeyword("else") || this.checkKeyword("elseif") || this.checkKeyword("until");
|
|
1116
|
+
}
|
|
1117
|
+
// ============================================================
|
|
1118
|
+
// Bodies
|
|
1119
|
+
// ------------------------------------------------------------
|
|
1120
|
+
// luaut writes a block in braces — `if (ready) { ... }`, `function f() {
|
|
1121
|
+
// ... }`. The `end` spellings Lua uses are still read, so a file written
|
|
1122
|
+
// in them keeps working while it is being moved over.
|
|
1123
|
+
//
|
|
1124
|
+
// A condition is in parentheses because `f {}` is a call: without them
|
|
1125
|
+
// `if ready { ... }` would be a call of `ready` and then a block. With
|
|
1126
|
+
// them the form is decided by looking past the closing parenthesis, which
|
|
1127
|
+
// is why a parenthesized condition in the older spelling still reads.
|
|
1128
|
+
// ============================================================
|
|
1129
|
+
/** `{ ... }` — a block in braces. */
|
|
1130
|
+
parseBraceBlock() {
|
|
1131
|
+
const brace = this.advance();
|
|
1132
|
+
const body = this.parseBlock(brace);
|
|
1133
|
+
if (this.matchPunctuator("}")) return body;
|
|
1134
|
+
if (!this.recover) this.error(`Expected '}' to close the block opened on line ${brace.line.start}`);
|
|
1135
|
+
this.softError(`Expected '}' to close the block opened on line ${brace.line.start}`);
|
|
1136
|
+
this.missingEnd = true;
|
|
1137
|
+
return body;
|
|
1138
|
+
}
|
|
1139
|
+
/** The `{ ... }` a construct's body is written in. Half-written — the
|
|
1140
|
+
* `{` not typed yet — it is empty and says so, rather than throwing the
|
|
1141
|
+
* whole construct away: what has been written is what an editor answers
|
|
1142
|
+
* from. */
|
|
1143
|
+
parseBracedBody(what) {
|
|
1144
|
+
if (this.checkPunctuator("{")) return this.parseBraceBlock();
|
|
1145
|
+
const at = this.current();
|
|
1146
|
+
if (!this.recover) this.error(`Expected '{' to open the body of '${what}'`);
|
|
1147
|
+
this.softError(`Expected '{' to open the body of '${what}'`);
|
|
1148
|
+
return { type: "Block", statements: [], ...spanFrom(at, at) };
|
|
1149
|
+
}
|
|
1150
|
+
/** Does a `{` follow the parenthesized group starting here? That is what
|
|
1151
|
+
* tells `if (ready) { ... }` from `if (ready) then ... end`. */
|
|
1152
|
+
braceFollowsGroup() {
|
|
1153
|
+
if (!this.checkPunctuator("(")) return false;
|
|
1154
|
+
const closers = { "(": ")", "[": "]", "{": "}" };
|
|
1155
|
+
const stack = [];
|
|
1156
|
+
for (let i = 0; ; i++) {
|
|
1157
|
+
const token = this.peek(i);
|
|
1158
|
+
if (token.type === "EOF") return false;
|
|
1159
|
+
if (token.type === "Punctuator") {
|
|
1160
|
+
const value = String(token.value);
|
|
1161
|
+
if (closers[value]) stack.push(closers[value]);
|
|
1162
|
+
else if (value === stack[stack.length - 1]) {
|
|
1163
|
+
stack.pop();
|
|
1164
|
+
if (!stack.length) {
|
|
1165
|
+
const next = this.peek(i + 1);
|
|
1166
|
+
return next.type === "Punctuator" && next.value === "{";
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1100
1171
|
}
|
|
1101
1172
|
/** `opener` is the token that began the block (`if`, `function`, ...), for
|
|
1102
1173
|
* indentation recovery. */
|
|
@@ -1208,6 +1279,9 @@ var Parser = class {
|
|
|
1208
1279
|
if (t.type === "Identifier" && t.value === "type" && this.peek(1).type === "Identifier") {
|
|
1209
1280
|
return this.parseTypeAliasStatement();
|
|
1210
1281
|
}
|
|
1282
|
+
if (t.type === "Identifier" && t.value === "class" && this.peek(1).type === "Identifier") {
|
|
1283
|
+
return this.parseClassDeclaration();
|
|
1284
|
+
}
|
|
1211
1285
|
if (t.type === "Identifier" && t.value === "declare") {
|
|
1212
1286
|
const p1 = this.peek(1);
|
|
1213
1287
|
if (p1.type === "Identifier" && p1.value === "class" && this.peek(2).type === "Identifier") {
|
|
@@ -1233,6 +1307,7 @@ var Parser = class {
|
|
|
1233
1307
|
name: p.name || void 0,
|
|
1234
1308
|
id: p.name ? nameIdentifier(p.name, p) : void 0,
|
|
1235
1309
|
optional: p.optional,
|
|
1310
|
+
rest: p.rest,
|
|
1236
1311
|
typeAnnotation: p.typeAnnotation ?? { type: "TypeReference", base: "any", typeArguments: [], line: p.line, column: p.column },
|
|
1237
1312
|
line: p.line,
|
|
1238
1313
|
column: p.column
|
|
@@ -1381,7 +1456,7 @@ var Parser = class {
|
|
|
1381
1456
|
this.advance();
|
|
1382
1457
|
if (this.checkIdentifierValue("default")) {
|
|
1383
1458
|
this.advance();
|
|
1384
|
-
const declaration = this.parseExpression(0);
|
|
1459
|
+
const declaration = this.checkIdentifierValue("class") && this.peek(1).type === "Identifier" && !this.punctuatorAt(2, ":") && !this.operatorAt(2, "=") && !this.punctuatorAt(2, "(") ? this.parseClassDeclaration() : this.parseExpression(0);
|
|
1385
1460
|
return { type: "ExportDefaultStatement", declaration, ...spanFrom(start, this.previous()) };
|
|
1386
1461
|
}
|
|
1387
1462
|
if (this.checkIdentifierValue("type") && this.peek(1).type === "Identifier") {
|
|
@@ -1392,6 +1467,10 @@ var Parser = class {
|
|
|
1392
1467
|
const declaration = this.parseVariableDeclaration();
|
|
1393
1468
|
return { type: "ExportStatement", declaration, ...spanFrom(start, this.previous()) };
|
|
1394
1469
|
}
|
|
1470
|
+
if (this.checkIdentifierValue("class") && this.peek(1).type === "Identifier") {
|
|
1471
|
+
const declaration = this.parseClassDeclaration();
|
|
1472
|
+
return { type: "ExportStatement", declaration, ...spanFrom(start, this.previous()) };
|
|
1473
|
+
}
|
|
1395
1474
|
if (this.checkKeyword("function")) {
|
|
1396
1475
|
const declaration = this.parseFunctionStatement(true);
|
|
1397
1476
|
if (declaration.type !== "FunctionDeclaration") this.error("An exported function needs a plain name: 'export function name()'");
|
|
@@ -1420,7 +1499,7 @@ var Parser = class {
|
|
|
1420
1499
|
const source = this.parseModuleSource();
|
|
1421
1500
|
return { type: "ExportAllStatement", source, ...spanFrom(start, this.previous()) };
|
|
1422
1501
|
}
|
|
1423
|
-
this.error("Expected 'const', 'let', 'function', 'type', 'default', '{' or '*' after 'export'");
|
|
1502
|
+
this.error("Expected 'const', 'let', 'function', 'class', 'type', 'default', '{' or '*' after 'export'");
|
|
1424
1503
|
}
|
|
1425
1504
|
// `const x = ...` / `let x, y = ...`.
|
|
1426
1505
|
// luaut has no `local` — `const` bindings are immutable, `let` mutable.
|
|
@@ -1448,63 +1527,63 @@ var Parser = class {
|
|
|
1448
1527
|
parseIfStatement() {
|
|
1449
1528
|
const start = this.current();
|
|
1450
1529
|
this.expectKeyword("if");
|
|
1530
|
+
return this.parseBracedIf(start);
|
|
1531
|
+
}
|
|
1532
|
+
parseBracedIf(start) {
|
|
1451
1533
|
const clauses = [];
|
|
1452
|
-
const
|
|
1453
|
-
const cond = this.expressionOr(untilThen);
|
|
1454
|
-
this.expectKeywordSoft("then");
|
|
1455
|
-
const body = this.parseBlock(start);
|
|
1456
|
-
clauses.push({ type: "IfClause", condition: cond, body, ...spanFrom(cond, this.previous()) });
|
|
1457
|
-
while (this.checkKeyword("elseif")) {
|
|
1534
|
+
const clause = () => {
|
|
1458
1535
|
const clauseStart = this.current();
|
|
1459
|
-
this.
|
|
1460
|
-
const
|
|
1461
|
-
this.
|
|
1462
|
-
const
|
|
1463
|
-
clauses.push({ type: "IfClause", condition
|
|
1464
|
-
}
|
|
1536
|
+
this.expectPunctuator("(");
|
|
1537
|
+
const condition = this.expressionOr(() => this.checkPunctuator(")"));
|
|
1538
|
+
this.expectCloser(")");
|
|
1539
|
+
const body = this.parseBracedBody("if");
|
|
1540
|
+
clauses.push({ type: "IfClause", condition, body, ...spanFrom(clauseStart, this.previous()) });
|
|
1541
|
+
};
|
|
1542
|
+
clause();
|
|
1465
1543
|
let alternate;
|
|
1466
|
-
|
|
1467
|
-
|
|
1544
|
+
while (this.checkKeyword("elseif") || this.checkKeyword("else")) {
|
|
1545
|
+
const isElse = this.checkKeyword("else");
|
|
1546
|
+
this.advance();
|
|
1547
|
+
if (!isElse) {
|
|
1548
|
+
clause();
|
|
1549
|
+
continue;
|
|
1550
|
+
}
|
|
1551
|
+
alternate = this.parseBracedBody("else");
|
|
1552
|
+
break;
|
|
1468
1553
|
}
|
|
1469
|
-
this.expectEnd(start);
|
|
1470
1554
|
return { type: "IfStatement", clauses, alternate, ...spanFrom(start, this.previous()) };
|
|
1471
1555
|
}
|
|
1472
1556
|
parseWhileStatement() {
|
|
1473
1557
|
const start = this.current();
|
|
1474
1558
|
this.expectKeyword("while");
|
|
1475
|
-
|
|
1476
|
-
this.
|
|
1477
|
-
|
|
1478
|
-
this.
|
|
1559
|
+
this.expectPunctuator("(");
|
|
1560
|
+
const condition = this.expressionOr(() => this.checkPunctuator(")"));
|
|
1561
|
+
this.expectCloser(")");
|
|
1562
|
+
const body = this.parseBracedBody("while");
|
|
1479
1563
|
return { type: "WhileStatement", condition, body, ...spanFrom(start, this.previous()) };
|
|
1480
1564
|
}
|
|
1481
1565
|
parseRepeatStatement() {
|
|
1482
1566
|
const start = this.current();
|
|
1483
1567
|
this.expectKeyword("repeat");
|
|
1484
|
-
const body = this.
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
} else {
|
|
1490
|
-
this.softError(`Expected 'until' to close 'repeat' on line ${start.line.start}`);
|
|
1491
|
-
this.missingEnd = true;
|
|
1492
|
-
condition = this.errorExpression(this.current(), this.cursor);
|
|
1493
|
-
}
|
|
1568
|
+
const body = this.parseBracedBody("repeat");
|
|
1569
|
+
this.expectKeyword("until");
|
|
1570
|
+
this.expectPunctuator("(");
|
|
1571
|
+
const condition = this.expressionOr(() => this.checkPunctuator(")"));
|
|
1572
|
+
this.expectCloser(")");
|
|
1494
1573
|
return { type: "RepeatStatement", body, condition, ...spanFrom(start, this.previous()) };
|
|
1495
1574
|
}
|
|
1496
1575
|
parseDoStatement() {
|
|
1497
1576
|
const start = this.current();
|
|
1498
1577
|
this.expectKeyword("do");
|
|
1499
|
-
const body = this.
|
|
1500
|
-
this.expectEnd(start);
|
|
1578
|
+
const body = this.parseBracedBody("do");
|
|
1501
1579
|
return { type: "DoStatement", body, ...spanFrom(start, this.previous()) };
|
|
1502
1580
|
}
|
|
1503
1581
|
parseForStatement() {
|
|
1504
1582
|
const start = this.current();
|
|
1505
1583
|
this.expectKeyword("for");
|
|
1584
|
+
this.expectPunctuator("(");
|
|
1506
1585
|
const first = this.parseBindingTarget(true);
|
|
1507
|
-
const untilDo = () => this.
|
|
1586
|
+
const untilDo = () => this.checkPunctuator(")");
|
|
1508
1587
|
if (first.type === "IdentifierPattern" && this.matchOperator("=")) {
|
|
1509
1588
|
const from = this.expressionOr(() => untilDo() || this.checkPunctuator(","));
|
|
1510
1589
|
this.expectPunctuator(",");
|
|
@@ -1513,9 +1592,7 @@ var Parser = class {
|
|
|
1513
1592
|
if (this.matchPunctuator(",")) {
|
|
1514
1593
|
step = this.expressionOr(untilDo);
|
|
1515
1594
|
}
|
|
1516
|
-
this.
|
|
1517
|
-
const body2 = this.parseBlock(start);
|
|
1518
|
-
this.expectEnd(start);
|
|
1595
|
+
const body2 = this.parseForBody();
|
|
1519
1596
|
return {
|
|
1520
1597
|
type: "NumericForStatement",
|
|
1521
1598
|
variable: this.identifierPatternToTypedIdentifier(first),
|
|
@@ -1532,9 +1609,7 @@ var Parser = class {
|
|
|
1532
1609
|
}
|
|
1533
1610
|
this.expectKeyword("in");
|
|
1534
1611
|
const iterators = this.expressionListOr(untilDo);
|
|
1535
|
-
this.
|
|
1536
|
-
const body = this.parseBlock(start);
|
|
1537
|
-
this.expectEnd(start);
|
|
1612
|
+
const body = this.parseForBody();
|
|
1538
1613
|
return {
|
|
1539
1614
|
type: "GenericForStatement",
|
|
1540
1615
|
variables,
|
|
@@ -1543,6 +1618,10 @@ var Parser = class {
|
|
|
1543
1618
|
...spanFrom(start, this.previous())
|
|
1544
1619
|
};
|
|
1545
1620
|
}
|
|
1621
|
+
parseForBody() {
|
|
1622
|
+
this.expectCloser(")");
|
|
1623
|
+
return this.parseBracedBody("for");
|
|
1624
|
+
}
|
|
1546
1625
|
/** `function name() end` declares `name`; `function a.b() end` and
|
|
1547
1626
|
* `function T:m() end` define a member. */
|
|
1548
1627
|
/** `exported` — the `export` before this `function` has been consumed, so
|
|
@@ -1609,6 +1688,183 @@ var Parser = class {
|
|
|
1609
1688
|
if (!this.recover) throw error;
|
|
1610
1689
|
this.record(error);
|
|
1611
1690
|
}
|
|
1691
|
+
/** `class Name extends Base <members> end`.
|
|
1692
|
+
*
|
|
1693
|
+
* The body is a block like every other in luaut, closed by `end` — not a
|
|
1694
|
+
* brace-delimited list. Members are written the way the same thing is
|
|
1695
|
+
* written outside a class: a field like a field (`x: number`), a method
|
|
1696
|
+
* like a function (`function m() ... end`). */
|
|
1697
|
+
parseClassDeclaration() {
|
|
1698
|
+
const start = this.current();
|
|
1699
|
+
this.advance();
|
|
1700
|
+
const name = this.parseIdentifier();
|
|
1701
|
+
const typeParams = this.checkOperator("<") ? this.parseGenericTypeParameterList() : [];
|
|
1702
|
+
const { superclass, superArguments } = this.parseExtends();
|
|
1703
|
+
const members = this.parseClassBody(start);
|
|
1704
|
+
return {
|
|
1705
|
+
type: "ClassDeclaration",
|
|
1706
|
+
name,
|
|
1707
|
+
typeParams,
|
|
1708
|
+
superclass,
|
|
1709
|
+
superArguments,
|
|
1710
|
+
members,
|
|
1711
|
+
...spanFrom(start, this.previous())
|
|
1712
|
+
};
|
|
1713
|
+
}
|
|
1714
|
+
/** `class ... end` as a value. It may be named — the name is for the class
|
|
1715
|
+
* itself, not for the scope around it — and takes no type parameters,
|
|
1716
|
+
* since nothing could write the arguments. */
|
|
1717
|
+
parseClassExpression() {
|
|
1718
|
+
const start = this.current();
|
|
1719
|
+
this.advance();
|
|
1720
|
+
const name = this.checkType("Identifier") && !this.checkIdentifierValue("extends") && !this.punctuatorAt(1, ":") && !this.operatorAt(1, "=") && !this.punctuatorAt(1, "(") ? this.parseIdentifier() : void 0;
|
|
1721
|
+
if (this.checkOperator("<")) {
|
|
1722
|
+
this.error("A class written as a value takes no type parameters: nothing could write the arguments");
|
|
1723
|
+
}
|
|
1724
|
+
const { superclass, superArguments } = this.parseExtends();
|
|
1725
|
+
const members = this.parseClassBody(start);
|
|
1726
|
+
return { type: "ClassExpression", name, superclass, superArguments, members, ...spanFrom(start, this.previous()) };
|
|
1727
|
+
}
|
|
1728
|
+
/** `extends Base` / `extends Box<number>`. */
|
|
1729
|
+
parseExtends() {
|
|
1730
|
+
if (!this.checkIdentifierValue("extends")) return {};
|
|
1731
|
+
this.advance();
|
|
1732
|
+
const superclass = this.parseIdentifier();
|
|
1733
|
+
let superArguments;
|
|
1734
|
+
if (this.checkOperator("<")) {
|
|
1735
|
+
const written = this.tryTypeArguments();
|
|
1736
|
+
if (written) superArguments = written;
|
|
1737
|
+
}
|
|
1738
|
+
return { superclass, superArguments };
|
|
1739
|
+
}
|
|
1740
|
+
parseClassBody(start) {
|
|
1741
|
+
void start;
|
|
1742
|
+
if (!this.checkPunctuator("{")) this.error("Expected '{' to open the class body");
|
|
1743
|
+
this.advance();
|
|
1744
|
+
const members = [];
|
|
1745
|
+
this.classDepth++;
|
|
1746
|
+
try {
|
|
1747
|
+
while (!this.checkPunctuator("}") && !this.isAtEnd()) {
|
|
1748
|
+
if (this.matchPunctuator(",") || this.matchPunctuator(";")) continue;
|
|
1749
|
+
const member = this.parseClassMember();
|
|
1750
|
+
if (member) members.push(member);
|
|
1751
|
+
}
|
|
1752
|
+
} finally {
|
|
1753
|
+
this.classDepth--;
|
|
1754
|
+
}
|
|
1755
|
+
this.expectCloser("}");
|
|
1756
|
+
return members;
|
|
1757
|
+
}
|
|
1758
|
+
/** `<A, B>` in a type position that is not a call: the arguments a class
|
|
1759
|
+
* extends its base with. */
|
|
1760
|
+
tryTypeArguments() {
|
|
1761
|
+
const saved = this.cursor;
|
|
1762
|
+
try {
|
|
1763
|
+
this.expectOperator("<");
|
|
1764
|
+
const args = [this.parseType()];
|
|
1765
|
+
while (this.matchPunctuator(",")) args.push(this.parseType());
|
|
1766
|
+
this.expectOperator(">");
|
|
1767
|
+
return args;
|
|
1768
|
+
} catch (error) {
|
|
1769
|
+
if (error instanceof ParseError || error instanceof ParseRecover) {
|
|
1770
|
+
this.cursor = saved;
|
|
1771
|
+
return void 0;
|
|
1772
|
+
}
|
|
1773
|
+
throw error;
|
|
1774
|
+
}
|
|
1775
|
+
}
|
|
1776
|
+
parseClassMember() {
|
|
1777
|
+
const start = this.current();
|
|
1778
|
+
const isStatic = this.checkIdentifierValue("static") && !this.punctuatorAt(1, ":") && !this.operatorAt(1, "=");
|
|
1779
|
+
if (isStatic) this.advance();
|
|
1780
|
+
if (this.checkKeyword("function")) {
|
|
1781
|
+
this.advance();
|
|
1782
|
+
const memberName = this.parseIdentifier();
|
|
1783
|
+
const signatures = [];
|
|
1784
|
+
let written = memberName;
|
|
1785
|
+
while (true) {
|
|
1786
|
+
const head = this.parseFunctionHead();
|
|
1787
|
+
if (this.isClassOverloadContinuation(memberName.name, isStatic)) {
|
|
1788
|
+
if (!isStatic) bindThisParam(head.params, start);
|
|
1789
|
+
signatures.push({ ...this.headToSignature(head), name: written });
|
|
1790
|
+
if (isStatic) this.advance();
|
|
1791
|
+
this.expectKeyword("function");
|
|
1792
|
+
written = this.parseIdentifier();
|
|
1793
|
+
continue;
|
|
1794
|
+
}
|
|
1795
|
+
const func = this.headToBody(head, start);
|
|
1796
|
+
if (!isStatic) bindThis(func, start);
|
|
1797
|
+
return {
|
|
1798
|
+
type: "ClassMethod",
|
|
1799
|
+
name: memberName,
|
|
1800
|
+
isStatic,
|
|
1801
|
+
func,
|
|
1802
|
+
signatures: signatures.length ? signatures : void 0,
|
|
1803
|
+
...spanFrom(start, this.previous())
|
|
1804
|
+
};
|
|
1805
|
+
}
|
|
1806
|
+
}
|
|
1807
|
+
if (!isStatic && this.checkIdentifierValue("constructor") && this.punctuatorAt(1, "(")) {
|
|
1808
|
+
this.advance();
|
|
1809
|
+
const head = this.parseFunctionHead();
|
|
1810
|
+
if (head.returnType) this.problem("A constructor has no return type; it always builds the instance");
|
|
1811
|
+
const func = this.headToBody(head, start);
|
|
1812
|
+
bindThis(func, start);
|
|
1813
|
+
return { type: "ClassConstructor", func, ...spanFrom(start, this.previous()) };
|
|
1814
|
+
}
|
|
1815
|
+
if ((this.checkIdentifierValue("get") || this.checkIdentifierValue("set")) && this.peek(1).type === "Identifier" && this.punctuatorAt(2, "(")) {
|
|
1816
|
+
const kind = this.advance().value;
|
|
1817
|
+
const memberName = this.parseIdentifier();
|
|
1818
|
+
const head = this.parseFunctionHead();
|
|
1819
|
+
const func = this.headToBody(head, start);
|
|
1820
|
+
if (!isStatic) bindThis(func, start);
|
|
1821
|
+
const written = func.params.length - (isStatic ? 0 : 1);
|
|
1822
|
+
if (kind === "get" && written > 0) {
|
|
1823
|
+
this.problem("A getter takes no parameters");
|
|
1824
|
+
}
|
|
1825
|
+
if (kind === "set" && written !== 1) {
|
|
1826
|
+
this.problem("A setter takes exactly one parameter: the value being assigned");
|
|
1827
|
+
}
|
|
1828
|
+
return { type: "ClassAccessor", kind, name: memberName, isStatic, func, ...spanFrom(start, this.previous()) };
|
|
1829
|
+
}
|
|
1830
|
+
if (this.checkType("Identifier")) {
|
|
1831
|
+
const memberName = this.parseIdentifier();
|
|
1832
|
+
let typeAnnotation;
|
|
1833
|
+
if (this.matchPunctuator(":")) {
|
|
1834
|
+
typeAnnotation = this.typeOr(() => this.checkOperator("="));
|
|
1835
|
+
}
|
|
1836
|
+
let init;
|
|
1837
|
+
if (this.matchOperator("=")) init = this.expressionOr(() => false);
|
|
1838
|
+
if (!typeAnnotation && !init) {
|
|
1839
|
+
this.error("A class field needs a type ('name: T') or a value ('name = v')");
|
|
1840
|
+
}
|
|
1841
|
+
return { type: "ClassField", name: memberName, isStatic, typeAnnotation, init, ...spanFrom(start, this.previous()) };
|
|
1842
|
+
}
|
|
1843
|
+
if (this.recover) {
|
|
1844
|
+
this.softError("Expected a class member: a field, 'function', 'constructor', 'get' or 'set'");
|
|
1845
|
+
this.advance();
|
|
1846
|
+
return void 0;
|
|
1847
|
+
}
|
|
1848
|
+
this.error("Expected a class member: a field, 'function', 'constructor', 'get' or 'set'");
|
|
1849
|
+
}
|
|
1850
|
+
/** Give a class method its `this`: a real first parameter, the way
|
|
1851
|
+
* `function T:m()` gets a real `self`. Every later pass — scopes, types,
|
|
1852
|
+
* arity, lowering — then sees an ordinary parameter and needs to know
|
|
1853
|
+
* nothing about classes. */
|
|
1854
|
+
/** After a bodyless head inside a class body, does another declaration of
|
|
1855
|
+
* the same member follow? Then the head was an overload signature. */
|
|
1856
|
+
isClassOverloadContinuation(name, isStatic) {
|
|
1857
|
+
const offset = isStatic ? 1 : 0;
|
|
1858
|
+
if (isStatic && !this.checkIdentifierValue("static")) return false;
|
|
1859
|
+
const keyword = this.peek(offset);
|
|
1860
|
+
if (!(keyword.type === "Keyword" && keyword.value === "function")) return false;
|
|
1861
|
+
const named = this.peek(offset + 1);
|
|
1862
|
+
return named.type === "Identifier" && named.value === name;
|
|
1863
|
+
}
|
|
1864
|
+
operatorAt(ahead, value) {
|
|
1865
|
+
const token = this.peek(ahead);
|
|
1866
|
+
return token.type === "Operator" && token.value === value;
|
|
1867
|
+
}
|
|
1612
1868
|
parseFunctionName() {
|
|
1613
1869
|
const start = this.current();
|
|
1614
1870
|
const base = this.parseIdentifier();
|
|
@@ -1702,7 +1958,7 @@ var Parser = class {
|
|
|
1702
1958
|
...spanFrom(start, this.previous())
|
|
1703
1959
|
};
|
|
1704
1960
|
}
|
|
1705
|
-
if (first.type === "CallExpression" || first.type === "MethodCallExpression") {
|
|
1961
|
+
if (first.type === "CallExpression" || first.type === "MethodCallExpression" || first.type === "NewExpression") {
|
|
1706
1962
|
return { type: "CallStatement", expression: first, ...spanFrom(start, this.previous()) };
|
|
1707
1963
|
}
|
|
1708
1964
|
this.error("Unexpected expression statement (expected assignment or call)");
|
|
@@ -1890,12 +2146,20 @@ var Parser = class {
|
|
|
1890
2146
|
if (t.type === "Keyword" && t.value === "if") {
|
|
1891
2147
|
return this.parseIfElseExpression();
|
|
1892
2148
|
}
|
|
2149
|
+
if (t.type === "Identifier" && t.value === "class") {
|
|
2150
|
+
return this.parseClassExpression();
|
|
2151
|
+
}
|
|
1893
2152
|
if (t.type === "Punctuator" && t.value === "{") {
|
|
1894
2153
|
return this.parseTableExpression();
|
|
1895
2154
|
}
|
|
1896
2155
|
if (t.type === "Punctuator" && t.value === "[") {
|
|
1897
2156
|
return this.parseArrayExpression();
|
|
1898
2157
|
}
|
|
2158
|
+
if (this.checkType("Identifier") && this.punctuatorAt(1, "=>")) return this.parseArrow();
|
|
2159
|
+
if (this.checkPunctuator("(") || this.checkOperator("<")) {
|
|
2160
|
+
const arrow = this.tryParse(() => this.parseArrow());
|
|
2161
|
+
if (arrow) return arrow;
|
|
2162
|
+
}
|
|
1899
2163
|
if (t.type === "Identifier" || t.type === "Punctuator" && t.value === "(") {
|
|
1900
2164
|
return this.parsePrefixExpression();
|
|
1901
2165
|
}
|
|
@@ -1909,7 +2173,7 @@ var Parser = class {
|
|
|
1909
2173
|
} else {
|
|
1910
2174
|
let expression;
|
|
1911
2175
|
try {
|
|
1912
|
-
expression = shiftSpans(parseExpressionFromSource(p.raw), p.line, p.column);
|
|
2176
|
+
expression = shiftSpans(parseExpressionFromSource(p.raw, this.classDepth > 0), p.line, p.column);
|
|
1913
2177
|
} catch (e) {
|
|
1914
2178
|
if (!this.recover || !(e instanceof ParseError || e instanceof LexError)) throw e;
|
|
1915
2179
|
const at = token;
|
|
@@ -1947,7 +2211,12 @@ var Parser = class {
|
|
|
1947
2211
|
parsePrefixExpression() {
|
|
1948
2212
|
const start = this.current();
|
|
1949
2213
|
let base;
|
|
1950
|
-
if (this.
|
|
2214
|
+
if (this.startsNew()) {
|
|
2215
|
+
base = this.parseNewExpression();
|
|
2216
|
+
} else if (this.classDepth > 0 && this.checkIdentifierValue("super") && this.startsSuperUse()) {
|
|
2217
|
+
this.advance();
|
|
2218
|
+
base = { type: "SuperExpression", ...spanFrom(start, start) };
|
|
2219
|
+
} else if (this.checkType("Identifier")) {
|
|
1951
2220
|
base = this.parseIdentifier();
|
|
1952
2221
|
} else if (this.matchPunctuator("(")) {
|
|
1953
2222
|
const inner = this.parseExpression();
|
|
@@ -2044,11 +2313,13 @@ var Parser = class {
|
|
|
2044
2313
|
}
|
|
2045
2314
|
}
|
|
2046
2315
|
if (this.startsCallArguments()) {
|
|
2316
|
+
const onNewLine = this.current().line.start > base.line.end;
|
|
2047
2317
|
const args = this.parseCallArguments();
|
|
2048
2318
|
base = {
|
|
2049
2319
|
type: "CallExpression",
|
|
2050
2320
|
callee: base,
|
|
2051
2321
|
arguments: args,
|
|
2322
|
+
argumentsOnNewLine: onNewLine || void 0,
|
|
2052
2323
|
...spanFrom(base, this.previous())
|
|
2053
2324
|
};
|
|
2054
2325
|
continue;
|
|
@@ -2057,6 +2328,52 @@ var Parser = class {
|
|
|
2057
2328
|
}
|
|
2058
2329
|
return base;
|
|
2059
2330
|
}
|
|
2331
|
+
/** `new` is a soft keyword: it starts a construction only when a name
|
|
2332
|
+
* follows it, so a function or field called `new` — `Instance.new(x)`,
|
|
2333
|
+
* and `Vec.new(1)` itself — is untouched. */
|
|
2334
|
+
startsNew() {
|
|
2335
|
+
return this.checkIdentifierValue("new") && this.peek(1).type === "Identifier";
|
|
2336
|
+
}
|
|
2337
|
+
/** `super` on its own means the base class, and only `super(...)` and
|
|
2338
|
+
* `super.member` say anything; anything else is a name that happens to
|
|
2339
|
+
* be spelled that way. */
|
|
2340
|
+
startsSuperUse() {
|
|
2341
|
+
return this.punctuatorAt(1, "(") || this.punctuatorAt(1, ".") && this.peek(2).type === "Identifier";
|
|
2342
|
+
}
|
|
2343
|
+
/** `new Name(args)` / `new Module.Name(args)`. The callee is a name, or a
|
|
2344
|
+
* name reached through a module — never an arbitrary expression, so the
|
|
2345
|
+
* arguments are unambiguously the constructor's. */
|
|
2346
|
+
parseNewExpression() {
|
|
2347
|
+
const start = this.current();
|
|
2348
|
+
this.advance();
|
|
2349
|
+
let callee = this.parseIdentifier();
|
|
2350
|
+
while (this.checkPunctuator(".") && this.peek(1).type === "Identifier") {
|
|
2351
|
+
this.advance();
|
|
2352
|
+
const property = this.parseIdentifier();
|
|
2353
|
+
callee = { type: "MemberExpression", object: callee, property, ...spanFrom(start, property) };
|
|
2354
|
+
}
|
|
2355
|
+
const typeArguments = this.tryCallTypeArguments();
|
|
2356
|
+
if (!this.checkPunctuator("(")) {
|
|
2357
|
+
this.error("Expected '(' after the class being constructed: 'new Name(...)'");
|
|
2358
|
+
}
|
|
2359
|
+
const args = this.parseCallArguments();
|
|
2360
|
+
return { type: "NewExpression", callee, arguments: args, typeArguments, ...spanFrom(start, this.previous()) };
|
|
2361
|
+
}
|
|
2362
|
+
/** After `...`, is there something to spread? Nothing following it means
|
|
2363
|
+
* the vararg pack, which is what `f(...)` has always passed on. */
|
|
2364
|
+
startsSpread() {
|
|
2365
|
+
const next = this.peek(1);
|
|
2366
|
+
if (next.type === "Punctuator") {
|
|
2367
|
+
const value = next.value;
|
|
2368
|
+
return value === "(" || value === "{" || value === "[";
|
|
2369
|
+
}
|
|
2370
|
+
return next.type === "Identifier" || next.type === "Literal" || next.type === "InterpolatedString";
|
|
2371
|
+
}
|
|
2372
|
+
parseSpreadArgument(stop) {
|
|
2373
|
+
const dots = this.advance();
|
|
2374
|
+
const argument = this.expressionOr(stop);
|
|
2375
|
+
return { type: "SpreadElement", argument, ...spanFrom(dots, argument) };
|
|
2376
|
+
}
|
|
2060
2377
|
/** Is the token `ahead` places on the punctuator `value`? */
|
|
2061
2378
|
punctuatorAt(ahead, value) {
|
|
2062
2379
|
const token = this.peek(ahead);
|
|
@@ -2103,7 +2420,7 @@ var Parser = class {
|
|
|
2103
2420
|
while (true) {
|
|
2104
2421
|
if (this.recover && this.onNewLine() && this.startsTableField() && !this.startsMethodCall(1)) break;
|
|
2105
2422
|
const before = this.cursor;
|
|
2106
|
-
const argument = this.expressionOr(stop);
|
|
2423
|
+
const argument = this.checkOperator("...") && this.startsSpread() ? this.parseSpreadArgument(stop) : this.expressionOr(stop);
|
|
2107
2424
|
if (argument.type !== "ErrorExpression" || this.cursor > before || list.length) list.push(argument);
|
|
2108
2425
|
if (this.matchPunctuator(",") && !this.checkPunctuator(")")) continue;
|
|
2109
2426
|
if (!this.recover || this.checkPunctuator(")")) break;
|
|
@@ -2206,8 +2523,12 @@ var Parser = class {
|
|
|
2206
2523
|
while (!this.checkPunctuator("]")) {
|
|
2207
2524
|
if (this.checkOperator("...")) {
|
|
2208
2525
|
const dots = this.advance();
|
|
2209
|
-
|
|
2210
|
-
|
|
2526
|
+
if (this.checkPunctuator("]") || this.checkPunctuator(",")) {
|
|
2527
|
+
elements.push({ type: "VarargExpression", ...spanFrom(dots, dots) });
|
|
2528
|
+
} else {
|
|
2529
|
+
const argument = this.expressionOr(stop);
|
|
2530
|
+
elements.push({ type: "SpreadElement", argument, ...spanFrom(dots, argument) });
|
|
2531
|
+
}
|
|
2211
2532
|
} else {
|
|
2212
2533
|
elements.push(this.expressionOr(stop));
|
|
2213
2534
|
}
|
|
@@ -2396,8 +2717,26 @@ var Parser = class {
|
|
|
2396
2717
|
if (!this.checkPunctuator(")")) {
|
|
2397
2718
|
while (true) {
|
|
2398
2719
|
if (this.checkOperator("...")) {
|
|
2399
|
-
this.advance();
|
|
2720
|
+
const dots = this.advance();
|
|
2400
2721
|
hasVarargs = true;
|
|
2722
|
+
if (this.checkType("Identifier")) {
|
|
2723
|
+
const nameTok = this.expectIdentifier();
|
|
2724
|
+
let typeAnnotation2;
|
|
2725
|
+
if (this.matchPunctuator(":")) {
|
|
2726
|
+
typeAnnotation2 = this.typeOr(() => this.checkPunctuator(")"));
|
|
2727
|
+
}
|
|
2728
|
+
params.push({
|
|
2729
|
+
type: "FunctionParameter",
|
|
2730
|
+
name: nameTok.value,
|
|
2731
|
+
typeAnnotation: typeAnnotation2,
|
|
2732
|
+
rest: true,
|
|
2733
|
+
...spanFrom(dots, this.previous())
|
|
2734
|
+
});
|
|
2735
|
+
if (this.checkPunctuator(",")) {
|
|
2736
|
+
this.problem("A rest parameter is the last one: nothing can follow '...'");
|
|
2737
|
+
}
|
|
2738
|
+
break;
|
|
2739
|
+
}
|
|
2401
2740
|
if (this.matchPunctuator(":")) {
|
|
2402
2741
|
varargTypeAnnotation = this.parseTypeOrTypePackReference();
|
|
2403
2742
|
}
|
|
@@ -2489,10 +2828,64 @@ var Parser = class {
|
|
|
2489
2828
|
}
|
|
2490
2829
|
return void 0;
|
|
2491
2830
|
}
|
|
2831
|
+
/** Run `parse`, and put the parser back where it was if it fails. Used
|
|
2832
|
+
* where two forms start alike and only their end tells them apart: `(a,
|
|
2833
|
+
* b) => a + b` and `(a + b)` both open with a `(`. */
|
|
2834
|
+
tryParse(parse2) {
|
|
2835
|
+
const cursor = this.cursor;
|
|
2836
|
+
const errors = this.errors.length;
|
|
2837
|
+
try {
|
|
2838
|
+
return parse2();
|
|
2839
|
+
} catch (error) {
|
|
2840
|
+
if (!(error instanceof ParseError || error instanceof ParseRecover)) throw error;
|
|
2841
|
+
this.cursor = cursor;
|
|
2842
|
+
this.errors.length = errors;
|
|
2843
|
+
return void 0;
|
|
2844
|
+
}
|
|
2845
|
+
}
|
|
2846
|
+
/** `x => x * 2` — a function, written short. The body is an expression,
|
|
2847
|
+
* which is returned, or a block in braces, as in TypeScript. */
|
|
2848
|
+
parseArrow() {
|
|
2849
|
+
const start = this.current();
|
|
2850
|
+
const head = this.checkType("Identifier") ? (() => {
|
|
2851
|
+
const name = this.expectIdentifier();
|
|
2852
|
+
return {
|
|
2853
|
+
start,
|
|
2854
|
+
generics: [],
|
|
2855
|
+
params: [{ type: "FunctionParameter", name: name.value, ...spanFrom(name, name) }],
|
|
2856
|
+
hasVarargs: false,
|
|
2857
|
+
varargTypeAnnotation: void 0,
|
|
2858
|
+
returnType: void 0,
|
|
2859
|
+
predicate: void 0
|
|
2860
|
+
};
|
|
2861
|
+
})() : this.parseFunctionHead();
|
|
2862
|
+
this.expectPunctuator("=>");
|
|
2863
|
+
const body = this.checkPunctuator("{") ? this.parseBraceBlock() : this.returnOf(this.parseExpression(0));
|
|
2864
|
+
const func = {
|
|
2865
|
+
type: "FunctionBody",
|
|
2866
|
+
generics: head.generics,
|
|
2867
|
+
params: head.params,
|
|
2868
|
+
hasVarargs: head.hasVarargs,
|
|
2869
|
+
varargTypeAnnotation: head.varargTypeAnnotation,
|
|
2870
|
+
returnType: head.returnType,
|
|
2871
|
+
predicate: head.predicate,
|
|
2872
|
+
body,
|
|
2873
|
+
...spanFrom(start, this.previous())
|
|
2874
|
+
};
|
|
2875
|
+
return { type: "FunctionExpression", func, ...spanFrom(start, this.previous()) };
|
|
2876
|
+
}
|
|
2877
|
+
/** A one-expression body: the value is what the function returns. */
|
|
2878
|
+
returnOf(expression) {
|
|
2879
|
+
const statement = {
|
|
2880
|
+
type: "ReturnStatement",
|
|
2881
|
+
arguments: [expression],
|
|
2882
|
+
...spanFrom(expression, expression)
|
|
2883
|
+
};
|
|
2884
|
+
return { type: "Block", statements: [statement], ...spanFrom(expression, expression) };
|
|
2885
|
+
}
|
|
2492
2886
|
parseFunctionBody(opener) {
|
|
2493
2887
|
const head = this.parseFunctionHead();
|
|
2494
|
-
const body = this.
|
|
2495
|
-
this.expectEnd(opener);
|
|
2888
|
+
const body = this.parseStatementBody(opener);
|
|
2496
2889
|
return {
|
|
2497
2890
|
type: "FunctionBody",
|
|
2498
2891
|
generics: head.generics,
|
|
@@ -2517,9 +2910,13 @@ var Parser = class {
|
|
|
2517
2910
|
...spanFrom(head.start, this.previous())
|
|
2518
2911
|
};
|
|
2519
2912
|
}
|
|
2913
|
+
/** A function's statements. */
|
|
2914
|
+
parseStatementBody(opener) {
|
|
2915
|
+
void opener;
|
|
2916
|
+
return this.parseBracedBody("function");
|
|
2917
|
+
}
|
|
2520
2918
|
headToBody(head, opener) {
|
|
2521
|
-
const body = this.
|
|
2522
|
-
this.expectEnd(opener);
|
|
2919
|
+
const body = this.parseStatementBody(opener);
|
|
2523
2920
|
return {
|
|
2524
2921
|
type: "FunctionBody",
|
|
2525
2922
|
generics: head.generics,
|
|
@@ -2743,8 +3140,21 @@ var Parser = class {
|
|
|
2743
3140
|
if (!this.checkPunctuator(")")) {
|
|
2744
3141
|
while (true) {
|
|
2745
3142
|
if (this.checkOperator("...")) {
|
|
2746
|
-
this.advance();
|
|
3143
|
+
const dots = this.advance();
|
|
2747
3144
|
hasVarargs = true;
|
|
3145
|
+
if (this.checkType("Identifier") && this.punctuatorAt(1, ":")) {
|
|
3146
|
+
const nameTok2 = this.expectIdentifier();
|
|
3147
|
+
this.advance();
|
|
3148
|
+
params.push({
|
|
3149
|
+
type: "FunctionTypeParameter",
|
|
3150
|
+
name: nameTok2.value,
|
|
3151
|
+
id: tokenIdentifier(nameTok2),
|
|
3152
|
+
typeAnnotation: this.parseType(),
|
|
3153
|
+
rest: true,
|
|
3154
|
+
...spanFrom(dots, this.previous())
|
|
3155
|
+
});
|
|
3156
|
+
break;
|
|
3157
|
+
}
|
|
2748
3158
|
varargType = this.parseType();
|
|
2749
3159
|
break;
|
|
2750
3160
|
}
|
|
@@ -2782,7 +3192,7 @@ var Parser = class {
|
|
|
2782
3192
|
}
|
|
2783
3193
|
}
|
|
2784
3194
|
this.expectPunctuator(")");
|
|
2785
|
-
if (this.matchPunctuator("->")) {
|
|
3195
|
+
if (this.matchPunctuator("=>") || this.matchPunctuator("->")) {
|
|
2786
3196
|
const predicate = this.tryParseTypePredicate();
|
|
2787
3197
|
const returnType = predicate ? { type: "TypeReference", base: "boolean", typeArguments: [], ...spanFrom(start, this.previous()) } : this.parseTypeOrTypePackReference();
|
|
2788
3198
|
return {
|
|
@@ -2925,8 +3335,22 @@ var Parser = class {
|
|
|
2925
3335
|
optional: optional2,
|
|
2926
3336
|
...spanFrom(propStart, this.previous())
|
|
2927
3337
|
});
|
|
3338
|
+
} 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 === ":")) {
|
|
3339
|
+
const keyTok = this.advance();
|
|
3340
|
+
const name = String(keyTok.value);
|
|
3341
|
+
const optional2 = this.matchPunctuator("?");
|
|
3342
|
+
this.expectPunctuator(":");
|
|
3343
|
+
const valueType = this.parseType();
|
|
3344
|
+
properties.push({
|
|
3345
|
+
type: "TableTypeProperty",
|
|
3346
|
+
name,
|
|
3347
|
+
key: tokenIdentifier(keyTok),
|
|
3348
|
+
valueType,
|
|
3349
|
+
optional: optional2,
|
|
3350
|
+
...spanFrom(propStart, this.previous())
|
|
3351
|
+
});
|
|
2928
3352
|
} else {
|
|
2929
|
-
this.error(
|
|
3353
|
+
this.error(`Expected object type property ('name: T', '"name": T' or '[K]: V'); use 'T[]' for arrays and '[T, U]' for tuples`);
|
|
2930
3354
|
}
|
|
2931
3355
|
if (this.matchPunctuator(",") || this.matchPunctuator(";")) continue;
|
|
2932
3356
|
break;
|
|
@@ -3012,9 +3436,10 @@ function parseTypeFromSource(raw) {
|
|
|
3012
3436
|
const parser = new Parser(tokenize(raw));
|
|
3013
3437
|
return parser.parseType();
|
|
3014
3438
|
}
|
|
3015
|
-
function parseExpressionFromSource(raw) {
|
|
3439
|
+
function parseExpressionFromSource(raw, inClass = false) {
|
|
3016
3440
|
const tokens = tokenize(raw);
|
|
3017
3441
|
const parser = new Parser(tokens);
|
|
3442
|
+
if (inClass) parser.classDepth = 1;
|
|
3018
3443
|
const expr = parser.parseExpression();
|
|
3019
3444
|
return expr;
|
|
3020
3445
|
}
|
|
@@ -3123,6 +3548,11 @@ var Analyzer = class {
|
|
|
3123
3548
|
hoistFunctions(block, scope) {
|
|
3124
3549
|
for (const statement of block.statements) {
|
|
3125
3550
|
const declaration = statement.type === "ExportStatement" ? statement.declaration : statement;
|
|
3551
|
+
if (declaration.type === "ClassDeclaration") {
|
|
3552
|
+
this.declare(scope, declaration.name.name, "local", declaration.name, true, "class");
|
|
3553
|
+
this.hoisted.set(declaration.name, scope === this.moduleScope ? -1 : this.functionDepth);
|
|
3554
|
+
continue;
|
|
3555
|
+
}
|
|
3126
3556
|
if (declaration.type !== "FunctionDeclaration") continue;
|
|
3127
3557
|
this.declare(scope, declaration.name.name, "local", declaration.name, true, "function");
|
|
3128
3558
|
this.hoisted.set(declaration.name, scope === this.moduleScope ? -1 : this.functionDepth);
|
|
@@ -3135,7 +3565,7 @@ var Analyzer = class {
|
|
|
3135
3565
|
* hoisted, and this does not apply. */
|
|
3136
3566
|
checkUseBeforeDefine(identifier, id) {
|
|
3137
3567
|
const binding = this.bindings.get(id);
|
|
3138
|
-
if (binding.declaredBy !== "function" || this.typeQueryDepth > 0) return;
|
|
3568
|
+
if (binding.declaredBy !== "function" && binding.declaredBy !== "class" || this.typeQueryDepth > 0) return;
|
|
3139
3569
|
const declaration = binding.declarationNode;
|
|
3140
3570
|
const depth = declaration && this.hoisted.get(declaration);
|
|
3141
3571
|
if (depth === void 0 || depth !== this.functionDepth) return;
|
|
@@ -3390,6 +3820,11 @@ var Analyzer = class {
|
|
|
3390
3820
|
this.visitFunctionBody(stmt.func, scope);
|
|
3391
3821
|
return;
|
|
3392
3822
|
}
|
|
3823
|
+
case "ClassDeclaration": {
|
|
3824
|
+
if (!this.hoisted.has(stmt.name)) this.declare(scope, stmt.name.name, "local", stmt.name, true, "class");
|
|
3825
|
+
this.visitClassBody(stmt, scope);
|
|
3826
|
+
return;
|
|
3827
|
+
}
|
|
3393
3828
|
case "FunctionDeclarationStatement": {
|
|
3394
3829
|
if (stmt.target.path.length === 0 && !stmt.target.method) {
|
|
3395
3830
|
this.referenceAsAssignmentTarget(scope, stmt.target.base);
|
|
@@ -3509,7 +3944,8 @@ var Analyzer = class {
|
|
|
3509
3944
|
this.visitStatement(stmt.declaration, scope);
|
|
3510
3945
|
return;
|
|
3511
3946
|
case "ExportDefaultStatement":
|
|
3512
|
-
this.
|
|
3947
|
+
if (stmt.declaration.type === "ClassDeclaration") this.visitStatement(stmt.declaration, scope);
|
|
3948
|
+
else this.visitExpression(stmt.declaration, scope);
|
|
3513
3949
|
return;
|
|
3514
3950
|
case "ExportNamedStatement":
|
|
3515
3951
|
if (!stmt.source) {
|
|
@@ -3557,6 +3993,33 @@ var Analyzer = class {
|
|
|
3557
3993
|
}
|
|
3558
3994
|
/** `<K extends typeof config>` — a constraint is a type like any other,
|
|
3559
3995
|
* and the `typeof` in it reads a value. */
|
|
3996
|
+
/** A class body. Its type parameters live in a scope of their own, and
|
|
3997
|
+
* every member is written inside it — so a method's annotations see `T`,
|
|
3998
|
+
* and everything else sees what the class declaration sees, itself
|
|
3999
|
+
* included. `this` is not declared here: the parser makes it a real first
|
|
4000
|
+
* parameter, so it arrives with the rest of them. */
|
|
4001
|
+
visitClassBody(node, outer) {
|
|
4002
|
+
const scope = node.typeParams?.length ? childScope(outer) : outer;
|
|
4003
|
+
this.visitGenerics(node.typeParams, scope);
|
|
4004
|
+
if (node.superclass) this.reference(outer, node.superclass);
|
|
4005
|
+
for (const argument of node.superArguments ?? []) this.visitType(argument, scope);
|
|
4006
|
+
for (const member of node.members) {
|
|
4007
|
+
switch (member.type) {
|
|
4008
|
+
case "ClassField":
|
|
4009
|
+
this.visitType(member.typeAnnotation, scope);
|
|
4010
|
+
if (member.init) this.visitExpression(member.init, scope);
|
|
4011
|
+
break;
|
|
4012
|
+
case "ClassMethod":
|
|
4013
|
+
for (const signature of member.signatures ?? []) this.visitSignature(signature, scope);
|
|
4014
|
+
this.visitFunctionBody(member.func, scope, member.func.isMethod);
|
|
4015
|
+
break;
|
|
4016
|
+
case "ClassAccessor":
|
|
4017
|
+
case "ClassConstructor":
|
|
4018
|
+
this.visitFunctionBody(member.func, scope, member.func.isMethod);
|
|
4019
|
+
break;
|
|
4020
|
+
}
|
|
4021
|
+
}
|
|
4022
|
+
}
|
|
3560
4023
|
visitGenerics(generics, scope) {
|
|
3561
4024
|
for (const generic of generics ?? []) {
|
|
3562
4025
|
this.visitType(generic.constraint, scope);
|
|
@@ -3639,6 +4102,22 @@ var Analyzer = class {
|
|
|
3639
4102
|
this.visitExpression(expr.callee, scope);
|
|
3640
4103
|
for (const arg of expr.arguments) this.visitExpression(arg, scope);
|
|
3641
4104
|
return;
|
|
4105
|
+
case "NewExpression":
|
|
4106
|
+
this.visitExpression(expr.callee, scope);
|
|
4107
|
+
for (const argument of expr.arguments) this.visitExpression(argument, scope);
|
|
4108
|
+
for (const argument of expr.typeArguments ?? []) this.visitType(argument, scope);
|
|
4109
|
+
return;
|
|
4110
|
+
case "SuperExpression":
|
|
4111
|
+
return;
|
|
4112
|
+
case "SpreadElement":
|
|
4113
|
+
this.visitExpression(expr.argument, scope);
|
|
4114
|
+
return;
|
|
4115
|
+
case "ClassExpression": {
|
|
4116
|
+
const inner = childScope(scope);
|
|
4117
|
+
if (expr.name) this.declare(inner, expr.name.name, "local", expr.name, true, "class");
|
|
4118
|
+
this.visitClassBody(expr, inner);
|
|
4119
|
+
return;
|
|
4120
|
+
}
|
|
3642
4121
|
case "MethodCallExpression":
|
|
3643
4122
|
this.visitExpression(expr.object, scope);
|
|
3644
4123
|
for (const arg of expr.arguments) this.visitExpression(arg, scope);
|
|
@@ -3720,6 +4199,14 @@ function preludeProgram() {
|
|
|
3720
4199
|
function isClassType(t) {
|
|
3721
4200
|
return t.kind === "object" && t.class !== void 0;
|
|
3722
4201
|
}
|
|
4202
|
+
function isClassAssignable(got, want) {
|
|
4203
|
+
if (!got || !got.ancestors.includes(want.name)) return false;
|
|
4204
|
+
const wanted = want.typeArguments?.get(want.name);
|
|
4205
|
+
if (!wanted?.length) return true;
|
|
4206
|
+
const given = got.typeArguments?.get(want.name);
|
|
4207
|
+
if (!given) return true;
|
|
4208
|
+
return wanted.every((w, i) => given[i] !== void 0 && isAssignable(given[i], w));
|
|
4209
|
+
}
|
|
3723
4210
|
function typeParam(name, constraint, isConst) {
|
|
3724
4211
|
return { kind: "typeParam", name, constraint, isConst };
|
|
3725
4212
|
}
|
|
@@ -3771,11 +4258,21 @@ function substitute(t, subst) {
|
|
|
3771
4258
|
case "object": {
|
|
3772
4259
|
const entries = [];
|
|
3773
4260
|
for (const [k, v] of t.properties) entries.push([k, { ...v, type: substitute(v.type, subst) }]);
|
|
3774
|
-
|
|
4261
|
+
const out = objectType(
|
|
3775
4262
|
entries,
|
|
3776
4263
|
t.indexer && { key: substitute(t.indexer.key, subst), value: substitute(t.indexer.value, subst) },
|
|
3777
4264
|
t.frozen
|
|
3778
4265
|
);
|
|
4266
|
+
if (t.name) out.name = t.name;
|
|
4267
|
+
if (t.class) {
|
|
4268
|
+
out.class = {
|
|
4269
|
+
...t.class,
|
|
4270
|
+
typeArguments: t.class.typeArguments && new Map(
|
|
4271
|
+
[...t.class.typeArguments].map(([name, args]) => [name, args.map((a) => substitute(a, subst))])
|
|
4272
|
+
)
|
|
4273
|
+
};
|
|
4274
|
+
}
|
|
4275
|
+
return out;
|
|
3779
4276
|
}
|
|
3780
4277
|
case "function": {
|
|
3781
4278
|
const inner = t.typeParams ? new Map([...subst].filter(([k]) => !t.typeParams.includes(k))) : subst;
|
|
@@ -3842,6 +4339,11 @@ function substitute(t, subst) {
|
|
|
3842
4339
|
return t;
|
|
3843
4340
|
}
|
|
3844
4341
|
}
|
|
4342
|
+
function classArguments(t, name) {
|
|
4343
|
+
if (t.kind === "genericRef") return t.name === name ? t.typeArguments : void 0;
|
|
4344
|
+
if (t.kind === "object") return t.class?.typeArguments?.get(name);
|
|
4345
|
+
return void 0;
|
|
4346
|
+
}
|
|
3845
4347
|
function unify(param, arg, vars, out) {
|
|
3846
4348
|
if (param.kind === "typeParam" && param.constraint && !vars.has(param.name)) {
|
|
3847
4349
|
unify(param.constraint, arg, vars, out);
|
|
@@ -3869,7 +4371,12 @@ function unify(param, arg, vars, out) {
|
|
|
3869
4371
|
}
|
|
3870
4372
|
return;
|
|
3871
4373
|
case "object":
|
|
3872
|
-
if (param.class)
|
|
4374
|
+
if (param.class) {
|
|
4375
|
+
const wanted = param.class.typeArguments?.get(param.class.name);
|
|
4376
|
+
const given = classArguments(arg, param.class.name);
|
|
4377
|
+
if (wanted && given) wanted.forEach((w, i) => given[i] && unify(w, given[i], vars, out));
|
|
4378
|
+
return;
|
|
4379
|
+
}
|
|
3873
4380
|
if (arg.kind === "object" && !arg.class) {
|
|
3874
4381
|
for (const [k, pv] of param.properties) {
|
|
3875
4382
|
const av = arg.properties.get(k);
|
|
@@ -3878,6 +4385,11 @@ function unify(param, arg, vars, out) {
|
|
|
3878
4385
|
if (param.indexer && arg.indexer) unify(param.indexer.value, arg.indexer.value, vars, out);
|
|
3879
4386
|
}
|
|
3880
4387
|
return;
|
|
4388
|
+
case "genericRef": {
|
|
4389
|
+
const given = classArguments(arg, param.name);
|
|
4390
|
+
if (given) param.typeArguments.forEach((p, i) => given[i] && unify(p, given[i], vars, out));
|
|
4391
|
+
return;
|
|
4392
|
+
}
|
|
3881
4393
|
case "union":
|
|
3882
4394
|
for (const m of param.types) unify(m, arg, vars, out);
|
|
3883
4395
|
return;
|
|
@@ -3972,6 +4484,10 @@ var expandAlias;
|
|
|
3972
4484
|
function setAliasExpander(fn2) {
|
|
3973
4485
|
expandAlias = fn2;
|
|
3974
4486
|
}
|
|
4487
|
+
var deferredBound;
|
|
4488
|
+
function setDeferredBound(fn2) {
|
|
4489
|
+
deferredBound = fn2;
|
|
4490
|
+
}
|
|
3975
4491
|
var comparing = [];
|
|
3976
4492
|
function isAssignable(rawA, rawB) {
|
|
3977
4493
|
let a = isNoValue(rawA) ? nilType : rawA;
|
|
@@ -4002,10 +4518,29 @@ function isAssignableInner(a, b) {
|
|
|
4002
4518
|
if (b.kind === "unknown") return true;
|
|
4003
4519
|
if (b.kind === "never") return false;
|
|
4004
4520
|
if (a.kind === "unknown") return false;
|
|
4521
|
+
if (a.kind === "difference" && b.kind === "difference") {
|
|
4522
|
+
return isAssignable(a.base, b.base) && isAssignable(b.excluded, a.excluded);
|
|
4523
|
+
}
|
|
4005
4524
|
if (b.kind === "difference") {
|
|
4006
4525
|
return isAssignable(a, b.base) && !overlaps(a, b.excluded);
|
|
4007
4526
|
}
|
|
4008
|
-
if (a.kind === "difference")
|
|
4527
|
+
if (a.kind === "difference") {
|
|
4528
|
+
if (b.kind === "union" && b.types.some((m) => isAssignable(a, m))) return true;
|
|
4529
|
+
return isAssignable(a.base, b);
|
|
4530
|
+
}
|
|
4531
|
+
if (a.kind === "conditional" || a.kind === "indexedAccess") {
|
|
4532
|
+
if ((b.kind === "conditional" || b.kind === "indexedAccess" || b.kind === "union") && equalTypes(a, b)) {
|
|
4533
|
+
return true;
|
|
4534
|
+
}
|
|
4535
|
+
if (b.kind === "union" && b.types.some((m) => equalTypes(a, m))) return true;
|
|
4536
|
+
const bound = deferredBound?.(a);
|
|
4537
|
+
if (bound && bound !== a) return isAssignable(bound, b);
|
|
4538
|
+
}
|
|
4539
|
+
if (a.kind === "typeParam") {
|
|
4540
|
+
if (b.kind === "typeParam" && a.name === b.name) return true;
|
|
4541
|
+
if (b.kind === "union" && b.types.some((m) => m.kind === "typeParam" && m.name === a.name)) return true;
|
|
4542
|
+
return a.constraint ? isAssignable(a.constraint, b) : false;
|
|
4543
|
+
}
|
|
4009
4544
|
if (a.kind === "union") return a.types.every((t) => isAssignable(t, b));
|
|
4010
4545
|
if (b.kind === "union") return b.types.some((t) => isAssignable(a, t));
|
|
4011
4546
|
if (b.kind === "intersection") return b.types.every((t) => isAssignable(a, t));
|
|
@@ -4039,7 +4574,7 @@ function isAssignableInner(a, b) {
|
|
|
4039
4574
|
}
|
|
4040
4575
|
if (a.kind === "object") {
|
|
4041
4576
|
if (b.kind !== "object") return false;
|
|
4042
|
-
if (b.class) return a.class
|
|
4577
|
+
if (b.class) return isClassAssignable(a.class, b.class);
|
|
4043
4578
|
if (a.class && (b.indexer || b.properties.size === 0)) return false;
|
|
4044
4579
|
for (const [name, bp] of b.properties) {
|
|
4045
4580
|
const ap = a.properties.get(name);
|
|
@@ -4048,7 +4583,7 @@ function isAssignableInner(a, b) {
|
|
|
4048
4583
|
if (a.indexer && isAssignable(a.indexer.value, bp.type)) continue;
|
|
4049
4584
|
return false;
|
|
4050
4585
|
}
|
|
4051
|
-
if (!isAssignable(ap.type, bp.type)) return false;
|
|
4586
|
+
if (!isAssignable(ap.type, bp.optional ? optional(bp.type) : bp.type)) return false;
|
|
4052
4587
|
}
|
|
4053
4588
|
if (b.indexer) {
|
|
4054
4589
|
for (const [name, ap] of a.properties) {
|
|
@@ -4069,10 +4604,6 @@ function isAssignableInner(a, b) {
|
|
|
4069
4604
|
}
|
|
4070
4605
|
return isAssignable(a.returns, b.returns);
|
|
4071
4606
|
}
|
|
4072
|
-
if (a.kind === "typeParam") {
|
|
4073
|
-
if (b.kind === "typeParam" && a.name === b.name) return true;
|
|
4074
|
-
return a.constraint ? isAssignable(a.constraint, b) : false;
|
|
4075
|
-
}
|
|
4076
4607
|
if (b.kind === "typeParam") return false;
|
|
4077
4608
|
if (a.kind === "genericRef" || b.kind === "genericRef") {
|
|
4078
4609
|
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]));
|
|
@@ -4192,7 +4723,9 @@ function containsFreeTypeParam(t, seen, bound) {
|
|
|
4192
4723
|
case "intersection":
|
|
4193
4724
|
return t.types.some((m) => containsTypeParam(m, seen, bound));
|
|
4194
4725
|
case "object":
|
|
4195
|
-
if (t.class)
|
|
4726
|
+
if (t.class) {
|
|
4727
|
+
return [...t.class.typeArguments?.values() ?? []].some((args) => args.some((a) => containsTypeParam(a, seen, bound)));
|
|
4728
|
+
}
|
|
4196
4729
|
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));
|
|
4197
4730
|
case "function": {
|
|
4198
4731
|
const inner = t.typeParams?.length ? /* @__PURE__ */ new Set([...bound, ...t.typeParams]) : bound;
|
|
@@ -4362,7 +4895,10 @@ function formatTypeUncached(t) {
|
|
|
4362
4895
|
return t.isPack ? `(${inner})` : `[${inner}]`;
|
|
4363
4896
|
}
|
|
4364
4897
|
case "object": {
|
|
4365
|
-
if (t.name)
|
|
4898
|
+
if (t.name) {
|
|
4899
|
+
const args = t.class?.typeArguments?.get(t.class.name);
|
|
4900
|
+
return args?.length ? `${t.name}<${args.map(formatType).join(", ")}>` : t.name;
|
|
4901
|
+
}
|
|
4366
4902
|
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)}`);
|
|
4367
4903
|
if (t.indexer) props.push(`[${formatType(t.indexer.key)}]: ${formatType(t.indexer.value)}`);
|
|
4368
4904
|
return props.length ? `{ ${props.join(", ")} }` : "{}";
|
|
@@ -4381,7 +4917,7 @@ function formatTypeUncached(t) {
|
|
|
4381
4917
|
}).join(", ")}>` : "";
|
|
4382
4918
|
const ps = t.params.map((p) => `${p.name ? p.name + ": " : ""}${formatType(p.type)}`);
|
|
4383
4919
|
if (t.varargs) ps.push(`...${formatType(t.varargs)}`);
|
|
4384
|
-
return `${gen}(${ps.join(", ")})
|
|
4920
|
+
return `${gen}(${ps.join(", ")}) => ${formatPredicate(t) ?? formatType(t.returns)}`;
|
|
4385
4921
|
}
|
|
4386
4922
|
case "typeParam":
|
|
4387
4923
|
return t.name;
|
|
@@ -4425,7 +4961,7 @@ function formatPredicate(t) {
|
|
|
4425
4961
|
}
|
|
4426
4962
|
function formatAtom(t) {
|
|
4427
4963
|
if (t.kind === "intersection" && t.name) return t.name;
|
|
4428
|
-
if (t.kind === "union" || t.kind === "intersection" || t.kind === "function" || t.kind === "difference") {
|
|
4964
|
+
if (t.kind === "union" || t.kind === "intersection" || t.kind === "function" || t.kind === "difference" || t.kind === "conditional") {
|
|
4429
4965
|
return `(${formatType(t)})`;
|
|
4430
4966
|
}
|
|
4431
4967
|
return formatType(t);
|
|
@@ -4529,13 +5065,33 @@ function moduleExports(program, scopes, types, resolveModule) {
|
|
|
4529
5065
|
if (stmt.type === "ExportStatement") {
|
|
4530
5066
|
const declaration = stmt.declaration;
|
|
4531
5067
|
if (declaration.type === "FunctionDeclaration") exportName(declaration.name, declaration.name.name);
|
|
4532
|
-
else
|
|
5068
|
+
else if (declaration.type === "ClassDeclaration") {
|
|
5069
|
+
exportName(declaration.name, declaration.name.name);
|
|
5070
|
+
const instance = types.aliases.get(declaration.name.name);
|
|
5071
|
+
if (instance) {
|
|
5072
|
+
exportedTypes.set(declaration.name.name, {
|
|
5073
|
+
type: instance,
|
|
5074
|
+
params: declaration.typeParams.map((g) => g.name)
|
|
5075
|
+
});
|
|
5076
|
+
}
|
|
5077
|
+
} else for (const target of declaration.names) exportPattern(target);
|
|
4533
5078
|
} else if (stmt.type === "ExportTypeAliasStatement") {
|
|
4534
5079
|
const name = stmt.alias.name.name;
|
|
4535
5080
|
const type = types.aliases.get(name);
|
|
4536
5081
|
if (type) exportedTypes.set(name, { type, params: stmt.alias.generics.map((g) => g.name) });
|
|
4537
5082
|
} else if (stmt.type === "ExportDefaultStatement") {
|
|
4538
|
-
|
|
5083
|
+
if (stmt.declaration.type === "ClassDeclaration") {
|
|
5084
|
+
const id = byDeclaration.get(stmt.declaration.name);
|
|
5085
|
+
defaultType = (id !== void 0 ? types.bindingType.get(id) : void 0) ?? anyType;
|
|
5086
|
+
const instance = types.aliases.get(stmt.declaration.name.name);
|
|
5087
|
+
if (instance) {
|
|
5088
|
+
const exported = { type: instance, params: stmt.declaration.typeParams.map((g) => g.name) };
|
|
5089
|
+
exportedTypes.set(stmt.declaration.name.name, exported);
|
|
5090
|
+
exportedTypes.set("default", exported);
|
|
5091
|
+
}
|
|
5092
|
+
} else {
|
|
5093
|
+
defaultType = types.typeOf.get(stmt.declaration) ?? anyType;
|
|
5094
|
+
}
|
|
4539
5095
|
} else if (stmt.type === "ExportNamedStatement") {
|
|
4540
5096
|
if (stmt.source) {
|
|
4541
5097
|
const from = resolveModule?.(stmt.source.value);
|
|
@@ -4663,6 +5219,51 @@ function keepsLiterals(paramType) {
|
|
|
4663
5219
|
const members = paramType.constraint.kind === "union" ? paramType.constraint.types : [paramType.constraint];
|
|
4664
5220
|
return members.some((m) => m.kind === "literal");
|
|
4665
5221
|
}
|
|
5222
|
+
var CLASS_LINKS = /* @__PURE__ */ new Set(["new", "ClassObject", "ParentClass"]);
|
|
5223
|
+
var CLASS_RESERVED = /* @__PURE__ */ new Set([
|
|
5224
|
+
"new",
|
|
5225
|
+
"ClassObject",
|
|
5226
|
+
"ParentClass",
|
|
5227
|
+
"__init",
|
|
5228
|
+
"__index",
|
|
5229
|
+
"__newindex",
|
|
5230
|
+
"__getters",
|
|
5231
|
+
"__setters",
|
|
5232
|
+
"__dynamic"
|
|
5233
|
+
]);
|
|
5234
|
+
function callsSuper(block) {
|
|
5235
|
+
let found = false;
|
|
5236
|
+
walkNodes(block, (node) => {
|
|
5237
|
+
const record = node;
|
|
5238
|
+
if (record.type === "CallExpression" && record.callee?.type === "SuperExpression") found = true;
|
|
5239
|
+
});
|
|
5240
|
+
return found;
|
|
5241
|
+
}
|
|
5242
|
+
function assignedFields(block) {
|
|
5243
|
+
const names = /* @__PURE__ */ new Set();
|
|
5244
|
+
walkNodes(block, (node) => {
|
|
5245
|
+
const record = node;
|
|
5246
|
+
const targets = record.type === "AssignmentStatement" ? record.targets : record.type === "CompoundAssignmentStatement" ? [record.target] : void 0;
|
|
5247
|
+
for (const target of targets ?? []) {
|
|
5248
|
+
const member = target;
|
|
5249
|
+
if (member.type === "MemberExpression" && member.object?.type === "Identifier" && member.object.name === "this" && member.property?.name) {
|
|
5250
|
+
names.add(member.property.name);
|
|
5251
|
+
}
|
|
5252
|
+
}
|
|
5253
|
+
});
|
|
5254
|
+
return names;
|
|
5255
|
+
}
|
|
5256
|
+
function walkNodes(root, visit) {
|
|
5257
|
+
if (!root || typeof root !== "object") return;
|
|
5258
|
+
if (Array.isArray(root)) {
|
|
5259
|
+
for (const item of root) walkNodes(item, visit);
|
|
5260
|
+
return;
|
|
5261
|
+
}
|
|
5262
|
+
visit(root);
|
|
5263
|
+
for (const [key, value] of Object.entries(root)) {
|
|
5264
|
+
if (key !== "line" && key !== "column" && value && typeof value === "object") walkNodes(value, visit);
|
|
5265
|
+
}
|
|
5266
|
+
}
|
|
4666
5267
|
var AliasMap = class extends Map {
|
|
4667
5268
|
pending = /* @__PURE__ */ new Map();
|
|
4668
5269
|
defer(name, resolve5) {
|
|
@@ -4786,6 +5387,8 @@ var TypeAnalyzer = class {
|
|
|
4786
5387
|
aliasDefs = /* @__PURE__ */ new Map();
|
|
4787
5388
|
/** See `resolveClass`. */
|
|
4788
5389
|
classTypes = /* @__PURE__ */ new WeakMap();
|
|
5390
|
+
/** See `instanceType` — one instance type per `class ... end`. */
|
|
5391
|
+
instanceTypes = /* @__PURE__ */ new WeakMap();
|
|
4789
5392
|
classMembers = /* @__PURE__ */ new WeakMap();
|
|
4790
5393
|
/** Generic parameters currently in lexical scope (alias body / generic fn),
|
|
4791
5394
|
* with their `extends` constraints resolved. */
|
|
@@ -4827,6 +5430,7 @@ var TypeAnalyzer = class {
|
|
|
4827
5430
|
this.registerAliasDefs(preludeProgram().body, true);
|
|
4828
5431
|
for (const lib of this.options.libs ?? []) this.registerAliasDefs(lib.body, true);
|
|
4829
5432
|
this.registerAliasDefs(this.program.body);
|
|
5433
|
+
this.registerNestedClasses();
|
|
4830
5434
|
for (const lib of this.options.libs ?? []) this.harvestDeclares(lib.body);
|
|
4831
5435
|
this.registerImportedTypes();
|
|
4832
5436
|
this.resolveAllAliases();
|
|
@@ -4838,6 +5442,7 @@ var TypeAnalyzer = class {
|
|
|
4838
5442
|
this.bindingType.set(id, t);
|
|
4839
5443
|
}
|
|
4840
5444
|
setAliasExpander((t) => this.expand(t));
|
|
5445
|
+
setDeferredBound((t) => this.deferredBound(t));
|
|
4841
5446
|
try {
|
|
4842
5447
|
const env = /* @__PURE__ */ new Map();
|
|
4843
5448
|
this.visitBlock(this.program.body, env);
|
|
@@ -4845,6 +5450,7 @@ var TypeAnalyzer = class {
|
|
|
4845
5450
|
if (this.options.reportUnknownTypes) this.reportUnknownTypes();
|
|
4846
5451
|
} finally {
|
|
4847
5452
|
setAliasExpander(void 0);
|
|
5453
|
+
setDeferredBound(void 0);
|
|
4848
5454
|
}
|
|
4849
5455
|
return {
|
|
4850
5456
|
typeOf: this.typeOf,
|
|
@@ -4894,6 +5500,11 @@ var TypeAnalyzer = class {
|
|
|
4894
5500
|
this.aliases.set(qualified, exported.type);
|
|
4895
5501
|
}
|
|
4896
5502
|
}
|
|
5503
|
+
const asDefault = stmt.defaultImport && exports2.types.get("default");
|
|
5504
|
+
if (stmt.defaultImport && asDefault) {
|
|
5505
|
+
this.importedTypes.set(stmt.defaultImport.name, asDefault);
|
|
5506
|
+
this.aliases.set(stmt.defaultImport.name, asDefault.type);
|
|
5507
|
+
}
|
|
4897
5508
|
for (const s of stmt.specifiers) {
|
|
4898
5509
|
const exported = exports2.types.get(s.imported.name);
|
|
4899
5510
|
if (exported) {
|
|
@@ -4928,10 +5539,40 @@ var TypeAnalyzer = class {
|
|
|
4928
5539
|
if (stmt.type === "DeclareClassStatement") {
|
|
4929
5540
|
this.aliasDefs.set(stmt.name.name, { params: [], node: stmt.body, class: stmt });
|
|
4930
5541
|
}
|
|
5542
|
+
const declaration = stmt.type === "ExportStatement" || stmt.type === "ExportDefaultStatement" ? stmt.declaration : stmt;
|
|
5543
|
+
if (declaration.type === "ClassDeclaration") this.registerClass(declaration);
|
|
4931
5544
|
}
|
|
4932
5545
|
}
|
|
5546
|
+
/** A class written inside a function or a block names a type too — its
|
|
5547
|
+
* own instances', which its methods' `this` is annotated with. Type names
|
|
5548
|
+
* are one namespace here, so it is registered with the rest; only a
|
|
5549
|
+
* second class of the same name would notice. */
|
|
5550
|
+
registerNestedClasses() {
|
|
5551
|
+
walkNodes(this.program.body, (node) => {
|
|
5552
|
+
const record = node;
|
|
5553
|
+
if (record.type !== "ClassDeclaration") return;
|
|
5554
|
+
const declaration = node;
|
|
5555
|
+
if (this.aliasDefs.has(declaration.name.name)) return;
|
|
5556
|
+
this.registerClass(declaration);
|
|
5557
|
+
});
|
|
5558
|
+
}
|
|
5559
|
+
/** A class's name as a type. `node` is a placeholder: `resolveDef` and
|
|
5560
|
+
* `instantiateAlias` both go to the declaration itself. */
|
|
5561
|
+
registerClass(declaration) {
|
|
5562
|
+
this.aliasDefs.set(declaration.name.name, {
|
|
5563
|
+
params: declaration.typeParams,
|
|
5564
|
+
node: {
|
|
5565
|
+
type: "TableTypeNode",
|
|
5566
|
+
properties: [],
|
|
5567
|
+
line: declaration.line,
|
|
5568
|
+
column: declaration.column
|
|
5569
|
+
},
|
|
5570
|
+
runtimeClass: declaration
|
|
5571
|
+
});
|
|
5572
|
+
}
|
|
4933
5573
|
/** A non-generic definition's type. */
|
|
4934
5574
|
resolveDef(def) {
|
|
5575
|
+
if (def.runtimeClass) return this.instanceType(def.runtimeClass);
|
|
4935
5576
|
return def.class ? this.classType(def.class) : this.resolveType(def.node);
|
|
4936
5577
|
}
|
|
4937
5578
|
/** One type per class declaration, so every mention of a class is the same
|
|
@@ -4989,6 +5630,381 @@ var TypeAnalyzer = class {
|
|
|
4989
5630
|
if (this.program.body.statements.includes(stmt)) ownMembers();
|
|
4990
5631
|
return type;
|
|
4991
5632
|
}
|
|
5633
|
+
// ============================================================
|
|
5634
|
+
// `class ... end` — the runtime kind
|
|
5635
|
+
// ------------------------------------------------------------
|
|
5636
|
+
// A declaration says two things at once. Its *name as a type* is
|
|
5637
|
+
// the type of its instances, nominal the way `declare class` is:
|
|
5638
|
+
// only the class and the classes extending it produce one. Its
|
|
5639
|
+
// *name as a value* is the class table — the statics, the class
|
|
5640
|
+
// it extends (`ParentClass`), and the `new` that builds an
|
|
5641
|
+
// instance, which is an ordinary function and can be called as
|
|
5642
|
+
// one. An instance reaches its own class back through
|
|
5643
|
+
// `ClassObject`.
|
|
5644
|
+
//
|
|
5645
|
+
// Members are resolved into one shape, filled in two passes: the
|
|
5646
|
+
// fields first, then the functions. That order is what lets a
|
|
5647
|
+
// method body read `this.x` while the class it belongs to is
|
|
5648
|
+
// still being worked out.
|
|
5649
|
+
// ============================================================
|
|
5650
|
+
classShapes = /* @__PURE__ */ new WeakMap();
|
|
5651
|
+
classValues = /* @__PURE__ */ new WeakMap();
|
|
5652
|
+
/** Identity for a class written as a value, which has no name to be known
|
|
5653
|
+
* by: two of them are different types however alike they look. */
|
|
5654
|
+
classIdentities = /* @__PURE__ */ new WeakMap();
|
|
5655
|
+
classIdentityCount = 0;
|
|
5656
|
+
/** The class whose members are being read, so `super` knows its base. */
|
|
5657
|
+
currentClass;
|
|
5658
|
+
withClass(stmt, fn2) {
|
|
5659
|
+
const previous = this.currentClass;
|
|
5660
|
+
this.currentClass = stmt;
|
|
5661
|
+
try {
|
|
5662
|
+
return fn2();
|
|
5663
|
+
} finally {
|
|
5664
|
+
this.currentClass = previous;
|
|
5665
|
+
}
|
|
5666
|
+
}
|
|
5667
|
+
/** What the class is known by. A declaration is known by its name, the way
|
|
5668
|
+
* a `declare class` is — that is what makes it the same class across
|
|
5669
|
+
* modules. A class written as a value is known by where it is written. */
|
|
5670
|
+
classIdentity(stmt) {
|
|
5671
|
+
if (stmt.type === "ClassDeclaration") return stmt.name.name;
|
|
5672
|
+
let identity = this.classIdentities.get(stmt);
|
|
5673
|
+
if (!identity) {
|
|
5674
|
+
identity = `${stmt.name?.name ?? "class"}@${++this.classIdentityCount}`;
|
|
5675
|
+
this.classIdentities.set(stmt, identity);
|
|
5676
|
+
}
|
|
5677
|
+
return identity;
|
|
5678
|
+
}
|
|
5679
|
+
/** What it is *shown* as. A class written as a value has no name of its
|
|
5680
|
+
* own, so it borrows the one it is being bound to — `const Counter =
|
|
5681
|
+
* class ... end` reads as `Counter` everywhere. */
|
|
5682
|
+
className(stmt) {
|
|
5683
|
+
return stmt.name?.name ?? this.classDisplayNames.get(stmt) ?? "(class)";
|
|
5684
|
+
}
|
|
5685
|
+
classDisplayNames = /* @__PURE__ */ new WeakMap();
|
|
5686
|
+
/** `const Name = class ... end` — the name the class will be known by. */
|
|
5687
|
+
nameClassExpressions(stmt) {
|
|
5688
|
+
stmt.names.forEach((target, i) => {
|
|
5689
|
+
const value = stmt.init[i];
|
|
5690
|
+
if (target.type === "IdentifierPattern" && value?.type === "ClassExpression" && !value.name) {
|
|
5691
|
+
this.classDisplayNames.set(value, target.name);
|
|
5692
|
+
}
|
|
5693
|
+
});
|
|
5694
|
+
}
|
|
5695
|
+
classTypeParams(stmt) {
|
|
5696
|
+
return stmt.type === "ClassDeclaration" ? stmt.typeParams : [];
|
|
5697
|
+
}
|
|
5698
|
+
/** The class a declaration extends, when it is one written in this file.
|
|
5699
|
+
* An imported class is reached through its type and its value instead. */
|
|
5700
|
+
superDecl(stmt) {
|
|
5701
|
+
if (!stmt.superclass) return void 0;
|
|
5702
|
+
const base = this.aliasDefs.get(stmt.superclass.name)?.runtimeClass;
|
|
5703
|
+
return base && base !== stmt && !this.extendsThrough(base, stmt) ? base : void 0;
|
|
5704
|
+
}
|
|
5705
|
+
/** Does `from` reach `target` by `extends`? Guards against a cycle turning
|
|
5706
|
+
* resolution into a loop. */
|
|
5707
|
+
extendsThrough(from, target) {
|
|
5708
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5709
|
+
for (let cls = from; cls && !seen.has(cls); ) {
|
|
5710
|
+
if (cls === target) return true;
|
|
5711
|
+
seen.add(cls);
|
|
5712
|
+
cls = cls.superclass ? this.aliasDefs.get(cls.superclass.name)?.runtimeClass : void 0;
|
|
5713
|
+
}
|
|
5714
|
+
return false;
|
|
5715
|
+
}
|
|
5716
|
+
/** The instance type of what `stmt` extends, with the arguments it was
|
|
5717
|
+
* extended with filled in — a class in this file, or any class type a
|
|
5718
|
+
* name in scope stands for (an imported one). */
|
|
5719
|
+
baseInstance(stmt) {
|
|
5720
|
+
if (!stmt.superclass) return void 0;
|
|
5721
|
+
const written = (stmt.superArguments ?? []).map((argument) => this.resolveType(argument));
|
|
5722
|
+
if (this.aliasDefs.get(stmt.superclass.name)?.runtimeClass) {
|
|
5723
|
+
const local = this.superDecl(stmt);
|
|
5724
|
+
if (!local) return void 0;
|
|
5725
|
+
const base = this.instanceType(local);
|
|
5726
|
+
const params = this.classTypeParams(local);
|
|
5727
|
+
if (!params.length) return base;
|
|
5728
|
+
const applied = substitute(base, this.bindTypeArguments(params, written));
|
|
5729
|
+
return applied.kind === "object" ? applied : void 0;
|
|
5730
|
+
}
|
|
5731
|
+
const imported = this.importedTypes.get(stmt.superclass.name);
|
|
5732
|
+
const named = imported ? this.importedType(imported, stmt.superArguments ?? []) : this.aliases.get(stmt.superclass.name);
|
|
5733
|
+
return named && isClassType(named) ? named : void 0;
|
|
5734
|
+
}
|
|
5735
|
+
/** One instance type per declaration, so every mention of the class is the
|
|
5736
|
+
* same object — the `this` of its own methods included. Members are read
|
|
5737
|
+
* lazily for the reason `declare class` reads them lazily: a class can
|
|
5738
|
+
* name itself, and two classes can name each other. */
|
|
5739
|
+
instanceType(stmt) {
|
|
5740
|
+
const cached = this.instanceTypes.get(stmt);
|
|
5741
|
+
if (cached) return cached;
|
|
5742
|
+
const name = this.className(stmt);
|
|
5743
|
+
const identity = this.classIdentity(stmt);
|
|
5744
|
+
const params = this.classTypeParams(stmt);
|
|
5745
|
+
const type = { kind: "object", name };
|
|
5746
|
+
this.instanceTypes.set(stmt, type);
|
|
5747
|
+
const own = params.map((p) => typeParam(p.name, p.constraint ? this.resolveType(p.constraint) : void 0));
|
|
5748
|
+
const info = () => {
|
|
5749
|
+
const base = this.baseInstance(stmt)?.class;
|
|
5750
|
+
const typeArguments = new Map(base?.typeArguments ?? []);
|
|
5751
|
+
if (own.length) typeArguments.set(identity, own);
|
|
5752
|
+
return {
|
|
5753
|
+
name: identity,
|
|
5754
|
+
superclass: base?.name,
|
|
5755
|
+
ancestors: [identity, ...base?.ancestors ?? []],
|
|
5756
|
+
typeArguments: typeArguments.size ? typeArguments : void 0
|
|
5757
|
+
};
|
|
5758
|
+
};
|
|
5759
|
+
Object.defineProperties(type, {
|
|
5760
|
+
properties: {
|
|
5761
|
+
enumerable: true,
|
|
5762
|
+
get: () => {
|
|
5763
|
+
const shape = this.shapeOf(stmt);
|
|
5764
|
+
const base = this.baseInstance(stmt)?.properties;
|
|
5765
|
+
const members = base?.size ? new Map([...base, ...shape.instance]) : new Map(shape.instance);
|
|
5766
|
+
members.set("ClassObject", { type: this.classValueType(stmt), optional: false, readonly: true });
|
|
5767
|
+
return members;
|
|
5768
|
+
}
|
|
5769
|
+
},
|
|
5770
|
+
class: { enumerable: true, get: info }
|
|
5771
|
+
});
|
|
5772
|
+
return type;
|
|
5773
|
+
}
|
|
5774
|
+
/** `Box<T>` as its own methods see it — a reference, not the object, so
|
|
5775
|
+
* substituting the arguments in does not have to walk the class. */
|
|
5776
|
+
selfTypeOf(stmt) {
|
|
5777
|
+
const params = this.classTypeParams(stmt);
|
|
5778
|
+
if (!params.length || stmt.type !== "ClassDeclaration") return this.instanceType(stmt);
|
|
5779
|
+
return {
|
|
5780
|
+
kind: "genericRef",
|
|
5781
|
+
name: stmt.name.name,
|
|
5782
|
+
typeArguments: params.map((p) => typeParam(p.name, p.constraint ? this.resolveType(p.constraint) : void 0))
|
|
5783
|
+
};
|
|
5784
|
+
}
|
|
5785
|
+
/** The class table: the statics, what it inherits from the class it
|
|
5786
|
+
* extends, `ParentClass`, `ClassObject`, and `new`. */
|
|
5787
|
+
classValueType(stmt) {
|
|
5788
|
+
const cached = this.classValues.get(stmt);
|
|
5789
|
+
if (cached) return cached;
|
|
5790
|
+
const type = objectType([]);
|
|
5791
|
+
this.classValues.set(stmt, type);
|
|
5792
|
+
type.name = `typeof ${this.className(stmt)}`;
|
|
5793
|
+
const shape = this.shapeOf(stmt);
|
|
5794
|
+
const local = this.superDecl(stmt);
|
|
5795
|
+
const parent = local ? this.classValueType(local) : stmt.superclass ? this.classStaticsByNameType(stmt.superclass.name) : void 0;
|
|
5796
|
+
if (parent?.kind === "object") {
|
|
5797
|
+
for (const [key, property] of parent.properties) {
|
|
5798
|
+
if (!CLASS_LINKS.has(key)) type.properties.set(key, property);
|
|
5799
|
+
}
|
|
5800
|
+
}
|
|
5801
|
+
for (const [key, property] of shape.statics) type.properties.set(key, property);
|
|
5802
|
+
const constructor = this.constructorType(stmt);
|
|
5803
|
+
const params = this.classTypeParams(stmt);
|
|
5804
|
+
type.properties.set("new", {
|
|
5805
|
+
type: fn(
|
|
5806
|
+
constructor?.params.filter((p) => p.name !== "this") ?? [],
|
|
5807
|
+
this.selfTypeOf(stmt),
|
|
5808
|
+
constructor?.varargs,
|
|
5809
|
+
params.map((p) => p.name)
|
|
5810
|
+
),
|
|
5811
|
+
optional: false,
|
|
5812
|
+
readonly: true
|
|
5813
|
+
});
|
|
5814
|
+
type.properties.set("ParentClass", { type: parent ?? nilType, optional: false, readonly: true });
|
|
5815
|
+
return type;
|
|
5816
|
+
}
|
|
5817
|
+
/** The value side of a class named by a binding rather than by a
|
|
5818
|
+
* declaration in this file — an imported one. */
|
|
5819
|
+
classStaticsByNameType(name) {
|
|
5820
|
+
const id = this.classBindingByName(name);
|
|
5821
|
+
const declared = id !== void 0 ? this.bindingType.get(id) : void 0;
|
|
5822
|
+
return declared?.kind === "object" ? declared : void 0;
|
|
5823
|
+
}
|
|
5824
|
+
/** The binding a class name stands for, wherever it was declared. */
|
|
5825
|
+
classBindingByName(name) {
|
|
5826
|
+
for (const [id, binding] of this.scopes.bindings) {
|
|
5827
|
+
if (binding.name === name && binding.declaredBy === "class") return id;
|
|
5828
|
+
}
|
|
5829
|
+
return this.scopes.globalsByName.get(name);
|
|
5830
|
+
}
|
|
5831
|
+
/** What `super(...)` takes: the constructor of the class `stmt` extends,
|
|
5832
|
+
* its own skipped. */
|
|
5833
|
+
baseConstructorType(stmt) {
|
|
5834
|
+
return this.constructorType(stmt, /* @__PURE__ */ new Set([stmt]));
|
|
5835
|
+
}
|
|
5836
|
+
/** A class's constructor signature — its own, or the one it inherits. */
|
|
5837
|
+
constructorType(stmt, seen = /* @__PURE__ */ new Set()) {
|
|
5838
|
+
if (seen.has(stmt)) return void 0;
|
|
5839
|
+
seen.add(stmt);
|
|
5840
|
+
const own = this.shapeOf(stmt).ctor;
|
|
5841
|
+
if (own) return own;
|
|
5842
|
+
const local = this.superDecl(stmt);
|
|
5843
|
+
if (local) return this.constructorType(local, seen);
|
|
5844
|
+
const parent = stmt.superclass ? this.classStaticsByNameType(stmt.superclass.name) : void 0;
|
|
5845
|
+
const inherited = parent && this.overloadsOf(this.propertyType(parent, "new"))[0];
|
|
5846
|
+
return inherited;
|
|
5847
|
+
}
|
|
5848
|
+
/** `super` as a value: the base class's instance members with the `this`
|
|
5849
|
+
* slot already filled, because `super.m(a)` passes this instance. */
|
|
5850
|
+
superType(stmt) {
|
|
5851
|
+
const base = this.baseInstance(stmt);
|
|
5852
|
+
if (!base) return anyType;
|
|
5853
|
+
const entries = [];
|
|
5854
|
+
for (const [name, property] of base.properties) {
|
|
5855
|
+
const bound = this.overloadsOf(property.type);
|
|
5856
|
+
entries.push([name, bound.length ? {
|
|
5857
|
+
...property,
|
|
5858
|
+
type: intersection(bound.map((f) => this.takesSelf(f) ? fn(f.params.slice(1), f.returns, f.varargs, f.typeParams) : f))
|
|
5859
|
+
} : property]);
|
|
5860
|
+
}
|
|
5861
|
+
return objectType(entries);
|
|
5862
|
+
}
|
|
5863
|
+
shapeOf(stmt) {
|
|
5864
|
+
const cached = this.classShapes.get(stmt);
|
|
5865
|
+
if (cached) return cached;
|
|
5866
|
+
const shape = { instance: /* @__PURE__ */ new Map(), statics: /* @__PURE__ */ new Map(), filling: true };
|
|
5867
|
+
this.classShapes.set(stmt, shape);
|
|
5868
|
+
const wasEmitting = this.emitDiagnostics;
|
|
5869
|
+
this.emitDiagnostics = false;
|
|
5870
|
+
try {
|
|
5871
|
+
this.withClass(stmt, () => this.withTypeParams(this.classTypeParams(stmt), () => {
|
|
5872
|
+
this.withSelfType(this.selfTypeOf(stmt), () => this.fillShape(stmt, shape));
|
|
5873
|
+
}));
|
|
5874
|
+
} finally {
|
|
5875
|
+
this.emitDiagnostics = wasEmitting;
|
|
5876
|
+
shape.filling = false;
|
|
5877
|
+
}
|
|
5878
|
+
return shape;
|
|
5879
|
+
}
|
|
5880
|
+
fillShape(stmt, shape) {
|
|
5881
|
+
const put = (isStatic, name, property) => {
|
|
5882
|
+
(isStatic ? shape.statics : shape.instance).set(name, property);
|
|
5883
|
+
};
|
|
5884
|
+
for (const member of stmt.members) {
|
|
5885
|
+
if (member.type !== "ClassField") continue;
|
|
5886
|
+
const type = member.typeAnnotation ? this.resolveType(member.typeAnnotation) : member.init ? widen(this.infer(member.init, /* @__PURE__ */ new Map())) : anyType;
|
|
5887
|
+
put(member.isStatic, member.name.name, { type, optional: false });
|
|
5888
|
+
}
|
|
5889
|
+
for (const member of stmt.members) {
|
|
5890
|
+
switch (member.type) {
|
|
5891
|
+
case "ClassField":
|
|
5892
|
+
break;
|
|
5893
|
+
case "ClassMethod": {
|
|
5894
|
+
this.paramsFromSignatures(member.func, member.signatures);
|
|
5895
|
+
const type = member.signatures?.length ? intersection(member.signatures.map((sig) => this.signatureToFnType(sig))) : this.inferFunctionBody(member.func, /* @__PURE__ */ new Map());
|
|
5896
|
+
put(member.isStatic, member.name.name, { type, optional: false });
|
|
5897
|
+
break;
|
|
5898
|
+
}
|
|
5899
|
+
case "ClassAccessor": {
|
|
5900
|
+
const signature = this.inferFunctionBody(member.func, /* @__PURE__ */ new Map());
|
|
5901
|
+
if (signature.kind !== "function") break;
|
|
5902
|
+
const target = member.isStatic ? shape.statics : shape.instance;
|
|
5903
|
+
const existing = target.get(member.name.name);
|
|
5904
|
+
if (member.kind === "get") {
|
|
5905
|
+
put(member.isStatic, member.name.name, {
|
|
5906
|
+
type: signature.returns,
|
|
5907
|
+
optional: false,
|
|
5908
|
+
readonly: existing === void 0 || existing.readonly !== false
|
|
5909
|
+
});
|
|
5910
|
+
} else {
|
|
5911
|
+
put(member.isStatic, member.name.name, {
|
|
5912
|
+
type: existing?.type ?? signature.params[signature.params.length - 1]?.type ?? anyType,
|
|
5913
|
+
optional: false,
|
|
5914
|
+
readonly: false
|
|
5915
|
+
});
|
|
5916
|
+
}
|
|
5917
|
+
break;
|
|
5918
|
+
}
|
|
5919
|
+
case "ClassConstructor": {
|
|
5920
|
+
const signature = this.inferFunctionBody(member.func, /* @__PURE__ */ new Map());
|
|
5921
|
+
if (signature.kind === "function") shape.ctor = signature;
|
|
5922
|
+
break;
|
|
5923
|
+
}
|
|
5924
|
+
}
|
|
5925
|
+
}
|
|
5926
|
+
}
|
|
5927
|
+
/** Bind the class's value, and check what its members say. Shared by the
|
|
5928
|
+
* declaration and the expression forms. */
|
|
5929
|
+
visitClass(stmt, env) {
|
|
5930
|
+
this.checkClassDeclaration(stmt);
|
|
5931
|
+
const value = this.classValueType(stmt);
|
|
5932
|
+
this.withClass(stmt, () => this.withTypeParams(this.classTypeParams(stmt), () => {
|
|
5933
|
+
this.withSelfType(this.selfTypeOf(stmt), () => {
|
|
5934
|
+
for (const member of stmt.members) {
|
|
5935
|
+
if (member.type === "ClassField") {
|
|
5936
|
+
if (!member.init) continue;
|
|
5937
|
+
const declared = member.typeAnnotation ? this.resolveType(member.typeAnnotation) : void 0;
|
|
5938
|
+
if (declared) this.applyContext(member.init, declared);
|
|
5939
|
+
const actual = this.infer(member.init, env);
|
|
5940
|
+
if (declared && this.emitDiagnostics && !isAssignable(actual, declared)) {
|
|
5941
|
+
this.diagnostics.push({
|
|
5942
|
+
node: member.init,
|
|
5943
|
+
message: `Type '${formatType(actual)}' is not assignable to type '${formatType(declared)}'`
|
|
5944
|
+
});
|
|
5945
|
+
}
|
|
5946
|
+
continue;
|
|
5947
|
+
}
|
|
5948
|
+
this.checkParamOrder(member.func.params, member);
|
|
5949
|
+
for (const signature of member.signatures ?? []) {
|
|
5950
|
+
this.checkParamOrder(signature.params, member);
|
|
5951
|
+
}
|
|
5952
|
+
this.visitFunctionBody(member.func, env);
|
|
5953
|
+
}
|
|
5954
|
+
});
|
|
5955
|
+
}));
|
|
5956
|
+
return value;
|
|
5957
|
+
}
|
|
5958
|
+
/** What a class gets wrong, reported where it is written. */
|
|
5959
|
+
checkClassDeclaration(stmt) {
|
|
5960
|
+
if (!this.emitDiagnostics) return;
|
|
5961
|
+
const report = (node, message) => {
|
|
5962
|
+
this.diagnostics.push({ node, message });
|
|
5963
|
+
};
|
|
5964
|
+
const name = this.className(stmt);
|
|
5965
|
+
if (stmt.superclass) {
|
|
5966
|
+
const local = this.aliasDefs.get(stmt.superclass.name)?.runtimeClass;
|
|
5967
|
+
if (local && this.extendsThrough(local, stmt)) {
|
|
5968
|
+
report(stmt.superclass, `'${name}' cannot extend itself`);
|
|
5969
|
+
} else if (!this.baseInstance(stmt)) {
|
|
5970
|
+
const known = this.aliases.has(stmt.superclass.name) || this.importedTypes.has(stmt.superclass.name);
|
|
5971
|
+
report(stmt.superclass, known ? `'${stmt.superclass.name}' is not a class; a class can only extend another class` : `Cannot find class '${stmt.superclass.name}'`);
|
|
5972
|
+
}
|
|
5973
|
+
}
|
|
5974
|
+
for (const member of stmt.members) {
|
|
5975
|
+
if (member.type === "ClassConstructor") continue;
|
|
5976
|
+
if (CLASS_RESERVED.has(member.name.name)) {
|
|
5977
|
+
report(member.name, `'${member.name.name}' is what the compiler calls part of a class; a member cannot be named that`);
|
|
5978
|
+
}
|
|
5979
|
+
}
|
|
5980
|
+
const seen = /* @__PURE__ */ new Map();
|
|
5981
|
+
for (const member of stmt.members) {
|
|
5982
|
+
if (member.type === "ClassConstructor") {
|
|
5983
|
+
if (seen.has("constructor")) report(member, "A class has one constructor");
|
|
5984
|
+
seen.set("constructor", member.type);
|
|
5985
|
+
continue;
|
|
5986
|
+
}
|
|
5987
|
+
const key = `${member.isStatic ? "static " : ""}${member.name.name}`;
|
|
5988
|
+
const before = seen.get(key);
|
|
5989
|
+
const pair = member.type === "ClassAccessor" && before === "ClassAccessor";
|
|
5990
|
+
if (before !== void 0 && !pair) {
|
|
5991
|
+
report(member.name, `'${member.name.name}' is declared twice in class '${name}'`);
|
|
5992
|
+
}
|
|
5993
|
+
seen.set(key, member.type);
|
|
5994
|
+
}
|
|
5995
|
+
const constructor = stmt.members.find((m) => m.type === "ClassConstructor");
|
|
5996
|
+
if (stmt.superclass && this.baseInstance(stmt) && constructor && !callsSuper(constructor.func.body)) {
|
|
5997
|
+
report(constructor, `'${name}' extends '${stmt.superclass.name}', so its constructor must call 'super(...)'`);
|
|
5998
|
+
}
|
|
5999
|
+
const assigned = constructor ? assignedFields(constructor.func.body) : /* @__PURE__ */ new Set();
|
|
6000
|
+
for (const member of stmt.members) {
|
|
6001
|
+
if (member.type !== "ClassField" || member.isStatic || member.init) continue;
|
|
6002
|
+
if (assigned.has(member.name.name)) continue;
|
|
6003
|
+
const type = member.typeAnnotation ? this.withTypeParams(this.classTypeParams(stmt), () => this.resolveType(member.typeAnnotation)) : anyType;
|
|
6004
|
+
if (isAssignable(nilType, type)) continue;
|
|
6005
|
+
report(member.name, `'${member.name.name}' has no value: give it one, assign it in the constructor, or let its type admit nil`);
|
|
6006
|
+
}
|
|
6007
|
+
}
|
|
4992
6008
|
/** `extends` must name a class, and the chain must end. */
|
|
4993
6009
|
checkClass(stmt) {
|
|
4994
6010
|
if (!stmt.superclass || !this.emitDiagnostics) return;
|
|
@@ -5245,6 +6261,7 @@ var TypeAnalyzer = class {
|
|
|
5245
6261
|
instantiateAlias(def, args) {
|
|
5246
6262
|
if (this.instantiationDepth > 20) return unknownType;
|
|
5247
6263
|
const subst = this.bindTypeArguments(def.params, args);
|
|
6264
|
+
if (def.runtimeClass) return substitute(this.instanceType(def.runtimeClass), subst);
|
|
5248
6265
|
this.instantiationDepth++;
|
|
5249
6266
|
try {
|
|
5250
6267
|
const body = this.withTypeParams(def.params, () => this.resolveType(def.node));
|
|
@@ -5389,15 +6406,17 @@ var TypeAnalyzer = class {
|
|
|
5389
6406
|
case "FunctionTypeNode": {
|
|
5390
6407
|
const names = node.generics.map((g) => g.name);
|
|
5391
6408
|
return this.withTypeParams(node.generics, () => {
|
|
5392
|
-
const params = node.params.map((p) => ({
|
|
6409
|
+
const params = node.params.filter((p) => !p.rest).map((p) => ({
|
|
5393
6410
|
name: p.name,
|
|
5394
6411
|
type: p.optional ? optional(this.resolveType(p.typeAnnotation)) : this.resolveType(p.typeAnnotation),
|
|
5395
6412
|
optional: p.optional
|
|
5396
6413
|
}));
|
|
6414
|
+
const restParam = node.params.find((p) => p.rest);
|
|
6415
|
+
const restElement = restParam ? this.resolveType(restParam.typeAnnotation) : void 0;
|
|
5397
6416
|
return this.withTypeParamDefaults(fn(
|
|
5398
6417
|
params,
|
|
5399
6418
|
this.resolveType(node.returnType),
|
|
5400
|
-
node.hasVarargs ? node.varargType ? this.resolveType(node.varargType) : anyType : void 0,
|
|
6419
|
+
restParam ? restElement?.kind === "array" ? restElement.element : unknownType : node.hasVarargs ? node.varargType ? this.resolveType(node.varargType) : anyType : void 0,
|
|
5401
6420
|
names,
|
|
5402
6421
|
this.resolvePredicate(node.predicate, params)
|
|
5403
6422
|
), node.generics);
|
|
@@ -5591,7 +6610,10 @@ var TypeAnalyzer = class {
|
|
|
5591
6610
|
accessType(obj, index) {
|
|
5592
6611
|
if (index.kind === "union") return union(index.types.map((m) => this.accessType(obj, m)));
|
|
5593
6612
|
if (index.kind === "literal" && typeof index.value === "string") {
|
|
5594
|
-
|
|
6613
|
+
const member = this.propertyType(obj, index.value);
|
|
6614
|
+
if (member.kind !== "unknown") return member;
|
|
6615
|
+
const t = this.expand(obj);
|
|
6616
|
+
return t.kind === "object" && !t.class && !t.indexer ? nilType : member;
|
|
5595
6617
|
}
|
|
5596
6618
|
return this.indexedType(obj, index);
|
|
5597
6619
|
}
|
|
@@ -5744,12 +6766,13 @@ var TypeAnalyzer = class {
|
|
|
5744
6766
|
visitStatement(stmt, env) {
|
|
5745
6767
|
switch (stmt.type) {
|
|
5746
6768
|
case "VariableDeclaration": {
|
|
6769
|
+
this.nameClassExpressions(stmt);
|
|
5747
6770
|
stmt.names.forEach((target, i) => {
|
|
5748
6771
|
if (target.type === "IdentifierPattern" && target.typeAnnotation && stmt.init[i]) {
|
|
5749
6772
|
this.applyContext(stmt.init[i], this.resolveType(target.typeAnnotation));
|
|
5750
6773
|
}
|
|
5751
6774
|
});
|
|
5752
|
-
const { types: valueTypes, sources } = this.valueList(stmt.init, env);
|
|
6775
|
+
const { types: valueTypes, sources } = this.valueList(stmt.init, env, stmt.names.length);
|
|
5753
6776
|
stmt.names.forEach((target, i) => {
|
|
5754
6777
|
const inferred = valueTypes[i] ?? (stmt.init.length ? unknownType : nilType);
|
|
5755
6778
|
const source = sources[i];
|
|
@@ -5774,6 +6797,15 @@ var TypeAnalyzer = class {
|
|
|
5774
6797
|
});
|
|
5775
6798
|
return;
|
|
5776
6799
|
}
|
|
6800
|
+
case "ClassDeclaration": {
|
|
6801
|
+
const id = this.bindingIdByName(stmt.name.name, stmt.name);
|
|
6802
|
+
const value = this.visitClass(stmt, env);
|
|
6803
|
+
if (id !== void 0) {
|
|
6804
|
+
this.bindingType.set(id, value);
|
|
6805
|
+
this.setBinding(env, id, value);
|
|
6806
|
+
}
|
|
6807
|
+
return;
|
|
6808
|
+
}
|
|
5777
6809
|
case "FunctionDeclaration": {
|
|
5778
6810
|
this.checkParamOrder(stmt.func.params, stmt);
|
|
5779
6811
|
for (const sig of stmt.signatures ?? []) this.checkParamOrder(sig.params, stmt);
|
|
@@ -5829,7 +6861,7 @@ var TypeAnalyzer = class {
|
|
|
5829
6861
|
if (id !== void 0 && this.annotated.has(id)) this.applyContext(value, this.bindingType.get(id));
|
|
5830
6862
|
}
|
|
5831
6863
|
});
|
|
5832
|
-
const { types: valueTypes, sources } = this.valueList(stmt.values, env);
|
|
6864
|
+
const { types: valueTypes, sources } = this.valueList(stmt.values, env, stmt.targets.length);
|
|
5833
6865
|
stmt.targets.forEach((target, i) => {
|
|
5834
6866
|
const vt = valueTypes[i] ?? unknownType;
|
|
5835
6867
|
const source = sources[i];
|
|
@@ -5937,7 +6969,8 @@ var TypeAnalyzer = class {
|
|
|
5937
6969
|
stmt.arguments.forEach((a, i) => this.applyContext(a, declared.elements[i]));
|
|
5938
6970
|
}
|
|
5939
6971
|
}
|
|
5940
|
-
const
|
|
6972
|
+
const want = declared?.kind === "tuple" && declared.isPack ? declared.elements.length : 0;
|
|
6973
|
+
const { types, sources } = this.valueList(stmt.arguments, env, want);
|
|
5941
6974
|
this.checkReturn(stmt, declared, types, sources, env);
|
|
5942
6975
|
if (this.returnTypes) {
|
|
5943
6976
|
this.returnTypes.push(stmt.arguments.length === 0 ? nilType : types.length === 1 ? types[0] : tuple([...types], true));
|
|
@@ -5948,7 +6981,8 @@ var TypeAnalyzer = class {
|
|
|
5948
6981
|
this.visitStatement(stmt.declaration, env);
|
|
5949
6982
|
return;
|
|
5950
6983
|
case "ExportDefaultStatement":
|
|
5951
|
-
this.
|
|
6984
|
+
if (stmt.declaration.type === "ClassDeclaration") this.visitStatement(stmt.declaration, env);
|
|
6985
|
+
else this.infer(stmt.declaration, env);
|
|
5952
6986
|
return;
|
|
5953
6987
|
case "ExportNamedStatement": {
|
|
5954
6988
|
if (stmt.source) {
|
|
@@ -6027,12 +7061,35 @@ var TypeAnalyzer = class {
|
|
|
6027
7061
|
* `sources` maps each produced value back to the expression it came from
|
|
6028
7062
|
* (undefined for the 2nd and later values of a multi-value call), so the
|
|
6029
7063
|
* caller can still do contextual typing against the written expression. */
|
|
6030
|
-
valueList(exprs, env) {
|
|
7064
|
+
valueList(exprs, env, want = 0) {
|
|
6031
7065
|
const types = [];
|
|
6032
7066
|
const sources = [];
|
|
6033
7067
|
exprs.forEach((e, i) => {
|
|
6034
7068
|
const t = this.infer(e, env);
|
|
6035
7069
|
const last = i === exprs.length - 1;
|
|
7070
|
+
if (e.type === "SpreadElement") {
|
|
7071
|
+
const held = this.typeOf.get(e.argument);
|
|
7072
|
+
const expanded = held && this.expand(held);
|
|
7073
|
+
if (expanded?.kind === "tuple") {
|
|
7074
|
+
for (const element of expanded.elements) {
|
|
7075
|
+
types.push(element);
|
|
7076
|
+
sources.push(e);
|
|
7077
|
+
}
|
|
7078
|
+
return;
|
|
7079
|
+
}
|
|
7080
|
+
do {
|
|
7081
|
+
types.push(t);
|
|
7082
|
+
sources.push(e);
|
|
7083
|
+
} while (last && types.length < want);
|
|
7084
|
+
return;
|
|
7085
|
+
}
|
|
7086
|
+
if (last && e.type === "VarargExpression") {
|
|
7087
|
+
do {
|
|
7088
|
+
types.push(t);
|
|
7089
|
+
sources.push(types.length - 1 === i ? e : void 0);
|
|
7090
|
+
} while (types.length < want);
|
|
7091
|
+
return;
|
|
7092
|
+
}
|
|
6036
7093
|
if (last && t.kind === "tuple" && t.isPack && producesMultipleValues(e)) {
|
|
6037
7094
|
t.elements.forEach((el, j) => {
|
|
6038
7095
|
types.push(el);
|
|
@@ -6121,7 +7178,8 @@ var TypeAnalyzer = class {
|
|
|
6121
7178
|
/** A parameter's type: annotation, else a shape synthesized from a
|
|
6122
7179
|
* destructuring pattern, else inferred from its default, else `any`. */
|
|
6123
7180
|
paramType(p, env) {
|
|
6124
|
-
|
|
7181
|
+
const receiver = p.name === "self" || p.name === "this";
|
|
7182
|
+
if (!p.typeAnnotation && !p.pattern && !p.default && receiver && this.selfType) {
|
|
6125
7183
|
return this.selfType;
|
|
6126
7184
|
}
|
|
6127
7185
|
if (p.typeAnnotation) {
|
|
@@ -6129,6 +7187,7 @@ var TypeAnalyzer = class {
|
|
|
6129
7187
|
if (p.default) this.applyContext(p.default, t);
|
|
6130
7188
|
return p.optional ? optional(t) : t;
|
|
6131
7189
|
}
|
|
7190
|
+
if (p.rest) return arrayOf(unknownType);
|
|
6132
7191
|
if (p.pattern) return this.patternToType(p.pattern, env);
|
|
6133
7192
|
if (p.default) return widen(this.infer(p.default, env));
|
|
6134
7193
|
return this.contextualParams.get(p) ?? anyType;
|
|
@@ -6149,6 +7208,11 @@ var TypeAnalyzer = class {
|
|
|
6149
7208
|
this.expectedTypeOf.set(e, expected);
|
|
6150
7209
|
if (e.type === "ArrayExpression") return this.applyArrayContext(e, expected);
|
|
6151
7210
|
if (e.type === "TableExpression") return this.applyTableContext(e, expected);
|
|
7211
|
+
if (e.type === "BinaryExpression" && (e.operator === "or" || e.operator === "and")) {
|
|
7212
|
+
if (e.operator === "or") this.applyContext(e.left, expected);
|
|
7213
|
+
this.applyContext(e.right, expected);
|
|
7214
|
+
return;
|
|
7215
|
+
}
|
|
6152
7216
|
if (e.type !== "FunctionExpression") return;
|
|
6153
7217
|
const members = expected.kind === "union" ? expected.types : [expected];
|
|
6154
7218
|
const signatures = members.flatMap((m) => this.overloadsOf(this.expand(m)));
|
|
@@ -6174,7 +7238,7 @@ var TypeAnalyzer = class {
|
|
|
6174
7238
|
const target = this.expectedMembers(expected).find((m) => m.kind === "array" || m.kind === "tuple");
|
|
6175
7239
|
if (!target) return;
|
|
6176
7240
|
if (!e.elements.length) {
|
|
6177
|
-
|
|
7241
|
+
this.contextualArrays.set(e, target);
|
|
6178
7242
|
return;
|
|
6179
7243
|
}
|
|
6180
7244
|
e.elements.forEach((element, i) => {
|
|
@@ -6234,13 +7298,26 @@ var TypeAnalyzer = class {
|
|
|
6234
7298
|
}
|
|
6235
7299
|
return tuple(target.elements.map((el) => el ? leaf(el.value, el.default) : anyType));
|
|
6236
7300
|
}
|
|
7301
|
+
/** What a vararg function's `...` holds, one value at a time.
|
|
7302
|
+
*
|
|
7303
|
+
* Written three ways, and they mean the same call: `...` says nothing,
|
|
7304
|
+
* `...: T` says each value is a `T`, and `...rest: T[]` collects them
|
|
7305
|
+
* into an array the body reads by name. Only the last changes what the
|
|
7306
|
+
* body sees — the signature is the same either way. */
|
|
7307
|
+
varargElement(func) {
|
|
7308
|
+
if (!func.hasVarargs) return void 0;
|
|
7309
|
+
const rest = func.params.find((p) => p.rest);
|
|
7310
|
+
if (!rest) return func.varargTypeAnnotation ? this.resolveType(func.varargTypeAnnotation) : anyType;
|
|
7311
|
+
const declared = rest.typeAnnotation ? this.resolveType(rest.typeAnnotation) : void 0;
|
|
7312
|
+
return declared?.kind === "array" ? declared.element : unknownType;
|
|
7313
|
+
}
|
|
6237
7314
|
/** The type of `...` in each function body being walked. */
|
|
6238
7315
|
varargs = [];
|
|
6239
7316
|
/** What each function body being walked declared it returns. */
|
|
6240
7317
|
declaredReturns = [];
|
|
6241
7318
|
/** Run `body` with `...` and `return` as `func` declares them. */
|
|
6242
7319
|
withVarargs(func, body) {
|
|
6243
|
-
this.varargs.push(
|
|
7320
|
+
this.varargs.push(this.varargElement(func));
|
|
6244
7321
|
this.declaredReturns.push(func.predicate ? booleanType : func.returnType ? this.resolveType(func.returnType) : void 0);
|
|
6245
7322
|
try {
|
|
6246
7323
|
return body();
|
|
@@ -6321,6 +7398,13 @@ var TypeAnalyzer = class {
|
|
|
6321
7398
|
const param = p.type.kind === "typeParam" && p.type.constraint ? { ...p.type, constraint: this.reduceType(p.type.constraint) } : p.type;
|
|
6322
7399
|
unify(p.type, keepsLiterals(param) ? arg : widen(arg), vars, subst);
|
|
6323
7400
|
});
|
|
7401
|
+
if (f.varargs) {
|
|
7402
|
+
const keeps = keepsLiterals(f.varargs);
|
|
7403
|
+
for (let i = f.params.length; i < argTypes.length; i++) {
|
|
7404
|
+
const arg = argTypes[i];
|
|
7405
|
+
if (arg !== void 0) unify(f.varargs, keeps ? arg : widen(arg), vars, subst);
|
|
7406
|
+
}
|
|
7407
|
+
}
|
|
6324
7408
|
for (const name of f.typeParams ?? []) {
|
|
6325
7409
|
if (!subst.has(name)) subst.set(name, f.typeParamDefaults?.[name] ?? unknownType);
|
|
6326
7410
|
}
|
|
@@ -6345,11 +7429,11 @@ var TypeAnalyzer = class {
|
|
|
6345
7429
|
* after every concrete signature has been tried. That ordering is what
|
|
6346
7430
|
* lets `typeof` declare `(v: number) -> "number"` alongside a trailing
|
|
6347
7431
|
* `<T>(v: T) -> string` and still pick the precise one. */
|
|
6348
|
-
pickOverload(fns, argTypes, argsFor) {
|
|
7432
|
+
pickOverload(fns, argTypes, argsFor, spread) {
|
|
6349
7433
|
for (const generic of [false, true]) {
|
|
6350
7434
|
for (const f of fns) {
|
|
6351
7435
|
if ((f.typeParams?.length ?? 0) > 0 !== generic) continue;
|
|
6352
|
-
if (this.overloadAccepts(f, argsFor ? argsFor(f) : argTypes)) return f;
|
|
7436
|
+
if (this.overloadAccepts(f, argsFor ? argsFor(f) : argTypes, spread)) return f;
|
|
6353
7437
|
}
|
|
6354
7438
|
}
|
|
6355
7439
|
return void 0;
|
|
@@ -6384,12 +7468,27 @@ var TypeAnalyzer = class {
|
|
|
6384
7468
|
* own type parameters stand for what the call would infer, so each is
|
|
6385
7469
|
* checked only against its constraint — `<K extends keyof Services>`
|
|
6386
7470
|
* accepts `"Players"` but not `""`. */
|
|
6387
|
-
overloadAccepts(f, argTypes) {
|
|
6388
|
-
|
|
7471
|
+
overloadAccepts(f, argTypes, spread) {
|
|
7472
|
+
const at = (i) => {
|
|
7473
|
+
if (!spread || i < spread.index) return argTypes[i];
|
|
7474
|
+
if (!spread.elements) return argTypes[spread.index];
|
|
7475
|
+
const held = spread.elements[i - spread.index];
|
|
7476
|
+
return held ?? argTypes[i - spread.index + 1 + spread.elements.length - 1];
|
|
7477
|
+
};
|
|
7478
|
+
const written = spread?.elements ? argTypes.length + spread.elements.length - 1 : argTypes.length;
|
|
7479
|
+
if (!f.varargs && (!spread || spread.elements) && written > f.params.length) return false;
|
|
7480
|
+
if (f.varargs && !f.typeParams?.length) {
|
|
7481
|
+
const last = spread && !spread.elements ? Math.max(f.params.length + 1, written) : written;
|
|
7482
|
+
for (let i = f.params.length; i < last; i++) {
|
|
7483
|
+
const arg = at(i);
|
|
7484
|
+
if (arg !== void 0 && !isAssignable(arg, f.varargs)) return false;
|
|
7485
|
+
}
|
|
7486
|
+
}
|
|
6389
7487
|
const params = this.boundParams(f);
|
|
6390
7488
|
return f.params.every((p, i) => {
|
|
6391
|
-
|
|
6392
|
-
|
|
7489
|
+
const arg = at(i);
|
|
7490
|
+
if (arg === void 0) return p.optional === true;
|
|
7491
|
+
return isAssignable(arg, params[i]);
|
|
6393
7492
|
});
|
|
6394
7493
|
}
|
|
6395
7494
|
/** A signature's parameter types as a call site sees them before inference:
|
|
@@ -6419,17 +7518,40 @@ var TypeAnalyzer = class {
|
|
|
6419
7518
|
}
|
|
6420
7519
|
/** Record what each written argument is expected to be — see
|
|
6421
7520
|
* `TypeAnalysis.expectedTypeOf`. */
|
|
6422
|
-
recordExpected(written, fns, selfOf) {
|
|
7521
|
+
recordExpected(written, fns, selfOf, argsOf = () => []) {
|
|
7522
|
+
const paramsOf = /* @__PURE__ */ new Map();
|
|
7523
|
+
for (const f of fns) paramsOf.set(f, this.paramsAsCalled(f, argsOf(f)));
|
|
6423
7524
|
written.forEach((arg, j) => {
|
|
6424
7525
|
const candidates = [];
|
|
6425
7526
|
for (const f of fns) {
|
|
6426
7527
|
const i = j + selfOf(f);
|
|
6427
|
-
const
|
|
7528
|
+
const params = paramsOf.get(f);
|
|
7529
|
+
const param = i < params.length ? params[i] : f.varargs;
|
|
6428
7530
|
if (param) candidates.push(param);
|
|
6429
7531
|
}
|
|
6430
7532
|
if (candidates.length) this.expectedTypeOf.set(arg, union(candidates));
|
|
6431
7533
|
});
|
|
6432
7534
|
}
|
|
7535
|
+
/** The parameters as *this* call makes them read: a type argument the
|
|
7536
|
+
* arguments already written pin down is substituted in, and one nothing
|
|
7537
|
+
* has pinned down yet falls back to its constraint.
|
|
7538
|
+
*
|
|
7539
|
+
* It is what makes the second argument of
|
|
7540
|
+
* `get(page, skill: Extract<Rows, { Page: Page }>["Skills"][number])`
|
|
7541
|
+
* worth completing — with `page` written, `skill` is the skills of that
|
|
7542
|
+
* page, not of every page. */
|
|
7543
|
+
paramsAsCalled(f, argTypes) {
|
|
7544
|
+
const fallback = this.boundParams(f);
|
|
7545
|
+
if (!f.typeParams?.length || !argTypes.length) return fallback;
|
|
7546
|
+
return f.params.map((p, i) => {
|
|
7547
|
+
if (!containsTypeParam(p.type)) return p.type;
|
|
7548
|
+
const subst = this.inferTypeArgs(f, argTypes.map((t, k) => k === i ? void 0 : t));
|
|
7549
|
+
for (const [name, bound] of [...subst]) if (bound.kind === "unknown") subst.delete(name);
|
|
7550
|
+
if (!subst.size) return fallback[i];
|
|
7551
|
+
const applied = this.reduceType(substitute(p.type, subst));
|
|
7552
|
+
return containsTypeParam(applied) ? fallback[i] : applied;
|
|
7553
|
+
});
|
|
7554
|
+
}
|
|
6433
7555
|
/** No signature accepts the call, and the argument count is not the
|
|
6434
7556
|
* problem: say which argument is wrong, the way TypeScript does. */
|
|
6435
7557
|
/** Check what was written against the parameters as this call's own type
|
|
@@ -6440,10 +7562,11 @@ var TypeAnalyzer = class {
|
|
|
6440
7562
|
if (!this.emitDiagnostics || !f.typeParams?.length) return;
|
|
6441
7563
|
const subst = this.inferTypeArgs(f, [...argTypes]);
|
|
6442
7564
|
for (const bound of subst.values()) if (bound.kind === "unknown") return;
|
|
6443
|
-
|
|
7565
|
+
const declaredAt = (i) => i < f.params.length ? f.params[i].type : f.varargs;
|
|
7566
|
+
for (let i = 0; i < Math.max(f.params.length, argTypes.length); i++) {
|
|
6444
7567
|
const arg = argTypes[i];
|
|
6445
|
-
const declared =
|
|
6446
|
-
if (arg === void 0 || !containsTypeParam(declared)) continue;
|
|
7568
|
+
const declared = declaredAt(i);
|
|
7569
|
+
if (arg === void 0 || declared === void 0 || !containsTypeParam(declared)) continue;
|
|
6447
7570
|
const expected = this.reduceType(substitute(declared, subst));
|
|
6448
7571
|
if (containsTypeParam(expected) || expected.kind === "any" || expected.kind === "unknown") continue;
|
|
6449
7572
|
if (isAssignable(arg, expected) || isAssignable(widen(arg), expected)) continue;
|
|
@@ -6464,15 +7587,26 @@ var TypeAnalyzer = class {
|
|
|
6464
7587
|
const args = argsFor(f);
|
|
6465
7588
|
const params = this.boundParams(f);
|
|
6466
7589
|
const self = selfOf(f);
|
|
7590
|
+
const spread = this.spreadOf(written, self);
|
|
6467
7591
|
for (let i = 0; i < f.params.length; i++) {
|
|
6468
|
-
const arg = args[i];
|
|
6469
|
-
if (
|
|
7592
|
+
const arg = spread && i >= spread.index ? this.spreadValue(spread, i) : args[i];
|
|
7593
|
+
if (spread && i > spread.index && !spread.elements) break;
|
|
7594
|
+
if (arg === void 0 || arg.kind === "never" || isAssignable(arg, params[i])) continue;
|
|
6470
7595
|
this.diagnostics.push({
|
|
6471
|
-
node: written[i - self] ?? call,
|
|
7596
|
+
node: (spread && i >= spread.index ? written[spread.index - self] : written[i - self]) ?? call,
|
|
6472
7597
|
message: `Argument of type '${formatType(arg)}' is not assignable to parameter of type '${briefType(params[i])}'`
|
|
6473
7598
|
});
|
|
6474
7599
|
return;
|
|
6475
7600
|
}
|
|
7601
|
+
if (!f.varargs || f.typeParams?.length) return;
|
|
7602
|
+
for (let i = f.params.length; i < args.length; i++) {
|
|
7603
|
+
if (isAssignable(args[i], f.varargs)) continue;
|
|
7604
|
+
this.diagnostics.push({
|
|
7605
|
+
node: written[i - self] ?? call,
|
|
7606
|
+
message: `Argument of type '${formatType(args[i])}' is not assignable to parameter of type '${briefType(f.varargs)}'`
|
|
7607
|
+
});
|
|
7608
|
+
return;
|
|
7609
|
+
}
|
|
6476
7610
|
}
|
|
6477
7611
|
/** A required parameter may not follow an optional one — otherwise the
|
|
6478
7612
|
* optional one could never actually be omitted. Same rule as TypeScript,
|
|
@@ -6481,6 +7615,10 @@ var TypeAnalyzer = class {
|
|
|
6481
7615
|
if (!this.emitDiagnostics) return;
|
|
6482
7616
|
let seenOptional;
|
|
6483
7617
|
for (const p of params) {
|
|
7618
|
+
if (p.rest === true) {
|
|
7619
|
+
this.checkRestType(p, node);
|
|
7620
|
+
continue;
|
|
7621
|
+
}
|
|
6484
7622
|
const isOptional = p.optional === true || p.default !== void 0;
|
|
6485
7623
|
if (isOptional) {
|
|
6486
7624
|
if (seenOptional === void 0) seenOptional = p.name ?? "parameter";
|
|
@@ -6506,8 +7644,10 @@ var TypeAnalyzer = class {
|
|
|
6506
7644
|
* when *no* overload accepts the count, so an overload set still reports
|
|
6507
7645
|
* once, against its first signature. Returns whether the count fits, so
|
|
6508
7646
|
* an argument's type is only complained about when its count is right. */
|
|
6509
|
-
checkArity(node, fns, argCount, selfArgs) {
|
|
7647
|
+
checkArity(node, fns, argCount, selfArgs, spread) {
|
|
6510
7648
|
if (!fns.length) return true;
|
|
7649
|
+
if (spread && !spread.elements) return true;
|
|
7650
|
+
if (spread?.elements) argCount += spread.elements.length - 1;
|
|
6511
7651
|
const fits = fns.some((f) => {
|
|
6512
7652
|
const { min: min2, max: max2 } = this.arityOf(f);
|
|
6513
7653
|
const n = argCount + selfArgs;
|
|
@@ -6550,7 +7690,7 @@ var TypeAnalyzer = class {
|
|
|
6550
7690
|
return type;
|
|
6551
7691
|
};
|
|
6552
7692
|
return record(this.withTypeParams(sig.generics, () => {
|
|
6553
|
-
const params = sig.params.map((p) => ({
|
|
7693
|
+
const params = sig.params.filter((p) => !p.rest).map((p) => ({
|
|
6554
7694
|
name: p.pattern ? void 0 : p.name,
|
|
6555
7695
|
type: this.paramType(p, /* @__PURE__ */ new Map()),
|
|
6556
7696
|
optional: p.optional || p.default !== void 0
|
|
@@ -6558,12 +7698,23 @@ var TypeAnalyzer = class {
|
|
|
6558
7698
|
return fn(
|
|
6559
7699
|
params,
|
|
6560
7700
|
sig.returnType ? this.resolveType(sig.returnType) : sig.predicate ? booleanType : anyType,
|
|
6561
|
-
|
|
7701
|
+
this.varargElement(sig),
|
|
6562
7702
|
names,
|
|
6563
7703
|
this.resolvePredicate(sig.predicate, params)
|
|
6564
7704
|
);
|
|
6565
7705
|
}));
|
|
6566
7706
|
}
|
|
7707
|
+
/** `...rest: T[]` holds every argument from its position on, so its type
|
|
7708
|
+
* is an array of what each one is. */
|
|
7709
|
+
checkRestType(p, node) {
|
|
7710
|
+
if (!this.emitDiagnostics || !p.typeAnnotation) return;
|
|
7711
|
+
const declared = this.resolveType(p.typeAnnotation);
|
|
7712
|
+
if (declared.kind === "array" || declared.kind === "any" || declared.kind === "typeParam") return;
|
|
7713
|
+
this.diagnostics.push({
|
|
7714
|
+
node,
|
|
7715
|
+
message: `A rest parameter holds every argument from its position on, so '${p.name ?? "..."}' is an array: '${formatType(declared)}[]', not '${formatType(declared)}'`
|
|
7716
|
+
});
|
|
7717
|
+
}
|
|
6567
7718
|
/** Turn a parsed `v is T` / `asserts v` annotation into a `TypePredicate`,
|
|
6568
7719
|
* resolving the named parameter to its index. A guard naming a parameter
|
|
6569
7720
|
* the function does not have is dropped rather than mis-narrowing an
|
|
@@ -6581,17 +7732,18 @@ var TypeAnalyzer = class {
|
|
|
6581
7732
|
inferFunctionBody(func, env) {
|
|
6582
7733
|
const names = func.generics.map((g) => g.name);
|
|
6583
7734
|
return this.withTypeParams(func.generics, () => {
|
|
6584
|
-
const params = func.params.
|
|
7735
|
+
const params = func.params.flatMap((p) => {
|
|
6585
7736
|
const type = this.paramType(p, env);
|
|
6586
7737
|
if (!p.pattern) {
|
|
6587
7738
|
const id = this.bindingIdByName(p.name, p);
|
|
6588
7739
|
if (id !== void 0 && !this.bindingType.has(id)) this.bindingType.set(id, type);
|
|
6589
7740
|
}
|
|
6590
|
-
return
|
|
7741
|
+
if (p.rest) return [];
|
|
7742
|
+
return [{
|
|
6591
7743
|
name: p.pattern ? void 0 : p.name,
|
|
6592
7744
|
type,
|
|
6593
7745
|
optional: p.optional || p.default !== void 0
|
|
6594
|
-
};
|
|
7746
|
+
}];
|
|
6595
7747
|
});
|
|
6596
7748
|
const bodyEnv = forkEnv(env);
|
|
6597
7749
|
for (const p of func.params) {
|
|
@@ -6616,7 +7768,7 @@ var TypeAnalyzer = class {
|
|
|
6616
7768
|
return fn(
|
|
6617
7769
|
params,
|
|
6618
7770
|
returns,
|
|
6619
|
-
|
|
7771
|
+
this.varargElement(func),
|
|
6620
7772
|
names,
|
|
6621
7773
|
this.resolvePredicate(func.predicate, params)
|
|
6622
7774
|
);
|
|
@@ -6830,6 +7982,13 @@ var TypeAnalyzer = class {
|
|
|
6830
7982
|
if (src.kind === "array") return [numberType, src.element];
|
|
6831
7983
|
}
|
|
6832
7984
|
}
|
|
7985
|
+
const iterator = this.expand(iterType);
|
|
7986
|
+
if (iterator.kind === "function") {
|
|
7987
|
+
const returns = this.expand(iterator.returns);
|
|
7988
|
+
const parts = returns.kind === "tuple" ? returns.elements.map((m) => this.expand(m)) : [returns];
|
|
7989
|
+
const at = (i) => parts[i] ?? (parts.length === 1 ? parts[0] : unknownType);
|
|
7990
|
+
return [at(0), at(1)];
|
|
7991
|
+
}
|
|
6833
7992
|
const t = this.expand(iterType);
|
|
6834
7993
|
if (t.kind === "array") return varCount >= 2 ? [numberType, t.element] : [t.element, unknownType];
|
|
6835
7994
|
if (t.kind === "object") {
|
|
@@ -6981,7 +8140,21 @@ var TypeAnalyzer = class {
|
|
|
6981
8140
|
expand(t) {
|
|
6982
8141
|
if (t.kind !== "genericRef") return t;
|
|
6983
8142
|
const def = this.aliasDefs.get(t.name);
|
|
6984
|
-
if (!def
|
|
8143
|
+
if (!def) {
|
|
8144
|
+
const imported = this.importedTypes.get(t.name);
|
|
8145
|
+
if (!imported) return t;
|
|
8146
|
+
if (!imported.params.length) return imported.type;
|
|
8147
|
+
const subst = /* @__PURE__ */ new Map();
|
|
8148
|
+
imported.params.forEach((name, i) => subst.set(name, t.typeArguments[i] ?? unknownType));
|
|
8149
|
+
const key2 = `import ${formatType(t)}`;
|
|
8150
|
+
const cached2 = this.expandCache.get(key2);
|
|
8151
|
+
if (cached2) return cached2;
|
|
8152
|
+
this.expandCache.set(key2, t);
|
|
8153
|
+
const applied = substitute(imported.type, subst);
|
|
8154
|
+
this.expandCache.set(key2, applied);
|
|
8155
|
+
return applied;
|
|
8156
|
+
}
|
|
8157
|
+
if (this.resolvingAliases.has(t.name)) return t;
|
|
6985
8158
|
const key = t.typeArguments.length ? formatType(t) : t.name;
|
|
6986
8159
|
const cached = this.expandCache.get(key);
|
|
6987
8160
|
if (cached) return cached;
|
|
@@ -7002,19 +8175,120 @@ var TypeAnalyzer = class {
|
|
|
7002
8175
|
* those names again replaces the whole set, and nothing here is a special
|
|
7003
8176
|
* case in the analyzer. The build lowers each call to a plain function. */
|
|
7004
8177
|
builtInMethod(t, name) {
|
|
7005
|
-
const
|
|
7006
|
-
const
|
|
8178
|
+
const parts = (t.kind === "union" ? t.types : [t]).map((m) => this.expand(m));
|
|
8179
|
+
const elements = parts.map((m) => m.kind === "array" ? m.element : m.kind === "tuple" ? union(m.elements) : void 0);
|
|
8180
|
+
const element = elements.every((e) => e !== void 0) ? union(elements) : void 0;
|
|
8181
|
+
const isString = (m) => m.kind === "primitive" && m.name === "string" || m.kind === "literal" && m.base === "string" || m.kind === "templateLiteral";
|
|
8182
|
+
const methodTable = element !== void 0 ? "ArrayMethods" : parts.every(isString) ? "StringMethods" : void 0;
|
|
7007
8183
|
const def = methodTable === void 0 ? void 0 : this.aliasDefs.get(methodTable);
|
|
7008
8184
|
if (!def || def.class) return void 0;
|
|
7009
8185
|
const table = this.expand(this.instantiateAlias(def, element !== void 0 ? [element] : []));
|
|
7010
|
-
const
|
|
7011
|
-
for (let i =
|
|
7012
|
-
const part =
|
|
8186
|
+
const layers = table.kind === "intersection" ? table.types.map((m) => this.expand(m)) : [table];
|
|
8187
|
+
for (let i = layers.length - 1; i >= 0; i--) {
|
|
8188
|
+
const part = layers[i];
|
|
7013
8189
|
const property = part.kind === "object" ? part.properties.get(name) : void 0;
|
|
7014
8190
|
if (property) return property.type;
|
|
7015
8191
|
}
|
|
7016
8192
|
return void 0;
|
|
7017
8193
|
}
|
|
8194
|
+
/** The most a deferred type could turn out to be. A conditional is one of
|
|
8195
|
+
* its branches, and the true branch stands for a member of what was
|
|
8196
|
+
* tested (`T` in `T extends U ? T : never`); an indexed access reads
|
|
8197
|
+
* through the bound of what it indexes. Anything else has no bound worth
|
|
8198
|
+
* giving — `undefined` leaves the comparison as it was. */
|
|
8199
|
+
deferredBound(t, depth = 0) {
|
|
8200
|
+
if (depth > 8) return void 0;
|
|
8201
|
+
switch (t.kind) {
|
|
8202
|
+
case "conditional": {
|
|
8203
|
+
const check = this.reduceType(t.checkType);
|
|
8204
|
+
if (containsTypeParam(check)) return void 0;
|
|
8205
|
+
const subst = /* @__PURE__ */ new Map();
|
|
8206
|
+
if (t.distributeParam) subst.set(t.distributeParam, check);
|
|
8207
|
+
for (const name of t.inferVars ?? []) subst.set(name, unknownType);
|
|
8208
|
+
const branches = [substitute(t.trueType, subst), t.falseType].map((branch) => this.reduceType(branch));
|
|
8209
|
+
if (branches.some((branch) => containsTypeParam(branch))) return void 0;
|
|
8210
|
+
return union(branches);
|
|
8211
|
+
}
|
|
8212
|
+
case "indexedAccess": {
|
|
8213
|
+
const object = this.deferredBound(t.objectType, depth + 1) ?? this.atConstraints(t.objectType);
|
|
8214
|
+
const index = this.atConstraints(this.reduceType(t.indexType));
|
|
8215
|
+
if (!object || !index) return void 0;
|
|
8216
|
+
return this.indexedType(object, index);
|
|
8217
|
+
}
|
|
8218
|
+
default:
|
|
8219
|
+
return void 0;
|
|
8220
|
+
}
|
|
8221
|
+
}
|
|
8222
|
+
/** `t` with every type parameter standing at its constraint: `Map[K]`
|
|
8223
|
+
* where `K extends "a" | "b"` is at most what those two keys hold. A
|
|
8224
|
+
* parameter with no constraint bounds nothing, and says so. */
|
|
8225
|
+
atConstraints(t) {
|
|
8226
|
+
if (!containsTypeParam(t)) return t;
|
|
8227
|
+
const bounds = /* @__PURE__ */ new Map();
|
|
8228
|
+
const seen = /* @__PURE__ */ new WeakSet();
|
|
8229
|
+
let open = false;
|
|
8230
|
+
const walk = (value) => {
|
|
8231
|
+
if (!value || typeof value !== "object" || seen.has(value)) return;
|
|
8232
|
+
seen.add(value);
|
|
8233
|
+
if (value instanceof Map) {
|
|
8234
|
+
value.forEach(walk);
|
|
8235
|
+
return;
|
|
8236
|
+
}
|
|
8237
|
+
const part = value;
|
|
8238
|
+
if (part.kind === "object" && part.class) return;
|
|
8239
|
+
if (part.kind === "typeParam" && typeof part.name === "string") {
|
|
8240
|
+
if (part.constraint && !containsTypeParam(part.constraint)) {
|
|
8241
|
+
bounds.set(part.name, this.reduceType(part.constraint));
|
|
8242
|
+
} else {
|
|
8243
|
+
open = true;
|
|
8244
|
+
}
|
|
8245
|
+
}
|
|
8246
|
+
for (const child of Object.values(value)) walk(child);
|
|
8247
|
+
};
|
|
8248
|
+
walk(t);
|
|
8249
|
+
if (open) return void 0;
|
|
8250
|
+
const applied = this.reduceType(substitute(t, bounds));
|
|
8251
|
+
return containsTypeParam(applied) ? void 0 : applied;
|
|
8252
|
+
}
|
|
8253
|
+
/** What `text["upper"]` reads: a string answers only to its methods, the
|
|
8254
|
+
* way Lua's string metatable does. */
|
|
8255
|
+
stringMember(object, index) {
|
|
8256
|
+
if (index.kind !== "literal" || typeof index.value !== "string") return void 0;
|
|
8257
|
+
const parts = this.stringParts(object);
|
|
8258
|
+
if (!parts) return void 0;
|
|
8259
|
+
const found = parts.map((part) => this.builtInMethod(part, index.value));
|
|
8260
|
+
return found.every((t) => t !== void 0) ? union(found) : void 0;
|
|
8261
|
+
}
|
|
8262
|
+
/** The members of `t` when every one of them is a string — `"a" | "b"` is
|
|
8263
|
+
* as much a string as `string` is. */
|
|
8264
|
+
stringParts(t) {
|
|
8265
|
+
let expanded = this.expand(t);
|
|
8266
|
+
if (expanded.kind === "conditional" || expanded.kind === "indexedAccess") {
|
|
8267
|
+
const bound = this.deferredBound(expanded);
|
|
8268
|
+
if (!bound) return void 0;
|
|
8269
|
+
expanded = this.expand(bound);
|
|
8270
|
+
}
|
|
8271
|
+
const parts = (expanded.kind === "union" ? expanded.types : [expanded]).map((m) => this.expand(m));
|
|
8272
|
+
const isString = (m) => m.kind === "primitive" && m.name === "string" || m.kind === "literal" && m.base === "string" || m.kind === "templateLiteral";
|
|
8273
|
+
return parts.length && parts.every(isString) ? parts : void 0;
|
|
8274
|
+
}
|
|
8275
|
+
/** `text.Sans` or `text["Sans"]`: a string is not a table, and the only
|
|
8276
|
+
* members it has are the ones a type library gave it — so a name that is
|
|
8277
|
+
* not one of them is a mistake worth reporting, rather than the nil Lua
|
|
8278
|
+
* would hand back. */
|
|
8279
|
+
checkStringMember(node, object, key) {
|
|
8280
|
+
if (!this.emitDiagnostics) return;
|
|
8281
|
+
const parts = this.stringParts(object);
|
|
8282
|
+
if (!parts) return;
|
|
8283
|
+
const keys = (key.kind === "union" ? key.types : [key]).map((m) => this.expand(m));
|
|
8284
|
+
if (!keys.length || !keys.every((m) => m.kind === "literal" && typeof m.value === "string")) return;
|
|
8285
|
+
const names = keys.map((m) => String(m.value));
|
|
8286
|
+
if (names.some((name) => parts.some((part) => this.builtInMethod(part, name)))) return;
|
|
8287
|
+
this.diagnostics.push({
|
|
8288
|
+
node,
|
|
8289
|
+
message: names.length === 1 ? `'${names[0]}' does not exist on a string` : `'${briefType(key)}' does not name a member of a string`
|
|
8290
|
+
});
|
|
8291
|
+
}
|
|
7018
8292
|
propertyType(raw, name) {
|
|
7019
8293
|
const t = this.deferredAccess(this.expand(raw));
|
|
7020
8294
|
if (t.kind === "object") {
|
|
@@ -7024,7 +8298,11 @@ var TypeAnalyzer = class {
|
|
|
7024
8298
|
}
|
|
7025
8299
|
const built = this.builtInMethod(t, name);
|
|
7026
8300
|
if (built) return built;
|
|
7027
|
-
if (t.kind === "union")
|
|
8301
|
+
if (t.kind === "union") {
|
|
8302
|
+
const built2 = this.builtInMethod(t, name);
|
|
8303
|
+
if (built2) return built2;
|
|
8304
|
+
return union(t.types.map((m) => this.propertyType(m, name)));
|
|
8305
|
+
}
|
|
7028
8306
|
if (t.kind === "intersection") {
|
|
7029
8307
|
const parts = t.types.map((m) => this.propertyType(m, name)).filter((p) => p.kind !== "unknown");
|
|
7030
8308
|
if (parts.length) return intersection(parts);
|
|
@@ -7032,6 +8310,10 @@ var TypeAnalyzer = class {
|
|
|
7032
8310
|
if (t.kind === "typeParam" && t.constraint) return this.propertyType(t.constraint, name);
|
|
7033
8311
|
if (t.kind === "difference") return this.propertyType(t.base, name);
|
|
7034
8312
|
if (t.kind === "any") return anyType;
|
|
8313
|
+
if (t.kind === "conditional" || t.kind === "indexedAccess") {
|
|
8314
|
+
const bound = this.deferredBound(t);
|
|
8315
|
+
if (bound) return this.propertyType(bound, name);
|
|
8316
|
+
}
|
|
7035
8317
|
return unknownType;
|
|
7036
8318
|
}
|
|
7037
8319
|
/** `t[k]`. A statically known string key resolves against the declared
|
|
@@ -7107,6 +8389,20 @@ var TypeAnalyzer = class {
|
|
|
7107
8389
|
// `...` holds what the function declared it takes.
|
|
7108
8390
|
case "VarargExpression":
|
|
7109
8391
|
return this.varargs[this.varargs.length - 1] ?? anyType;
|
|
8392
|
+
// `f(a, ...rest)` — every value the array holds, one after
|
|
8393
|
+
// another. Each of them is an element, so that is what the
|
|
8394
|
+
// parameters it fills are checked against.
|
|
8395
|
+
case "SpreadElement": {
|
|
8396
|
+
const spread = this.infer(expr.argument, env);
|
|
8397
|
+
const element = this.spreadElement(spread);
|
|
8398
|
+
if (element === void 0 && this.emitDiagnostics) {
|
|
8399
|
+
this.diagnostics.push({
|
|
8400
|
+
node: expr,
|
|
8401
|
+
message: `Only an array can be spread, and '${formatType(spread)}' is not one`
|
|
8402
|
+
});
|
|
8403
|
+
}
|
|
8404
|
+
return element ?? anyType;
|
|
8405
|
+
}
|
|
7110
8406
|
// Broken syntax is reported by the parser; nothing more to say.
|
|
7111
8407
|
case "ErrorExpression":
|
|
7112
8408
|
return anyType;
|
|
@@ -7212,6 +8508,7 @@ var TypeAnalyzer = class {
|
|
|
7212
8508
|
const { type: obj, shortCircuits } = this.chainObject(expr, expr.object, env);
|
|
7213
8509
|
const key = this.refKeyOf(expr);
|
|
7214
8510
|
const narrowed = key === void 0 ? void 0 : env.get(key);
|
|
8511
|
+
this.checkStringMember(expr, obj, literal(expr.property.name));
|
|
7215
8512
|
return this.chainResult(expr, narrowed ?? this.propertyType(obj, expr.property.name), shortCircuits);
|
|
7216
8513
|
}
|
|
7217
8514
|
case "IndexExpression": {
|
|
@@ -7219,12 +8516,33 @@ var TypeAnalyzer = class {
|
|
|
7219
8516
|
const idx = this.infer(expr.index, env);
|
|
7220
8517
|
const key = this.refKeyOf(expr);
|
|
7221
8518
|
const narrowed = key === void 0 ? void 0 : env.get(key);
|
|
8519
|
+
this.checkStringMember(expr, obj, this.expand(idx));
|
|
8520
|
+
const member = this.stringMember(obj, this.expand(idx));
|
|
8521
|
+
if (member) return this.chainResult(expr, member, shortCircuits);
|
|
7222
8522
|
return this.chainResult(expr, narrowed ?? this.indexedType(obj, idx), shortCircuits);
|
|
7223
8523
|
}
|
|
7224
8524
|
case "CallExpression": {
|
|
8525
|
+
if (expr.callee.type === "SuperExpression") return this.inferSuperCall(expr, env);
|
|
7225
8526
|
const { type: callee, shortCircuits } = this.chainObject(expr, expr.callee, env);
|
|
7226
8527
|
return this.chainResult(expr, this.inferCall(expr, callee, env), shortCircuits);
|
|
7227
8528
|
}
|
|
8529
|
+
case "NewExpression":
|
|
8530
|
+
return this.inferNew(expr, env);
|
|
8531
|
+
case "ClassExpression":
|
|
8532
|
+
return this.visitClass(expr, env);
|
|
8533
|
+
case "SuperExpression": {
|
|
8534
|
+
const stmt = this.currentClass;
|
|
8535
|
+
if (!stmt?.superclass) {
|
|
8536
|
+
if (this.emitDiagnostics) {
|
|
8537
|
+
this.diagnostics.push({
|
|
8538
|
+
node: expr,
|
|
8539
|
+
message: "'super' is only available inside a class that extends another"
|
|
8540
|
+
});
|
|
8541
|
+
}
|
|
8542
|
+
return anyType;
|
|
8543
|
+
}
|
|
8544
|
+
return this.superType(stmt);
|
|
8545
|
+
}
|
|
7228
8546
|
case "MethodCallExpression": {
|
|
7229
8547
|
const { type: objType, shortCircuits } = this.chainObject(expr, expr.object, env);
|
|
7230
8548
|
return this.chainResult(expr, this.inferMethodCall(expr, objType, env), shortCircuits);
|
|
@@ -7243,16 +8561,56 @@ var TypeAnalyzer = class {
|
|
|
7243
8561
|
}
|
|
7244
8562
|
}
|
|
7245
8563
|
}
|
|
8564
|
+
/** `new Name(args)` is `Name.new(args)` — the same function, and the
|
|
8565
|
+
* same check. Saying so here rather than rewriting the tree keeps the
|
|
8566
|
+
* error messages pointing at what was written. */
|
|
8567
|
+
inferNew(expr, env) {
|
|
8568
|
+
const calleeType = this.infer(expr.callee, env);
|
|
8569
|
+
const constructor = this.propertyType(calleeType, "new");
|
|
8570
|
+
if (!this.overloadsOf(constructor).length && calleeType.kind !== "any") {
|
|
8571
|
+
if (this.emitDiagnostics) {
|
|
8572
|
+
const label = expressionLabel(expr.callee) ?? formatType(calleeType);
|
|
8573
|
+
this.diagnostics.push({ node: expr.callee, message: `'${label}' is not a class; 'new' needs one` });
|
|
8574
|
+
}
|
|
8575
|
+
for (const argument of expr.arguments) this.infer(argument, env);
|
|
8576
|
+
return anyType;
|
|
8577
|
+
}
|
|
8578
|
+
return this.inferCall(expr, constructor, env);
|
|
8579
|
+
}
|
|
8580
|
+
/** `super(...)` — the base constructor, run on the instance being built. */
|
|
8581
|
+
inferSuperCall(expr, env) {
|
|
8582
|
+
const stmt = this.currentClass;
|
|
8583
|
+
const constructor = stmt ? this.baseConstructorType(stmt) : void 0;
|
|
8584
|
+
if (!stmt?.superclass) {
|
|
8585
|
+
if (this.emitDiagnostics) {
|
|
8586
|
+
this.diagnostics.push({
|
|
8587
|
+
node: expr,
|
|
8588
|
+
message: "'super(...)' is only available inside the constructor of a class that extends another"
|
|
8589
|
+
});
|
|
8590
|
+
}
|
|
8591
|
+
for (const argument of expr.arguments) this.infer(argument, env);
|
|
8592
|
+
return nilType;
|
|
8593
|
+
}
|
|
8594
|
+
if (!constructor) {
|
|
8595
|
+
for (const argument of expr.arguments) this.infer(argument, env);
|
|
8596
|
+
return nilType;
|
|
8597
|
+
}
|
|
8598
|
+
const callable = fn(constructor.params.filter((p) => p.name !== "this"), nilType, constructor.varargs);
|
|
8599
|
+
this.inferCall(expr, callable, env);
|
|
8600
|
+
return nilType;
|
|
8601
|
+
}
|
|
7246
8602
|
inferCall(expr, callee, env) {
|
|
8603
|
+
this.checkAmbiguousCall(expr);
|
|
7247
8604
|
const fns = this.overloadsOf(callee);
|
|
7248
8605
|
const explicit = this.explicitTypeArguments(expr, fns);
|
|
7249
8606
|
const expected = this.expectedArguments(expr.arguments, fns, () => 0);
|
|
7250
8607
|
expr.arguments.forEach((a, i) => this.applyContext(a, expected[i]));
|
|
7251
8608
|
const argTypes = expr.arguments.map((a) => this.infer(a, env));
|
|
7252
8609
|
if (fns.length) {
|
|
7253
|
-
this.recordExpected(expr.arguments, fns, () => 0);
|
|
7254
|
-
const
|
|
7255
|
-
const
|
|
8610
|
+
this.recordExpected(expr.arguments, fns, () => 0, () => argTypes);
|
|
8611
|
+
const spread = this.spreadOf(expr.arguments);
|
|
8612
|
+
const arityFits = this.checkArity(expr, fns, argTypes.length, 0, spread);
|
|
8613
|
+
const picked = this.pickOverload(fns, argTypes, void 0, spread);
|
|
7256
8614
|
const distributed = this.distributedReturn(fns, argTypes, picked, (_, args) => args);
|
|
7257
8615
|
if (distributed) return distributed;
|
|
7258
8616
|
if (picked) {
|
|
@@ -7264,6 +8622,21 @@ var TypeAnalyzer = class {
|
|
|
7264
8622
|
}
|
|
7265
8623
|
return callee.kind === "any" ? anyType : unknownType;
|
|
7266
8624
|
}
|
|
8625
|
+
/** A `(` on a line of its own continues the statement above it:
|
|
8626
|
+
*
|
|
8627
|
+
* const value = map[key]
|
|
8628
|
+
* ("text"):upper()
|
|
8629
|
+
*
|
|
8630
|
+
* calls `map[key]`, in luaut as in Lua and in JavaScript. It is almost
|
|
8631
|
+
* never what was meant, and what it does instead is invisible — so say
|
|
8632
|
+
* so, and name the fix. */
|
|
8633
|
+
checkAmbiguousCall(expr) {
|
|
8634
|
+
if (!this.emitDiagnostics || !expr.argumentsOnNewLine) return;
|
|
8635
|
+
this.diagnostics.push({
|
|
8636
|
+
node: expr,
|
|
8637
|
+
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."
|
|
8638
|
+
});
|
|
8639
|
+
}
|
|
7267
8640
|
inferMethodCall(expr, objType, env) {
|
|
7268
8641
|
const fns = this.overloadsOf(this.propertyType(objType, expr.method.name));
|
|
7269
8642
|
const explicit = this.explicitTypeArguments(expr, fns);
|
|
@@ -7273,9 +8646,11 @@ var TypeAnalyzer = class {
|
|
|
7273
8646
|
if (fns.length) {
|
|
7274
8647
|
const withSelf = (f) => this.takesSelf(f) ? [objType, ...argTypes] : argTypes;
|
|
7275
8648
|
const selfOf = (f) => this.takesSelf(f) ? 1 : 0;
|
|
7276
|
-
this.recordExpected(expr.arguments, fns, selfOf);
|
|
7277
|
-
const
|
|
7278
|
-
const
|
|
8649
|
+
this.recordExpected(expr.arguments, fns, selfOf, withSelf);
|
|
8650
|
+
const self0 = this.takesSelf(fns[0]) ? 1 : 0;
|
|
8651
|
+
const spread = this.spreadOf(expr.arguments, self0);
|
|
8652
|
+
const arityFits = this.checkArity(expr, fns, argTypes.length, self0, spread);
|
|
8653
|
+
const picked = this.pickOverload(fns, argTypes, withSelf, spread);
|
|
7279
8654
|
const distributed = this.distributedReturn(
|
|
7280
8655
|
fns,
|
|
7281
8656
|
argTypes,
|
|
@@ -7924,7 +9299,39 @@ var TypeAnalyzer = class {
|
|
|
7924
9299
|
* out. Every place that has to line arguments up with parameters goes
|
|
7925
9300
|
* through here so the two sides cannot drift apart. */
|
|
7926
9301
|
takesSelf(f) {
|
|
7927
|
-
|
|
9302
|
+
const first = f.params[0]?.name;
|
|
9303
|
+
return first === "self" || first === "this";
|
|
9304
|
+
}
|
|
9305
|
+
/** What one value of a spread array is, or `undefined` when the thing
|
|
9306
|
+
* spread is not a list of values at all. */
|
|
9307
|
+
spreadElement(t) {
|
|
9308
|
+
const spread = this.expand(t);
|
|
9309
|
+
if (spread.kind === "array") return spread.element;
|
|
9310
|
+
if (spread.kind === "tuple") return union(spread.elements);
|
|
9311
|
+
if (spread.kind === "any") return anyType;
|
|
9312
|
+
return void 0;
|
|
9313
|
+
}
|
|
9314
|
+
/** Where a call's arguments stop being one each, and what fills the rest.
|
|
9315
|
+
* From a spread on, every remaining parameter is filled by one of the
|
|
9316
|
+
* array's values — however many that turns out to be, so neither the
|
|
9317
|
+
* count nor the positions after it are known. A *tuple* is the exception:
|
|
9318
|
+
* it holds a known value at each position, and `elements` says which. */
|
|
9319
|
+
spreadOf(args, self = 0) {
|
|
9320
|
+
const index = args.findIndex((a) => a.type === "SpreadElement");
|
|
9321
|
+
if (index < 0) return void 0;
|
|
9322
|
+
const spread = args[index];
|
|
9323
|
+
const held = this.typeOf.get(spread.argument);
|
|
9324
|
+
const expanded = held && this.expand(held);
|
|
9325
|
+
return {
|
|
9326
|
+
index: index + self,
|
|
9327
|
+
element: this.typeOf.get(spread) ?? unknownType,
|
|
9328
|
+
elements: expanded?.kind === "tuple" ? expanded.elements : void 0
|
|
9329
|
+
};
|
|
9330
|
+
}
|
|
9331
|
+
/** One of `spread`'s values, at the position `i` of a call's arguments. */
|
|
9332
|
+
spreadValue(spread, i) {
|
|
9333
|
+
if (!spread.elements) return spread.element;
|
|
9334
|
+
return spread.elements[i - spread.index] ?? neverType;
|
|
7928
9335
|
}
|
|
7929
9336
|
/** A function type as a list of call signatures: a lone function is a
|
|
7930
9337
|
* one-element list, an intersection is the overload set in source order. */
|
|
@@ -7979,6 +9386,8 @@ var TypeAnalyzer = class {
|
|
|
7979
9386
|
type = this.resolveType(statement.valueType);
|
|
7980
9387
|
} else if (statement.type === "FunctionDeclaration") {
|
|
7981
9388
|
type = statement.signatures?.length ? intersection(statement.signatures.map((sig) => this.signatureToFnType(sig))) : this.inferFunctionBody(statement.func, /* @__PURE__ */ new Map());
|
|
9389
|
+
} else if (statement.type === "ClassDeclaration") {
|
|
9390
|
+
type = this.classValueType(statement);
|
|
7982
9391
|
} else if (statement.type === "VariableDeclaration") {
|
|
7983
9392
|
const target = statement.names[index];
|
|
7984
9393
|
if (target.type === "IdentifierPattern" && target.typeAnnotation) {
|
|
@@ -8015,6 +9424,10 @@ var TypeAnalyzer = class {
|
|
|
8015
9424
|
return;
|
|
8016
9425
|
}
|
|
8017
9426
|
const record = node;
|
|
9427
|
+
if (record.type === "ClassDeclaration" && record.name) {
|
|
9428
|
+
const id = this.bindingIdByName(record.name.name, record.name);
|
|
9429
|
+
if (id !== void 0) out.set(id, { statement: node, index: 0 });
|
|
9430
|
+
}
|
|
8018
9431
|
if (record.type === "FunctionDeclaration" && record.name) {
|
|
8019
9432
|
const id = this.bindingIdByName(record.name.name, record.name);
|
|
8020
9433
|
if (id !== void 0) out.set(id, { statement: node, index: 0 });
|
|
@@ -8534,6 +9947,7 @@ var index_default = luautparser;
|
|
|
8534
9947
|
resolveModulePath,
|
|
8535
9948
|
resolveTypeLibraries,
|
|
8536
9949
|
setAliasExpander,
|
|
9950
|
+
setDeferredBound,
|
|
8537
9951
|
sourceMapTypes,
|
|
8538
9952
|
stringType,
|
|
8539
9953
|
stripJsonComments,
|