@tilua/parser 1.0.2 → 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 +183 -37
- package/dist/index.cjs +1173 -417
- package/dist/index.d.cts +189 -68
- package/dist/index.d.ts +189 -68
- package/dist/index.js +1172 -418
- 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() {
|
|
@@ -1727,11 +1776,67 @@ var Parser = class {
|
|
|
1727
1776
|
}
|
|
1728
1777
|
parseClassMember() {
|
|
1729
1778
|
const start = this.current();
|
|
1730
|
-
|
|
1731
|
-
|
|
1779
|
+
let accessibility;
|
|
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
|
+
}
|
|
1791
|
+
}
|
|
1792
|
+
const member = this.parseClassMemberAfterModifiers(start, modifiers);
|
|
1793
|
+
if (!member) return member;
|
|
1794
|
+
const misplaced = (message) => this.problemAt(start, message);
|
|
1795
|
+
if (member.type === "ClassConstructor") {
|
|
1796
|
+
const written = [...accessibility ? [accessibility] : [], ...modifiers];
|
|
1797
|
+
if (written.length) misplaced(`A constructor cannot be '${written[0]}'`);
|
|
1798
|
+
return member;
|
|
1799
|
+
}
|
|
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
|
+
};
|
|
1810
|
+
}
|
|
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");
|
|
1732
1820
|
if (this.checkKeyword("function")) {
|
|
1733
1821
|
this.advance();
|
|
1734
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
|
+
}
|
|
1735
1840
|
const signatures = [];
|
|
1736
1841
|
let written = memberName;
|
|
1737
1842
|
while (true) {
|
|
@@ -1849,12 +1954,17 @@ var Parser = class {
|
|
|
1849
1954
|
parseReturnStatement(stopAtNewline = false) {
|
|
1850
1955
|
const start = this.current();
|
|
1851
1956
|
this.expectKeyword("return");
|
|
1852
|
-
let
|
|
1957
|
+
let argument;
|
|
1853
1958
|
const pastLine = () => stopAtNewline && this.current().line.start > start.line.start;
|
|
1854
1959
|
if (!pastLine() && this.isExpressionStart()) {
|
|
1855
|
-
|
|
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
|
+
}
|
|
1856
1966
|
}
|
|
1857
|
-
return { type: "ReturnStatement",
|
|
1967
|
+
return { type: "ReturnStatement", argument, ...spanFrom(start, this.previous()) };
|
|
1858
1968
|
}
|
|
1859
1969
|
parseTypeAliasStatement() {
|
|
1860
1970
|
const start = this.current();
|
|
@@ -1913,7 +2023,7 @@ var Parser = class {
|
|
|
1913
2023
|
...spanFrom(start, this.previous())
|
|
1914
2024
|
};
|
|
1915
2025
|
}
|
|
1916
|
-
if (first.type === "CallExpression" || first.type === "MethodCallExpression"
|
|
2026
|
+
if (first.type === "CallExpression" || first.type === "MethodCallExpression") {
|
|
1917
2027
|
return { type: "CallStatement", expression: first, ...spanFrom(start, this.previous()) };
|
|
1918
2028
|
}
|
|
1919
2029
|
let expression = first;
|
|
@@ -1966,6 +2076,15 @@ var Parser = class {
|
|
|
1966
2076
|
}
|
|
1967
2077
|
return false;
|
|
1968
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
|
+
}
|
|
1969
2088
|
/** Does the next token start right where the current one ends? */
|
|
1970
2089
|
touchesNext() {
|
|
1971
2090
|
const current = this.current();
|
|
@@ -2152,6 +2271,7 @@ var Parser = class {
|
|
|
2152
2271
|
}
|
|
2153
2272
|
parseAtom() {
|
|
2154
2273
|
const t = this.current();
|
|
2274
|
+
if (this.startsStringMethodCall()) return this.parsePrefixExpression();
|
|
2155
2275
|
if (t.type === "Literal") {
|
|
2156
2276
|
this.advance();
|
|
2157
2277
|
const lit = t;
|
|
@@ -2171,8 +2291,9 @@ var Parser = class {
|
|
|
2171
2291
|
return this.buildInterpolatedString(t);
|
|
2172
2292
|
}
|
|
2173
2293
|
if (t.type === "Operator" && t.value === "...") {
|
|
2294
|
+
this.problem(NO_DOTS);
|
|
2174
2295
|
this.advance();
|
|
2175
|
-
return { type: "
|
|
2296
|
+
return { type: "ErrorExpression", ...spanFrom(t, t) };
|
|
2176
2297
|
}
|
|
2177
2298
|
if (t.type === "Keyword" && t.value === "function") {
|
|
2178
2299
|
this.advance();
|
|
@@ -2248,12 +2369,17 @@ var Parser = class {
|
|
|
2248
2369
|
const start = this.current();
|
|
2249
2370
|
let base;
|
|
2250
2371
|
if (this.startsNew()) {
|
|
2251
|
-
|
|
2252
|
-
|
|
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()) {
|
|
2253
2376
|
this.advance();
|
|
2254
2377
|
base = { type: "SuperExpression", ...spanFrom(start, start) };
|
|
2255
2378
|
} else if (this.checkType("Identifier")) {
|
|
2256
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) };
|
|
2257
2383
|
} else if (this.matchPunctuator("(")) {
|
|
2258
2384
|
const inner = this.inBrackets(() => this.parseExpression());
|
|
2259
2385
|
this.expectPunctuator(")");
|
|
@@ -2364,9 +2490,9 @@ var Parser = class {
|
|
|
2364
2490
|
}
|
|
2365
2491
|
return base;
|
|
2366
2492
|
}
|
|
2367
|
-
/** `new
|
|
2368
|
-
*
|
|
2369
|
-
*
|
|
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. */
|
|
2370
2496
|
startsNew() {
|
|
2371
2497
|
return this.checkIdentifierValue("new") && this.peek(1).type === "Identifier";
|
|
2372
2498
|
}
|
|
@@ -2376,25 +2502,6 @@ var Parser = class {
|
|
|
2376
2502
|
startsSuperUse() {
|
|
2377
2503
|
return this.punctuatorAt(1, "(") || this.punctuatorAt(1, ".") && this.peek(2).type === "Identifier";
|
|
2378
2504
|
}
|
|
2379
|
-
/** `new Name(args)` / `new Module.Name(args)`. The callee is a name, or a
|
|
2380
|
-
* name reached through a module — never an arbitrary expression, so the
|
|
2381
|
-
* arguments are unambiguously the constructor's. */
|
|
2382
|
-
parseNewExpression() {
|
|
2383
|
-
const start = this.current();
|
|
2384
|
-
this.advance();
|
|
2385
|
-
let callee = this.parseIdentifier();
|
|
2386
|
-
while (this.checkPunctuator(".") && this.peek(1).type === "Identifier") {
|
|
2387
|
-
this.advance();
|
|
2388
|
-
const property = this.parseIdentifier();
|
|
2389
|
-
callee = { type: "MemberExpression", object: callee, property, ...spanFrom(start, property) };
|
|
2390
|
-
}
|
|
2391
|
-
const typeArguments = this.tryCallTypeArguments();
|
|
2392
|
-
if (!this.checkPunctuator("(")) {
|
|
2393
|
-
this.error("Expected '(' after the class being constructed: 'new Name(...)'");
|
|
2394
|
-
}
|
|
2395
|
-
const args = this.parseCallArguments();
|
|
2396
|
-
return { type: "NewExpression", callee, arguments: args, typeArguments, ...spanFrom(start, this.previous()) };
|
|
2397
|
-
}
|
|
2398
2505
|
/** After `...`, is there something to spread? Nothing following it means
|
|
2399
2506
|
* the vararg pack, which is what `f(...)` has always passed on. */
|
|
2400
2507
|
startsSpread() {
|
|
@@ -2436,8 +2543,8 @@ var Parser = class {
|
|
|
2436
2543
|
const errors = this.errors.length;
|
|
2437
2544
|
try {
|
|
2438
2545
|
this.advance();
|
|
2439
|
-
const list = [this.
|
|
2440
|
-
while (this.matchPunctuator(",") && !this.checkOperator(">")) list.push(this.
|
|
2546
|
+
const list = [this.parseType()];
|
|
2547
|
+
while (this.matchPunctuator(",") && !this.checkOperator(">")) list.push(this.parseType());
|
|
2441
2548
|
this.expectOperator(">");
|
|
2442
2549
|
if (!this.startsCallArguments()) throw new ParseRecover("not a call");
|
|
2443
2550
|
return list;
|
|
@@ -2569,7 +2676,7 @@ var Parser = class {
|
|
|
2569
2676
|
if (this.checkOperator("...")) {
|
|
2570
2677
|
const dots = this.advance();
|
|
2571
2678
|
if (this.checkPunctuator("]") || this.checkPunctuator(",")) {
|
|
2572
|
-
|
|
2679
|
+
this.problemAt(dots, NO_DOTS);
|
|
2573
2680
|
} else {
|
|
2574
2681
|
const argument = this.expressionOr(stop);
|
|
2575
2682
|
elements.push({ type: "SpreadElement", argument, ...spanFrom(dots, argument) });
|
|
@@ -2723,35 +2830,16 @@ var Parser = class {
|
|
|
2723
2830
|
column: p.column
|
|
2724
2831
|
};
|
|
2725
2832
|
}
|
|
2726
|
-
|
|
2833
|
+
/** `T...` written where a type goes: a pack, which tilua does not have. */
|
|
2834
|
+
rejectPackReference() {
|
|
2727
2835
|
if (this.checkType("Identifier") && this.peek(1).type === "Operator" && this.peek(1).value === "...") {
|
|
2728
|
-
|
|
2729
|
-
|
|
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();
|
|
2730
2841
|
this.advance();
|
|
2731
|
-
const packRef = { type: "TypeReference", base, typeArguments: [], ...spanFrom(start, start) };
|
|
2732
|
-
return {
|
|
2733
|
-
type: "TypePackNode",
|
|
2734
|
-
types: [],
|
|
2735
|
-
hasVarargs: true,
|
|
2736
|
-
// Wrap in `VariadicTypeNode`, matching the convention used by
|
|
2737
|
-
// `parseFunctionTypeAfterParen`'s identifier-pack-reference
|
|
2738
|
-
// branch, so the printer can tell `A...` (name-first, this
|
|
2739
|
-
// case) apart from `...T` (dots-first) and append rather
|
|
2740
|
-
// than prepend the `...`.
|
|
2741
|
-
varargType: { type: "VariadicTypeNode", typeAnnotation: packRef, ...spanFrom(start, this.previous()) },
|
|
2742
|
-
...spanFrom(start, this.previous())
|
|
2743
|
-
};
|
|
2744
|
-
}
|
|
2745
|
-
return this.parseType();
|
|
2746
|
-
}
|
|
2747
|
-
parseTypeArgument() {
|
|
2748
|
-
if (this.checkOperator("...")) {
|
|
2749
|
-
return this.parseTypePack();
|
|
2750
|
-
}
|
|
2751
|
-
if (this.checkType("Identifier") && this.peek(1).type === "Operator" && this.peek(1).value === "...") {
|
|
2752
|
-
return this.parseTypeOrTypePackReference();
|
|
2753
2842
|
}
|
|
2754
|
-
return this.parseType();
|
|
2755
2843
|
}
|
|
2756
2844
|
/** The part shared by a real function body and an overload signature:
|
|
2757
2845
|
* `<generics>(params): ReturnType`, up to (but not including) the block. */
|
|
@@ -2764,7 +2852,6 @@ var Parser = class {
|
|
|
2764
2852
|
this.expectPunctuator("(");
|
|
2765
2853
|
const params = [];
|
|
2766
2854
|
let hasVarargs = false;
|
|
2767
|
-
let varargTypeAnnotation;
|
|
2768
2855
|
if (!this.checkPunctuator(")")) {
|
|
2769
2856
|
while (true) {
|
|
2770
2857
|
if (this.checkOperator("...")) {
|
|
@@ -2772,14 +2859,14 @@ var Parser = class {
|
|
|
2772
2859
|
hasVarargs = true;
|
|
2773
2860
|
if (this.checkType("Identifier")) {
|
|
2774
2861
|
const nameTok = this.expectIdentifier();
|
|
2775
|
-
let
|
|
2862
|
+
let typeAnnotation3;
|
|
2776
2863
|
if (this.matchPunctuator(":")) {
|
|
2777
|
-
|
|
2864
|
+
typeAnnotation3 = this.typeOr(() => this.checkPunctuator(")"));
|
|
2778
2865
|
}
|
|
2779
2866
|
params.push({
|
|
2780
2867
|
type: "FunctionParameter",
|
|
2781
2868
|
name: nameTok.value,
|
|
2782
|
-
typeAnnotation:
|
|
2869
|
+
typeAnnotation: typeAnnotation3,
|
|
2783
2870
|
rest: true,
|
|
2784
2871
|
...spanFrom(dots, this.previous())
|
|
2785
2872
|
});
|
|
@@ -2788,9 +2875,20 @@ var Parser = class {
|
|
|
2788
2875
|
}
|
|
2789
2876
|
break;
|
|
2790
2877
|
}
|
|
2878
|
+
this.problemAt(dots, NO_DOTS);
|
|
2879
|
+
let typeAnnotation2;
|
|
2791
2880
|
if (this.matchPunctuator(":")) {
|
|
2792
|
-
|
|
2881
|
+
this.rejectPackReference();
|
|
2882
|
+
const element = this.parseType();
|
|
2883
|
+
typeAnnotation2 = { type: "ArrayTypeNode", element, ...spanFrom(element, element) };
|
|
2793
2884
|
}
|
|
2885
|
+
params.push({
|
|
2886
|
+
type: "FunctionParameter",
|
|
2887
|
+
name: "args",
|
|
2888
|
+
typeAnnotation: typeAnnotation2,
|
|
2889
|
+
rest: true,
|
|
2890
|
+
...spanFrom(dots, this.previous())
|
|
2891
|
+
});
|
|
2794
2892
|
break;
|
|
2795
2893
|
}
|
|
2796
2894
|
const paramStart = this.current();
|
|
@@ -2832,10 +2930,16 @@ var Parser = class {
|
|
|
2832
2930
|
if (this.matchPunctuator(":")) {
|
|
2833
2931
|
predicate = this.tryParseTypePredicate();
|
|
2834
2932
|
if (!predicate) {
|
|
2835
|
-
returnType = this.attempt(() => this.
|
|
2933
|
+
returnType = this.attempt(() => this.parseReturnType(), () => false, () => void 0);
|
|
2836
2934
|
}
|
|
2837
2935
|
}
|
|
2838
|
-
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();
|
|
2839
2943
|
}
|
|
2840
2944
|
/** TypeScript-style type-guard return annotations, in return position only:
|
|
2841
2945
|
*
|
|
@@ -2905,7 +3009,6 @@ var Parser = class {
|
|
|
2905
3009
|
generics: [],
|
|
2906
3010
|
params: [{ type: "FunctionParameter", name: name.value, ...spanFrom(name, name) }],
|
|
2907
3011
|
hasVarargs: false,
|
|
2908
|
-
varargTypeAnnotation: void 0,
|
|
2909
3012
|
returnType: void 0,
|
|
2910
3013
|
predicate: void 0
|
|
2911
3014
|
};
|
|
@@ -2917,7 +3020,6 @@ var Parser = class {
|
|
|
2917
3020
|
generics: head.generics,
|
|
2918
3021
|
params: head.params,
|
|
2919
3022
|
hasVarargs: head.hasVarargs,
|
|
2920
|
-
varargTypeAnnotation: head.varargTypeAnnotation,
|
|
2921
3023
|
returnType: head.returnType,
|
|
2922
3024
|
predicate: head.predicate,
|
|
2923
3025
|
body,
|
|
@@ -2929,7 +3031,7 @@ var Parser = class {
|
|
|
2929
3031
|
returnOf(expression) {
|
|
2930
3032
|
const statement = {
|
|
2931
3033
|
type: "ReturnStatement",
|
|
2932
|
-
|
|
3034
|
+
argument: expression,
|
|
2933
3035
|
...spanFrom(expression, expression)
|
|
2934
3036
|
};
|
|
2935
3037
|
return { type: "Block", statements: [statement], ...spanFrom(expression, expression) };
|
|
@@ -2942,7 +3044,6 @@ var Parser = class {
|
|
|
2942
3044
|
generics: head.generics,
|
|
2943
3045
|
params: head.params,
|
|
2944
3046
|
hasVarargs: head.hasVarargs,
|
|
2945
|
-
varargTypeAnnotation: head.varargTypeAnnotation,
|
|
2946
3047
|
returnType: head.returnType,
|
|
2947
3048
|
predicate: head.predicate,
|
|
2948
3049
|
body,
|
|
@@ -2955,7 +3056,6 @@ var Parser = class {
|
|
|
2955
3056
|
generics: head.generics,
|
|
2956
3057
|
params: head.params,
|
|
2957
3058
|
hasVarargs: head.hasVarargs,
|
|
2958
|
-
varargTypeAnnotation: head.varargTypeAnnotation,
|
|
2959
3059
|
returnType: head.returnType,
|
|
2960
3060
|
predicate: head.predicate,
|
|
2961
3061
|
...spanFrom(head.start, this.previous())
|
|
@@ -2973,7 +3073,6 @@ var Parser = class {
|
|
|
2973
3073
|
generics: head.generics,
|
|
2974
3074
|
params: head.params,
|
|
2975
3075
|
hasVarargs: head.hasVarargs,
|
|
2976
|
-
varargTypeAnnotation: head.varargTypeAnnotation,
|
|
2977
3076
|
returnType: head.returnType,
|
|
2978
3077
|
predicate: head.predicate,
|
|
2979
3078
|
body,
|
|
@@ -3114,9 +3213,10 @@ var Parser = class {
|
|
|
3114
3213
|
return this.parseFunctionTypeAfterParen(t, []);
|
|
3115
3214
|
}
|
|
3116
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[]]'");
|
|
3117
3217
|
this.advance();
|
|
3118
|
-
const
|
|
3119
|
-
return { type: "
|
|
3218
|
+
const element = this.parseType();
|
|
3219
|
+
return { type: "ArrayTypeNode", element, ...spanFrom(t, this.previous()) };
|
|
3120
3220
|
}
|
|
3121
3221
|
if (t.type === "Punctuator" && t.value === "{") {
|
|
3122
3222
|
return this.parseTableType();
|
|
@@ -3124,13 +3224,22 @@ var Parser = class {
|
|
|
3124
3224
|
if (t.type === "Punctuator" && t.value === "[") {
|
|
3125
3225
|
this.advance();
|
|
3126
3226
|
const elements = [];
|
|
3227
|
+
let rest;
|
|
3127
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
|
+
}
|
|
3128
3237
|
elements.push(this.parseType());
|
|
3129
3238
|
if (this.matchPunctuator(",")) continue;
|
|
3130
3239
|
break;
|
|
3131
3240
|
}
|
|
3132
3241
|
this.expectPunctuator("]");
|
|
3133
|
-
return { type: "TupleTypeNode", elements, ...spanFrom(t, this.previous()) };
|
|
3242
|
+
return { type: "TupleTypeNode", elements, ...rest ? { rest } : {}, ...spanFrom(t, this.previous()) };
|
|
3134
3243
|
}
|
|
3135
3244
|
if (t.type === "Identifier" && t.value === "typeof" && this.peek(1).type === "Punctuator" && this.peek(1).value === "(") {
|
|
3136
3245
|
this.advance();
|
|
@@ -3177,9 +3286,9 @@ var Parser = class {
|
|
|
3177
3286
|
if (this.checkOperator("<")) {
|
|
3178
3287
|
this.advance();
|
|
3179
3288
|
if (!this.checkOperator(">")) {
|
|
3180
|
-
typeArguments.push(this.
|
|
3289
|
+
typeArguments.push(this.parseType());
|
|
3181
3290
|
while (this.matchPunctuator(",") && !this.checkOperator(">")) {
|
|
3182
|
-
typeArguments.push(this.
|
|
3291
|
+
typeArguments.push(this.parseType());
|
|
3183
3292
|
}
|
|
3184
3293
|
}
|
|
3185
3294
|
this.expectOperator(">");
|
|
@@ -3190,13 +3299,12 @@ var Parser = class {
|
|
|
3190
3299
|
}
|
|
3191
3300
|
parseFunctionTypeAfterParen(start, generics) {
|
|
3192
3301
|
const params = [];
|
|
3193
|
-
let
|
|
3194
|
-
let varargType;
|
|
3302
|
+
let rested = false;
|
|
3195
3303
|
if (!this.checkPunctuator(")")) {
|
|
3196
3304
|
while (true) {
|
|
3197
3305
|
if (this.checkOperator("...")) {
|
|
3198
3306
|
const dots = this.advance();
|
|
3199
|
-
|
|
3307
|
+
rested = true;
|
|
3200
3308
|
if (this.checkType("Identifier") && this.punctuatorAt(1, ":")) {
|
|
3201
3309
|
const nameTok2 = this.expectIdentifier();
|
|
3202
3310
|
this.advance();
|
|
@@ -3210,16 +3318,21 @@ var Parser = class {
|
|
|
3210
3318
|
});
|
|
3211
3319
|
break;
|
|
3212
3320
|
}
|
|
3213
|
-
|
|
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
|
+
});
|
|
3214
3331
|
break;
|
|
3215
3332
|
}
|
|
3216
3333
|
if (this.checkType("Identifier") && this.peek(1).type === "Operator" && this.peek(1).value === "...") {
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
this.advance();
|
|
3220
|
-
hasVarargs = true;
|
|
3221
|
-
varargType = { type: "VariadicTypeNode", typeAnnotation: packRef, ...spanFrom(packStart, this.previous()) };
|
|
3222
|
-
break;
|
|
3334
|
+
this.rejectPackReference();
|
|
3335
|
+
continue;
|
|
3223
3336
|
}
|
|
3224
3337
|
let name;
|
|
3225
3338
|
let optional2 = false;
|
|
@@ -3249,19 +3362,17 @@ var Parser = class {
|
|
|
3249
3362
|
this.expectPunctuator(")");
|
|
3250
3363
|
if (this.matchPunctuator("=>") || this.mistypedArrow()) {
|
|
3251
3364
|
const predicate = this.tryParseTypePredicate();
|
|
3252
|
-
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();
|
|
3253
3366
|
return {
|
|
3254
3367
|
type: "FunctionTypeNode",
|
|
3255
3368
|
generics,
|
|
3256
3369
|
params,
|
|
3257
|
-
hasVarargs,
|
|
3258
|
-
varargType,
|
|
3259
3370
|
returnType,
|
|
3260
3371
|
predicate,
|
|
3261
3372
|
...spanFrom(start, this.previous())
|
|
3262
3373
|
};
|
|
3263
3374
|
}
|
|
3264
|
-
if (params.length === 1 && !params[0].name && !
|
|
3375
|
+
if (params.length === 1 && !params[0].name && !rested) {
|
|
3265
3376
|
return {
|
|
3266
3377
|
type: "ParenthesizedTypeNode",
|
|
3267
3378
|
typeAnnotation: params[0].typeAnnotation,
|
|
@@ -3271,11 +3382,14 @@ var Parser = class {
|
|
|
3271
3382
|
if (params.some((p) => p.name !== void 0)) {
|
|
3272
3383
|
this.error("Expected '=>' for function type");
|
|
3273
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]'");
|
|
3274
3390
|
return {
|
|
3275
|
-
type: "
|
|
3276
|
-
|
|
3277
|
-
hasVarargs,
|
|
3278
|
-
varargType,
|
|
3391
|
+
type: "TupleTypeNode",
|
|
3392
|
+
elements: params.map((p) => p.typeAnnotation),
|
|
3279
3393
|
...spanFrom(start, this.previous())
|
|
3280
3394
|
};
|
|
3281
3395
|
}
|
|
@@ -3413,40 +3527,15 @@ var Parser = class {
|
|
|
3413
3527
|
this.expectPunctuator("}");
|
|
3414
3528
|
return { type: "TableTypeNode", properties, ...spanFrom(start, this.previous()) };
|
|
3415
3529
|
}
|
|
3416
|
-
parseTypePack() {
|
|
3417
|
-
const start = this.current();
|
|
3418
|
-
if (this.matchOperator("...")) {
|
|
3419
|
-
const varargType2 = this.parseType();
|
|
3420
|
-
return { type: "TypePackNode", types: [], hasVarargs: true, varargType: varargType2, ...spanFrom(start, this.previous()) };
|
|
3421
|
-
}
|
|
3422
|
-
this.expectPunctuator("(");
|
|
3423
|
-
const types = [];
|
|
3424
|
-
let hasVarargs = false;
|
|
3425
|
-
let varargType;
|
|
3426
|
-
if (!(this.current().type === "Punctuator" && this.current().value === ")")) {
|
|
3427
|
-
while (true) {
|
|
3428
|
-
if (this.matchOperator("...")) {
|
|
3429
|
-
hasVarargs = true;
|
|
3430
|
-
varargType = this.parseType();
|
|
3431
|
-
break;
|
|
3432
|
-
}
|
|
3433
|
-
types.push(this.parseType());
|
|
3434
|
-
if (this.matchPunctuator(",")) continue;
|
|
3435
|
-
break;
|
|
3436
|
-
}
|
|
3437
|
-
}
|
|
3438
|
-
this.expectPunctuator(")");
|
|
3439
|
-
return { type: "TypePackNode", types, hasVarargs, varargType, ...spanFrom(start, this.previous()) };
|
|
3440
|
-
}
|
|
3441
3530
|
parseGenericTypeParameterList() {
|
|
3442
3531
|
const list = [];
|
|
3443
3532
|
this.expectOperator("<");
|
|
3444
3533
|
while (true) {
|
|
3445
3534
|
const isConst = this.matchKeyword("const");
|
|
3446
3535
|
const nameTok = this.expectIdentifier();
|
|
3447
|
-
|
|
3448
|
-
if (
|
|
3449
|
-
|
|
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'");
|
|
3450
3539
|
}
|
|
3451
3540
|
let constraint;
|
|
3452
3541
|
if (this.checkIdentifierValue("extends")) {
|
|
@@ -3455,17 +3544,13 @@ var Parser = class {
|
|
|
3455
3544
|
}
|
|
3456
3545
|
let def;
|
|
3457
3546
|
if (this.matchOperator("=")) {
|
|
3458
|
-
|
|
3459
|
-
|
|
3460
|
-
} else {
|
|
3461
|
-
def = this.parseType();
|
|
3462
|
-
}
|
|
3547
|
+
this.rejectPackReference();
|
|
3548
|
+
def = this.parseType();
|
|
3463
3549
|
}
|
|
3464
3550
|
list.push({
|
|
3465
3551
|
type: "GenericTypeParameter",
|
|
3466
3552
|
name: nameTok.value,
|
|
3467
3553
|
id: tokenIdentifier(nameTok),
|
|
3468
|
-
isPack,
|
|
3469
3554
|
isConst: isConst || void 0,
|
|
3470
3555
|
constraint,
|
|
3471
3556
|
default: def,
|
|
@@ -3556,7 +3641,7 @@ function childScope(parent) {
|
|
|
3556
3641
|
var Analyzer = class {
|
|
3557
3642
|
constructor(options) {
|
|
3558
3643
|
this.options = options;
|
|
3559
|
-
for (const name of options.builtinGlobals ?? []) {
|
|
3644
|
+
for (const name of [...LANGUAGE_GLOBALS, ...options.builtinGlobals ?? []]) {
|
|
3560
3645
|
const id = this.getOrCreateGlobalBinding(name);
|
|
3561
3646
|
this.bindings.get(id).isBuiltin = true;
|
|
3562
3647
|
}
|
|
@@ -3958,14 +4043,14 @@ var Analyzer = class {
|
|
|
3958
4043
|
return;
|
|
3959
4044
|
}
|
|
3960
4045
|
case "GenericForStatement": {
|
|
3961
|
-
|
|
4046
|
+
this.visitExpression(stmt.iterator, scope);
|
|
3962
4047
|
const bodyScope = childScope(scope);
|
|
3963
|
-
|
|
4048
|
+
this.declarePattern(bodyScope, stmt.variable, "for-generic", scope, stmt.kind === "const");
|
|
3964
4049
|
this.visitBlock(stmt.body, bodyScope);
|
|
3965
4050
|
return;
|
|
3966
4051
|
}
|
|
3967
4052
|
case "ReturnStatement":
|
|
3968
|
-
|
|
4053
|
+
if (stmt.argument) this.visitExpression(stmt.argument, scope);
|
|
3969
4054
|
return;
|
|
3970
4055
|
case "BreakStatement":
|
|
3971
4056
|
case "ContinueStatement":
|
|
@@ -4033,7 +4118,6 @@ var Analyzer = class {
|
|
|
4033
4118
|
this.declare(fnScope, param.name, kind, param);
|
|
4034
4119
|
}
|
|
4035
4120
|
});
|
|
4036
|
-
this.visitType(func.varargTypeAnnotation, fnScope);
|
|
4037
4121
|
this.visitType(func.returnType, fnScope);
|
|
4038
4122
|
this.functionDepth++;
|
|
4039
4123
|
try {
|
|
@@ -4061,6 +4145,7 @@ var Analyzer = class {
|
|
|
4061
4145
|
this.visitGenerics(node.typeParams, scope);
|
|
4062
4146
|
if (node.superclass) this.reference(outer, node.superclass);
|
|
4063
4147
|
for (const argument of node.superArguments ?? []) this.visitType(argument, scope);
|
|
4148
|
+
for (const shape of node.implements ?? []) this.visitType(shape, scope);
|
|
4064
4149
|
for (const member of node.members) {
|
|
4065
4150
|
switch (member.type) {
|
|
4066
4151
|
case "ClassField":
|
|
@@ -4120,7 +4205,6 @@ var Analyzer = class {
|
|
|
4120
4205
|
case "BooleanLiteral":
|
|
4121
4206
|
case "NumberLiteral":
|
|
4122
4207
|
case "StringLiteral":
|
|
4123
|
-
case "VarargExpression":
|
|
4124
4208
|
case "ErrorExpression":
|
|
4125
4209
|
return;
|
|
4126
4210
|
case "InterpolatedStringExpression":
|
|
@@ -4160,11 +4244,6 @@ var Analyzer = class {
|
|
|
4160
4244
|
this.visitExpression(expr.callee, scope);
|
|
4161
4245
|
for (const arg of expr.arguments) this.visitExpression(arg, scope);
|
|
4162
4246
|
return;
|
|
4163
|
-
case "NewExpression":
|
|
4164
|
-
this.visitExpression(expr.callee, scope);
|
|
4165
|
-
for (const argument of expr.arguments) this.visitExpression(argument, scope);
|
|
4166
|
-
for (const argument of expr.typeArguments ?? []) this.visitType(argument, scope);
|
|
4167
|
-
return;
|
|
4168
4247
|
case "SuperExpression":
|
|
4169
4248
|
return;
|
|
4170
4249
|
case "SpreadElement":
|
|
@@ -4218,6 +4297,7 @@ var Analyzer = class {
|
|
|
4218
4297
|
}
|
|
4219
4298
|
}
|
|
4220
4299
|
};
|
|
4300
|
+
var LANGUAGE_GLOBALS = ["scriptArgs"];
|
|
4221
4301
|
function analyzeScopes(program, options = {}) {
|
|
4222
4302
|
return new Analyzer(options).run(program);
|
|
4223
4303
|
}
|
|
@@ -4236,8 +4316,8 @@ type Exclude<T, U> = T - U
|
|
|
4236
4316
|
type Extract<T, U> = T extends U ? T : never
|
|
4237
4317
|
type NonNullable<T> = T - nil
|
|
4238
4318
|
|
|
4239
|
-
type ReturnType<T> = T extends (...unknown) => infer R ? R : never
|
|
4240
|
-
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
|
|
4241
4321
|
|
|
4242
4322
|
type Partial<T> = { [K in keyof T]?: T[K] }
|
|
4243
4323
|
type Required<T> = { [K in keyof T]-?: T[K] }
|
|
@@ -4322,8 +4402,11 @@ function literal(value) {
|
|
|
4322
4402
|
function arrayOf(element) {
|
|
4323
4403
|
return { kind: "array", element };
|
|
4324
4404
|
}
|
|
4325
|
-
function tuple(elements,
|
|
4326
|
-
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;
|
|
4327
4410
|
}
|
|
4328
4411
|
function objectType(entries, indexer, frozen) {
|
|
4329
4412
|
return { kind: "object", properties: new Map(entries), indexer, frozen };
|
|
@@ -4347,7 +4430,7 @@ function substitute(t, subst) {
|
|
|
4347
4430
|
case "array":
|
|
4348
4431
|
return arrayOf(substitute(t.element, subst));
|
|
4349
4432
|
case "tuple":
|
|
4350
|
-
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));
|
|
4351
4434
|
case "object": {
|
|
4352
4435
|
const entries = [];
|
|
4353
4436
|
for (const [k, v] of t.properties) entries.push([k, { ...v, type: substitute(v.type, subst) }]);
|
|
@@ -4371,14 +4454,20 @@ function substitute(t, subst) {
|
|
|
4371
4454
|
const inner = t.typeParams ? new Map([...subst].filter(([k]) => !t.typeParams.includes(k))) : subst;
|
|
4372
4455
|
let params = t.params.map((p) => ({ ...p, type: substitute(p.type, inner) }));
|
|
4373
4456
|
let varargs = t.varargs && substitute(t.varargs, inner);
|
|
4374
|
-
|
|
4457
|
+
let restIsWhole = t.restIsWhole;
|
|
4458
|
+
if (restIsWhole && varargs?.kind === "tuple") {
|
|
4375
4459
|
params = [...params, ...varargs.elements.map((type) => ({ type }))];
|
|
4376
|
-
varargs =
|
|
4460
|
+
varargs = varargs.rest;
|
|
4461
|
+
restIsWhole = void 0;
|
|
4462
|
+
} else if (restIsWhole && varargs?.kind === "array") {
|
|
4463
|
+
varargs = varargs.element;
|
|
4464
|
+
restIsWhole = void 0;
|
|
4377
4465
|
}
|
|
4378
4466
|
return {
|
|
4379
4467
|
kind: "function",
|
|
4380
4468
|
params,
|
|
4381
4469
|
varargs,
|
|
4470
|
+
...restIsWhole ? { restIsWhole } : {},
|
|
4382
4471
|
returns: substitute(t.returns, inner),
|
|
4383
4472
|
typeParams: t.typeParams,
|
|
4384
4473
|
typeParamDefaults: t.typeParamDefaults,
|
|
@@ -4461,11 +4550,19 @@ function unify(param, arg, vars, out) {
|
|
|
4461
4550
|
switch (param.kind) {
|
|
4462
4551
|
case "array":
|
|
4463
4552
|
if (arg.kind === "array") unify(param.element, arg.element, vars, out);
|
|
4464
|
-
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);
|
|
4465
4554
|
return;
|
|
4466
4555
|
case "tuple":
|
|
4467
|
-
if (arg.kind === "tuple")
|
|
4468
|
-
|
|
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
|
+
}
|
|
4469
4566
|
return;
|
|
4470
4567
|
case "function":
|
|
4471
4568
|
if (arg.kind === "function") {
|
|
@@ -4567,7 +4664,7 @@ function widen(t) {
|
|
|
4567
4664
|
case "array":
|
|
4568
4665
|
return arrayOf(widen(t.element));
|
|
4569
4666
|
case "tuple":
|
|
4570
|
-
return tuple(t.elements.map(widen), t.
|
|
4667
|
+
return tuple(t.elements.map(widen), t.rest && widen(t.rest));
|
|
4571
4668
|
case "object": {
|
|
4572
4669
|
if (t.frozen || t.class) return t;
|
|
4573
4670
|
const entries = [];
|
|
@@ -4667,13 +4764,18 @@ function isAssignableInner(a, b) {
|
|
|
4667
4764
|
if (a.kind === "primitive") return b.kind === "primitive" && b.name === a.name;
|
|
4668
4765
|
if (a.kind === "array") {
|
|
4669
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);
|
|
4670
4768
|
return false;
|
|
4671
4769
|
}
|
|
4672
4770
|
if (a.kind === "tuple") {
|
|
4673
4771
|
if (b.kind === "tuple") {
|
|
4674
|
-
|
|
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));
|
|
4675
4777
|
}
|
|
4676
|
-
if (b.kind === "array") return a.
|
|
4778
|
+
if (b.kind === "array") return tupleMembers(a).every((t) => isAssignable(t, b.element));
|
|
4677
4779
|
return false;
|
|
4678
4780
|
}
|
|
4679
4781
|
if (a.kind === "object") {
|
|
@@ -4722,7 +4824,7 @@ function sameRefTarget(a, b) {
|
|
|
4722
4824
|
return a.name === b.name && (!a.origin || !b.origin || a.origin === b.origin);
|
|
4723
4825
|
}
|
|
4724
4826
|
function isNoValue(t) {
|
|
4725
|
-
return t.kind === "tuple" && t.elements.length === 0;
|
|
4827
|
+
return t.kind === "tuple" && t.elements.length === 0 && !t.rest;
|
|
4726
4828
|
}
|
|
4727
4829
|
function equalTypes(a, b) {
|
|
4728
4830
|
return formatType(a) === formatType(b);
|
|
@@ -4829,7 +4931,7 @@ function containsFreeTypeParam(t, seen, bound) {
|
|
|
4829
4931
|
case "array":
|
|
4830
4932
|
return containsTypeParam(t.element, seen, bound);
|
|
4831
4933
|
case "tuple":
|
|
4832
|
-
return t.
|
|
4934
|
+
return tupleMembers(t).some((e) => containsTypeParam(e, seen, bound));
|
|
4833
4935
|
case "union":
|
|
4834
4936
|
case "intersection":
|
|
4835
4937
|
return t.types.some((m) => containsTypeParam(m, seen, bound));
|
|
@@ -4874,15 +4976,19 @@ function matchInfer(arg, pattern, out) {
|
|
|
4874
4976
|
switch (pattern.kind) {
|
|
4875
4977
|
case "array":
|
|
4876
4978
|
if (arg.kind === "array") return matchInfer(arg.element, pattern.element, out);
|
|
4877
|
-
if (arg.kind === "tuple") return matchInfer(union(arg
|
|
4979
|
+
if (arg.kind === "tuple") return matchInfer(union(tupleMembers(arg)), pattern.element, out);
|
|
4878
4980
|
return false;
|
|
4879
4981
|
case "tuple":
|
|
4880
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;
|
|
4881
4984
|
return pattern.elements.every((pt, i) => matchInfer(arg.elements[i], pt, out));
|
|
4882
4985
|
case "function": {
|
|
4883
4986
|
if (arg.kind !== "function") return false;
|
|
4884
4987
|
if (pattern.varargs?.kind === "infer" && !pattern.params.length) {
|
|
4885
|
-
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
|
+
));
|
|
4886
4992
|
} else {
|
|
4887
4993
|
for (let i = 0; i < pattern.params.length; i++) {
|
|
4888
4994
|
if (!arg.params[i]) return false;
|
|
@@ -4956,7 +5062,7 @@ function collectTypeParams(t, out, seen = /* @__PURE__ */ new Set()) {
|
|
|
4956
5062
|
collectTypeParams(t.element, out, seen);
|
|
4957
5063
|
return;
|
|
4958
5064
|
case "tuple":
|
|
4959
|
-
for (const e of t
|
|
5065
|
+
for (const e of tupleMembers(t)) collectTypeParams(e, out, seen);
|
|
4960
5066
|
return;
|
|
4961
5067
|
case "union":
|
|
4962
5068
|
case "intersection":
|
|
@@ -5003,9 +5109,8 @@ function formatTypeUncached(t) {
|
|
|
5003
5109
|
case "array":
|
|
5004
5110
|
return `${formatAtom(t.element)}[]`;
|
|
5005
5111
|
case "tuple": {
|
|
5006
|
-
|
|
5007
|
-
|
|
5008
|
-
return t.isPack ? `(${inner})` : `[${inner}]`;
|
|
5112
|
+
const inner = [...t.elements.map(formatType), ...t.rest ? [`...${formatAtom(t.rest)}[]`] : []];
|
|
5113
|
+
return `[${inner.join(", ")}]`;
|
|
5009
5114
|
}
|
|
5010
5115
|
case "object": {
|
|
5011
5116
|
if (t.name) {
|
|
@@ -5029,7 +5134,7 @@ function formatTypeUncached(t) {
|
|
|
5029
5134
|
return `${consts.has(n) ? "const " : ""}${n}${constraint ? ` extends ${briefConstraint(constraint)}` : ""}`;
|
|
5030
5135
|
}).join(", ")}>` : "";
|
|
5031
5136
|
const ps = t.params.map((p) => `${p.name ? p.name + ": " : ""}${formatType(p.type)}`);
|
|
5032
|
-
if (t.varargs) ps.push(
|
|
5137
|
+
if (t.varargs) ps.push(t.restIsWhole ? `...args: ${formatType(t.varargs)}` : `...args: ${formatAtom(t.varargs)}[]`);
|
|
5033
5138
|
return `${gen}(${ps.join(", ")}) => ${formatPredicate(t) ?? formatType(t.returns)}`;
|
|
5034
5139
|
}
|
|
5035
5140
|
case "typeParam":
|
|
@@ -5233,6 +5338,11 @@ function moduleExports(program, scopes, types, resolveModule) {
|
|
|
5233
5338
|
}
|
|
5234
5339
|
return { values, types: exportedTypes, default: defaultType };
|
|
5235
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
|
+
}
|
|
5236
5346
|
function bindKey(id) {
|
|
5237
5347
|
return `$${id}`;
|
|
5238
5348
|
}
|
|
@@ -5250,9 +5360,6 @@ function mergeEnv(a, b, base) {
|
|
|
5250
5360
|
function mergeIndexer(a, b) {
|
|
5251
5361
|
return a ? { key: union([a.key, b.key]), value: union([a.value, b.value]) } : b;
|
|
5252
5362
|
}
|
|
5253
|
-
function producesMultipleValues(e) {
|
|
5254
|
-
return e.type === "CallExpression" || e.type === "MethodCallExpression" || e.type === "VarargExpression";
|
|
5255
|
-
}
|
|
5256
5363
|
function isFreshLiteralExpr(e) {
|
|
5257
5364
|
if (!e) return false;
|
|
5258
5365
|
switch (e.type) {
|
|
@@ -5289,11 +5396,11 @@ function collectInferNames(node) {
|
|
|
5289
5396
|
walk(n.element);
|
|
5290
5397
|
return;
|
|
5291
5398
|
case "ParenthesizedTypeNode":
|
|
5292
|
-
case "VariadicTypeNode":
|
|
5293
5399
|
walk(n.typeAnnotation);
|
|
5294
5400
|
return;
|
|
5295
5401
|
case "TupleTypeNode":
|
|
5296
5402
|
n.elements.forEach(walk);
|
|
5403
|
+
walk(n.rest);
|
|
5297
5404
|
return;
|
|
5298
5405
|
case "UnionTypeNode":
|
|
5299
5406
|
case "IntersectionTypeNode":
|
|
@@ -5311,7 +5418,6 @@ function collectInferNames(node) {
|
|
|
5311
5418
|
return;
|
|
5312
5419
|
case "FunctionTypeNode":
|
|
5313
5420
|
n.params.forEach((pp) => walk(pp.typeAnnotation));
|
|
5314
|
-
walk(n.varargType);
|
|
5315
5421
|
walk(n.returnType);
|
|
5316
5422
|
return;
|
|
5317
5423
|
case "TableTypeNode":
|
|
@@ -5356,6 +5462,75 @@ function callsSuper(block) {
|
|
|
5356
5462
|
});
|
|
5357
5463
|
return found;
|
|
5358
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
|
+
};
|
|
5359
5534
|
function assignedFields(block) {
|
|
5360
5535
|
const names = /* @__PURE__ */ new Set();
|
|
5361
5536
|
walkNodes(block, (node) => {
|
|
@@ -5381,7 +5556,7 @@ function walkNodes(root, visit) {
|
|
|
5381
5556
|
if (key !== "line" && key !== "column" && value && typeof value === "object") walkNodes(value, visit);
|
|
5382
5557
|
}
|
|
5383
5558
|
}
|
|
5384
|
-
var
|
|
5559
|
+
var LazyMap = class extends Map {
|
|
5385
5560
|
pending = /* @__PURE__ */ new Map();
|
|
5386
5561
|
defer(name, resolve5) {
|
|
5387
5562
|
super.delete(name);
|
|
@@ -5496,14 +5671,14 @@ var TypeAnalyzer = class {
|
|
|
5496
5671
|
scopes;
|
|
5497
5672
|
options;
|
|
5498
5673
|
typeOf = /* @__PURE__ */ new Map();
|
|
5499
|
-
bindingType =
|
|
5674
|
+
bindingType = new LazyMap();
|
|
5500
5675
|
narrowedTypeOf = /* @__PURE__ */ new Map();
|
|
5501
5676
|
typeOfTypeNode = /* @__PURE__ */ new Map();
|
|
5502
5677
|
expectedTypeOf = /* @__PURE__ */ new Map();
|
|
5503
|
-
|
|
5678
|
+
loops = /* @__PURE__ */ new Map();
|
|
5504
5679
|
/** Public: each alias resolved once (generic aliases keep their params as
|
|
5505
5680
|
* `typeParam` nodes in the body). */
|
|
5506
|
-
aliases = new
|
|
5681
|
+
aliases = new LazyMap();
|
|
5507
5682
|
/** Uninstantiated alias definitions, for `Name<Args>` instantiation. */
|
|
5508
5683
|
aliasDefs = /* @__PURE__ */ new Map();
|
|
5509
5684
|
/** Put on every ref to one of `aliasDefs`, so a module that imports the
|
|
@@ -5517,8 +5692,12 @@ var TypeAnalyzer = class {
|
|
|
5517
5692
|
/** Generic parameters currently in lexical scope (alias body / generic fn),
|
|
5518
5693
|
* with their `extends` constraints resolved. */
|
|
5519
5694
|
typeParamScope = [];
|
|
5520
|
-
/** Global types contributed by `declare` statements (libs, then this program).
|
|
5521
|
-
|
|
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();
|
|
5522
5701
|
/** Declaration node -> binding, built once so `bindingIdByName` is O(1)
|
|
5523
5702
|
* instead of a scan of every binding per declaration site. */
|
|
5524
5703
|
bindingByDecl = /* @__PURE__ */ new Map();
|
|
@@ -5563,8 +5742,11 @@ var TypeAnalyzer = class {
|
|
|
5563
5742
|
this.indexDeclarations();
|
|
5564
5743
|
for (const [name, id] of this.scopes.globalsByName) {
|
|
5565
5744
|
if (this.deferredDeclares.has(name) && !this.options.globalTypes?.[name]) continue;
|
|
5566
|
-
const
|
|
5567
|
-
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);
|
|
5568
5750
|
}
|
|
5569
5751
|
setAliasExpander((t) => this.expand(t));
|
|
5570
5752
|
setDeferredBound((t) => this.deferredBound(t));
|
|
@@ -5584,7 +5766,7 @@ var TypeAnalyzer = class {
|
|
|
5584
5766
|
typeOfTypeNode: this.typeOfTypeNode,
|
|
5585
5767
|
expectedTypeOf: this.expectedTypeOf,
|
|
5586
5768
|
aliases: this.resolveDeferredAliases(),
|
|
5587
|
-
|
|
5769
|
+
loops: this.loops,
|
|
5588
5770
|
diagnostics: this.diagnostics
|
|
5589
5771
|
};
|
|
5590
5772
|
}
|
|
@@ -5793,13 +5975,18 @@ var TypeAnalyzer = class {
|
|
|
5793
5975
|
classIdentityCount = 0;
|
|
5794
5976
|
/** The class whose members are being read, so `super` knows its base. */
|
|
5795
5977
|
currentClass;
|
|
5978
|
+
/** Every class whose body the checker is inside, innermost last — what
|
|
5979
|
+
* decides whether a `private` member may be reached. */
|
|
5980
|
+
enclosingClasses = [];
|
|
5796
5981
|
withClass(stmt, fn2) {
|
|
5797
5982
|
const previous = this.currentClass;
|
|
5798
5983
|
this.currentClass = stmt;
|
|
5984
|
+
this.enclosingClasses.push(stmt);
|
|
5799
5985
|
try {
|
|
5800
5986
|
return fn2();
|
|
5801
5987
|
} finally {
|
|
5802
5988
|
this.currentClass = previous;
|
|
5989
|
+
this.enclosingClasses.pop();
|
|
5803
5990
|
}
|
|
5804
5991
|
}
|
|
5805
5992
|
/** What the class is known by. A declaration is known by its name, the way
|
|
@@ -5931,28 +6118,55 @@ var TypeAnalyzer = class {
|
|
|
5931
6118
|
type.name = `typeof ${this.className(stmt)}`;
|
|
5932
6119
|
const shape = this.shapeOf(stmt);
|
|
5933
6120
|
const local = this.superDecl(stmt);
|
|
5934
|
-
const parent = local ? this.classValueType(local) : stmt.superclass ? this.
|
|
6121
|
+
const parent = local ? this.classValueType(local) : stmt.superclass ? this.classStaticsOf(stmt.superclass) : void 0;
|
|
5935
6122
|
if (parent?.kind === "object") {
|
|
5936
6123
|
for (const [key, property] of parent.properties) {
|
|
5937
6124
|
if (!CLASS_LINKS.has(key)) type.properties.set(key, property);
|
|
5938
6125
|
}
|
|
5939
6126
|
}
|
|
5940
6127
|
for (const [key, property] of shape.statics) type.properties.set(key, property);
|
|
5941
|
-
|
|
6128
|
+
if (shape.filling) {
|
|
6129
|
+
(shape.filled ??= []).push(() => {
|
|
6130
|
+
for (const [key, property] of shape.statics) type.properties.set(key, property);
|
|
6131
|
+
});
|
|
6132
|
+
}
|
|
5942
6133
|
const params = this.classTypeParams(stmt);
|
|
5943
|
-
|
|
5944
|
-
|
|
5945
|
-
constructor?.params.filter((p) => p.name !== "this") ?? [],
|
|
5946
|
-
this.selfTypeOf(stmt),
|
|
5947
|
-
constructor?.varargs,
|
|
5948
|
-
params.map((p) => p.name)
|
|
5949
|
-
),
|
|
6134
|
+
let settled;
|
|
6135
|
+
const construct = {
|
|
5950
6136
|
optional: false,
|
|
5951
|
-
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
|
+
}
|
|
5952
6156
|
});
|
|
6157
|
+
type.properties.set("new", construct);
|
|
5953
6158
|
type.properties.set("ParentClass", { type: parent ?? nilType, optional: false, readonly: true });
|
|
5954
6159
|
return type;
|
|
5955
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
|
+
}
|
|
5956
6170
|
/** The value side of a class named by a binding rather than by a
|
|
5957
6171
|
* declaration in this file — an imported one. */
|
|
5958
6172
|
classStaticsByNameType(name) {
|
|
@@ -6013,17 +6227,26 @@ var TypeAnalyzer = class {
|
|
|
6013
6227
|
} finally {
|
|
6014
6228
|
this.emitDiagnostics = wasEmitting;
|
|
6015
6229
|
shape.filling = false;
|
|
6230
|
+
for (const then of shape.filled ?? []) then();
|
|
6231
|
+
shape.filled = void 0;
|
|
6016
6232
|
}
|
|
6017
6233
|
return shape;
|
|
6018
6234
|
}
|
|
6019
6235
|
fillShape(stmt, shape) {
|
|
6236
|
+
const className = this.className(stmt);
|
|
6020
6237
|
const put = (isStatic, name, property) => {
|
|
6021
|
-
(
|
|
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;
|
|
6240
|
+
(isStatic ? shape.statics : shape.instance).set(name, marked);
|
|
6022
6241
|
};
|
|
6023
6242
|
for (const member of stmt.members) {
|
|
6024
6243
|
if (member.type !== "ClassField") continue;
|
|
6025
6244
|
const type = member.typeAnnotation ? this.resolveType(member.typeAnnotation) : member.init ? widen(this.infer(member.init, /* @__PURE__ */ new Map())) : anyType;
|
|
6026
|
-
put(member.isStatic, member.name.name, {
|
|
6245
|
+
put(member.isStatic, member.name.name, {
|
|
6246
|
+
type,
|
|
6247
|
+
optional: false,
|
|
6248
|
+
...member.isReadonly ? { readonly: true } : {}
|
|
6249
|
+
});
|
|
6027
6250
|
}
|
|
6028
6251
|
for (const member of stmt.members) {
|
|
6029
6252
|
switch (member.type) {
|
|
@@ -6032,7 +6255,11 @@ var TypeAnalyzer = class {
|
|
|
6032
6255
|
case "ClassMethod": {
|
|
6033
6256
|
this.paramsFromSignatures(member.func, member.signatures);
|
|
6034
6257
|
const type = member.signatures?.length ? intersection(member.signatures.map((sig) => this.signatureToFnType(sig))) : this.inferFunctionBody(member.func, /* @__PURE__ */ new Map());
|
|
6035
|
-
put(member.isStatic, member.name.name, {
|
|
6258
|
+
put(member.isStatic, member.name.name, {
|
|
6259
|
+
type,
|
|
6260
|
+
optional: false,
|
|
6261
|
+
...member.isAbstract ? { abstract: true } : {}
|
|
6262
|
+
});
|
|
6036
6263
|
break;
|
|
6037
6264
|
}
|
|
6038
6265
|
case "ClassAccessor": {
|
|
@@ -6088,7 +6315,14 @@ var TypeAnalyzer = class {
|
|
|
6088
6315
|
for (const signature of member.signatures ?? []) {
|
|
6089
6316
|
this.checkParamOrder(signature.params, member);
|
|
6090
6317
|
}
|
|
6091
|
-
|
|
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
|
+
}
|
|
6092
6326
|
}
|
|
6093
6327
|
});
|
|
6094
6328
|
}));
|
|
@@ -6135,13 +6369,148 @@ var TypeAnalyzer = class {
|
|
|
6135
6369
|
if (stmt.superclass && this.baseInstance(stmt) && constructor && !callsSuper(constructor.func.body)) {
|
|
6136
6370
|
report(constructor, `'${name}' extends '${stmt.superclass.name}', so its constructor must call 'super(...)'`);
|
|
6137
6371
|
}
|
|
6138
|
-
|
|
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();
|
|
6139
6377
|
for (const member of stmt.members) {
|
|
6140
6378
|
if (member.type !== "ClassField" || member.isStatic || member.init) continue;
|
|
6141
|
-
if (assigned.has(member.name.name)) continue;
|
|
6379
|
+
if (assigned === ALL_FIELDS || assigned.has(member.name.name)) continue;
|
|
6142
6380
|
const type = member.typeAnnotation ? this.withTypeParams(this.classTypeParams(stmt), () => this.resolveType(member.typeAnnotation)) : anyType;
|
|
6143
6381
|
if (isAssignable(nilType, type)) continue;
|
|
6144
|
-
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
|
+
}
|
|
6145
6514
|
}
|
|
6146
6515
|
}
|
|
6147
6516
|
/** `extends` must name a class, and the chain must end. */
|
|
@@ -6199,19 +6568,42 @@ var TypeAnalyzer = class {
|
|
|
6199
6568
|
harvestDeclares(block, own = false) {
|
|
6200
6569
|
for (const stmt of block.statements) {
|
|
6201
6570
|
if (stmt.type !== "DeclareStatement") continue;
|
|
6202
|
-
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))) {
|
|
6203
6581
|
this.deferredDeclares.set(stmt.name, stmt);
|
|
6204
6582
|
continue;
|
|
6205
6583
|
}
|
|
6206
|
-
|
|
6207
|
-
|
|
6208
|
-
|
|
6209
|
-
|
|
6210
|
-
|
|
6211
|
-
|
|
6212
|
-
|
|
6584
|
+
this.libGlobalTypes.set(stmt.name, this.declaredOver(this.libGlobalTypes.get(stmt.name), stmt));
|
|
6585
|
+
}
|
|
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);
|
|
6213
6598
|
}
|
|
6214
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
|
+
}
|
|
6215
6607
|
resolveAllAliases() {
|
|
6216
6608
|
for (const [name, def] of this.aliasDefs) {
|
|
6217
6609
|
if (def.class && !this.program.body.statements.includes(def.class)) {
|
|
@@ -6257,11 +6649,10 @@ var TypeAnalyzer = class {
|
|
|
6257
6649
|
}
|
|
6258
6650
|
imported;
|
|
6259
6651
|
/** What a `return` gives, against what the function declared. */
|
|
6260
|
-
checkReturn(stmt, declared,
|
|
6652
|
+
checkReturn(stmt, declared, actual, env) {
|
|
6261
6653
|
if (!declared || !this.emitDiagnostics) return;
|
|
6262
6654
|
if (declared.kind === "any" || declared.kind === "unknown" || this.namesNothing(declared)) return;
|
|
6263
|
-
const
|
|
6264
|
-
const source = stmt.arguments.length === 1 ? sources[0] : void 0;
|
|
6655
|
+
const source = stmt.argument;
|
|
6265
6656
|
const fits = source ? this.fitsAnnotation(source, declared, actual, env) : isAssignable(actual, declared) || isAssignable(widen(actual), declared);
|
|
6266
6657
|
if (fits) return;
|
|
6267
6658
|
this.diagnostics.push({
|
|
@@ -6409,21 +6800,12 @@ var TypeAnalyzer = class {
|
|
|
6409
6800
|
this.instantiationDepth--;
|
|
6410
6801
|
}
|
|
6411
6802
|
}
|
|
6412
|
-
/** Pair written type arguments with the parameters they instantiate.
|
|
6413
|
-
*
|
|
6414
|
-
* one pack: `Signal<Instance, string>` binds `T` to `(Instance, string)`,
|
|
6415
|
-
* and `Signal<()>` to the empty pack. Left out, a parameter takes its
|
|
6416
|
-
* 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`. */
|
|
6417
6805
|
bindTypeArguments(params, args) {
|
|
6418
6806
|
const subst = /* @__PURE__ */ new Map();
|
|
6419
6807
|
params.forEach((p, i) => {
|
|
6420
|
-
|
|
6421
|
-
if (p.isPack && i < args.length) {
|
|
6422
|
-
const rest = args.slice(i);
|
|
6423
|
-
const single = rest.length === 1 ? rest[0] : void 0;
|
|
6424
|
-
arg = single && (single.kind === "tuple" && single.isPack || single.kind === "typeParam" || single.kind === "any") ? single : tuple([...rest], true);
|
|
6425
|
-
}
|
|
6426
|
-
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));
|
|
6427
6809
|
});
|
|
6428
6810
|
return subst;
|
|
6429
6811
|
}
|
|
@@ -6515,8 +6897,13 @@ var TypeAnalyzer = class {
|
|
|
6515
6897
|
return literal(node.value);
|
|
6516
6898
|
case "ArrayTypeNode":
|
|
6517
6899
|
return arrayOf(this.resolveType(node.element));
|
|
6518
|
-
case "TupleTypeNode":
|
|
6519
|
-
|
|
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
|
+
}
|
|
6520
6907
|
case "UnionTypeNode":
|
|
6521
6908
|
return union(node.types.map((t) => this.resolveType(t)));
|
|
6522
6909
|
case "IntersectionTypeNode":
|
|
@@ -6552,15 +6939,13 @@ var TypeAnalyzer = class {
|
|
|
6552
6939
|
type: p.optional ? optional(this.resolveType(p.typeAnnotation)) : this.resolveType(p.typeAnnotation),
|
|
6553
6940
|
optional: p.optional
|
|
6554
6941
|
}));
|
|
6555
|
-
|
|
6556
|
-
const restElement = restParam ? this.resolveType(restParam.typeAnnotation) : void 0;
|
|
6557
|
-
return this.withTypeParamDefaults(fn(
|
|
6942
|
+
return this.withTypeParamDefaults(this.withRest(fn(
|
|
6558
6943
|
params,
|
|
6559
6944
|
this.resolveType(node.returnType),
|
|
6560
|
-
|
|
6945
|
+
void 0,
|
|
6561
6946
|
names,
|
|
6562
6947
|
this.resolvePredicate(node.predicate, params)
|
|
6563
|
-
), node.generics);
|
|
6948
|
+
), node.params), node.generics);
|
|
6564
6949
|
});
|
|
6565
6950
|
}
|
|
6566
6951
|
case "TypeofTypeNode": {
|
|
@@ -6624,13 +7009,6 @@ var TypeAnalyzer = class {
|
|
|
6624
7009
|
})
|
|
6625
7010
|
);
|
|
6626
7011
|
}
|
|
6627
|
-
case "VariadicTypeNode":
|
|
6628
|
-
return this.resolveType(node.typeAnnotation);
|
|
6629
|
-
case "TypePackNode": {
|
|
6630
|
-
if (node.types.length === 1 && !node.hasVarargs) return this.resolveType(node.types[0]);
|
|
6631
|
-
if (!node.types.length && node.varargType) return this.resolveType(node.varargType);
|
|
6632
|
-
return tuple(node.types.map((t) => this.resolveType(t)), true);
|
|
6633
|
-
}
|
|
6634
7012
|
}
|
|
6635
7013
|
}
|
|
6636
7014
|
// --------------------------------------------------------
|
|
@@ -6696,15 +7074,18 @@ var TypeAnalyzer = class {
|
|
|
6696
7074
|
case "array":
|
|
6697
7075
|
return arrayOf(this.reduceType(t.element));
|
|
6698
7076
|
case "tuple":
|
|
6699
|
-
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));
|
|
6700
7078
|
case "function":
|
|
6701
|
-
return
|
|
6702
|
-
|
|
6703
|
-
|
|
6704
|
-
|
|
6705
|
-
|
|
6706
|
-
|
|
6707
|
-
|
|
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
|
+
};
|
|
6708
7089
|
case "object": {
|
|
6709
7090
|
if (t.class) return t;
|
|
6710
7091
|
const entries = [];
|
|
@@ -6919,7 +7300,8 @@ var TypeAnalyzer = class {
|
|
|
6919
7300
|
this.applyContext(stmt.init[i], this.resolveType(target.typeAnnotation));
|
|
6920
7301
|
}
|
|
6921
7302
|
});
|
|
6922
|
-
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);
|
|
6923
7305
|
stmt.names.forEach((target, i) => {
|
|
6924
7306
|
const inferred = valueTypes[i] ?? (stmt.init.length ? unknownType : nilType);
|
|
6925
7307
|
const source = sources[i];
|
|
@@ -7008,7 +7390,8 @@ var TypeAnalyzer = class {
|
|
|
7008
7390
|
if (id !== void 0 && this.annotated.has(id)) this.applyContext(value, this.bindingType.get(id));
|
|
7009
7391
|
}
|
|
7010
7392
|
});
|
|
7011
|
-
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);
|
|
7012
7395
|
stmt.targets.forEach((target, i) => {
|
|
7013
7396
|
const vt = valueTypes[i] ?? unknownType;
|
|
7014
7397
|
const source = sources[i];
|
|
@@ -7095,23 +7478,26 @@ var TypeAnalyzer = class {
|
|
|
7095
7478
|
return;
|
|
7096
7479
|
}
|
|
7097
7480
|
case "GenericForStatement": {
|
|
7098
|
-
const
|
|
7481
|
+
const sourceType = this.infer(stmt.iterator, env);
|
|
7099
7482
|
const bodyEnv = forkEnv(env);
|
|
7100
|
-
const rows =
|
|
7483
|
+
const rows = this.iterationRows(stmt.iterator, sourceType);
|
|
7101
7484
|
if (rows) {
|
|
7102
|
-
|
|
7103
|
-
|
|
7104
|
-
|
|
7105
|
-
|
|
7106
|
-
|
|
7107
|
-
|
|
7108
|
-
|
|
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 });
|
|
7109
7497
|
} else {
|
|
7110
|
-
const [
|
|
7111
|
-
stmt.
|
|
7112
|
-
|
|
7113
|
-
});
|
|
7114
|
-
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);
|
|
7115
7501
|
}
|
|
7116
7502
|
this.visitBlock(stmt.body, bodyEnv);
|
|
7117
7503
|
return;
|
|
@@ -7119,19 +7505,10 @@ var TypeAnalyzer = class {
|
|
|
7119
7505
|
case "ReturnStatement": {
|
|
7120
7506
|
const declared = this.declaredReturns[this.declaredReturns.length - 1];
|
|
7121
7507
|
const wanted = declared ?? this.contextualReturns[this.contextualReturns.length - 1];
|
|
7122
|
-
if (wanted)
|
|
7123
|
-
|
|
7124
|
-
|
|
7125
|
-
|
|
7126
|
-
stmt.arguments.forEach((a, i) => this.applyContext(a, wanted.elements[i]));
|
|
7127
|
-
}
|
|
7128
|
-
}
|
|
7129
|
-
const want = declared?.kind === "tuple" && declared.isPack ? declared.elements.length : 0;
|
|
7130
|
-
const { types, sources } = this.valueList(stmt.arguments, env, want);
|
|
7131
|
-
this.checkReturn(stmt, declared, types, sources, env);
|
|
7132
|
-
if (this.returnTypes) {
|
|
7133
|
-
this.returnTypes.push(stmt.arguments.length === 0 ? nilType : types.length === 1 ? types[0] : tuple([...types], true));
|
|
7134
|
-
}
|
|
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);
|
|
7135
7512
|
return;
|
|
7136
7513
|
}
|
|
7137
7514
|
case "ExportStatement":
|
|
@@ -7218,7 +7595,7 @@ var TypeAnalyzer = class {
|
|
|
7218
7595
|
* `sources` maps each produced value back to the expression it came from
|
|
7219
7596
|
* (undefined for the 2nd and later values of a multi-value call), so the
|
|
7220
7597
|
* caller can still do contextual typing against the written expression. */
|
|
7221
|
-
valueList(exprs, env
|
|
7598
|
+
valueList(exprs, env) {
|
|
7222
7599
|
const types = [];
|
|
7223
7600
|
const sources = [];
|
|
7224
7601
|
exprs.forEach((e, i) => {
|
|
@@ -7234,31 +7611,29 @@ var TypeAnalyzer = class {
|
|
|
7234
7611
|
}
|
|
7235
7612
|
return;
|
|
7236
7613
|
}
|
|
7237
|
-
|
|
7238
|
-
|
|
7239
|
-
sources.push(e);
|
|
7240
|
-
} while (last && types.length < want);
|
|
7241
|
-
return;
|
|
7242
|
-
}
|
|
7243
|
-
if (last && e.type === "VarargExpression") {
|
|
7244
|
-
do {
|
|
7245
|
-
types.push(t);
|
|
7246
|
-
sources.push(types.length - 1 === i ? e : void 0);
|
|
7247
|
-
} while (types.length < want);
|
|
7614
|
+
types.push(t);
|
|
7615
|
+
sources.push(e);
|
|
7248
7616
|
return;
|
|
7249
7617
|
}
|
|
7250
|
-
|
|
7251
|
-
|
|
7252
|
-
types.push(el);
|
|
7253
|
-
sources.push(j === 0 ? e : void 0);
|
|
7254
|
-
});
|
|
7255
|
-
return;
|
|
7256
|
-
}
|
|
7257
|
-
types.push(t.kind === "tuple" && t.isPack && producesMultipleValues(e) ? t.elements[0] ?? nilType : t);
|
|
7618
|
+
void last;
|
|
7619
|
+
types.push(t);
|
|
7258
7620
|
sources.push(e);
|
|
7259
7621
|
});
|
|
7260
7622
|
return { types, sources };
|
|
7261
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
|
+
}
|
|
7262
7637
|
/** Run `visit` with a fresh place to collect `break` states, and return
|
|
7263
7638
|
* what it collected. */
|
|
7264
7639
|
withBreakScope(visit) {
|
|
@@ -7407,18 +7782,29 @@ var TypeAnalyzer = class {
|
|
|
7407
7782
|
* context. */
|
|
7408
7783
|
contextualArrays = /* @__PURE__ */ new WeakMap();
|
|
7409
7784
|
applyArrayContext(e, expected) {
|
|
7410
|
-
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];
|
|
7411
7791
|
if (!target) return;
|
|
7792
|
+
if (target.kind === "tuple" && e.elements.length) this.tupleArrays.set(e, target);
|
|
7412
7793
|
if (!e.elements.length) {
|
|
7413
7794
|
this.contextualArrays.set(e, target);
|
|
7414
7795
|
return;
|
|
7415
7796
|
}
|
|
7416
7797
|
e.elements.forEach((element, i) => {
|
|
7417
7798
|
if (element.type === "SpreadElement") return;
|
|
7418
|
-
const
|
|
7799
|
+
const tupleTarget = target;
|
|
7800
|
+
const elementType = target.kind === "array" ? target.element : tupleTarget.elements[i] ?? tupleTarget.rest;
|
|
7419
7801
|
this.applyContext(element, elementType);
|
|
7420
7802
|
});
|
|
7421
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();
|
|
7422
7808
|
/** `{ list: [] }` where `{ list: thread[] }` is expected: each field's
|
|
7423
7809
|
* value gets its property's type as context. */
|
|
7424
7810
|
applyTableContext(e, expected) {
|
|
@@ -7474,32 +7860,33 @@ var TypeAnalyzer = class {
|
|
|
7474
7860
|
}
|
|
7475
7861
|
return tuple(target.elements.map((el) => el ? leaf(el.value, el.default) : anyType));
|
|
7476
7862
|
}
|
|
7477
|
-
/**
|
|
7478
|
-
*
|
|
7479
|
-
*
|
|
7480
|
-
|
|
7481
|
-
|
|
7482
|
-
|
|
7483
|
-
|
|
7484
|
-
|
|
7485
|
-
const
|
|
7486
|
-
if (
|
|
7487
|
-
|
|
7488
|
-
|
|
7489
|
-
|
|
7490
|
-
|
|
7491
|
-
|
|
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
|
+
}
|
|
7492
7881
|
/** What each function body being walked declared it returns. */
|
|
7493
7882
|
declaredReturns = [];
|
|
7494
|
-
/** Run `body` with
|
|
7883
|
+
/** Run `body` with `return` as `func` declares it. */
|
|
7495
7884
|
withVarargs(func, body) {
|
|
7496
|
-
this.varargs.push(this.varargElement(func));
|
|
7497
7885
|
this.declaredReturns.push(func.predicate ? booleanType : func.returnType ? this.resolveType(func.returnType) : void 0);
|
|
7498
7886
|
this.contextualReturns.push(this.contextualReturnOf.get(func));
|
|
7499
7887
|
try {
|
|
7500
7888
|
return body();
|
|
7501
7889
|
} finally {
|
|
7502
|
-
this.varargs.pop();
|
|
7503
7890
|
this.declaredReturns.pop();
|
|
7504
7891
|
this.contextualReturns.pop();
|
|
7505
7892
|
}
|
|
@@ -7558,7 +7945,7 @@ var TypeAnalyzer = class {
|
|
|
7558
7945
|
if (type.kind !== "function") return type;
|
|
7559
7946
|
const defaults = {};
|
|
7560
7947
|
for (const generic of generics) {
|
|
7561
|
-
if (generic.default
|
|
7948
|
+
if (generic.default) defaults[generic.name] = this.resolveType(generic.default);
|
|
7562
7949
|
}
|
|
7563
7950
|
return Object.keys(defaults).length ? { ...type, typeParamDefaults: defaults } : type;
|
|
7564
7951
|
}
|
|
@@ -7577,7 +7964,10 @@ var TypeAnalyzer = class {
|
|
|
7577
7964
|
const preserve = keepsLiterals(param) || param.kind !== "typeParam" && containsTypeParam(param);
|
|
7578
7965
|
unify(p.type, preserve ? arg : widen(arg), vars, subst);
|
|
7579
7966
|
});
|
|
7580
|
-
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) {
|
|
7581
7971
|
const keeps = keepsLiterals(f.varargs);
|
|
7582
7972
|
for (let i = f.params.length; i < argTypes.length; i++) {
|
|
7583
7973
|
const arg = argTypes[i];
|
|
@@ -7746,7 +8136,12 @@ var TypeAnalyzer = class {
|
|
|
7746
8136
|
const arg = argTypes[i];
|
|
7747
8137
|
const declared = declaredAt(i);
|
|
7748
8138
|
if (arg === void 0 || declared === void 0 || !containsTypeParam(declared)) continue;
|
|
7749
|
-
|
|
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
|
+
}
|
|
7750
8145
|
if (containsTypeParam(expected) || expected.kind === "any" || expected.kind === "unknown") continue;
|
|
7751
8146
|
if (isAssignable(arg, expected) || isAssignable(widen(arg), expected)) continue;
|
|
7752
8147
|
this.diagnostics.push({
|
|
@@ -7874,13 +8269,13 @@ var TypeAnalyzer = class {
|
|
|
7874
8269
|
type: this.paramType(p, /* @__PURE__ */ new Map()),
|
|
7875
8270
|
optional: p.optional || p.default !== void 0
|
|
7876
8271
|
}));
|
|
7877
|
-
return fn(
|
|
8272
|
+
return this.withRest(fn(
|
|
7878
8273
|
params,
|
|
7879
8274
|
sig.returnType ? this.resolveType(sig.returnType) : sig.predicate ? booleanType : anyType,
|
|
7880
|
-
|
|
8275
|
+
void 0,
|
|
7881
8276
|
names,
|
|
7882
8277
|
this.resolvePredicate(sig.predicate, params)
|
|
7883
|
-
);
|
|
8278
|
+
), sig.params);
|
|
7884
8279
|
}));
|
|
7885
8280
|
}
|
|
7886
8281
|
/** `...rest: T[]` holds every argument from its position on, so its type
|
|
@@ -7888,7 +8283,7 @@ var TypeAnalyzer = class {
|
|
|
7888
8283
|
checkRestType(p, node) {
|
|
7889
8284
|
if (!this.emitDiagnostics || !p.typeAnnotation) return;
|
|
7890
8285
|
const declared = this.resolveType(p.typeAnnotation);
|
|
7891
|
-
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;
|
|
7892
8287
|
this.diagnostics.push({
|
|
7893
8288
|
node,
|
|
7894
8289
|
message: `A rest parameter holds every argument from its position on, so '${p.name ?? "..."}' is an array: '${formatType(declared)}[]', not '${formatType(declared)}'`
|
|
@@ -7944,13 +8339,13 @@ var TypeAnalyzer = class {
|
|
|
7944
8339
|
return collected.length ? union(collected) : this.inferReturnType(func.body, bodyEnv);
|
|
7945
8340
|
}));
|
|
7946
8341
|
}
|
|
7947
|
-
return fn(
|
|
8342
|
+
return this.withRest(fn(
|
|
7948
8343
|
params,
|
|
7949
8344
|
returns,
|
|
7950
|
-
|
|
8345
|
+
void 0,
|
|
7951
8346
|
names,
|
|
7952
8347
|
this.resolvePredicate(func.predicate, params)
|
|
7953
|
-
);
|
|
8348
|
+
), func.params);
|
|
7954
8349
|
});
|
|
7955
8350
|
}
|
|
7956
8351
|
/** Where the return types of the function being walked are collected, so
|
|
@@ -7992,17 +8387,16 @@ var TypeAnalyzer = class {
|
|
|
7992
8387
|
this.preVisitDepth--;
|
|
7993
8388
|
}
|
|
7994
8389
|
}
|
|
7995
|
-
/** The `[key, value]` pairs
|
|
7996
|
-
*
|
|
7997
|
-
*
|
|
7998
|
-
* 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. */
|
|
7999
8393
|
iterationRows(iterNode, iterType) {
|
|
8000
8394
|
let source;
|
|
8001
8395
|
if (iterNode?.type === "CallExpression" && iterNode.callee.type === "Identifier" && iterNode.arguments[0]) {
|
|
8002
|
-
if (iterNode.callee.name !== "pairs"
|
|
8396
|
+
if (iterNode.callee.name !== "pairs") return void 0;
|
|
8003
8397
|
source = this.typeOf.get(iterNode.arguments[0]);
|
|
8004
8398
|
} else {
|
|
8005
|
-
|
|
8399
|
+
return void 0;
|
|
8006
8400
|
}
|
|
8007
8401
|
const t = source && this.expand(source);
|
|
8008
8402
|
if (!t || t.kind !== "object" || t.class || t.indexer || !t.properties.size) return void 0;
|
|
@@ -8143,54 +8537,69 @@ var TypeAnalyzer = class {
|
|
|
8143
8537
|
if (ids.length < 2) return;
|
|
8144
8538
|
this.correlateBindings(env, ids, objects.map((member) => names.map((name) => this.propertyType(member, name))));
|
|
8145
8539
|
}
|
|
8146
|
-
/** `
|
|
8147
|
-
*
|
|
8148
|
-
*
|
|
8149
|
-
|
|
8150
|
-
|
|
8151
|
-
|
|
8152
|
-
|
|
8153
|
-
|
|
8154
|
-
|
|
8155
|
-
|
|
8156
|
-
|
|
8157
|
-
|
|
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") {
|
|
8158
8558
|
if (src.kind === "object") {
|
|
8159
|
-
return [
|
|
8559
|
+
return [tuple([
|
|
8160
8560
|
src.indexer?.key ?? stringType,
|
|
8161
|
-
src.indexer?.value ?? union([...src.properties.values()].map((p) => p.type))
|
|
8162
|
-
|
|
8163
|
-
];
|
|
8561
|
+
src.indexer?.value ?? union([...src.properties.values()].map((p) => p.type))
|
|
8562
|
+
]), iteration];
|
|
8164
8563
|
}
|
|
8165
|
-
if (src.kind === "array") return [numberType, src.element,
|
|
8564
|
+
if (src.kind === "array") return [tuple([numberType, src.element]), iteration];
|
|
8166
8565
|
}
|
|
8167
8566
|
}
|
|
8168
|
-
const
|
|
8169
|
-
|
|
8170
|
-
|
|
8171
|
-
const
|
|
8172
|
-
|
|
8173
|
-
return
|
|
8174
|
-
}
|
|
8175
|
-
const
|
|
8176
|
-
if (
|
|
8177
|
-
|
|
8178
|
-
|
|
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];
|
|
8179
8586
|
if (t.kind === "object") {
|
|
8180
|
-
|
|
8181
|
-
const v = t.indexer?.value ?? union([...t.properties.values()].map((p) => p.type));
|
|
8182
|
-
return varCount >= 2 ? [k, v, false] : [v, unknownType, true];
|
|
8587
|
+
return [t.indexer?.value ?? union([...t.properties.values()].map((p) => p.type)), values];
|
|
8183
8588
|
}
|
|
8184
|
-
|
|
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];
|
|
8185
8596
|
}
|
|
8186
8597
|
inferReturnType(body, env) {
|
|
8187
8598
|
const returns = [];
|
|
8188
8599
|
const walk = (block) => {
|
|
8189
8600
|
for (const s of block.statements) {
|
|
8190
8601
|
if (s.type === "ReturnStatement") {
|
|
8191
|
-
|
|
8192
|
-
else if (s.arguments.length === 1) returns.push(this.infer(s.arguments[0], env));
|
|
8193
|
-
else returns.push(tuple(s.arguments.map((a) => this.infer(a, env)), true));
|
|
8602
|
+
returns.push(s.argument ? this.infer(s.argument, env) : nilType);
|
|
8194
8603
|
} else if (s.type === "IfStatement") {
|
|
8195
8604
|
for (const c of s.clauses) walk(c.body);
|
|
8196
8605
|
if (s.alternate) walk(s.alternate);
|
|
@@ -8212,8 +8621,9 @@ var TypeAnalyzer = class {
|
|
|
8212
8621
|
* `infer` would have widened it to an array type). */
|
|
8213
8622
|
fitsAnnotation(init, declared, inferred, env) {
|
|
8214
8623
|
if (declared.kind === "tuple" && init.type === "ArrayExpression" && !init.elements.some((e) => e.type === "SpreadElement")) {
|
|
8215
|
-
|
|
8216
|
-
|
|
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));
|
|
8217
8627
|
}
|
|
8218
8628
|
if (isAssignable(inferred, declared) || isAssignable(widen(inferred), declared)) return true;
|
|
8219
8629
|
if (init.type === "TableExpression") return isAssignable(this.inferObject(init, env, true), declared);
|
|
@@ -8270,7 +8680,7 @@ var TypeAnalyzer = class {
|
|
|
8270
8680
|
target.elements.forEach((el, i) => {
|
|
8271
8681
|
if (el) this.reassignPattern(el.value, this.withDefault(this.elementType(valueType, i), el.default, env), env);
|
|
8272
8682
|
});
|
|
8273
|
-
if (target.rest) this.reassignPattern(target.rest,
|
|
8683
|
+
if (target.rest) this.reassignPattern(target.rest, this.restAfter(valueType, target.elements.length), env);
|
|
8274
8684
|
return;
|
|
8275
8685
|
}
|
|
8276
8686
|
}
|
|
@@ -8306,7 +8716,7 @@ var TypeAnalyzer = class {
|
|
|
8306
8716
|
if (!el) return;
|
|
8307
8717
|
this.bindPattern(el.value, this.withDefault(this.elementType(valueType, i), el.default, env), env, mode);
|
|
8308
8718
|
});
|
|
8309
|
-
if (target.rest) this.bindPattern(target.rest,
|
|
8719
|
+
if (target.rest) this.bindPattern(target.rest, this.restAfter(valueType, target.elements.length), env, mode);
|
|
8310
8720
|
return;
|
|
8311
8721
|
}
|
|
8312
8722
|
}
|
|
@@ -8463,7 +8873,7 @@ var TypeAnalyzer = class {
|
|
|
8463
8873
|
* not one of them is a mistake worth reporting, rather than the nil Lua
|
|
8464
8874
|
* would hand back. */
|
|
8465
8875
|
checkStringMember(node, object, key) {
|
|
8466
|
-
if (!this.emitDiagnostics) return;
|
|
8876
|
+
if (!this.emitDiagnostics || !this.aliasDefs.has("StringMethods")) return;
|
|
8467
8877
|
const parts = this.stringParts(object);
|
|
8468
8878
|
if (!parts) return;
|
|
8469
8879
|
const keys = (key.kind === "union" ? key.types : [key]).map((m) => this.expand(m));
|
|
@@ -8475,6 +8885,170 @@ var TypeAnalyzer = class {
|
|
|
8475
8885
|
message: names.length === 1 ? `'${names[0]}' does not exist on a string` : `'${briefType(key)}' does not name a member of a string`
|
|
8476
8886
|
});
|
|
8477
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();
|
|
9002
|
+
/** A `private` member read from outside the class that declared it —
|
|
9003
|
+
* a subclass counts as outside, as in TypeScript. */
|
|
9004
|
+
checkPrivateMember(node, object, name) {
|
|
9005
|
+
if (!this.emitDiagnostics) return;
|
|
9006
|
+
const raw = this.expand(object);
|
|
9007
|
+
for (const part of raw.kind === "union" ? raw.types : [raw]) {
|
|
9008
|
+
const t = this.deferredAccess(this.expand(part));
|
|
9009
|
+
if (t.kind !== "object") continue;
|
|
9010
|
+
const access = t.properties.get(name)?.private;
|
|
9011
|
+
if (!access || this.insideClass(access.owner)) continue;
|
|
9012
|
+
if (access.protected && this.insideSubclassOf(access.owner)) continue;
|
|
9013
|
+
this.diagnostics.push({
|
|
9014
|
+
node,
|
|
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}'`
|
|
9016
|
+
});
|
|
9017
|
+
return;
|
|
9018
|
+
}
|
|
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
|
+
}
|
|
9042
|
+
/** Is the code being checked written inside `owner`'s body — its own
|
|
9043
|
+
* methods, or a class or function nested in them? */
|
|
9044
|
+
insideClass(owner) {
|
|
9045
|
+
return this.enclosingClasses.includes(owner);
|
|
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
|
+
}
|
|
8478
9052
|
propertyType(raw, name) {
|
|
8479
9053
|
const t = this.deferredAccess(this.expand(raw));
|
|
8480
9054
|
if (t.kind === "object") {
|
|
@@ -8529,8 +9103,12 @@ var TypeAnalyzer = class {
|
|
|
8529
9103
|
}
|
|
8530
9104
|
if (containsTypeParam(index)) return this.reduceType({ kind: "indexedAccess", objectType: t, indexType: index });
|
|
8531
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
|
+
}
|
|
8532
9110
|
}
|
|
8533
|
-
return unknownType;
|
|
9111
|
+
return index.kind === "any" ? anyType : unknownType;
|
|
8534
9112
|
}
|
|
8535
9113
|
/** What a deferred `T[K]` can be: every property its index could name.
|
|
8536
9114
|
* Reading a member of one, or calling it, sees that. */
|
|
@@ -8540,10 +9118,21 @@ var TypeAnalyzer = class {
|
|
|
8540
9118
|
if (containsTypeParam(index)) return unknownType;
|
|
8541
9119
|
return this.accessType(t.objectType, index);
|
|
8542
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
|
+
}
|
|
8543
9132
|
elementType(raw, index) {
|
|
8544
9133
|
const t = this.expand(raw);
|
|
8545
9134
|
if (t.kind === "array") return t.element;
|
|
8546
|
-
if (t.kind === "tuple") return t.elements[index] ?? unknownType;
|
|
9135
|
+
if (t.kind === "tuple") return t.elements[index] ?? t.rest ?? unknownType;
|
|
8547
9136
|
if (t.kind === "union") return union(t.types.map((m) => this.elementType(m, index)));
|
|
8548
9137
|
if (t.kind === "difference") return this.elementType(t.base, index);
|
|
8549
9138
|
if (t.kind === "typeParam" && t.constraint) return this.elementType(t.constraint, index);
|
|
@@ -8573,8 +9162,6 @@ var TypeAnalyzer = class {
|
|
|
8573
9162
|
return stringType;
|
|
8574
9163
|
}
|
|
8575
9164
|
// `...` holds what the function declared it takes.
|
|
8576
|
-
case "VarargExpression":
|
|
8577
|
-
return this.varargs[this.varargs.length - 1] ?? anyType;
|
|
8578
9165
|
// `f(a, ...rest)` — every value the array holds, one after
|
|
8579
9166
|
// another. Each of them is an element, so that is what the
|
|
8580
9167
|
// parameters it fills are checked against.
|
|
@@ -8607,10 +9194,8 @@ var TypeAnalyzer = class {
|
|
|
8607
9194
|
this.checkParamOrder(expr.func.params, expr);
|
|
8608
9195
|
this.visitFunctionBody(expr.func, env);
|
|
8609
9196
|
return this.inferFunctionBody(expr.func, env);
|
|
8610
|
-
case "ParenthesizedExpression":
|
|
8611
|
-
|
|
8612
|
-
return inner.kind === "tuple" && inner.isPack && producesMultipleValues(expr.expression) ? inner.elements[0] ?? nilType : inner;
|
|
8613
|
-
}
|
|
9197
|
+
case "ParenthesizedExpression":
|
|
9198
|
+
return this.infer(expr.expression, env);
|
|
8614
9199
|
case "TypeAssertionExpression": {
|
|
8615
9200
|
this.infer(expr.expression, env);
|
|
8616
9201
|
return this.resolveType(expr.typeAnnotation);
|
|
@@ -8643,7 +9228,7 @@ var TypeAnalyzer = class {
|
|
|
8643
9228
|
case "not":
|
|
8644
9229
|
return booleanType;
|
|
8645
9230
|
case "-":
|
|
8646
|
-
return this.operatorResult(expr, "-", arg, void 0) ??
|
|
9231
|
+
return this.operatorResult(expr, "-", arg, void 0) ?? this.arithmeticOn([arg]);
|
|
8647
9232
|
case "#":
|
|
8648
9233
|
return this.operatorResult(expr, "#", arg, void 0) ?? numberType;
|
|
8649
9234
|
}
|
|
@@ -8661,7 +9246,11 @@ var TypeAnalyzer = class {
|
|
|
8661
9246
|
const left = this.infer(expr.left, env);
|
|
8662
9247
|
const { whenFalse } = this.narrowFromCondition(expr.left, env);
|
|
8663
9248
|
const right = this.infer(expr.right, whenFalse);
|
|
8664
|
-
|
|
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]);
|
|
8665
9254
|
}
|
|
8666
9255
|
const l = this.infer(expr.left, env);
|
|
8667
9256
|
const r = this.infer(expr.right, env);
|
|
@@ -8672,12 +9261,14 @@ var TypeAnalyzer = class {
|
|
|
8672
9261
|
switch (op) {
|
|
8673
9262
|
case "..":
|
|
8674
9263
|
return this.operatorResult(expr, op, l, r) ?? stringType;
|
|
8675
|
-
case "==":
|
|
8676
|
-
case "~=":
|
|
8677
9264
|
case "<":
|
|
8678
9265
|
case ">":
|
|
8679
9266
|
case "<=":
|
|
8680
9267
|
case ">=":
|
|
9268
|
+
this.checkComparison(expr, op, l, r);
|
|
9269
|
+
return booleanType;
|
|
9270
|
+
case "==":
|
|
9271
|
+
case "~=":
|
|
8681
9272
|
return booleanType;
|
|
8682
9273
|
case "+":
|
|
8683
9274
|
case "-":
|
|
@@ -8686,15 +9277,18 @@ var TypeAnalyzer = class {
|
|
|
8686
9277
|
case "//":
|
|
8687
9278
|
case "%":
|
|
8688
9279
|
case "^":
|
|
8689
|
-
return this.operatorResult(expr, op, l, r) ??
|
|
9280
|
+
return this.operatorResult(expr, op, l, r) ?? this.arithmeticOn([l, r]);
|
|
8690
9281
|
}
|
|
8691
9282
|
return union([l, r]);
|
|
8692
9283
|
}
|
|
8693
9284
|
case "MemberExpression": {
|
|
8694
9285
|
const { type: obj, shortCircuits } = this.chainObject(expr, expr.object, env);
|
|
9286
|
+
this.checkPrivateMember(expr.property, obj, expr.property.name);
|
|
9287
|
+
this.checkAbstractAccess(expr, obj);
|
|
8695
9288
|
const key = this.refKeyOf(expr);
|
|
8696
9289
|
const narrowed = key === void 0 ? void 0 : env.get(key);
|
|
8697
9290
|
this.checkStringMember(expr, obj, literal(expr.property.name));
|
|
9291
|
+
if (narrowed === void 0) this.checkMissingMember(expr.property, obj, expr.property.name);
|
|
8698
9292
|
return this.chainResult(expr, narrowed ?? this.propertyType(obj, expr.property.name), shortCircuits);
|
|
8699
9293
|
}
|
|
8700
9294
|
case "IndexExpression": {
|
|
@@ -8703,6 +9297,7 @@ var TypeAnalyzer = class {
|
|
|
8703
9297
|
const key = this.refKeyOf(expr);
|
|
8704
9298
|
const narrowed = key === void 0 ? void 0 : env.get(key);
|
|
8705
9299
|
this.checkStringMember(expr, obj, this.expand(idx));
|
|
9300
|
+
if (this.checkIndexKey(expr, obj, idx)) return this.chainResult(expr, anyType, shortCircuits);
|
|
8706
9301
|
const member = this.stringMember(obj, this.expand(idx));
|
|
8707
9302
|
if (member) return this.chainResult(expr, member, shortCircuits);
|
|
8708
9303
|
return this.chainResult(expr, narrowed ?? this.indexedType(obj, idx), shortCircuits);
|
|
@@ -8712,8 +9307,6 @@ var TypeAnalyzer = class {
|
|
|
8712
9307
|
const { type: callee, shortCircuits } = this.chainObject(expr, expr.callee, env);
|
|
8713
9308
|
return this.chainResult(expr, this.inferCall(expr, callee, env), shortCircuits);
|
|
8714
9309
|
}
|
|
8715
|
-
case "NewExpression":
|
|
8716
|
-
return this.inferNew(expr, env);
|
|
8717
9310
|
case "ClassExpression":
|
|
8718
9311
|
return this.visitClass(expr, env);
|
|
8719
9312
|
case "SuperExpression": {
|
|
@@ -8750,18 +9343,14 @@ var TypeAnalyzer = class {
|
|
|
8750
9343
|
/** `new Name(args)` is `Name.new(args)` — the same function, and the
|
|
8751
9344
|
* same check. Saying so here rather than rewriting the tree keeps the
|
|
8752
9345
|
* error messages pointing at what was written. */
|
|
8753
|
-
|
|
8754
|
-
|
|
8755
|
-
const
|
|
8756
|
-
if (
|
|
8757
|
-
|
|
8758
|
-
|
|
8759
|
-
|
|
8760
|
-
|
|
8761
|
-
for (const argument of expr.arguments) this.infer(argument, env);
|
|
8762
|
-
return anyType;
|
|
8763
|
-
}
|
|
8764
|
-
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;
|
|
8765
9354
|
}
|
|
8766
9355
|
/** `super(...)` — the base constructor, run on the instance being built. */
|
|
8767
9356
|
inferSuperCall(expr, env) {
|
|
@@ -8785,8 +9374,9 @@ var TypeAnalyzer = class {
|
|
|
8785
9374
|
this.inferCall(expr, callable, env);
|
|
8786
9375
|
return nilType;
|
|
8787
9376
|
}
|
|
8788
|
-
inferCall(expr,
|
|
9377
|
+
inferCall(expr, called, env) {
|
|
8789
9378
|
this.checkAmbiguousCall(expr);
|
|
9379
|
+
const callee = this.callMetamethod(called) ?? called;
|
|
8790
9380
|
const united = this.unionSignatures(callee);
|
|
8791
9381
|
const fns = united ?? this.overloadsOf(callee);
|
|
8792
9382
|
const explicit = this.explicitTypeArguments(expr, fns);
|
|
@@ -8809,8 +9399,31 @@ var TypeAnalyzer = class {
|
|
|
8809
9399
|
if (arityFits && !picked) this.reportArguments(expr, expr.arguments, fns, () => argTypes, () => 0);
|
|
8810
9400
|
return union(fns.map((f) => this.callReturn(f, argTypes, explicit)));
|
|
8811
9401
|
}
|
|
9402
|
+
this.checkCallable(expr.callee, callee);
|
|
8812
9403
|
return callee.kind === "any" ? anyType : unknownType;
|
|
8813
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();
|
|
8814
9427
|
/** A `(` on a line of its own continues the statement above it:
|
|
8815
9428
|
*
|
|
8816
9429
|
* const value = map[key]
|
|
@@ -8827,6 +9440,9 @@ var TypeAnalyzer = class {
|
|
|
8827
9440
|
});
|
|
8828
9441
|
}
|
|
8829
9442
|
inferMethodCall(expr, objType, env) {
|
|
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);
|
|
8830
9446
|
const method = this.propertyType(objType, expr.method.name);
|
|
8831
9447
|
const united = this.unionSignatures(method);
|
|
8832
9448
|
const fns = united ?? this.overloadsOf(method);
|
|
@@ -8860,6 +9476,7 @@ var TypeAnalyzer = class {
|
|
|
8860
9476
|
if (arityFits && !picked) this.reportArguments(expr, expr.arguments, fns, withSelf, selfOf);
|
|
8861
9477
|
return union(fns.map((f) => this.callReturn(f, withSelf(f), explicit)));
|
|
8862
9478
|
}
|
|
9479
|
+
this.checkCallable(expr.method, method);
|
|
8863
9480
|
return objType.kind === "any" ? anyType : unknownType;
|
|
8864
9481
|
}
|
|
8865
9482
|
// --------------------------------------------------------
|
|
@@ -8920,6 +9537,8 @@ var TypeAnalyzer = class {
|
|
|
8920
9537
|
if (id !== void 0 && this.scopes.bindings.get(id)?.declaredBy === "namespace") return;
|
|
8921
9538
|
}
|
|
8922
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;
|
|
8923
9542
|
this.diagnostics.push({
|
|
8924
9543
|
node: target,
|
|
8925
9544
|
message: `Cannot assign to '${name}' because it is a read-only property`
|
|
@@ -8979,6 +9598,8 @@ var TypeAnalyzer = class {
|
|
|
8979
9598
|
inferArray(expr, env, asConst) {
|
|
8980
9599
|
const contextual = this.contextualArrays.get(expr);
|
|
8981
9600
|
if (contextual && !asConst) return contextual;
|
|
9601
|
+
const wantedTuple = this.tupleArrays.get(expr);
|
|
9602
|
+
if (wantedTuple && !asConst) return this.inferTupleLiteral(expr, env, wantedTuple);
|
|
8982
9603
|
const elems = [];
|
|
8983
9604
|
let hadSpread = false;
|
|
8984
9605
|
for (const el of expr.elements) {
|
|
@@ -8998,6 +9619,28 @@ var TypeAnalyzer = class {
|
|
|
8998
9619
|
return asConst || !element || element.type === "SpreadElement" ? t : this.widenUnlessAsked(t, element);
|
|
8999
9620
|
})) : unknownType);
|
|
9000
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
|
+
}
|
|
9001
9644
|
/** A literal written inside a fresh table or array widens — `{ n = 1 }` is
|
|
9002
9645
|
* `{ n: number }` — unless the surroundings said a literal belongs there.
|
|
9003
9646
|
* `request({ Method: "GET" })` keeps `"GET"` when `Method` is a union of
|
|
@@ -9083,7 +9726,7 @@ var TypeAnalyzer = class {
|
|
|
9083
9726
|
case "tuple": {
|
|
9084
9727
|
const tupleContext = ctx && this.membersOf(ctx).find((m) => m.kind === "tuple");
|
|
9085
9728
|
if (tupleContext?.kind === "tuple") {
|
|
9086
|
-
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);
|
|
9087
9730
|
}
|
|
9088
9731
|
const arrayContext = ctx && this.membersOf(ctx).find((m) => m.kind === "array");
|
|
9089
9732
|
const element = arrayContext?.kind === "array" ? arrayContext.element : void 0;
|
|
@@ -9340,7 +9983,8 @@ var TypeAnalyzer = class {
|
|
|
9340
9983
|
for (const f of candidates) {
|
|
9341
9984
|
if (!f.predicate) continue;
|
|
9342
9985
|
const arg = args[f.predicate.param];
|
|
9343
|
-
|
|
9986
|
+
const onCondition = f.predicate.asserts && !f.predicate.type;
|
|
9987
|
+
if (!arg || this.refKeyOf(arg) === void 0 && !onCondition) continue;
|
|
9344
9988
|
if (f.typeParams?.length && f.predicate.type) {
|
|
9345
9989
|
const subst = this.inferTypeArgs(f, argTypes);
|
|
9346
9990
|
return {
|
|
@@ -9361,6 +10005,10 @@ var TypeAnalyzer = class {
|
|
|
9361
10005
|
const found = this.predicateCallTarget(call, env);
|
|
9362
10006
|
if (!found || !found.predicate.asserts) return;
|
|
9363
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
|
+
}
|
|
9364
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) });
|
|
9365
10013
|
}
|
|
9366
10014
|
// --------------------------------------------------------
|
|
@@ -9385,6 +10033,10 @@ var TypeAnalyzer = class {
|
|
|
9385
10033
|
if (base === void 0) return void 0;
|
|
9386
10034
|
if (expr.index.type === "StringLiteral") return `${base}.${expr.index.value}`;
|
|
9387
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
|
+
}
|
|
9388
10040
|
return void 0;
|
|
9389
10041
|
}
|
|
9390
10042
|
default:
|
|
@@ -9480,26 +10132,45 @@ var TypeAnalyzer = class {
|
|
|
9480
10132
|
* that copied it no longer holds that value: forget the alias. */
|
|
9481
10133
|
unalias(key) {
|
|
9482
10134
|
for (const k of [...this.refAliases.keys()]) {
|
|
9483
|
-
if (k !== key && !k
|
|
10135
|
+
if (k !== key && !isBelow(k, key) && !k.includes(`[${key}]`)) continue;
|
|
9484
10136
|
for (const other of this.refAliases.get(k) ?? []) this.refAliases.get(other)?.delete(k);
|
|
9485
10137
|
this.refAliases.delete(k);
|
|
9486
10138
|
}
|
|
9487
10139
|
}
|
|
9488
|
-
/** 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. */
|
|
9489
10143
|
invalidateBelow(env, key) {
|
|
10144
|
+
const asIndex = `[${key}]`;
|
|
9490
10145
|
for (const k of [...env.keys()]) {
|
|
9491
|
-
if (k
|
|
10146
|
+
if (isBelow(k, key) || k.includes(asIndex)) env.delete(k);
|
|
9492
10147
|
}
|
|
9493
10148
|
}
|
|
9494
10149
|
/** An assignment through a reference invalidates it and everything under
|
|
9495
10150
|
* it, then records the assigned type. */
|
|
9496
10151
|
assignToRef(expr, value, env) {
|
|
10152
|
+
this.invalidateSiblings(expr, env);
|
|
9497
10153
|
const key = this.refKeyOf(expr);
|
|
9498
10154
|
if (key === void 0) return;
|
|
9499
10155
|
this.invalidateBelow(env, key);
|
|
9500
10156
|
this.unalias(key);
|
|
9501
10157
|
env.set(key, value);
|
|
9502
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
|
+
}
|
|
9503
10174
|
// --------------------------------------------------------
|
|
9504
10175
|
// Small helpers
|
|
9505
10176
|
// --------------------------------------------------------
|
|
@@ -9514,6 +10185,35 @@ var TypeAnalyzer = class {
|
|
|
9514
10185
|
this.selfType = saved;
|
|
9515
10186
|
}
|
|
9516
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
|
+
}
|
|
9517
10217
|
/** What an operator on a value with metamethods gives: `a + b` calls
|
|
9518
10218
|
* `__add` on `a`, or failing that on `b` with the operands swapped — the
|
|
9519
10219
|
* order Luau tries them in. That is how `Vector3 + Vector3`, `CFrame *
|
|
@@ -9838,7 +10538,7 @@ function briefType(t) {
|
|
|
9838
10538
|
}
|
|
9839
10539
|
|
|
9840
10540
|
// src/project/host.ts
|
|
9841
|
-
import { readFileSync, statSync } from "fs";
|
|
10541
|
+
import { readdirSync, readFileSync, statSync } from "fs";
|
|
9842
10542
|
var nodeHost = {
|
|
9843
10543
|
readFile(path) {
|
|
9844
10544
|
try {
|
|
@@ -9846,6 +10546,13 @@ var nodeHost = {
|
|
|
9846
10546
|
} catch {
|
|
9847
10547
|
return void 0;
|
|
9848
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
|
+
}
|
|
9849
10556
|
}
|
|
9850
10557
|
};
|
|
9851
10558
|
|
|
@@ -10037,13 +10744,31 @@ function resolveTypeLibraries(config, host = nodeHost) {
|
|
|
10037
10744
|
else problems.push({ file: config.path, message: `'${entry}' has no ${ENTRY_FILE} (or 'tilua.types' in its package.json)`, ...entryPosition(config, entry) });
|
|
10038
10745
|
continue;
|
|
10039
10746
|
}
|
|
10040
|
-
|
|
10041
|
-
|
|
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);
|
|
10042
10767
|
if (found) addPackage(found.directory, found.file, /* @__PURE__ */ new Set());
|
|
10043
10768
|
else {
|
|
10044
10769
|
problems.push({
|
|
10045
10770
|
file: config.path,
|
|
10046
|
-
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]}`,
|
|
10047
10772
|
...entryPosition(config, entry)
|
|
10048
10773
|
});
|
|
10049
10774
|
}
|
|
@@ -10080,6 +10805,32 @@ function findPackage(name, from, host) {
|
|
|
10080
10805
|
directory = parent;
|
|
10081
10806
|
}
|
|
10082
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
|
+
}
|
|
10083
10834
|
function dependencyNames(directory, host) {
|
|
10084
10835
|
const manifest = readJson(join2(directory, "package.json"), host);
|
|
10085
10836
|
const names = /* @__PURE__ */ new Set();
|
|
@@ -10178,7 +10929,8 @@ function sourceMapTypes(text, path, options) {
|
|
|
10178
10929
|
for (const child of node.children ?? []) {
|
|
10179
10930
|
if (!isNode(child)) continue;
|
|
10180
10931
|
const childAlias = visit(child, [...segments, child.name], alias);
|
|
10181
|
-
|
|
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;
|
|
10182
10934
|
named.add(child.name);
|
|
10183
10935
|
members.push(`${child.name}: ${childAlias}`);
|
|
10184
10936
|
}
|
|
@@ -10238,6 +10990,7 @@ export {
|
|
|
10238
10990
|
BinaryOperators,
|
|
10239
10991
|
CONFIG_FILE_NAMES,
|
|
10240
10992
|
Keywords,
|
|
10993
|
+
LANGUAGE_GLOBALS,
|
|
10241
10994
|
LexError,
|
|
10242
10995
|
Operators,
|
|
10243
10996
|
PRELUDE_SOURCE,
|
|
@@ -10306,6 +11059,7 @@ export {
|
|
|
10306
11059
|
tilua,
|
|
10307
11060
|
tokenize,
|
|
10308
11061
|
tuple,
|
|
11062
|
+
tupleMembers,
|
|
10309
11063
|
typeParam,
|
|
10310
11064
|
unify,
|
|
10311
11065
|
union,
|