@tilua/parser 1.1.0 → 2.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 +170 -44
- package/dist/index.cjs +1137 -426
- package/dist/index.d.cts +98 -64
- package/dist/index.d.ts +98 -64
- package/dist/index.js +1136 -427
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -595,6 +595,9 @@ function applyDirectives(directives, diagnostics, lineOf) {
|
|
|
595
595
|
var UNUSED_EXPECT_ERROR = "Unused '@tilua-expect-error' directive";
|
|
596
596
|
|
|
597
597
|
// src/ast/builders.ts
|
|
598
|
+
var NO_DOTS = "tilua has no bare '...': take a rest parameter, '...args: T[]', or 'scriptArgs' for the script's own";
|
|
599
|
+
var ACCESSIBILITIES = /* @__PURE__ */ new Set(["public", "private", "protected"]);
|
|
600
|
+
var CLASS_MODIFIERS = /* @__PURE__ */ new Set([...ACCESSIBILITIES, "static", "abstract", "override", "readonly"]);
|
|
598
601
|
function bindThis(func, at) {
|
|
599
602
|
bindThisParam(func.params, at);
|
|
600
603
|
func.isMethod = true;
|
|
@@ -862,11 +865,16 @@ var Parser = class {
|
|
|
862
865
|
return this.attempt(() => this.parseExpression(), stop, (start, from) => this.errorExpression(start, from));
|
|
863
866
|
}
|
|
864
867
|
/** A comma-separated list of values: a `return`'s, a declaration's, an
|
|
865
|
-
* assignment's.
|
|
866
|
-
* arguments
|
|
868
|
+
* assignment's. Each is one value; a spread belongs in a call's
|
|
869
|
+
* arguments or an array, and is reported here — then read as one, so
|
|
870
|
+
* the rest still means something. */
|
|
867
871
|
expressionListOr(stop) {
|
|
868
872
|
const until = () => stop() || this.checkPunctuator(",");
|
|
869
|
-
const item = () =>
|
|
873
|
+
const item = () => {
|
|
874
|
+
if (!(this.checkOperator("...") && this.startsSpread())) return this.expressionOr(until);
|
|
875
|
+
this.problem("A spread goes in a call's arguments or an array: take values out of an array with a destructuring, 'const [a, b] = xs'");
|
|
876
|
+
return this.parseSpreadArgument(until);
|
|
877
|
+
};
|
|
870
878
|
const list = [item()];
|
|
871
879
|
while (this.matchPunctuator(",")) list.push(item());
|
|
872
880
|
return list;
|
|
@@ -1231,7 +1239,7 @@ var Parser = class {
|
|
|
1231
1239
|
if (t.type === "Identifier" && t.value === "type" && this.peek(1).type === "Identifier") {
|
|
1232
1240
|
return this.parseTypeAliasStatement();
|
|
1233
1241
|
}
|
|
1234
|
-
if (
|
|
1242
|
+
if (this.startsClassDeclaration()) {
|
|
1235
1243
|
return this.parseClassDeclaration();
|
|
1236
1244
|
}
|
|
1237
1245
|
if (t.type === "Identifier" && t.value === "declare") {
|
|
@@ -1268,8 +1276,6 @@ var Parser = class {
|
|
|
1268
1276
|
type: "FunctionTypeNode",
|
|
1269
1277
|
generics: head.generics,
|
|
1270
1278
|
params,
|
|
1271
|
-
hasVarargs: head.hasVarargs,
|
|
1272
|
-
varargType: head.varargTypeAnnotation,
|
|
1273
1279
|
returnType: head.returnType ?? { type: "TypeReference", base: head.predicate ? "boolean" : "unknown", typeArguments: [], ...spanFrom(start, start) },
|
|
1274
1280
|
predicate: head.predicate,
|
|
1275
1281
|
...spanFrom(start, this.previous())
|
|
@@ -1408,7 +1414,7 @@ var Parser = class {
|
|
|
1408
1414
|
this.advance();
|
|
1409
1415
|
if (this.checkIdentifierValue("default")) {
|
|
1410
1416
|
this.advance();
|
|
1411
|
-
const declaration = this.
|
|
1417
|
+
const declaration = this.startsClassDeclaration() ? this.parseClassDeclaration() : this.parseExpression(0);
|
|
1412
1418
|
return { type: "ExportDefaultStatement", declaration, ...spanFrom(start, this.previous()) };
|
|
1413
1419
|
}
|
|
1414
1420
|
if (this.checkIdentifierValue("type") && this.peek(1).type === "Identifier") {
|
|
@@ -1419,7 +1425,7 @@ var Parser = class {
|
|
|
1419
1425
|
const declaration = this.parseVariableDeclaration();
|
|
1420
1426
|
return { type: "ExportStatement", declaration, ...spanFrom(start, this.previous()) };
|
|
1421
1427
|
}
|
|
1422
|
-
if (this.
|
|
1428
|
+
if (this.startsClassDeclaration()) {
|
|
1423
1429
|
const declaration = this.parseClassDeclaration();
|
|
1424
1430
|
return { type: "ExportStatement", declaration, ...spanFrom(start, this.previous()) };
|
|
1425
1431
|
}
|
|
@@ -1534,9 +1540,11 @@ var Parser = class {
|
|
|
1534
1540
|
const start = this.current();
|
|
1535
1541
|
this.expectKeyword("for");
|
|
1536
1542
|
this.expectPunctuator("(");
|
|
1543
|
+
const declared = this.checkKeyword("const") || this.checkKeyword("let") ? this.advance().value : void 0;
|
|
1537
1544
|
const first = this.parseBindingTarget(true);
|
|
1538
1545
|
const untilDo = () => this.checkPunctuator(")");
|
|
1539
1546
|
if (first.type === "IdentifierPattern" && this.matchOperator("=")) {
|
|
1547
|
+
if (declared) this.problemAt(start, `A counting loop names its variable without '${declared}': 'for (i = 1, 10)'`);
|
|
1540
1548
|
const from = this.expressionOr(() => untilDo() || this.checkPunctuator(","));
|
|
1541
1549
|
this.expectPunctuator(",");
|
|
1542
1550
|
const to = this.expressionOr(() => untilDo() || this.checkPunctuator(","));
|
|
@@ -1555,17 +1563,24 @@ var Parser = class {
|
|
|
1555
1563
|
...spanFrom(start, this.previous())
|
|
1556
1564
|
};
|
|
1557
1565
|
}
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1566
|
+
if (this.checkPunctuator(",")) {
|
|
1567
|
+
this.problemAt(this.current(), "A loop takes one value each time: take its parts with a destructuring, 'for (const [k, v] in pairs(t))'");
|
|
1568
|
+
while (this.matchPunctuator(",")) this.parseBindingTarget(true);
|
|
1569
|
+
} else if (!declared) {
|
|
1570
|
+
this.problemAt(start, "A loop over a source names its value with 'const' or 'let': 'for (const item in source)'");
|
|
1561
1571
|
}
|
|
1562
1572
|
this.expectKeyword("in");
|
|
1563
|
-
const
|
|
1573
|
+
const iterator = this.expressionOr(() => untilDo() || this.checkPunctuator(","));
|
|
1574
|
+
if (this.checkPunctuator(",")) {
|
|
1575
|
+
this.problemAt(this.current(), "A loop has one source: an array, a table, an iterator function or an iteration '[step, state, first]'");
|
|
1576
|
+
while (this.matchPunctuator(",")) this.expressionOr(() => untilDo() || this.checkPunctuator(","));
|
|
1577
|
+
}
|
|
1564
1578
|
const body = this.parseForBody();
|
|
1565
1579
|
return {
|
|
1566
1580
|
type: "GenericForStatement",
|
|
1567
|
-
|
|
1568
|
-
|
|
1581
|
+
kind: declared ?? "const",
|
|
1582
|
+
variable: first,
|
|
1583
|
+
iterator,
|
|
1569
1584
|
body,
|
|
1570
1585
|
...spanFrom(start, this.previous())
|
|
1571
1586
|
};
|
|
@@ -1635,7 +1650,10 @@ var Parser = class {
|
|
|
1635
1650
|
* the compiler must not accept it, and recorded inside. The message says
|
|
1636
1651
|
* what is wrong on its own — no token is appended. */
|
|
1637
1652
|
problem(message) {
|
|
1638
|
-
|
|
1653
|
+
this.problemAt(this.current(), message);
|
|
1654
|
+
}
|
|
1655
|
+
/** `problem`, pointed at `t` rather than at where parsing has got to. */
|
|
1656
|
+
problemAt(t, message) {
|
|
1639
1657
|
const error = new ParseError(message, t.line.start, t.column.start);
|
|
1640
1658
|
if (!this.recover) throw error;
|
|
1641
1659
|
this.record(error);
|
|
@@ -1648,10 +1666,13 @@ var Parser = class {
|
|
|
1648
1666
|
* like a function (`function m() ... end`). */
|
|
1649
1667
|
parseClassDeclaration() {
|
|
1650
1668
|
const start = this.current();
|
|
1669
|
+
const isAbstract = this.checkIdentifierValue("abstract");
|
|
1670
|
+
if (isAbstract) this.advance();
|
|
1651
1671
|
this.advance();
|
|
1652
1672
|
const name = this.parseIdentifier();
|
|
1653
1673
|
const typeParams = this.checkOperator("<") ? this.parseGenericTypeParameterList() : [];
|
|
1654
1674
|
const { superclass, superArguments } = this.parseExtends();
|
|
1675
|
+
const shapes = this.parseImplements();
|
|
1655
1676
|
const members = this.parseClassBody(start);
|
|
1656
1677
|
return {
|
|
1657
1678
|
type: "ClassDeclaration",
|
|
@@ -1659,10 +1680,21 @@ var Parser = class {
|
|
|
1659
1680
|
typeParams,
|
|
1660
1681
|
superclass,
|
|
1661
1682
|
superArguments,
|
|
1683
|
+
...shapes ? { implements: shapes } : {},
|
|
1684
|
+
...isAbstract ? { isAbstract } : {},
|
|
1662
1685
|
members,
|
|
1663
1686
|
...spanFrom(start, this.previous())
|
|
1664
1687
|
};
|
|
1665
1688
|
}
|
|
1689
|
+
/** `class Name` or `abstract class Name` — all three words soft, so a name
|
|
1690
|
+
* has to follow for either to be a declaration: `class: T`, `class = 1`
|
|
1691
|
+
* and `abstract(x)` are names. */
|
|
1692
|
+
startsClassDeclaration() {
|
|
1693
|
+
const offset = this.checkIdentifierValue("abstract") ? 1 : 0;
|
|
1694
|
+
const word = this.peek(offset);
|
|
1695
|
+
if (word.type !== "Identifier" || word.value !== "class") return false;
|
|
1696
|
+
return this.peek(offset + 1).type === "Identifier" && !this.punctuatorAt(offset + 2, ":") && !this.operatorAt(offset + 2, "=") && !this.punctuatorAt(offset + 2, "(");
|
|
1697
|
+
}
|
|
1666
1698
|
/** `class ... end` as a value. It may be named — the name is for the class
|
|
1667
1699
|
* itself, not for the scope around it — and takes no type parameters,
|
|
1668
1700
|
* since nothing could write the arguments. */
|
|
@@ -1674,8 +1706,25 @@ var Parser = class {
|
|
|
1674
1706
|
this.error("A class written as a value takes no type parameters: nothing could write the arguments");
|
|
1675
1707
|
}
|
|
1676
1708
|
const { superclass, superArguments } = this.parseExtends();
|
|
1709
|
+
const shapes = this.parseImplements();
|
|
1677
1710
|
const members = this.parseClassBody(start);
|
|
1678
|
-
return {
|
|
1711
|
+
return {
|
|
1712
|
+
type: "ClassExpression",
|
|
1713
|
+
name,
|
|
1714
|
+
superclass,
|
|
1715
|
+
superArguments,
|
|
1716
|
+
...shapes ? { implements: shapes } : {},
|
|
1717
|
+
members,
|
|
1718
|
+
...spanFrom(start, this.previous())
|
|
1719
|
+
};
|
|
1720
|
+
}
|
|
1721
|
+
/** `implements Shape, Named` — each a type, checked against the instance. */
|
|
1722
|
+
parseImplements() {
|
|
1723
|
+
if (!this.checkIdentifierValue("implements")) return void 0;
|
|
1724
|
+
this.advance();
|
|
1725
|
+
const shapes = [this.parseType()];
|
|
1726
|
+
while (this.matchPunctuator(",")) shapes.push(this.parseType());
|
|
1727
|
+
return shapes;
|
|
1679
1728
|
}
|
|
1680
1729
|
/** `extends Base` / `extends Box<number>`. */
|
|
1681
1730
|
parseExtends() {
|
|
@@ -1728,23 +1777,66 @@ var Parser = class {
|
|
|
1728
1777
|
parseClassMember() {
|
|
1729
1778
|
const start = this.current();
|
|
1730
1779
|
let accessibility;
|
|
1731
|
-
|
|
1732
|
-
|
|
1780
|
+
const modifiers = /* @__PURE__ */ new Set();
|
|
1781
|
+
while (this.startsClassModifier()) {
|
|
1782
|
+
const token = this.advance();
|
|
1783
|
+
const word = token.value;
|
|
1784
|
+
if (ACCESSIBILITIES.has(word)) {
|
|
1785
|
+
if (accessibility) this.problemAt(token, `A member has one accessibility: '${accessibility}' is already written`);
|
|
1786
|
+
accessibility = word;
|
|
1787
|
+
} else {
|
|
1788
|
+
if (modifiers.has(word)) this.problemAt(token, `'${word}' is written twice`);
|
|
1789
|
+
modifiers.add(word);
|
|
1790
|
+
}
|
|
1733
1791
|
}
|
|
1734
|
-
const member = this.
|
|
1735
|
-
if (!
|
|
1792
|
+
const member = this.parseClassMemberAfterModifiers(start, modifiers);
|
|
1793
|
+
if (!member) return member;
|
|
1794
|
+
const misplaced = (message) => this.problemAt(start, message);
|
|
1736
1795
|
if (member.type === "ClassConstructor") {
|
|
1737
|
-
|
|
1796
|
+
const written = [...accessibility ? [accessibility] : [], ...modifiers];
|
|
1797
|
+
if (written.length) misplaced(`A constructor cannot be '${written[0]}'`);
|
|
1738
1798
|
return member;
|
|
1739
1799
|
}
|
|
1740
|
-
|
|
1800
|
+
if (modifiers.has("readonly") && member.type !== "ClassField") misplaced("Only a field can be 'readonly'");
|
|
1801
|
+
if (modifiers.has("abstract") && member.type !== "ClassMethod") misplaced("Only a method can be 'abstract'");
|
|
1802
|
+
if (modifiers.has("abstract") && member.isStatic) misplaced("A static method cannot be 'abstract'");
|
|
1803
|
+
if (modifiers.has("override") && member.isStatic) misplaced("A static member cannot be 'override'");
|
|
1804
|
+
return {
|
|
1805
|
+
...member,
|
|
1806
|
+
...accessibility ? { accessibility } : {},
|
|
1807
|
+
...modifiers.has("readonly") && member.type === "ClassField" ? { isReadonly: true } : {},
|
|
1808
|
+
...modifiers.has("override") ? { isOverride: true } : {}
|
|
1809
|
+
};
|
|
1741
1810
|
}
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1811
|
+
/** Is the word here a member modifier, rather than a member's name? */
|
|
1812
|
+
startsClassModifier() {
|
|
1813
|
+
const t = this.current();
|
|
1814
|
+
if (t.type !== "Identifier" || !CLASS_MODIFIERS.has(t.value)) return false;
|
|
1815
|
+
const next = this.peek(1);
|
|
1816
|
+
return next.type === "Identifier" || next.type === "Keyword" && next.value === "function";
|
|
1817
|
+
}
|
|
1818
|
+
parseClassMemberAfterModifiers(start, modifiers) {
|
|
1819
|
+
const isStatic = modifiers.has("static");
|
|
1745
1820
|
if (this.checkKeyword("function")) {
|
|
1746
1821
|
this.advance();
|
|
1747
1822
|
const memberName = this.parseIdentifier();
|
|
1823
|
+
if (modifiers.has("abstract")) {
|
|
1824
|
+
const head = this.parseFunctionHead();
|
|
1825
|
+
const func = {
|
|
1826
|
+
type: "FunctionBody",
|
|
1827
|
+
generics: head.generics,
|
|
1828
|
+
params: head.params,
|
|
1829
|
+
hasVarargs: head.hasVarargs,
|
|
1830
|
+
returnType: head.returnType,
|
|
1831
|
+
predicate: head.predicate,
|
|
1832
|
+
body: { type: "Block", statements: [], ...spanFrom(head.start, this.previous()) },
|
|
1833
|
+
...spanFrom(head.start, this.previous())
|
|
1834
|
+
};
|
|
1835
|
+
if (!isStatic) bindThis(func, start);
|
|
1836
|
+
if (!head.returnType) this.problem("An abstract method has no body to infer from: write its return type");
|
|
1837
|
+
if (this.checkPunctuator("{")) this.problem("An abstract method has no body");
|
|
1838
|
+
return { type: "ClassMethod", name: memberName, isStatic, func, isAbstract: true, ...spanFrom(start, this.previous()) };
|
|
1839
|
+
}
|
|
1748
1840
|
const signatures = [];
|
|
1749
1841
|
let written = memberName;
|
|
1750
1842
|
while (true) {
|
|
@@ -1862,12 +1954,17 @@ var Parser = class {
|
|
|
1862
1954
|
parseReturnStatement(stopAtNewline = false) {
|
|
1863
1955
|
const start = this.current();
|
|
1864
1956
|
this.expectKeyword("return");
|
|
1865
|
-
let
|
|
1957
|
+
let argument;
|
|
1866
1958
|
const pastLine = () => stopAtNewline && this.current().line.start > start.line.start;
|
|
1867
1959
|
if (!pastLine() && this.isExpressionStart()) {
|
|
1868
|
-
|
|
1960
|
+
const values = this.expressionListOr(pastLine);
|
|
1961
|
+
argument = values[0];
|
|
1962
|
+
if (values.length > 1) {
|
|
1963
|
+
this.problemAt(start, "A function returns one value: return several as an array, 'return [a, b]'");
|
|
1964
|
+
argument = { type: "ArrayExpression", elements: values, ...spanFrom(values[0], values[values.length - 1]) };
|
|
1965
|
+
}
|
|
1869
1966
|
}
|
|
1870
|
-
return { type: "ReturnStatement",
|
|
1967
|
+
return { type: "ReturnStatement", argument, ...spanFrom(start, this.previous()) };
|
|
1871
1968
|
}
|
|
1872
1969
|
parseTypeAliasStatement() {
|
|
1873
1970
|
const start = this.current();
|
|
@@ -1926,7 +2023,7 @@ var Parser = class {
|
|
|
1926
2023
|
...spanFrom(start, this.previous())
|
|
1927
2024
|
};
|
|
1928
2025
|
}
|
|
1929
|
-
if (first.type === "CallExpression" || first.type === "MethodCallExpression"
|
|
2026
|
+
if (first.type === "CallExpression" || first.type === "MethodCallExpression") {
|
|
1930
2027
|
return { type: "CallStatement", expression: first, ...spanFrom(start, this.previous()) };
|
|
1931
2028
|
}
|
|
1932
2029
|
let expression = first;
|
|
@@ -1979,6 +2076,15 @@ var Parser = class {
|
|
|
1979
2076
|
}
|
|
1980
2077
|
return false;
|
|
1981
2078
|
}
|
|
2079
|
+
/** A string written straight before a method call — `"a":upper()`, or
|
|
2080
|
+
* `` `{n}`:rep(2) ``. Luau only takes `("a"):upper()`; the compiler adds
|
|
2081
|
+
* the parentheses. Anything else after a string (`"a".x`, `"a"(1)`) is
|
|
2082
|
+
* not a prefix expression here either. */
|
|
2083
|
+
startsStringMethodCall() {
|
|
2084
|
+
const t = this.current();
|
|
2085
|
+
const isString = t.type === "InterpolatedString" || t.type === "Literal" && t.kind === "string";
|
|
2086
|
+
return isString && this.punctuatorAt(1, ":") && this.startsMethodCall(1);
|
|
2087
|
+
}
|
|
1982
2088
|
/** Does the next token start right where the current one ends? */
|
|
1983
2089
|
touchesNext() {
|
|
1984
2090
|
const current = this.current();
|
|
@@ -2165,6 +2271,7 @@ var Parser = class {
|
|
|
2165
2271
|
}
|
|
2166
2272
|
parseAtom() {
|
|
2167
2273
|
const t = this.current();
|
|
2274
|
+
if (this.startsStringMethodCall()) return this.parsePrefixExpression();
|
|
2168
2275
|
if (t.type === "Literal") {
|
|
2169
2276
|
this.advance();
|
|
2170
2277
|
const lit = t;
|
|
@@ -2184,8 +2291,9 @@ var Parser = class {
|
|
|
2184
2291
|
return this.buildInterpolatedString(t);
|
|
2185
2292
|
}
|
|
2186
2293
|
if (t.type === "Operator" && t.value === "...") {
|
|
2294
|
+
this.problem(NO_DOTS);
|
|
2187
2295
|
this.advance();
|
|
2188
|
-
return { type: "
|
|
2296
|
+
return { type: "ErrorExpression", ...spanFrom(t, t) };
|
|
2189
2297
|
}
|
|
2190
2298
|
if (t.type === "Keyword" && t.value === "function") {
|
|
2191
2299
|
this.advance();
|
|
@@ -2261,12 +2369,17 @@ var Parser = class {
|
|
|
2261
2369
|
const start = this.current();
|
|
2262
2370
|
let base;
|
|
2263
2371
|
if (this.startsNew()) {
|
|
2264
|
-
|
|
2265
|
-
|
|
2372
|
+
this.problem("tilua has no 'new' operator: construct with 'Name.new(...)'");
|
|
2373
|
+
this.advance();
|
|
2374
|
+
}
|
|
2375
|
+
if (this.classDepth > 0 && this.checkIdentifierValue("super") && this.startsSuperUse()) {
|
|
2266
2376
|
this.advance();
|
|
2267
2377
|
base = { type: "SuperExpression", ...spanFrom(start, start) };
|
|
2268
2378
|
} else if (this.checkType("Identifier")) {
|
|
2269
2379
|
base = this.parseIdentifier();
|
|
2380
|
+
} else if (this.startsStringMethodCall()) {
|
|
2381
|
+
const t = this.advance();
|
|
2382
|
+
base = t.type === "InterpolatedString" ? this.buildInterpolatedString(t) : { type: "StringLiteral", value: t.value, raw: t.raw, ...spanFrom(t, t) };
|
|
2270
2383
|
} else if (this.matchPunctuator("(")) {
|
|
2271
2384
|
const inner = this.inBrackets(() => this.parseExpression());
|
|
2272
2385
|
this.expectPunctuator(")");
|
|
@@ -2377,9 +2490,9 @@ var Parser = class {
|
|
|
2377
2490
|
}
|
|
2378
2491
|
return base;
|
|
2379
2492
|
}
|
|
2380
|
-
/** `new
|
|
2381
|
-
*
|
|
2382
|
-
*
|
|
2493
|
+
/** `new Name(...)`, the way other languages build an instance — only to
|
|
2494
|
+
* say so. A function or field called `new` — `Instance.new(x)`, and
|
|
2495
|
+
* `Vec.new(1)` itself — is untouched. */
|
|
2383
2496
|
startsNew() {
|
|
2384
2497
|
return this.checkIdentifierValue("new") && this.peek(1).type === "Identifier";
|
|
2385
2498
|
}
|
|
@@ -2389,25 +2502,6 @@ var Parser = class {
|
|
|
2389
2502
|
startsSuperUse() {
|
|
2390
2503
|
return this.punctuatorAt(1, "(") || this.punctuatorAt(1, ".") && this.peek(2).type === "Identifier";
|
|
2391
2504
|
}
|
|
2392
|
-
/** `new Name(args)` / `new Module.Name(args)`. The callee is a name, or a
|
|
2393
|
-
* name reached through a module — never an arbitrary expression, so the
|
|
2394
|
-
* arguments are unambiguously the constructor's. */
|
|
2395
|
-
parseNewExpression() {
|
|
2396
|
-
const start = this.current();
|
|
2397
|
-
this.advance();
|
|
2398
|
-
let callee = this.parseIdentifier();
|
|
2399
|
-
while (this.checkPunctuator(".") && this.peek(1).type === "Identifier") {
|
|
2400
|
-
this.advance();
|
|
2401
|
-
const property = this.parseIdentifier();
|
|
2402
|
-
callee = { type: "MemberExpression", object: callee, property, ...spanFrom(start, property) };
|
|
2403
|
-
}
|
|
2404
|
-
const typeArguments = this.tryCallTypeArguments();
|
|
2405
|
-
if (!this.checkPunctuator("(")) {
|
|
2406
|
-
this.error("Expected '(' after the class being constructed: 'new Name(...)'");
|
|
2407
|
-
}
|
|
2408
|
-
const args = this.parseCallArguments();
|
|
2409
|
-
return { type: "NewExpression", callee, arguments: args, typeArguments, ...spanFrom(start, this.previous()) };
|
|
2410
|
-
}
|
|
2411
2505
|
/** After `...`, is there something to spread? Nothing following it means
|
|
2412
2506
|
* the vararg pack, which is what `f(...)` has always passed on. */
|
|
2413
2507
|
startsSpread() {
|
|
@@ -2449,8 +2543,8 @@ var Parser = class {
|
|
|
2449
2543
|
const errors = this.errors.length;
|
|
2450
2544
|
try {
|
|
2451
2545
|
this.advance();
|
|
2452
|
-
const list = [this.
|
|
2453
|
-
while (this.matchPunctuator(",") && !this.checkOperator(">")) list.push(this.
|
|
2546
|
+
const list = [this.parseType()];
|
|
2547
|
+
while (this.matchPunctuator(",") && !this.checkOperator(">")) list.push(this.parseType());
|
|
2454
2548
|
this.expectOperator(">");
|
|
2455
2549
|
if (!this.startsCallArguments()) throw new ParseRecover("not a call");
|
|
2456
2550
|
return list;
|
|
@@ -2582,7 +2676,7 @@ var Parser = class {
|
|
|
2582
2676
|
if (this.checkOperator("...")) {
|
|
2583
2677
|
const dots = this.advance();
|
|
2584
2678
|
if (this.checkPunctuator("]") || this.checkPunctuator(",")) {
|
|
2585
|
-
|
|
2679
|
+
this.problemAt(dots, NO_DOTS);
|
|
2586
2680
|
} else {
|
|
2587
2681
|
const argument = this.expressionOr(stop);
|
|
2588
2682
|
elements.push({ type: "SpreadElement", argument, ...spanFrom(dots, argument) });
|
|
@@ -2736,35 +2830,16 @@ var Parser = class {
|
|
|
2736
2830
|
column: p.column
|
|
2737
2831
|
};
|
|
2738
2832
|
}
|
|
2739
|
-
|
|
2833
|
+
/** `T...` written where a type goes: a pack, which tilua does not have. */
|
|
2834
|
+
rejectPackReference() {
|
|
2740
2835
|
if (this.checkType("Identifier") && this.peek(1).type === "Operator" && this.peek(1).value === "...") {
|
|
2741
|
-
|
|
2742
|
-
|
|
2836
|
+
this.problemAt(
|
|
2837
|
+
this.peek(1),
|
|
2838
|
+
"tilua has no type packs: several values are a tuple, '[A, B]', and a varying number an array, 'T[]'"
|
|
2839
|
+
);
|
|
2840
|
+
this.advance();
|
|
2743
2841
|
this.advance();
|
|
2744
|
-
const packRef = { type: "TypeReference", base, typeArguments: [], ...spanFrom(start, start) };
|
|
2745
|
-
return {
|
|
2746
|
-
type: "TypePackNode",
|
|
2747
|
-
types: [],
|
|
2748
|
-
hasVarargs: true,
|
|
2749
|
-
// Wrap in `VariadicTypeNode`, matching the convention used by
|
|
2750
|
-
// `parseFunctionTypeAfterParen`'s identifier-pack-reference
|
|
2751
|
-
// branch, so the printer can tell `A...` (name-first, this
|
|
2752
|
-
// case) apart from `...T` (dots-first) and append rather
|
|
2753
|
-
// than prepend the `...`.
|
|
2754
|
-
varargType: { type: "VariadicTypeNode", typeAnnotation: packRef, ...spanFrom(start, this.previous()) },
|
|
2755
|
-
...spanFrom(start, this.previous())
|
|
2756
|
-
};
|
|
2757
|
-
}
|
|
2758
|
-
return this.parseType();
|
|
2759
|
-
}
|
|
2760
|
-
parseTypeArgument() {
|
|
2761
|
-
if (this.checkOperator("...")) {
|
|
2762
|
-
return this.parseTypePack();
|
|
2763
|
-
}
|
|
2764
|
-
if (this.checkType("Identifier") && this.peek(1).type === "Operator" && this.peek(1).value === "...") {
|
|
2765
|
-
return this.parseTypeOrTypePackReference();
|
|
2766
2842
|
}
|
|
2767
|
-
return this.parseType();
|
|
2768
2843
|
}
|
|
2769
2844
|
/** The part shared by a real function body and an overload signature:
|
|
2770
2845
|
* `<generics>(params): ReturnType`, up to (but not including) the block. */
|
|
@@ -2777,7 +2852,6 @@ var Parser = class {
|
|
|
2777
2852
|
this.expectPunctuator("(");
|
|
2778
2853
|
const params = [];
|
|
2779
2854
|
let hasVarargs = false;
|
|
2780
|
-
let varargTypeAnnotation;
|
|
2781
2855
|
if (!this.checkPunctuator(")")) {
|
|
2782
2856
|
while (true) {
|
|
2783
2857
|
if (this.checkOperator("...")) {
|
|
@@ -2785,14 +2859,14 @@ var Parser = class {
|
|
|
2785
2859
|
hasVarargs = true;
|
|
2786
2860
|
if (this.checkType("Identifier")) {
|
|
2787
2861
|
const nameTok = this.expectIdentifier();
|
|
2788
|
-
let
|
|
2862
|
+
let typeAnnotation3;
|
|
2789
2863
|
if (this.matchPunctuator(":")) {
|
|
2790
|
-
|
|
2864
|
+
typeAnnotation3 = this.typeOr(() => this.checkPunctuator(")"));
|
|
2791
2865
|
}
|
|
2792
2866
|
params.push({
|
|
2793
2867
|
type: "FunctionParameter",
|
|
2794
2868
|
name: nameTok.value,
|
|
2795
|
-
typeAnnotation:
|
|
2869
|
+
typeAnnotation: typeAnnotation3,
|
|
2796
2870
|
rest: true,
|
|
2797
2871
|
...spanFrom(dots, this.previous())
|
|
2798
2872
|
});
|
|
@@ -2801,9 +2875,20 @@ var Parser = class {
|
|
|
2801
2875
|
}
|
|
2802
2876
|
break;
|
|
2803
2877
|
}
|
|
2878
|
+
this.problemAt(dots, NO_DOTS);
|
|
2879
|
+
let typeAnnotation2;
|
|
2804
2880
|
if (this.matchPunctuator(":")) {
|
|
2805
|
-
|
|
2881
|
+
this.rejectPackReference();
|
|
2882
|
+
const element = this.parseType();
|
|
2883
|
+
typeAnnotation2 = { type: "ArrayTypeNode", element, ...spanFrom(element, element) };
|
|
2806
2884
|
}
|
|
2885
|
+
params.push({
|
|
2886
|
+
type: "FunctionParameter",
|
|
2887
|
+
name: "args",
|
|
2888
|
+
typeAnnotation: typeAnnotation2,
|
|
2889
|
+
rest: true,
|
|
2890
|
+
...spanFrom(dots, this.previous())
|
|
2891
|
+
});
|
|
2807
2892
|
break;
|
|
2808
2893
|
}
|
|
2809
2894
|
const paramStart = this.current();
|
|
@@ -2845,10 +2930,16 @@ var Parser = class {
|
|
|
2845
2930
|
if (this.matchPunctuator(":")) {
|
|
2846
2931
|
predicate = this.tryParseTypePredicate();
|
|
2847
2932
|
if (!predicate) {
|
|
2848
|
-
returnType = this.attempt(() => this.
|
|
2933
|
+
returnType = this.attempt(() => this.parseReturnType(), () => false, () => void 0);
|
|
2849
2934
|
}
|
|
2850
2935
|
}
|
|
2851
|
-
return { start, generics, params, hasVarargs,
|
|
2936
|
+
return { start, generics, params, hasVarargs, returnType, predicate };
|
|
2937
|
+
}
|
|
2938
|
+
/** What a function returns: one type. `T...` is reported as the pack it
|
|
2939
|
+
* is, and the type after it read. */
|
|
2940
|
+
parseReturnType() {
|
|
2941
|
+
this.rejectPackReference();
|
|
2942
|
+
return this.parseType();
|
|
2852
2943
|
}
|
|
2853
2944
|
/** TypeScript-style type-guard return annotations, in return position only:
|
|
2854
2945
|
*
|
|
@@ -2918,7 +3009,6 @@ var Parser = class {
|
|
|
2918
3009
|
generics: [],
|
|
2919
3010
|
params: [{ type: "FunctionParameter", name: name.value, ...spanFrom(name, name) }],
|
|
2920
3011
|
hasVarargs: false,
|
|
2921
|
-
varargTypeAnnotation: void 0,
|
|
2922
3012
|
returnType: void 0,
|
|
2923
3013
|
predicate: void 0
|
|
2924
3014
|
};
|
|
@@ -2930,7 +3020,6 @@ var Parser = class {
|
|
|
2930
3020
|
generics: head.generics,
|
|
2931
3021
|
params: head.params,
|
|
2932
3022
|
hasVarargs: head.hasVarargs,
|
|
2933
|
-
varargTypeAnnotation: head.varargTypeAnnotation,
|
|
2934
3023
|
returnType: head.returnType,
|
|
2935
3024
|
predicate: head.predicate,
|
|
2936
3025
|
body,
|
|
@@ -2942,7 +3031,7 @@ var Parser = class {
|
|
|
2942
3031
|
returnOf(expression) {
|
|
2943
3032
|
const statement = {
|
|
2944
3033
|
type: "ReturnStatement",
|
|
2945
|
-
|
|
3034
|
+
argument: expression,
|
|
2946
3035
|
...spanFrom(expression, expression)
|
|
2947
3036
|
};
|
|
2948
3037
|
return { type: "Block", statements: [statement], ...spanFrom(expression, expression) };
|
|
@@ -2955,7 +3044,6 @@ var Parser = class {
|
|
|
2955
3044
|
generics: head.generics,
|
|
2956
3045
|
params: head.params,
|
|
2957
3046
|
hasVarargs: head.hasVarargs,
|
|
2958
|
-
varargTypeAnnotation: head.varargTypeAnnotation,
|
|
2959
3047
|
returnType: head.returnType,
|
|
2960
3048
|
predicate: head.predicate,
|
|
2961
3049
|
body,
|
|
@@ -2968,7 +3056,6 @@ var Parser = class {
|
|
|
2968
3056
|
generics: head.generics,
|
|
2969
3057
|
params: head.params,
|
|
2970
3058
|
hasVarargs: head.hasVarargs,
|
|
2971
|
-
varargTypeAnnotation: head.varargTypeAnnotation,
|
|
2972
3059
|
returnType: head.returnType,
|
|
2973
3060
|
predicate: head.predicate,
|
|
2974
3061
|
...spanFrom(head.start, this.previous())
|
|
@@ -2986,7 +3073,6 @@ var Parser = class {
|
|
|
2986
3073
|
generics: head.generics,
|
|
2987
3074
|
params: head.params,
|
|
2988
3075
|
hasVarargs: head.hasVarargs,
|
|
2989
|
-
varargTypeAnnotation: head.varargTypeAnnotation,
|
|
2990
3076
|
returnType: head.returnType,
|
|
2991
3077
|
predicate: head.predicate,
|
|
2992
3078
|
body,
|
|
@@ -3127,9 +3213,10 @@ var Parser = class {
|
|
|
3127
3213
|
return this.parseFunctionTypeAfterParen(t, []);
|
|
3128
3214
|
}
|
|
3129
3215
|
if (t.type === "Operator" && t.value === "...") {
|
|
3216
|
+
this.problem("'...' in a type goes before a rest parameter, '...args: T[]', or at the end of a tuple, '[A, ...B[]]'");
|
|
3130
3217
|
this.advance();
|
|
3131
|
-
const
|
|
3132
|
-
return { type: "
|
|
3218
|
+
const element = this.parseType();
|
|
3219
|
+
return { type: "ArrayTypeNode", element, ...spanFrom(t, this.previous()) };
|
|
3133
3220
|
}
|
|
3134
3221
|
if (t.type === "Punctuator" && t.value === "{") {
|
|
3135
3222
|
return this.parseTableType();
|
|
@@ -3137,13 +3224,22 @@ var Parser = class {
|
|
|
3137
3224
|
if (t.type === "Punctuator" && t.value === "[") {
|
|
3138
3225
|
this.advance();
|
|
3139
3226
|
const elements = [];
|
|
3227
|
+
let rest;
|
|
3140
3228
|
while (!this.checkPunctuator("]")) {
|
|
3229
|
+
if (this.matchOperator("...")) {
|
|
3230
|
+
rest = this.parseType();
|
|
3231
|
+
if (rest.type !== "ArrayTypeNode" && !(rest.type === "TypeReference" && rest.base !== "nil")) {
|
|
3232
|
+
this.problem("The rest of a tuple is an array type: '...T[]'");
|
|
3233
|
+
}
|
|
3234
|
+
this.matchPunctuator(",");
|
|
3235
|
+
break;
|
|
3236
|
+
}
|
|
3141
3237
|
elements.push(this.parseType());
|
|
3142
3238
|
if (this.matchPunctuator(",")) continue;
|
|
3143
3239
|
break;
|
|
3144
3240
|
}
|
|
3145
3241
|
this.expectPunctuator("]");
|
|
3146
|
-
return { type: "TupleTypeNode", elements, ...spanFrom(t, this.previous()) };
|
|
3242
|
+
return { type: "TupleTypeNode", elements, ...rest ? { rest } : {}, ...spanFrom(t, this.previous()) };
|
|
3147
3243
|
}
|
|
3148
3244
|
if (t.type === "Identifier" && t.value === "typeof" && this.peek(1).type === "Punctuator" && this.peek(1).value === "(") {
|
|
3149
3245
|
this.advance();
|
|
@@ -3190,9 +3286,9 @@ var Parser = class {
|
|
|
3190
3286
|
if (this.checkOperator("<")) {
|
|
3191
3287
|
this.advance();
|
|
3192
3288
|
if (!this.checkOperator(">")) {
|
|
3193
|
-
typeArguments.push(this.
|
|
3289
|
+
typeArguments.push(this.parseType());
|
|
3194
3290
|
while (this.matchPunctuator(",") && !this.checkOperator(">")) {
|
|
3195
|
-
typeArguments.push(this.
|
|
3291
|
+
typeArguments.push(this.parseType());
|
|
3196
3292
|
}
|
|
3197
3293
|
}
|
|
3198
3294
|
this.expectOperator(">");
|
|
@@ -3203,13 +3299,12 @@ var Parser = class {
|
|
|
3203
3299
|
}
|
|
3204
3300
|
parseFunctionTypeAfterParen(start, generics) {
|
|
3205
3301
|
const params = [];
|
|
3206
|
-
let
|
|
3207
|
-
let varargType;
|
|
3302
|
+
let rested = false;
|
|
3208
3303
|
if (!this.checkPunctuator(")")) {
|
|
3209
3304
|
while (true) {
|
|
3210
3305
|
if (this.checkOperator("...")) {
|
|
3211
3306
|
const dots = this.advance();
|
|
3212
|
-
|
|
3307
|
+
rested = true;
|
|
3213
3308
|
if (this.checkType("Identifier") && this.punctuatorAt(1, ":")) {
|
|
3214
3309
|
const nameTok2 = this.expectIdentifier();
|
|
3215
3310
|
this.advance();
|
|
@@ -3223,16 +3318,21 @@ var Parser = class {
|
|
|
3223
3318
|
});
|
|
3224
3319
|
break;
|
|
3225
3320
|
}
|
|
3226
|
-
|
|
3321
|
+
this.problemAt(dots, "A function type takes the rest of its arguments as an array: '(...args: T[]) => R'");
|
|
3322
|
+
this.rejectPackReference();
|
|
3323
|
+
const element = this.parseType();
|
|
3324
|
+
params.push({
|
|
3325
|
+
type: "FunctionTypeParameter",
|
|
3326
|
+
name: "args",
|
|
3327
|
+
rest: true,
|
|
3328
|
+
typeAnnotation: { type: "ArrayTypeNode", element, ...spanFrom(element, element) },
|
|
3329
|
+
...spanFrom(dots, this.previous())
|
|
3330
|
+
});
|
|
3227
3331
|
break;
|
|
3228
3332
|
}
|
|
3229
3333
|
if (this.checkType("Identifier") && this.peek(1).type === "Operator" && this.peek(1).value === "...") {
|
|
3230
|
-
|
|
3231
|
-
|
|
3232
|
-
this.advance();
|
|
3233
|
-
hasVarargs = true;
|
|
3234
|
-
varargType = { type: "VariadicTypeNode", typeAnnotation: packRef, ...spanFrom(packStart, this.previous()) };
|
|
3235
|
-
break;
|
|
3334
|
+
this.rejectPackReference();
|
|
3335
|
+
continue;
|
|
3236
3336
|
}
|
|
3237
3337
|
let name;
|
|
3238
3338
|
let optional2 = false;
|
|
@@ -3262,19 +3362,17 @@ var Parser = class {
|
|
|
3262
3362
|
this.expectPunctuator(")");
|
|
3263
3363
|
if (this.matchPunctuator("=>") || this.mistypedArrow()) {
|
|
3264
3364
|
const predicate = this.tryParseTypePredicate();
|
|
3265
|
-
const returnType = predicate ? { type: "TypeReference", base: "boolean", typeArguments: [], ...spanFrom(start, this.previous()) } : this.
|
|
3365
|
+
const returnType = predicate ? { type: "TypeReference", base: "boolean", typeArguments: [], ...spanFrom(start, this.previous()) } : this.parseReturnType();
|
|
3266
3366
|
return {
|
|
3267
3367
|
type: "FunctionTypeNode",
|
|
3268
3368
|
generics,
|
|
3269
3369
|
params,
|
|
3270
|
-
hasVarargs,
|
|
3271
|
-
varargType,
|
|
3272
3370
|
returnType,
|
|
3273
3371
|
predicate,
|
|
3274
3372
|
...spanFrom(start, this.previous())
|
|
3275
3373
|
};
|
|
3276
3374
|
}
|
|
3277
|
-
if (params.length === 1 && !params[0].name && !
|
|
3375
|
+
if (params.length === 1 && !params[0].name && !rested) {
|
|
3278
3376
|
return {
|
|
3279
3377
|
type: "ParenthesizedTypeNode",
|
|
3280
3378
|
typeAnnotation: params[0].typeAnnotation,
|
|
@@ -3284,11 +3382,14 @@ var Parser = class {
|
|
|
3284
3382
|
if (params.some((p) => p.name !== void 0)) {
|
|
3285
3383
|
this.error("Expected '=>' for function type");
|
|
3286
3384
|
}
|
|
3385
|
+
if (!params.length) {
|
|
3386
|
+
this.problemAt(start, "'()' is not a type: a function that returns nothing returns 'nil'");
|
|
3387
|
+
return { type: "TypeReference", base: "nil", typeArguments: [], ...spanFrom(start, this.previous()) };
|
|
3388
|
+
}
|
|
3389
|
+
this.problemAt(start, "Several types in parentheses are a pack, which tilua does not have: write a tuple, '[A, B]'");
|
|
3287
3390
|
return {
|
|
3288
|
-
type: "
|
|
3289
|
-
|
|
3290
|
-
hasVarargs,
|
|
3291
|
-
varargType,
|
|
3391
|
+
type: "TupleTypeNode",
|
|
3392
|
+
elements: params.map((p) => p.typeAnnotation),
|
|
3292
3393
|
...spanFrom(start, this.previous())
|
|
3293
3394
|
};
|
|
3294
3395
|
}
|
|
@@ -3426,40 +3527,15 @@ var Parser = class {
|
|
|
3426
3527
|
this.expectPunctuator("}");
|
|
3427
3528
|
return { type: "TableTypeNode", properties, ...spanFrom(start, this.previous()) };
|
|
3428
3529
|
}
|
|
3429
|
-
parseTypePack() {
|
|
3430
|
-
const start = this.current();
|
|
3431
|
-
if (this.matchOperator("...")) {
|
|
3432
|
-
const varargType2 = this.parseType();
|
|
3433
|
-
return { type: "TypePackNode", types: [], hasVarargs: true, varargType: varargType2, ...spanFrom(start, this.previous()) };
|
|
3434
|
-
}
|
|
3435
|
-
this.expectPunctuator("(");
|
|
3436
|
-
const types = [];
|
|
3437
|
-
let hasVarargs = false;
|
|
3438
|
-
let varargType;
|
|
3439
|
-
if (!(this.current().type === "Punctuator" && this.current().value === ")")) {
|
|
3440
|
-
while (true) {
|
|
3441
|
-
if (this.matchOperator("...")) {
|
|
3442
|
-
hasVarargs = true;
|
|
3443
|
-
varargType = this.parseType();
|
|
3444
|
-
break;
|
|
3445
|
-
}
|
|
3446
|
-
types.push(this.parseType());
|
|
3447
|
-
if (this.matchPunctuator(",")) continue;
|
|
3448
|
-
break;
|
|
3449
|
-
}
|
|
3450
|
-
}
|
|
3451
|
-
this.expectPunctuator(")");
|
|
3452
|
-
return { type: "TypePackNode", types, hasVarargs, varargType, ...spanFrom(start, this.previous()) };
|
|
3453
|
-
}
|
|
3454
3530
|
parseGenericTypeParameterList() {
|
|
3455
3531
|
const list = [];
|
|
3456
3532
|
this.expectOperator("<");
|
|
3457
3533
|
while (true) {
|
|
3458
3534
|
const isConst = this.matchKeyword("const");
|
|
3459
3535
|
const nameTok = this.expectIdentifier();
|
|
3460
|
-
|
|
3461
|
-
if (
|
|
3462
|
-
|
|
3536
|
+
const dots = this.checkOperator("...") ? this.advance() : void 0;
|
|
3537
|
+
if (dots) {
|
|
3538
|
+
this.problemAt(dots, "tilua has no pack parameters: write '<T extends unknown[]>', and take it as '...args: T'");
|
|
3463
3539
|
}
|
|
3464
3540
|
let constraint;
|
|
3465
3541
|
if (this.checkIdentifierValue("extends")) {
|
|
@@ -3468,17 +3544,13 @@ var Parser = class {
|
|
|
3468
3544
|
}
|
|
3469
3545
|
let def;
|
|
3470
3546
|
if (this.matchOperator("=")) {
|
|
3471
|
-
|
|
3472
|
-
|
|
3473
|
-
} else {
|
|
3474
|
-
def = this.parseType();
|
|
3475
|
-
}
|
|
3547
|
+
this.rejectPackReference();
|
|
3548
|
+
def = this.parseType();
|
|
3476
3549
|
}
|
|
3477
3550
|
list.push({
|
|
3478
3551
|
type: "GenericTypeParameter",
|
|
3479
3552
|
name: nameTok.value,
|
|
3480
3553
|
id: tokenIdentifier(nameTok),
|
|
3481
|
-
isPack,
|
|
3482
3554
|
isConst: isConst || void 0,
|
|
3483
3555
|
constraint,
|
|
3484
3556
|
default: def,
|
|
@@ -3569,7 +3641,7 @@ function childScope(parent) {
|
|
|
3569
3641
|
var Analyzer = class {
|
|
3570
3642
|
constructor(options) {
|
|
3571
3643
|
this.options = options;
|
|
3572
|
-
for (const name of options.builtinGlobals ?? []) {
|
|
3644
|
+
for (const name of [...LANGUAGE_GLOBALS, ...options.builtinGlobals ?? []]) {
|
|
3573
3645
|
const id = this.getOrCreateGlobalBinding(name);
|
|
3574
3646
|
this.bindings.get(id).isBuiltin = true;
|
|
3575
3647
|
}
|
|
@@ -3971,14 +4043,14 @@ var Analyzer = class {
|
|
|
3971
4043
|
return;
|
|
3972
4044
|
}
|
|
3973
4045
|
case "GenericForStatement": {
|
|
3974
|
-
|
|
4046
|
+
this.visitExpression(stmt.iterator, scope);
|
|
3975
4047
|
const bodyScope = childScope(scope);
|
|
3976
|
-
|
|
4048
|
+
this.declarePattern(bodyScope, stmt.variable, "for-generic", scope, stmt.kind === "const");
|
|
3977
4049
|
this.visitBlock(stmt.body, bodyScope);
|
|
3978
4050
|
return;
|
|
3979
4051
|
}
|
|
3980
4052
|
case "ReturnStatement":
|
|
3981
|
-
|
|
4053
|
+
if (stmt.argument) this.visitExpression(stmt.argument, scope);
|
|
3982
4054
|
return;
|
|
3983
4055
|
case "BreakStatement":
|
|
3984
4056
|
case "ContinueStatement":
|
|
@@ -4046,7 +4118,6 @@ var Analyzer = class {
|
|
|
4046
4118
|
this.declare(fnScope, param.name, kind, param);
|
|
4047
4119
|
}
|
|
4048
4120
|
});
|
|
4049
|
-
this.visitType(func.varargTypeAnnotation, fnScope);
|
|
4050
4121
|
this.visitType(func.returnType, fnScope);
|
|
4051
4122
|
this.functionDepth++;
|
|
4052
4123
|
try {
|
|
@@ -4074,6 +4145,7 @@ var Analyzer = class {
|
|
|
4074
4145
|
this.visitGenerics(node.typeParams, scope);
|
|
4075
4146
|
if (node.superclass) this.reference(outer, node.superclass);
|
|
4076
4147
|
for (const argument of node.superArguments ?? []) this.visitType(argument, scope);
|
|
4148
|
+
for (const shape of node.implements ?? []) this.visitType(shape, scope);
|
|
4077
4149
|
for (const member of node.members) {
|
|
4078
4150
|
switch (member.type) {
|
|
4079
4151
|
case "ClassField":
|
|
@@ -4133,7 +4205,6 @@ var Analyzer = class {
|
|
|
4133
4205
|
case "BooleanLiteral":
|
|
4134
4206
|
case "NumberLiteral":
|
|
4135
4207
|
case "StringLiteral":
|
|
4136
|
-
case "VarargExpression":
|
|
4137
4208
|
case "ErrorExpression":
|
|
4138
4209
|
return;
|
|
4139
4210
|
case "InterpolatedStringExpression":
|
|
@@ -4173,11 +4244,6 @@ var Analyzer = class {
|
|
|
4173
4244
|
this.visitExpression(expr.callee, scope);
|
|
4174
4245
|
for (const arg of expr.arguments) this.visitExpression(arg, scope);
|
|
4175
4246
|
return;
|
|
4176
|
-
case "NewExpression":
|
|
4177
|
-
this.visitExpression(expr.callee, scope);
|
|
4178
|
-
for (const argument of expr.arguments) this.visitExpression(argument, scope);
|
|
4179
|
-
for (const argument of expr.typeArguments ?? []) this.visitType(argument, scope);
|
|
4180
|
-
return;
|
|
4181
4247
|
case "SuperExpression":
|
|
4182
4248
|
return;
|
|
4183
4249
|
case "SpreadElement":
|
|
@@ -4231,6 +4297,7 @@ var Analyzer = class {
|
|
|
4231
4297
|
}
|
|
4232
4298
|
}
|
|
4233
4299
|
};
|
|
4300
|
+
var LANGUAGE_GLOBALS = ["scriptArgs"];
|
|
4234
4301
|
function analyzeScopes(program, options = {}) {
|
|
4235
4302
|
return new Analyzer(options).run(program);
|
|
4236
4303
|
}
|
|
@@ -4249,8 +4316,8 @@ type Exclude<T, U> = T - U
|
|
|
4249
4316
|
type Extract<T, U> = T extends U ? T : never
|
|
4250
4317
|
type NonNullable<T> = T - nil
|
|
4251
4318
|
|
|
4252
|
-
type ReturnType<T> = T extends (...unknown) => infer R ? R : never
|
|
4253
|
-
type Parameters<T> = T extends (...infer P) => unknown ? P : never
|
|
4319
|
+
type ReturnType<T> = T extends (...args: unknown[]) => infer R ? R : never
|
|
4320
|
+
type Parameters<T> = T extends (...args: infer P) => unknown ? P : never
|
|
4254
4321
|
|
|
4255
4322
|
type Partial<T> = { [K in keyof T]?: T[K] }
|
|
4256
4323
|
type Required<T> = { [K in keyof T]-?: T[K] }
|
|
@@ -4335,8 +4402,11 @@ function literal(value) {
|
|
|
4335
4402
|
function arrayOf(element) {
|
|
4336
4403
|
return { kind: "array", element };
|
|
4337
4404
|
}
|
|
4338
|
-
function tuple(elements,
|
|
4339
|
-
return { kind: "tuple", elements,
|
|
4405
|
+
function tuple(elements, rest) {
|
|
4406
|
+
return rest ? { kind: "tuple", elements, rest } : { kind: "tuple", elements };
|
|
4407
|
+
}
|
|
4408
|
+
function tupleMembers(t) {
|
|
4409
|
+
return t.rest ? [...t.elements, t.rest] : t.elements;
|
|
4340
4410
|
}
|
|
4341
4411
|
function objectType(entries, indexer, frozen) {
|
|
4342
4412
|
return { kind: "object", properties: new Map(entries), indexer, frozen };
|
|
@@ -4360,7 +4430,7 @@ function substitute(t, subst) {
|
|
|
4360
4430
|
case "array":
|
|
4361
4431
|
return arrayOf(substitute(t.element, subst));
|
|
4362
4432
|
case "tuple":
|
|
4363
|
-
return tuple(t.elements.map((e) => substitute(e, subst)), t.
|
|
4433
|
+
return tuple(t.elements.map((e) => substitute(e, subst)), t.rest && substitute(t.rest, subst));
|
|
4364
4434
|
case "object": {
|
|
4365
4435
|
const entries = [];
|
|
4366
4436
|
for (const [k, v] of t.properties) entries.push([k, { ...v, type: substitute(v.type, subst) }]);
|
|
@@ -4384,14 +4454,20 @@ function substitute(t, subst) {
|
|
|
4384
4454
|
const inner = t.typeParams ? new Map([...subst].filter(([k]) => !t.typeParams.includes(k))) : subst;
|
|
4385
4455
|
let params = t.params.map((p) => ({ ...p, type: substitute(p.type, inner) }));
|
|
4386
4456
|
let varargs = t.varargs && substitute(t.varargs, inner);
|
|
4387
|
-
|
|
4457
|
+
let restIsWhole = t.restIsWhole;
|
|
4458
|
+
if (restIsWhole && varargs?.kind === "tuple") {
|
|
4388
4459
|
params = [...params, ...varargs.elements.map((type) => ({ type }))];
|
|
4389
|
-
varargs =
|
|
4460
|
+
varargs = varargs.rest;
|
|
4461
|
+
restIsWhole = void 0;
|
|
4462
|
+
} else if (restIsWhole && varargs?.kind === "array") {
|
|
4463
|
+
varargs = varargs.element;
|
|
4464
|
+
restIsWhole = void 0;
|
|
4390
4465
|
}
|
|
4391
4466
|
return {
|
|
4392
4467
|
kind: "function",
|
|
4393
4468
|
params,
|
|
4394
4469
|
varargs,
|
|
4470
|
+
...restIsWhole ? { restIsWhole } : {},
|
|
4395
4471
|
returns: substitute(t.returns, inner),
|
|
4396
4472
|
typeParams: t.typeParams,
|
|
4397
4473
|
typeParamDefaults: t.typeParamDefaults,
|
|
@@ -4474,11 +4550,19 @@ function unify(param, arg, vars, out) {
|
|
|
4474
4550
|
switch (param.kind) {
|
|
4475
4551
|
case "array":
|
|
4476
4552
|
if (arg.kind === "array") unify(param.element, arg.element, vars, out);
|
|
4477
|
-
else if (arg.kind === "tuple") for (const e of arg
|
|
4553
|
+
else if (arg.kind === "tuple") for (const e of tupleMembers(arg)) unify(param.element, e, vars, out);
|
|
4478
4554
|
return;
|
|
4479
4555
|
case "tuple":
|
|
4480
|
-
if (arg.kind === "tuple")
|
|
4481
|
-
|
|
4556
|
+
if (arg.kind === "tuple") {
|
|
4557
|
+
param.elements.forEach((p, i) => {
|
|
4558
|
+
const a = arg.elements[i] ?? arg.rest;
|
|
4559
|
+
if (a) unify(p, a, vars, out);
|
|
4560
|
+
});
|
|
4561
|
+
if (param.rest) for (const a of arg.elements.slice(param.elements.length)) unify(param.rest, a, vars, out);
|
|
4562
|
+
if (param.rest && arg.rest) unify(param.rest, arg.rest, vars, out);
|
|
4563
|
+
} else if (arg.kind === "array") {
|
|
4564
|
+
for (const p of tupleMembers(param)) unify(p, arg.element, vars, out);
|
|
4565
|
+
}
|
|
4482
4566
|
return;
|
|
4483
4567
|
case "function":
|
|
4484
4568
|
if (arg.kind === "function") {
|
|
@@ -4580,7 +4664,7 @@ function widen(t) {
|
|
|
4580
4664
|
case "array":
|
|
4581
4665
|
return arrayOf(widen(t.element));
|
|
4582
4666
|
case "tuple":
|
|
4583
|
-
return tuple(t.elements.map(widen), t.
|
|
4667
|
+
return tuple(t.elements.map(widen), t.rest && widen(t.rest));
|
|
4584
4668
|
case "object": {
|
|
4585
4669
|
if (t.frozen || t.class) return t;
|
|
4586
4670
|
const entries = [];
|
|
@@ -4680,13 +4764,18 @@ function isAssignableInner(a, b) {
|
|
|
4680
4764
|
if (a.kind === "primitive") return b.kind === "primitive" && b.name === a.name;
|
|
4681
4765
|
if (a.kind === "array") {
|
|
4682
4766
|
if (b.kind === "array") return isAssignable(a.element, b.element);
|
|
4767
|
+
if (b.kind === "tuple") return b.elements.length === 0 && !!b.rest && isAssignable(a.element, b.rest);
|
|
4683
4768
|
return false;
|
|
4684
4769
|
}
|
|
4685
4770
|
if (a.kind === "tuple") {
|
|
4686
4771
|
if (b.kind === "tuple") {
|
|
4687
|
-
|
|
4772
|
+
if (a.elements.length < b.elements.length) return false;
|
|
4773
|
+
if (!b.elements.every((t, i) => isAssignable(a.elements[i], t))) return false;
|
|
4774
|
+
const extra = [...a.elements.slice(b.elements.length), ...a.rest ? [a.rest] : []];
|
|
4775
|
+
if (!b.rest) return extra.length === 0;
|
|
4776
|
+
return extra.every((t) => isAssignable(t, b.rest));
|
|
4688
4777
|
}
|
|
4689
|
-
if (b.kind === "array") return a.
|
|
4778
|
+
if (b.kind === "array") return tupleMembers(a).every((t) => isAssignable(t, b.element));
|
|
4690
4779
|
return false;
|
|
4691
4780
|
}
|
|
4692
4781
|
if (a.kind === "object") {
|
|
@@ -4735,7 +4824,7 @@ function sameRefTarget(a, b) {
|
|
|
4735
4824
|
return a.name === b.name && (!a.origin || !b.origin || a.origin === b.origin);
|
|
4736
4825
|
}
|
|
4737
4826
|
function isNoValue(t) {
|
|
4738
|
-
return t.kind === "tuple" && t.elements.length === 0;
|
|
4827
|
+
return t.kind === "tuple" && t.elements.length === 0 && !t.rest;
|
|
4739
4828
|
}
|
|
4740
4829
|
function equalTypes(a, b) {
|
|
4741
4830
|
return formatType(a) === formatType(b);
|
|
@@ -4842,7 +4931,7 @@ function containsFreeTypeParam(t, seen, bound) {
|
|
|
4842
4931
|
case "array":
|
|
4843
4932
|
return containsTypeParam(t.element, seen, bound);
|
|
4844
4933
|
case "tuple":
|
|
4845
|
-
return t.
|
|
4934
|
+
return tupleMembers(t).some((e) => containsTypeParam(e, seen, bound));
|
|
4846
4935
|
case "union":
|
|
4847
4936
|
case "intersection":
|
|
4848
4937
|
return t.types.some((m) => containsTypeParam(m, seen, bound));
|
|
@@ -4887,15 +4976,19 @@ function matchInfer(arg, pattern, out) {
|
|
|
4887
4976
|
switch (pattern.kind) {
|
|
4888
4977
|
case "array":
|
|
4889
4978
|
if (arg.kind === "array") return matchInfer(arg.element, pattern.element, out);
|
|
4890
|
-
if (arg.kind === "tuple") return matchInfer(union(arg
|
|
4979
|
+
if (arg.kind === "tuple") return matchInfer(union(tupleMembers(arg)), pattern.element, out);
|
|
4891
4980
|
return false;
|
|
4892
4981
|
case "tuple":
|
|
4893
4982
|
if (arg.kind !== "tuple" || arg.elements.length !== pattern.elements.length) return false;
|
|
4983
|
+
if (pattern.rest && !matchInfer(arg.rest ?? neverType, pattern.rest, out)) return false;
|
|
4894
4984
|
return pattern.elements.every((pt, i) => matchInfer(arg.elements[i], pt, out));
|
|
4895
4985
|
case "function": {
|
|
4896
4986
|
if (arg.kind !== "function") return false;
|
|
4897
4987
|
if (pattern.varargs?.kind === "infer" && !pattern.params.length) {
|
|
4898
|
-
out.set(pattern.varargs.name, tuple(
|
|
4988
|
+
out.set(pattern.varargs.name, tuple(
|
|
4989
|
+
arg.params.map((p) => p.type),
|
|
4990
|
+
arg.varargs && !arg.restIsWhole ? arg.varargs : void 0
|
|
4991
|
+
));
|
|
4899
4992
|
} else {
|
|
4900
4993
|
for (let i = 0; i < pattern.params.length; i++) {
|
|
4901
4994
|
if (!arg.params[i]) return false;
|
|
@@ -4969,7 +5062,7 @@ function collectTypeParams(t, out, seen = /* @__PURE__ */ new Set()) {
|
|
|
4969
5062
|
collectTypeParams(t.element, out, seen);
|
|
4970
5063
|
return;
|
|
4971
5064
|
case "tuple":
|
|
4972
|
-
for (const e of t
|
|
5065
|
+
for (const e of tupleMembers(t)) collectTypeParams(e, out, seen);
|
|
4973
5066
|
return;
|
|
4974
5067
|
case "union":
|
|
4975
5068
|
case "intersection":
|
|
@@ -5016,9 +5109,8 @@ function formatTypeUncached(t) {
|
|
|
5016
5109
|
case "array":
|
|
5017
5110
|
return `${formatAtom(t.element)}[]`;
|
|
5018
5111
|
case "tuple": {
|
|
5019
|
-
|
|
5020
|
-
|
|
5021
|
-
return t.isPack ? `(${inner})` : `[${inner}]`;
|
|
5112
|
+
const inner = [...t.elements.map(formatType), ...t.rest ? [`...${formatAtom(t.rest)}[]`] : []];
|
|
5113
|
+
return `[${inner.join(", ")}]`;
|
|
5022
5114
|
}
|
|
5023
5115
|
case "object": {
|
|
5024
5116
|
if (t.name) {
|
|
@@ -5042,7 +5134,7 @@ function formatTypeUncached(t) {
|
|
|
5042
5134
|
return `${consts.has(n) ? "const " : ""}${n}${constraint ? ` extends ${briefConstraint(constraint)}` : ""}`;
|
|
5043
5135
|
}).join(", ")}>` : "";
|
|
5044
5136
|
const ps = t.params.map((p) => `${p.name ? p.name + ": " : ""}${formatType(p.type)}`);
|
|
5045
|
-
if (t.varargs) ps.push(
|
|
5137
|
+
if (t.varargs) ps.push(t.restIsWhole ? `...args: ${formatType(t.varargs)}` : `...args: ${formatAtom(t.varargs)}[]`);
|
|
5046
5138
|
return `${gen}(${ps.join(", ")}) => ${formatPredicate(t) ?? formatType(t.returns)}`;
|
|
5047
5139
|
}
|
|
5048
5140
|
case "typeParam":
|
|
@@ -5246,6 +5338,11 @@ function moduleExports(program, scopes, types, resolveModule) {
|
|
|
5246
5338
|
}
|
|
5247
5339
|
return { values, types: exportedTypes, default: defaultType };
|
|
5248
5340
|
}
|
|
5341
|
+
function isBelow(k, key) {
|
|
5342
|
+
if (!k.startsWith(key) || k.length === key.length) return false;
|
|
5343
|
+
const next = k[key.length];
|
|
5344
|
+
return next === "." || next === "#" || next === "[";
|
|
5345
|
+
}
|
|
5249
5346
|
function bindKey(id) {
|
|
5250
5347
|
return `$${id}`;
|
|
5251
5348
|
}
|
|
@@ -5263,9 +5360,6 @@ function mergeEnv(a, b, base) {
|
|
|
5263
5360
|
function mergeIndexer(a, b) {
|
|
5264
5361
|
return a ? { key: union([a.key, b.key]), value: union([a.value, b.value]) } : b;
|
|
5265
5362
|
}
|
|
5266
|
-
function producesMultipleValues(e) {
|
|
5267
|
-
return e.type === "CallExpression" || e.type === "MethodCallExpression" || e.type === "VarargExpression";
|
|
5268
|
-
}
|
|
5269
5363
|
function isFreshLiteralExpr(e) {
|
|
5270
5364
|
if (!e) return false;
|
|
5271
5365
|
switch (e.type) {
|
|
@@ -5302,11 +5396,11 @@ function collectInferNames(node) {
|
|
|
5302
5396
|
walk(n.element);
|
|
5303
5397
|
return;
|
|
5304
5398
|
case "ParenthesizedTypeNode":
|
|
5305
|
-
case "VariadicTypeNode":
|
|
5306
5399
|
walk(n.typeAnnotation);
|
|
5307
5400
|
return;
|
|
5308
5401
|
case "TupleTypeNode":
|
|
5309
5402
|
n.elements.forEach(walk);
|
|
5403
|
+
walk(n.rest);
|
|
5310
5404
|
return;
|
|
5311
5405
|
case "UnionTypeNode":
|
|
5312
5406
|
case "IntersectionTypeNode":
|
|
@@ -5324,7 +5418,6 @@ function collectInferNames(node) {
|
|
|
5324
5418
|
return;
|
|
5325
5419
|
case "FunctionTypeNode":
|
|
5326
5420
|
n.params.forEach((pp) => walk(pp.typeAnnotation));
|
|
5327
|
-
walk(n.varargType);
|
|
5328
5421
|
walk(n.returnType);
|
|
5329
5422
|
return;
|
|
5330
5423
|
case "TableTypeNode":
|
|
@@ -5369,6 +5462,75 @@ function callsSuper(block) {
|
|
|
5369
5462
|
});
|
|
5370
5463
|
return found;
|
|
5371
5464
|
}
|
|
5465
|
+
var ALL_FIELDS = /* @__PURE__ */ new Set();
|
|
5466
|
+
function definitelyAssigned(block) {
|
|
5467
|
+
const names = /* @__PURE__ */ new Set();
|
|
5468
|
+
for (const statement of block.statements) {
|
|
5469
|
+
switch (statement.type) {
|
|
5470
|
+
case "AssignmentStatement":
|
|
5471
|
+
case "CompoundAssignmentStatement":
|
|
5472
|
+
for (const name of assignedFields({ ...block, statements: [statement] })) names.add(name);
|
|
5473
|
+
break;
|
|
5474
|
+
case "DoStatement": {
|
|
5475
|
+
const inner = definitelyAssigned(statement.body);
|
|
5476
|
+
if (!inner) return void 0;
|
|
5477
|
+
for (const name of inner) names.add(name);
|
|
5478
|
+
break;
|
|
5479
|
+
}
|
|
5480
|
+
case "IfStatement": {
|
|
5481
|
+
if (!statement.alternate) break;
|
|
5482
|
+
const arms = [...statement.clauses.map((c) => c.body), statement.alternate].map(definitelyAssigned);
|
|
5483
|
+
const finishing = arms.filter((arm) => arm !== void 0);
|
|
5484
|
+
if (!finishing.length) return void 0;
|
|
5485
|
+
for (const name of finishing[0]) {
|
|
5486
|
+
if (finishing.every((arm) => arm.has(name))) names.add(name);
|
|
5487
|
+
}
|
|
5488
|
+
break;
|
|
5489
|
+
}
|
|
5490
|
+
case "ReturnStatement":
|
|
5491
|
+
return void 0;
|
|
5492
|
+
case "CallStatement": {
|
|
5493
|
+
const call = statement.expression;
|
|
5494
|
+
if (call.type === "CallExpression" && call.callee.type === "Identifier" && call.callee.name === "error") {
|
|
5495
|
+
return void 0;
|
|
5496
|
+
}
|
|
5497
|
+
break;
|
|
5498
|
+
}
|
|
5499
|
+
}
|
|
5500
|
+
}
|
|
5501
|
+
return names;
|
|
5502
|
+
}
|
|
5503
|
+
function withoutReceiver(t) {
|
|
5504
|
+
if (t.kind === "intersection") return intersection(t.types.map(withoutReceiver));
|
|
5505
|
+
if (t.kind !== "function") return t;
|
|
5506
|
+
const first = t.params[0]?.name;
|
|
5507
|
+
if (first !== "this" && first !== "self") return t;
|
|
5508
|
+
return { ...t, params: t.params.slice(1) };
|
|
5509
|
+
}
|
|
5510
|
+
var METAMETHOD_OPERANDS = {
|
|
5511
|
+
__add: 1,
|
|
5512
|
+
__sub: 1,
|
|
5513
|
+
__mul: 1,
|
|
5514
|
+
__div: 1,
|
|
5515
|
+
__idiv: 1,
|
|
5516
|
+
__mod: 1,
|
|
5517
|
+
__pow: 1,
|
|
5518
|
+
__concat: 1,
|
|
5519
|
+
__eq: 1,
|
|
5520
|
+
__lt: 1,
|
|
5521
|
+
__le: 1,
|
|
5522
|
+
__unm: 0,
|
|
5523
|
+
__len: 0,
|
|
5524
|
+
__tostring: 0,
|
|
5525
|
+
__iter: 0,
|
|
5526
|
+
__call: void 0
|
|
5527
|
+
};
|
|
5528
|
+
var METAMETHOD_RETURNS = {
|
|
5529
|
+
__tostring: stringType,
|
|
5530
|
+
__eq: booleanType,
|
|
5531
|
+
__lt: booleanType,
|
|
5532
|
+
__le: booleanType
|
|
5533
|
+
};
|
|
5372
5534
|
function assignedFields(block) {
|
|
5373
5535
|
const names = /* @__PURE__ */ new Set();
|
|
5374
5536
|
walkNodes(block, (node) => {
|
|
@@ -5394,7 +5556,7 @@ function walkNodes(root, visit) {
|
|
|
5394
5556
|
if (key !== "line" && key !== "column" && value && typeof value === "object") walkNodes(value, visit);
|
|
5395
5557
|
}
|
|
5396
5558
|
}
|
|
5397
|
-
var
|
|
5559
|
+
var LazyMap = class extends Map {
|
|
5398
5560
|
pending = /* @__PURE__ */ new Map();
|
|
5399
5561
|
defer(name, resolve5) {
|
|
5400
5562
|
super.delete(name);
|
|
@@ -5509,14 +5671,14 @@ var TypeAnalyzer = class {
|
|
|
5509
5671
|
scopes;
|
|
5510
5672
|
options;
|
|
5511
5673
|
typeOf = /* @__PURE__ */ new Map();
|
|
5512
|
-
bindingType =
|
|
5674
|
+
bindingType = new LazyMap();
|
|
5513
5675
|
narrowedTypeOf = /* @__PURE__ */ new Map();
|
|
5514
5676
|
typeOfTypeNode = /* @__PURE__ */ new Map();
|
|
5515
5677
|
expectedTypeOf = /* @__PURE__ */ new Map();
|
|
5516
|
-
|
|
5678
|
+
loops = /* @__PURE__ */ new Map();
|
|
5517
5679
|
/** Public: each alias resolved once (generic aliases keep their params as
|
|
5518
5680
|
* `typeParam` nodes in the body). */
|
|
5519
|
-
aliases = new
|
|
5681
|
+
aliases = new LazyMap();
|
|
5520
5682
|
/** Uninstantiated alias definitions, for `Name<Args>` instantiation. */
|
|
5521
5683
|
aliasDefs = /* @__PURE__ */ new Map();
|
|
5522
5684
|
/** Put on every ref to one of `aliasDefs`, so a module that imports the
|
|
@@ -5530,8 +5692,12 @@ var TypeAnalyzer = class {
|
|
|
5530
5692
|
/** Generic parameters currently in lexical scope (alias body / generic fn),
|
|
5531
5693
|
* with their `extends` constraints resolved. */
|
|
5532
5694
|
typeParamScope = [];
|
|
5533
|
-
/** Global types contributed by `declare` statements (libs, then this program).
|
|
5534
|
-
|
|
5695
|
+
/** Global types contributed by `declare` statements (libs, then this program).
|
|
5696
|
+
* A library's are resolved when the name is first used: an engine's
|
|
5697
|
+
* definitions declare hundreds of globals, and a script uses a few. */
|
|
5698
|
+
libGlobalTypes = new LazyMap();
|
|
5699
|
+
/** Each library `declare` of a name, in order, until it is resolved. */
|
|
5700
|
+
libDeclares = /* @__PURE__ */ new Map();
|
|
5535
5701
|
/** Declaration node -> binding, built once so `bindingIdByName` is O(1)
|
|
5536
5702
|
* instead of a scan of every binding per declaration site. */
|
|
5537
5703
|
bindingByDecl = /* @__PURE__ */ new Map();
|
|
@@ -5576,8 +5742,11 @@ var TypeAnalyzer = class {
|
|
|
5576
5742
|
this.indexDeclarations();
|
|
5577
5743
|
for (const [name, id] of this.scopes.globalsByName) {
|
|
5578
5744
|
if (this.deferredDeclares.has(name) && !this.options.globalTypes?.[name]) continue;
|
|
5579
|
-
const
|
|
5580
|
-
this.bindingType.set(id,
|
|
5745
|
+
const given = this.options.globalTypes?.[name];
|
|
5746
|
+
if (given) this.bindingType.set(id, given);
|
|
5747
|
+
else if (this.libGlobalTypes.has(name)) this.bindingType.defer(id, () => this.libGlobalTypes.get(name) ?? anyType);
|
|
5748
|
+
else if (LANGUAGE_GLOBALS.includes(name)) this.bindingType.set(id, arrayOf(unknownType));
|
|
5749
|
+
else this.bindingType.set(id, anyType);
|
|
5581
5750
|
}
|
|
5582
5751
|
setAliasExpander((t) => this.expand(t));
|
|
5583
5752
|
setDeferredBound((t) => this.deferredBound(t));
|
|
@@ -5597,7 +5766,7 @@ var TypeAnalyzer = class {
|
|
|
5597
5766
|
typeOfTypeNode: this.typeOfTypeNode,
|
|
5598
5767
|
expectedTypeOf: this.expectedTypeOf,
|
|
5599
5768
|
aliases: this.resolveDeferredAliases(),
|
|
5600
|
-
|
|
5769
|
+
loops: this.loops,
|
|
5601
5770
|
diagnostics: this.diagnostics
|
|
5602
5771
|
};
|
|
5603
5772
|
}
|
|
@@ -5949,28 +6118,55 @@ var TypeAnalyzer = class {
|
|
|
5949
6118
|
type.name = `typeof ${this.className(stmt)}`;
|
|
5950
6119
|
const shape = this.shapeOf(stmt);
|
|
5951
6120
|
const local = this.superDecl(stmt);
|
|
5952
|
-
const parent = local ? this.classValueType(local) : stmt.superclass ? this.
|
|
6121
|
+
const parent = local ? this.classValueType(local) : stmt.superclass ? this.classStaticsOf(stmt.superclass) : void 0;
|
|
5953
6122
|
if (parent?.kind === "object") {
|
|
5954
6123
|
for (const [key, property] of parent.properties) {
|
|
5955
6124
|
if (!CLASS_LINKS.has(key)) type.properties.set(key, property);
|
|
5956
6125
|
}
|
|
5957
6126
|
}
|
|
5958
6127
|
for (const [key, property] of shape.statics) type.properties.set(key, property);
|
|
5959
|
-
|
|
6128
|
+
if (shape.filling) {
|
|
6129
|
+
(shape.filled ??= []).push(() => {
|
|
6130
|
+
for (const [key, property] of shape.statics) type.properties.set(key, property);
|
|
6131
|
+
});
|
|
6132
|
+
}
|
|
5960
6133
|
const params = this.classTypeParams(stmt);
|
|
5961
|
-
|
|
5962
|
-
|
|
5963
|
-
constructor?.params.filter((p) => p.name !== "this") ?? [],
|
|
5964
|
-
this.selfTypeOf(stmt),
|
|
5965
|
-
constructor?.varargs,
|
|
5966
|
-
params.map((p) => p.name)
|
|
5967
|
-
),
|
|
6134
|
+
let settled;
|
|
6135
|
+
const construct = {
|
|
5968
6136
|
optional: false,
|
|
5969
|
-
readonly: true
|
|
6137
|
+
readonly: true,
|
|
6138
|
+
// Kept, so a class extending it can read what it takes; reaching
|
|
6139
|
+
// it from outside is the error.
|
|
6140
|
+
...stmt.isAbstract ? { abstract: true } : {}
|
|
6141
|
+
};
|
|
6142
|
+
Object.defineProperty(construct, "type", {
|
|
6143
|
+
enumerable: true,
|
|
6144
|
+
get: () => {
|
|
6145
|
+
if (settled) return settled;
|
|
6146
|
+
const constructor = this.constructorType(stmt);
|
|
6147
|
+
const made = fn(
|
|
6148
|
+
constructor?.params.filter((p) => p.name !== "this") ?? [],
|
|
6149
|
+
this.selfTypeOf(stmt),
|
|
6150
|
+
constructor?.varargs,
|
|
6151
|
+
params.map((p) => p.name)
|
|
6152
|
+
);
|
|
6153
|
+
if (!this.shapeOf(stmt).filling) settled = made;
|
|
6154
|
+
return made;
|
|
6155
|
+
}
|
|
5970
6156
|
});
|
|
6157
|
+
type.properties.set("new", construct);
|
|
5971
6158
|
type.properties.set("ParentClass", { type: parent ?? nilType, optional: false, readonly: true });
|
|
5972
6159
|
return type;
|
|
5973
6160
|
}
|
|
6161
|
+
/** The value side of the class `superclass` names when it is not
|
|
6162
|
+
* declared in this file: what the name it refers to holds — an import
|
|
6163
|
+
* brings the class table with it — or else a class of that name. */
|
|
6164
|
+
classStaticsOf(superclass) {
|
|
6165
|
+
const id = this.bindingIdOf(superclass);
|
|
6166
|
+
const bound = id !== void 0 ? this.expand(this.bindingType.get(id) ?? unknownType) : void 0;
|
|
6167
|
+
if (bound?.kind === "object") return bound;
|
|
6168
|
+
return this.classStaticsByNameType(superclass.name);
|
|
6169
|
+
}
|
|
5974
6170
|
/** The value side of a class named by a binding rather than by a
|
|
5975
6171
|
* declaration in this file — an imported one. */
|
|
5976
6172
|
classStaticsByNameType(name) {
|
|
@@ -6031,20 +6227,26 @@ var TypeAnalyzer = class {
|
|
|
6031
6227
|
} finally {
|
|
6032
6228
|
this.emitDiagnostics = wasEmitting;
|
|
6033
6229
|
shape.filling = false;
|
|
6230
|
+
for (const then of shape.filled ?? []) then();
|
|
6231
|
+
shape.filled = void 0;
|
|
6034
6232
|
}
|
|
6035
6233
|
return shape;
|
|
6036
6234
|
}
|
|
6037
6235
|
fillShape(stmt, shape) {
|
|
6038
6236
|
const className = this.className(stmt);
|
|
6039
6237
|
const put = (isStatic, name, property) => {
|
|
6040
|
-
const member = stmt.members.find((m) => m.type !== "ClassConstructor" && m.isStatic === isStatic && m.name.name === name && m.accessibility === "private");
|
|
6041
|
-
const marked = member ? { ...property, private: { owner: stmt, className } } : property;
|
|
6238
|
+
const member = stmt.members.find((m) => m.type !== "ClassConstructor" && m.isStatic === isStatic && m.name.name === name && (m.accessibility === "private" || m.accessibility === "protected"));
|
|
6239
|
+
const marked = member ? { ...property, private: { owner: stmt, className, ...member.accessibility === "protected" ? { protected: true } : {} } } : property;
|
|
6042
6240
|
(isStatic ? shape.statics : shape.instance).set(name, marked);
|
|
6043
6241
|
};
|
|
6044
6242
|
for (const member of stmt.members) {
|
|
6045
6243
|
if (member.type !== "ClassField") continue;
|
|
6046
6244
|
const type = member.typeAnnotation ? this.resolveType(member.typeAnnotation) : member.init ? widen(this.infer(member.init, /* @__PURE__ */ new Map())) : anyType;
|
|
6047
|
-
put(member.isStatic, member.name.name, {
|
|
6245
|
+
put(member.isStatic, member.name.name, {
|
|
6246
|
+
type,
|
|
6247
|
+
optional: false,
|
|
6248
|
+
...member.isReadonly ? { readonly: true } : {}
|
|
6249
|
+
});
|
|
6048
6250
|
}
|
|
6049
6251
|
for (const member of stmt.members) {
|
|
6050
6252
|
switch (member.type) {
|
|
@@ -6053,7 +6255,11 @@ var TypeAnalyzer = class {
|
|
|
6053
6255
|
case "ClassMethod": {
|
|
6054
6256
|
this.paramsFromSignatures(member.func, member.signatures);
|
|
6055
6257
|
const type = member.signatures?.length ? intersection(member.signatures.map((sig) => this.signatureToFnType(sig))) : this.inferFunctionBody(member.func, /* @__PURE__ */ new Map());
|
|
6056
|
-
put(member.isStatic, member.name.name, {
|
|
6258
|
+
put(member.isStatic, member.name.name, {
|
|
6259
|
+
type,
|
|
6260
|
+
optional: false,
|
|
6261
|
+
...member.isAbstract ? { abstract: true } : {}
|
|
6262
|
+
});
|
|
6057
6263
|
break;
|
|
6058
6264
|
}
|
|
6059
6265
|
case "ClassAccessor": {
|
|
@@ -6109,7 +6315,14 @@ var TypeAnalyzer = class {
|
|
|
6109
6315
|
for (const signature of member.signatures ?? []) {
|
|
6110
6316
|
this.checkParamOrder(signature.params, member);
|
|
6111
6317
|
}
|
|
6112
|
-
|
|
6318
|
+
if (member.type === "ClassMethod" && member.isAbstract) continue;
|
|
6319
|
+
const constructing = this.constructing;
|
|
6320
|
+
if (member.type === "ClassConstructor") this.constructing = stmt;
|
|
6321
|
+
try {
|
|
6322
|
+
this.visitFunctionBody(member.func, env);
|
|
6323
|
+
} finally {
|
|
6324
|
+
this.constructing = constructing;
|
|
6325
|
+
}
|
|
6113
6326
|
}
|
|
6114
6327
|
});
|
|
6115
6328
|
}));
|
|
@@ -6156,13 +6369,148 @@ var TypeAnalyzer = class {
|
|
|
6156
6369
|
if (stmt.superclass && this.baseInstance(stmt) && constructor && !callsSuper(constructor.func.body)) {
|
|
6157
6370
|
report(constructor, `'${name}' extends '${stmt.superclass.name}', so its constructor must call 'super(...)'`);
|
|
6158
6371
|
}
|
|
6159
|
-
|
|
6372
|
+
this.checkAbstractMembers(stmt);
|
|
6373
|
+
this.checkOverrides(stmt);
|
|
6374
|
+
this.checkImplements(stmt);
|
|
6375
|
+
this.checkMetamethods(stmt);
|
|
6376
|
+
const assigned = constructor ? definitelyAssigned(constructor.func.body) ?? ALL_FIELDS : /* @__PURE__ */ new Set();
|
|
6160
6377
|
for (const member of stmt.members) {
|
|
6161
6378
|
if (member.type !== "ClassField" || member.isStatic || member.init) continue;
|
|
6162
|
-
if (assigned.has(member.name.name)) continue;
|
|
6379
|
+
if (assigned === ALL_FIELDS || assigned.has(member.name.name)) continue;
|
|
6163
6380
|
const type = member.typeAnnotation ? this.withTypeParams(this.classTypeParams(stmt), () => this.resolveType(member.typeAnnotation)) : anyType;
|
|
6164
6381
|
if (isAssignable(nilType, type)) continue;
|
|
6165
|
-
report(member.name, `'${member.name.name}' has no value: give it one, assign it in the constructor, or let its type admit nil`);
|
|
6382
|
+
report(member.name, `'${member.name.name}' has no value: give it one, assign it in the constructor on every path, or let its type admit nil`);
|
|
6383
|
+
}
|
|
6384
|
+
}
|
|
6385
|
+
/** The class whose constructor is being checked: it, and only it, may
|
|
6386
|
+
* assign the `readonly` fields it declares. */
|
|
6387
|
+
constructing;
|
|
6388
|
+
/** A class that is not abstract has a `new`, so everything its instances
|
|
6389
|
+
* promise has to be there: no abstract member of its own, and every one
|
|
6390
|
+
* it inherits written. */
|
|
6391
|
+
checkAbstractMembers(stmt) {
|
|
6392
|
+
if (stmt.isAbstract) return;
|
|
6393
|
+
const name = this.className(stmt);
|
|
6394
|
+
for (const member of stmt.members) {
|
|
6395
|
+
if (member.type === "ClassMethod" && member.isAbstract) {
|
|
6396
|
+
this.diagnostics.push({
|
|
6397
|
+
node: member.name,
|
|
6398
|
+
message: `'${member.name.name}' is abstract, so '${name}' has to be an 'abstract class'`
|
|
6399
|
+
});
|
|
6400
|
+
}
|
|
6401
|
+
}
|
|
6402
|
+
const own = this.shapeOf(stmt).instance;
|
|
6403
|
+
const missing = [...this.instanceType(stmt).properties].filter(([key, property]) => property.abstract && !own.has(key)).map(([key]) => `'${key}'`);
|
|
6404
|
+
if (missing.length) {
|
|
6405
|
+
this.diagnostics.push({
|
|
6406
|
+
node: stmt.name ?? stmt,
|
|
6407
|
+
message: `'${name}' does not write the abstract ${missing.length === 1 ? "member" : "members"} ${missing.join(", ")} of the class it extends; write ${missing.length === 1 ? "it" : "them"}, or make '${name}' abstract too`
|
|
6408
|
+
});
|
|
6409
|
+
}
|
|
6410
|
+
}
|
|
6411
|
+
/** A member written again in a class that extends another has to fit
|
|
6412
|
+
* where the base's stood — anything reading an instance as the base
|
|
6413
|
+
* would otherwise be told wrong. `override` says a member is such a one,
|
|
6414
|
+
* and is wrong on a member that is not. */
|
|
6415
|
+
checkOverrides(stmt) {
|
|
6416
|
+
const base = this.baseInstance(stmt);
|
|
6417
|
+
const name = this.className(stmt);
|
|
6418
|
+
const own = this.shapeOf(stmt).instance;
|
|
6419
|
+
for (const member of stmt.members) {
|
|
6420
|
+
if (member.type === "ClassConstructor" || member.isStatic) continue;
|
|
6421
|
+
const key = member.name.name;
|
|
6422
|
+
const inherited = CLASS_LINKS.has(key) ? void 0 : base?.properties.get(key);
|
|
6423
|
+
if (member.isOverride && !inherited) {
|
|
6424
|
+
this.diagnostics.push({
|
|
6425
|
+
node: member.name,
|
|
6426
|
+
message: base ? `'${key}' is marked 'override', but '${stmt.superclass.name}' has no member of that name` : `'${key}' is marked 'override', but '${name}' does not extend another class`
|
|
6427
|
+
});
|
|
6428
|
+
continue;
|
|
6429
|
+
}
|
|
6430
|
+
const mine = own.get(key);
|
|
6431
|
+
if (!inherited || !mine || inherited.private && !inherited.private.protected) continue;
|
|
6432
|
+
const written = withoutReceiver(mine.type);
|
|
6433
|
+
const expected = withoutReceiver(inherited.type);
|
|
6434
|
+
if (isAssignable(written, expected)) continue;
|
|
6435
|
+
this.diagnostics.push({
|
|
6436
|
+
node: member.name,
|
|
6437
|
+
message: `'${key}' in '${name}' does not fit the '${key}' of '${stmt.superclass.name}' it replaces: '${briefType(written)}' is not assignable to '${briefType(expected)}'`
|
|
6438
|
+
});
|
|
6439
|
+
}
|
|
6440
|
+
}
|
|
6441
|
+
/** `implements Shape`: every member the shape names, of a type that fits.
|
|
6442
|
+
* A method's own `this` is left out of the comparison on both sides. */
|
|
6443
|
+
checkImplements(stmt) {
|
|
6444
|
+
if (!stmt.implements?.length) return;
|
|
6445
|
+
const name = this.className(stmt);
|
|
6446
|
+
const instance = this.instanceType(stmt).properties;
|
|
6447
|
+
for (const node of stmt.implements) {
|
|
6448
|
+
const shown = node.type === "TypeReference" ? node.namespace ? `${node.namespace}.${node.base}` : node.base : void 0;
|
|
6449
|
+
const target = this.expand(this.withTypeParams(this.classTypeParams(stmt), () => this.resolveType(node)));
|
|
6450
|
+
if (target.kind === "any") continue;
|
|
6451
|
+
if (target.kind !== "object") {
|
|
6452
|
+
this.diagnostics.push({
|
|
6453
|
+
node,
|
|
6454
|
+
message: `A class can only implement an object type or a class, not '${briefType(target)}'`
|
|
6455
|
+
});
|
|
6456
|
+
continue;
|
|
6457
|
+
}
|
|
6458
|
+
const label = shown ?? briefType(target);
|
|
6459
|
+
const missing = [];
|
|
6460
|
+
for (const [key, wanted] of target.properties) {
|
|
6461
|
+
const have = instance.get(key);
|
|
6462
|
+
if (!have) {
|
|
6463
|
+
if (!wanted.optional) missing.push(`'${key}'`);
|
|
6464
|
+
continue;
|
|
6465
|
+
}
|
|
6466
|
+
const written = withoutReceiver(have.type);
|
|
6467
|
+
const expected = withoutReceiver(wanted.optional ? optional(wanted.type) : wanted.type);
|
|
6468
|
+
if (isAssignable(written, expected)) continue;
|
|
6469
|
+
this.diagnostics.push({
|
|
6470
|
+
node,
|
|
6471
|
+
message: `'${name}' does not implement '${label}': its '${key}' is '${briefType(written)}', not '${briefType(expected)}'`
|
|
6472
|
+
});
|
|
6473
|
+
}
|
|
6474
|
+
if (missing.length) {
|
|
6475
|
+
this.diagnostics.push({
|
|
6476
|
+
node,
|
|
6477
|
+
message: `'${name}' does not implement '${label}': ${missing.join(", ")} ${missing.length === 1 ? "is" : "are"} missing`
|
|
6478
|
+
});
|
|
6479
|
+
}
|
|
6480
|
+
}
|
|
6481
|
+
}
|
|
6482
|
+
/** A method named for a metamethod *is* one — the class table is its
|
|
6483
|
+
* instances' metatable — so it has to take what Luau hands it and give
|
|
6484
|
+
* back what Luau expects. */
|
|
6485
|
+
checkMetamethods(stmt) {
|
|
6486
|
+
const instance = this.shapeOf(stmt).instance;
|
|
6487
|
+
for (const member of stmt.members) {
|
|
6488
|
+
if (member.type !== "ClassMethod" || !(member.name.name in METAMETHOD_OPERANDS)) continue;
|
|
6489
|
+
const key = member.name.name;
|
|
6490
|
+
if (member.isStatic) {
|
|
6491
|
+
this.diagnostics.push({
|
|
6492
|
+
node: member.name,
|
|
6493
|
+
message: `'${key}' is a metamethod of the instances; it cannot be static`
|
|
6494
|
+
});
|
|
6495
|
+
continue;
|
|
6496
|
+
}
|
|
6497
|
+
const operands = METAMETHOD_OPERANDS[key];
|
|
6498
|
+
const written = member.func.params.filter((p) => p.name !== "this").length;
|
|
6499
|
+
if (operands !== void 0 && (written !== operands || member.func.hasVarargs)) {
|
|
6500
|
+
this.diagnostics.push({
|
|
6501
|
+
node: member.name,
|
|
6502
|
+
message: operands === 0 ? `'${key}' takes nothing besides 'this'` : `'${key}' takes one operand besides 'this'`
|
|
6503
|
+
});
|
|
6504
|
+
continue;
|
|
6505
|
+
}
|
|
6506
|
+
const required = METAMETHOD_RETURNS[key];
|
|
6507
|
+
const returns = this.overloadsOf(instance.get(key)?.type ?? anyType)[0]?.returns;
|
|
6508
|
+
if (required && returns && !isAssignable(returns, required)) {
|
|
6509
|
+
this.diagnostics.push({
|
|
6510
|
+
node: member.name,
|
|
6511
|
+
message: `'${key}' has to return ${briefType(required)}, not '${briefType(returns)}'`
|
|
6512
|
+
});
|
|
6513
|
+
}
|
|
6166
6514
|
}
|
|
6167
6515
|
}
|
|
6168
6516
|
/** `extends` must name a class, and the chain must end. */
|
|
@@ -6220,19 +6568,42 @@ var TypeAnalyzer = class {
|
|
|
6220
6568
|
harvestDeclares(block, own = false) {
|
|
6221
6569
|
for (const stmt of block.statements) {
|
|
6222
6570
|
if (stmt.type !== "DeclareStatement") continue;
|
|
6223
|
-
if (own
|
|
6571
|
+
if (!own) {
|
|
6572
|
+
const declares = this.libDeclares.get(stmt.name);
|
|
6573
|
+
if (declares) declares.push(stmt);
|
|
6574
|
+
else {
|
|
6575
|
+
this.libDeclares.set(stmt.name, [stmt]);
|
|
6576
|
+
this.libGlobalTypes.defer(stmt.name, () => this.resolveLibDeclares(stmt.name));
|
|
6577
|
+
}
|
|
6578
|
+
continue;
|
|
6579
|
+
}
|
|
6580
|
+
if (containsTypeQuery(stmt.valueType) || referencedTypeNames(stmt.valueType).some((name) => this.dependsOnTypeQuery(name))) {
|
|
6224
6581
|
this.deferredDeclares.set(stmt.name, stmt);
|
|
6225
6582
|
continue;
|
|
6226
6583
|
}
|
|
6227
|
-
|
|
6228
|
-
const prev = this.libGlobalTypes.get(stmt.name);
|
|
6229
|
-
const overload = prev && stmt.valueType.type === "FunctionTypeNode" && (prev.kind === "function" || prev.kind === "intersection");
|
|
6230
|
-
this.libGlobalTypes.set(
|
|
6231
|
-
stmt.name,
|
|
6232
|
-
overload ? intersection([prev, t]) : this.mergeDeclared(prev, t)
|
|
6233
|
-
);
|
|
6584
|
+
this.libGlobalTypes.set(stmt.name, this.declaredOver(this.libGlobalTypes.get(stmt.name), stmt));
|
|
6234
6585
|
}
|
|
6235
6586
|
}
|
|
6587
|
+
/** Every library `declare` of `name`, in order. They are written at the
|
|
6588
|
+
* top level of their files, so they are read as if there — outside any
|
|
6589
|
+
* generic the use that asked for them happens to sit in. */
|
|
6590
|
+
resolveLibDeclares(name) {
|
|
6591
|
+
const scope = this.typeParamScope.splice(0);
|
|
6592
|
+
try {
|
|
6593
|
+
let type;
|
|
6594
|
+
for (const stmt of this.libDeclares.get(name) ?? []) type = this.declaredOver(type, stmt);
|
|
6595
|
+
return type ?? anyType;
|
|
6596
|
+
} finally {
|
|
6597
|
+
this.typeParamScope.push(...scope);
|
|
6598
|
+
}
|
|
6599
|
+
}
|
|
6600
|
+
/** A `declare` on top of what earlier ones said about the same name: a
|
|
6601
|
+
* function adds an overload, a table adds members. */
|
|
6602
|
+
declaredOver(prev, stmt) {
|
|
6603
|
+
const t = this.resolveType(stmt.valueType);
|
|
6604
|
+
const overload = prev && stmt.valueType.type === "FunctionTypeNode" && (prev.kind === "function" || prev.kind === "intersection");
|
|
6605
|
+
return overload ? intersection([prev, t]) : this.mergeDeclared(prev, t);
|
|
6606
|
+
}
|
|
6236
6607
|
resolveAllAliases() {
|
|
6237
6608
|
for (const [name, def] of this.aliasDefs) {
|
|
6238
6609
|
if (def.class && !this.program.body.statements.includes(def.class)) {
|
|
@@ -6278,11 +6649,10 @@ var TypeAnalyzer = class {
|
|
|
6278
6649
|
}
|
|
6279
6650
|
imported;
|
|
6280
6651
|
/** What a `return` gives, against what the function declared. */
|
|
6281
|
-
checkReturn(stmt, declared,
|
|
6652
|
+
checkReturn(stmt, declared, actual, env) {
|
|
6282
6653
|
if (!declared || !this.emitDiagnostics) return;
|
|
6283
6654
|
if (declared.kind === "any" || declared.kind === "unknown" || this.namesNothing(declared)) return;
|
|
6284
|
-
const
|
|
6285
|
-
const source = stmt.arguments.length === 1 ? sources[0] : void 0;
|
|
6655
|
+
const source = stmt.argument;
|
|
6286
6656
|
const fits = source ? this.fitsAnnotation(source, declared, actual, env) : isAssignable(actual, declared) || isAssignable(widen(actual), declared);
|
|
6287
6657
|
if (fits) return;
|
|
6288
6658
|
this.diagnostics.push({
|
|
@@ -6430,21 +6800,12 @@ var TypeAnalyzer = class {
|
|
|
6430
6800
|
this.instantiationDepth--;
|
|
6431
6801
|
}
|
|
6432
6802
|
}
|
|
6433
|
-
/** Pair written type arguments with the parameters they instantiate.
|
|
6434
|
-
*
|
|
6435
|
-
* one pack: `Signal<Instance, string>` binds `T` to `(Instance, string)`,
|
|
6436
|
-
* and `Signal<()>` to the empty pack. Left out, a parameter takes its
|
|
6437
|
-
* default (`T... = ...any` is `any`), or `unknown`. */
|
|
6803
|
+
/** Pair written type arguments with the parameters they instantiate.
|
|
6804
|
+
* Left out, a parameter takes its default, or `unknown`. */
|
|
6438
6805
|
bindTypeArguments(params, args) {
|
|
6439
6806
|
const subst = /* @__PURE__ */ new Map();
|
|
6440
6807
|
params.forEach((p, i) => {
|
|
6441
|
-
|
|
6442
|
-
if (p.isPack && i < args.length) {
|
|
6443
|
-
const rest = args.slice(i);
|
|
6444
|
-
const single = rest.length === 1 ? rest[0] : void 0;
|
|
6445
|
-
arg = single && (single.kind === "tuple" && single.isPack || single.kind === "typeParam" || single.kind === "any") ? single : tuple([...rest], true);
|
|
6446
|
-
}
|
|
6447
|
-
subst.set(p.name, arg ?? (p.default ? this.resolveType(p.default) : unknownType));
|
|
6808
|
+
subst.set(p.name, args[i] ?? (p.default ? this.resolveType(p.default) : unknownType));
|
|
6448
6809
|
});
|
|
6449
6810
|
return subst;
|
|
6450
6811
|
}
|
|
@@ -6536,8 +6897,13 @@ var TypeAnalyzer = class {
|
|
|
6536
6897
|
return literal(node.value);
|
|
6537
6898
|
case "ArrayTypeNode":
|
|
6538
6899
|
return arrayOf(this.resolveType(node.element));
|
|
6539
|
-
case "TupleTypeNode":
|
|
6540
|
-
|
|
6900
|
+
case "TupleTypeNode": {
|
|
6901
|
+
const rest = node.rest && this.expand(this.resolveType(node.rest));
|
|
6902
|
+
return tuple(
|
|
6903
|
+
node.elements.map((e) => this.resolveType(e)),
|
|
6904
|
+
rest ? rest.kind === "array" ? rest.element : rest.kind === "any" ? anyType : unknownType : void 0
|
|
6905
|
+
);
|
|
6906
|
+
}
|
|
6541
6907
|
case "UnionTypeNode":
|
|
6542
6908
|
return union(node.types.map((t) => this.resolveType(t)));
|
|
6543
6909
|
case "IntersectionTypeNode":
|
|
@@ -6573,15 +6939,13 @@ var TypeAnalyzer = class {
|
|
|
6573
6939
|
type: p.optional ? optional(this.resolveType(p.typeAnnotation)) : this.resolveType(p.typeAnnotation),
|
|
6574
6940
|
optional: p.optional
|
|
6575
6941
|
}));
|
|
6576
|
-
|
|
6577
|
-
const restElement = restParam ? this.resolveType(restParam.typeAnnotation) : void 0;
|
|
6578
|
-
return this.withTypeParamDefaults(fn(
|
|
6942
|
+
return this.withTypeParamDefaults(this.withRest(fn(
|
|
6579
6943
|
params,
|
|
6580
6944
|
this.resolveType(node.returnType),
|
|
6581
|
-
|
|
6945
|
+
void 0,
|
|
6582
6946
|
names,
|
|
6583
6947
|
this.resolvePredicate(node.predicate, params)
|
|
6584
|
-
), node.generics);
|
|
6948
|
+
), node.params), node.generics);
|
|
6585
6949
|
});
|
|
6586
6950
|
}
|
|
6587
6951
|
case "TypeofTypeNode": {
|
|
@@ -6645,13 +7009,6 @@ var TypeAnalyzer = class {
|
|
|
6645
7009
|
})
|
|
6646
7010
|
);
|
|
6647
7011
|
}
|
|
6648
|
-
case "VariadicTypeNode":
|
|
6649
|
-
return this.resolveType(node.typeAnnotation);
|
|
6650
|
-
case "TypePackNode": {
|
|
6651
|
-
if (node.types.length === 1 && !node.hasVarargs) return this.resolveType(node.types[0]);
|
|
6652
|
-
if (!node.types.length && node.varargType) return this.resolveType(node.varargType);
|
|
6653
|
-
return tuple(node.types.map((t) => this.resolveType(t)), true);
|
|
6654
|
-
}
|
|
6655
7012
|
}
|
|
6656
7013
|
}
|
|
6657
7014
|
// --------------------------------------------------------
|
|
@@ -6717,15 +7074,18 @@ var TypeAnalyzer = class {
|
|
|
6717
7074
|
case "array":
|
|
6718
7075
|
return arrayOf(this.reduceType(t.element));
|
|
6719
7076
|
case "tuple":
|
|
6720
|
-
return tuple(t.elements.map((e) => this.reduceType(e)), t.
|
|
7077
|
+
return tuple(t.elements.map((e) => this.reduceType(e)), t.rest && this.reduceType(t.rest));
|
|
6721
7078
|
case "function":
|
|
6722
|
-
return
|
|
6723
|
-
|
|
6724
|
-
|
|
6725
|
-
|
|
6726
|
-
|
|
6727
|
-
|
|
6728
|
-
|
|
7079
|
+
return {
|
|
7080
|
+
...fn(
|
|
7081
|
+
t.params.map((p) => ({ ...p, type: this.reduceType(p.type) })),
|
|
7082
|
+
this.reduceType(t.returns),
|
|
7083
|
+
t.varargs && this.reduceType(t.varargs),
|
|
7084
|
+
t.typeParams,
|
|
7085
|
+
t.predicate
|
|
7086
|
+
),
|
|
7087
|
+
...t.restIsWhole ? { restIsWhole: true } : {}
|
|
7088
|
+
};
|
|
6729
7089
|
case "object": {
|
|
6730
7090
|
if (t.class) return t;
|
|
6731
7091
|
const entries = [];
|
|
@@ -6940,7 +7300,8 @@ var TypeAnalyzer = class {
|
|
|
6940
7300
|
this.applyContext(stmt.init[i], this.resolveType(target.typeAnnotation));
|
|
6941
7301
|
}
|
|
6942
7302
|
});
|
|
6943
|
-
const { types: valueTypes, sources } = this.valueList(stmt.init, env
|
|
7303
|
+
const { types: valueTypes, sources } = this.valueList(stmt.init, env);
|
|
7304
|
+
this.checkValueCount(stmt, stmt.names.length, stmt.init, valueTypes);
|
|
6944
7305
|
stmt.names.forEach((target, i) => {
|
|
6945
7306
|
const inferred = valueTypes[i] ?? (stmt.init.length ? unknownType : nilType);
|
|
6946
7307
|
const source = sources[i];
|
|
@@ -7029,7 +7390,8 @@ var TypeAnalyzer = class {
|
|
|
7029
7390
|
if (id !== void 0 && this.annotated.has(id)) this.applyContext(value, this.bindingType.get(id));
|
|
7030
7391
|
}
|
|
7031
7392
|
});
|
|
7032
|
-
const { types: valueTypes, sources } = this.valueList(stmt.values, env
|
|
7393
|
+
const { types: valueTypes, sources } = this.valueList(stmt.values, env);
|
|
7394
|
+
this.checkValueCount(stmt, stmt.targets.length, stmt.values, valueTypes);
|
|
7033
7395
|
stmt.targets.forEach((target, i) => {
|
|
7034
7396
|
const vt = valueTypes[i] ?? unknownType;
|
|
7035
7397
|
const source = sources[i];
|
|
@@ -7116,23 +7478,26 @@ var TypeAnalyzer = class {
|
|
|
7116
7478
|
return;
|
|
7117
7479
|
}
|
|
7118
7480
|
case "GenericForStatement": {
|
|
7119
|
-
const
|
|
7481
|
+
const sourceType = this.infer(stmt.iterator, env);
|
|
7120
7482
|
const bodyEnv = forkEnv(env);
|
|
7121
|
-
const rows =
|
|
7483
|
+
const rows = this.iterationRows(stmt.iterator, sourceType);
|
|
7122
7484
|
if (rows) {
|
|
7123
|
-
|
|
7124
|
-
|
|
7125
|
-
|
|
7126
|
-
|
|
7127
|
-
|
|
7128
|
-
|
|
7129
|
-
|
|
7485
|
+
this.bindPattern(
|
|
7486
|
+
stmt.variable,
|
|
7487
|
+
union(rows.map((row) => tuple(row))),
|
|
7488
|
+
bodyEnv,
|
|
7489
|
+
stmt.kind === "const" ? "keep" : "widen"
|
|
7490
|
+
);
|
|
7491
|
+
const parts = stmt.variable.type === "ArrayPattern" && !stmt.variable.rest ? stmt.variable.elements.slice(0, 2).map((el) => el?.value) : [];
|
|
7492
|
+
const ids = parts.map((p) => p?.type === "IdentifierPattern" ? this.bindingIdByName(p.name, p) : void 0);
|
|
7493
|
+
if (ids.length === 2 && ids.every((id) => id !== void 0)) {
|
|
7494
|
+
this.correlateBindings(bodyEnv, ids, rows);
|
|
7495
|
+
}
|
|
7496
|
+
this.loops.set(stmt, { walks: "iteration", viaIter: false });
|
|
7130
7497
|
} else {
|
|
7131
|
-
const [
|
|
7132
|
-
stmt.
|
|
7133
|
-
|
|
7134
|
-
});
|
|
7135
|
-
if (iteratesValues && stmt.iterators.length === 1) this.iteratesValues.add(stmt);
|
|
7498
|
+
const [item, form] = this.iterationOf(stmt.iterator, sourceType);
|
|
7499
|
+
this.bindPattern(stmt.variable, item, bodyEnv, "widen");
|
|
7500
|
+
this.loops.set(stmt, form);
|
|
7136
7501
|
}
|
|
7137
7502
|
this.visitBlock(stmt.body, bodyEnv);
|
|
7138
7503
|
return;
|
|
@@ -7140,19 +7505,10 @@ var TypeAnalyzer = class {
|
|
|
7140
7505
|
case "ReturnStatement": {
|
|
7141
7506
|
const declared = this.declaredReturns[this.declaredReturns.length - 1];
|
|
7142
7507
|
const wanted = declared ?? this.contextualReturns[this.contextualReturns.length - 1];
|
|
7143
|
-
if (wanted)
|
|
7144
|
-
|
|
7145
|
-
|
|
7146
|
-
|
|
7147
|
-
stmt.arguments.forEach((a, i) => this.applyContext(a, wanted.elements[i]));
|
|
7148
|
-
}
|
|
7149
|
-
}
|
|
7150
|
-
const want = declared?.kind === "tuple" && declared.isPack ? declared.elements.length : 0;
|
|
7151
|
-
const { types, sources } = this.valueList(stmt.arguments, env, want);
|
|
7152
|
-
this.checkReturn(stmt, declared, types, sources, env);
|
|
7153
|
-
if (this.returnTypes) {
|
|
7154
|
-
this.returnTypes.push(stmt.arguments.length === 0 ? nilType : types.length === 1 ? types[0] : tuple([...types], true));
|
|
7155
|
-
}
|
|
7508
|
+
if (wanted && stmt.argument) this.applyContext(stmt.argument, wanted);
|
|
7509
|
+
const actual = stmt.argument ? this.infer(stmt.argument, env) : nilType;
|
|
7510
|
+
this.checkReturn(stmt, declared, actual, env);
|
|
7511
|
+
if (this.returnTypes) this.returnTypes.push(actual);
|
|
7156
7512
|
return;
|
|
7157
7513
|
}
|
|
7158
7514
|
case "ExportStatement":
|
|
@@ -7239,7 +7595,7 @@ var TypeAnalyzer = class {
|
|
|
7239
7595
|
* `sources` maps each produced value back to the expression it came from
|
|
7240
7596
|
* (undefined for the 2nd and later values of a multi-value call), so the
|
|
7241
7597
|
* caller can still do contextual typing against the written expression. */
|
|
7242
|
-
valueList(exprs, env
|
|
7598
|
+
valueList(exprs, env) {
|
|
7243
7599
|
const types = [];
|
|
7244
7600
|
const sources = [];
|
|
7245
7601
|
exprs.forEach((e, i) => {
|
|
@@ -7255,31 +7611,29 @@ var TypeAnalyzer = class {
|
|
|
7255
7611
|
}
|
|
7256
7612
|
return;
|
|
7257
7613
|
}
|
|
7258
|
-
|
|
7259
|
-
|
|
7260
|
-
sources.push(e);
|
|
7261
|
-
} while (last && types.length < want);
|
|
7262
|
-
return;
|
|
7263
|
-
}
|
|
7264
|
-
if (last && e.type === "VarargExpression") {
|
|
7265
|
-
do {
|
|
7266
|
-
types.push(t);
|
|
7267
|
-
sources.push(types.length - 1 === i ? e : void 0);
|
|
7268
|
-
} while (types.length < want);
|
|
7614
|
+
types.push(t);
|
|
7615
|
+
sources.push(e);
|
|
7269
7616
|
return;
|
|
7270
7617
|
}
|
|
7271
|
-
|
|
7272
|
-
|
|
7273
|
-
types.push(el);
|
|
7274
|
-
sources.push(j === 0 ? e : void 0);
|
|
7275
|
-
});
|
|
7276
|
-
return;
|
|
7277
|
-
}
|
|
7278
|
-
types.push(t.kind === "tuple" && t.isPack && producesMultipleValues(e) ? t.elements[0] ?? nilType : t);
|
|
7618
|
+
void last;
|
|
7619
|
+
types.push(t);
|
|
7279
7620
|
sources.push(e);
|
|
7280
7621
|
});
|
|
7281
7622
|
return { types, sources };
|
|
7282
7623
|
}
|
|
7624
|
+
/** `const a, b = x, y` pairs names with values, one each. A call is one
|
|
7625
|
+
* value — several come back as an array — so `const a, b = f()` is a
|
|
7626
|
+
* name short of a value, and says how to take them apart. */
|
|
7627
|
+
checkValueCount(node, names, values, types) {
|
|
7628
|
+
if (!this.emitDiagnostics || !values.length || types.length >= names) return;
|
|
7629
|
+
const last = values[values.length - 1];
|
|
7630
|
+
const lastType = types[types.length - 1] && this.expand(types[types.length - 1]);
|
|
7631
|
+
const call = last.type === "CallExpression" || last.type === "MethodCallExpression";
|
|
7632
|
+
this.diagnostics.push({
|
|
7633
|
+
node,
|
|
7634
|
+
message: call && (lastType?.kind === "tuple" || lastType?.kind === "array") ? `A call is one value, here an array: take its parts with '[${Array.from({ length: names }, (_, i) => String.fromCharCode(97 + i)).join(", ")}] = ...'` : `${names} names, but ${types.length} ${types.length === 1 ? "value" : "values"}: each name needs one`
|
|
7635
|
+
});
|
|
7636
|
+
}
|
|
7283
7637
|
/** Run `visit` with a fresh place to collect `break` states, and return
|
|
7284
7638
|
* what it collected. */
|
|
7285
7639
|
withBreakScope(visit) {
|
|
@@ -7428,18 +7782,29 @@ var TypeAnalyzer = class {
|
|
|
7428
7782
|
* context. */
|
|
7429
7783
|
contextualArrays = /* @__PURE__ */ new WeakMap();
|
|
7430
7784
|
applyArrayContext(e, expected) {
|
|
7431
|
-
const
|
|
7785
|
+
const members = this.expectedMembers(expected);
|
|
7786
|
+
const tuples = members.filter((m) => m.kind === "tuple");
|
|
7787
|
+
const spread = e.elements.some((el) => el.type === "SpreadElement");
|
|
7788
|
+
const length = e.elements.length;
|
|
7789
|
+
const fitting = tuples.find((t) => !spread && !t.rest && t.elements.length === length) ?? tuples.find((t) => t.rest !== void 0 && (spread || length >= t.elements.length));
|
|
7790
|
+
const target = fitting ?? members.find((m) => m.kind === "array") ?? tuples[0];
|
|
7432
7791
|
if (!target) return;
|
|
7792
|
+
if (target.kind === "tuple" && e.elements.length) this.tupleArrays.set(e, target);
|
|
7433
7793
|
if (!e.elements.length) {
|
|
7434
7794
|
this.contextualArrays.set(e, target);
|
|
7435
7795
|
return;
|
|
7436
7796
|
}
|
|
7437
7797
|
e.elements.forEach((element, i) => {
|
|
7438
7798
|
if (element.type === "SpreadElement") return;
|
|
7439
|
-
const
|
|
7799
|
+
const tupleTarget = target;
|
|
7800
|
+
const elementType = target.kind === "array" ? target.element : tupleTarget.elements[i] ?? tupleTarget.rest;
|
|
7440
7801
|
this.applyContext(element, elementType);
|
|
7441
7802
|
});
|
|
7442
7803
|
}
|
|
7804
|
+
/** Array literals written where a tuple is wanted — a `return` of a
|
|
7805
|
+
* function that returns one, an argument, an annotated `const` — and
|
|
7806
|
+
* the tuple each is to be. */
|
|
7807
|
+
tupleArrays = /* @__PURE__ */ new WeakMap();
|
|
7443
7808
|
/** `{ list: [] }` where `{ list: thread[] }` is expected: each field's
|
|
7444
7809
|
* value gets its property's type as context. */
|
|
7445
7810
|
applyTableContext(e, expected) {
|
|
@@ -7495,32 +7860,33 @@ var TypeAnalyzer = class {
|
|
|
7495
7860
|
}
|
|
7496
7861
|
return tuple(target.elements.map((el) => el ? leaf(el.value, el.default) : anyType));
|
|
7497
7862
|
}
|
|
7498
|
-
/**
|
|
7499
|
-
*
|
|
7500
|
-
*
|
|
7501
|
-
|
|
7502
|
-
|
|
7503
|
-
|
|
7504
|
-
|
|
7505
|
-
|
|
7506
|
-
const
|
|
7507
|
-
if (
|
|
7508
|
-
|
|
7509
|
-
|
|
7510
|
-
|
|
7511
|
-
|
|
7512
|
-
|
|
7863
|
+
/** A function type with the rest parameter among `params` written in:
|
|
7864
|
+
* `...args: T[]` collects `T`s, and `...args: A` — `A` a tuple, or a
|
|
7865
|
+
* type parameter standing for one — is exactly `A`'s elements. */
|
|
7866
|
+
withRest(f, params) {
|
|
7867
|
+
const rest = params.find((p) => p.rest);
|
|
7868
|
+
if (!rest) return f;
|
|
7869
|
+
if (!rest.typeAnnotation) return { ...f, varargs: unknownType };
|
|
7870
|
+
const declared = this.resolveType(rest.typeAnnotation);
|
|
7871
|
+
const t = this.expand(declared);
|
|
7872
|
+
if (t.kind === "array") return { ...f, varargs: t.element };
|
|
7873
|
+
if (t.kind === "any") return { ...f, varargs: anyType };
|
|
7874
|
+
if (declared.kind === "infer") return { ...f, varargs: declared, restIsWhole: true };
|
|
7875
|
+
if (t.kind === "tuple") {
|
|
7876
|
+
return { ...f, params: [...f.params, ...t.elements.map((type) => ({ type }))], varargs: t.rest };
|
|
7877
|
+
}
|
|
7878
|
+
if (declared.kind === "typeParam") return { ...f, varargs: declared, restIsWhole: true };
|
|
7879
|
+
return { ...f, varargs: unknownType };
|
|
7880
|
+
}
|
|
7513
7881
|
/** What each function body being walked declared it returns. */
|
|
7514
7882
|
declaredReturns = [];
|
|
7515
|
-
/** Run `body` with
|
|
7883
|
+
/** Run `body` with `return` as `func` declares it. */
|
|
7516
7884
|
withVarargs(func, body) {
|
|
7517
|
-
this.varargs.push(this.varargElement(func));
|
|
7518
7885
|
this.declaredReturns.push(func.predicate ? booleanType : func.returnType ? this.resolveType(func.returnType) : void 0);
|
|
7519
7886
|
this.contextualReturns.push(this.contextualReturnOf.get(func));
|
|
7520
7887
|
try {
|
|
7521
7888
|
return body();
|
|
7522
7889
|
} finally {
|
|
7523
|
-
this.varargs.pop();
|
|
7524
7890
|
this.declaredReturns.pop();
|
|
7525
7891
|
this.contextualReturns.pop();
|
|
7526
7892
|
}
|
|
@@ -7579,7 +7945,7 @@ var TypeAnalyzer = class {
|
|
|
7579
7945
|
if (type.kind !== "function") return type;
|
|
7580
7946
|
const defaults = {};
|
|
7581
7947
|
for (const generic of generics) {
|
|
7582
|
-
if (generic.default
|
|
7948
|
+
if (generic.default) defaults[generic.name] = this.resolveType(generic.default);
|
|
7583
7949
|
}
|
|
7584
7950
|
return Object.keys(defaults).length ? { ...type, typeParamDefaults: defaults } : type;
|
|
7585
7951
|
}
|
|
@@ -7598,7 +7964,10 @@ var TypeAnalyzer = class {
|
|
|
7598
7964
|
const preserve = keepsLiterals(param) || param.kind !== "typeParam" && containsTypeParam(param);
|
|
7599
7965
|
unify(p.type, preserve ? arg : widen(arg), vars, subst);
|
|
7600
7966
|
});
|
|
7601
|
-
if (f.varargs) {
|
|
7967
|
+
if (f.varargs && f.restIsWhole) {
|
|
7968
|
+
const rest = argTypes.slice(f.params.length).map((a) => a === void 0 ? unknownType : widen(a));
|
|
7969
|
+
unify(f.varargs, tuple(rest), vars, subst);
|
|
7970
|
+
} else if (f.varargs) {
|
|
7602
7971
|
const keeps = keepsLiterals(f.varargs);
|
|
7603
7972
|
for (let i = f.params.length; i < argTypes.length; i++) {
|
|
7604
7973
|
const arg = argTypes[i];
|
|
@@ -7767,7 +8136,12 @@ var TypeAnalyzer = class {
|
|
|
7767
8136
|
const arg = argTypes[i];
|
|
7768
8137
|
const declared = declaredAt(i);
|
|
7769
8138
|
if (arg === void 0 || declared === void 0 || !containsTypeParam(declared)) continue;
|
|
7770
|
-
|
|
8139
|
+
let expected = this.reduceType(substitute(declared, subst));
|
|
8140
|
+
if (f.restIsWhole && i >= f.params.length) {
|
|
8141
|
+
const whole = this.expand(expected);
|
|
8142
|
+
expected = whole.kind === "tuple" ? whole.elements[i - f.params.length] ?? whole.rest : whole.kind === "array" ? whole.element : void 0;
|
|
8143
|
+
if (!expected) continue;
|
|
8144
|
+
}
|
|
7771
8145
|
if (containsTypeParam(expected) || expected.kind === "any" || expected.kind === "unknown") continue;
|
|
7772
8146
|
if (isAssignable(arg, expected) || isAssignable(widen(arg), expected)) continue;
|
|
7773
8147
|
this.diagnostics.push({
|
|
@@ -7895,13 +8269,13 @@ var TypeAnalyzer = class {
|
|
|
7895
8269
|
type: this.paramType(p, /* @__PURE__ */ new Map()),
|
|
7896
8270
|
optional: p.optional || p.default !== void 0
|
|
7897
8271
|
}));
|
|
7898
|
-
return fn(
|
|
8272
|
+
return this.withRest(fn(
|
|
7899
8273
|
params,
|
|
7900
8274
|
sig.returnType ? this.resolveType(sig.returnType) : sig.predicate ? booleanType : anyType,
|
|
7901
|
-
|
|
8275
|
+
void 0,
|
|
7902
8276
|
names,
|
|
7903
8277
|
this.resolvePredicate(sig.predicate, params)
|
|
7904
|
-
);
|
|
8278
|
+
), sig.params);
|
|
7905
8279
|
}));
|
|
7906
8280
|
}
|
|
7907
8281
|
/** `...rest: T[]` holds every argument from its position on, so its type
|
|
@@ -7909,7 +8283,7 @@ var TypeAnalyzer = class {
|
|
|
7909
8283
|
checkRestType(p, node) {
|
|
7910
8284
|
if (!this.emitDiagnostics || !p.typeAnnotation) return;
|
|
7911
8285
|
const declared = this.resolveType(p.typeAnnotation);
|
|
7912
|
-
if (declared.kind === "array" || declared.kind === "any" || declared.kind === "typeParam") return;
|
|
8286
|
+
if (declared.kind === "array" || declared.kind === "tuple" || declared.kind === "any" || declared.kind === "typeParam") return;
|
|
7913
8287
|
this.diagnostics.push({
|
|
7914
8288
|
node,
|
|
7915
8289
|
message: `A rest parameter holds every argument from its position on, so '${p.name ?? "..."}' is an array: '${formatType(declared)}[]', not '${formatType(declared)}'`
|
|
@@ -7965,13 +8339,13 @@ var TypeAnalyzer = class {
|
|
|
7965
8339
|
return collected.length ? union(collected) : this.inferReturnType(func.body, bodyEnv);
|
|
7966
8340
|
}));
|
|
7967
8341
|
}
|
|
7968
|
-
return fn(
|
|
8342
|
+
return this.withRest(fn(
|
|
7969
8343
|
params,
|
|
7970
8344
|
returns,
|
|
7971
|
-
|
|
8345
|
+
void 0,
|
|
7972
8346
|
names,
|
|
7973
8347
|
this.resolvePredicate(func.predicate, params)
|
|
7974
|
-
);
|
|
8348
|
+
), func.params);
|
|
7975
8349
|
});
|
|
7976
8350
|
}
|
|
7977
8351
|
/** Where the return types of the function being walked are collected, so
|
|
@@ -8013,17 +8387,16 @@ var TypeAnalyzer = class {
|
|
|
8013
8387
|
this.preVisitDepth--;
|
|
8014
8388
|
}
|
|
8015
8389
|
}
|
|
8016
|
-
/** The `[key, value]` pairs
|
|
8017
|
-
*
|
|
8018
|
-
*
|
|
8019
|
-
* iterator function), whose keys have no names to list. */
|
|
8390
|
+
/** The `[key, value]` items `pairs(record)` hands out, one per property.
|
|
8391
|
+
* `undefined` for anything else (an array, a dictionary, an iterator
|
|
8392
|
+
* function), whose keys have no names to list. */
|
|
8020
8393
|
iterationRows(iterNode, iterType) {
|
|
8021
8394
|
let source;
|
|
8022
8395
|
if (iterNode?.type === "CallExpression" && iterNode.callee.type === "Identifier" && iterNode.arguments[0]) {
|
|
8023
|
-
if (iterNode.callee.name !== "pairs"
|
|
8396
|
+
if (iterNode.callee.name !== "pairs") return void 0;
|
|
8024
8397
|
source = this.typeOf.get(iterNode.arguments[0]);
|
|
8025
8398
|
} else {
|
|
8026
|
-
|
|
8399
|
+
return void 0;
|
|
8027
8400
|
}
|
|
8028
8401
|
const t = source && this.expand(source);
|
|
8029
8402
|
if (!t || t.kind !== "object" || t.class || t.indexer || !t.properties.size) return void 0;
|
|
@@ -8164,54 +8537,69 @@ var TypeAnalyzer = class {
|
|
|
8164
8537
|
if (ids.length < 2) return;
|
|
8165
8538
|
this.correlateBindings(env, ids, objects.map((member) => names.map((name) => this.propertyType(member, name))));
|
|
8166
8539
|
}
|
|
8167
|
-
/** `
|
|
8168
|
-
*
|
|
8169
|
-
*
|
|
8170
|
-
|
|
8171
|
-
|
|
8172
|
-
|
|
8173
|
-
|
|
8174
|
-
|
|
8175
|
-
|
|
8176
|
-
|
|
8177
|
-
|
|
8178
|
-
|
|
8540
|
+
/** `[step, state, first]` — what a call such as `pairs(t)` answers — as
|
|
8541
|
+
* the `step` function, or `undefined` for an array that is just an
|
|
8542
|
+
* array. Three places exactly, the first a function. */
|
|
8543
|
+
iterationTriple(t) {
|
|
8544
|
+
if (t.kind !== "tuple" || t.rest || t.elements.length !== 3) return void 0;
|
|
8545
|
+
const step = this.expand(t.elements[0]);
|
|
8546
|
+
return step.kind === "function" ? step : void 0;
|
|
8547
|
+
}
|
|
8548
|
+
/** What one `for (const item in source)` hands over each time, and how it
|
|
8549
|
+
* walks the source. `pairs`/`ipairs` are read from the table they are
|
|
8550
|
+
* given, so a record's keys stay the literals they are. */
|
|
8551
|
+
iterationOf(source, sourceType) {
|
|
8552
|
+
if (source.type === "CallExpression" && source.callee.type === "Identifier" && source.arguments[0]) {
|
|
8553
|
+
const name = source.callee.name;
|
|
8554
|
+
const src = this.expand(this.typeOf.get(source.arguments[0]) ?? unknownType);
|
|
8555
|
+
const iteration = { walks: "iteration", viaIter: false };
|
|
8556
|
+
if (name === "ipairs") return [tuple([numberType, this.elementType(src, 0)]), iteration];
|
|
8557
|
+
if (name === "pairs") {
|
|
8179
8558
|
if (src.kind === "object") {
|
|
8180
|
-
return [
|
|
8559
|
+
return [tuple([
|
|
8181
8560
|
src.indexer?.key ?? stringType,
|
|
8182
|
-
src.indexer?.value ?? union([...src.properties.values()].map((p) => p.type))
|
|
8183
|
-
|
|
8184
|
-
];
|
|
8561
|
+
src.indexer?.value ?? union([...src.properties.values()].map((p) => p.type))
|
|
8562
|
+
]), iteration];
|
|
8185
8563
|
}
|
|
8186
|
-
if (src.kind === "array") return [numberType, src.element,
|
|
8564
|
+
if (src.kind === "array") return [tuple([numberType, src.element]), iteration];
|
|
8187
8565
|
}
|
|
8188
8566
|
}
|
|
8189
|
-
const
|
|
8190
|
-
|
|
8191
|
-
|
|
8192
|
-
const
|
|
8193
|
-
|
|
8194
|
-
return
|
|
8195
|
-
}
|
|
8196
|
-
const
|
|
8197
|
-
if (
|
|
8198
|
-
|
|
8199
|
-
|
|
8567
|
+
const handsOut = (t2, viaIter) => {
|
|
8568
|
+
const x = this.expand(t2);
|
|
8569
|
+
if (x.kind === "function") return [withoutNil(this.expand(x.returns)), { walks: "function", viaIter }];
|
|
8570
|
+
const step = this.iterationTriple(x);
|
|
8571
|
+
if (step) return [withoutNil(this.expand(step.returns)), { walks: "iteration", viaIter }];
|
|
8572
|
+
return void 0;
|
|
8573
|
+
};
|
|
8574
|
+
const direct = handsOut(sourceType, false);
|
|
8575
|
+
if (direct) return direct;
|
|
8576
|
+
const values = { walks: "values", viaIter: false };
|
|
8577
|
+
const t = this.expand(sourceType);
|
|
8578
|
+
if (t.kind === "any") return [anyType, values];
|
|
8579
|
+
const iter = t.kind === "object" ? t.properties.get("__iter") : void 0;
|
|
8580
|
+
if (iter) {
|
|
8581
|
+
const made = this.overloadsOf(iter.type)[0]?.returns ?? unknownType;
|
|
8582
|
+
return handsOut(made, true) ?? [unknownType, { walks: "values", viaIter: true }];
|
|
8583
|
+
}
|
|
8584
|
+
if (t.kind === "array") return [t.element, values];
|
|
8585
|
+
if (t.kind === "tuple") return [union([...t.elements, ...t.rest ? [t.rest] : []]), values];
|
|
8200
8586
|
if (t.kind === "object") {
|
|
8201
|
-
|
|
8202
|
-
const v = t.indexer?.value ?? union([...t.properties.values()].map((p) => p.type));
|
|
8203
|
-
return varCount >= 2 ? [k, v, false] : [v, unknownType, true];
|
|
8587
|
+
return [t.indexer?.value ?? union([...t.properties.values()].map((p) => p.type)), values];
|
|
8204
8588
|
}
|
|
8205
|
-
|
|
8589
|
+
if (t.kind !== "never" && this.emitDiagnostics) {
|
|
8590
|
+
this.diagnostics.push({
|
|
8591
|
+
node: source,
|
|
8592
|
+
message: `Cannot loop over '${formatType(sourceType)}': a loop walks an array, a table, an iterator function or an iteration such as 'pairs(t)'`
|
|
8593
|
+
});
|
|
8594
|
+
}
|
|
8595
|
+
return [unknownType, values];
|
|
8206
8596
|
}
|
|
8207
8597
|
inferReturnType(body, env) {
|
|
8208
8598
|
const returns = [];
|
|
8209
8599
|
const walk = (block) => {
|
|
8210
8600
|
for (const s of block.statements) {
|
|
8211
8601
|
if (s.type === "ReturnStatement") {
|
|
8212
|
-
|
|
8213
|
-
else if (s.arguments.length === 1) returns.push(this.infer(s.arguments[0], env));
|
|
8214
|
-
else returns.push(tuple(s.arguments.map((a) => this.infer(a, env)), true));
|
|
8602
|
+
returns.push(s.argument ? this.infer(s.argument, env) : nilType);
|
|
8215
8603
|
} else if (s.type === "IfStatement") {
|
|
8216
8604
|
for (const c of s.clauses) walk(c.body);
|
|
8217
8605
|
if (s.alternate) walk(s.alternate);
|
|
@@ -8233,8 +8621,9 @@ var TypeAnalyzer = class {
|
|
|
8233
8621
|
* `infer` would have widened it to an array type). */
|
|
8234
8622
|
fitsAnnotation(init, declared, inferred, env) {
|
|
8235
8623
|
if (declared.kind === "tuple" && init.type === "ArrayExpression" && !init.elements.some((e) => e.type === "SpreadElement")) {
|
|
8236
|
-
|
|
8237
|
-
|
|
8624
|
+
const fits = declared.rest ? init.elements.length >= declared.elements.length : init.elements.length === declared.elements.length;
|
|
8625
|
+
if (!fits) return false;
|
|
8626
|
+
return init.elements.every((el, i) => isAssignable(widen(this.infer(el, env)), declared.elements[i] ?? declared.rest));
|
|
8238
8627
|
}
|
|
8239
8628
|
if (isAssignable(inferred, declared) || isAssignable(widen(inferred), declared)) return true;
|
|
8240
8629
|
if (init.type === "TableExpression") return isAssignable(this.inferObject(init, env, true), declared);
|
|
@@ -8291,7 +8680,7 @@ var TypeAnalyzer = class {
|
|
|
8291
8680
|
target.elements.forEach((el, i) => {
|
|
8292
8681
|
if (el) this.reassignPattern(el.value, this.withDefault(this.elementType(valueType, i), el.default, env), env);
|
|
8293
8682
|
});
|
|
8294
|
-
if (target.rest) this.reassignPattern(target.rest,
|
|
8683
|
+
if (target.rest) this.reassignPattern(target.rest, this.restAfter(valueType, target.elements.length), env);
|
|
8295
8684
|
return;
|
|
8296
8685
|
}
|
|
8297
8686
|
}
|
|
@@ -8327,7 +8716,7 @@ var TypeAnalyzer = class {
|
|
|
8327
8716
|
if (!el) return;
|
|
8328
8717
|
this.bindPattern(el.value, this.withDefault(this.elementType(valueType, i), el.default, env), env, mode);
|
|
8329
8718
|
});
|
|
8330
|
-
if (target.rest) this.bindPattern(target.rest,
|
|
8719
|
+
if (target.rest) this.bindPattern(target.rest, this.restAfter(valueType, target.elements.length), env, mode);
|
|
8331
8720
|
return;
|
|
8332
8721
|
}
|
|
8333
8722
|
}
|
|
@@ -8484,7 +8873,7 @@ var TypeAnalyzer = class {
|
|
|
8484
8873
|
* not one of them is a mistake worth reporting, rather than the nil Lua
|
|
8485
8874
|
* would hand back. */
|
|
8486
8875
|
checkStringMember(node, object, key) {
|
|
8487
|
-
if (!this.emitDiagnostics) return;
|
|
8876
|
+
if (!this.emitDiagnostics || !this.aliasDefs.has("StringMethods")) return;
|
|
8488
8877
|
const parts = this.stringParts(object);
|
|
8489
8878
|
if (!parts) return;
|
|
8490
8879
|
const keys = (key.kind === "union" ? key.types : [key]).map((m) => this.expand(m));
|
|
@@ -8496,6 +8885,120 @@ var TypeAnalyzer = class {
|
|
|
8496
8885
|
message: names.length === 1 ? `'${names[0]}' does not exist on a string` : `'${briefType(key)}' does not name a member of a string`
|
|
8497
8886
|
});
|
|
8498
8887
|
}
|
|
8888
|
+
/** `game.Anything` on a `{ GetService: ... }`, or `a.a` on a number: a
|
|
8889
|
+
* value whose members are all known, and none of them is this one. Only
|
|
8890
|
+
* a type that is known through and through is reported — an indexer, an
|
|
8891
|
+
* `any`, or a part still waiting on a type parameter could each supply
|
|
8892
|
+
* the name. An empty `{}` is a table still being filled in, so it is left
|
|
8893
|
+
* alone; an array's and a string's members come from a library, so
|
|
8894
|
+
* without one they say nothing. A string on its own is `checkStringMember`'s. */
|
|
8895
|
+
checkMissingMember(node, object, name) {
|
|
8896
|
+
if (!this.emitDiagnostics || this.missingMemberReported.has(node)) return;
|
|
8897
|
+
if (!this.memberMissing(object, name)) return;
|
|
8898
|
+
this.missingMemberReported.add(node);
|
|
8899
|
+
this.diagnostics.push({
|
|
8900
|
+
node,
|
|
8901
|
+
message: `Property '${name}' does not exist on type '${briefType(withoutNil(this.expand(object)))}'`
|
|
8902
|
+
});
|
|
8903
|
+
}
|
|
8904
|
+
/** Is `name` certainly not a member of `object`? See `checkMissingMember`. */
|
|
8905
|
+
memberMissing(object, name) {
|
|
8906
|
+
if (this.stringParts(object)) return false;
|
|
8907
|
+
const raw = this.expand(object);
|
|
8908
|
+
const parts = (raw.kind === "union" ? raw.types : [raw]).map((m) => this.deferredAccess(this.expand(m)));
|
|
8909
|
+
let missing = false;
|
|
8910
|
+
for (const part of parts) {
|
|
8911
|
+
switch (part.kind) {
|
|
8912
|
+
case "primitive":
|
|
8913
|
+
if (part.name === "nil") continue;
|
|
8914
|
+
if (part.name === "string" && !this.aliasDefs.has("StringMethods")) return false;
|
|
8915
|
+
if (part.name === "string" && this.builtInMethod(part, name)) continue;
|
|
8916
|
+
break;
|
|
8917
|
+
case "literal":
|
|
8918
|
+
case "templateLiteral":
|
|
8919
|
+
if (part.kind === "templateLiteral" || part.base === "string") {
|
|
8920
|
+
if (!this.aliasDefs.has("StringMethods")) return false;
|
|
8921
|
+
}
|
|
8922
|
+
if (this.builtInMethod(part, name)) continue;
|
|
8923
|
+
break;
|
|
8924
|
+
case "array":
|
|
8925
|
+
case "tuple":
|
|
8926
|
+
if (!this.aliasDefs.has("ArrayMethods")) return false;
|
|
8927
|
+
if (this.builtInMethod(part, name)) continue;
|
|
8928
|
+
break;
|
|
8929
|
+
case "object":
|
|
8930
|
+
if (part.properties.has(name)) continue;
|
|
8931
|
+
if (part.indexer || part.properties.size === 0 && !part.class || this.builtInMethod(part, name)) return false;
|
|
8932
|
+
break;
|
|
8933
|
+
default:
|
|
8934
|
+
return false;
|
|
8935
|
+
}
|
|
8936
|
+
missing = true;
|
|
8937
|
+
}
|
|
8938
|
+
return missing;
|
|
8939
|
+
}
|
|
8940
|
+
/** `t[k]` read with a `k` that cannot be a key of `t`: an `unknown`, a
|
|
8941
|
+
* type none of `t`'s keys has (a number into a record, a string into an
|
|
8942
|
+
* array or a class), or a name `t` does not have — which is `t.name`'s
|
|
8943
|
+
* error. An `any` on either side says nothing, and neither does a type
|
|
8944
|
+
* parameter still to be decided. A plain `string` into a record is
|
|
8945
|
+
* allowed: it reads as one of the values or nil, and the nil has to be
|
|
8946
|
+
* dealt with. An empty `{}` is a table still being filled in, and is
|
|
8947
|
+
* left alone. Reports, and answers whether it did. */
|
|
8948
|
+
checkIndexKey(node, object, key) {
|
|
8949
|
+
if (!this.emitDiagnostics || this.badIndexReported.has(node)) return false;
|
|
8950
|
+
const index = this.expand(key);
|
|
8951
|
+
if (index.kind === "any" || index.kind === "never" || containsTypeParam(index)) return false;
|
|
8952
|
+
const keys = index.kind === "union" ? index.types.map((k) => this.expand(k)) : [index];
|
|
8953
|
+
const nameOf = (k) => k.kind === "literal" && typeof k.value === "string" ? k.value : void 0;
|
|
8954
|
+
const shown = briefType(withoutNil(this.expand(object)));
|
|
8955
|
+
const names = keys.map(nameOf);
|
|
8956
|
+
const others = keys.filter((_, i) => names[i] === void 0);
|
|
8957
|
+
let message;
|
|
8958
|
+
if (others.length && this.cannotIndex(object, others)) {
|
|
8959
|
+
message = `Type '${briefType(index)}' cannot be used to index type '${shown}'`;
|
|
8960
|
+
} else if (!others.length && names.every((name) => this.memberMissing(object, name))) {
|
|
8961
|
+
message = keys.length === 1 ? `Property '${names[0]}' does not exist on type '${shown}'` : `None of ${briefType(index)} is a property of type '${shown}'`;
|
|
8962
|
+
}
|
|
8963
|
+
if (!message) return false;
|
|
8964
|
+
this.badIndexReported.add(node);
|
|
8965
|
+
this.diagnostics.push({ node, message });
|
|
8966
|
+
return true;
|
|
8967
|
+
}
|
|
8968
|
+
/** Can one of `keys` (string literals aside) not index some part of
|
|
8969
|
+
* `object`? `false` as soon as a part cannot be judged. */
|
|
8970
|
+
cannotIndex(object, keys) {
|
|
8971
|
+
const raw = this.expand(object);
|
|
8972
|
+
const parts = (raw.kind === "union" ? raw.types : [raw]).map((m) => this.deferredAccess(this.expand(m)));
|
|
8973
|
+
const isNumber = (k) => k.kind === "primitive" && k.name === "number" || k.kind === "literal" && typeof k.value === "number";
|
|
8974
|
+
const isString = (k) => k.kind === "primitive" && k.name === "string" || k.kind === "templateLiteral";
|
|
8975
|
+
let bad = false;
|
|
8976
|
+
for (const part of parts) {
|
|
8977
|
+
switch (part.kind) {
|
|
8978
|
+
case "primitive":
|
|
8979
|
+
if (part.name === "nil") continue;
|
|
8980
|
+
return false;
|
|
8981
|
+
case "array":
|
|
8982
|
+
case "tuple":
|
|
8983
|
+
if (!keys.every(isNumber)) bad = true;
|
|
8984
|
+
continue;
|
|
8985
|
+
case "object": {
|
|
8986
|
+
if (!part.indexer && part.properties.size === 0 && !part.class) return false;
|
|
8987
|
+
const fits = (k) => part.indexer !== void 0 && isAssignable(k, part.indexer.key) || !part.indexer && !part.class && isString(k) || k.kind === "literal" && part.properties.has(String(k.value));
|
|
8988
|
+
if (!keys.every(fits)) bad = true;
|
|
8989
|
+
continue;
|
|
8990
|
+
}
|
|
8991
|
+
default:
|
|
8992
|
+
return false;
|
|
8993
|
+
}
|
|
8994
|
+
}
|
|
8995
|
+
return bad;
|
|
8996
|
+
}
|
|
8997
|
+
/** Indexes already reported: a loop body is visited more than once. */
|
|
8998
|
+
badIndexReported = /* @__PURE__ */ new WeakSet();
|
|
8999
|
+
/** Members already reported as missing: a loop body, or an assignment
|
|
9000
|
+
* target, is visited more than once. */
|
|
9001
|
+
missingMemberReported = /* @__PURE__ */ new WeakSet();
|
|
8499
9002
|
/** A `private` member read from outside the class that declared it —
|
|
8500
9003
|
* a subclass counts as outside, as in TypeScript. */
|
|
8501
9004
|
checkPrivateMember(node, object, name) {
|
|
@@ -8506,18 +9009,46 @@ var TypeAnalyzer = class {
|
|
|
8506
9009
|
if (t.kind !== "object") continue;
|
|
8507
9010
|
const access = t.properties.get(name)?.private;
|
|
8508
9011
|
if (!access || this.insideClass(access.owner)) continue;
|
|
9012
|
+
if (access.protected && this.insideSubclassOf(access.owner)) continue;
|
|
8509
9013
|
this.diagnostics.push({
|
|
8510
9014
|
node,
|
|
8511
|
-
message: `Property '${name}' is private and only accessible within class '${access.className}'`
|
|
9015
|
+
message: access.protected ? `Property '${name}' is protected and only accessible within class '${access.className}' and the classes extending it` : `Property '${name}' is private and only accessible within class '${access.className}'`
|
|
8512
9016
|
});
|
|
8513
9017
|
return;
|
|
8514
9018
|
}
|
|
8515
9019
|
}
|
|
9020
|
+
/** `Shape.new` on an abstract class, and `super.area` on an abstract
|
|
9021
|
+
* method: each reaches something with nothing behind it. */
|
|
9022
|
+
checkAbstractAccess(expr, object) {
|
|
9023
|
+
if (!this.emitDiagnostics) return;
|
|
9024
|
+
const t = this.expand(object);
|
|
9025
|
+
if (t.kind !== "object") return;
|
|
9026
|
+
const name = expr.property.name;
|
|
9027
|
+
const property = t.properties.get(name);
|
|
9028
|
+
if (!property?.abstract) return;
|
|
9029
|
+
if (name === "new" && expr.object.type !== "SuperExpression") {
|
|
9030
|
+
const label = expressionLabel(expr.object) ?? formatType(t);
|
|
9031
|
+
this.diagnostics.push({
|
|
9032
|
+
node: expr,
|
|
9033
|
+
message: `'${label}' is an abstract class: build one of the classes extending it instead`
|
|
9034
|
+
});
|
|
9035
|
+
} else if (expr.object.type === "SuperExpression") {
|
|
9036
|
+
this.diagnostics.push({
|
|
9037
|
+
node: expr.property,
|
|
9038
|
+
message: `'${name}' is abstract in the class this one extends: there is nothing to call`
|
|
9039
|
+
});
|
|
9040
|
+
}
|
|
9041
|
+
}
|
|
8516
9042
|
/** Is the code being checked written inside `owner`'s body — its own
|
|
8517
9043
|
* methods, or a class or function nested in them? */
|
|
8518
9044
|
insideClass(owner) {
|
|
8519
9045
|
return this.enclosingClasses.includes(owner);
|
|
8520
9046
|
}
|
|
9047
|
+
/** Is the code being checked inside a class that extends `owner`? */
|
|
9048
|
+
insideSubclassOf(owner) {
|
|
9049
|
+
const identity = this.classIdentity(owner);
|
|
9050
|
+
return this.enclosingClasses.some((cls) => this.instanceType(cls).class?.ancestors.includes(identity));
|
|
9051
|
+
}
|
|
8521
9052
|
propertyType(raw, name) {
|
|
8522
9053
|
const t = this.deferredAccess(this.expand(raw));
|
|
8523
9054
|
if (t.kind === "object") {
|
|
@@ -8572,8 +9103,12 @@ var TypeAnalyzer = class {
|
|
|
8572
9103
|
}
|
|
8573
9104
|
if (containsTypeParam(index)) return this.reduceType({ kind: "indexedAccess", objectType: t, indexType: index });
|
|
8574
9105
|
if (t.indexer) return t.indexer.value;
|
|
9106
|
+
const anyString = index.kind === "primitive" && index.name === "string" || index.kind === "templateLiteral";
|
|
9107
|
+
if (anyString && !t.class && t.properties.size > 0) {
|
|
9108
|
+
return union([...[...t.properties.values()].map((p) => p.type), nilType]);
|
|
9109
|
+
}
|
|
8575
9110
|
}
|
|
8576
|
-
return unknownType;
|
|
9111
|
+
return index.kind === "any" ? anyType : unknownType;
|
|
8577
9112
|
}
|
|
8578
9113
|
/** What a deferred `T[K]` can be: every property its index could name.
|
|
8579
9114
|
* Reading a member of one, or calling it, sees that. */
|
|
@@ -8583,10 +9118,21 @@ var TypeAnalyzer = class {
|
|
|
8583
9118
|
if (containsTypeParam(index)) return unknownType;
|
|
8584
9119
|
return this.accessType(t.objectType, index);
|
|
8585
9120
|
}
|
|
9121
|
+
/** `[a, ...rest]`: what `rest` holds — an array of whatever comes after
|
|
9122
|
+
* the first `from` places. */
|
|
9123
|
+
restAfter(raw, from) {
|
|
9124
|
+
const t = this.expand(raw);
|
|
9125
|
+
if (t.kind === "tuple") {
|
|
9126
|
+
const left = [...t.elements.slice(from), ...t.rest ? [t.rest] : []];
|
|
9127
|
+
return arrayOf(left.length ? union(left) : neverType);
|
|
9128
|
+
}
|
|
9129
|
+
if (t.kind === "union") return union(t.types.map((m) => this.restAfter(m, from)));
|
|
9130
|
+
return arrayOf(this.elementType(t, from));
|
|
9131
|
+
}
|
|
8586
9132
|
elementType(raw, index) {
|
|
8587
9133
|
const t = this.expand(raw);
|
|
8588
9134
|
if (t.kind === "array") return t.element;
|
|
8589
|
-
if (t.kind === "tuple") return t.elements[index] ?? unknownType;
|
|
9135
|
+
if (t.kind === "tuple") return t.elements[index] ?? t.rest ?? unknownType;
|
|
8590
9136
|
if (t.kind === "union") return union(t.types.map((m) => this.elementType(m, index)));
|
|
8591
9137
|
if (t.kind === "difference") return this.elementType(t.base, index);
|
|
8592
9138
|
if (t.kind === "typeParam" && t.constraint) return this.elementType(t.constraint, index);
|
|
@@ -8616,8 +9162,6 @@ var TypeAnalyzer = class {
|
|
|
8616
9162
|
return stringType;
|
|
8617
9163
|
}
|
|
8618
9164
|
// `...` holds what the function declared it takes.
|
|
8619
|
-
case "VarargExpression":
|
|
8620
|
-
return this.varargs[this.varargs.length - 1] ?? anyType;
|
|
8621
9165
|
// `f(a, ...rest)` — every value the array holds, one after
|
|
8622
9166
|
// another. Each of them is an element, so that is what the
|
|
8623
9167
|
// parameters it fills are checked against.
|
|
@@ -8650,10 +9194,8 @@ var TypeAnalyzer = class {
|
|
|
8650
9194
|
this.checkParamOrder(expr.func.params, expr);
|
|
8651
9195
|
this.visitFunctionBody(expr.func, env);
|
|
8652
9196
|
return this.inferFunctionBody(expr.func, env);
|
|
8653
|
-
case "ParenthesizedExpression":
|
|
8654
|
-
|
|
8655
|
-
return inner.kind === "tuple" && inner.isPack && producesMultipleValues(expr.expression) ? inner.elements[0] ?? nilType : inner;
|
|
8656
|
-
}
|
|
9197
|
+
case "ParenthesizedExpression":
|
|
9198
|
+
return this.infer(expr.expression, env);
|
|
8657
9199
|
case "TypeAssertionExpression": {
|
|
8658
9200
|
this.infer(expr.expression, env);
|
|
8659
9201
|
return this.resolveType(expr.typeAnnotation);
|
|
@@ -8686,7 +9228,7 @@ var TypeAnalyzer = class {
|
|
|
8686
9228
|
case "not":
|
|
8687
9229
|
return booleanType;
|
|
8688
9230
|
case "-":
|
|
8689
|
-
return this.operatorResult(expr, "-", arg, void 0) ??
|
|
9231
|
+
return this.operatorResult(expr, "-", arg, void 0) ?? this.arithmeticOn([arg]);
|
|
8690
9232
|
case "#":
|
|
8691
9233
|
return this.operatorResult(expr, "#", arg, void 0) ?? numberType;
|
|
8692
9234
|
}
|
|
@@ -8704,7 +9246,11 @@ var TypeAnalyzer = class {
|
|
|
8704
9246
|
const left = this.infer(expr.left, env);
|
|
8705
9247
|
const { whenFalse } = this.narrowFromCondition(expr.left, env);
|
|
8706
9248
|
const right = this.infer(expr.right, whenFalse);
|
|
8707
|
-
|
|
9249
|
+
const fallback = unwrapParens(expr.right);
|
|
9250
|
+
const empty = fallback.type === "TableExpression" && fallback.fields.length === 0 || fallback.type === "ArrayExpression" && fallback.elements.length === 0;
|
|
9251
|
+
const truthy = narrowTruthy(left);
|
|
9252
|
+
if (empty && truthy.kind !== "never" && truthy.kind !== "any" && isAssignable(right, truthy)) return truthy;
|
|
9253
|
+
return union([truthy, right]);
|
|
8708
9254
|
}
|
|
8709
9255
|
const l = this.infer(expr.left, env);
|
|
8710
9256
|
const r = this.infer(expr.right, env);
|
|
@@ -8715,12 +9261,14 @@ var TypeAnalyzer = class {
|
|
|
8715
9261
|
switch (op) {
|
|
8716
9262
|
case "..":
|
|
8717
9263
|
return this.operatorResult(expr, op, l, r) ?? stringType;
|
|
8718
|
-
case "==":
|
|
8719
|
-
case "~=":
|
|
8720
9264
|
case "<":
|
|
8721
9265
|
case ">":
|
|
8722
9266
|
case "<=":
|
|
8723
9267
|
case ">=":
|
|
9268
|
+
this.checkComparison(expr, op, l, r);
|
|
9269
|
+
return booleanType;
|
|
9270
|
+
case "==":
|
|
9271
|
+
case "~=":
|
|
8724
9272
|
return booleanType;
|
|
8725
9273
|
case "+":
|
|
8726
9274
|
case "-":
|
|
@@ -8729,16 +9277,18 @@ var TypeAnalyzer = class {
|
|
|
8729
9277
|
case "//":
|
|
8730
9278
|
case "%":
|
|
8731
9279
|
case "^":
|
|
8732
|
-
return this.operatorResult(expr, op, l, r) ??
|
|
9280
|
+
return this.operatorResult(expr, op, l, r) ?? this.arithmeticOn([l, r]);
|
|
8733
9281
|
}
|
|
8734
9282
|
return union([l, r]);
|
|
8735
9283
|
}
|
|
8736
9284
|
case "MemberExpression": {
|
|
8737
9285
|
const { type: obj, shortCircuits } = this.chainObject(expr, expr.object, env);
|
|
8738
9286
|
this.checkPrivateMember(expr.property, obj, expr.property.name);
|
|
9287
|
+
this.checkAbstractAccess(expr, obj);
|
|
8739
9288
|
const key = this.refKeyOf(expr);
|
|
8740
9289
|
const narrowed = key === void 0 ? void 0 : env.get(key);
|
|
8741
9290
|
this.checkStringMember(expr, obj, literal(expr.property.name));
|
|
9291
|
+
if (narrowed === void 0) this.checkMissingMember(expr.property, obj, expr.property.name);
|
|
8742
9292
|
return this.chainResult(expr, narrowed ?? this.propertyType(obj, expr.property.name), shortCircuits);
|
|
8743
9293
|
}
|
|
8744
9294
|
case "IndexExpression": {
|
|
@@ -8747,6 +9297,7 @@ var TypeAnalyzer = class {
|
|
|
8747
9297
|
const key = this.refKeyOf(expr);
|
|
8748
9298
|
const narrowed = key === void 0 ? void 0 : env.get(key);
|
|
8749
9299
|
this.checkStringMember(expr, obj, this.expand(idx));
|
|
9300
|
+
if (this.checkIndexKey(expr, obj, idx)) return this.chainResult(expr, anyType, shortCircuits);
|
|
8750
9301
|
const member = this.stringMember(obj, this.expand(idx));
|
|
8751
9302
|
if (member) return this.chainResult(expr, member, shortCircuits);
|
|
8752
9303
|
return this.chainResult(expr, narrowed ?? this.indexedType(obj, idx), shortCircuits);
|
|
@@ -8756,8 +9307,6 @@ var TypeAnalyzer = class {
|
|
|
8756
9307
|
const { type: callee, shortCircuits } = this.chainObject(expr, expr.callee, env);
|
|
8757
9308
|
return this.chainResult(expr, this.inferCall(expr, callee, env), shortCircuits);
|
|
8758
9309
|
}
|
|
8759
|
-
case "NewExpression":
|
|
8760
|
-
return this.inferNew(expr, env);
|
|
8761
9310
|
case "ClassExpression":
|
|
8762
9311
|
return this.visitClass(expr, env);
|
|
8763
9312
|
case "SuperExpression": {
|
|
@@ -8794,18 +9343,14 @@ var TypeAnalyzer = class {
|
|
|
8794
9343
|
/** `new Name(args)` is `Name.new(args)` — the same function, and the
|
|
8795
9344
|
* same check. Saying so here rather than rewriting the tree keeps the
|
|
8796
9345
|
* error messages pointing at what was written. */
|
|
8797
|
-
|
|
8798
|
-
|
|
8799
|
-
const
|
|
8800
|
-
if (
|
|
8801
|
-
|
|
8802
|
-
|
|
8803
|
-
|
|
8804
|
-
|
|
8805
|
-
for (const argument of expr.arguments) this.infer(argument, env);
|
|
8806
|
-
return anyType;
|
|
8807
|
-
}
|
|
8808
|
-
return this.inferCall(expr, constructor, env);
|
|
9346
|
+
/** `__call` of a value with one, as what calling the value calls. */
|
|
9347
|
+
callMetamethod(raw) {
|
|
9348
|
+
const t = this.expand(raw);
|
|
9349
|
+
if (t.kind !== "object" || this.overloadsOf(t).length) return void 0;
|
|
9350
|
+
const method = t.properties.get("__call");
|
|
9351
|
+
if (!method) return void 0;
|
|
9352
|
+
const bound = this.overloadsOf(method.type).map((f) => this.takesSelf(f) ? fn(f.params.slice(1), f.returns, f.varargs, f.typeParams) : f);
|
|
9353
|
+
return bound.length ? intersection(bound) : void 0;
|
|
8809
9354
|
}
|
|
8810
9355
|
/** `super(...)` — the base constructor, run on the instance being built. */
|
|
8811
9356
|
inferSuperCall(expr, env) {
|
|
@@ -8829,8 +9374,9 @@ var TypeAnalyzer = class {
|
|
|
8829
9374
|
this.inferCall(expr, callable, env);
|
|
8830
9375
|
return nilType;
|
|
8831
9376
|
}
|
|
8832
|
-
inferCall(expr,
|
|
9377
|
+
inferCall(expr, called, env) {
|
|
8833
9378
|
this.checkAmbiguousCall(expr);
|
|
9379
|
+
const callee = this.callMetamethod(called) ?? called;
|
|
8834
9380
|
const united = this.unionSignatures(callee);
|
|
8835
9381
|
const fns = united ?? this.overloadsOf(callee);
|
|
8836
9382
|
const explicit = this.explicitTypeArguments(expr, fns);
|
|
@@ -8853,8 +9399,31 @@ var TypeAnalyzer = class {
|
|
|
8853
9399
|
if (arityFits && !picked) this.reportArguments(expr, expr.arguments, fns, () => argTypes, () => 0);
|
|
8854
9400
|
return union(fns.map((f) => this.callReturn(f, argTypes, explicit)));
|
|
8855
9401
|
}
|
|
9402
|
+
this.checkCallable(expr.callee, callee);
|
|
8856
9403
|
return callee.kind === "any" ? anyType : unknownType;
|
|
8857
9404
|
}
|
|
9405
|
+
/** `a()` where `a` is `1`: a value whose type is known and has no call
|
|
9406
|
+
* signature. A union is reported when any member cannot be called, but
|
|
9407
|
+
* only when every member is decided — `unknown`, `any` and anything still
|
|
9408
|
+
* generic say too little. */
|
|
9409
|
+
checkCallable(node, callee) {
|
|
9410
|
+
if (!this.emitDiagnostics || this.notCallableReported.has(node)) return;
|
|
9411
|
+
const raw = this.expand(callee);
|
|
9412
|
+
const parts = (raw.kind === "union" ? raw.types : [raw]).map((m) => this.expand(m));
|
|
9413
|
+
const notCallable = (m) => m.kind === "primitive" && m.name !== "nil" || m.kind === "literal" || m.kind === "templateLiteral" || m.kind === "array" || m.kind === "tuple" || m.kind === "object";
|
|
9414
|
+
const callable = (m) => this.overloadsOf(m).length > 0;
|
|
9415
|
+
const concrete = parts.filter((m) => !(m.kind === "primitive" && m.name === "nil"));
|
|
9416
|
+
if (!concrete.some(notCallable) || !concrete.every((m) => notCallable(m) || callable(m))) return;
|
|
9417
|
+
this.notCallableReported.add(node);
|
|
9418
|
+
const label = node.type === "Identifier" ? node.name : expressionLabel(node);
|
|
9419
|
+
this.diagnostics.push({
|
|
9420
|
+
node,
|
|
9421
|
+
message: `This expression is not callable: ${label === void 0 ? "" : `'${label}' `}is of type '${briefType(withoutNil(raw))}'`
|
|
9422
|
+
});
|
|
9423
|
+
}
|
|
9424
|
+
/** Callees already reported as not callable: a loop body is visited more
|
|
9425
|
+
* than once. */
|
|
9426
|
+
notCallableReported = /* @__PURE__ */ new WeakSet();
|
|
8858
9427
|
/** A `(` on a line of its own continues the statement above it:
|
|
8859
9428
|
*
|
|
8860
9429
|
* const value = map[key]
|
|
@@ -8872,6 +9441,8 @@ var TypeAnalyzer = class {
|
|
|
8872
9441
|
}
|
|
8873
9442
|
inferMethodCall(expr, objType, env) {
|
|
8874
9443
|
this.checkPrivateMember(expr.method, objType, expr.method.name);
|
|
9444
|
+
this.checkStringMember(expr.method, objType, literal(expr.method.name));
|
|
9445
|
+
this.checkMissingMember(expr.method, objType, expr.method.name);
|
|
8875
9446
|
const method = this.propertyType(objType, expr.method.name);
|
|
8876
9447
|
const united = this.unionSignatures(method);
|
|
8877
9448
|
const fns = united ?? this.overloadsOf(method);
|
|
@@ -8905,6 +9476,7 @@ var TypeAnalyzer = class {
|
|
|
8905
9476
|
if (arityFits && !picked) this.reportArguments(expr, expr.arguments, fns, withSelf, selfOf);
|
|
8906
9477
|
return union(fns.map((f) => this.callReturn(f, withSelf(f), explicit)));
|
|
8907
9478
|
}
|
|
9479
|
+
this.checkCallable(expr.method, method);
|
|
8908
9480
|
return objType.kind === "any" ? anyType : unknownType;
|
|
8909
9481
|
}
|
|
8910
9482
|
// --------------------------------------------------------
|
|
@@ -8965,6 +9537,8 @@ var TypeAnalyzer = class {
|
|
|
8965
9537
|
if (id !== void 0 && this.scopes.bindings.get(id)?.declaredBy === "namespace") return;
|
|
8966
9538
|
}
|
|
8967
9539
|
if (!this.isReadonlyProperty(this.expand(this.infer(object, env)), name)) return;
|
|
9540
|
+
const constructing = this.constructing;
|
|
9541
|
+
if (constructing && object.type === "Identifier" && object.name === "this" && constructing.members.some((m) => m.type === "ClassField" && !m.isStatic && m.isReadonly && m.name.name === name)) return;
|
|
8968
9542
|
this.diagnostics.push({
|
|
8969
9543
|
node: target,
|
|
8970
9544
|
message: `Cannot assign to '${name}' because it is a read-only property`
|
|
@@ -9024,6 +9598,8 @@ var TypeAnalyzer = class {
|
|
|
9024
9598
|
inferArray(expr, env, asConst) {
|
|
9025
9599
|
const contextual = this.contextualArrays.get(expr);
|
|
9026
9600
|
if (contextual && !asConst) return contextual;
|
|
9601
|
+
const wantedTuple = this.tupleArrays.get(expr);
|
|
9602
|
+
if (wantedTuple && !asConst) return this.inferTupleLiteral(expr, env, wantedTuple);
|
|
9027
9603
|
const elems = [];
|
|
9028
9604
|
let hadSpread = false;
|
|
9029
9605
|
for (const el of expr.elements) {
|
|
@@ -9043,6 +9619,28 @@ var TypeAnalyzer = class {
|
|
|
9043
9619
|
return asConst || !element || element.type === "SpreadElement" ? t : this.widenUnlessAsked(t, element);
|
|
9044
9620
|
})) : unknownType);
|
|
9045
9621
|
}
|
|
9622
|
+
/** `[a, b, ...more]` where a tuple is wanted: the places it names are the
|
|
9623
|
+
* tuple's, and what comes after them — more elements, or a spread — is
|
|
9624
|
+
* its rest. */
|
|
9625
|
+
inferTupleLiteral(expr, env, wanted) {
|
|
9626
|
+
const fixed = [];
|
|
9627
|
+
const tail = [];
|
|
9628
|
+
let spread = false;
|
|
9629
|
+
expr.elements.forEach((element, i) => {
|
|
9630
|
+
if (element.type === "SpreadElement") {
|
|
9631
|
+
spread = true;
|
|
9632
|
+
const held = this.expand(this.infer(element.argument, env));
|
|
9633
|
+
if (held.kind === "array") tail.push(held.element);
|
|
9634
|
+
else if (held.kind === "tuple") tail.push(...tupleMembers(held));
|
|
9635
|
+
else tail.push(unknownType);
|
|
9636
|
+
return;
|
|
9637
|
+
}
|
|
9638
|
+
const t = this.widenUnlessAsked(this.infer(element, env), element);
|
|
9639
|
+
if (!spread && i < wanted.elements.length) fixed.push(t);
|
|
9640
|
+
else tail.push(t);
|
|
9641
|
+
});
|
|
9642
|
+
return tuple(fixed, tail.length ? union(tail) : void 0);
|
|
9643
|
+
}
|
|
9046
9644
|
/** A literal written inside a fresh table or array widens — `{ n = 1 }` is
|
|
9047
9645
|
* `{ n: number }` — unless the surroundings said a literal belongs there.
|
|
9048
9646
|
* `request({ Method: "GET" })` keeps `"GET"` when `Method` is a union of
|
|
@@ -9128,7 +9726,7 @@ var TypeAnalyzer = class {
|
|
|
9128
9726
|
case "tuple": {
|
|
9129
9727
|
const tupleContext = ctx && this.membersOf(ctx).find((m) => m.kind === "tuple");
|
|
9130
9728
|
if (tupleContext?.kind === "tuple") {
|
|
9131
|
-
return tuple(value.elements.map((e, i) => this.keepContextualLiterals(e, tupleContext.elements[i])), value.
|
|
9729
|
+
return tuple(value.elements.map((e, i) => this.keepContextualLiterals(e, tupleContext.elements[i])), value.rest);
|
|
9132
9730
|
}
|
|
9133
9731
|
const arrayContext = ctx && this.membersOf(ctx).find((m) => m.kind === "array");
|
|
9134
9732
|
const element = arrayContext?.kind === "array" ? arrayContext.element : void 0;
|
|
@@ -9385,7 +9983,8 @@ var TypeAnalyzer = class {
|
|
|
9385
9983
|
for (const f of candidates) {
|
|
9386
9984
|
if (!f.predicate) continue;
|
|
9387
9985
|
const arg = args[f.predicate.param];
|
|
9388
|
-
|
|
9986
|
+
const onCondition = f.predicate.asserts && !f.predicate.type;
|
|
9987
|
+
if (!arg || this.refKeyOf(arg) === void 0 && !onCondition) continue;
|
|
9389
9988
|
if (f.typeParams?.length && f.predicate.type) {
|
|
9390
9989
|
const subst = this.inferTypeArgs(f, argTypes);
|
|
9391
9990
|
return {
|
|
@@ -9406,6 +10005,10 @@ var TypeAnalyzer = class {
|
|
|
9406
10005
|
const found = this.predicateCallTarget(call, env);
|
|
9407
10006
|
if (!found || !found.predicate.asserts) return;
|
|
9408
10007
|
const filter = found.predicate.type;
|
|
10008
|
+
if (this.refKeyOf(found.arg) === void 0) {
|
|
10009
|
+
if (!filter) this.applyNarrowing(found.arg, env, env, forkEnv(env));
|
|
10010
|
+
return;
|
|
10011
|
+
}
|
|
9409
10012
|
this.narrowRef(found.arg, env, env, forkEnv(env), (cur) => filter ? { yes: narrowTo(cur, filter), no: narrowExclude(cur, filter) } : { yes: narrowTruthy(cur), no: narrowFalsy(cur) });
|
|
9410
10013
|
}
|
|
9411
10014
|
// --------------------------------------------------------
|
|
@@ -9430,6 +10033,10 @@ var TypeAnalyzer = class {
|
|
|
9430
10033
|
if (base === void 0) return void 0;
|
|
9431
10034
|
if (expr.index.type === "StringLiteral") return `${base}.${expr.index.value}`;
|
|
9432
10035
|
if (expr.index.type === "NumberLiteral") return `${base}#${expr.index.value}`;
|
|
10036
|
+
if (expr.index.type === "Identifier") {
|
|
10037
|
+
const id = this.bindingIdOf(expr.index);
|
|
10038
|
+
return id === void 0 ? void 0 : `${base}[${bindKey(id)}]`;
|
|
10039
|
+
}
|
|
9433
10040
|
return void 0;
|
|
9434
10041
|
}
|
|
9435
10042
|
default:
|
|
@@ -9525,26 +10132,45 @@ var TypeAnalyzer = class {
|
|
|
9525
10132
|
* that copied it no longer holds that value: forget the alias. */
|
|
9526
10133
|
unalias(key) {
|
|
9527
10134
|
for (const k of [...this.refAliases.keys()]) {
|
|
9528
|
-
if (k !== key && !k
|
|
10135
|
+
if (k !== key && !isBelow(k, key) && !k.includes(`[${key}]`)) continue;
|
|
9529
10136
|
for (const other of this.refAliases.get(k) ?? []) this.refAliases.get(other)?.delete(k);
|
|
9530
10137
|
this.refAliases.delete(k);
|
|
9531
10138
|
}
|
|
9532
10139
|
}
|
|
9533
|
-
/** Drop every narrowing recorded for a path strictly under `key
|
|
10140
|
+
/** Drop every narrowing recorded for a path strictly under `key` — and,
|
|
10141
|
+
* when `key` is a whole variable, for every `t[key]` indexed by it: that
|
|
10142
|
+
* now names a different slot. */
|
|
9534
10143
|
invalidateBelow(env, key) {
|
|
10144
|
+
const asIndex = `[${key}]`;
|
|
9535
10145
|
for (const k of [...env.keys()]) {
|
|
9536
|
-
if (k
|
|
10146
|
+
if (isBelow(k, key) || k.includes(asIndex)) env.delete(k);
|
|
9537
10147
|
}
|
|
9538
10148
|
}
|
|
9539
10149
|
/** An assignment through a reference invalidates it and everything under
|
|
9540
10150
|
* it, then records the assigned type. */
|
|
9541
10151
|
assignToRef(expr, value, env) {
|
|
10152
|
+
this.invalidateSiblings(expr, env);
|
|
9542
10153
|
const key = this.refKeyOf(expr);
|
|
9543
10154
|
if (key === void 0) return;
|
|
9544
10155
|
this.invalidateBelow(env, key);
|
|
9545
10156
|
this.unalias(key);
|
|
9546
10157
|
env.set(key, value);
|
|
9547
10158
|
}
|
|
10159
|
+
/** A write to one slot of a table can be a write to a slot narrowed under
|
|
10160
|
+
* another name: `t.x = nil` is `t[k] = nil` when `k` is `"x"`. So a
|
|
10161
|
+
* write by name forgets what is known through variable keys, and a write
|
|
10162
|
+
* through any computed key forgets everything known about the table's
|
|
10163
|
+
* slots. */
|
|
10164
|
+
invalidateSiblings(expr, env) {
|
|
10165
|
+
const inner = expr.type === "ParenthesizedExpression" ? expr.expression : expr;
|
|
10166
|
+
if (inner.type !== "MemberExpression" && inner.type !== "IndexExpression") return;
|
|
10167
|
+
const parent = this.refKeyOf(inner.object);
|
|
10168
|
+
if (parent === void 0) return;
|
|
10169
|
+
const computed = inner.type === "IndexExpression" && inner.index.type !== "StringLiteral" && inner.index.type !== "NumberLiteral";
|
|
10170
|
+
for (const k of [...env.keys()]) {
|
|
10171
|
+
if (computed ? isBelow(k, parent) : k.startsWith(`${parent}[`)) env.delete(k);
|
|
10172
|
+
}
|
|
10173
|
+
}
|
|
9548
10174
|
// --------------------------------------------------------
|
|
9549
10175
|
// Small helpers
|
|
9550
10176
|
// --------------------------------------------------------
|
|
@@ -9559,6 +10185,35 @@ var TypeAnalyzer = class {
|
|
|
9559
10185
|
this.selfType = saved;
|
|
9560
10186
|
}
|
|
9561
10187
|
}
|
|
10188
|
+
/** Arithmetic no declared metamethod answers: a number — unless an
|
|
10189
|
+
* operand is `any`, which may be a value with metamethods of its own.
|
|
10190
|
+
* `(a.Position - b.Position).Magnitude` on an `a` nothing typed is a
|
|
10191
|
+
* `Vector3` as far as anyone knows, not a number with no `Magnitude`. */
|
|
10192
|
+
arithmeticOn(operands) {
|
|
10193
|
+
return operands.some((t) => this.expand(t).kind === "any") ? anyType : numberType;
|
|
10194
|
+
}
|
|
10195
|
+
/** `a < b` between class instances: Luau asks `__lt` (`__le` for `<=`),
|
|
10196
|
+
* with the operands swapped for `>` and `>=`, and an instance without
|
|
10197
|
+
* one cannot be compared at all. */
|
|
10198
|
+
checkComparison(node, op, l, r) {
|
|
10199
|
+
if (!this.emitDiagnostics) return;
|
|
10200
|
+
const left = this.expand(l);
|
|
10201
|
+
const right = this.expand(r);
|
|
10202
|
+
const instance = (t) => t.kind === "object" && t.class !== void 0;
|
|
10203
|
+
if (!instance(left) && !instance(right)) return;
|
|
10204
|
+
const name = op === "<" || op === ">" ? "__lt" : "__le";
|
|
10205
|
+
const operands = op === ">" || op === ">=" ? [right, left] : [left, right];
|
|
10206
|
+
for (const receiver of operands) {
|
|
10207
|
+
const method = receiver.kind === "object" ? receiver.properties.get(name) : void 0;
|
|
10208
|
+
if (!method) continue;
|
|
10209
|
+
if (this.pickOverload(this.overloadsOf(method.type), operands)) return;
|
|
10210
|
+
break;
|
|
10211
|
+
}
|
|
10212
|
+
this.diagnostics.push({
|
|
10213
|
+
node,
|
|
10214
|
+
message: `Operator '${op}' cannot be applied to types '${formatType(l)}' and '${formatType(r)}'`
|
|
10215
|
+
});
|
|
10216
|
+
}
|
|
9562
10217
|
/** What an operator on a value with metamethods gives: `a + b` calls
|
|
9563
10218
|
* `__add` on `a`, or failing that on `b` with the operands swapped — the
|
|
9564
10219
|
* order Luau tries them in. That is how `Vector3 + Vector3`, `CFrame *
|
|
@@ -9883,7 +10538,7 @@ function briefType(t) {
|
|
|
9883
10538
|
}
|
|
9884
10539
|
|
|
9885
10540
|
// src/project/host.ts
|
|
9886
|
-
import { readFileSync, statSync } from "fs";
|
|
10541
|
+
import { readdirSync, readFileSync, statSync } from "fs";
|
|
9887
10542
|
var nodeHost = {
|
|
9888
10543
|
readFile(path) {
|
|
9889
10544
|
try {
|
|
@@ -9891,6 +10546,13 @@ var nodeHost = {
|
|
|
9891
10546
|
} catch {
|
|
9892
10547
|
return void 0;
|
|
9893
10548
|
}
|
|
10549
|
+
},
|
|
10550
|
+
readDirectory(path) {
|
|
10551
|
+
try {
|
|
10552
|
+
return readdirSync(path, { withFileTypes: true }).filter((entry) => entry.isDirectory() || entry.isSymbolicLink()).map((entry) => entry.name);
|
|
10553
|
+
} catch {
|
|
10554
|
+
return void 0;
|
|
10555
|
+
}
|
|
9894
10556
|
}
|
|
9895
10557
|
};
|
|
9896
10558
|
|
|
@@ -10082,13 +10744,31 @@ function resolveTypeLibraries(config, host = nodeHost) {
|
|
|
10082
10744
|
else problems.push({ file: config.path, message: `'${entry}' has no ${ENTRY_FILE} (or 'tilua.types' in its package.json)`, ...entryPosition(config, entry) });
|
|
10083
10745
|
continue;
|
|
10084
10746
|
}
|
|
10085
|
-
|
|
10086
|
-
|
|
10747
|
+
if (entry.includes("*")) {
|
|
10748
|
+
const matched = matchPackages(entry, config.directory, host);
|
|
10749
|
+
if (matched === void 0) {
|
|
10750
|
+
problems.push({
|
|
10751
|
+
file: config.path,
|
|
10752
|
+
message: `'${entry}' needs the folders listed, and this tool cannot list them; name the libraries one by one`,
|
|
10753
|
+
...entryPosition(config, entry)
|
|
10754
|
+
});
|
|
10755
|
+
} else if (!matched.length) {
|
|
10756
|
+
problems.push({
|
|
10757
|
+
file: config.path,
|
|
10758
|
+
message: `No installed type library matches '${entry}'`,
|
|
10759
|
+
...entryPosition(config, entry)
|
|
10760
|
+
});
|
|
10761
|
+
}
|
|
10762
|
+
for (const found2 of matched ?? []) addPackage(found2.directory, found2.file, /* @__PURE__ */ new Set());
|
|
10763
|
+
continue;
|
|
10764
|
+
}
|
|
10765
|
+
const names = entry.startsWith("@") ? [entry] : [`@tilua-types/${entry}`, entry];
|
|
10766
|
+
const found = names.map((name) => findPackage(name, config.directory, host)).find(Boolean);
|
|
10087
10767
|
if (found) addPackage(found.directory, found.file, /* @__PURE__ */ new Set());
|
|
10088
10768
|
else {
|
|
10089
10769
|
problems.push({
|
|
10090
10770
|
file: config.path,
|
|
10091
|
-
message: `Cannot find type library '${
|
|
10771
|
+
message: names.length === 1 ? `Cannot find type library '${entry}'. Install it with: npm i -D ${entry}` : `Cannot find type library '${names[0]}' or '${entry}'. Install it with: npm i -D ${names[0]}`,
|
|
10092
10772
|
...entryPosition(config, entry)
|
|
10093
10773
|
});
|
|
10094
10774
|
}
|
|
@@ -10125,6 +10805,32 @@ function findPackage(name, from, host) {
|
|
|
10125
10805
|
directory = parent;
|
|
10126
10806
|
}
|
|
10127
10807
|
}
|
|
10808
|
+
function matchPackages(pattern, from, host) {
|
|
10809
|
+
if (!host.readDirectory) return void 0;
|
|
10810
|
+
const slash = pattern.lastIndexOf("/");
|
|
10811
|
+
const scope = slash < 0 ? "" : pattern.slice(0, slash);
|
|
10812
|
+
const last = pattern.slice(slash + 1);
|
|
10813
|
+
const matches = new RegExp(`^${last.split("*").map(escapeRegExp2).join(".*")}$`);
|
|
10814
|
+
const found = /* @__PURE__ */ new Map();
|
|
10815
|
+
let directory = resolve2(from);
|
|
10816
|
+
for (; ; ) {
|
|
10817
|
+
const folder = join2(directory, "node_modules", ...scope ? scope.split("/") : []);
|
|
10818
|
+
for (const name of host.readDirectory(folder) ?? []) {
|
|
10819
|
+
const full = scope ? `${scope}/${name}` : name;
|
|
10820
|
+
if (!matches.test(name) || found.has(full)) continue;
|
|
10821
|
+
const candidate = join2(folder, name);
|
|
10822
|
+
const file = packageEntry(candidate, host);
|
|
10823
|
+
if (file) found.set(full, { directory: candidate, file });
|
|
10824
|
+
}
|
|
10825
|
+
const parent = dirname2(directory);
|
|
10826
|
+
if (parent === directory) break;
|
|
10827
|
+
directory = parent;
|
|
10828
|
+
}
|
|
10829
|
+
return [...found.keys()].sort().map((name) => found.get(name));
|
|
10830
|
+
}
|
|
10831
|
+
function escapeRegExp2(text) {
|
|
10832
|
+
return text.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
|
|
10833
|
+
}
|
|
10128
10834
|
function dependencyNames(directory, host) {
|
|
10129
10835
|
const manifest = readJson(join2(directory, "package.json"), host);
|
|
10130
10836
|
const names = /* @__PURE__ */ new Set();
|
|
@@ -10223,7 +10929,8 @@ function sourceMapTypes(text, path, options) {
|
|
|
10223
10929
|
for (const child of node.children ?? []) {
|
|
10224
10930
|
if (!isNode(child)) continue;
|
|
10225
10931
|
const childAlias = visit(child, [...segments, child.name], alias);
|
|
10226
|
-
|
|
10932
|
+
const service = child.name === child.className && options.classes.has(child.className);
|
|
10933
|
+
if (!IDENTIFIER.test(child.name) || taken.has(child.name) && !service || named.has(child.name)) continue;
|
|
10227
10934
|
named.add(child.name);
|
|
10228
10935
|
members.push(`${child.name}: ${childAlias}`);
|
|
10229
10936
|
}
|
|
@@ -10283,6 +10990,7 @@ export {
|
|
|
10283
10990
|
BinaryOperators,
|
|
10284
10991
|
CONFIG_FILE_NAMES,
|
|
10285
10992
|
Keywords,
|
|
10993
|
+
LANGUAGE_GLOBALS,
|
|
10286
10994
|
LexError,
|
|
10287
10995
|
Operators,
|
|
10288
10996
|
PRELUDE_SOURCE,
|
|
@@ -10351,6 +11059,7 @@ export {
|
|
|
10351
11059
|
tilua,
|
|
10352
11060
|
tokenize,
|
|
10353
11061
|
tuple,
|
|
11062
|
+
tupleMembers,
|
|
10354
11063
|
typeParam,
|
|
10355
11064
|
unify,
|
|
10356
11065
|
union,
|