luaut-parser 3.1.0 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +339 -14
- package/dist/index.cjs +1620 -128
- package/dist/index.d.cts +226 -13
- package/dist/index.d.ts +226 -13
- package/dist/index.js +1619 -128
- package/package.json +1 -1
package/dist/index.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. */
|
|
@@ -4824,9 +5427,10 @@ var TypeAnalyzer = class {
|
|
|
4824
5427
|
/** Recursion guard for `preVisitBody`. */
|
|
4825
5428
|
preVisitDepth = 0;
|
|
4826
5429
|
run() {
|
|
4827
|
-
this.registerAliasDefs(preludeProgram().body);
|
|
4828
|
-
for (const lib of this.options.libs ?? []) this.registerAliasDefs(lib.body);
|
|
5430
|
+
this.registerAliasDefs(preludeProgram().body, true);
|
|
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) {
|
|
@@ -4903,17 +5514,65 @@ var TypeAnalyzer = class {
|
|
|
4903
5514
|
}
|
|
4904
5515
|
}
|
|
4905
5516
|
}
|
|
4906
|
-
|
|
5517
|
+
/** `layering` is on for the prelude and for definitions files: a second
|
|
5518
|
+
* library that declares an alias already declared *adds* to it, the way a
|
|
5519
|
+
* second `declare` of a table's name does, so `@luaut/roblox` can give
|
|
5520
|
+
* `StringMethods` Luau's `split` without restating Lua's. The file being
|
|
5521
|
+
* analysed is not a layer: its own alias replaces what the libraries
|
|
5522
|
+
* gave, which is how a project opts out of a set. */
|
|
5523
|
+
registerAliasDefs(block, layering = false) {
|
|
4907
5524
|
for (const stmt of block.statements) {
|
|
4908
5525
|
const alias = stmt.type === "TypeAliasStatement" ? stmt : stmt.type === "ExportTypeAliasStatement" ? stmt.alias : void 0;
|
|
4909
|
-
if (alias)
|
|
5526
|
+
if (alias) {
|
|
5527
|
+
const previous = layering ? this.aliasDefs.get(alias.name.name) : void 0;
|
|
5528
|
+
const node = previous && !previous.class ? {
|
|
5529
|
+
type: "IntersectionTypeNode",
|
|
5530
|
+
types: [previous.node, alias.definition],
|
|
5531
|
+
line: alias.definition.line,
|
|
5532
|
+
column: alias.definition.column
|
|
5533
|
+
} : alias.definition;
|
|
5534
|
+
this.aliasDefs.set(alias.name.name, {
|
|
5535
|
+
params: previous && !previous.class && previous.params.length ? previous.params : alias.generics,
|
|
5536
|
+
node
|
|
5537
|
+
});
|
|
5538
|
+
}
|
|
4910
5539
|
if (stmt.type === "DeclareClassStatement") {
|
|
4911
5540
|
this.aliasDefs.set(stmt.name.name, { params: [], node: stmt.body, class: stmt });
|
|
4912
5541
|
}
|
|
5542
|
+
const declaration = stmt.type === "ExportStatement" || stmt.type === "ExportDefaultStatement" ? stmt.declaration : stmt;
|
|
5543
|
+
if (declaration.type === "ClassDeclaration") this.registerClass(declaration);
|
|
4913
5544
|
}
|
|
4914
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
|
+
}
|
|
4915
5573
|
/** A non-generic definition's type. */
|
|
4916
5574
|
resolveDef(def) {
|
|
5575
|
+
if (def.runtimeClass) return this.instanceType(def.runtimeClass);
|
|
4917
5576
|
return def.class ? this.classType(def.class) : this.resolveType(def.node);
|
|
4918
5577
|
}
|
|
4919
5578
|
/** One type per class declaration, so every mention of a class is the same
|
|
@@ -4971,6 +5630,381 @@ var TypeAnalyzer = class {
|
|
|
4971
5630
|
if (this.program.body.statements.includes(stmt)) ownMembers();
|
|
4972
5631
|
return type;
|
|
4973
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
|
+
}
|
|
4974
6008
|
/** `extends` must name a class, and the chain must end. */
|
|
4975
6009
|
checkClass(stmt) {
|
|
4976
6010
|
if (!stmt.superclass || !this.emitDiagnostics) return;
|
|
@@ -5227,6 +6261,7 @@ var TypeAnalyzer = class {
|
|
|
5227
6261
|
instantiateAlias(def, args) {
|
|
5228
6262
|
if (this.instantiationDepth > 20) return unknownType;
|
|
5229
6263
|
const subst = this.bindTypeArguments(def.params, args);
|
|
6264
|
+
if (def.runtimeClass) return substitute(this.instanceType(def.runtimeClass), subst);
|
|
5230
6265
|
this.instantiationDepth++;
|
|
5231
6266
|
try {
|
|
5232
6267
|
const body = this.withTypeParams(def.params, () => this.resolveType(def.node));
|
|
@@ -5371,15 +6406,17 @@ var TypeAnalyzer = class {
|
|
|
5371
6406
|
case "FunctionTypeNode": {
|
|
5372
6407
|
const names = node.generics.map((g) => g.name);
|
|
5373
6408
|
return this.withTypeParams(node.generics, () => {
|
|
5374
|
-
const params = node.params.map((p) => ({
|
|
6409
|
+
const params = node.params.filter((p) => !p.rest).map((p) => ({
|
|
5375
6410
|
name: p.name,
|
|
5376
6411
|
type: p.optional ? optional(this.resolveType(p.typeAnnotation)) : this.resolveType(p.typeAnnotation),
|
|
5377
6412
|
optional: p.optional
|
|
5378
6413
|
}));
|
|
6414
|
+
const restParam = node.params.find((p) => p.rest);
|
|
6415
|
+
const restElement = restParam ? this.resolveType(restParam.typeAnnotation) : void 0;
|
|
5379
6416
|
return this.withTypeParamDefaults(fn(
|
|
5380
6417
|
params,
|
|
5381
6418
|
this.resolveType(node.returnType),
|
|
5382
|
-
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,
|
|
5383
6420
|
names,
|
|
5384
6421
|
this.resolvePredicate(node.predicate, params)
|
|
5385
6422
|
), node.generics);
|
|
@@ -5573,7 +6610,10 @@ var TypeAnalyzer = class {
|
|
|
5573
6610
|
accessType(obj, index) {
|
|
5574
6611
|
if (index.kind === "union") return union(index.types.map((m) => this.accessType(obj, m)));
|
|
5575
6612
|
if (index.kind === "literal" && typeof index.value === "string") {
|
|
5576
|
-
|
|
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;
|
|
5577
6617
|
}
|
|
5578
6618
|
return this.indexedType(obj, index);
|
|
5579
6619
|
}
|
|
@@ -5726,12 +6766,13 @@ var TypeAnalyzer = class {
|
|
|
5726
6766
|
visitStatement(stmt, env) {
|
|
5727
6767
|
switch (stmt.type) {
|
|
5728
6768
|
case "VariableDeclaration": {
|
|
6769
|
+
this.nameClassExpressions(stmt);
|
|
5729
6770
|
stmt.names.forEach((target, i) => {
|
|
5730
6771
|
if (target.type === "IdentifierPattern" && target.typeAnnotation && stmt.init[i]) {
|
|
5731
6772
|
this.applyContext(stmt.init[i], this.resolveType(target.typeAnnotation));
|
|
5732
6773
|
}
|
|
5733
6774
|
});
|
|
5734
|
-
const { types: valueTypes, sources } = this.valueList(stmt.init, env);
|
|
6775
|
+
const { types: valueTypes, sources } = this.valueList(stmt.init, env, stmt.names.length);
|
|
5735
6776
|
stmt.names.forEach((target, i) => {
|
|
5736
6777
|
const inferred = valueTypes[i] ?? (stmt.init.length ? unknownType : nilType);
|
|
5737
6778
|
const source = sources[i];
|
|
@@ -5756,6 +6797,15 @@ var TypeAnalyzer = class {
|
|
|
5756
6797
|
});
|
|
5757
6798
|
return;
|
|
5758
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
|
+
}
|
|
5759
6809
|
case "FunctionDeclaration": {
|
|
5760
6810
|
this.checkParamOrder(stmt.func.params, stmt);
|
|
5761
6811
|
for (const sig of stmt.signatures ?? []) this.checkParamOrder(sig.params, stmt);
|
|
@@ -5811,7 +6861,7 @@ var TypeAnalyzer = class {
|
|
|
5811
6861
|
if (id !== void 0 && this.annotated.has(id)) this.applyContext(value, this.bindingType.get(id));
|
|
5812
6862
|
}
|
|
5813
6863
|
});
|
|
5814
|
-
const { types: valueTypes, sources } = this.valueList(stmt.values, env);
|
|
6864
|
+
const { types: valueTypes, sources } = this.valueList(stmt.values, env, stmt.targets.length);
|
|
5815
6865
|
stmt.targets.forEach((target, i) => {
|
|
5816
6866
|
const vt = valueTypes[i] ?? unknownType;
|
|
5817
6867
|
const source = sources[i];
|
|
@@ -5919,7 +6969,8 @@ var TypeAnalyzer = class {
|
|
|
5919
6969
|
stmt.arguments.forEach((a, i) => this.applyContext(a, declared.elements[i]));
|
|
5920
6970
|
}
|
|
5921
6971
|
}
|
|
5922
|
-
const
|
|
6972
|
+
const want = declared?.kind === "tuple" && declared.isPack ? declared.elements.length : 0;
|
|
6973
|
+
const { types, sources } = this.valueList(stmt.arguments, env, want);
|
|
5923
6974
|
this.checkReturn(stmt, declared, types, sources, env);
|
|
5924
6975
|
if (this.returnTypes) {
|
|
5925
6976
|
this.returnTypes.push(stmt.arguments.length === 0 ? nilType : types.length === 1 ? types[0] : tuple([...types], true));
|
|
@@ -5930,7 +6981,8 @@ var TypeAnalyzer = class {
|
|
|
5930
6981
|
this.visitStatement(stmt.declaration, env);
|
|
5931
6982
|
return;
|
|
5932
6983
|
case "ExportDefaultStatement":
|
|
5933
|
-
this.
|
|
6984
|
+
if (stmt.declaration.type === "ClassDeclaration") this.visitStatement(stmt.declaration, env);
|
|
6985
|
+
else this.infer(stmt.declaration, env);
|
|
5934
6986
|
return;
|
|
5935
6987
|
case "ExportNamedStatement": {
|
|
5936
6988
|
if (stmt.source) {
|
|
@@ -6009,12 +7061,35 @@ var TypeAnalyzer = class {
|
|
|
6009
7061
|
* `sources` maps each produced value back to the expression it came from
|
|
6010
7062
|
* (undefined for the 2nd and later values of a multi-value call), so the
|
|
6011
7063
|
* caller can still do contextual typing against the written expression. */
|
|
6012
|
-
valueList(exprs, env) {
|
|
7064
|
+
valueList(exprs, env, want = 0) {
|
|
6013
7065
|
const types = [];
|
|
6014
7066
|
const sources = [];
|
|
6015
7067
|
exprs.forEach((e, i) => {
|
|
6016
7068
|
const t = this.infer(e, env);
|
|
6017
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
|
+
}
|
|
6018
7093
|
if (last && t.kind === "tuple" && t.isPack && producesMultipleValues(e)) {
|
|
6019
7094
|
t.elements.forEach((el, j) => {
|
|
6020
7095
|
types.push(el);
|
|
@@ -6103,7 +7178,8 @@ var TypeAnalyzer = class {
|
|
|
6103
7178
|
/** A parameter's type: annotation, else a shape synthesized from a
|
|
6104
7179
|
* destructuring pattern, else inferred from its default, else `any`. */
|
|
6105
7180
|
paramType(p, env) {
|
|
6106
|
-
|
|
7181
|
+
const receiver = p.name === "self" || p.name === "this";
|
|
7182
|
+
if (!p.typeAnnotation && !p.pattern && !p.default && receiver && this.selfType) {
|
|
6107
7183
|
return this.selfType;
|
|
6108
7184
|
}
|
|
6109
7185
|
if (p.typeAnnotation) {
|
|
@@ -6111,6 +7187,7 @@ var TypeAnalyzer = class {
|
|
|
6111
7187
|
if (p.default) this.applyContext(p.default, t);
|
|
6112
7188
|
return p.optional ? optional(t) : t;
|
|
6113
7189
|
}
|
|
7190
|
+
if (p.rest) return arrayOf(unknownType);
|
|
6114
7191
|
if (p.pattern) return this.patternToType(p.pattern, env);
|
|
6115
7192
|
if (p.default) return widen(this.infer(p.default, env));
|
|
6116
7193
|
return this.contextualParams.get(p) ?? anyType;
|
|
@@ -6131,6 +7208,11 @@ var TypeAnalyzer = class {
|
|
|
6131
7208
|
this.expectedTypeOf.set(e, expected);
|
|
6132
7209
|
if (e.type === "ArrayExpression") return this.applyArrayContext(e, expected);
|
|
6133
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
|
+
}
|
|
6134
7216
|
if (e.type !== "FunctionExpression") return;
|
|
6135
7217
|
const members = expected.kind === "union" ? expected.types : [expected];
|
|
6136
7218
|
const signatures = members.flatMap((m) => this.overloadsOf(this.expand(m)));
|
|
@@ -6156,7 +7238,7 @@ var TypeAnalyzer = class {
|
|
|
6156
7238
|
const target = this.expectedMembers(expected).find((m) => m.kind === "array" || m.kind === "tuple");
|
|
6157
7239
|
if (!target) return;
|
|
6158
7240
|
if (!e.elements.length) {
|
|
6159
|
-
|
|
7241
|
+
this.contextualArrays.set(e, target);
|
|
6160
7242
|
return;
|
|
6161
7243
|
}
|
|
6162
7244
|
e.elements.forEach((element, i) => {
|
|
@@ -6171,13 +7253,15 @@ var TypeAnalyzer = class {
|
|
|
6171
7253
|
const objects = this.expectedMembers(expected).filter((m) => m.kind === "object");
|
|
6172
7254
|
if (!objects.length) return;
|
|
6173
7255
|
for (const field of e.fields) {
|
|
6174
|
-
if (field.type !== "TableFieldNamed") continue;
|
|
6175
|
-
const key = field.key.type === "Identifier" ? field.key.name : field.key.value;
|
|
7256
|
+
if (field.type !== "TableFieldNamed" && field.type !== "TableFieldShorthand") continue;
|
|
7257
|
+
const key = field.type === "TableFieldShorthand" ? field.name.name : field.key.type === "Identifier" ? field.key.name : field.key.value;
|
|
6176
7258
|
const types = objects.flatMap((o) => {
|
|
6177
7259
|
const property = o.properties.get(key);
|
|
6178
7260
|
return property ? [property.type] : o.indexer ? [o.indexer.value] : [];
|
|
6179
7261
|
});
|
|
6180
|
-
if (types.length)
|
|
7262
|
+
if (types.length) {
|
|
7263
|
+
this.applyContext(field.type === "TableFieldShorthand" ? field.name : field.value, union(types));
|
|
7264
|
+
}
|
|
6181
7265
|
}
|
|
6182
7266
|
}
|
|
6183
7267
|
/** The members of an expected type worth matching a literal against:
|
|
@@ -6214,13 +7298,26 @@ var TypeAnalyzer = class {
|
|
|
6214
7298
|
}
|
|
6215
7299
|
return tuple(target.elements.map((el) => el ? leaf(el.value, el.default) : anyType));
|
|
6216
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
|
+
}
|
|
6217
7314
|
/** The type of `...` in each function body being walked. */
|
|
6218
7315
|
varargs = [];
|
|
6219
7316
|
/** What each function body being walked declared it returns. */
|
|
6220
7317
|
declaredReturns = [];
|
|
6221
7318
|
/** Run `body` with `...` and `return` as `func` declares them. */
|
|
6222
7319
|
withVarargs(func, body) {
|
|
6223
|
-
this.varargs.push(
|
|
7320
|
+
this.varargs.push(this.varargElement(func));
|
|
6224
7321
|
this.declaredReturns.push(func.predicate ? booleanType : func.returnType ? this.resolveType(func.returnType) : void 0);
|
|
6225
7322
|
try {
|
|
6226
7323
|
return body();
|
|
@@ -6301,6 +7398,13 @@ var TypeAnalyzer = class {
|
|
|
6301
7398
|
const param = p.type.kind === "typeParam" && p.type.constraint ? { ...p.type, constraint: this.reduceType(p.type.constraint) } : p.type;
|
|
6302
7399
|
unify(p.type, keepsLiterals(param) ? arg : widen(arg), vars, subst);
|
|
6303
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
|
+
}
|
|
6304
7408
|
for (const name of f.typeParams ?? []) {
|
|
6305
7409
|
if (!subst.has(name)) subst.set(name, f.typeParamDefaults?.[name] ?? unknownType);
|
|
6306
7410
|
}
|
|
@@ -6325,11 +7429,11 @@ var TypeAnalyzer = class {
|
|
|
6325
7429
|
* after every concrete signature has been tried. That ordering is what
|
|
6326
7430
|
* lets `typeof` declare `(v: number) -> "number"` alongside a trailing
|
|
6327
7431
|
* `<T>(v: T) -> string` and still pick the precise one. */
|
|
6328
|
-
pickOverload(fns, argTypes, argsFor) {
|
|
7432
|
+
pickOverload(fns, argTypes, argsFor, spread) {
|
|
6329
7433
|
for (const generic of [false, true]) {
|
|
6330
7434
|
for (const f of fns) {
|
|
6331
7435
|
if ((f.typeParams?.length ?? 0) > 0 !== generic) continue;
|
|
6332
|
-
if (this.overloadAccepts(f, argsFor ? argsFor(f) : argTypes)) return f;
|
|
7436
|
+
if (this.overloadAccepts(f, argsFor ? argsFor(f) : argTypes, spread)) return f;
|
|
6333
7437
|
}
|
|
6334
7438
|
}
|
|
6335
7439
|
return void 0;
|
|
@@ -6364,12 +7468,27 @@ var TypeAnalyzer = class {
|
|
|
6364
7468
|
* own type parameters stand for what the call would infer, so each is
|
|
6365
7469
|
* checked only against its constraint — `<K extends keyof Services>`
|
|
6366
7470
|
* accepts `"Players"` but not `""`. */
|
|
6367
|
-
overloadAccepts(f, argTypes) {
|
|
6368
|
-
|
|
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
|
+
}
|
|
6369
7487
|
const params = this.boundParams(f);
|
|
6370
7488
|
return f.params.every((p, i) => {
|
|
6371
|
-
|
|
6372
|
-
|
|
7489
|
+
const arg = at(i);
|
|
7490
|
+
if (arg === void 0) return p.optional === true;
|
|
7491
|
+
return isAssignable(arg, params[i]);
|
|
6373
7492
|
});
|
|
6374
7493
|
}
|
|
6375
7494
|
/** A signature's parameter types as a call site sees them before inference:
|
|
@@ -6399,17 +7518,40 @@ var TypeAnalyzer = class {
|
|
|
6399
7518
|
}
|
|
6400
7519
|
/** Record what each written argument is expected to be — see
|
|
6401
7520
|
* `TypeAnalysis.expectedTypeOf`. */
|
|
6402
|
-
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)));
|
|
6403
7524
|
written.forEach((arg, j) => {
|
|
6404
7525
|
const candidates = [];
|
|
6405
7526
|
for (const f of fns) {
|
|
6406
7527
|
const i = j + selfOf(f);
|
|
6407
|
-
const
|
|
7528
|
+
const params = paramsOf.get(f);
|
|
7529
|
+
const param = i < params.length ? params[i] : f.varargs;
|
|
6408
7530
|
if (param) candidates.push(param);
|
|
6409
7531
|
}
|
|
6410
7532
|
if (candidates.length) this.expectedTypeOf.set(arg, union(candidates));
|
|
6411
7533
|
});
|
|
6412
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
|
+
}
|
|
6413
7555
|
/** No signature accepts the call, and the argument count is not the
|
|
6414
7556
|
* problem: say which argument is wrong, the way TypeScript does. */
|
|
6415
7557
|
/** Check what was written against the parameters as this call's own type
|
|
@@ -6420,10 +7562,11 @@ var TypeAnalyzer = class {
|
|
|
6420
7562
|
if (!this.emitDiagnostics || !f.typeParams?.length) return;
|
|
6421
7563
|
const subst = this.inferTypeArgs(f, [...argTypes]);
|
|
6422
7564
|
for (const bound of subst.values()) if (bound.kind === "unknown") return;
|
|
6423
|
-
|
|
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++) {
|
|
6424
7567
|
const arg = argTypes[i];
|
|
6425
|
-
const declared =
|
|
6426
|
-
if (arg === void 0 || !containsTypeParam(declared)) continue;
|
|
7568
|
+
const declared = declaredAt(i);
|
|
7569
|
+
if (arg === void 0 || declared === void 0 || !containsTypeParam(declared)) continue;
|
|
6427
7570
|
const expected = this.reduceType(substitute(declared, subst));
|
|
6428
7571
|
if (containsTypeParam(expected) || expected.kind === "any" || expected.kind === "unknown") continue;
|
|
6429
7572
|
if (isAssignable(arg, expected) || isAssignable(widen(arg), expected)) continue;
|
|
@@ -6444,15 +7587,26 @@ var TypeAnalyzer = class {
|
|
|
6444
7587
|
const args = argsFor(f);
|
|
6445
7588
|
const params = this.boundParams(f);
|
|
6446
7589
|
const self = selfOf(f);
|
|
7590
|
+
const spread = this.spreadOf(written, self);
|
|
6447
7591
|
for (let i = 0; i < f.params.length; i++) {
|
|
6448
|
-
const arg = args[i];
|
|
6449
|
-
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;
|
|
6450
7595
|
this.diagnostics.push({
|
|
6451
|
-
node: written[i - self] ?? call,
|
|
7596
|
+
node: (spread && i >= spread.index ? written[spread.index - self] : written[i - self]) ?? call,
|
|
6452
7597
|
message: `Argument of type '${formatType(arg)}' is not assignable to parameter of type '${briefType(params[i])}'`
|
|
6453
7598
|
});
|
|
6454
7599
|
return;
|
|
6455
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
|
+
}
|
|
6456
7610
|
}
|
|
6457
7611
|
/** A required parameter may not follow an optional one — otherwise the
|
|
6458
7612
|
* optional one could never actually be omitted. Same rule as TypeScript,
|
|
@@ -6461,6 +7615,10 @@ var TypeAnalyzer = class {
|
|
|
6461
7615
|
if (!this.emitDiagnostics) return;
|
|
6462
7616
|
let seenOptional;
|
|
6463
7617
|
for (const p of params) {
|
|
7618
|
+
if (p.rest === true) {
|
|
7619
|
+
this.checkRestType(p, node);
|
|
7620
|
+
continue;
|
|
7621
|
+
}
|
|
6464
7622
|
const isOptional = p.optional === true || p.default !== void 0;
|
|
6465
7623
|
if (isOptional) {
|
|
6466
7624
|
if (seenOptional === void 0) seenOptional = p.name ?? "parameter";
|
|
@@ -6486,8 +7644,10 @@ var TypeAnalyzer = class {
|
|
|
6486
7644
|
* when *no* overload accepts the count, so an overload set still reports
|
|
6487
7645
|
* once, against its first signature. Returns whether the count fits, so
|
|
6488
7646
|
* an argument's type is only complained about when its count is right. */
|
|
6489
|
-
checkArity(node, fns, argCount, selfArgs) {
|
|
7647
|
+
checkArity(node, fns, argCount, selfArgs, spread) {
|
|
6490
7648
|
if (!fns.length) return true;
|
|
7649
|
+
if (spread && !spread.elements) return true;
|
|
7650
|
+
if (spread?.elements) argCount += spread.elements.length - 1;
|
|
6491
7651
|
const fits = fns.some((f) => {
|
|
6492
7652
|
const { min: min2, max: max2 } = this.arityOf(f);
|
|
6493
7653
|
const n = argCount + selfArgs;
|
|
@@ -6530,7 +7690,7 @@ var TypeAnalyzer = class {
|
|
|
6530
7690
|
return type;
|
|
6531
7691
|
};
|
|
6532
7692
|
return record(this.withTypeParams(sig.generics, () => {
|
|
6533
|
-
const params = sig.params.map((p) => ({
|
|
7693
|
+
const params = sig.params.filter((p) => !p.rest).map((p) => ({
|
|
6534
7694
|
name: p.pattern ? void 0 : p.name,
|
|
6535
7695
|
type: this.paramType(p, /* @__PURE__ */ new Map()),
|
|
6536
7696
|
optional: p.optional || p.default !== void 0
|
|
@@ -6538,12 +7698,23 @@ var TypeAnalyzer = class {
|
|
|
6538
7698
|
return fn(
|
|
6539
7699
|
params,
|
|
6540
7700
|
sig.returnType ? this.resolveType(sig.returnType) : sig.predicate ? booleanType : anyType,
|
|
6541
|
-
|
|
7701
|
+
this.varargElement(sig),
|
|
6542
7702
|
names,
|
|
6543
7703
|
this.resolvePredicate(sig.predicate, params)
|
|
6544
7704
|
);
|
|
6545
7705
|
}));
|
|
6546
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
|
+
}
|
|
6547
7718
|
/** Turn a parsed `v is T` / `asserts v` annotation into a `TypePredicate`,
|
|
6548
7719
|
* resolving the named parameter to its index. A guard naming a parameter
|
|
6549
7720
|
* the function does not have is dropped rather than mis-narrowing an
|
|
@@ -6561,17 +7732,18 @@ var TypeAnalyzer = class {
|
|
|
6561
7732
|
inferFunctionBody(func, env) {
|
|
6562
7733
|
const names = func.generics.map((g) => g.name);
|
|
6563
7734
|
return this.withTypeParams(func.generics, () => {
|
|
6564
|
-
const params = func.params.
|
|
7735
|
+
const params = func.params.flatMap((p) => {
|
|
6565
7736
|
const type = this.paramType(p, env);
|
|
6566
7737
|
if (!p.pattern) {
|
|
6567
7738
|
const id = this.bindingIdByName(p.name, p);
|
|
6568
7739
|
if (id !== void 0 && !this.bindingType.has(id)) this.bindingType.set(id, type);
|
|
6569
7740
|
}
|
|
6570
|
-
return
|
|
7741
|
+
if (p.rest) return [];
|
|
7742
|
+
return [{
|
|
6571
7743
|
name: p.pattern ? void 0 : p.name,
|
|
6572
7744
|
type,
|
|
6573
7745
|
optional: p.optional || p.default !== void 0
|
|
6574
|
-
};
|
|
7746
|
+
}];
|
|
6575
7747
|
});
|
|
6576
7748
|
const bodyEnv = forkEnv(env);
|
|
6577
7749
|
for (const p of func.params) {
|
|
@@ -6596,7 +7768,7 @@ var TypeAnalyzer = class {
|
|
|
6596
7768
|
return fn(
|
|
6597
7769
|
params,
|
|
6598
7770
|
returns,
|
|
6599
|
-
|
|
7771
|
+
this.varargElement(func),
|
|
6600
7772
|
names,
|
|
6601
7773
|
this.resolvePredicate(func.predicate, params)
|
|
6602
7774
|
);
|
|
@@ -6810,6 +7982,13 @@ var TypeAnalyzer = class {
|
|
|
6810
7982
|
if (src.kind === "array") return [numberType, src.element];
|
|
6811
7983
|
}
|
|
6812
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
|
+
}
|
|
6813
7992
|
const t = this.expand(iterType);
|
|
6814
7993
|
if (t.kind === "array") return varCount >= 2 ? [numberType, t.element] : [t.element, unknownType];
|
|
6815
7994
|
if (t.kind === "object") {
|
|
@@ -6961,7 +8140,21 @@ var TypeAnalyzer = class {
|
|
|
6961
8140
|
expand(t) {
|
|
6962
8141
|
if (t.kind !== "genericRef") return t;
|
|
6963
8142
|
const def = this.aliasDefs.get(t.name);
|
|
6964
|
-
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;
|
|
6965
8158
|
const key = t.typeArguments.length ? formatType(t) : t.name;
|
|
6966
8159
|
const cached = this.expandCache.get(key);
|
|
6967
8160
|
if (cached) return cached;
|
|
@@ -6976,6 +8169,126 @@ var TypeAnalyzer = class {
|
|
|
6976
8169
|
this.resolvingAliases.delete(t.name);
|
|
6977
8170
|
}
|
|
6978
8171
|
}
|
|
8172
|
+
/** `names:filter(f)`, `text:trim()` — the methods arrays and strings have.
|
|
8173
|
+
* They are written in the prelude as `ArrayMethods<T>` and
|
|
8174
|
+
* `StringMethods`, so a file (or a type library) that declares one of
|
|
8175
|
+
* those names again replaces the whole set, and nothing here is a special
|
|
8176
|
+
* case in the analyzer. The build lowers each call to a plain function. */
|
|
8177
|
+
builtInMethod(t, name) {
|
|
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;
|
|
8183
|
+
const def = methodTable === void 0 ? void 0 : this.aliasDefs.get(methodTable);
|
|
8184
|
+
if (!def || def.class) return void 0;
|
|
8185
|
+
const table = this.expand(this.instantiateAlias(def, element !== void 0 ? [element] : []));
|
|
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];
|
|
8189
|
+
const property = part.kind === "object" ? part.properties.get(name) : void 0;
|
|
8190
|
+
if (property) return property.type;
|
|
8191
|
+
}
|
|
8192
|
+
return void 0;
|
|
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
|
+
}
|
|
6979
8292
|
propertyType(raw, name) {
|
|
6980
8293
|
const t = this.deferredAccess(this.expand(raw));
|
|
6981
8294
|
if (t.kind === "object") {
|
|
@@ -6983,7 +8296,13 @@ var TypeAnalyzer = class {
|
|
|
6983
8296
|
if (p) return p.optional ? optional(p.type) : p.type;
|
|
6984
8297
|
if (t.indexer) return t.indexer.value;
|
|
6985
8298
|
}
|
|
6986
|
-
|
|
8299
|
+
const built = this.builtInMethod(t, name);
|
|
8300
|
+
if (built) return built;
|
|
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
|
+
}
|
|
6987
8306
|
if (t.kind === "intersection") {
|
|
6988
8307
|
const parts = t.types.map((m) => this.propertyType(m, name)).filter((p) => p.kind !== "unknown");
|
|
6989
8308
|
if (parts.length) return intersection(parts);
|
|
@@ -6991,6 +8310,10 @@ var TypeAnalyzer = class {
|
|
|
6991
8310
|
if (t.kind === "typeParam" && t.constraint) return this.propertyType(t.constraint, name);
|
|
6992
8311
|
if (t.kind === "difference") return this.propertyType(t.base, name);
|
|
6993
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
|
+
}
|
|
6994
8317
|
return unknownType;
|
|
6995
8318
|
}
|
|
6996
8319
|
/** `t[k]`. A statically known string key resolves against the declared
|
|
@@ -7066,6 +8389,20 @@ var TypeAnalyzer = class {
|
|
|
7066
8389
|
// `...` holds what the function declared it takes.
|
|
7067
8390
|
case "VarargExpression":
|
|
7068
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
|
+
}
|
|
7069
8406
|
// Broken syntax is reported by the parser; nothing more to say.
|
|
7070
8407
|
case "ErrorExpression":
|
|
7071
8408
|
return anyType;
|
|
@@ -7171,6 +8508,7 @@ var TypeAnalyzer = class {
|
|
|
7171
8508
|
const { type: obj, shortCircuits } = this.chainObject(expr, expr.object, env);
|
|
7172
8509
|
const key = this.refKeyOf(expr);
|
|
7173
8510
|
const narrowed = key === void 0 ? void 0 : env.get(key);
|
|
8511
|
+
this.checkStringMember(expr, obj, literal(expr.property.name));
|
|
7174
8512
|
return this.chainResult(expr, narrowed ?? this.propertyType(obj, expr.property.name), shortCircuits);
|
|
7175
8513
|
}
|
|
7176
8514
|
case "IndexExpression": {
|
|
@@ -7178,12 +8516,33 @@ var TypeAnalyzer = class {
|
|
|
7178
8516
|
const idx = this.infer(expr.index, env);
|
|
7179
8517
|
const key = this.refKeyOf(expr);
|
|
7180
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);
|
|
7181
8522
|
return this.chainResult(expr, narrowed ?? this.indexedType(obj, idx), shortCircuits);
|
|
7182
8523
|
}
|
|
7183
8524
|
case "CallExpression": {
|
|
8525
|
+
if (expr.callee.type === "SuperExpression") return this.inferSuperCall(expr, env);
|
|
7184
8526
|
const { type: callee, shortCircuits } = this.chainObject(expr, expr.callee, env);
|
|
7185
8527
|
return this.chainResult(expr, this.inferCall(expr, callee, env), shortCircuits);
|
|
7186
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
|
+
}
|
|
7187
8546
|
case "MethodCallExpression": {
|
|
7188
8547
|
const { type: objType, shortCircuits } = this.chainObject(expr, expr.object, env);
|
|
7189
8548
|
return this.chainResult(expr, this.inferMethodCall(expr, objType, env), shortCircuits);
|
|
@@ -7202,16 +8561,56 @@ var TypeAnalyzer = class {
|
|
|
7202
8561
|
}
|
|
7203
8562
|
}
|
|
7204
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
|
+
}
|
|
7205
8602
|
inferCall(expr, callee, env) {
|
|
8603
|
+
this.checkAmbiguousCall(expr);
|
|
7206
8604
|
const fns = this.overloadsOf(callee);
|
|
7207
8605
|
const explicit = this.explicitTypeArguments(expr, fns);
|
|
7208
8606
|
const expected = this.expectedArguments(expr.arguments, fns, () => 0);
|
|
7209
8607
|
expr.arguments.forEach((a, i) => this.applyContext(a, expected[i]));
|
|
7210
8608
|
const argTypes = expr.arguments.map((a) => this.infer(a, env));
|
|
7211
8609
|
if (fns.length) {
|
|
7212
|
-
this.recordExpected(expr.arguments, fns, () => 0);
|
|
7213
|
-
const
|
|
7214
|
-
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);
|
|
7215
8614
|
const distributed = this.distributedReturn(fns, argTypes, picked, (_, args) => args);
|
|
7216
8615
|
if (distributed) return distributed;
|
|
7217
8616
|
if (picked) {
|
|
@@ -7223,6 +8622,21 @@ var TypeAnalyzer = class {
|
|
|
7223
8622
|
}
|
|
7224
8623
|
return callee.kind === "any" ? anyType : unknownType;
|
|
7225
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
|
+
}
|
|
7226
8640
|
inferMethodCall(expr, objType, env) {
|
|
7227
8641
|
const fns = this.overloadsOf(this.propertyType(objType, expr.method.name));
|
|
7228
8642
|
const explicit = this.explicitTypeArguments(expr, fns);
|
|
@@ -7232,9 +8646,11 @@ var TypeAnalyzer = class {
|
|
|
7232
8646
|
if (fns.length) {
|
|
7233
8647
|
const withSelf = (f) => this.takesSelf(f) ? [objType, ...argTypes] : argTypes;
|
|
7234
8648
|
const selfOf = (f) => this.takesSelf(f) ? 1 : 0;
|
|
7235
|
-
this.recordExpected(expr.arguments, fns, selfOf);
|
|
7236
|
-
const
|
|
7237
|
-
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);
|
|
7238
8654
|
const distributed = this.distributedReturn(
|
|
7239
8655
|
fns,
|
|
7240
8656
|
argTypes,
|
|
@@ -7336,7 +8752,21 @@ var TypeAnalyzer = class {
|
|
|
7336
8752
|
}
|
|
7337
8753
|
}
|
|
7338
8754
|
if (asConst && !hadSpread) return tuple(elems);
|
|
7339
|
-
return arrayOf(elems.length ? union(elems.map((t) =>
|
|
8755
|
+
return arrayOf(elems.length ? union(elems.map((t, i) => {
|
|
8756
|
+
const element = expr.elements[i];
|
|
8757
|
+
return asConst || !element || element.type === "SpreadElement" ? t : this.widenUnlessAsked(t, element);
|
|
8758
|
+
})) : unknownType);
|
|
8759
|
+
}
|
|
8760
|
+
/** A literal written inside a fresh table or array widens — `{ n = 1 }` is
|
|
8761
|
+
* `{ n: number }` — unless the surroundings said a literal belongs there.
|
|
8762
|
+
* `request({ Method: "GET" })` keeps `"GET"` when `Method` is a union of
|
|
8763
|
+
* string literals, exactly as TypeScript's contextual typing does, and
|
|
8764
|
+
* goes on widening to `string` when the parameter only says `string`.
|
|
8765
|
+
* The context was recorded by `applyContext` before the value was
|
|
8766
|
+
* inferred, so this is a lookup rather than a second pass. */
|
|
8767
|
+
widenUnlessAsked(value, at) {
|
|
8768
|
+
const wanted = this.expectedTypeOf.get(at);
|
|
8769
|
+
return wanted === void 0 ? widen(value) : this.keepContextualLiterals(value, wanted);
|
|
7340
8770
|
}
|
|
7341
8771
|
inferObject(expr, env, asConst) {
|
|
7342
8772
|
const entries = [];
|
|
@@ -7344,16 +8774,24 @@ var TypeAnalyzer = class {
|
|
|
7344
8774
|
for (const field of expr.fields) {
|
|
7345
8775
|
if (field.type === "TableFieldNamed") {
|
|
7346
8776
|
const key = field.key.type === "Identifier" ? field.key.name : field.key.value;
|
|
7347
|
-
const v = asConst ? this.inferAsConst(field.value, env) :
|
|
8777
|
+
const v = asConst ? this.inferAsConst(field.value, env) : this.widenUnlessAsked(this.infer(field.value, env), field.value);
|
|
7348
8778
|
entries.push([key, { type: v, optional: false, readonly: asConst }]);
|
|
7349
8779
|
} else if (field.type === "TableFieldShorthand") {
|
|
7350
8780
|
const v = this.infer(field.name, env);
|
|
7351
|
-
entries.push([field.name.name, {
|
|
8781
|
+
entries.push([field.name.name, {
|
|
8782
|
+
type: asConst ? v : this.widenUnlessAsked(v, field.name),
|
|
8783
|
+
optional: false,
|
|
8784
|
+
readonly: asConst
|
|
8785
|
+
}]);
|
|
7352
8786
|
} else if (field.type === "TableFieldComputed") {
|
|
7353
8787
|
const k = this.infer(field.key, env);
|
|
7354
8788
|
const v = this.infer(field.value, env);
|
|
7355
8789
|
if (k.kind === "literal" && typeof k.value === "string") {
|
|
7356
|
-
entries.push([k.value, {
|
|
8790
|
+
entries.push([k.value, {
|
|
8791
|
+
type: asConst ? v : this.widenUnlessAsked(v, field.value),
|
|
8792
|
+
optional: false,
|
|
8793
|
+
readonly: asConst
|
|
8794
|
+
}]);
|
|
7357
8795
|
} else {
|
|
7358
8796
|
indexer = mergeIndexer(indexer, { key: widen(k), value: asConst ? v : widen(v) });
|
|
7359
8797
|
}
|
|
@@ -7861,7 +9299,39 @@ var TypeAnalyzer = class {
|
|
|
7861
9299
|
* out. Every place that has to line arguments up with parameters goes
|
|
7862
9300
|
* through here so the two sides cannot drift apart. */
|
|
7863
9301
|
takesSelf(f) {
|
|
7864
|
-
|
|
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;
|
|
7865
9335
|
}
|
|
7866
9336
|
/** A function type as a list of call signatures: a lone function is a
|
|
7867
9337
|
* one-element list, an intersection is the overload set in source order. */
|
|
@@ -7916,6 +9386,8 @@ var TypeAnalyzer = class {
|
|
|
7916
9386
|
type = this.resolveType(statement.valueType);
|
|
7917
9387
|
} else if (statement.type === "FunctionDeclaration") {
|
|
7918
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);
|
|
7919
9391
|
} else if (statement.type === "VariableDeclaration") {
|
|
7920
9392
|
const target = statement.names[index];
|
|
7921
9393
|
if (target.type === "IdentifierPattern" && target.typeAnnotation) {
|
|
@@ -7952,6 +9424,10 @@ var TypeAnalyzer = class {
|
|
|
7952
9424
|
return;
|
|
7953
9425
|
}
|
|
7954
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
|
+
}
|
|
7955
9431
|
if (record.type === "FunctionDeclaration" && record.name) {
|
|
7956
9432
|
const id = this.bindingIdByName(record.name.name, record.name);
|
|
7957
9433
|
if (id !== void 0) out.set(id, { statement: node, index: 0 });
|
|
@@ -8178,6 +9654,7 @@ function offsetPosition(source, offset) {
|
|
|
8178
9654
|
var import_node_path2 = require("path");
|
|
8179
9655
|
function resolveTypeLibraries(config, host = nodeHost) {
|
|
8180
9656
|
const files = [];
|
|
9657
|
+
const lowerings = [];
|
|
8181
9658
|
const problems = [];
|
|
8182
9659
|
const loaded = /* @__PURE__ */ new Set();
|
|
8183
9660
|
const addFile = (file) => {
|
|
@@ -8195,6 +9672,8 @@ function resolveTypeLibraries(config, host = nodeHost) {
|
|
|
8195
9672
|
if (found) addPackage(found.directory, found.file, visiting);
|
|
8196
9673
|
}
|
|
8197
9674
|
addFile(entryFile);
|
|
9675
|
+
const lowering = loweringModule(directory, host, problems, config);
|
|
9676
|
+
if (lowering) lowerings.push(lowering);
|
|
8198
9677
|
};
|
|
8199
9678
|
for (const entry of config.types) {
|
|
8200
9679
|
const relative = entry.startsWith("./") || entry.startsWith("../") || entry.startsWith("/") || /^[A-Za-z]:[\\/]/.test(entry);
|
|
@@ -8221,7 +9700,19 @@ function resolveTypeLibraries(config, host = nodeHost) {
|
|
|
8221
9700
|
});
|
|
8222
9701
|
}
|
|
8223
9702
|
}
|
|
8224
|
-
return { files, problems };
|
|
9703
|
+
return { files, lowerings, problems };
|
|
9704
|
+
}
|
|
9705
|
+
function loweringModule(directory, host, problems, config) {
|
|
9706
|
+
const manifest = readJson((0, import_node_path2.join)(directory, "package.json"), host);
|
|
9707
|
+
const declared = manifest?.luaut?.lowering;
|
|
9708
|
+
if (typeof declared !== "string") return void 0;
|
|
9709
|
+
const from = typeof manifest?.name === "string" ? manifest.name : directory;
|
|
9710
|
+
const file = (0, import_node_path2.resolve)(directory, declared);
|
|
9711
|
+
if (host.readFile(file) === void 0) {
|
|
9712
|
+
problems.push({ file: config.path, message: `'${from}' names a lowering module '${declared}', which is not there` });
|
|
9713
|
+
return void 0;
|
|
9714
|
+
}
|
|
9715
|
+
return { file, from };
|
|
8225
9716
|
}
|
|
8226
9717
|
var ENTRY_FILE = "index.d.luaut";
|
|
8227
9718
|
function packageEntry(directory, host) {
|
|
@@ -8456,6 +9947,7 @@ var index_default = luautparser;
|
|
|
8456
9947
|
resolveModulePath,
|
|
8457
9948
|
resolveTypeLibraries,
|
|
8458
9949
|
setAliasExpander,
|
|
9950
|
+
setDeferredBound,
|
|
8459
9951
|
sourceMapTypes,
|
|
8460
9952
|
stringType,
|
|
8461
9953
|
stripJsonComments,
|