@tilua/parser 1.1.0 → 2.1.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 +186 -47
- package/dist/index.cjs +1405 -455
- package/dist/index.d.cts +107 -64
- package/dist/index.d.ts +107 -64
- package/dist/index.js +1404 -456
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -595,6 +595,9 @@ function applyDirectives(directives, diagnostics, lineOf) {
|
|
|
595
595
|
var UNUSED_EXPECT_ERROR = "Unused '@tilua-expect-error' directive";
|
|
596
596
|
|
|
597
597
|
// src/ast/builders.ts
|
|
598
|
+
var NO_DOTS = "tilua has no bare '...': take a rest parameter, '...args: T[]', or 'scriptArgs' for the script's own";
|
|
599
|
+
var ACCESSIBILITIES = /* @__PURE__ */ new Set(["public", "private", "protected"]);
|
|
600
|
+
var CLASS_MODIFIERS = /* @__PURE__ */ new Set([...ACCESSIBILITIES, "static", "abstract", "override", "readonly"]);
|
|
598
601
|
function bindThis(func, at) {
|
|
599
602
|
bindThisParam(func.params, at);
|
|
600
603
|
func.isMethod = true;
|
|
@@ -862,11 +865,16 @@ var Parser = class {
|
|
|
862
865
|
return this.attempt(() => this.parseExpression(), stop, (start, from) => this.errorExpression(start, from));
|
|
863
866
|
}
|
|
864
867
|
/** A comma-separated list of values: a `return`'s, a declaration's, an
|
|
865
|
-
* assignment's.
|
|
866
|
-
* arguments
|
|
868
|
+
* assignment's. Each is one value; a spread belongs in a call's
|
|
869
|
+
* arguments or an array, and is reported here — then read as one, so
|
|
870
|
+
* the rest still means something. */
|
|
867
871
|
expressionListOr(stop) {
|
|
868
872
|
const until = () => stop() || this.checkPunctuator(",");
|
|
869
|
-
const item = () =>
|
|
873
|
+
const item = () => {
|
|
874
|
+
if (!(this.checkOperator("...") && this.startsSpread())) return this.expressionOr(until);
|
|
875
|
+
this.problem("A spread goes in a call's arguments or an array: take values out of an array with a destructuring, 'const [a, b] = xs'");
|
|
876
|
+
return this.parseSpreadArgument(until);
|
|
877
|
+
};
|
|
870
878
|
const list = [item()];
|
|
871
879
|
while (this.matchPunctuator(",")) list.push(item());
|
|
872
880
|
return list;
|
|
@@ -1231,7 +1239,7 @@ var Parser = class {
|
|
|
1231
1239
|
if (t.type === "Identifier" && t.value === "type" && this.peek(1).type === "Identifier") {
|
|
1232
1240
|
return this.parseTypeAliasStatement();
|
|
1233
1241
|
}
|
|
1234
|
-
if (
|
|
1242
|
+
if (this.startsClassDeclaration()) {
|
|
1235
1243
|
return this.parseClassDeclaration();
|
|
1236
1244
|
}
|
|
1237
1245
|
if (t.type === "Identifier" && t.value === "declare") {
|
|
@@ -1268,8 +1276,6 @@ var Parser = class {
|
|
|
1268
1276
|
type: "FunctionTypeNode",
|
|
1269
1277
|
generics: head.generics,
|
|
1270
1278
|
params,
|
|
1271
|
-
hasVarargs: head.hasVarargs,
|
|
1272
|
-
varargType: head.varargTypeAnnotation,
|
|
1273
1279
|
returnType: head.returnType ?? { type: "TypeReference", base: head.predicate ? "boolean" : "unknown", typeArguments: [], ...spanFrom(start, start) },
|
|
1274
1280
|
predicate: head.predicate,
|
|
1275
1281
|
...spanFrom(start, this.previous())
|
|
@@ -1408,7 +1414,7 @@ var Parser = class {
|
|
|
1408
1414
|
this.advance();
|
|
1409
1415
|
if (this.checkIdentifierValue("default")) {
|
|
1410
1416
|
this.advance();
|
|
1411
|
-
const declaration = this.
|
|
1417
|
+
const declaration = this.startsClassDeclaration() ? this.parseClassDeclaration() : this.parseExpression(0);
|
|
1412
1418
|
return { type: "ExportDefaultStatement", declaration, ...spanFrom(start, this.previous()) };
|
|
1413
1419
|
}
|
|
1414
1420
|
if (this.checkIdentifierValue("type") && this.peek(1).type === "Identifier") {
|
|
@@ -1419,7 +1425,7 @@ var Parser = class {
|
|
|
1419
1425
|
const declaration = this.parseVariableDeclaration();
|
|
1420
1426
|
return { type: "ExportStatement", declaration, ...spanFrom(start, this.previous()) };
|
|
1421
1427
|
}
|
|
1422
|
-
if (this.
|
|
1428
|
+
if (this.startsClassDeclaration()) {
|
|
1423
1429
|
const declaration = this.parseClassDeclaration();
|
|
1424
1430
|
return { type: "ExportStatement", declaration, ...spanFrom(start, this.previous()) };
|
|
1425
1431
|
}
|
|
@@ -1534,9 +1540,11 @@ var Parser = class {
|
|
|
1534
1540
|
const start = this.current();
|
|
1535
1541
|
this.expectKeyword("for");
|
|
1536
1542
|
this.expectPunctuator("(");
|
|
1543
|
+
const declared = this.checkKeyword("const") || this.checkKeyword("let") ? this.advance().value : void 0;
|
|
1537
1544
|
const first = this.parseBindingTarget(true);
|
|
1538
1545
|
const untilDo = () => this.checkPunctuator(")");
|
|
1539
1546
|
if (first.type === "IdentifierPattern" && this.matchOperator("=")) {
|
|
1547
|
+
if (declared) this.problemAt(start, `A counting loop names its variable without '${declared}': 'for (i = 1, 10)'`);
|
|
1540
1548
|
const from = this.expressionOr(() => untilDo() || this.checkPunctuator(","));
|
|
1541
1549
|
this.expectPunctuator(",");
|
|
1542
1550
|
const to = this.expressionOr(() => untilDo() || this.checkPunctuator(","));
|
|
@@ -1555,17 +1563,24 @@ var Parser = class {
|
|
|
1555
1563
|
...spanFrom(start, this.previous())
|
|
1556
1564
|
};
|
|
1557
1565
|
}
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1566
|
+
if (this.checkPunctuator(",")) {
|
|
1567
|
+
this.problemAt(this.current(), "A loop takes one value each time: take its parts with a destructuring, 'for (const [k, v] in pairs(t))'");
|
|
1568
|
+
while (this.matchPunctuator(",")) this.parseBindingTarget(true);
|
|
1569
|
+
} else if (!declared) {
|
|
1570
|
+
this.problemAt(start, "A loop over a source names its value with 'const' or 'let': 'for (const item in source)'");
|
|
1561
1571
|
}
|
|
1562
1572
|
this.expectKeyword("in");
|
|
1563
|
-
const
|
|
1573
|
+
const iterator = this.expressionOr(() => untilDo() || this.checkPunctuator(","));
|
|
1574
|
+
if (this.checkPunctuator(",")) {
|
|
1575
|
+
this.problemAt(this.current(), "A loop has one source: an array, a table, an iterator function or an iteration '[step, state, first]'");
|
|
1576
|
+
while (this.matchPunctuator(",")) this.expressionOr(() => untilDo() || this.checkPunctuator(","));
|
|
1577
|
+
}
|
|
1564
1578
|
const body = this.parseForBody();
|
|
1565
1579
|
return {
|
|
1566
1580
|
type: "GenericForStatement",
|
|
1567
|
-
|
|
1568
|
-
|
|
1581
|
+
kind: declared ?? "const",
|
|
1582
|
+
variable: first,
|
|
1583
|
+
iterator,
|
|
1569
1584
|
body,
|
|
1570
1585
|
...spanFrom(start, this.previous())
|
|
1571
1586
|
};
|
|
@@ -1635,7 +1650,10 @@ var Parser = class {
|
|
|
1635
1650
|
* the compiler must not accept it, and recorded inside. The message says
|
|
1636
1651
|
* what is wrong on its own — no token is appended. */
|
|
1637
1652
|
problem(message) {
|
|
1638
|
-
|
|
1653
|
+
this.problemAt(this.current(), message);
|
|
1654
|
+
}
|
|
1655
|
+
/** `problem`, pointed at `t` rather than at where parsing has got to. */
|
|
1656
|
+
problemAt(t, message) {
|
|
1639
1657
|
const error = new ParseError(message, t.line.start, t.column.start);
|
|
1640
1658
|
if (!this.recover) throw error;
|
|
1641
1659
|
this.record(error);
|
|
@@ -1648,10 +1666,13 @@ var Parser = class {
|
|
|
1648
1666
|
* like a function (`function m() ... end`). */
|
|
1649
1667
|
parseClassDeclaration() {
|
|
1650
1668
|
const start = this.current();
|
|
1669
|
+
const isAbstract = this.checkIdentifierValue("abstract");
|
|
1670
|
+
if (isAbstract) this.advance();
|
|
1651
1671
|
this.advance();
|
|
1652
1672
|
const name = this.parseIdentifier();
|
|
1653
1673
|
const typeParams = this.checkOperator("<") ? this.parseGenericTypeParameterList() : [];
|
|
1654
1674
|
const { superclass, superArguments } = this.parseExtends();
|
|
1675
|
+
const shapes = this.parseImplements();
|
|
1655
1676
|
const members = this.parseClassBody(start);
|
|
1656
1677
|
return {
|
|
1657
1678
|
type: "ClassDeclaration",
|
|
@@ -1659,10 +1680,21 @@ var Parser = class {
|
|
|
1659
1680
|
typeParams,
|
|
1660
1681
|
superclass,
|
|
1661
1682
|
superArguments,
|
|
1683
|
+
...shapes ? { implements: shapes } : {},
|
|
1684
|
+
...isAbstract ? { isAbstract } : {},
|
|
1662
1685
|
members,
|
|
1663
1686
|
...spanFrom(start, this.previous())
|
|
1664
1687
|
};
|
|
1665
1688
|
}
|
|
1689
|
+
/** `class Name` or `abstract class Name` — all three words soft, so a name
|
|
1690
|
+
* has to follow for either to be a declaration: `class: T`, `class = 1`
|
|
1691
|
+
* and `abstract(x)` are names. */
|
|
1692
|
+
startsClassDeclaration() {
|
|
1693
|
+
const offset = this.checkIdentifierValue("abstract") ? 1 : 0;
|
|
1694
|
+
const word = this.peek(offset);
|
|
1695
|
+
if (word.type !== "Identifier" || word.value !== "class") return false;
|
|
1696
|
+
return this.peek(offset + 1).type === "Identifier" && !this.punctuatorAt(offset + 2, ":") && !this.operatorAt(offset + 2, "=") && !this.punctuatorAt(offset + 2, "(");
|
|
1697
|
+
}
|
|
1666
1698
|
/** `class ... end` as a value. It may be named — the name is for the class
|
|
1667
1699
|
* itself, not for the scope around it — and takes no type parameters,
|
|
1668
1700
|
* since nothing could write the arguments. */
|
|
@@ -1674,8 +1706,25 @@ var Parser = class {
|
|
|
1674
1706
|
this.error("A class written as a value takes no type parameters: nothing could write the arguments");
|
|
1675
1707
|
}
|
|
1676
1708
|
const { superclass, superArguments } = this.parseExtends();
|
|
1709
|
+
const shapes = this.parseImplements();
|
|
1677
1710
|
const members = this.parseClassBody(start);
|
|
1678
|
-
return {
|
|
1711
|
+
return {
|
|
1712
|
+
type: "ClassExpression",
|
|
1713
|
+
name,
|
|
1714
|
+
superclass,
|
|
1715
|
+
superArguments,
|
|
1716
|
+
...shapes ? { implements: shapes } : {},
|
|
1717
|
+
members,
|
|
1718
|
+
...spanFrom(start, this.previous())
|
|
1719
|
+
};
|
|
1720
|
+
}
|
|
1721
|
+
/** `implements Shape, Named` — each a type, checked against the instance. */
|
|
1722
|
+
parseImplements() {
|
|
1723
|
+
if (!this.checkIdentifierValue("implements")) return void 0;
|
|
1724
|
+
this.advance();
|
|
1725
|
+
const shapes = [this.parseType()];
|
|
1726
|
+
while (this.matchPunctuator(",")) shapes.push(this.parseType());
|
|
1727
|
+
return shapes;
|
|
1679
1728
|
}
|
|
1680
1729
|
/** `extends Base` / `extends Box<number>`. */
|
|
1681
1730
|
parseExtends() {
|
|
@@ -1728,23 +1777,66 @@ var Parser = class {
|
|
|
1728
1777
|
parseClassMember() {
|
|
1729
1778
|
const start = this.current();
|
|
1730
1779
|
let accessibility;
|
|
1731
|
-
|
|
1732
|
-
|
|
1780
|
+
const modifiers = /* @__PURE__ */ new Set();
|
|
1781
|
+
while (this.startsClassModifier()) {
|
|
1782
|
+
const token = this.advance();
|
|
1783
|
+
const word = token.value;
|
|
1784
|
+
if (ACCESSIBILITIES.has(word)) {
|
|
1785
|
+
if (accessibility) this.problemAt(token, `A member has one accessibility: '${accessibility}' is already written`);
|
|
1786
|
+
accessibility = word;
|
|
1787
|
+
} else {
|
|
1788
|
+
if (modifiers.has(word)) this.problemAt(token, `'${word}' is written twice`);
|
|
1789
|
+
modifiers.add(word);
|
|
1790
|
+
}
|
|
1733
1791
|
}
|
|
1734
|
-
const member = this.
|
|
1735
|
-
if (!
|
|
1792
|
+
const member = this.parseClassMemberAfterModifiers(start, modifiers);
|
|
1793
|
+
if (!member) return member;
|
|
1794
|
+
const misplaced = (message) => this.problemAt(start, message);
|
|
1736
1795
|
if (member.type === "ClassConstructor") {
|
|
1737
|
-
|
|
1796
|
+
const written = [...accessibility ? [accessibility] : [], ...modifiers];
|
|
1797
|
+
if (written.length) misplaced(`A constructor cannot be '${written[0]}'`);
|
|
1738
1798
|
return member;
|
|
1739
1799
|
}
|
|
1740
|
-
|
|
1800
|
+
if (modifiers.has("readonly") && member.type !== "ClassField") misplaced("Only a field can be 'readonly'");
|
|
1801
|
+
if (modifiers.has("abstract") && member.type !== "ClassMethod") misplaced("Only a method can be 'abstract'");
|
|
1802
|
+
if (modifiers.has("abstract") && member.isStatic) misplaced("A static method cannot be 'abstract'");
|
|
1803
|
+
if (modifiers.has("override") && member.isStatic) misplaced("A static member cannot be 'override'");
|
|
1804
|
+
return {
|
|
1805
|
+
...member,
|
|
1806
|
+
...accessibility ? { accessibility } : {},
|
|
1807
|
+
...modifiers.has("readonly") && member.type === "ClassField" ? { isReadonly: true } : {},
|
|
1808
|
+
...modifiers.has("override") ? { isOverride: true } : {}
|
|
1809
|
+
};
|
|
1741
1810
|
}
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1811
|
+
/** Is the word here a member modifier, rather than a member's name? */
|
|
1812
|
+
startsClassModifier() {
|
|
1813
|
+
const t = this.current();
|
|
1814
|
+
if (t.type !== "Identifier" || !CLASS_MODIFIERS.has(t.value)) return false;
|
|
1815
|
+
const next = this.peek(1);
|
|
1816
|
+
return next.type === "Identifier" || next.type === "Keyword" && next.value === "function";
|
|
1817
|
+
}
|
|
1818
|
+
parseClassMemberAfterModifiers(start, modifiers) {
|
|
1819
|
+
const isStatic = modifiers.has("static");
|
|
1745
1820
|
if (this.checkKeyword("function")) {
|
|
1746
1821
|
this.advance();
|
|
1747
1822
|
const memberName = this.parseIdentifier();
|
|
1823
|
+
if (modifiers.has("abstract")) {
|
|
1824
|
+
const head = this.parseFunctionHead();
|
|
1825
|
+
const func = {
|
|
1826
|
+
type: "FunctionBody",
|
|
1827
|
+
generics: head.generics,
|
|
1828
|
+
params: head.params,
|
|
1829
|
+
hasVarargs: head.hasVarargs,
|
|
1830
|
+
returnType: head.returnType,
|
|
1831
|
+
predicate: head.predicate,
|
|
1832
|
+
body: { type: "Block", statements: [], ...spanFrom(head.start, this.previous()) },
|
|
1833
|
+
...spanFrom(head.start, this.previous())
|
|
1834
|
+
};
|
|
1835
|
+
if (!isStatic) bindThis(func, start);
|
|
1836
|
+
if (!head.returnType) this.problem("An abstract method has no body to infer from: write its return type");
|
|
1837
|
+
if (this.checkPunctuator("{")) this.problem("An abstract method has no body");
|
|
1838
|
+
return { type: "ClassMethod", name: memberName, isStatic, func, isAbstract: true, ...spanFrom(start, this.previous()) };
|
|
1839
|
+
}
|
|
1748
1840
|
const signatures = [];
|
|
1749
1841
|
let written = memberName;
|
|
1750
1842
|
while (true) {
|
|
@@ -1862,12 +1954,17 @@ var Parser = class {
|
|
|
1862
1954
|
parseReturnStatement(stopAtNewline = false) {
|
|
1863
1955
|
const start = this.current();
|
|
1864
1956
|
this.expectKeyword("return");
|
|
1865
|
-
let
|
|
1957
|
+
let argument;
|
|
1866
1958
|
const pastLine = () => stopAtNewline && this.current().line.start > start.line.start;
|
|
1867
1959
|
if (!pastLine() && this.isExpressionStart()) {
|
|
1868
|
-
|
|
1960
|
+
const values = this.expressionListOr(pastLine);
|
|
1961
|
+
argument = values[0];
|
|
1962
|
+
if (values.length > 1) {
|
|
1963
|
+
this.problemAt(start, "A function returns one value: return several as an array, 'return [a, b]'");
|
|
1964
|
+
argument = { type: "ArrayExpression", elements: values, ...spanFrom(values[0], values[values.length - 1]) };
|
|
1965
|
+
}
|
|
1869
1966
|
}
|
|
1870
|
-
return { type: "ReturnStatement",
|
|
1967
|
+
return { type: "ReturnStatement", argument, ...spanFrom(start, this.previous()) };
|
|
1871
1968
|
}
|
|
1872
1969
|
parseTypeAliasStatement() {
|
|
1873
1970
|
const start = this.current();
|
|
@@ -1926,7 +2023,7 @@ var Parser = class {
|
|
|
1926
2023
|
...spanFrom(start, this.previous())
|
|
1927
2024
|
};
|
|
1928
2025
|
}
|
|
1929
|
-
if (first.type === "CallExpression" || first.type === "MethodCallExpression"
|
|
2026
|
+
if (first.type === "CallExpression" || first.type === "MethodCallExpression") {
|
|
1930
2027
|
return { type: "CallStatement", expression: first, ...spanFrom(start, this.previous()) };
|
|
1931
2028
|
}
|
|
1932
2029
|
let expression = first;
|
|
@@ -1979,6 +2076,15 @@ var Parser = class {
|
|
|
1979
2076
|
}
|
|
1980
2077
|
return false;
|
|
1981
2078
|
}
|
|
2079
|
+
/** A string written straight before a method call — `"a":upper()`, or
|
|
2080
|
+
* `` `{n}`:rep(2) ``. Luau only takes `("a"):upper()`; the compiler adds
|
|
2081
|
+
* the parentheses. Anything else after a string (`"a".x`, `"a"(1)`) is
|
|
2082
|
+
* not a prefix expression here either. */
|
|
2083
|
+
startsStringMethodCall() {
|
|
2084
|
+
const t = this.current();
|
|
2085
|
+
const isString = t.type === "InterpolatedString" || t.type === "Literal" && t.kind === "string";
|
|
2086
|
+
return isString && this.punctuatorAt(1, ":") && this.startsMethodCall(1);
|
|
2087
|
+
}
|
|
1982
2088
|
/** Does the next token start right where the current one ends? */
|
|
1983
2089
|
touchesNext() {
|
|
1984
2090
|
const current = this.current();
|
|
@@ -2165,6 +2271,7 @@ var Parser = class {
|
|
|
2165
2271
|
}
|
|
2166
2272
|
parseAtom() {
|
|
2167
2273
|
const t = this.current();
|
|
2274
|
+
if (this.startsStringMethodCall()) return this.parsePrefixExpression();
|
|
2168
2275
|
if (t.type === "Literal") {
|
|
2169
2276
|
this.advance();
|
|
2170
2277
|
const lit = t;
|
|
@@ -2184,8 +2291,9 @@ var Parser = class {
|
|
|
2184
2291
|
return this.buildInterpolatedString(t);
|
|
2185
2292
|
}
|
|
2186
2293
|
if (t.type === "Operator" && t.value === "...") {
|
|
2294
|
+
this.problem(NO_DOTS);
|
|
2187
2295
|
this.advance();
|
|
2188
|
-
return { type: "
|
|
2296
|
+
return { type: "ErrorExpression", ...spanFrom(t, t) };
|
|
2189
2297
|
}
|
|
2190
2298
|
if (t.type === "Keyword" && t.value === "function") {
|
|
2191
2299
|
this.advance();
|
|
@@ -2261,12 +2369,17 @@ var Parser = class {
|
|
|
2261
2369
|
const start = this.current();
|
|
2262
2370
|
let base;
|
|
2263
2371
|
if (this.startsNew()) {
|
|
2264
|
-
|
|
2265
|
-
|
|
2372
|
+
this.problem("tilua has no 'new' operator: construct with 'Name.new(...)'");
|
|
2373
|
+
this.advance();
|
|
2374
|
+
}
|
|
2375
|
+
if (this.classDepth > 0 && this.checkIdentifierValue("super") && this.startsSuperUse()) {
|
|
2266
2376
|
this.advance();
|
|
2267
2377
|
base = { type: "SuperExpression", ...spanFrom(start, start) };
|
|
2268
2378
|
} else if (this.checkType("Identifier")) {
|
|
2269
2379
|
base = this.parseIdentifier();
|
|
2380
|
+
} else if (this.startsStringMethodCall()) {
|
|
2381
|
+
const t = this.advance();
|
|
2382
|
+
base = t.type === "InterpolatedString" ? this.buildInterpolatedString(t) : { type: "StringLiteral", value: t.value, raw: t.raw, ...spanFrom(t, t) };
|
|
2270
2383
|
} else if (this.matchPunctuator("(")) {
|
|
2271
2384
|
const inner = this.inBrackets(() => this.parseExpression());
|
|
2272
2385
|
this.expectPunctuator(")");
|
|
@@ -2285,6 +2398,21 @@ var Parser = class {
|
|
|
2285
2398
|
base = { type: "MemberExpression", object: base, property: prop, optional: true, ...spanFrom(base, prop) };
|
|
2286
2399
|
continue;
|
|
2287
2400
|
}
|
|
2401
|
+
if (punct === "." && this.punctuatorAt(2, "[")) {
|
|
2402
|
+
this.advance();
|
|
2403
|
+
this.advance();
|
|
2404
|
+
this.expectPunctuator("[");
|
|
2405
|
+
const index = this.inBrackets(() => this.parseExpression());
|
|
2406
|
+
this.expectPunctuator("]");
|
|
2407
|
+
base = {
|
|
2408
|
+
type: "IndexExpression",
|
|
2409
|
+
object: base,
|
|
2410
|
+
index,
|
|
2411
|
+
optional: true,
|
|
2412
|
+
...spanFrom(base, this.previous())
|
|
2413
|
+
};
|
|
2414
|
+
continue;
|
|
2415
|
+
}
|
|
2288
2416
|
if (punct === "." && this.punctuatorAt(2, "(")) {
|
|
2289
2417
|
this.advance();
|
|
2290
2418
|
this.advance();
|
|
@@ -2377,9 +2505,9 @@ var Parser = class {
|
|
|
2377
2505
|
}
|
|
2378
2506
|
return base;
|
|
2379
2507
|
}
|
|
2380
|
-
/** `new
|
|
2381
|
-
*
|
|
2382
|
-
*
|
|
2508
|
+
/** `new Name(...)`, the way other languages build an instance — only to
|
|
2509
|
+
* say so. A function or field called `new` — `Instance.new(x)`, and
|
|
2510
|
+
* `Vec.new(1)` itself — is untouched. */
|
|
2383
2511
|
startsNew() {
|
|
2384
2512
|
return this.checkIdentifierValue("new") && this.peek(1).type === "Identifier";
|
|
2385
2513
|
}
|
|
@@ -2389,25 +2517,6 @@ var Parser = class {
|
|
|
2389
2517
|
startsSuperUse() {
|
|
2390
2518
|
return this.punctuatorAt(1, "(") || this.punctuatorAt(1, ".") && this.peek(2).type === "Identifier";
|
|
2391
2519
|
}
|
|
2392
|
-
/** `new Name(args)` / `new Module.Name(args)`. The callee is a name, or a
|
|
2393
|
-
* name reached through a module — never an arbitrary expression, so the
|
|
2394
|
-
* arguments are unambiguously the constructor's. */
|
|
2395
|
-
parseNewExpression() {
|
|
2396
|
-
const start = this.current();
|
|
2397
|
-
this.advance();
|
|
2398
|
-
let callee = this.parseIdentifier();
|
|
2399
|
-
while (this.checkPunctuator(".") && this.peek(1).type === "Identifier") {
|
|
2400
|
-
this.advance();
|
|
2401
|
-
const property = this.parseIdentifier();
|
|
2402
|
-
callee = { type: "MemberExpression", object: callee, property, ...spanFrom(start, property) };
|
|
2403
|
-
}
|
|
2404
|
-
const typeArguments = this.tryCallTypeArguments();
|
|
2405
|
-
if (!this.checkPunctuator("(")) {
|
|
2406
|
-
this.error("Expected '(' after the class being constructed: 'new Name(...)'");
|
|
2407
|
-
}
|
|
2408
|
-
const args = this.parseCallArguments();
|
|
2409
|
-
return { type: "NewExpression", callee, arguments: args, typeArguments, ...spanFrom(start, this.previous()) };
|
|
2410
|
-
}
|
|
2411
2520
|
/** After `...`, is there something to spread? Nothing following it means
|
|
2412
2521
|
* the vararg pack, which is what `f(...)` has always passed on. */
|
|
2413
2522
|
startsSpread() {
|
|
@@ -2449,8 +2558,8 @@ var Parser = class {
|
|
|
2449
2558
|
const errors = this.errors.length;
|
|
2450
2559
|
try {
|
|
2451
2560
|
this.advance();
|
|
2452
|
-
const list = [this.
|
|
2453
|
-
while (this.matchPunctuator(",") && !this.checkOperator(">")) list.push(this.
|
|
2561
|
+
const list = [this.parseType()];
|
|
2562
|
+
while (this.matchPunctuator(",") && !this.checkOperator(">")) list.push(this.parseType());
|
|
2454
2563
|
this.expectOperator(">");
|
|
2455
2564
|
if (!this.startsCallArguments()) throw new ParseRecover("not a call");
|
|
2456
2565
|
return list;
|
|
@@ -2582,7 +2691,7 @@ var Parser = class {
|
|
|
2582
2691
|
if (this.checkOperator("...")) {
|
|
2583
2692
|
const dots = this.advance();
|
|
2584
2693
|
if (this.checkPunctuator("]") || this.checkPunctuator(",")) {
|
|
2585
|
-
|
|
2694
|
+
this.problemAt(dots, NO_DOTS);
|
|
2586
2695
|
} else {
|
|
2587
2696
|
const argument = this.expressionOr(stop);
|
|
2588
2697
|
elements.push({ type: "SpreadElement", argument, ...spanFrom(dots, argument) });
|
|
@@ -2736,35 +2845,16 @@ var Parser = class {
|
|
|
2736
2845
|
column: p.column
|
|
2737
2846
|
};
|
|
2738
2847
|
}
|
|
2739
|
-
|
|
2848
|
+
/** `T...` written where a type goes: a pack, which tilua does not have. */
|
|
2849
|
+
rejectPackReference() {
|
|
2740
2850
|
if (this.checkType("Identifier") && this.peek(1).type === "Operator" && this.peek(1).value === "...") {
|
|
2741
|
-
|
|
2742
|
-
|
|
2851
|
+
this.problemAt(
|
|
2852
|
+
this.peek(1),
|
|
2853
|
+
"tilua has no type packs: several values are a tuple, '[A, B]', and a varying number an array, 'T[]'"
|
|
2854
|
+
);
|
|
2855
|
+
this.advance();
|
|
2743
2856
|
this.advance();
|
|
2744
|
-
const packRef = { type: "TypeReference", base, typeArguments: [], ...spanFrom(start, start) };
|
|
2745
|
-
return {
|
|
2746
|
-
type: "TypePackNode",
|
|
2747
|
-
types: [],
|
|
2748
|
-
hasVarargs: true,
|
|
2749
|
-
// Wrap in `VariadicTypeNode`, matching the convention used by
|
|
2750
|
-
// `parseFunctionTypeAfterParen`'s identifier-pack-reference
|
|
2751
|
-
// branch, so the printer can tell `A...` (name-first, this
|
|
2752
|
-
// case) apart from `...T` (dots-first) and append rather
|
|
2753
|
-
// than prepend the `...`.
|
|
2754
|
-
varargType: { type: "VariadicTypeNode", typeAnnotation: packRef, ...spanFrom(start, this.previous()) },
|
|
2755
|
-
...spanFrom(start, this.previous())
|
|
2756
|
-
};
|
|
2757
|
-
}
|
|
2758
|
-
return this.parseType();
|
|
2759
|
-
}
|
|
2760
|
-
parseTypeArgument() {
|
|
2761
|
-
if (this.checkOperator("...")) {
|
|
2762
|
-
return this.parseTypePack();
|
|
2763
|
-
}
|
|
2764
|
-
if (this.checkType("Identifier") && this.peek(1).type === "Operator" && this.peek(1).value === "...") {
|
|
2765
|
-
return this.parseTypeOrTypePackReference();
|
|
2766
2857
|
}
|
|
2767
|
-
return this.parseType();
|
|
2768
2858
|
}
|
|
2769
2859
|
/** The part shared by a real function body and an overload signature:
|
|
2770
2860
|
* `<generics>(params): ReturnType`, up to (but not including) the block. */
|
|
@@ -2777,7 +2867,6 @@ var Parser = class {
|
|
|
2777
2867
|
this.expectPunctuator("(");
|
|
2778
2868
|
const params = [];
|
|
2779
2869
|
let hasVarargs = false;
|
|
2780
|
-
let varargTypeAnnotation;
|
|
2781
2870
|
if (!this.checkPunctuator(")")) {
|
|
2782
2871
|
while (true) {
|
|
2783
2872
|
if (this.checkOperator("...")) {
|
|
@@ -2785,14 +2874,14 @@ var Parser = class {
|
|
|
2785
2874
|
hasVarargs = true;
|
|
2786
2875
|
if (this.checkType("Identifier")) {
|
|
2787
2876
|
const nameTok = this.expectIdentifier();
|
|
2788
|
-
let
|
|
2877
|
+
let typeAnnotation3;
|
|
2789
2878
|
if (this.matchPunctuator(":")) {
|
|
2790
|
-
|
|
2879
|
+
typeAnnotation3 = this.typeOr(() => this.checkPunctuator(")"));
|
|
2791
2880
|
}
|
|
2792
2881
|
params.push({
|
|
2793
2882
|
type: "FunctionParameter",
|
|
2794
2883
|
name: nameTok.value,
|
|
2795
|
-
typeAnnotation:
|
|
2884
|
+
typeAnnotation: typeAnnotation3,
|
|
2796
2885
|
rest: true,
|
|
2797
2886
|
...spanFrom(dots, this.previous())
|
|
2798
2887
|
});
|
|
@@ -2801,9 +2890,20 @@ var Parser = class {
|
|
|
2801
2890
|
}
|
|
2802
2891
|
break;
|
|
2803
2892
|
}
|
|
2893
|
+
this.problemAt(dots, NO_DOTS);
|
|
2894
|
+
let typeAnnotation2;
|
|
2804
2895
|
if (this.matchPunctuator(":")) {
|
|
2805
|
-
|
|
2896
|
+
this.rejectPackReference();
|
|
2897
|
+
const element = this.parseType();
|
|
2898
|
+
typeAnnotation2 = { type: "ArrayTypeNode", element, ...spanFrom(element, element) };
|
|
2806
2899
|
}
|
|
2900
|
+
params.push({
|
|
2901
|
+
type: "FunctionParameter",
|
|
2902
|
+
name: "args",
|
|
2903
|
+
typeAnnotation: typeAnnotation2,
|
|
2904
|
+
rest: true,
|
|
2905
|
+
...spanFrom(dots, this.previous())
|
|
2906
|
+
});
|
|
2807
2907
|
break;
|
|
2808
2908
|
}
|
|
2809
2909
|
const paramStart = this.current();
|
|
@@ -2845,10 +2945,16 @@ var Parser = class {
|
|
|
2845
2945
|
if (this.matchPunctuator(":")) {
|
|
2846
2946
|
predicate = this.tryParseTypePredicate();
|
|
2847
2947
|
if (!predicate) {
|
|
2848
|
-
returnType = this.attempt(() => this.
|
|
2948
|
+
returnType = this.attempt(() => this.parseReturnType(), () => false, () => void 0);
|
|
2849
2949
|
}
|
|
2850
2950
|
}
|
|
2851
|
-
return { start, generics, params, hasVarargs,
|
|
2951
|
+
return { start, generics, params, hasVarargs, returnType, predicate };
|
|
2952
|
+
}
|
|
2953
|
+
/** What a function returns: one type. `T...` is reported as the pack it
|
|
2954
|
+
* is, and the type after it read. */
|
|
2955
|
+
parseReturnType() {
|
|
2956
|
+
this.rejectPackReference();
|
|
2957
|
+
return this.parseType();
|
|
2852
2958
|
}
|
|
2853
2959
|
/** TypeScript-style type-guard return annotations, in return position only:
|
|
2854
2960
|
*
|
|
@@ -2918,7 +3024,6 @@ var Parser = class {
|
|
|
2918
3024
|
generics: [],
|
|
2919
3025
|
params: [{ type: "FunctionParameter", name: name.value, ...spanFrom(name, name) }],
|
|
2920
3026
|
hasVarargs: false,
|
|
2921
|
-
varargTypeAnnotation: void 0,
|
|
2922
3027
|
returnType: void 0,
|
|
2923
3028
|
predicate: void 0
|
|
2924
3029
|
};
|
|
@@ -2930,7 +3035,6 @@ var Parser = class {
|
|
|
2930
3035
|
generics: head.generics,
|
|
2931
3036
|
params: head.params,
|
|
2932
3037
|
hasVarargs: head.hasVarargs,
|
|
2933
|
-
varargTypeAnnotation: head.varargTypeAnnotation,
|
|
2934
3038
|
returnType: head.returnType,
|
|
2935
3039
|
predicate: head.predicate,
|
|
2936
3040
|
body,
|
|
@@ -2942,7 +3046,7 @@ var Parser = class {
|
|
|
2942
3046
|
returnOf(expression) {
|
|
2943
3047
|
const statement = {
|
|
2944
3048
|
type: "ReturnStatement",
|
|
2945
|
-
|
|
3049
|
+
argument: expression,
|
|
2946
3050
|
...spanFrom(expression, expression)
|
|
2947
3051
|
};
|
|
2948
3052
|
return { type: "Block", statements: [statement], ...spanFrom(expression, expression) };
|
|
@@ -2955,7 +3059,6 @@ var Parser = class {
|
|
|
2955
3059
|
generics: head.generics,
|
|
2956
3060
|
params: head.params,
|
|
2957
3061
|
hasVarargs: head.hasVarargs,
|
|
2958
|
-
varargTypeAnnotation: head.varargTypeAnnotation,
|
|
2959
3062
|
returnType: head.returnType,
|
|
2960
3063
|
predicate: head.predicate,
|
|
2961
3064
|
body,
|
|
@@ -2968,7 +3071,6 @@ var Parser = class {
|
|
|
2968
3071
|
generics: head.generics,
|
|
2969
3072
|
params: head.params,
|
|
2970
3073
|
hasVarargs: head.hasVarargs,
|
|
2971
|
-
varargTypeAnnotation: head.varargTypeAnnotation,
|
|
2972
3074
|
returnType: head.returnType,
|
|
2973
3075
|
predicate: head.predicate,
|
|
2974
3076
|
...spanFrom(head.start, this.previous())
|
|
@@ -2986,7 +3088,6 @@ var Parser = class {
|
|
|
2986
3088
|
generics: head.generics,
|
|
2987
3089
|
params: head.params,
|
|
2988
3090
|
hasVarargs: head.hasVarargs,
|
|
2989
|
-
varargTypeAnnotation: head.varargTypeAnnotation,
|
|
2990
3091
|
returnType: head.returnType,
|
|
2991
3092
|
predicate: head.predicate,
|
|
2992
3093
|
body,
|
|
@@ -3065,8 +3166,12 @@ var Parser = class {
|
|
|
3065
3166
|
* `name?: T` for an optional property or parameter. */
|
|
3066
3167
|
parseSuffixType() {
|
|
3067
3168
|
if (this.checkIdentifierValue("readonly")) {
|
|
3169
|
+
const start = this.current();
|
|
3068
3170
|
this.advance();
|
|
3069
|
-
|
|
3171
|
+
const t2 = this.parseSuffixType();
|
|
3172
|
+
if (t2.type === "ArrayTypeNode" || t2.type === "TupleTypeNode") return { ...t2, isReadonly: true };
|
|
3173
|
+
this.problemAt(start, "'readonly' belongs on an array or a tuple: a property is marked where it is written");
|
|
3174
|
+
return t2;
|
|
3070
3175
|
}
|
|
3071
3176
|
let t = this.parsePrimaryType();
|
|
3072
3177
|
while (true) {
|
|
@@ -3127,9 +3232,10 @@ var Parser = class {
|
|
|
3127
3232
|
return this.parseFunctionTypeAfterParen(t, []);
|
|
3128
3233
|
}
|
|
3129
3234
|
if (t.type === "Operator" && t.value === "...") {
|
|
3235
|
+
this.problem("'...' in a type goes before a rest parameter, '...args: T[]', or at the end of a tuple, '[A, ...B[]]'");
|
|
3130
3236
|
this.advance();
|
|
3131
|
-
const
|
|
3132
|
-
return { type: "
|
|
3237
|
+
const element = this.parseType();
|
|
3238
|
+
return { type: "ArrayTypeNode", element, ...spanFrom(t, this.previous()) };
|
|
3133
3239
|
}
|
|
3134
3240
|
if (t.type === "Punctuator" && t.value === "{") {
|
|
3135
3241
|
return this.parseTableType();
|
|
@@ -3137,13 +3243,22 @@ var Parser = class {
|
|
|
3137
3243
|
if (t.type === "Punctuator" && t.value === "[") {
|
|
3138
3244
|
this.advance();
|
|
3139
3245
|
const elements = [];
|
|
3246
|
+
let rest;
|
|
3140
3247
|
while (!this.checkPunctuator("]")) {
|
|
3248
|
+
if (this.matchOperator("...")) {
|
|
3249
|
+
rest = this.parseType();
|
|
3250
|
+
if (rest.type !== "ArrayTypeNode" && !(rest.type === "TypeReference" && rest.base !== "nil")) {
|
|
3251
|
+
this.problem("The rest of a tuple is an array type: '...T[]'");
|
|
3252
|
+
}
|
|
3253
|
+
this.matchPunctuator(",");
|
|
3254
|
+
break;
|
|
3255
|
+
}
|
|
3141
3256
|
elements.push(this.parseType());
|
|
3142
3257
|
if (this.matchPunctuator(",")) continue;
|
|
3143
3258
|
break;
|
|
3144
3259
|
}
|
|
3145
3260
|
this.expectPunctuator("]");
|
|
3146
|
-
return { type: "TupleTypeNode", elements, ...spanFrom(t, this.previous()) };
|
|
3261
|
+
return { type: "TupleTypeNode", elements, ...rest ? { rest } : {}, ...spanFrom(t, this.previous()) };
|
|
3147
3262
|
}
|
|
3148
3263
|
if (t.type === "Identifier" && t.value === "typeof" && this.peek(1).type === "Punctuator" && this.peek(1).value === "(") {
|
|
3149
3264
|
this.advance();
|
|
@@ -3190,9 +3305,9 @@ var Parser = class {
|
|
|
3190
3305
|
if (this.checkOperator("<")) {
|
|
3191
3306
|
this.advance();
|
|
3192
3307
|
if (!this.checkOperator(">")) {
|
|
3193
|
-
typeArguments.push(this.
|
|
3308
|
+
typeArguments.push(this.parseType());
|
|
3194
3309
|
while (this.matchPunctuator(",") && !this.checkOperator(">")) {
|
|
3195
|
-
typeArguments.push(this.
|
|
3310
|
+
typeArguments.push(this.parseType());
|
|
3196
3311
|
}
|
|
3197
3312
|
}
|
|
3198
3313
|
this.expectOperator(">");
|
|
@@ -3203,13 +3318,12 @@ var Parser = class {
|
|
|
3203
3318
|
}
|
|
3204
3319
|
parseFunctionTypeAfterParen(start, generics) {
|
|
3205
3320
|
const params = [];
|
|
3206
|
-
let
|
|
3207
|
-
let varargType;
|
|
3321
|
+
let rested = false;
|
|
3208
3322
|
if (!this.checkPunctuator(")")) {
|
|
3209
3323
|
while (true) {
|
|
3210
3324
|
if (this.checkOperator("...")) {
|
|
3211
3325
|
const dots = this.advance();
|
|
3212
|
-
|
|
3326
|
+
rested = true;
|
|
3213
3327
|
if (this.checkType("Identifier") && this.punctuatorAt(1, ":")) {
|
|
3214
3328
|
const nameTok2 = this.expectIdentifier();
|
|
3215
3329
|
this.advance();
|
|
@@ -3223,16 +3337,21 @@ var Parser = class {
|
|
|
3223
3337
|
});
|
|
3224
3338
|
break;
|
|
3225
3339
|
}
|
|
3226
|
-
|
|
3340
|
+
this.problemAt(dots, "A function type takes the rest of its arguments as an array: '(...args: T[]) => R'");
|
|
3341
|
+
this.rejectPackReference();
|
|
3342
|
+
const element = this.parseType();
|
|
3343
|
+
params.push({
|
|
3344
|
+
type: "FunctionTypeParameter",
|
|
3345
|
+
name: "args",
|
|
3346
|
+
rest: true,
|
|
3347
|
+
typeAnnotation: { type: "ArrayTypeNode", element, ...spanFrom(element, element) },
|
|
3348
|
+
...spanFrom(dots, this.previous())
|
|
3349
|
+
});
|
|
3227
3350
|
break;
|
|
3228
3351
|
}
|
|
3229
3352
|
if (this.checkType("Identifier") && this.peek(1).type === "Operator" && this.peek(1).value === "...") {
|
|
3230
|
-
|
|
3231
|
-
|
|
3232
|
-
this.advance();
|
|
3233
|
-
hasVarargs = true;
|
|
3234
|
-
varargType = { type: "VariadicTypeNode", typeAnnotation: packRef, ...spanFrom(packStart, this.previous()) };
|
|
3235
|
-
break;
|
|
3353
|
+
this.rejectPackReference();
|
|
3354
|
+
continue;
|
|
3236
3355
|
}
|
|
3237
3356
|
let name;
|
|
3238
3357
|
let optional2 = false;
|
|
@@ -3262,19 +3381,17 @@ var Parser = class {
|
|
|
3262
3381
|
this.expectPunctuator(")");
|
|
3263
3382
|
if (this.matchPunctuator("=>") || this.mistypedArrow()) {
|
|
3264
3383
|
const predicate = this.tryParseTypePredicate();
|
|
3265
|
-
const returnType = predicate ? { type: "TypeReference", base: "boolean", typeArguments: [], ...spanFrom(start, this.previous()) } : this.
|
|
3384
|
+
const returnType = predicate ? { type: "TypeReference", base: "boolean", typeArguments: [], ...spanFrom(start, this.previous()) } : this.parseReturnType();
|
|
3266
3385
|
return {
|
|
3267
3386
|
type: "FunctionTypeNode",
|
|
3268
3387
|
generics,
|
|
3269
3388
|
params,
|
|
3270
|
-
hasVarargs,
|
|
3271
|
-
varargType,
|
|
3272
3389
|
returnType,
|
|
3273
3390
|
predicate,
|
|
3274
3391
|
...spanFrom(start, this.previous())
|
|
3275
3392
|
};
|
|
3276
3393
|
}
|
|
3277
|
-
if (params.length === 1 && !params[0].name && !
|
|
3394
|
+
if (params.length === 1 && !params[0].name && !rested) {
|
|
3278
3395
|
return {
|
|
3279
3396
|
type: "ParenthesizedTypeNode",
|
|
3280
3397
|
typeAnnotation: params[0].typeAnnotation,
|
|
@@ -3284,11 +3401,14 @@ var Parser = class {
|
|
|
3284
3401
|
if (params.some((p) => p.name !== void 0)) {
|
|
3285
3402
|
this.error("Expected '=>' for function type");
|
|
3286
3403
|
}
|
|
3404
|
+
if (!params.length) {
|
|
3405
|
+
this.problemAt(start, "'()' is not a type: a function that returns nothing returns 'nil'");
|
|
3406
|
+
return { type: "TypeReference", base: "nil", typeArguments: [], ...spanFrom(start, this.previous()) };
|
|
3407
|
+
}
|
|
3408
|
+
this.problemAt(start, "Several types in parentheses are a pack, which tilua does not have: write a tuple, '[A, B]'");
|
|
3287
3409
|
return {
|
|
3288
|
-
type: "
|
|
3289
|
-
|
|
3290
|
-
hasVarargs,
|
|
3291
|
-
varargType,
|
|
3410
|
+
type: "TupleTypeNode",
|
|
3411
|
+
elements: params.map((p) => p.typeAnnotation),
|
|
3292
3412
|
...spanFrom(start, this.previous())
|
|
3293
3413
|
};
|
|
3294
3414
|
}
|
|
@@ -3426,40 +3546,15 @@ var Parser = class {
|
|
|
3426
3546
|
this.expectPunctuator("}");
|
|
3427
3547
|
return { type: "TableTypeNode", properties, ...spanFrom(start, this.previous()) };
|
|
3428
3548
|
}
|
|
3429
|
-
parseTypePack() {
|
|
3430
|
-
const start = this.current();
|
|
3431
|
-
if (this.matchOperator("...")) {
|
|
3432
|
-
const varargType2 = this.parseType();
|
|
3433
|
-
return { type: "TypePackNode", types: [], hasVarargs: true, varargType: varargType2, ...spanFrom(start, this.previous()) };
|
|
3434
|
-
}
|
|
3435
|
-
this.expectPunctuator("(");
|
|
3436
|
-
const types = [];
|
|
3437
|
-
let hasVarargs = false;
|
|
3438
|
-
let varargType;
|
|
3439
|
-
if (!(this.current().type === "Punctuator" && this.current().value === ")")) {
|
|
3440
|
-
while (true) {
|
|
3441
|
-
if (this.matchOperator("...")) {
|
|
3442
|
-
hasVarargs = true;
|
|
3443
|
-
varargType = this.parseType();
|
|
3444
|
-
break;
|
|
3445
|
-
}
|
|
3446
|
-
types.push(this.parseType());
|
|
3447
|
-
if (this.matchPunctuator(",")) continue;
|
|
3448
|
-
break;
|
|
3449
|
-
}
|
|
3450
|
-
}
|
|
3451
|
-
this.expectPunctuator(")");
|
|
3452
|
-
return { type: "TypePackNode", types, hasVarargs, varargType, ...spanFrom(start, this.previous()) };
|
|
3453
|
-
}
|
|
3454
3549
|
parseGenericTypeParameterList() {
|
|
3455
3550
|
const list = [];
|
|
3456
3551
|
this.expectOperator("<");
|
|
3457
3552
|
while (true) {
|
|
3458
3553
|
const isConst = this.matchKeyword("const");
|
|
3459
3554
|
const nameTok = this.expectIdentifier();
|
|
3460
|
-
|
|
3461
|
-
if (
|
|
3462
|
-
|
|
3555
|
+
const dots = this.checkOperator("...") ? this.advance() : void 0;
|
|
3556
|
+
if (dots) {
|
|
3557
|
+
this.problemAt(dots, "tilua has no pack parameters: write '<T extends unknown[]>', and take it as '...args: T'");
|
|
3463
3558
|
}
|
|
3464
3559
|
let constraint;
|
|
3465
3560
|
if (this.checkIdentifierValue("extends")) {
|
|
@@ -3468,17 +3563,13 @@ var Parser = class {
|
|
|
3468
3563
|
}
|
|
3469
3564
|
let def;
|
|
3470
3565
|
if (this.matchOperator("=")) {
|
|
3471
|
-
|
|
3472
|
-
|
|
3473
|
-
} else {
|
|
3474
|
-
def = this.parseType();
|
|
3475
|
-
}
|
|
3566
|
+
this.rejectPackReference();
|
|
3567
|
+
def = this.parseType();
|
|
3476
3568
|
}
|
|
3477
3569
|
list.push({
|
|
3478
3570
|
type: "GenericTypeParameter",
|
|
3479
3571
|
name: nameTok.value,
|
|
3480
3572
|
id: tokenIdentifier(nameTok),
|
|
3481
|
-
isPack,
|
|
3482
3573
|
isConst: isConst || void 0,
|
|
3483
3574
|
constraint,
|
|
3484
3575
|
default: def,
|
|
@@ -3569,7 +3660,7 @@ function childScope(parent) {
|
|
|
3569
3660
|
var Analyzer = class {
|
|
3570
3661
|
constructor(options) {
|
|
3571
3662
|
this.options = options;
|
|
3572
|
-
for (const name of options.builtinGlobals ?? []) {
|
|
3663
|
+
for (const name of [...LANGUAGE_GLOBALS, ...options.builtinGlobals ?? []]) {
|
|
3573
3664
|
const id = this.getOrCreateGlobalBinding(name);
|
|
3574
3665
|
this.bindings.get(id).isBuiltin = true;
|
|
3575
3666
|
}
|
|
@@ -3631,31 +3722,34 @@ var Analyzer = class {
|
|
|
3631
3722
|
* can call it, but a call straight in the block before the declaration
|
|
3632
3723
|
* finds nothing there yet. At a module's top level the whole function is
|
|
3633
3724
|
* hoisted, and this does not apply. */
|
|
3634
|
-
checkUseBeforeDefine(identifier, id) {
|
|
3725
|
+
checkUseBeforeDefine(identifier, id, at = this.functionDepth) {
|
|
3635
3726
|
const binding = this.bindings.get(id);
|
|
3636
|
-
if (
|
|
3727
|
+
if (this.typeQueryDepth > 0) return;
|
|
3637
3728
|
const declaration = binding.declarationNode;
|
|
3638
|
-
|
|
3639
|
-
|
|
3729
|
+
if (!declaration) return;
|
|
3730
|
+
const hoisted = binding.declaredBy === "function" || binding.declaredBy === "class";
|
|
3731
|
+
const depth = hoisted ? this.hoisted.get(declaration) : this.declaredDepth.get(id);
|
|
3732
|
+
const readable = hoisted || binding.kind === "local";
|
|
3733
|
+
if (!readable || depth === void 0 || depth !== at) return;
|
|
3640
3734
|
const before = identifier.line.start < declaration.line.start || identifier.line.start === declaration.line.start && identifier.column.start < declaration.column.start;
|
|
3641
3735
|
if (!before) return;
|
|
3642
3736
|
this.diagnostics.push({
|
|
3643
3737
|
node: identifier,
|
|
3644
|
-
message: `'${binding.name}' is used before its definition: inside a function, a function declared further down is only there once its declaration has run`,
|
|
3738
|
+
message: hoisted ? `'${binding.name}' is used before its definition: inside a function, a function declared further down is only there once its declaration has run` : `'${binding.name}' is used before its declaration, and holds nothing until that line has run`,
|
|
3645
3739
|
kind: "use-before-define"
|
|
3646
3740
|
});
|
|
3647
3741
|
}
|
|
3648
3742
|
noteDeferred(identifier, scope, id, assignment) {
|
|
3649
3743
|
if (this.functionDepth === 0 && this.typeQueryDepth === 0) return;
|
|
3650
3744
|
if (this.bindings.get(id).kind !== "global") return;
|
|
3651
|
-
this.deferredGlobals.push({ node: identifier, scope, assignment });
|
|
3745
|
+
this.deferredGlobals.push({ node: identifier, scope, assignment, depth: this.functionDepth });
|
|
3652
3746
|
}
|
|
3653
3747
|
/** Point each deferred read of a global at the declaration of that name
|
|
3654
3748
|
* that turned up later — in the module, or in any block around the code
|
|
3655
3749
|
* that reads it. Such code runs after the declaration has: a closure
|
|
3656
3750
|
* written inside a value reads the name the value is bound to. */
|
|
3657
3751
|
resolveForwardReferences() {
|
|
3658
|
-
for (const { node, scope, assignment } of this.deferredGlobals) {
|
|
3752
|
+
for (const { node, scope, assignment, depth } of this.deferredGlobals) {
|
|
3659
3753
|
const localId = this.lookup(scope, node.name);
|
|
3660
3754
|
const globalId = this.bindingOf.get(node);
|
|
3661
3755
|
if (localId === void 0 || globalId === void 0 || localId === globalId) continue;
|
|
@@ -3670,7 +3764,10 @@ var Analyzer = class {
|
|
|
3670
3764
|
this.bindingOf.set(node, localId);
|
|
3671
3765
|
this.bindings.get(localId).references.push(node);
|
|
3672
3766
|
if (assignment) this.checkConstAssign(localId, node);
|
|
3673
|
-
else if (this.typeQueryDepth === 0)
|
|
3767
|
+
else if (this.typeQueryDepth === 0) {
|
|
3768
|
+
this.checkTypeOnly(localId, node);
|
|
3769
|
+
this.checkUseBeforeDefine(node, localId, depth);
|
|
3770
|
+
}
|
|
3674
3771
|
}
|
|
3675
3772
|
}
|
|
3676
3773
|
/** Every read of a global nothing declares. A global assigned somewhere
|
|
@@ -3701,9 +3798,13 @@ var Analyzer = class {
|
|
|
3701
3798
|
}
|
|
3702
3799
|
const id = this.nextId++;
|
|
3703
3800
|
this.bindings.set(id, { id, name, kind, declarationNode: node, references: [], isConst, declaredBy });
|
|
3801
|
+
this.declaredDepth.set(id, this.functionDepth);
|
|
3704
3802
|
scope.declarations.set(name, id);
|
|
3705
3803
|
return id;
|
|
3706
3804
|
}
|
|
3805
|
+
/** Which function body each name was declared in. A read of a `const` from
|
|
3806
|
+
* the same body, but above it, is a read of nothing. */
|
|
3807
|
+
declaredDepth = /* @__PURE__ */ new Map();
|
|
3707
3808
|
resolve(scope, name) {
|
|
3708
3809
|
for (let s = scope; s; s = s.parent) {
|
|
3709
3810
|
const id = s.declarations.get(name);
|
|
@@ -3971,14 +4072,14 @@ var Analyzer = class {
|
|
|
3971
4072
|
return;
|
|
3972
4073
|
}
|
|
3973
4074
|
case "GenericForStatement": {
|
|
3974
|
-
|
|
4075
|
+
this.visitExpression(stmt.iterator, scope);
|
|
3975
4076
|
const bodyScope = childScope(scope);
|
|
3976
|
-
|
|
4077
|
+
this.declarePattern(bodyScope, stmt.variable, "for-generic", scope, stmt.kind === "const");
|
|
3977
4078
|
this.visitBlock(stmt.body, bodyScope);
|
|
3978
4079
|
return;
|
|
3979
4080
|
}
|
|
3980
4081
|
case "ReturnStatement":
|
|
3981
|
-
|
|
4082
|
+
if (stmt.argument) this.visitExpression(stmt.argument, scope);
|
|
3982
4083
|
return;
|
|
3983
4084
|
case "BreakStatement":
|
|
3984
4085
|
case "ContinueStatement":
|
|
@@ -4046,7 +4147,6 @@ var Analyzer = class {
|
|
|
4046
4147
|
this.declare(fnScope, param.name, kind, param);
|
|
4047
4148
|
}
|
|
4048
4149
|
});
|
|
4049
|
-
this.visitType(func.varargTypeAnnotation, fnScope);
|
|
4050
4150
|
this.visitType(func.returnType, fnScope);
|
|
4051
4151
|
this.functionDepth++;
|
|
4052
4152
|
try {
|
|
@@ -4074,6 +4174,7 @@ var Analyzer = class {
|
|
|
4074
4174
|
this.visitGenerics(node.typeParams, scope);
|
|
4075
4175
|
if (node.superclass) this.reference(outer, node.superclass);
|
|
4076
4176
|
for (const argument of node.superArguments ?? []) this.visitType(argument, scope);
|
|
4177
|
+
for (const shape of node.implements ?? []) this.visitType(shape, scope);
|
|
4077
4178
|
for (const member of node.members) {
|
|
4078
4179
|
switch (member.type) {
|
|
4079
4180
|
case "ClassField":
|
|
@@ -4133,7 +4234,6 @@ var Analyzer = class {
|
|
|
4133
4234
|
case "BooleanLiteral":
|
|
4134
4235
|
case "NumberLiteral":
|
|
4135
4236
|
case "StringLiteral":
|
|
4136
|
-
case "VarargExpression":
|
|
4137
4237
|
case "ErrorExpression":
|
|
4138
4238
|
return;
|
|
4139
4239
|
case "InterpolatedStringExpression":
|
|
@@ -4173,11 +4273,6 @@ var Analyzer = class {
|
|
|
4173
4273
|
this.visitExpression(expr.callee, scope);
|
|
4174
4274
|
for (const arg of expr.arguments) this.visitExpression(arg, scope);
|
|
4175
4275
|
return;
|
|
4176
|
-
case "NewExpression":
|
|
4177
|
-
this.visitExpression(expr.callee, scope);
|
|
4178
|
-
for (const argument of expr.arguments) this.visitExpression(argument, scope);
|
|
4179
|
-
for (const argument of expr.typeArguments ?? []) this.visitType(argument, scope);
|
|
4180
|
-
return;
|
|
4181
4276
|
case "SuperExpression":
|
|
4182
4277
|
return;
|
|
4183
4278
|
case "SpreadElement":
|
|
@@ -4231,6 +4326,7 @@ var Analyzer = class {
|
|
|
4231
4326
|
}
|
|
4232
4327
|
}
|
|
4233
4328
|
};
|
|
4329
|
+
var LANGUAGE_GLOBALS = ["scriptArgs"];
|
|
4234
4330
|
function analyzeScopes(program, options = {}) {
|
|
4235
4331
|
return new Analyzer(options).run(program);
|
|
4236
4332
|
}
|
|
@@ -4249,8 +4345,8 @@ type Exclude<T, U> = T - U
|
|
|
4249
4345
|
type Extract<T, U> = T extends U ? T : never
|
|
4250
4346
|
type NonNullable<T> = T - nil
|
|
4251
4347
|
|
|
4252
|
-
type ReturnType<T> = T extends (...unknown) => infer R ? R : never
|
|
4253
|
-
type Parameters<T> = T extends (...infer P) => unknown ? P : never
|
|
4348
|
+
type ReturnType<T> = T extends (...args: unknown[]) => infer R ? R : never
|
|
4349
|
+
type Parameters<T> = T extends (...args: infer P) => unknown ? P : never
|
|
4254
4350
|
|
|
4255
4351
|
type Partial<T> = { [K in keyof T]?: T[K] }
|
|
4256
4352
|
type Required<T> = { [K in keyof T]-?: T[K] }
|
|
@@ -4335,8 +4431,11 @@ function literal(value) {
|
|
|
4335
4431
|
function arrayOf(element) {
|
|
4336
4432
|
return { kind: "array", element };
|
|
4337
4433
|
}
|
|
4338
|
-
function tuple(elements,
|
|
4339
|
-
return { kind: "tuple", elements,
|
|
4434
|
+
function tuple(elements, rest) {
|
|
4435
|
+
return rest ? { kind: "tuple", elements, rest } : { kind: "tuple", elements };
|
|
4436
|
+
}
|
|
4437
|
+
function tupleMembers(t) {
|
|
4438
|
+
return t.rest ? [...t.elements, t.rest] : t.elements;
|
|
4340
4439
|
}
|
|
4341
4440
|
function objectType(entries, indexer, frozen) {
|
|
4342
4441
|
return { kind: "object", properties: new Map(entries), indexer, frozen };
|
|
@@ -4358,9 +4457,9 @@ function substitute(t, subst) {
|
|
|
4358
4457
|
case "typeParam":
|
|
4359
4458
|
return subst.get(t.name) ?? t;
|
|
4360
4459
|
case "array":
|
|
4361
|
-
return arrayOf(substitute(t.element, subst));
|
|
4460
|
+
return { ...arrayOf(substitute(t.element, subst)), readonly: t.readonly };
|
|
4362
4461
|
case "tuple":
|
|
4363
|
-
return tuple(t.elements.map((e) => substitute(e, subst)), t.
|
|
4462
|
+
return { ...tuple(t.elements.map((e) => substitute(e, subst)), t.rest && substitute(t.rest, subst)), readonly: t.readonly };
|
|
4364
4463
|
case "object": {
|
|
4365
4464
|
const entries = [];
|
|
4366
4465
|
for (const [k, v] of t.properties) entries.push([k, { ...v, type: substitute(v.type, subst) }]);
|
|
@@ -4384,14 +4483,20 @@ function substitute(t, subst) {
|
|
|
4384
4483
|
const inner = t.typeParams ? new Map([...subst].filter(([k]) => !t.typeParams.includes(k))) : subst;
|
|
4385
4484
|
let params = t.params.map((p) => ({ ...p, type: substitute(p.type, inner) }));
|
|
4386
4485
|
let varargs = t.varargs && substitute(t.varargs, inner);
|
|
4387
|
-
|
|
4486
|
+
let restIsWhole = t.restIsWhole;
|
|
4487
|
+
if (restIsWhole && varargs?.kind === "tuple") {
|
|
4388
4488
|
params = [...params, ...varargs.elements.map((type) => ({ type }))];
|
|
4389
|
-
varargs =
|
|
4489
|
+
varargs = varargs.rest;
|
|
4490
|
+
restIsWhole = void 0;
|
|
4491
|
+
} else if (restIsWhole && varargs?.kind === "array") {
|
|
4492
|
+
varargs = varargs.element;
|
|
4493
|
+
restIsWhole = void 0;
|
|
4390
4494
|
}
|
|
4391
4495
|
return {
|
|
4392
4496
|
kind: "function",
|
|
4393
4497
|
params,
|
|
4394
4498
|
varargs,
|
|
4499
|
+
...restIsWhole ? { restIsWhole } : {},
|
|
4395
4500
|
returns: substitute(t.returns, inner),
|
|
4396
4501
|
typeParams: t.typeParams,
|
|
4397
4502
|
typeParamDefaults: t.typeParamDefaults,
|
|
@@ -4474,11 +4579,19 @@ function unify(param, arg, vars, out) {
|
|
|
4474
4579
|
switch (param.kind) {
|
|
4475
4580
|
case "array":
|
|
4476
4581
|
if (arg.kind === "array") unify(param.element, arg.element, vars, out);
|
|
4477
|
-
else if (arg.kind === "tuple") for (const e of arg
|
|
4582
|
+
else if (arg.kind === "tuple") for (const e of tupleMembers(arg)) unify(param.element, e, vars, out);
|
|
4478
4583
|
return;
|
|
4479
4584
|
case "tuple":
|
|
4480
|
-
if (arg.kind === "tuple")
|
|
4481
|
-
|
|
4585
|
+
if (arg.kind === "tuple") {
|
|
4586
|
+
param.elements.forEach((p, i) => {
|
|
4587
|
+
const a = arg.elements[i] ?? arg.rest;
|
|
4588
|
+
if (a) unify(p, a, vars, out);
|
|
4589
|
+
});
|
|
4590
|
+
if (param.rest) for (const a of arg.elements.slice(param.elements.length)) unify(param.rest, a, vars, out);
|
|
4591
|
+
if (param.rest && arg.rest) unify(param.rest, arg.rest, vars, out);
|
|
4592
|
+
} else if (arg.kind === "array") {
|
|
4593
|
+
for (const p of tupleMembers(param)) unify(p, arg.element, vars, out);
|
|
4594
|
+
}
|
|
4482
4595
|
return;
|
|
4483
4596
|
case "function":
|
|
4484
4597
|
if (arg.kind === "function") {
|
|
@@ -4511,6 +4624,20 @@ function unify(param, arg, vars, out) {
|
|
|
4511
4624
|
return;
|
|
4512
4625
|
}
|
|
4513
4626
|
}
|
|
4627
|
+
function memberKey(t) {
|
|
4628
|
+
switch (t.kind) {
|
|
4629
|
+
case "literal":
|
|
4630
|
+
return `l${t.base}:${String(t.value)}`;
|
|
4631
|
+
case "primitive":
|
|
4632
|
+
return `p${t.name}`;
|
|
4633
|
+
case "any":
|
|
4634
|
+
case "unknown":
|
|
4635
|
+
case "never":
|
|
4636
|
+
return t.kind;
|
|
4637
|
+
default:
|
|
4638
|
+
return formatType(t);
|
|
4639
|
+
}
|
|
4640
|
+
}
|
|
4514
4641
|
function union(types) {
|
|
4515
4642
|
const flat = [];
|
|
4516
4643
|
for (const t of types) {
|
|
@@ -4522,7 +4649,7 @@ function union(types) {
|
|
|
4522
4649
|
const seen = /* @__PURE__ */ new Map();
|
|
4523
4650
|
for (const t of flat) {
|
|
4524
4651
|
if (t.kind === "never") continue;
|
|
4525
|
-
const key =
|
|
4652
|
+
const key = memberKey(t);
|
|
4526
4653
|
if (!seen.has(key)) seen.set(key, t);
|
|
4527
4654
|
}
|
|
4528
4655
|
let members = reduceSubtypes([...seen.values()]);
|
|
@@ -4551,7 +4678,7 @@ function intersection(types) {
|
|
|
4551
4678
|
const seen = /* @__PURE__ */ new Map();
|
|
4552
4679
|
for (const t of flat) {
|
|
4553
4680
|
if (t.kind === "unknown") continue;
|
|
4554
|
-
seen.set(
|
|
4681
|
+
seen.set(memberKey(t), t);
|
|
4555
4682
|
}
|
|
4556
4683
|
const members = [...seen.values()];
|
|
4557
4684
|
if (members.length === 0) return unknownType;
|
|
@@ -4578,9 +4705,9 @@ function widen(t) {
|
|
|
4578
4705
|
case "literal":
|
|
4579
4706
|
return primitive(t.base);
|
|
4580
4707
|
case "array":
|
|
4581
|
-
return arrayOf(widen(t.element));
|
|
4708
|
+
return { ...arrayOf(widen(t.element)), readonly: t.readonly };
|
|
4582
4709
|
case "tuple":
|
|
4583
|
-
return tuple(t.elements.map(widen), t.
|
|
4710
|
+
return { ...tuple(t.elements.map(widen), t.rest && widen(t.rest)), readonly: t.readonly };
|
|
4584
4711
|
case "object": {
|
|
4585
4712
|
if (t.frozen || t.class) return t;
|
|
4586
4713
|
const entries = [];
|
|
@@ -4678,15 +4805,21 @@ function isAssignableInner(a, b) {
|
|
|
4678
4805
|
return b.kind === "primitive" && b.name === "string";
|
|
4679
4806
|
}
|
|
4680
4807
|
if (a.kind === "primitive") return b.kind === "primitive" && b.name === a.name;
|
|
4808
|
+
if ((a.kind === "array" || a.kind === "tuple") && a.readonly && (b.kind === "array" || b.kind === "tuple") && !b.readonly) return false;
|
|
4681
4809
|
if (a.kind === "array") {
|
|
4682
4810
|
if (b.kind === "array") return isAssignable(a.element, b.element);
|
|
4811
|
+
if (b.kind === "tuple") return b.elements.length === 0 && !!b.rest && isAssignable(a.element, b.rest);
|
|
4683
4812
|
return false;
|
|
4684
4813
|
}
|
|
4685
4814
|
if (a.kind === "tuple") {
|
|
4686
4815
|
if (b.kind === "tuple") {
|
|
4687
|
-
|
|
4816
|
+
if (a.elements.length < b.elements.length) return false;
|
|
4817
|
+
if (!b.elements.every((t, i) => isAssignable(a.elements[i], t))) return false;
|
|
4818
|
+
const extra = [...a.elements.slice(b.elements.length), ...a.rest ? [a.rest] : []];
|
|
4819
|
+
if (!b.rest) return extra.length === 0;
|
|
4820
|
+
return extra.every((t) => isAssignable(t, b.rest));
|
|
4688
4821
|
}
|
|
4689
|
-
if (b.kind === "array") return a.
|
|
4822
|
+
if (b.kind === "array") return tupleMembers(a).every((t) => isAssignable(t, b.element));
|
|
4690
4823
|
return false;
|
|
4691
4824
|
}
|
|
4692
4825
|
if (a.kind === "object") {
|
|
@@ -4735,7 +4868,7 @@ function sameRefTarget(a, b) {
|
|
|
4735
4868
|
return a.name === b.name && (!a.origin || !b.origin || a.origin === b.origin);
|
|
4736
4869
|
}
|
|
4737
4870
|
function isNoValue(t) {
|
|
4738
|
-
return t.kind === "tuple" && t.elements.length === 0;
|
|
4871
|
+
return t.kind === "tuple" && t.elements.length === 0 && !t.rest;
|
|
4739
4872
|
}
|
|
4740
4873
|
function equalTypes(a, b) {
|
|
4741
4874
|
return formatType(a) === formatType(b);
|
|
@@ -4842,7 +4975,7 @@ function containsFreeTypeParam(t, seen, bound) {
|
|
|
4842
4975
|
case "array":
|
|
4843
4976
|
return containsTypeParam(t.element, seen, bound);
|
|
4844
4977
|
case "tuple":
|
|
4845
|
-
return t.
|
|
4978
|
+
return tupleMembers(t).some((e) => containsTypeParam(e, seen, bound));
|
|
4846
4979
|
case "union":
|
|
4847
4980
|
case "intersection":
|
|
4848
4981
|
return t.types.some((m) => containsTypeParam(m, seen, bound));
|
|
@@ -4887,15 +5020,19 @@ function matchInfer(arg, pattern, out) {
|
|
|
4887
5020
|
switch (pattern.kind) {
|
|
4888
5021
|
case "array":
|
|
4889
5022
|
if (arg.kind === "array") return matchInfer(arg.element, pattern.element, out);
|
|
4890
|
-
if (arg.kind === "tuple") return matchInfer(union(arg
|
|
5023
|
+
if (arg.kind === "tuple") return matchInfer(union(tupleMembers(arg)), pattern.element, out);
|
|
4891
5024
|
return false;
|
|
4892
5025
|
case "tuple":
|
|
4893
5026
|
if (arg.kind !== "tuple" || arg.elements.length !== pattern.elements.length) return false;
|
|
5027
|
+
if (pattern.rest && !matchInfer(arg.rest ?? neverType, pattern.rest, out)) return false;
|
|
4894
5028
|
return pattern.elements.every((pt, i) => matchInfer(arg.elements[i], pt, out));
|
|
4895
5029
|
case "function": {
|
|
4896
5030
|
if (arg.kind !== "function") return false;
|
|
4897
5031
|
if (pattern.varargs?.kind === "infer" && !pattern.params.length) {
|
|
4898
|
-
out.set(pattern.varargs.name, tuple(
|
|
5032
|
+
out.set(pattern.varargs.name, tuple(
|
|
5033
|
+
arg.params.map((p) => p.type),
|
|
5034
|
+
arg.varargs && !arg.restIsWhole ? arg.varargs : void 0
|
|
5035
|
+
));
|
|
4899
5036
|
} else {
|
|
4900
5037
|
for (let i = 0; i < pattern.params.length; i++) {
|
|
4901
5038
|
if (!arg.params[i]) return false;
|
|
@@ -4969,7 +5106,7 @@ function collectTypeParams(t, out, seen = /* @__PURE__ */ new Set()) {
|
|
|
4969
5106
|
collectTypeParams(t.element, out, seen);
|
|
4970
5107
|
return;
|
|
4971
5108
|
case "tuple":
|
|
4972
|
-
for (const e of t
|
|
5109
|
+
for (const e of tupleMembers(t)) collectTypeParams(e, out, seen);
|
|
4973
5110
|
return;
|
|
4974
5111
|
case "union":
|
|
4975
5112
|
case "intersection":
|
|
@@ -5014,11 +5151,10 @@ function formatTypeUncached(t) {
|
|
|
5014
5151
|
case "literal":
|
|
5015
5152
|
return t.base === "string" ? JSON.stringify(t.value) : String(t.value);
|
|
5016
5153
|
case "array":
|
|
5017
|
-
return `${formatAtom(t.element)}[]`;
|
|
5154
|
+
return `${t.readonly ? "readonly " : ""}${formatAtom(t.element)}[]`;
|
|
5018
5155
|
case "tuple": {
|
|
5019
|
-
|
|
5020
|
-
|
|
5021
|
-
return t.isPack ? `(${inner})` : `[${inner}]`;
|
|
5156
|
+
const inner = [...t.elements.map(formatType), ...t.rest ? [`...${formatAtom(t.rest)}[]`] : []];
|
|
5157
|
+
return `${t.readonly ? "readonly " : ""}[${inner.join(", ")}]`;
|
|
5022
5158
|
}
|
|
5023
5159
|
case "object": {
|
|
5024
5160
|
if (t.name) {
|
|
@@ -5042,7 +5178,7 @@ function formatTypeUncached(t) {
|
|
|
5042
5178
|
return `${consts.has(n) ? "const " : ""}${n}${constraint ? ` extends ${briefConstraint(constraint)}` : ""}`;
|
|
5043
5179
|
}).join(", ")}>` : "";
|
|
5044
5180
|
const ps = t.params.map((p) => `${p.name ? p.name + ": " : ""}${formatType(p.type)}`);
|
|
5045
|
-
if (t.varargs) ps.push(
|
|
5181
|
+
if (t.varargs) ps.push(t.restIsWhole ? `...args: ${formatType(t.varargs)}` : `...args: ${formatAtom(t.varargs)}[]`);
|
|
5046
5182
|
return `${gen}(${ps.join(", ")}) => ${formatPredicate(t) ?? formatType(t.returns)}`;
|
|
5047
5183
|
}
|
|
5048
5184
|
case "typeParam":
|
|
@@ -5246,6 +5382,11 @@ function moduleExports(program, scopes, types, resolveModule) {
|
|
|
5246
5382
|
}
|
|
5247
5383
|
return { values, types: exportedTypes, default: defaultType };
|
|
5248
5384
|
}
|
|
5385
|
+
function isBelow(k, key) {
|
|
5386
|
+
if (!k.startsWith(key) || k.length === key.length) return false;
|
|
5387
|
+
const next = k[key.length];
|
|
5388
|
+
return next === "." || next === "#" || next === "[";
|
|
5389
|
+
}
|
|
5249
5390
|
function bindKey(id) {
|
|
5250
5391
|
return `$${id}`;
|
|
5251
5392
|
}
|
|
@@ -5263,9 +5404,6 @@ function mergeEnv(a, b, base) {
|
|
|
5263
5404
|
function mergeIndexer(a, b) {
|
|
5264
5405
|
return a ? { key: union([a.key, b.key]), value: union([a.value, b.value]) } : b;
|
|
5265
5406
|
}
|
|
5266
|
-
function producesMultipleValues(e) {
|
|
5267
|
-
return e.type === "CallExpression" || e.type === "MethodCallExpression" || e.type === "VarargExpression";
|
|
5268
|
-
}
|
|
5269
5407
|
function isFreshLiteralExpr(e) {
|
|
5270
5408
|
if (!e) return false;
|
|
5271
5409
|
switch (e.type) {
|
|
@@ -5302,11 +5440,11 @@ function collectInferNames(node) {
|
|
|
5302
5440
|
walk(n.element);
|
|
5303
5441
|
return;
|
|
5304
5442
|
case "ParenthesizedTypeNode":
|
|
5305
|
-
case "VariadicTypeNode":
|
|
5306
5443
|
walk(n.typeAnnotation);
|
|
5307
5444
|
return;
|
|
5308
5445
|
case "TupleTypeNode":
|
|
5309
5446
|
n.elements.forEach(walk);
|
|
5447
|
+
walk(n.rest);
|
|
5310
5448
|
return;
|
|
5311
5449
|
case "UnionTypeNode":
|
|
5312
5450
|
case "IntersectionTypeNode":
|
|
@@ -5324,7 +5462,6 @@ function collectInferNames(node) {
|
|
|
5324
5462
|
return;
|
|
5325
5463
|
case "FunctionTypeNode":
|
|
5326
5464
|
n.params.forEach((pp) => walk(pp.typeAnnotation));
|
|
5327
|
-
walk(n.varargType);
|
|
5328
5465
|
walk(n.returnType);
|
|
5329
5466
|
return;
|
|
5330
5467
|
case "TableTypeNode":
|
|
@@ -5369,6 +5506,75 @@ function callsSuper(block) {
|
|
|
5369
5506
|
});
|
|
5370
5507
|
return found;
|
|
5371
5508
|
}
|
|
5509
|
+
var ALL_FIELDS = /* @__PURE__ */ new Set();
|
|
5510
|
+
function definitelyAssigned(block) {
|
|
5511
|
+
const names = /* @__PURE__ */ new Set();
|
|
5512
|
+
for (const statement of block.statements) {
|
|
5513
|
+
switch (statement.type) {
|
|
5514
|
+
case "AssignmentStatement":
|
|
5515
|
+
case "CompoundAssignmentStatement":
|
|
5516
|
+
for (const name of assignedFields({ ...block, statements: [statement] })) names.add(name);
|
|
5517
|
+
break;
|
|
5518
|
+
case "DoStatement": {
|
|
5519
|
+
const inner = definitelyAssigned(statement.body);
|
|
5520
|
+
if (!inner) return void 0;
|
|
5521
|
+
for (const name of inner) names.add(name);
|
|
5522
|
+
break;
|
|
5523
|
+
}
|
|
5524
|
+
case "IfStatement": {
|
|
5525
|
+
if (!statement.alternate) break;
|
|
5526
|
+
const arms = [...statement.clauses.map((c) => c.body), statement.alternate].map(definitelyAssigned);
|
|
5527
|
+
const finishing = arms.filter((arm) => arm !== void 0);
|
|
5528
|
+
if (!finishing.length) return void 0;
|
|
5529
|
+
for (const name of finishing[0]) {
|
|
5530
|
+
if (finishing.every((arm) => arm.has(name))) names.add(name);
|
|
5531
|
+
}
|
|
5532
|
+
break;
|
|
5533
|
+
}
|
|
5534
|
+
case "ReturnStatement":
|
|
5535
|
+
return void 0;
|
|
5536
|
+
case "CallStatement": {
|
|
5537
|
+
const call = statement.expression;
|
|
5538
|
+
if (call.type === "CallExpression" && call.callee.type === "Identifier" && call.callee.name === "error") {
|
|
5539
|
+
return void 0;
|
|
5540
|
+
}
|
|
5541
|
+
break;
|
|
5542
|
+
}
|
|
5543
|
+
}
|
|
5544
|
+
}
|
|
5545
|
+
return names;
|
|
5546
|
+
}
|
|
5547
|
+
function withoutReceiver(t) {
|
|
5548
|
+
if (t.kind === "intersection") return intersection(t.types.map(withoutReceiver));
|
|
5549
|
+
if (t.kind !== "function") return t;
|
|
5550
|
+
const first = t.params[0]?.name;
|
|
5551
|
+
if (first !== "this" && first !== "self") return t;
|
|
5552
|
+
return { ...t, params: t.params.slice(1) };
|
|
5553
|
+
}
|
|
5554
|
+
var METAMETHOD_OPERANDS = {
|
|
5555
|
+
__add: 1,
|
|
5556
|
+
__sub: 1,
|
|
5557
|
+
__mul: 1,
|
|
5558
|
+
__div: 1,
|
|
5559
|
+
__idiv: 1,
|
|
5560
|
+
__mod: 1,
|
|
5561
|
+
__pow: 1,
|
|
5562
|
+
__concat: 1,
|
|
5563
|
+
__eq: 1,
|
|
5564
|
+
__lt: 1,
|
|
5565
|
+
__le: 1,
|
|
5566
|
+
__unm: 0,
|
|
5567
|
+
__len: 0,
|
|
5568
|
+
__tostring: 0,
|
|
5569
|
+
__iter: 0,
|
|
5570
|
+
__call: void 0
|
|
5571
|
+
};
|
|
5572
|
+
var METAMETHOD_RETURNS = {
|
|
5573
|
+
__tostring: stringType,
|
|
5574
|
+
__eq: booleanType,
|
|
5575
|
+
__lt: booleanType,
|
|
5576
|
+
__le: booleanType
|
|
5577
|
+
};
|
|
5372
5578
|
function assignedFields(block) {
|
|
5373
5579
|
const names = /* @__PURE__ */ new Set();
|
|
5374
5580
|
walkNodes(block, (node) => {
|
|
@@ -5394,7 +5600,7 @@ function walkNodes(root, visit) {
|
|
|
5394
5600
|
if (key !== "line" && key !== "column" && value && typeof value === "object") walkNodes(value, visit);
|
|
5395
5601
|
}
|
|
5396
5602
|
}
|
|
5397
|
-
var
|
|
5603
|
+
var LazyMap = class extends Map {
|
|
5398
5604
|
pending = /* @__PURE__ */ new Map();
|
|
5399
5605
|
defer(name, resolve5) {
|
|
5400
5606
|
super.delete(name);
|
|
@@ -5471,7 +5677,7 @@ function expressionLabel(e, depth = 0) {
|
|
|
5471
5677
|
case "IndexExpression": {
|
|
5472
5678
|
const o = expressionLabel(e.object, depth + 1);
|
|
5473
5679
|
const i = e.index.type === "StringLiteral" ? JSON.stringify(e.index.value) : e.index.type === "NumberLiteral" ? e.index.raw : e.index.type === "Identifier" ? e.index.name : "...";
|
|
5474
|
-
return o === void 0 ? void 0 : `${o}[${i}]`;
|
|
5680
|
+
return o === void 0 ? void 0 : `${o}${e.optional ? "?." : ""}[${i}]`;
|
|
5475
5681
|
}
|
|
5476
5682
|
case "ParenthesizedExpression": {
|
|
5477
5683
|
const inner = expressionLabel(e.expression, depth + 1);
|
|
@@ -5509,20 +5715,21 @@ var TypeAnalyzer = class {
|
|
|
5509
5715
|
scopes;
|
|
5510
5716
|
options;
|
|
5511
5717
|
typeOf = /* @__PURE__ */ new Map();
|
|
5512
|
-
bindingType =
|
|
5718
|
+
bindingType = new LazyMap();
|
|
5513
5719
|
narrowedTypeOf = /* @__PURE__ */ new Map();
|
|
5514
5720
|
typeOfTypeNode = /* @__PURE__ */ new Map();
|
|
5515
5721
|
expectedTypeOf = /* @__PURE__ */ new Map();
|
|
5516
|
-
|
|
5722
|
+
loops = /* @__PURE__ */ new Map();
|
|
5517
5723
|
/** Public: each alias resolved once (generic aliases keep their params as
|
|
5518
5724
|
* `typeParam` nodes in the body). */
|
|
5519
|
-
aliases = new
|
|
5725
|
+
aliases = new LazyMap();
|
|
5520
5726
|
/** Uninstantiated alias definitions, for `Name<Args>` instantiation. */
|
|
5521
5727
|
aliasDefs = /* @__PURE__ */ new Map();
|
|
5522
5728
|
/** Put on every ref to one of `aliasDefs`, so a module that imports the
|
|
5523
5729
|
* type expands the ref here rather than by its name there. */
|
|
5524
5730
|
origin = { expand: (t) => this.expand(t) };
|
|
5525
|
-
/** See `resolveClass`.
|
|
5731
|
+
/** See `resolveClass`. Shared with other analyses over the same libraries
|
|
5732
|
+
* when none of their names is redefined here; see `shareableClasses`. */
|
|
5526
5733
|
classTypes = /* @__PURE__ */ new WeakMap();
|
|
5527
5734
|
/** See `instanceType` — one instance type per `class ... end`. */
|
|
5528
5735
|
instanceTypes = /* @__PURE__ */ new WeakMap();
|
|
@@ -5530,8 +5737,12 @@ var TypeAnalyzer = class {
|
|
|
5530
5737
|
/** Generic parameters currently in lexical scope (alias body / generic fn),
|
|
5531
5738
|
* with their `extends` constraints resolved. */
|
|
5532
5739
|
typeParamScope = [];
|
|
5533
|
-
/** Global types contributed by `declare` statements (libs, then this program).
|
|
5534
|
-
|
|
5740
|
+
/** Global types contributed by `declare` statements (libs, then this program).
|
|
5741
|
+
* A library's are resolved when the name is first used: an engine's
|
|
5742
|
+
* definitions declare hundreds of globals, and a script uses a few. */
|
|
5743
|
+
libGlobalTypes = new LazyMap();
|
|
5744
|
+
/** Each library `declare` of a name, in order, until it is resolved. */
|
|
5745
|
+
libDeclares = /* @__PURE__ */ new Map();
|
|
5535
5746
|
/** Declaration node -> binding, built once so `bindingIdByName` is O(1)
|
|
5536
5747
|
* instead of a scan of every binding per declaration site. */
|
|
5537
5748
|
bindingByDecl = /* @__PURE__ */ new Map();
|
|
@@ -5564,6 +5775,11 @@ var TypeAnalyzer = class {
|
|
|
5564
5775
|
/** Recursion guard for `preVisitBody`. */
|
|
5565
5776
|
preVisitDepth = 0;
|
|
5566
5777
|
run() {
|
|
5778
|
+
const shared = shareableClasses(this.options.libs ?? [], this.program);
|
|
5779
|
+
if (shared) {
|
|
5780
|
+
this.classTypes = shared.classTypes;
|
|
5781
|
+
this.classMembers = shared.classMembers;
|
|
5782
|
+
}
|
|
5567
5783
|
this.registerAliasDefs(preludeProgram().body, true);
|
|
5568
5784
|
for (const lib of this.options.libs ?? []) this.registerAliasDefs(lib.body, true);
|
|
5569
5785
|
this.registerAliasDefs(this.program.body);
|
|
@@ -5576,8 +5792,11 @@ var TypeAnalyzer = class {
|
|
|
5576
5792
|
this.indexDeclarations();
|
|
5577
5793
|
for (const [name, id] of this.scopes.globalsByName) {
|
|
5578
5794
|
if (this.deferredDeclares.has(name) && !this.options.globalTypes?.[name]) continue;
|
|
5579
|
-
const
|
|
5580
|
-
this.bindingType.set(id,
|
|
5795
|
+
const given = this.options.globalTypes?.[name];
|
|
5796
|
+
if (given) this.bindingType.set(id, given);
|
|
5797
|
+
else if (this.libGlobalTypes.has(name)) this.bindingType.defer(id, () => this.libGlobalTypes.get(name) ?? anyType);
|
|
5798
|
+
else if (LANGUAGE_GLOBALS.includes(name)) this.bindingType.set(id, arrayOf(unknownType));
|
|
5799
|
+
else this.bindingType.set(id, anyType);
|
|
5581
5800
|
}
|
|
5582
5801
|
setAliasExpander((t) => this.expand(t));
|
|
5583
5802
|
setDeferredBound((t) => this.deferredBound(t));
|
|
@@ -5597,7 +5816,7 @@ var TypeAnalyzer = class {
|
|
|
5597
5816
|
typeOfTypeNode: this.typeOfTypeNode,
|
|
5598
5817
|
expectedTypeOf: this.expectedTypeOf,
|
|
5599
5818
|
aliases: this.resolveDeferredAliases(),
|
|
5600
|
-
|
|
5819
|
+
loops: this.loops,
|
|
5601
5820
|
diagnostics: this.diagnostics
|
|
5602
5821
|
};
|
|
5603
5822
|
}
|
|
@@ -5949,28 +6168,55 @@ var TypeAnalyzer = class {
|
|
|
5949
6168
|
type.name = `typeof ${this.className(stmt)}`;
|
|
5950
6169
|
const shape = this.shapeOf(stmt);
|
|
5951
6170
|
const local = this.superDecl(stmt);
|
|
5952
|
-
const parent = local ? this.classValueType(local) : stmt.superclass ? this.
|
|
6171
|
+
const parent = local ? this.classValueType(local) : stmt.superclass ? this.classStaticsOf(stmt.superclass) : void 0;
|
|
5953
6172
|
if (parent?.kind === "object") {
|
|
5954
6173
|
for (const [key, property] of parent.properties) {
|
|
5955
6174
|
if (!CLASS_LINKS.has(key)) type.properties.set(key, property);
|
|
5956
6175
|
}
|
|
5957
6176
|
}
|
|
5958
6177
|
for (const [key, property] of shape.statics) type.properties.set(key, property);
|
|
5959
|
-
|
|
6178
|
+
if (shape.filling) {
|
|
6179
|
+
(shape.filled ??= []).push(() => {
|
|
6180
|
+
for (const [key, property] of shape.statics) type.properties.set(key, property);
|
|
6181
|
+
});
|
|
6182
|
+
}
|
|
5960
6183
|
const params = this.classTypeParams(stmt);
|
|
5961
|
-
|
|
5962
|
-
|
|
5963
|
-
constructor?.params.filter((p) => p.name !== "this") ?? [],
|
|
5964
|
-
this.selfTypeOf(stmt),
|
|
5965
|
-
constructor?.varargs,
|
|
5966
|
-
params.map((p) => p.name)
|
|
5967
|
-
),
|
|
6184
|
+
let settled;
|
|
6185
|
+
const construct = {
|
|
5968
6186
|
optional: false,
|
|
5969
|
-
readonly: true
|
|
6187
|
+
readonly: true,
|
|
6188
|
+
// Kept, so a class extending it can read what it takes; reaching
|
|
6189
|
+
// it from outside is the error.
|
|
6190
|
+
...stmt.isAbstract ? { abstract: true } : {}
|
|
6191
|
+
};
|
|
6192
|
+
Object.defineProperty(construct, "type", {
|
|
6193
|
+
enumerable: true,
|
|
6194
|
+
get: () => {
|
|
6195
|
+
if (settled) return settled;
|
|
6196
|
+
const constructor = this.constructorType(stmt);
|
|
6197
|
+
const made = fn(
|
|
6198
|
+
constructor?.params.filter((p) => p.name !== "this") ?? [],
|
|
6199
|
+
this.selfTypeOf(stmt),
|
|
6200
|
+
constructor?.varargs,
|
|
6201
|
+
params.map((p) => p.name)
|
|
6202
|
+
);
|
|
6203
|
+
if (!this.shapeOf(stmt).filling) settled = made;
|
|
6204
|
+
return made;
|
|
6205
|
+
}
|
|
5970
6206
|
});
|
|
6207
|
+
type.properties.set("new", construct);
|
|
5971
6208
|
type.properties.set("ParentClass", { type: parent ?? nilType, optional: false, readonly: true });
|
|
5972
6209
|
return type;
|
|
5973
6210
|
}
|
|
6211
|
+
/** The value side of the class `superclass` names when it is not
|
|
6212
|
+
* declared in this file: what the name it refers to holds — an import
|
|
6213
|
+
* brings the class table with it — or else a class of that name. */
|
|
6214
|
+
classStaticsOf(superclass) {
|
|
6215
|
+
const id = this.bindingIdOf(superclass);
|
|
6216
|
+
const bound = id !== void 0 ? this.expand(this.bindingType.get(id) ?? unknownType) : void 0;
|
|
6217
|
+
if (bound?.kind === "object") return bound;
|
|
6218
|
+
return this.classStaticsByNameType(superclass.name);
|
|
6219
|
+
}
|
|
5974
6220
|
/** The value side of a class named by a binding rather than by a
|
|
5975
6221
|
* declaration in this file — an imported one. */
|
|
5976
6222
|
classStaticsByNameType(name) {
|
|
@@ -6031,20 +6277,26 @@ var TypeAnalyzer = class {
|
|
|
6031
6277
|
} finally {
|
|
6032
6278
|
this.emitDiagnostics = wasEmitting;
|
|
6033
6279
|
shape.filling = false;
|
|
6280
|
+
for (const then of shape.filled ?? []) then();
|
|
6281
|
+
shape.filled = void 0;
|
|
6034
6282
|
}
|
|
6035
6283
|
return shape;
|
|
6036
6284
|
}
|
|
6037
6285
|
fillShape(stmt, shape) {
|
|
6038
6286
|
const className = this.className(stmt);
|
|
6039
6287
|
const put = (isStatic, name, property) => {
|
|
6040
|
-
const member = stmt.members.find((m) => m.type !== "ClassConstructor" && m.isStatic === isStatic && m.name.name === name && m.accessibility === "private");
|
|
6041
|
-
const marked = member ? { ...property, private: { owner: stmt, className } } : property;
|
|
6288
|
+
const member = stmt.members.find((m) => m.type !== "ClassConstructor" && m.isStatic === isStatic && m.name.name === name && (m.accessibility === "private" || m.accessibility === "protected"));
|
|
6289
|
+
const marked = member ? { ...property, private: { owner: stmt, className, ...member.accessibility === "protected" ? { protected: true } : {} } } : property;
|
|
6042
6290
|
(isStatic ? shape.statics : shape.instance).set(name, marked);
|
|
6043
6291
|
};
|
|
6044
6292
|
for (const member of stmt.members) {
|
|
6045
6293
|
if (member.type !== "ClassField") continue;
|
|
6046
6294
|
const type = member.typeAnnotation ? this.resolveType(member.typeAnnotation) : member.init ? widen(this.infer(member.init, /* @__PURE__ */ new Map())) : anyType;
|
|
6047
|
-
put(member.isStatic, member.name.name, {
|
|
6295
|
+
put(member.isStatic, member.name.name, {
|
|
6296
|
+
type,
|
|
6297
|
+
optional: false,
|
|
6298
|
+
...member.isReadonly ? { readonly: true } : {}
|
|
6299
|
+
});
|
|
6048
6300
|
}
|
|
6049
6301
|
for (const member of stmt.members) {
|
|
6050
6302
|
switch (member.type) {
|
|
@@ -6053,7 +6305,11 @@ var TypeAnalyzer = class {
|
|
|
6053
6305
|
case "ClassMethod": {
|
|
6054
6306
|
this.paramsFromSignatures(member.func, member.signatures);
|
|
6055
6307
|
const type = member.signatures?.length ? intersection(member.signatures.map((sig) => this.signatureToFnType(sig))) : this.inferFunctionBody(member.func, /* @__PURE__ */ new Map());
|
|
6056
|
-
put(member.isStatic, member.name.name, {
|
|
6308
|
+
put(member.isStatic, member.name.name, {
|
|
6309
|
+
type,
|
|
6310
|
+
optional: false,
|
|
6311
|
+
...member.isAbstract ? { abstract: true } : {}
|
|
6312
|
+
});
|
|
6057
6313
|
break;
|
|
6058
6314
|
}
|
|
6059
6315
|
case "ClassAccessor": {
|
|
@@ -6109,7 +6365,14 @@ var TypeAnalyzer = class {
|
|
|
6109
6365
|
for (const signature of member.signatures ?? []) {
|
|
6110
6366
|
this.checkParamOrder(signature.params, member);
|
|
6111
6367
|
}
|
|
6112
|
-
|
|
6368
|
+
if (member.type === "ClassMethod" && member.isAbstract) continue;
|
|
6369
|
+
const constructing = this.constructing;
|
|
6370
|
+
if (member.type === "ClassConstructor") this.constructing = stmt;
|
|
6371
|
+
try {
|
|
6372
|
+
this.visitFunctionBody(member.func, env);
|
|
6373
|
+
} finally {
|
|
6374
|
+
this.constructing = constructing;
|
|
6375
|
+
}
|
|
6113
6376
|
}
|
|
6114
6377
|
});
|
|
6115
6378
|
}));
|
|
@@ -6156,13 +6419,148 @@ var TypeAnalyzer = class {
|
|
|
6156
6419
|
if (stmt.superclass && this.baseInstance(stmt) && constructor && !callsSuper(constructor.func.body)) {
|
|
6157
6420
|
report(constructor, `'${name}' extends '${stmt.superclass.name}', so its constructor must call 'super(...)'`);
|
|
6158
6421
|
}
|
|
6159
|
-
|
|
6422
|
+
this.checkAbstractMembers(stmt);
|
|
6423
|
+
this.checkOverrides(stmt);
|
|
6424
|
+
this.checkImplements(stmt);
|
|
6425
|
+
this.checkMetamethods(stmt);
|
|
6426
|
+
const assigned = constructor ? definitelyAssigned(constructor.func.body) ?? ALL_FIELDS : /* @__PURE__ */ new Set();
|
|
6160
6427
|
for (const member of stmt.members) {
|
|
6161
6428
|
if (member.type !== "ClassField" || member.isStatic || member.init) continue;
|
|
6162
|
-
if (assigned.has(member.name.name)) continue;
|
|
6429
|
+
if (assigned === ALL_FIELDS || assigned.has(member.name.name)) continue;
|
|
6163
6430
|
const type = member.typeAnnotation ? this.withTypeParams(this.classTypeParams(stmt), () => this.resolveType(member.typeAnnotation)) : anyType;
|
|
6164
6431
|
if (isAssignable(nilType, type)) continue;
|
|
6165
|
-
report(member.name, `'${member.name.name}' has no value: give it one, assign it in the constructor, or let its type admit nil`);
|
|
6432
|
+
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`);
|
|
6433
|
+
}
|
|
6434
|
+
}
|
|
6435
|
+
/** The class whose constructor is being checked: it, and only it, may
|
|
6436
|
+
* assign the `readonly` fields it declares. */
|
|
6437
|
+
constructing;
|
|
6438
|
+
/** A class that is not abstract has a `new`, so everything its instances
|
|
6439
|
+
* promise has to be there: no abstract member of its own, and every one
|
|
6440
|
+
* it inherits written. */
|
|
6441
|
+
checkAbstractMembers(stmt) {
|
|
6442
|
+
if (stmt.isAbstract) return;
|
|
6443
|
+
const name = this.className(stmt);
|
|
6444
|
+
for (const member of stmt.members) {
|
|
6445
|
+
if (member.type === "ClassMethod" && member.isAbstract) {
|
|
6446
|
+
this.diagnostics.push({
|
|
6447
|
+
node: member.name,
|
|
6448
|
+
message: `'${member.name.name}' is abstract, so '${name}' has to be an 'abstract class'`
|
|
6449
|
+
});
|
|
6450
|
+
}
|
|
6451
|
+
}
|
|
6452
|
+
const own = this.shapeOf(stmt).instance;
|
|
6453
|
+
const missing = [...this.instanceType(stmt).properties].filter(([key, property]) => property.abstract && !own.has(key)).map(([key]) => `'${key}'`);
|
|
6454
|
+
if (missing.length) {
|
|
6455
|
+
this.diagnostics.push({
|
|
6456
|
+
node: stmt.name ?? stmt,
|
|
6457
|
+
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`
|
|
6458
|
+
});
|
|
6459
|
+
}
|
|
6460
|
+
}
|
|
6461
|
+
/** A member written again in a class that extends another has to fit
|
|
6462
|
+
* where the base's stood — anything reading an instance as the base
|
|
6463
|
+
* would otherwise be told wrong. `override` says a member is such a one,
|
|
6464
|
+
* and is wrong on a member that is not. */
|
|
6465
|
+
checkOverrides(stmt) {
|
|
6466
|
+
const base = this.baseInstance(stmt);
|
|
6467
|
+
const name = this.className(stmt);
|
|
6468
|
+
const own = this.shapeOf(stmt).instance;
|
|
6469
|
+
for (const member of stmt.members) {
|
|
6470
|
+
if (member.type === "ClassConstructor" || member.isStatic) continue;
|
|
6471
|
+
const key = member.name.name;
|
|
6472
|
+
const inherited = CLASS_LINKS.has(key) ? void 0 : base?.properties.get(key);
|
|
6473
|
+
if (member.isOverride && !inherited) {
|
|
6474
|
+
this.diagnostics.push({
|
|
6475
|
+
node: member.name,
|
|
6476
|
+
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`
|
|
6477
|
+
});
|
|
6478
|
+
continue;
|
|
6479
|
+
}
|
|
6480
|
+
const mine = own.get(key);
|
|
6481
|
+
if (!inherited || !mine || inherited.private && !inherited.private.protected) continue;
|
|
6482
|
+
const written = withoutReceiver(mine.type);
|
|
6483
|
+
const expected = withoutReceiver(inherited.type);
|
|
6484
|
+
if (isAssignable(written, expected)) continue;
|
|
6485
|
+
this.diagnostics.push({
|
|
6486
|
+
node: member.name,
|
|
6487
|
+
message: `'${key}' in '${name}' does not fit the '${key}' of '${stmt.superclass.name}' it replaces: '${briefType(written)}' is not assignable to '${briefType(expected)}'`
|
|
6488
|
+
});
|
|
6489
|
+
}
|
|
6490
|
+
}
|
|
6491
|
+
/** `implements Shape`: every member the shape names, of a type that fits.
|
|
6492
|
+
* A method's own `this` is left out of the comparison on both sides. */
|
|
6493
|
+
checkImplements(stmt) {
|
|
6494
|
+
if (!stmt.implements?.length) return;
|
|
6495
|
+
const name = this.className(stmt);
|
|
6496
|
+
const instance = this.instanceType(stmt).properties;
|
|
6497
|
+
for (const node of stmt.implements) {
|
|
6498
|
+
const shown = node.type === "TypeReference" ? node.namespace ? `${node.namespace}.${node.base}` : node.base : void 0;
|
|
6499
|
+
const target = this.expand(this.withTypeParams(this.classTypeParams(stmt), () => this.resolveType(node)));
|
|
6500
|
+
if (target.kind === "any") continue;
|
|
6501
|
+
if (target.kind !== "object") {
|
|
6502
|
+
this.diagnostics.push({
|
|
6503
|
+
node,
|
|
6504
|
+
message: `A class can only implement an object type or a class, not '${briefType(target)}'`
|
|
6505
|
+
});
|
|
6506
|
+
continue;
|
|
6507
|
+
}
|
|
6508
|
+
const label = shown ?? briefType(target);
|
|
6509
|
+
const missing = [];
|
|
6510
|
+
for (const [key, wanted] of target.properties) {
|
|
6511
|
+
const have = instance.get(key);
|
|
6512
|
+
if (!have) {
|
|
6513
|
+
if (!wanted.optional) missing.push(`'${key}'`);
|
|
6514
|
+
continue;
|
|
6515
|
+
}
|
|
6516
|
+
const written = withoutReceiver(have.type);
|
|
6517
|
+
const expected = withoutReceiver(wanted.optional ? optional(wanted.type) : wanted.type);
|
|
6518
|
+
if (isAssignable(written, expected)) continue;
|
|
6519
|
+
this.diagnostics.push({
|
|
6520
|
+
node,
|
|
6521
|
+
message: `'${name}' does not implement '${label}': its '${key}' is '${briefType(written)}', not '${briefType(expected)}'`
|
|
6522
|
+
});
|
|
6523
|
+
}
|
|
6524
|
+
if (missing.length) {
|
|
6525
|
+
this.diagnostics.push({
|
|
6526
|
+
node,
|
|
6527
|
+
message: `'${name}' does not implement '${label}': ${missing.join(", ")} ${missing.length === 1 ? "is" : "are"} missing`
|
|
6528
|
+
});
|
|
6529
|
+
}
|
|
6530
|
+
}
|
|
6531
|
+
}
|
|
6532
|
+
/** A method named for a metamethod *is* one — the class table is its
|
|
6533
|
+
* instances' metatable — so it has to take what Luau hands it and give
|
|
6534
|
+
* back what Luau expects. */
|
|
6535
|
+
checkMetamethods(stmt) {
|
|
6536
|
+
const instance = this.shapeOf(stmt).instance;
|
|
6537
|
+
for (const member of stmt.members) {
|
|
6538
|
+
if (member.type !== "ClassMethod" || !(member.name.name in METAMETHOD_OPERANDS)) continue;
|
|
6539
|
+
const key = member.name.name;
|
|
6540
|
+
if (member.isStatic) {
|
|
6541
|
+
this.diagnostics.push({
|
|
6542
|
+
node: member.name,
|
|
6543
|
+
message: `'${key}' is a metamethod of the instances; it cannot be static`
|
|
6544
|
+
});
|
|
6545
|
+
continue;
|
|
6546
|
+
}
|
|
6547
|
+
const operands = METAMETHOD_OPERANDS[key];
|
|
6548
|
+
const written = member.func.params.filter((p) => p.name !== "this").length;
|
|
6549
|
+
if (operands !== void 0 && (written !== operands || member.func.hasVarargs)) {
|
|
6550
|
+
this.diagnostics.push({
|
|
6551
|
+
node: member.name,
|
|
6552
|
+
message: operands === 0 ? `'${key}' takes nothing besides 'this'` : `'${key}' takes one operand besides 'this'`
|
|
6553
|
+
});
|
|
6554
|
+
continue;
|
|
6555
|
+
}
|
|
6556
|
+
const required = METAMETHOD_RETURNS[key];
|
|
6557
|
+
const returns = this.overloadsOf(instance.get(key)?.type ?? anyType)[0]?.returns;
|
|
6558
|
+
if (required && returns && !isAssignable(returns, required)) {
|
|
6559
|
+
this.diagnostics.push({
|
|
6560
|
+
node: member.name,
|
|
6561
|
+
message: `'${key}' has to return ${briefType(required)}, not '${briefType(returns)}'`
|
|
6562
|
+
});
|
|
6563
|
+
}
|
|
6166
6564
|
}
|
|
6167
6565
|
}
|
|
6168
6566
|
/** `extends` must name a class, and the chain must end. */
|
|
@@ -6220,19 +6618,42 @@ var TypeAnalyzer = class {
|
|
|
6220
6618
|
harvestDeclares(block, own = false) {
|
|
6221
6619
|
for (const stmt of block.statements) {
|
|
6222
6620
|
if (stmt.type !== "DeclareStatement") continue;
|
|
6223
|
-
if (own
|
|
6621
|
+
if (!own) {
|
|
6622
|
+
const declares = this.libDeclares.get(stmt.name);
|
|
6623
|
+
if (declares) declares.push(stmt);
|
|
6624
|
+
else {
|
|
6625
|
+
this.libDeclares.set(stmt.name, [stmt]);
|
|
6626
|
+
this.libGlobalTypes.defer(stmt.name, () => this.resolveLibDeclares(stmt.name));
|
|
6627
|
+
}
|
|
6628
|
+
continue;
|
|
6629
|
+
}
|
|
6630
|
+
if (containsTypeQuery(stmt.valueType) || referencedTypeNames(stmt.valueType).some((name) => this.dependsOnTypeQuery(name))) {
|
|
6224
6631
|
this.deferredDeclares.set(stmt.name, stmt);
|
|
6225
6632
|
continue;
|
|
6226
6633
|
}
|
|
6227
|
-
|
|
6228
|
-
const prev = this.libGlobalTypes.get(stmt.name);
|
|
6229
|
-
const overload = prev && stmt.valueType.type === "FunctionTypeNode" && (prev.kind === "function" || prev.kind === "intersection");
|
|
6230
|
-
this.libGlobalTypes.set(
|
|
6231
|
-
stmt.name,
|
|
6232
|
-
overload ? intersection([prev, t]) : this.mergeDeclared(prev, t)
|
|
6233
|
-
);
|
|
6634
|
+
this.libGlobalTypes.set(stmt.name, this.declaredOver(this.libGlobalTypes.get(stmt.name), stmt));
|
|
6234
6635
|
}
|
|
6235
6636
|
}
|
|
6637
|
+
/** Every library `declare` of `name`, in order. They are written at the
|
|
6638
|
+
* top level of their files, so they are read as if there — outside any
|
|
6639
|
+
* generic the use that asked for them happens to sit in. */
|
|
6640
|
+
resolveLibDeclares(name) {
|
|
6641
|
+
const scope = this.typeParamScope.splice(0);
|
|
6642
|
+
try {
|
|
6643
|
+
let type;
|
|
6644
|
+
for (const stmt of this.libDeclares.get(name) ?? []) type = this.declaredOver(type, stmt);
|
|
6645
|
+
return type ?? anyType;
|
|
6646
|
+
} finally {
|
|
6647
|
+
this.typeParamScope.push(...scope);
|
|
6648
|
+
}
|
|
6649
|
+
}
|
|
6650
|
+
/** A `declare` on top of what earlier ones said about the same name: a
|
|
6651
|
+
* function adds an overload, a table adds members. */
|
|
6652
|
+
declaredOver(prev, stmt) {
|
|
6653
|
+
const t = this.resolveType(stmt.valueType);
|
|
6654
|
+
const overload = prev && stmt.valueType.type === "FunctionTypeNode" && (prev.kind === "function" || prev.kind === "intersection");
|
|
6655
|
+
return overload ? intersection([prev, t]) : this.mergeDeclared(prev, t);
|
|
6656
|
+
}
|
|
6236
6657
|
resolveAllAliases() {
|
|
6237
6658
|
for (const [name, def] of this.aliasDefs) {
|
|
6238
6659
|
if (def.class && !this.program.body.statements.includes(def.class)) {
|
|
@@ -6278,11 +6699,10 @@ var TypeAnalyzer = class {
|
|
|
6278
6699
|
}
|
|
6279
6700
|
imported;
|
|
6280
6701
|
/** What a `return` gives, against what the function declared. */
|
|
6281
|
-
checkReturn(stmt, declared,
|
|
6702
|
+
checkReturn(stmt, declared, actual, env) {
|
|
6282
6703
|
if (!declared || !this.emitDiagnostics) return;
|
|
6283
6704
|
if (declared.kind === "any" || declared.kind === "unknown" || this.namesNothing(declared)) return;
|
|
6284
|
-
const
|
|
6285
|
-
const source = stmt.arguments.length === 1 ? sources[0] : void 0;
|
|
6705
|
+
const source = stmt.argument;
|
|
6286
6706
|
const fits = source ? this.fitsAnnotation(source, declared, actual, env) : isAssignable(actual, declared) || isAssignable(widen(actual), declared);
|
|
6287
6707
|
if (fits) return;
|
|
6288
6708
|
this.diagnostics.push({
|
|
@@ -6430,21 +6850,12 @@ var TypeAnalyzer = class {
|
|
|
6430
6850
|
this.instantiationDepth--;
|
|
6431
6851
|
}
|
|
6432
6852
|
}
|
|
6433
|
-
/** Pair written type arguments with the parameters they instantiate.
|
|
6434
|
-
*
|
|
6435
|
-
* one pack: `Signal<Instance, string>` binds `T` to `(Instance, string)`,
|
|
6436
|
-
* and `Signal<()>` to the empty pack. Left out, a parameter takes its
|
|
6437
|
-
* default (`T... = ...any` is `any`), or `unknown`. */
|
|
6853
|
+
/** Pair written type arguments with the parameters they instantiate.
|
|
6854
|
+
* Left out, a parameter takes its default, or `unknown`. */
|
|
6438
6855
|
bindTypeArguments(params, args) {
|
|
6439
6856
|
const subst = /* @__PURE__ */ new Map();
|
|
6440
6857
|
params.forEach((p, i) => {
|
|
6441
|
-
|
|
6442
|
-
if (p.isPack && i < args.length) {
|
|
6443
|
-
const rest = args.slice(i);
|
|
6444
|
-
const single = rest.length === 1 ? rest[0] : void 0;
|
|
6445
|
-
arg = single && (single.kind === "tuple" && single.isPack || single.kind === "typeParam" || single.kind === "any") ? single : tuple([...rest], true);
|
|
6446
|
-
}
|
|
6447
|
-
subst.set(p.name, arg ?? (p.default ? this.resolveType(p.default) : unknownType));
|
|
6858
|
+
subst.set(p.name, args[i] ?? (p.default ? this.resolveType(p.default) : unknownType));
|
|
6448
6859
|
});
|
|
6449
6860
|
return subst;
|
|
6450
6861
|
}
|
|
@@ -6534,10 +6945,18 @@ var TypeAnalyzer = class {
|
|
|
6534
6945
|
return literal(node.value);
|
|
6535
6946
|
case "TypeLiteralNumber":
|
|
6536
6947
|
return literal(node.value);
|
|
6537
|
-
case "ArrayTypeNode":
|
|
6538
|
-
|
|
6539
|
-
|
|
6540
|
-
|
|
6948
|
+
case "ArrayTypeNode": {
|
|
6949
|
+
const made = arrayOf(this.resolveType(node.element));
|
|
6950
|
+
return node.isReadonly ? { ...made, readonly: true } : made;
|
|
6951
|
+
}
|
|
6952
|
+
case "TupleTypeNode": {
|
|
6953
|
+
const rest = node.rest && this.expand(this.resolveType(node.rest));
|
|
6954
|
+
const made = tuple(
|
|
6955
|
+
node.elements.map((e) => this.resolveType(e)),
|
|
6956
|
+
rest ? rest.kind === "array" ? rest.element : rest.kind === "any" ? anyType : unknownType : void 0
|
|
6957
|
+
);
|
|
6958
|
+
return node.isReadonly && made.kind === "tuple" ? { ...made, readonly: true } : made;
|
|
6959
|
+
}
|
|
6541
6960
|
case "UnionTypeNode":
|
|
6542
6961
|
return union(node.types.map((t) => this.resolveType(t)));
|
|
6543
6962
|
case "IntersectionTypeNode":
|
|
@@ -6573,15 +6992,13 @@ var TypeAnalyzer = class {
|
|
|
6573
6992
|
type: p.optional ? optional(this.resolveType(p.typeAnnotation)) : this.resolveType(p.typeAnnotation),
|
|
6574
6993
|
optional: p.optional
|
|
6575
6994
|
}));
|
|
6576
|
-
|
|
6577
|
-
const restElement = restParam ? this.resolveType(restParam.typeAnnotation) : void 0;
|
|
6578
|
-
return this.withTypeParamDefaults(fn(
|
|
6995
|
+
return this.withTypeParamDefaults(this.withRest(fn(
|
|
6579
6996
|
params,
|
|
6580
6997
|
this.resolveType(node.returnType),
|
|
6581
|
-
|
|
6998
|
+
void 0,
|
|
6582
6999
|
names,
|
|
6583
7000
|
this.resolvePredicate(node.predicate, params)
|
|
6584
|
-
), node.generics);
|
|
7001
|
+
), node.params), node.generics);
|
|
6585
7002
|
});
|
|
6586
7003
|
}
|
|
6587
7004
|
case "TypeofTypeNode": {
|
|
@@ -6645,13 +7062,6 @@ var TypeAnalyzer = class {
|
|
|
6645
7062
|
})
|
|
6646
7063
|
);
|
|
6647
7064
|
}
|
|
6648
|
-
case "VariadicTypeNode":
|
|
6649
|
-
return this.resolveType(node.typeAnnotation);
|
|
6650
|
-
case "TypePackNode": {
|
|
6651
|
-
if (node.types.length === 1 && !node.hasVarargs) return this.resolveType(node.types[0]);
|
|
6652
|
-
if (!node.types.length && node.varargType) return this.resolveType(node.varargType);
|
|
6653
|
-
return tuple(node.types.map((t) => this.resolveType(t)), true);
|
|
6654
|
-
}
|
|
6655
7065
|
}
|
|
6656
7066
|
}
|
|
6657
7067
|
// --------------------------------------------------------
|
|
@@ -6717,15 +7127,18 @@ var TypeAnalyzer = class {
|
|
|
6717
7127
|
case "array":
|
|
6718
7128
|
return arrayOf(this.reduceType(t.element));
|
|
6719
7129
|
case "tuple":
|
|
6720
|
-
return tuple(t.elements.map((e) => this.reduceType(e)), t.
|
|
7130
|
+
return tuple(t.elements.map((e) => this.reduceType(e)), t.rest && this.reduceType(t.rest));
|
|
6721
7131
|
case "function":
|
|
6722
|
-
return
|
|
6723
|
-
|
|
6724
|
-
|
|
6725
|
-
|
|
6726
|
-
|
|
6727
|
-
|
|
6728
|
-
|
|
7132
|
+
return {
|
|
7133
|
+
...fn(
|
|
7134
|
+
t.params.map((p) => ({ ...p, type: this.reduceType(p.type) })),
|
|
7135
|
+
this.reduceType(t.returns),
|
|
7136
|
+
t.varargs && this.reduceType(t.varargs),
|
|
7137
|
+
t.typeParams,
|
|
7138
|
+
t.predicate
|
|
7139
|
+
),
|
|
7140
|
+
...t.restIsWhole ? { restIsWhole: true } : {}
|
|
7141
|
+
};
|
|
6729
7142
|
case "object": {
|
|
6730
7143
|
if (t.class) return t;
|
|
6731
7144
|
const entries = [];
|
|
@@ -6940,7 +7353,8 @@ var TypeAnalyzer = class {
|
|
|
6940
7353
|
this.applyContext(stmt.init[i], this.resolveType(target.typeAnnotation));
|
|
6941
7354
|
}
|
|
6942
7355
|
});
|
|
6943
|
-
const { types: valueTypes, sources } = this.valueList(stmt.init, env
|
|
7356
|
+
const { types: valueTypes, sources } = this.valueList(stmt.init, env);
|
|
7357
|
+
this.checkValueCount(stmt, stmt.names.length, stmt.init, valueTypes);
|
|
6944
7358
|
stmt.names.forEach((target, i) => {
|
|
6945
7359
|
const inferred = valueTypes[i] ?? (stmt.init.length ? unknownType : nilType);
|
|
6946
7360
|
const source = sources[i];
|
|
@@ -7029,7 +7443,8 @@ var TypeAnalyzer = class {
|
|
|
7029
7443
|
if (id !== void 0 && this.annotated.has(id)) this.applyContext(value, this.bindingType.get(id));
|
|
7030
7444
|
}
|
|
7031
7445
|
});
|
|
7032
|
-
const { types: valueTypes, sources } = this.valueList(stmt.values, env
|
|
7446
|
+
const { types: valueTypes, sources } = this.valueList(stmt.values, env);
|
|
7447
|
+
this.checkValueCount(stmt, stmt.targets.length, stmt.values, valueTypes);
|
|
7033
7448
|
stmt.targets.forEach((target, i) => {
|
|
7034
7449
|
const vt = valueTypes[i] ?? unknownType;
|
|
7035
7450
|
const source = sources[i];
|
|
@@ -7116,23 +7531,26 @@ var TypeAnalyzer = class {
|
|
|
7116
7531
|
return;
|
|
7117
7532
|
}
|
|
7118
7533
|
case "GenericForStatement": {
|
|
7119
|
-
const
|
|
7534
|
+
const sourceType = this.infer(stmt.iterator, env);
|
|
7120
7535
|
const bodyEnv = forkEnv(env);
|
|
7121
|
-
const rows =
|
|
7536
|
+
const rows = this.iterationRows(stmt.iterator, sourceType);
|
|
7122
7537
|
if (rows) {
|
|
7123
|
-
|
|
7124
|
-
|
|
7125
|
-
|
|
7126
|
-
|
|
7127
|
-
|
|
7128
|
-
|
|
7129
|
-
|
|
7538
|
+
this.bindPattern(
|
|
7539
|
+
stmt.variable,
|
|
7540
|
+
union(rows.map((row) => tuple(row))),
|
|
7541
|
+
bodyEnv,
|
|
7542
|
+
stmt.kind === "const" ? "keep" : "widen"
|
|
7543
|
+
);
|
|
7544
|
+
const parts = stmt.variable.type === "ArrayPattern" && !stmt.variable.rest ? stmt.variable.elements.slice(0, 2).map((el) => el?.value) : [];
|
|
7545
|
+
const ids = parts.map((p) => p?.type === "IdentifierPattern" ? this.bindingIdByName(p.name, p) : void 0);
|
|
7546
|
+
if (ids.length === 2 && ids.every((id) => id !== void 0)) {
|
|
7547
|
+
this.correlateBindings(bodyEnv, ids, rows);
|
|
7548
|
+
}
|
|
7549
|
+
this.loops.set(stmt, { walks: "iteration", viaIter: false });
|
|
7130
7550
|
} else {
|
|
7131
|
-
const [
|
|
7132
|
-
stmt.
|
|
7133
|
-
|
|
7134
|
-
});
|
|
7135
|
-
if (iteratesValues && stmt.iterators.length === 1) this.iteratesValues.add(stmt);
|
|
7551
|
+
const [item, form] = this.iterationOf(stmt.iterator, sourceType);
|
|
7552
|
+
this.bindPattern(stmt.variable, item, bodyEnv, "widen");
|
|
7553
|
+
this.loops.set(stmt, form);
|
|
7136
7554
|
}
|
|
7137
7555
|
this.visitBlock(stmt.body, bodyEnv);
|
|
7138
7556
|
return;
|
|
@@ -7140,19 +7558,10 @@ var TypeAnalyzer = class {
|
|
|
7140
7558
|
case "ReturnStatement": {
|
|
7141
7559
|
const declared = this.declaredReturns[this.declaredReturns.length - 1];
|
|
7142
7560
|
const wanted = declared ?? this.contextualReturns[this.contextualReturns.length - 1];
|
|
7143
|
-
if (wanted)
|
|
7144
|
-
|
|
7145
|
-
|
|
7146
|
-
|
|
7147
|
-
stmt.arguments.forEach((a, i) => this.applyContext(a, wanted.elements[i]));
|
|
7148
|
-
}
|
|
7149
|
-
}
|
|
7150
|
-
const want = declared?.kind === "tuple" && declared.isPack ? declared.elements.length : 0;
|
|
7151
|
-
const { types, sources } = this.valueList(stmt.arguments, env, want);
|
|
7152
|
-
this.checkReturn(stmt, declared, types, sources, env);
|
|
7153
|
-
if (this.returnTypes) {
|
|
7154
|
-
this.returnTypes.push(stmt.arguments.length === 0 ? nilType : types.length === 1 ? types[0] : tuple([...types], true));
|
|
7155
|
-
}
|
|
7561
|
+
if (wanted && stmt.argument) this.applyContext(stmt.argument, wanted);
|
|
7562
|
+
const actual = stmt.argument ? this.infer(stmt.argument, env) : nilType;
|
|
7563
|
+
this.checkReturn(stmt, declared, actual, env);
|
|
7564
|
+
if (this.returnTypes) this.returnTypes.push(actual);
|
|
7156
7565
|
return;
|
|
7157
7566
|
}
|
|
7158
7567
|
case "ExportStatement":
|
|
@@ -7239,7 +7648,7 @@ var TypeAnalyzer = class {
|
|
|
7239
7648
|
* `sources` maps each produced value back to the expression it came from
|
|
7240
7649
|
* (undefined for the 2nd and later values of a multi-value call), so the
|
|
7241
7650
|
* caller can still do contextual typing against the written expression. */
|
|
7242
|
-
valueList(exprs, env
|
|
7651
|
+
valueList(exprs, env) {
|
|
7243
7652
|
const types = [];
|
|
7244
7653
|
const sources = [];
|
|
7245
7654
|
exprs.forEach((e, i) => {
|
|
@@ -7255,31 +7664,29 @@ var TypeAnalyzer = class {
|
|
|
7255
7664
|
}
|
|
7256
7665
|
return;
|
|
7257
7666
|
}
|
|
7258
|
-
|
|
7259
|
-
|
|
7260
|
-
sources.push(e);
|
|
7261
|
-
} while (last && types.length < want);
|
|
7262
|
-
return;
|
|
7263
|
-
}
|
|
7264
|
-
if (last && e.type === "VarargExpression") {
|
|
7265
|
-
do {
|
|
7266
|
-
types.push(t);
|
|
7267
|
-
sources.push(types.length - 1 === i ? e : void 0);
|
|
7268
|
-
} while (types.length < want);
|
|
7269
|
-
return;
|
|
7270
|
-
}
|
|
7271
|
-
if (last && t.kind === "tuple" && t.isPack && producesMultipleValues(e)) {
|
|
7272
|
-
t.elements.forEach((el, j) => {
|
|
7273
|
-
types.push(el);
|
|
7274
|
-
sources.push(j === 0 ? e : void 0);
|
|
7275
|
-
});
|
|
7667
|
+
types.push(t);
|
|
7668
|
+
sources.push(e);
|
|
7276
7669
|
return;
|
|
7277
7670
|
}
|
|
7278
|
-
|
|
7671
|
+
void last;
|
|
7672
|
+
types.push(t);
|
|
7279
7673
|
sources.push(e);
|
|
7280
7674
|
});
|
|
7281
7675
|
return { types, sources };
|
|
7282
7676
|
}
|
|
7677
|
+
/** `const a, b = x, y` pairs names with values, one each. A call is one
|
|
7678
|
+
* value — several come back as an array — so `const a, b = f()` is a
|
|
7679
|
+
* name short of a value, and says how to take them apart. */
|
|
7680
|
+
checkValueCount(node, names, values, types) {
|
|
7681
|
+
if (!this.emitDiagnostics || !values.length || types.length >= names) return;
|
|
7682
|
+
const last = values[values.length - 1];
|
|
7683
|
+
const lastType = types[types.length - 1] && this.expand(types[types.length - 1]);
|
|
7684
|
+
const call = last.type === "CallExpression" || last.type === "MethodCallExpression";
|
|
7685
|
+
this.diagnostics.push({
|
|
7686
|
+
node,
|
|
7687
|
+
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`
|
|
7688
|
+
});
|
|
7689
|
+
}
|
|
7283
7690
|
/** Run `visit` with a fresh place to collect `break` states, and return
|
|
7284
7691
|
* what it collected. */
|
|
7285
7692
|
withBreakScope(visit) {
|
|
@@ -7368,8 +7775,19 @@ var TypeAnalyzer = class {
|
|
|
7368
7775
|
if (p.rest) return arrayOf(unknownType);
|
|
7369
7776
|
if (p.pattern) return this.patternToType(p.pattern, env);
|
|
7370
7777
|
if (p.default) return widen(this.infer(p.default, env));
|
|
7371
|
-
|
|
7778
|
+
const contextual = this.contextualParams.get(p);
|
|
7779
|
+
if (contextual) return contextual;
|
|
7780
|
+
if (this.emitDiagnostics && !receiver && p.name && p.line && !this.untypedParamReported.has(p)) {
|
|
7781
|
+
this.untypedParamReported.add(p);
|
|
7782
|
+
this.diagnostics.push({
|
|
7783
|
+
node: p,
|
|
7784
|
+
message: `Parameter '${p.name}' has no type, so it is 'any': give it one, or a default to read it from`
|
|
7785
|
+
});
|
|
7786
|
+
}
|
|
7787
|
+
return anyType;
|
|
7372
7788
|
}
|
|
7789
|
+
/** Parameters already spoken about: a body is visited more than once. */
|
|
7790
|
+
untypedParamReported = /* @__PURE__ */ new WeakSet();
|
|
7373
7791
|
/** What a function expression's unannotated parameters are, from where
|
|
7374
7792
|
* it is written — see `applyContext`. */
|
|
7375
7793
|
contextualParams = /* @__PURE__ */ new WeakMap();
|
|
@@ -7428,18 +7846,29 @@ var TypeAnalyzer = class {
|
|
|
7428
7846
|
* context. */
|
|
7429
7847
|
contextualArrays = /* @__PURE__ */ new WeakMap();
|
|
7430
7848
|
applyArrayContext(e, expected) {
|
|
7431
|
-
const
|
|
7849
|
+
const members = this.expectedMembers(expected);
|
|
7850
|
+
const tuples = members.filter((m) => m.kind === "tuple");
|
|
7851
|
+
const spread = e.elements.some((el) => el.type === "SpreadElement");
|
|
7852
|
+
const length = e.elements.length;
|
|
7853
|
+
const fitting = tuples.find((t) => !spread && !t.rest && t.elements.length === length) ?? tuples.find((t) => t.rest !== void 0 && (spread || length >= t.elements.length));
|
|
7854
|
+
const target = fitting ?? members.find((m) => m.kind === "array") ?? tuples[0];
|
|
7432
7855
|
if (!target) return;
|
|
7856
|
+
if (target.kind === "tuple" && e.elements.length) this.tupleArrays.set(e, target);
|
|
7433
7857
|
if (!e.elements.length) {
|
|
7434
7858
|
this.contextualArrays.set(e, target);
|
|
7435
7859
|
return;
|
|
7436
7860
|
}
|
|
7437
7861
|
e.elements.forEach((element, i) => {
|
|
7438
7862
|
if (element.type === "SpreadElement") return;
|
|
7439
|
-
const
|
|
7863
|
+
const tupleTarget = target;
|
|
7864
|
+
const elementType = target.kind === "array" ? target.element : tupleTarget.elements[i] ?? tupleTarget.rest;
|
|
7440
7865
|
this.applyContext(element, elementType);
|
|
7441
7866
|
});
|
|
7442
7867
|
}
|
|
7868
|
+
/** Array literals written where a tuple is wanted — a `return` of a
|
|
7869
|
+
* function that returns one, an argument, an annotated `const` — and
|
|
7870
|
+
* the tuple each is to be. */
|
|
7871
|
+
tupleArrays = /* @__PURE__ */ new WeakMap();
|
|
7443
7872
|
/** `{ list: [] }` where `{ list: thread[] }` is expected: each field's
|
|
7444
7873
|
* value gets its property's type as context. */
|
|
7445
7874
|
applyTableContext(e, expected) {
|
|
@@ -7495,32 +7924,33 @@ var TypeAnalyzer = class {
|
|
|
7495
7924
|
}
|
|
7496
7925
|
return tuple(target.elements.map((el) => el ? leaf(el.value, el.default) : anyType));
|
|
7497
7926
|
}
|
|
7498
|
-
/**
|
|
7499
|
-
*
|
|
7500
|
-
*
|
|
7501
|
-
|
|
7502
|
-
|
|
7503
|
-
|
|
7504
|
-
|
|
7505
|
-
|
|
7506
|
-
const
|
|
7507
|
-
if (
|
|
7508
|
-
|
|
7509
|
-
|
|
7510
|
-
|
|
7511
|
-
|
|
7512
|
-
|
|
7927
|
+
/** A function type with the rest parameter among `params` written in:
|
|
7928
|
+
* `...args: T[]` collects `T`s, and `...args: A` — `A` a tuple, or a
|
|
7929
|
+
* type parameter standing for one — is exactly `A`'s elements. */
|
|
7930
|
+
withRest(f, params) {
|
|
7931
|
+
const rest = params.find((p) => p.rest);
|
|
7932
|
+
if (!rest) return f;
|
|
7933
|
+
if (!rest.typeAnnotation) return { ...f, varargs: unknownType };
|
|
7934
|
+
const declared = this.resolveType(rest.typeAnnotation);
|
|
7935
|
+
const t = this.expand(declared);
|
|
7936
|
+
if (t.kind === "array") return { ...f, varargs: t.element };
|
|
7937
|
+
if (t.kind === "any") return { ...f, varargs: anyType };
|
|
7938
|
+
if (declared.kind === "infer") return { ...f, varargs: declared, restIsWhole: true };
|
|
7939
|
+
if (t.kind === "tuple") {
|
|
7940
|
+
return { ...f, params: [...f.params, ...t.elements.map((type) => ({ type }))], varargs: t.rest };
|
|
7941
|
+
}
|
|
7942
|
+
if (declared.kind === "typeParam") return { ...f, varargs: declared, restIsWhole: true };
|
|
7943
|
+
return { ...f, varargs: unknownType };
|
|
7944
|
+
}
|
|
7513
7945
|
/** What each function body being walked declared it returns. */
|
|
7514
7946
|
declaredReturns = [];
|
|
7515
|
-
/** Run `body` with
|
|
7947
|
+
/** Run `body` with `return` as `func` declares it. */
|
|
7516
7948
|
withVarargs(func, body) {
|
|
7517
|
-
this.varargs.push(this.varargElement(func));
|
|
7518
7949
|
this.declaredReturns.push(func.predicate ? booleanType : func.returnType ? this.resolveType(func.returnType) : void 0);
|
|
7519
7950
|
this.contextualReturns.push(this.contextualReturnOf.get(func));
|
|
7520
7951
|
try {
|
|
7521
7952
|
return body();
|
|
7522
7953
|
} finally {
|
|
7523
|
-
this.varargs.pop();
|
|
7524
7954
|
this.declaredReturns.pop();
|
|
7525
7955
|
this.contextualReturns.pop();
|
|
7526
7956
|
}
|
|
@@ -7579,7 +8009,7 @@ var TypeAnalyzer = class {
|
|
|
7579
8009
|
if (type.kind !== "function") return type;
|
|
7580
8010
|
const defaults = {};
|
|
7581
8011
|
for (const generic of generics) {
|
|
7582
|
-
if (generic.default
|
|
8012
|
+
if (generic.default) defaults[generic.name] = this.resolveType(generic.default);
|
|
7583
8013
|
}
|
|
7584
8014
|
return Object.keys(defaults).length ? { ...type, typeParamDefaults: defaults } : type;
|
|
7585
8015
|
}
|
|
@@ -7598,7 +8028,10 @@ var TypeAnalyzer = class {
|
|
|
7598
8028
|
const preserve = keepsLiterals(param) || param.kind !== "typeParam" && containsTypeParam(param);
|
|
7599
8029
|
unify(p.type, preserve ? arg : widen(arg), vars, subst);
|
|
7600
8030
|
});
|
|
7601
|
-
if (f.varargs) {
|
|
8031
|
+
if (f.varargs && f.restIsWhole) {
|
|
8032
|
+
const rest = argTypes.slice(f.params.length).map((a) => a === void 0 ? unknownType : widen(a));
|
|
8033
|
+
unify(f.varargs, tuple(rest), vars, subst);
|
|
8034
|
+
} else if (f.varargs) {
|
|
7602
8035
|
const keeps = keepsLiterals(f.varargs);
|
|
7603
8036
|
for (let i = f.params.length; i < argTypes.length; i++) {
|
|
7604
8037
|
const arg = argTypes[i];
|
|
@@ -7649,6 +8082,11 @@ var TypeAnalyzer = class {
|
|
|
7649
8082
|
* this returns `undefined` and the ordinary pick stands. */
|
|
7650
8083
|
distributedReturn(fns, argTypes, picked, argsFor) {
|
|
7651
8084
|
if (fns.length < 2) return void 0;
|
|
8085
|
+
if (argTypes.some((t) => this.expand(t).kind === "any")) {
|
|
8086
|
+
const taken = fns.filter((f) => this.overloadAccepts(f, argsFor(f, argTypes)));
|
|
8087
|
+
if (taken.length < 2) return void 0;
|
|
8088
|
+
return union(taken.map((f) => this.callReturn(f, argsFor(f, argTypes))));
|
|
8089
|
+
}
|
|
7652
8090
|
const position = argTypes.findIndex((t) => this.expand(t).kind === "union");
|
|
7653
8091
|
if (position < 0) return void 0;
|
|
7654
8092
|
const members = this.expand(argTypes[position]).types;
|
|
@@ -7697,6 +8135,8 @@ var TypeAnalyzer = class {
|
|
|
7697
8135
|
* lone argument cannot be checked against without false errors. */
|
|
7698
8136
|
boundParams(f) {
|
|
7699
8137
|
if (!f.typeParams?.length) return f.params.map((p) => p.type);
|
|
8138
|
+
const known = this.boundParamsCache.get(f);
|
|
8139
|
+
if (known) return known;
|
|
7700
8140
|
const bounds = new Map(f.typeParams.map((name) => [name, anyType]));
|
|
7701
8141
|
const seen = /* @__PURE__ */ new WeakSet();
|
|
7702
8142
|
const walk = (value) => {
|
|
@@ -7714,8 +8154,11 @@ var TypeAnalyzer = class {
|
|
|
7714
8154
|
for (const child of Object.values(value)) walk(child);
|
|
7715
8155
|
};
|
|
7716
8156
|
for (const p of f.params) if (containsTypeParam(p.type)) walk(p.type);
|
|
7717
|
-
|
|
8157
|
+
const bound = f.params.map((p) => this.reduceType(substitute(p.type, bounds)));
|
|
8158
|
+
this.boundParamsCache.set(f, bound);
|
|
8159
|
+
return bound;
|
|
7718
8160
|
}
|
|
8161
|
+
boundParamsCache = /* @__PURE__ */ new WeakMap();
|
|
7719
8162
|
/** Record what each written argument is expected to be — see
|
|
7720
8163
|
* `TypeAnalysis.expectedTypeOf`. */
|
|
7721
8164
|
recordExpected(written, fns, selfOf, argsOf = () => []) {
|
|
@@ -7767,7 +8210,12 @@ var TypeAnalyzer = class {
|
|
|
7767
8210
|
const arg = argTypes[i];
|
|
7768
8211
|
const declared = declaredAt(i);
|
|
7769
8212
|
if (arg === void 0 || declared === void 0 || !containsTypeParam(declared)) continue;
|
|
7770
|
-
|
|
8213
|
+
let expected = this.reduceType(substitute(declared, subst));
|
|
8214
|
+
if (f.restIsWhole && i >= f.params.length) {
|
|
8215
|
+
const whole = this.expand(expected);
|
|
8216
|
+
expected = whole.kind === "tuple" ? whole.elements[i - f.params.length] ?? whole.rest : whole.kind === "array" ? whole.element : void 0;
|
|
8217
|
+
if (!expected) continue;
|
|
8218
|
+
}
|
|
7771
8219
|
if (containsTypeParam(expected) || expected.kind === "any" || expected.kind === "unknown") continue;
|
|
7772
8220
|
if (isAssignable(arg, expected) || isAssignable(widen(arg), expected)) continue;
|
|
7773
8221
|
this.diagnostics.push({
|
|
@@ -7895,13 +8343,13 @@ var TypeAnalyzer = class {
|
|
|
7895
8343
|
type: this.paramType(p, /* @__PURE__ */ new Map()),
|
|
7896
8344
|
optional: p.optional || p.default !== void 0
|
|
7897
8345
|
}));
|
|
7898
|
-
return fn(
|
|
8346
|
+
return this.withRest(fn(
|
|
7899
8347
|
params,
|
|
7900
8348
|
sig.returnType ? this.resolveType(sig.returnType) : sig.predicate ? booleanType : anyType,
|
|
7901
|
-
|
|
8349
|
+
void 0,
|
|
7902
8350
|
names,
|
|
7903
8351
|
this.resolvePredicate(sig.predicate, params)
|
|
7904
|
-
);
|
|
8352
|
+
), sig.params);
|
|
7905
8353
|
}));
|
|
7906
8354
|
}
|
|
7907
8355
|
/** `...rest: T[]` holds every argument from its position on, so its type
|
|
@@ -7909,7 +8357,7 @@ var TypeAnalyzer = class {
|
|
|
7909
8357
|
checkRestType(p, node) {
|
|
7910
8358
|
if (!this.emitDiagnostics || !p.typeAnnotation) return;
|
|
7911
8359
|
const declared = this.resolveType(p.typeAnnotation);
|
|
7912
|
-
if (declared.kind === "array" || declared.kind === "any" || declared.kind === "typeParam") return;
|
|
8360
|
+
if (declared.kind === "array" || declared.kind === "tuple" || declared.kind === "any" || declared.kind === "typeParam") return;
|
|
7913
8361
|
this.diagnostics.push({
|
|
7914
8362
|
node,
|
|
7915
8363
|
message: `A rest parameter holds every argument from its position on, so '${p.name ?? "..."}' is an array: '${formatType(declared)}[]', not '${formatType(declared)}'`
|
|
@@ -7965,13 +8413,13 @@ var TypeAnalyzer = class {
|
|
|
7965
8413
|
return collected.length ? union(collected) : this.inferReturnType(func.body, bodyEnv);
|
|
7966
8414
|
}));
|
|
7967
8415
|
}
|
|
7968
|
-
return fn(
|
|
8416
|
+
return this.withRest(fn(
|
|
7969
8417
|
params,
|
|
7970
8418
|
returns,
|
|
7971
|
-
|
|
8419
|
+
void 0,
|
|
7972
8420
|
names,
|
|
7973
8421
|
this.resolvePredicate(func.predicate, params)
|
|
7974
|
-
);
|
|
8422
|
+
), func.params);
|
|
7975
8423
|
});
|
|
7976
8424
|
}
|
|
7977
8425
|
/** Where the return types of the function being walked are collected, so
|
|
@@ -8013,17 +8461,16 @@ var TypeAnalyzer = class {
|
|
|
8013
8461
|
this.preVisitDepth--;
|
|
8014
8462
|
}
|
|
8015
8463
|
}
|
|
8016
|
-
/** The `[key, value]` pairs
|
|
8017
|
-
*
|
|
8018
|
-
*
|
|
8019
|
-
* iterator function), whose keys have no names to list. */
|
|
8464
|
+
/** The `[key, value]` items `pairs(record)` hands out, one per property.
|
|
8465
|
+
* `undefined` for anything else (an array, a dictionary, an iterator
|
|
8466
|
+
* function), whose keys have no names to list. */
|
|
8020
8467
|
iterationRows(iterNode, iterType) {
|
|
8021
8468
|
let source;
|
|
8022
8469
|
if (iterNode?.type === "CallExpression" && iterNode.callee.type === "Identifier" && iterNode.arguments[0]) {
|
|
8023
|
-
if (iterNode.callee.name !== "pairs"
|
|
8470
|
+
if (iterNode.callee.name !== "pairs") return void 0;
|
|
8024
8471
|
source = this.typeOf.get(iterNode.arguments[0]);
|
|
8025
8472
|
} else {
|
|
8026
|
-
|
|
8473
|
+
return void 0;
|
|
8027
8474
|
}
|
|
8028
8475
|
const t = source && this.expand(source);
|
|
8029
8476
|
if (!t || t.kind !== "object" || t.class || t.indexer || !t.properties.size) return void 0;
|
|
@@ -8164,54 +8611,76 @@ var TypeAnalyzer = class {
|
|
|
8164
8611
|
if (ids.length < 2) return;
|
|
8165
8612
|
this.correlateBindings(env, ids, objects.map((member) => names.map((name) => this.propertyType(member, name))));
|
|
8166
8613
|
}
|
|
8167
|
-
/** `
|
|
8168
|
-
*
|
|
8169
|
-
*
|
|
8170
|
-
|
|
8171
|
-
|
|
8172
|
-
|
|
8173
|
-
|
|
8174
|
-
|
|
8175
|
-
|
|
8176
|
-
|
|
8177
|
-
|
|
8178
|
-
|
|
8614
|
+
/** `[step, state, first]` — what a call such as `pairs(t)` answers — as
|
|
8615
|
+
* the `step` function, or `undefined` for an array that is just an
|
|
8616
|
+
* array. Three places exactly, the first a function. */
|
|
8617
|
+
iterationTriple(t) {
|
|
8618
|
+
if (t.kind !== "tuple" || t.rest || t.elements.length !== 3) return void 0;
|
|
8619
|
+
const step = this.expand(t.elements[0]);
|
|
8620
|
+
return step.kind === "function" ? step : void 0;
|
|
8621
|
+
}
|
|
8622
|
+
/** What one `for (const item in source)` hands over each time, and how it
|
|
8623
|
+
* walks the source. `pairs`/`ipairs` are read from the table they are
|
|
8624
|
+
* given, so a record's keys stay the literals they are. */
|
|
8625
|
+
iterationOf(source, sourceType, report = true) {
|
|
8626
|
+
if (source.type === "CallExpression" && source.callee.type === "Identifier" && source.arguments[0]) {
|
|
8627
|
+
const name = source.callee.name;
|
|
8628
|
+
const src = this.expand(this.typeOf.get(source.arguments[0]) ?? unknownType);
|
|
8629
|
+
const iteration = { walks: "iteration", viaIter: false };
|
|
8630
|
+
if (name === "ipairs") return [tuple([numberType, this.elementType(src, 0)]), iteration];
|
|
8631
|
+
if (name === "pairs") {
|
|
8179
8632
|
if (src.kind === "object") {
|
|
8180
|
-
return [
|
|
8633
|
+
return [tuple([
|
|
8181
8634
|
src.indexer?.key ?? stringType,
|
|
8182
|
-
src.indexer?.value ?? union([...src.properties.values()].map((p) => p.type))
|
|
8183
|
-
|
|
8184
|
-
];
|
|
8635
|
+
src.indexer?.value ?? union([...src.properties.values()].map((p) => p.type))
|
|
8636
|
+
]), iteration];
|
|
8185
8637
|
}
|
|
8186
|
-
if (src.kind === "array") return [numberType, src.element,
|
|
8638
|
+
if (src.kind === "array") return [tuple([numberType, src.element]), iteration];
|
|
8187
8639
|
}
|
|
8188
8640
|
}
|
|
8189
|
-
const
|
|
8190
|
-
|
|
8191
|
-
|
|
8192
|
-
const
|
|
8193
|
-
|
|
8194
|
-
return
|
|
8641
|
+
const handsOut = (t2, viaIter) => {
|
|
8642
|
+
const x = this.expand(t2);
|
|
8643
|
+
if (x.kind === "function") return [withoutNil(this.expand(x.returns)), { walks: "function", viaIter }];
|
|
8644
|
+
const step = this.iterationTriple(x);
|
|
8645
|
+
if (step) return [withoutNil(this.expand(step.returns)), { walks: "iteration", viaIter }];
|
|
8646
|
+
return void 0;
|
|
8647
|
+
};
|
|
8648
|
+
const direct = handsOut(sourceType, false);
|
|
8649
|
+
if (direct) return direct;
|
|
8650
|
+
const values = { walks: "values", viaIter: false };
|
|
8651
|
+
const t = this.expand(sourceType);
|
|
8652
|
+
if (t.kind === "any") return [anyType, values];
|
|
8653
|
+
const iter = t.kind === "object" ? t.properties.get("__iter") : void 0;
|
|
8654
|
+
if (iter) {
|
|
8655
|
+
const made = this.overloadsOf(iter.type)[0]?.returns ?? unknownType;
|
|
8656
|
+
return handsOut(made, true) ?? [unknownType, { walks: "values", viaIter: true }];
|
|
8657
|
+
}
|
|
8658
|
+
if (t.kind === "array") return [t.element, values];
|
|
8659
|
+
if (t.kind === "tuple") return [union([...t.elements, ...t.rest ? [t.rest] : []]), values];
|
|
8660
|
+
if (t.kind === "object") {
|
|
8661
|
+
return [t.indexer?.value ?? union([...t.properties.values()].map((p) => p.type)), values];
|
|
8195
8662
|
}
|
|
8196
|
-
|
|
8197
|
-
|
|
8198
|
-
|
|
8663
|
+
if (t.kind === "union") {
|
|
8664
|
+
const each = t.types.map((member) => this.iterationOf(source, member, false));
|
|
8665
|
+
const [first] = each;
|
|
8666
|
+
if (first && each.every(([, form]) => form.walks === first[1].walks && form.viaIter === first[1].viaIter)) {
|
|
8667
|
+
return [union(each.map(([item]) => item)), first[1]];
|
|
8668
|
+
}
|
|
8199
8669
|
}
|
|
8200
|
-
if (t.kind
|
|
8201
|
-
|
|
8202
|
-
|
|
8203
|
-
|
|
8670
|
+
if (t.kind !== "never" && report && this.emitDiagnostics) {
|
|
8671
|
+
this.diagnostics.push({
|
|
8672
|
+
node: source,
|
|
8673
|
+
message: `Cannot loop over '${formatType(sourceType)}': a loop walks an array, a table, an iterator function or an iteration such as 'pairs(t)'`
|
|
8674
|
+
});
|
|
8204
8675
|
}
|
|
8205
|
-
return [unknownType,
|
|
8676
|
+
return [unknownType, values];
|
|
8206
8677
|
}
|
|
8207
8678
|
inferReturnType(body, env) {
|
|
8208
8679
|
const returns = [];
|
|
8209
8680
|
const walk = (block) => {
|
|
8210
8681
|
for (const s of block.statements) {
|
|
8211
8682
|
if (s.type === "ReturnStatement") {
|
|
8212
|
-
|
|
8213
|
-
else if (s.arguments.length === 1) returns.push(this.infer(s.arguments[0], env));
|
|
8214
|
-
else returns.push(tuple(s.arguments.map((a) => this.infer(a, env)), true));
|
|
8683
|
+
returns.push(s.argument ? this.infer(s.argument, env) : nilType);
|
|
8215
8684
|
} else if (s.type === "IfStatement") {
|
|
8216
8685
|
for (const c of s.clauses) walk(c.body);
|
|
8217
8686
|
if (s.alternate) walk(s.alternate);
|
|
@@ -8233,8 +8702,9 @@ var TypeAnalyzer = class {
|
|
|
8233
8702
|
* `infer` would have widened it to an array type). */
|
|
8234
8703
|
fitsAnnotation(init, declared, inferred, env) {
|
|
8235
8704
|
if (declared.kind === "tuple" && init.type === "ArrayExpression" && !init.elements.some((e) => e.type === "SpreadElement")) {
|
|
8236
|
-
|
|
8237
|
-
|
|
8705
|
+
const fits = declared.rest ? init.elements.length >= declared.elements.length : init.elements.length === declared.elements.length;
|
|
8706
|
+
if (!fits) return false;
|
|
8707
|
+
return init.elements.every((el, i) => isAssignable(widen(this.infer(el, env)), declared.elements[i] ?? declared.rest));
|
|
8238
8708
|
}
|
|
8239
8709
|
if (isAssignable(inferred, declared) || isAssignable(widen(inferred), declared)) return true;
|
|
8240
8710
|
if (init.type === "TableExpression") return isAssignable(this.inferObject(init, env, true), declared);
|
|
@@ -8291,7 +8761,7 @@ var TypeAnalyzer = class {
|
|
|
8291
8761
|
target.elements.forEach((el, i) => {
|
|
8292
8762
|
if (el) this.reassignPattern(el.value, this.withDefault(this.elementType(valueType, i), el.default, env), env);
|
|
8293
8763
|
});
|
|
8294
|
-
if (target.rest) this.reassignPattern(target.rest,
|
|
8764
|
+
if (target.rest) this.reassignPattern(target.rest, this.restAfter(valueType, target.elements.length), env);
|
|
8295
8765
|
return;
|
|
8296
8766
|
}
|
|
8297
8767
|
}
|
|
@@ -8327,7 +8797,7 @@ var TypeAnalyzer = class {
|
|
|
8327
8797
|
if (!el) return;
|
|
8328
8798
|
this.bindPattern(el.value, this.withDefault(this.elementType(valueType, i), el.default, env), env, mode);
|
|
8329
8799
|
});
|
|
8330
|
-
if (target.rest) this.bindPattern(target.rest,
|
|
8800
|
+
if (target.rest) this.bindPattern(target.rest, this.restAfter(valueType, target.elements.length), env, mode);
|
|
8331
8801
|
return;
|
|
8332
8802
|
}
|
|
8333
8803
|
}
|
|
@@ -8484,7 +8954,7 @@ var TypeAnalyzer = class {
|
|
|
8484
8954
|
* not one of them is a mistake worth reporting, rather than the nil Lua
|
|
8485
8955
|
* would hand back. */
|
|
8486
8956
|
checkStringMember(node, object, key) {
|
|
8487
|
-
if (!this.emitDiagnostics) return;
|
|
8957
|
+
if (!this.emitDiagnostics || !this.aliasDefs.has("StringMethods")) return;
|
|
8488
8958
|
const parts = this.stringParts(object);
|
|
8489
8959
|
if (!parts) return;
|
|
8490
8960
|
const keys = (key.kind === "union" ? key.types : [key]).map((m) => this.expand(m));
|
|
@@ -8496,6 +8966,127 @@ var TypeAnalyzer = class {
|
|
|
8496
8966
|
message: names.length === 1 ? `'${names[0]}' does not exist on a string` : `'${briefType(key)}' does not name a member of a string`
|
|
8497
8967
|
});
|
|
8498
8968
|
}
|
|
8969
|
+
/** `game.Anything` on a `{ GetService: ... }`, or `a.a` on a number: a
|
|
8970
|
+
* value whose members are all known, and none of them is this one. Only
|
|
8971
|
+
* a type that is known through and through is reported — an indexer, an
|
|
8972
|
+
* `any`, or a part still waiting on a type parameter could each supply
|
|
8973
|
+
* the name. An empty `{}` is a table still being filled in, so it is left
|
|
8974
|
+
* alone; an array's and a string's members come from a library, so
|
|
8975
|
+
* without one they say nothing. A string on its own is `checkStringMember`'s. */
|
|
8976
|
+
checkMissingMember(node, object, name) {
|
|
8977
|
+
if (!this.emitDiagnostics || this.missingMemberReported.has(node)) return;
|
|
8978
|
+
if (!this.memberMissing(object, name)) return;
|
|
8979
|
+
this.missingMemberReported.add(node);
|
|
8980
|
+
this.diagnostics.push({
|
|
8981
|
+
node,
|
|
8982
|
+
message: `Property '${name}' does not exist on type '${briefType(withoutNil(this.expand(object)))}'`
|
|
8983
|
+
});
|
|
8984
|
+
}
|
|
8985
|
+
/** Is `name` certainly not a member of `object`? See `checkMissingMember`. */
|
|
8986
|
+
memberMissing(object, name) {
|
|
8987
|
+
if (this.stringParts(object)) return false;
|
|
8988
|
+
const raw = this.expand(object);
|
|
8989
|
+
const parts = (raw.kind === "union" ? raw.types : [raw]).map((m) => this.deferredAccess(this.expand(m)));
|
|
8990
|
+
let missing = false;
|
|
8991
|
+
for (const part of parts) {
|
|
8992
|
+
switch (part.kind) {
|
|
8993
|
+
case "primitive":
|
|
8994
|
+
if (part.name === "nil") continue;
|
|
8995
|
+
if (part.name === "string" && !this.aliasDefs.has("StringMethods")) return false;
|
|
8996
|
+
if (part.name === "string" && this.builtInMethod(part, name)) continue;
|
|
8997
|
+
break;
|
|
8998
|
+
case "literal":
|
|
8999
|
+
case "templateLiteral":
|
|
9000
|
+
if (part.kind === "templateLiteral" || part.base === "string") {
|
|
9001
|
+
if (!this.aliasDefs.has("StringMethods")) return false;
|
|
9002
|
+
}
|
|
9003
|
+
if (this.builtInMethod(part, name)) continue;
|
|
9004
|
+
break;
|
|
9005
|
+
case "array":
|
|
9006
|
+
case "tuple":
|
|
9007
|
+
if (!this.aliasDefs.has("ArrayMethods")) return false;
|
|
9008
|
+
if (this.builtInMethod(part, name)) continue;
|
|
9009
|
+
break;
|
|
9010
|
+
case "object":
|
|
9011
|
+
if (part.properties.has(name)) continue;
|
|
9012
|
+
if (part.indexer || part.properties.size === 0 && !part.class || this.builtInMethod(part, name)) return false;
|
|
9013
|
+
break;
|
|
9014
|
+
default:
|
|
9015
|
+
return false;
|
|
9016
|
+
}
|
|
9017
|
+
missing = true;
|
|
9018
|
+
}
|
|
9019
|
+
return missing;
|
|
9020
|
+
}
|
|
9021
|
+
/** `t[k]` read with a `k` that cannot be a key of `t`: an `unknown`, a
|
|
9022
|
+
* type none of `t`'s keys has (a number into a record, a string into an
|
|
9023
|
+
* array or a class), or a name `t` does not have — which is `t.name`'s
|
|
9024
|
+
* error. An `any` on either side says nothing, and neither does a type
|
|
9025
|
+
* parameter still to be decided. A plain `string` into a record is
|
|
9026
|
+
* allowed: it reads as one of the values or nil, and the nil has to be
|
|
9027
|
+
* dealt with. An empty `{}` is a table still being filled in, and is
|
|
9028
|
+
* left alone. Reports, and answers whether it did. */
|
|
9029
|
+
checkIndexKey(node, object, key) {
|
|
9030
|
+
if (!this.emitDiagnostics || this.badIndexReported.has(node)) return false;
|
|
9031
|
+
const index = this.expand(key);
|
|
9032
|
+
if (index.kind === "any" || index.kind === "never" || containsTypeParam(index)) return false;
|
|
9033
|
+
const keys = index.kind === "union" ? index.types.map((k) => this.expand(k)) : [index];
|
|
9034
|
+
const nameOf = (k) => k.kind === "literal" && typeof k.value === "string" ? k.value : void 0;
|
|
9035
|
+
const shown = briefType(withoutNil(this.expand(object)));
|
|
9036
|
+
const names = keys.map(nameOf);
|
|
9037
|
+
const others = keys.filter((_, i) => names[i] === void 0);
|
|
9038
|
+
let message;
|
|
9039
|
+
const listOnly = (t) => {
|
|
9040
|
+
const x = this.expand(t);
|
|
9041
|
+
if (x.kind === "union") return x.types.every(listOnly);
|
|
9042
|
+
return x.kind === "array" || x.kind === "tuple";
|
|
9043
|
+
};
|
|
9044
|
+
if (names.some((name) => name !== void 0) && listOnly(withoutNil(this.expand(object)))) {
|
|
9045
|
+
message = `Type '${briefType(index)}' cannot be used to index type '${shown}'`;
|
|
9046
|
+
} else if (others.length && this.cannotIndex(object, others)) {
|
|
9047
|
+
message = `Type '${briefType(index)}' cannot be used to index type '${shown}'`;
|
|
9048
|
+
} else if (!others.length && names.every((name) => this.memberMissing(object, name))) {
|
|
9049
|
+
message = keys.length === 1 ? `Property '${names[0]}' does not exist on type '${shown}'` : `None of ${briefType(index)} is a property of type '${shown}'`;
|
|
9050
|
+
}
|
|
9051
|
+
if (!message) return false;
|
|
9052
|
+
this.badIndexReported.add(node);
|
|
9053
|
+
this.diagnostics.push({ node, message });
|
|
9054
|
+
return true;
|
|
9055
|
+
}
|
|
9056
|
+
/** Can one of `keys` (string literals aside) not index some part of
|
|
9057
|
+
* `object`? `false` as soon as a part cannot be judged. */
|
|
9058
|
+
cannotIndex(object, keys) {
|
|
9059
|
+
const raw = this.expand(object);
|
|
9060
|
+
const parts = (raw.kind === "union" ? raw.types : [raw]).map((m) => this.deferredAccess(this.expand(m)));
|
|
9061
|
+
const isNumber = (k) => k.kind === "primitive" && k.name === "number" || k.kind === "literal" && typeof k.value === "number";
|
|
9062
|
+
const isString = (k) => k.kind === "primitive" && k.name === "string" || k.kind === "templateLiteral";
|
|
9063
|
+
let bad = false;
|
|
9064
|
+
for (const part of parts) {
|
|
9065
|
+
switch (part.kind) {
|
|
9066
|
+
case "primitive":
|
|
9067
|
+
if (part.name === "nil") continue;
|
|
9068
|
+
return false;
|
|
9069
|
+
case "array":
|
|
9070
|
+
case "tuple":
|
|
9071
|
+
if (!keys.every(isNumber)) bad = true;
|
|
9072
|
+
continue;
|
|
9073
|
+
case "object": {
|
|
9074
|
+
if (!part.indexer && part.properties.size === 0 && !part.class) return false;
|
|
9075
|
+
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));
|
|
9076
|
+
if (!keys.every(fits)) bad = true;
|
|
9077
|
+
continue;
|
|
9078
|
+
}
|
|
9079
|
+
default:
|
|
9080
|
+
return false;
|
|
9081
|
+
}
|
|
9082
|
+
}
|
|
9083
|
+
return bad;
|
|
9084
|
+
}
|
|
9085
|
+
/** Indexes already reported: a loop body is visited more than once. */
|
|
9086
|
+
badIndexReported = /* @__PURE__ */ new WeakSet();
|
|
9087
|
+
/** Members already reported as missing: a loop body, or an assignment
|
|
9088
|
+
* target, is visited more than once. */
|
|
9089
|
+
missingMemberReported = /* @__PURE__ */ new WeakSet();
|
|
8499
9090
|
/** A `private` member read from outside the class that declared it —
|
|
8500
9091
|
* a subclass counts as outside, as in TypeScript. */
|
|
8501
9092
|
checkPrivateMember(node, object, name) {
|
|
@@ -8506,18 +9097,46 @@ var TypeAnalyzer = class {
|
|
|
8506
9097
|
if (t.kind !== "object") continue;
|
|
8507
9098
|
const access = t.properties.get(name)?.private;
|
|
8508
9099
|
if (!access || this.insideClass(access.owner)) continue;
|
|
9100
|
+
if (access.protected && this.insideSubclassOf(access.owner)) continue;
|
|
8509
9101
|
this.diagnostics.push({
|
|
8510
9102
|
node,
|
|
8511
|
-
message: `Property '${name}' is private and only accessible within class '${access.className}'`
|
|
9103
|
+
message: access.protected ? `Property '${name}' is protected and only accessible within class '${access.className}' and the classes extending it` : `Property '${name}' is private and only accessible within class '${access.className}'`
|
|
8512
9104
|
});
|
|
8513
9105
|
return;
|
|
8514
9106
|
}
|
|
8515
9107
|
}
|
|
9108
|
+
/** `Shape.new` on an abstract class, and `super.area` on an abstract
|
|
9109
|
+
* method: each reaches something with nothing behind it. */
|
|
9110
|
+
checkAbstractAccess(expr, object) {
|
|
9111
|
+
if (!this.emitDiagnostics) return;
|
|
9112
|
+
const t = this.expand(object);
|
|
9113
|
+
if (t.kind !== "object") return;
|
|
9114
|
+
const name = expr.property.name;
|
|
9115
|
+
const property = t.properties.get(name);
|
|
9116
|
+
if (!property?.abstract) return;
|
|
9117
|
+
if (name === "new" && expr.object.type !== "SuperExpression") {
|
|
9118
|
+
const label = expressionLabel(expr.object) ?? formatType(t);
|
|
9119
|
+
this.diagnostics.push({
|
|
9120
|
+
node: expr,
|
|
9121
|
+
message: `'${label}' is an abstract class: build one of the classes extending it instead`
|
|
9122
|
+
});
|
|
9123
|
+
} else if (expr.object.type === "SuperExpression") {
|
|
9124
|
+
this.diagnostics.push({
|
|
9125
|
+
node: expr.property,
|
|
9126
|
+
message: `'${name}' is abstract in the class this one extends: there is nothing to call`
|
|
9127
|
+
});
|
|
9128
|
+
}
|
|
9129
|
+
}
|
|
8516
9130
|
/** Is the code being checked written inside `owner`'s body — its own
|
|
8517
9131
|
* methods, or a class or function nested in them? */
|
|
8518
9132
|
insideClass(owner) {
|
|
8519
9133
|
return this.enclosingClasses.includes(owner);
|
|
8520
9134
|
}
|
|
9135
|
+
/** Is the code being checked inside a class that extends `owner`? */
|
|
9136
|
+
insideSubclassOf(owner) {
|
|
9137
|
+
const identity = this.classIdentity(owner);
|
|
9138
|
+
return this.enclosingClasses.some((cls) => this.instanceType(cls).class?.ancestors.includes(identity));
|
|
9139
|
+
}
|
|
8521
9140
|
propertyType(raw, name) {
|
|
8522
9141
|
const t = this.deferredAccess(this.expand(raw));
|
|
8523
9142
|
if (t.kind === "object") {
|
|
@@ -8572,8 +9191,12 @@ var TypeAnalyzer = class {
|
|
|
8572
9191
|
}
|
|
8573
9192
|
if (containsTypeParam(index)) return this.reduceType({ kind: "indexedAccess", objectType: t, indexType: index });
|
|
8574
9193
|
if (t.indexer) return t.indexer.value;
|
|
9194
|
+
const anyString = index.kind === "primitive" && index.name === "string" || index.kind === "templateLiteral";
|
|
9195
|
+
if (anyString && !t.class && t.properties.size > 0) {
|
|
9196
|
+
return union([...[...t.properties.values()].map((p) => p.type), nilType]);
|
|
9197
|
+
}
|
|
8575
9198
|
}
|
|
8576
|
-
return unknownType;
|
|
9199
|
+
return index.kind === "any" ? anyType : unknownType;
|
|
8577
9200
|
}
|
|
8578
9201
|
/** What a deferred `T[K]` can be: every property its index could name.
|
|
8579
9202
|
* Reading a member of one, or calling it, sees that. */
|
|
@@ -8583,10 +9206,21 @@ var TypeAnalyzer = class {
|
|
|
8583
9206
|
if (containsTypeParam(index)) return unknownType;
|
|
8584
9207
|
return this.accessType(t.objectType, index);
|
|
8585
9208
|
}
|
|
9209
|
+
/** `[a, ...rest]`: what `rest` holds — an array of whatever comes after
|
|
9210
|
+
* the first `from` places. */
|
|
9211
|
+
restAfter(raw, from) {
|
|
9212
|
+
const t = this.expand(raw);
|
|
9213
|
+
if (t.kind === "tuple") {
|
|
9214
|
+
const left = [...t.elements.slice(from), ...t.rest ? [t.rest] : []];
|
|
9215
|
+
return arrayOf(left.length ? union(left) : neverType);
|
|
9216
|
+
}
|
|
9217
|
+
if (t.kind === "union") return union(t.types.map((m) => this.restAfter(m, from)));
|
|
9218
|
+
return arrayOf(this.elementType(t, from));
|
|
9219
|
+
}
|
|
8586
9220
|
elementType(raw, index) {
|
|
8587
9221
|
const t = this.expand(raw);
|
|
8588
9222
|
if (t.kind === "array") return t.element;
|
|
8589
|
-
if (t.kind === "tuple") return t.elements[index] ?? unknownType;
|
|
9223
|
+
if (t.kind === "tuple") return t.elements[index] ?? t.rest ?? unknownType;
|
|
8590
9224
|
if (t.kind === "union") return union(t.types.map((m) => this.elementType(m, index)));
|
|
8591
9225
|
if (t.kind === "difference") return this.elementType(t.base, index);
|
|
8592
9226
|
if (t.kind === "typeParam" && t.constraint) return this.elementType(t.constraint, index);
|
|
@@ -8616,8 +9250,6 @@ var TypeAnalyzer = class {
|
|
|
8616
9250
|
return stringType;
|
|
8617
9251
|
}
|
|
8618
9252
|
// `...` holds what the function declared it takes.
|
|
8619
|
-
case "VarargExpression":
|
|
8620
|
-
return this.varargs[this.varargs.length - 1] ?? anyType;
|
|
8621
9253
|
// `f(a, ...rest)` — every value the array holds, one after
|
|
8622
9254
|
// another. Each of them is an element, so that is what the
|
|
8623
9255
|
// parameters it fills are checked against.
|
|
@@ -8650,10 +9282,8 @@ var TypeAnalyzer = class {
|
|
|
8650
9282
|
this.checkParamOrder(expr.func.params, expr);
|
|
8651
9283
|
this.visitFunctionBody(expr.func, env);
|
|
8652
9284
|
return this.inferFunctionBody(expr.func, env);
|
|
8653
|
-
case "ParenthesizedExpression":
|
|
8654
|
-
|
|
8655
|
-
return inner.kind === "tuple" && inner.isPack && producesMultipleValues(expr.expression) ? inner.elements[0] ?? nilType : inner;
|
|
8656
|
-
}
|
|
9285
|
+
case "ParenthesizedExpression":
|
|
9286
|
+
return this.infer(expr.expression, env);
|
|
8657
9287
|
case "TypeAssertionExpression": {
|
|
8658
9288
|
this.infer(expr.expression, env);
|
|
8659
9289
|
return this.resolveType(expr.typeAnnotation);
|
|
@@ -8685,10 +9315,18 @@ var TypeAnalyzer = class {
|
|
|
8685
9315
|
switch (expr.operator) {
|
|
8686
9316
|
case "not":
|
|
8687
9317
|
return booleanType;
|
|
8688
|
-
case "-":
|
|
8689
|
-
|
|
8690
|
-
|
|
8691
|
-
|
|
9318
|
+
case "-": {
|
|
9319
|
+
const answered = this.operatorResult(expr, "-", arg, void 0);
|
|
9320
|
+
if (answered) return answered;
|
|
9321
|
+
this.checkOperands(expr, "-", arg);
|
|
9322
|
+
return this.arithmeticOn([arg]);
|
|
9323
|
+
}
|
|
9324
|
+
case "#": {
|
|
9325
|
+
const answered = this.operatorResult(expr, "#", arg, void 0);
|
|
9326
|
+
if (answered) return answered;
|
|
9327
|
+
this.checkOperands(expr, "#", arg);
|
|
9328
|
+
return numberType;
|
|
9329
|
+
}
|
|
8692
9330
|
}
|
|
8693
9331
|
return arg;
|
|
8694
9332
|
}
|
|
@@ -8704,7 +9342,11 @@ var TypeAnalyzer = class {
|
|
|
8704
9342
|
const left = this.infer(expr.left, env);
|
|
8705
9343
|
const { whenFalse } = this.narrowFromCondition(expr.left, env);
|
|
8706
9344
|
const right = this.infer(expr.right, whenFalse);
|
|
8707
|
-
|
|
9345
|
+
const fallback = unwrapParens(expr.right);
|
|
9346
|
+
const empty = fallback.type === "TableExpression" && fallback.fields.length === 0 || fallback.type === "ArrayExpression" && fallback.elements.length === 0;
|
|
9347
|
+
const truthy = narrowTruthy(left);
|
|
9348
|
+
if (empty && truthy.kind !== "never" && truthy.kind !== "any" && isAssignable(right, truthy)) return truthy;
|
|
9349
|
+
return union([truthy, right]);
|
|
8708
9350
|
}
|
|
8709
9351
|
const l = this.infer(expr.left, env);
|
|
8710
9352
|
const r = this.infer(expr.right, env);
|
|
@@ -8713,14 +9355,21 @@ var TypeAnalyzer = class {
|
|
|
8713
9355
|
if (unwrapParens(expr.left).type === "StringLiteral") this.expectedTypeOf.set(unwrapParens(expr.left), r);
|
|
8714
9356
|
}
|
|
8715
9357
|
switch (op) {
|
|
8716
|
-
case "..":
|
|
8717
|
-
|
|
8718
|
-
|
|
8719
|
-
|
|
9358
|
+
case "..": {
|
|
9359
|
+
const answered = this.operatorResult(expr, op, l, r);
|
|
9360
|
+
if (answered) return answered;
|
|
9361
|
+
this.checkOperands(expr, op, l, r);
|
|
9362
|
+
return stringType;
|
|
9363
|
+
}
|
|
8720
9364
|
case "<":
|
|
8721
9365
|
case ">":
|
|
8722
9366
|
case "<=":
|
|
8723
9367
|
case ">=":
|
|
9368
|
+
if (!this.checkComparison(expr, op, l, r)) this.checkOperands(expr, op, l, r);
|
|
9369
|
+
return booleanType;
|
|
9370
|
+
case "==":
|
|
9371
|
+
case "~=":
|
|
9372
|
+
this.checkOverlap(expr, op, l, r);
|
|
8724
9373
|
return booleanType;
|
|
8725
9374
|
case "+":
|
|
8726
9375
|
case "-":
|
|
@@ -8728,17 +9377,23 @@ var TypeAnalyzer = class {
|
|
|
8728
9377
|
case "/":
|
|
8729
9378
|
case "//":
|
|
8730
9379
|
case "%":
|
|
8731
|
-
case "^":
|
|
8732
|
-
|
|
9380
|
+
case "^": {
|
|
9381
|
+
const answered = this.operatorResult(expr, op, l, r);
|
|
9382
|
+
if (answered) return answered;
|
|
9383
|
+
this.checkOperands(expr, op, l, r);
|
|
9384
|
+
return this.arithmeticOn([l, r]);
|
|
9385
|
+
}
|
|
8733
9386
|
}
|
|
8734
9387
|
return union([l, r]);
|
|
8735
9388
|
}
|
|
8736
9389
|
case "MemberExpression": {
|
|
8737
9390
|
const { type: obj, shortCircuits } = this.chainObject(expr, expr.object, env);
|
|
8738
9391
|
this.checkPrivateMember(expr.property, obj, expr.property.name);
|
|
9392
|
+
this.checkAbstractAccess(expr, obj);
|
|
8739
9393
|
const key = this.refKeyOf(expr);
|
|
8740
9394
|
const narrowed = key === void 0 ? void 0 : env.get(key);
|
|
8741
9395
|
this.checkStringMember(expr, obj, literal(expr.property.name));
|
|
9396
|
+
if (narrowed === void 0) this.checkMissingMember(expr.property, obj, expr.property.name);
|
|
8742
9397
|
return this.chainResult(expr, narrowed ?? this.propertyType(obj, expr.property.name), shortCircuits);
|
|
8743
9398
|
}
|
|
8744
9399
|
case "IndexExpression": {
|
|
@@ -8747,6 +9402,7 @@ var TypeAnalyzer = class {
|
|
|
8747
9402
|
const key = this.refKeyOf(expr);
|
|
8748
9403
|
const narrowed = key === void 0 ? void 0 : env.get(key);
|
|
8749
9404
|
this.checkStringMember(expr, obj, this.expand(idx));
|
|
9405
|
+
if (this.checkIndexKey(expr, obj, idx)) return this.chainResult(expr, anyType, shortCircuits);
|
|
8750
9406
|
const member = this.stringMember(obj, this.expand(idx));
|
|
8751
9407
|
if (member) return this.chainResult(expr, member, shortCircuits);
|
|
8752
9408
|
return this.chainResult(expr, narrowed ?? this.indexedType(obj, idx), shortCircuits);
|
|
@@ -8756,8 +9412,6 @@ var TypeAnalyzer = class {
|
|
|
8756
9412
|
const { type: callee, shortCircuits } = this.chainObject(expr, expr.callee, env);
|
|
8757
9413
|
return this.chainResult(expr, this.inferCall(expr, callee, env), shortCircuits);
|
|
8758
9414
|
}
|
|
8759
|
-
case "NewExpression":
|
|
8760
|
-
return this.inferNew(expr, env);
|
|
8761
9415
|
case "ClassExpression":
|
|
8762
9416
|
return this.visitClass(expr, env);
|
|
8763
9417
|
case "SuperExpression": {
|
|
@@ -8794,18 +9448,14 @@ var TypeAnalyzer = class {
|
|
|
8794
9448
|
/** `new Name(args)` is `Name.new(args)` — the same function, and the
|
|
8795
9449
|
* same check. Saying so here rather than rewriting the tree keeps the
|
|
8796
9450
|
* error messages pointing at what was written. */
|
|
8797
|
-
|
|
8798
|
-
|
|
8799
|
-
const
|
|
8800
|
-
if (
|
|
8801
|
-
|
|
8802
|
-
|
|
8803
|
-
|
|
8804
|
-
|
|
8805
|
-
for (const argument of expr.arguments) this.infer(argument, env);
|
|
8806
|
-
return anyType;
|
|
8807
|
-
}
|
|
8808
|
-
return this.inferCall(expr, constructor, env);
|
|
9451
|
+
/** `__call` of a value with one, as what calling the value calls. */
|
|
9452
|
+
callMetamethod(raw) {
|
|
9453
|
+
const t = this.expand(raw);
|
|
9454
|
+
if (t.kind !== "object" || this.overloadsOf(t).length) return void 0;
|
|
9455
|
+
const method = t.properties.get("__call");
|
|
9456
|
+
if (!method) return void 0;
|
|
9457
|
+
const bound = this.overloadsOf(method.type).map((f) => this.takesSelf(f) ? fn(f.params.slice(1), f.returns, f.varargs, f.typeParams) : f);
|
|
9458
|
+
return bound.length ? intersection(bound) : void 0;
|
|
8809
9459
|
}
|
|
8810
9460
|
/** `super(...)` — the base constructor, run on the instance being built. */
|
|
8811
9461
|
inferSuperCall(expr, env) {
|
|
@@ -8829,8 +9479,9 @@ var TypeAnalyzer = class {
|
|
|
8829
9479
|
this.inferCall(expr, callable, env);
|
|
8830
9480
|
return nilType;
|
|
8831
9481
|
}
|
|
8832
|
-
inferCall(expr,
|
|
9482
|
+
inferCall(expr, called, env) {
|
|
8833
9483
|
this.checkAmbiguousCall(expr);
|
|
9484
|
+
const callee = this.callMetamethod(called) ?? called;
|
|
8834
9485
|
const united = this.unionSignatures(callee);
|
|
8835
9486
|
const fns = united ?? this.overloadsOf(callee);
|
|
8836
9487
|
const explicit = this.explicitTypeArguments(expr, fns);
|
|
@@ -8853,8 +9504,31 @@ var TypeAnalyzer = class {
|
|
|
8853
9504
|
if (arityFits && !picked) this.reportArguments(expr, expr.arguments, fns, () => argTypes, () => 0);
|
|
8854
9505
|
return union(fns.map((f) => this.callReturn(f, argTypes, explicit)));
|
|
8855
9506
|
}
|
|
9507
|
+
this.checkCallable(expr.callee, callee);
|
|
8856
9508
|
return callee.kind === "any" ? anyType : unknownType;
|
|
8857
9509
|
}
|
|
9510
|
+
/** `a()` where `a` is `1`: a value whose type is known and has no call
|
|
9511
|
+
* signature. A union is reported when any member cannot be called, but
|
|
9512
|
+
* only when every member is decided — `unknown`, `any` and anything still
|
|
9513
|
+
* generic say too little. */
|
|
9514
|
+
checkCallable(node, callee) {
|
|
9515
|
+
if (!this.emitDiagnostics || this.notCallableReported.has(node)) return;
|
|
9516
|
+
const raw = this.expand(callee);
|
|
9517
|
+
const parts = (raw.kind === "union" ? raw.types : [raw]).map((m) => this.expand(m));
|
|
9518
|
+
const notCallable = (m) => m.kind === "primitive" && m.name !== "nil" || m.kind === "literal" || m.kind === "templateLiteral" || m.kind === "array" || m.kind === "tuple" || m.kind === "object";
|
|
9519
|
+
const callable = (m) => this.overloadsOf(m).length > 0;
|
|
9520
|
+
const concrete = parts.filter((m) => !(m.kind === "primitive" && m.name === "nil"));
|
|
9521
|
+
if (!concrete.some(notCallable) || !concrete.every((m) => notCallable(m) || callable(m))) return;
|
|
9522
|
+
this.notCallableReported.add(node);
|
|
9523
|
+
const label = node.type === "Identifier" ? node.name : expressionLabel(node);
|
|
9524
|
+
this.diagnostics.push({
|
|
9525
|
+
node,
|
|
9526
|
+
message: `This expression is not callable: ${label === void 0 ? "" : `'${label}' `}is of type '${briefType(withoutNil(raw))}'`
|
|
9527
|
+
});
|
|
9528
|
+
}
|
|
9529
|
+
/** Callees already reported as not callable: a loop body is visited more
|
|
9530
|
+
* than once. */
|
|
9531
|
+
notCallableReported = /* @__PURE__ */ new WeakSet();
|
|
8858
9532
|
/** A `(` on a line of its own continues the statement above it:
|
|
8859
9533
|
*
|
|
8860
9534
|
* const value = map[key]
|
|
@@ -8872,6 +9546,8 @@ var TypeAnalyzer = class {
|
|
|
8872
9546
|
}
|
|
8873
9547
|
inferMethodCall(expr, objType, env) {
|
|
8874
9548
|
this.checkPrivateMember(expr.method, objType, expr.method.name);
|
|
9549
|
+
this.checkStringMember(expr.method, objType, literal(expr.method.name));
|
|
9550
|
+
this.checkMissingMember(expr.method, objType, expr.method.name);
|
|
8875
9551
|
const method = this.propertyType(objType, expr.method.name);
|
|
8876
9552
|
const united = this.unionSignatures(method);
|
|
8877
9553
|
const fns = united ?? this.overloadsOf(method);
|
|
@@ -8905,6 +9581,7 @@ var TypeAnalyzer = class {
|
|
|
8905
9581
|
if (arityFits && !picked) this.reportArguments(expr, expr.arguments, fns, withSelf, selfOf);
|
|
8906
9582
|
return union(fns.map((f) => this.callReturn(f, withSelf(f), explicit)));
|
|
8907
9583
|
}
|
|
9584
|
+
this.checkCallable(expr.method, method);
|
|
8908
9585
|
return objType.kind === "any" ? anyType : unknownType;
|
|
8909
9586
|
}
|
|
8910
9587
|
// --------------------------------------------------------
|
|
@@ -8953,6 +9630,14 @@ var TypeAnalyzer = class {
|
|
|
8953
9630
|
name = target.property.name;
|
|
8954
9631
|
object = target.object;
|
|
8955
9632
|
} else if (target.type === "IndexExpression") {
|
|
9633
|
+
const list = this.expand(this.infer(target.object, env));
|
|
9634
|
+
if ((list.kind === "array" || list.kind === "tuple") && list.readonly) {
|
|
9635
|
+
this.diagnostics.push({
|
|
9636
|
+
node: target,
|
|
9637
|
+
message: `Cannot assign to an element of '${formatType(list)}': it is read-only`
|
|
9638
|
+
});
|
|
9639
|
+
return;
|
|
9640
|
+
}
|
|
8956
9641
|
const index = this.expand(this.infer(target.index, env));
|
|
8957
9642
|
if (index.kind !== "literal" || typeof index.value !== "string") return;
|
|
8958
9643
|
name = index.value;
|
|
@@ -8965,6 +9650,8 @@ var TypeAnalyzer = class {
|
|
|
8965
9650
|
if (id !== void 0 && this.scopes.bindings.get(id)?.declaredBy === "namespace") return;
|
|
8966
9651
|
}
|
|
8967
9652
|
if (!this.isReadonlyProperty(this.expand(this.infer(object, env)), name)) return;
|
|
9653
|
+
const constructing = this.constructing;
|
|
9654
|
+
if (constructing && object.type === "Identifier" && object.name === "this" && constructing.members.some((m) => m.type === "ClassField" && !m.isStatic && m.isReadonly && m.name.name === name)) return;
|
|
8968
9655
|
this.diagnostics.push({
|
|
8969
9656
|
node: target,
|
|
8970
9657
|
message: `Cannot assign to '${name}' because it is a read-only property`
|
|
@@ -9024,6 +9711,8 @@ var TypeAnalyzer = class {
|
|
|
9024
9711
|
inferArray(expr, env, asConst) {
|
|
9025
9712
|
const contextual = this.contextualArrays.get(expr);
|
|
9026
9713
|
if (contextual && !asConst) return contextual;
|
|
9714
|
+
const wantedTuple = this.tupleArrays.get(expr);
|
|
9715
|
+
if (wantedTuple && !asConst) return this.inferTupleLiteral(expr, env, wantedTuple);
|
|
9027
9716
|
const elems = [];
|
|
9028
9717
|
let hadSpread = false;
|
|
9029
9718
|
for (const el of expr.elements) {
|
|
@@ -9043,6 +9732,28 @@ var TypeAnalyzer = class {
|
|
|
9043
9732
|
return asConst || !element || element.type === "SpreadElement" ? t : this.widenUnlessAsked(t, element);
|
|
9044
9733
|
})) : unknownType);
|
|
9045
9734
|
}
|
|
9735
|
+
/** `[a, b, ...more]` where a tuple is wanted: the places it names are the
|
|
9736
|
+
* tuple's, and what comes after them — more elements, or a spread — is
|
|
9737
|
+
* its rest. */
|
|
9738
|
+
inferTupleLiteral(expr, env, wanted) {
|
|
9739
|
+
const fixed = [];
|
|
9740
|
+
const tail = [];
|
|
9741
|
+
let spread = false;
|
|
9742
|
+
expr.elements.forEach((element, i) => {
|
|
9743
|
+
if (element.type === "SpreadElement") {
|
|
9744
|
+
spread = true;
|
|
9745
|
+
const held = this.expand(this.infer(element.argument, env));
|
|
9746
|
+
if (held.kind === "array") tail.push(held.element);
|
|
9747
|
+
else if (held.kind === "tuple") tail.push(...tupleMembers(held));
|
|
9748
|
+
else tail.push(unknownType);
|
|
9749
|
+
return;
|
|
9750
|
+
}
|
|
9751
|
+
const t = this.widenUnlessAsked(this.infer(element, env), element);
|
|
9752
|
+
if (!spread && i < wanted.elements.length) fixed.push(t);
|
|
9753
|
+
else tail.push(t);
|
|
9754
|
+
});
|
|
9755
|
+
return tuple(fixed, tail.length ? union(tail) : void 0);
|
|
9756
|
+
}
|
|
9046
9757
|
/** A literal written inside a fresh table or array widens — `{ n = 1 }` is
|
|
9047
9758
|
* `{ n: number }` — unless the surroundings said a literal belongs there.
|
|
9048
9759
|
* `request({ Method: "GET" })` keeps `"GET"` when `Method` is a union of
|
|
@@ -9099,9 +9810,27 @@ var TypeAnalyzer = class {
|
|
|
9099
9810
|
if (s.indexer) indexer = mergeIndexer(indexer, s.indexer);
|
|
9100
9811
|
}
|
|
9101
9812
|
}
|
|
9813
|
+
this.checkDuplicateKey(expr, field, entries);
|
|
9102
9814
|
}
|
|
9103
9815
|
return objectType(entries, indexer, asConst || void 0);
|
|
9104
9816
|
}
|
|
9817
|
+
/** `{ a: 1, a: 2 }` — the second wins and the first was written for
|
|
9818
|
+
* nothing, which is a typo far more often than a decision. A spread is
|
|
9819
|
+
* not one: overriding what it brought is exactly what it is for. */
|
|
9820
|
+
checkDuplicateKey(expr, field, entries) {
|
|
9821
|
+
if (!this.emitDiagnostics || this.duplicateKeyReported.has(field)) return;
|
|
9822
|
+
const written = field.type === "TableFieldNamed" ? field.key.type === "Identifier" ? field.key.name : field.key.value : field.type === "TableFieldShorthand" ? field.name.name : void 0;
|
|
9823
|
+
if (written === void 0) return;
|
|
9824
|
+
const spreadBefore = expr.fields.slice(0, expr.fields.indexOf(field)).some((f) => f.type === "TableFieldSpread");
|
|
9825
|
+
if (spreadBefore) return;
|
|
9826
|
+
if (entries.filter(([k]) => k === written).length < 2) return;
|
|
9827
|
+
this.duplicateKeyReported.add(field);
|
|
9828
|
+
this.diagnostics.push({
|
|
9829
|
+
node: field.type === "TableFieldNamed" ? field.key : field.name,
|
|
9830
|
+
message: `'${written}' is given twice in this table; only the last one is kept`
|
|
9831
|
+
});
|
|
9832
|
+
}
|
|
9833
|
+
duplicateKeyReported = /* @__PURE__ */ new WeakSet();
|
|
9105
9834
|
/** A value inferred `as const`, widened back wherever `context` does not
|
|
9106
9835
|
* ask for a literal: `satisfies`' result type. A property keeps `"circle"`
|
|
9107
9836
|
* when the contract's property admits string literals, and becomes
|
|
@@ -9128,7 +9857,7 @@ var TypeAnalyzer = class {
|
|
|
9128
9857
|
case "tuple": {
|
|
9129
9858
|
const tupleContext = ctx && this.membersOf(ctx).find((m) => m.kind === "tuple");
|
|
9130
9859
|
if (tupleContext?.kind === "tuple") {
|
|
9131
|
-
return tuple(value.elements.map((e, i) => this.keepContextualLiterals(e, tupleContext.elements[i])), value.
|
|
9860
|
+
return tuple(value.elements.map((e, i) => this.keepContextualLiterals(e, tupleContext.elements[i])), value.rest);
|
|
9132
9861
|
}
|
|
9133
9862
|
const arrayContext = ctx && this.membersOf(ctx).find((m) => m.kind === "array");
|
|
9134
9863
|
const element = arrayContext?.kind === "array" ? arrayContext.element : void 0;
|
|
@@ -9385,7 +10114,8 @@ var TypeAnalyzer = class {
|
|
|
9385
10114
|
for (const f of candidates) {
|
|
9386
10115
|
if (!f.predicate) continue;
|
|
9387
10116
|
const arg = args[f.predicate.param];
|
|
9388
|
-
|
|
10117
|
+
const onCondition = f.predicate.asserts && !f.predicate.type;
|
|
10118
|
+
if (!arg || this.refKeyOf(arg) === void 0 && !onCondition) continue;
|
|
9389
10119
|
if (f.typeParams?.length && f.predicate.type) {
|
|
9390
10120
|
const subst = this.inferTypeArgs(f, argTypes);
|
|
9391
10121
|
return {
|
|
@@ -9406,6 +10136,10 @@ var TypeAnalyzer = class {
|
|
|
9406
10136
|
const found = this.predicateCallTarget(call, env);
|
|
9407
10137
|
if (!found || !found.predicate.asserts) return;
|
|
9408
10138
|
const filter = found.predicate.type;
|
|
10139
|
+
if (this.refKeyOf(found.arg) === void 0) {
|
|
10140
|
+
if (!filter) this.applyNarrowing(found.arg, env, env, forkEnv(env));
|
|
10141
|
+
return;
|
|
10142
|
+
}
|
|
9409
10143
|
this.narrowRef(found.arg, env, env, forkEnv(env), (cur) => filter ? { yes: narrowTo(cur, filter), no: narrowExclude(cur, filter) } : { yes: narrowTruthy(cur), no: narrowFalsy(cur) });
|
|
9410
10144
|
}
|
|
9411
10145
|
// --------------------------------------------------------
|
|
@@ -9430,6 +10164,10 @@ var TypeAnalyzer = class {
|
|
|
9430
10164
|
if (base === void 0) return void 0;
|
|
9431
10165
|
if (expr.index.type === "StringLiteral") return `${base}.${expr.index.value}`;
|
|
9432
10166
|
if (expr.index.type === "NumberLiteral") return `${base}#${expr.index.value}`;
|
|
10167
|
+
if (expr.index.type === "Identifier") {
|
|
10168
|
+
const id = this.bindingIdOf(expr.index);
|
|
10169
|
+
return id === void 0 ? void 0 : `${base}[${bindKey(id)}]`;
|
|
10170
|
+
}
|
|
9433
10171
|
return void 0;
|
|
9434
10172
|
}
|
|
9435
10173
|
default:
|
|
@@ -9525,26 +10263,45 @@ var TypeAnalyzer = class {
|
|
|
9525
10263
|
* that copied it no longer holds that value: forget the alias. */
|
|
9526
10264
|
unalias(key) {
|
|
9527
10265
|
for (const k of [...this.refAliases.keys()]) {
|
|
9528
|
-
if (k !== key && !k
|
|
10266
|
+
if (k !== key && !isBelow(k, key) && !k.includes(`[${key}]`)) continue;
|
|
9529
10267
|
for (const other of this.refAliases.get(k) ?? []) this.refAliases.get(other)?.delete(k);
|
|
9530
10268
|
this.refAliases.delete(k);
|
|
9531
10269
|
}
|
|
9532
10270
|
}
|
|
9533
|
-
/** Drop every narrowing recorded for a path strictly under `key
|
|
10271
|
+
/** Drop every narrowing recorded for a path strictly under `key` — and,
|
|
10272
|
+
* when `key` is a whole variable, for every `t[key]` indexed by it: that
|
|
10273
|
+
* now names a different slot. */
|
|
9534
10274
|
invalidateBelow(env, key) {
|
|
10275
|
+
const asIndex = `[${key}]`;
|
|
9535
10276
|
for (const k of [...env.keys()]) {
|
|
9536
|
-
if (k
|
|
10277
|
+
if (isBelow(k, key) || k.includes(asIndex)) env.delete(k);
|
|
9537
10278
|
}
|
|
9538
10279
|
}
|
|
9539
10280
|
/** An assignment through a reference invalidates it and everything under
|
|
9540
10281
|
* it, then records the assigned type. */
|
|
9541
10282
|
assignToRef(expr, value, env) {
|
|
10283
|
+
this.invalidateSiblings(expr, env);
|
|
9542
10284
|
const key = this.refKeyOf(expr);
|
|
9543
10285
|
if (key === void 0) return;
|
|
9544
10286
|
this.invalidateBelow(env, key);
|
|
9545
10287
|
this.unalias(key);
|
|
9546
10288
|
env.set(key, value);
|
|
9547
10289
|
}
|
|
10290
|
+
/** A write to one slot of a table can be a write to a slot narrowed under
|
|
10291
|
+
* another name: `t.x = nil` is `t[k] = nil` when `k` is `"x"`. So a
|
|
10292
|
+
* write by name forgets what is known through variable keys, and a write
|
|
10293
|
+
* through any computed key forgets everything known about the table's
|
|
10294
|
+
* slots. */
|
|
10295
|
+
invalidateSiblings(expr, env) {
|
|
10296
|
+
const inner = expr.type === "ParenthesizedExpression" ? expr.expression : expr;
|
|
10297
|
+
if (inner.type !== "MemberExpression" && inner.type !== "IndexExpression") return;
|
|
10298
|
+
const parent = this.refKeyOf(inner.object);
|
|
10299
|
+
if (parent === void 0) return;
|
|
10300
|
+
const computed = inner.type === "IndexExpression" && inner.index.type !== "StringLiteral" && inner.index.type !== "NumberLiteral";
|
|
10301
|
+
for (const k of [...env.keys()]) {
|
|
10302
|
+
if (computed ? isBelow(k, parent) : k.startsWith(`${parent}[`)) env.delete(k);
|
|
10303
|
+
}
|
|
10304
|
+
}
|
|
9548
10305
|
// --------------------------------------------------------
|
|
9549
10306
|
// Small helpers
|
|
9550
10307
|
// --------------------------------------------------------
|
|
@@ -9559,6 +10316,92 @@ var TypeAnalyzer = class {
|
|
|
9559
10316
|
this.selfType = saved;
|
|
9560
10317
|
}
|
|
9561
10318
|
}
|
|
10319
|
+
/** Arithmetic no declared metamethod answers: a number — unless an
|
|
10320
|
+
* operand is `any`, which may be a value with metamethods of its own.
|
|
10321
|
+
* `(a.Position - b.Position).Magnitude` on an `a` nothing typed is a
|
|
10322
|
+
* `Vector3` as far as anyone knows, not a number with no `Magnitude`. */
|
|
10323
|
+
arithmeticOn(operands) {
|
|
10324
|
+
return operands.some((t) => this.expand(t).kind === "any") ? anyType : numberType;
|
|
10325
|
+
}
|
|
10326
|
+
/** `a == b` where nothing `a` can hold is anything `b` can: the answer is
|
|
10327
|
+
* always the same, so the comparison was not the one meant. Only types
|
|
10328
|
+
* that say what they hold take part — `any`, `unknown` and a type
|
|
10329
|
+
* parameter say nothing, and a table compares by identity, so two of them
|
|
10330
|
+
* may well be the same table. */
|
|
10331
|
+
checkOverlap(node, op, l, r) {
|
|
10332
|
+
if (!this.emitDiagnostics) return;
|
|
10333
|
+
const left = this.expand(l);
|
|
10334
|
+
const right = this.expand(r);
|
|
10335
|
+
const judged = (t) => {
|
|
10336
|
+
const parts = t.kind === "union" ? t.types.map((m) => this.expand(m)) : [t];
|
|
10337
|
+
return parts.every((p) => p.kind === "literal" || p.kind === "primitive");
|
|
10338
|
+
};
|
|
10339
|
+
const isNil = (t) => t.kind === "primitive" && t.name === "nil";
|
|
10340
|
+
if (isNil(left) || isNil(right)) return;
|
|
10341
|
+
if (!judged(left) || !judged(right)) return;
|
|
10342
|
+
if (isAssignable(left, right) || isAssignable(right, left)) return;
|
|
10343
|
+
this.diagnostics.push({
|
|
10344
|
+
node,
|
|
10345
|
+
message: `This comparison is always ${op === "==" ? "false" : "true"}: '${formatType(l)}' and '${formatType(r)}' have no value in common`
|
|
10346
|
+
});
|
|
10347
|
+
}
|
|
10348
|
+
/** What an operator takes, where no metamethod answered for it. Luau
|
|
10349
|
+
* raises on the rest — `n * 2` with `n` nil is an error at the line it
|
|
10350
|
+
* runs, and a nil `n` is exactly what `number | nil` says may happen — so
|
|
10351
|
+
* it is said here instead. `any` passes: nothing is known about it. */
|
|
10352
|
+
checkOperands(node, op, left, right) {
|
|
10353
|
+
if (!this.emitDiagnostics) return;
|
|
10354
|
+
const fits = (t) => {
|
|
10355
|
+
const x = this.expand(t);
|
|
10356
|
+
if (x.kind === "any" || x.kind === "never") return true;
|
|
10357
|
+
switch (op) {
|
|
10358
|
+
// `..` joins strings, and Luau writes a number into one.
|
|
10359
|
+
case "..":
|
|
10360
|
+
return isAssignable(x, stringType) || isAssignable(x, numberType);
|
|
10361
|
+
// `#` counts a string's bytes or a table's entries.
|
|
10362
|
+
case "#":
|
|
10363
|
+
return x.kind === "array" || x.kind === "tuple" || x.kind === "object" || isAssignable(x, stringType);
|
|
10364
|
+
default:
|
|
10365
|
+
return isAssignable(x, numberType);
|
|
10366
|
+
}
|
|
10367
|
+
};
|
|
10368
|
+
if (op === "<" || op === ">" || op === "<=" || op === ">=") {
|
|
10369
|
+
const both = (t) => this.expand(t);
|
|
10370
|
+
const [l, r] = [both(left), both(right)];
|
|
10371
|
+
const loose = (t) => t.kind === "any" || t.kind === "never";
|
|
10372
|
+
const alike = (as) => isAssignable(l, as) && isAssignable(r, as);
|
|
10373
|
+
if (loose(l) || loose(r) || alike(numberType) || alike(stringType)) return;
|
|
10374
|
+
} else if (fits(left) && (right === void 0 || fits(right))) {
|
|
10375
|
+
return;
|
|
10376
|
+
}
|
|
10377
|
+
this.diagnostics.push({
|
|
10378
|
+
node,
|
|
10379
|
+
message: right === void 0 ? `Operator '${op}' cannot be applied to type '${formatType(left)}'` : `Operator '${op}' cannot be applied to types '${formatType(left)}' and '${formatType(right)}'`
|
|
10380
|
+
});
|
|
10381
|
+
}
|
|
10382
|
+
/** `a < b` between class instances: Luau asks `__lt` (`__le` for `<=`),
|
|
10383
|
+
* with the operands swapped for `>` and `>=`, and an instance without
|
|
10384
|
+
* one cannot be compared at all. */
|
|
10385
|
+
checkComparison(node, op, l, r) {
|
|
10386
|
+
if (!this.emitDiagnostics) return false;
|
|
10387
|
+
const left = this.expand(l);
|
|
10388
|
+
const right = this.expand(r);
|
|
10389
|
+
const instance = (t) => t.kind === "object" && t.class !== void 0;
|
|
10390
|
+
if (!instance(left) && !instance(right)) return false;
|
|
10391
|
+
const name = op === "<" || op === ">" ? "__lt" : "__le";
|
|
10392
|
+
const operands = op === ">" || op === ">=" ? [right, left] : [left, right];
|
|
10393
|
+
for (const receiver of operands) {
|
|
10394
|
+
const method = receiver.kind === "object" ? receiver.properties.get(name) : void 0;
|
|
10395
|
+
if (!method) continue;
|
|
10396
|
+
if (this.pickOverload(this.overloadsOf(method.type), operands)) return true;
|
|
10397
|
+
break;
|
|
10398
|
+
}
|
|
10399
|
+
this.diagnostics.push({
|
|
10400
|
+
node,
|
|
10401
|
+
message: `Operator '${op}' cannot be applied to types '${formatType(l)}' and '${formatType(r)}'`
|
|
10402
|
+
});
|
|
10403
|
+
return true;
|
|
10404
|
+
}
|
|
9562
10405
|
/** What an operator on a value with metamethods gives: `a + b` calls
|
|
9563
10406
|
* `__add` on `a`, or failing that on `b` with the operands swapped — the
|
|
9564
10407
|
* order Luau tries them in. That is how `Vector3 + Vector3`, `CFrame *
|
|
@@ -9844,7 +10687,56 @@ var TypeAnalyzer = class {
|
|
|
9844
10687
|
return this.bindingByDecl.get(node) ?? this.bindingByPos.get(posKey(name, node.line.start, node.column.start));
|
|
9845
10688
|
}
|
|
9846
10689
|
};
|
|
9847
|
-
|
|
10690
|
+
var libraryNames = /* @__PURE__ */ new WeakMap();
|
|
10691
|
+
function namesDeclaredIn(program) {
|
|
10692
|
+
const known = libraryNames.get(program);
|
|
10693
|
+
if (known) return known;
|
|
10694
|
+
const names = /* @__PURE__ */ new Set();
|
|
10695
|
+
for (const stmt of program.body.statements) {
|
|
10696
|
+
const declaration = stmt.type === "ExportStatement" || stmt.type === "ExportDefaultStatement" ? stmt.declaration : stmt;
|
|
10697
|
+
if (declaration.type === "TypeAliasStatement") names.add(declaration.name.name);
|
|
10698
|
+
else if (declaration.type === "ExportTypeAliasStatement") names.add(declaration.alias.name.name);
|
|
10699
|
+
else if (declaration.type === "DeclareClassStatement") names.add(declaration.name.name);
|
|
10700
|
+
else if (declaration.type === "ClassDeclaration" && declaration.name) names.add(declaration.name.name);
|
|
10701
|
+
else if (stmt.type === "ImportStatement") {
|
|
10702
|
+
for (const s of stmt.specifiers) names.add(s.local.name);
|
|
10703
|
+
if (stmt.defaultImport) names.add(stmt.defaultImport.name);
|
|
10704
|
+
if (stmt.namespaceImport) names.add(stmt.namespaceImport.name);
|
|
10705
|
+
}
|
|
10706
|
+
}
|
|
10707
|
+
libraryNames.set(program, names);
|
|
10708
|
+
return names;
|
|
10709
|
+
}
|
|
10710
|
+
var sharedClassesByLib = /* @__PURE__ */ new WeakMap();
|
|
10711
|
+
function shareableClasses(libs, program) {
|
|
10712
|
+
if (!libs.length) return void 0;
|
|
10713
|
+
const mine = namesDeclaredIn(program);
|
|
10714
|
+
if (mine.size) {
|
|
10715
|
+
for (const lib of libs) {
|
|
10716
|
+
for (const name of namesDeclaredIn(lib)) if (mine.has(name)) return void 0;
|
|
10717
|
+
}
|
|
10718
|
+
}
|
|
10719
|
+
const existing = sharedClassesByLib.get(libs[0]);
|
|
10720
|
+
if (existing) {
|
|
10721
|
+
const same = existing.libs.length === libs.length && existing.libs.every((lib, i) => lib === libs[i]);
|
|
10722
|
+
if (same) return existing;
|
|
10723
|
+
return void 0;
|
|
10724
|
+
}
|
|
10725
|
+
const store = { libs: [...libs], classTypes: /* @__PURE__ */ new WeakMap(), classMembers: /* @__PURE__ */ new WeakMap() };
|
|
10726
|
+
sharedClassesByLib.set(libs[0], store);
|
|
10727
|
+
return store;
|
|
10728
|
+
}
|
|
10729
|
+
var referencedNamesOf = /* @__PURE__ */ new WeakMap();
|
|
10730
|
+
var typeQueryIn = /* @__PURE__ */ new WeakMap();
|
|
10731
|
+
function referencedTypeNames(node, out) {
|
|
10732
|
+
if (out === void 0 && node && typeof node === "object") {
|
|
10733
|
+
const known = referencedNamesOf.get(node);
|
|
10734
|
+
if (known) return known;
|
|
10735
|
+
const found = referencedTypeNames(node, []);
|
|
10736
|
+
referencedNamesOf.set(node, found);
|
|
10737
|
+
return found;
|
|
10738
|
+
}
|
|
10739
|
+
out = out ?? [];
|
|
9848
10740
|
if (!node || typeof node !== "object") return out;
|
|
9849
10741
|
if (Array.isArray(node)) {
|
|
9850
10742
|
for (const item of node) referencedTypeNames(item, out);
|
|
@@ -9861,9 +10753,11 @@ function referencedTypeNames(node, out = []) {
|
|
|
9861
10753
|
}
|
|
9862
10754
|
function containsTypeQuery(node) {
|
|
9863
10755
|
if (!node || typeof node !== "object") return false;
|
|
9864
|
-
|
|
9865
|
-
if (
|
|
9866
|
-
|
|
10756
|
+
const known = typeQueryIn.get(node);
|
|
10757
|
+
if (known !== void 0) return known;
|
|
10758
|
+
const found = Array.isArray(node) ? node.some(containsTypeQuery) : node.type === "TypeofTypeNode" || Object.values(node).some(containsTypeQuery);
|
|
10759
|
+
typeQueryIn.set(node, found);
|
|
10760
|
+
return found;
|
|
9867
10761
|
}
|
|
9868
10762
|
var STRING_INTRINSICS = /* @__PURE__ */ new Set(["Uppercase", "Lowercase", "Capitalize", "Uncapitalize"]);
|
|
9869
10763
|
function briefType(t) {
|
|
@@ -9883,7 +10777,7 @@ function briefType(t) {
|
|
|
9883
10777
|
}
|
|
9884
10778
|
|
|
9885
10779
|
// src/project/host.ts
|
|
9886
|
-
import { readFileSync, statSync } from "fs";
|
|
10780
|
+
import { readdirSync, readFileSync, statSync } from "fs";
|
|
9887
10781
|
var nodeHost = {
|
|
9888
10782
|
readFile(path) {
|
|
9889
10783
|
try {
|
|
@@ -9891,6 +10785,13 @@ var nodeHost = {
|
|
|
9891
10785
|
} catch {
|
|
9892
10786
|
return void 0;
|
|
9893
10787
|
}
|
|
10788
|
+
},
|
|
10789
|
+
readDirectory(path) {
|
|
10790
|
+
try {
|
|
10791
|
+
return readdirSync(path, { withFileTypes: true }).filter((entry) => entry.isDirectory() || entry.isSymbolicLink()).map((entry) => entry.name);
|
|
10792
|
+
} catch {
|
|
10793
|
+
return void 0;
|
|
10794
|
+
}
|
|
9894
10795
|
}
|
|
9895
10796
|
};
|
|
9896
10797
|
|
|
@@ -10082,13 +10983,31 @@ function resolveTypeLibraries(config, host = nodeHost) {
|
|
|
10082
10983
|
else problems.push({ file: config.path, message: `'${entry}' has no ${ENTRY_FILE} (or 'tilua.types' in its package.json)`, ...entryPosition(config, entry) });
|
|
10083
10984
|
continue;
|
|
10084
10985
|
}
|
|
10085
|
-
|
|
10086
|
-
|
|
10986
|
+
if (entry.includes("*")) {
|
|
10987
|
+
const matched = matchPackages(entry, config.directory, host);
|
|
10988
|
+
if (matched === void 0) {
|
|
10989
|
+
problems.push({
|
|
10990
|
+
file: config.path,
|
|
10991
|
+
message: `'${entry}' needs the folders listed, and this tool cannot list them; name the libraries one by one`,
|
|
10992
|
+
...entryPosition(config, entry)
|
|
10993
|
+
});
|
|
10994
|
+
} else if (!matched.length) {
|
|
10995
|
+
problems.push({
|
|
10996
|
+
file: config.path,
|
|
10997
|
+
message: `No installed type library matches '${entry}'`,
|
|
10998
|
+
...entryPosition(config, entry)
|
|
10999
|
+
});
|
|
11000
|
+
}
|
|
11001
|
+
for (const found2 of matched ?? []) addPackage(found2.directory, found2.file, /* @__PURE__ */ new Set());
|
|
11002
|
+
continue;
|
|
11003
|
+
}
|
|
11004
|
+
const names = entry.startsWith("@") ? [entry] : [`@tilua-types/${entry}`, entry];
|
|
11005
|
+
const found = names.map((name) => findPackage(name, config.directory, host)).find(Boolean);
|
|
10087
11006
|
if (found) addPackage(found.directory, found.file, /* @__PURE__ */ new Set());
|
|
10088
11007
|
else {
|
|
10089
11008
|
problems.push({
|
|
10090
11009
|
file: config.path,
|
|
10091
|
-
message: `Cannot find type library '${
|
|
11010
|
+
message: names.length === 1 ? `Cannot find type library '${entry}'. Install it with: npm i -D ${entry}` : `Cannot find type library '${names[0]}' or '${entry}'. Install it with: npm i -D ${names[0]}`,
|
|
10092
11011
|
...entryPosition(config, entry)
|
|
10093
11012
|
});
|
|
10094
11013
|
}
|
|
@@ -10125,6 +11044,32 @@ function findPackage(name, from, host) {
|
|
|
10125
11044
|
directory = parent;
|
|
10126
11045
|
}
|
|
10127
11046
|
}
|
|
11047
|
+
function matchPackages(pattern, from, host) {
|
|
11048
|
+
if (!host.readDirectory) return void 0;
|
|
11049
|
+
const slash = pattern.lastIndexOf("/");
|
|
11050
|
+
const scope = slash < 0 ? "" : pattern.slice(0, slash);
|
|
11051
|
+
const last = pattern.slice(slash + 1);
|
|
11052
|
+
const matches = new RegExp(`^${last.split("*").map(escapeRegExp2).join(".*")}$`);
|
|
11053
|
+
const found = /* @__PURE__ */ new Map();
|
|
11054
|
+
let directory = resolve2(from);
|
|
11055
|
+
for (; ; ) {
|
|
11056
|
+
const folder = join2(directory, "node_modules", ...scope ? scope.split("/") : []);
|
|
11057
|
+
for (const name of host.readDirectory(folder) ?? []) {
|
|
11058
|
+
const full = scope ? `${scope}/${name}` : name;
|
|
11059
|
+
if (!matches.test(name) || found.has(full)) continue;
|
|
11060
|
+
const candidate = join2(folder, name);
|
|
11061
|
+
const file = packageEntry(candidate, host);
|
|
11062
|
+
if (file) found.set(full, { directory: candidate, file });
|
|
11063
|
+
}
|
|
11064
|
+
const parent = dirname2(directory);
|
|
11065
|
+
if (parent === directory) break;
|
|
11066
|
+
directory = parent;
|
|
11067
|
+
}
|
|
11068
|
+
return [...found.keys()].sort().map((name) => found.get(name));
|
|
11069
|
+
}
|
|
11070
|
+
function escapeRegExp2(text) {
|
|
11071
|
+
return text.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
|
|
11072
|
+
}
|
|
10128
11073
|
function dependencyNames(directory, host) {
|
|
10129
11074
|
const manifest = readJson(join2(directory, "package.json"), host);
|
|
10130
11075
|
const names = /* @__PURE__ */ new Set();
|
|
@@ -10223,7 +11168,8 @@ function sourceMapTypes(text, path, options) {
|
|
|
10223
11168
|
for (const child of node.children ?? []) {
|
|
10224
11169
|
if (!isNode(child)) continue;
|
|
10225
11170
|
const childAlias = visit(child, [...segments, child.name], alias);
|
|
10226
|
-
|
|
11171
|
+
const service = child.name === child.className && options.classes.has(child.className);
|
|
11172
|
+
if (!IDENTIFIER.test(child.name) || taken.has(child.name) && !service || named.has(child.name)) continue;
|
|
10227
11173
|
named.add(child.name);
|
|
10228
11174
|
members.push(`${child.name}: ${childAlias}`);
|
|
10229
11175
|
}
|
|
@@ -10283,6 +11229,7 @@ export {
|
|
|
10283
11229
|
BinaryOperators,
|
|
10284
11230
|
CONFIG_FILE_NAMES,
|
|
10285
11231
|
Keywords,
|
|
11232
|
+
LANGUAGE_GLOBALS,
|
|
10286
11233
|
LexError,
|
|
10287
11234
|
Operators,
|
|
10288
11235
|
PRELUDE_SOURCE,
|
|
@@ -10351,6 +11298,7 @@ export {
|
|
|
10351
11298
|
tilua,
|
|
10352
11299
|
tokenize,
|
|
10353
11300
|
tuple,
|
|
11301
|
+
tupleMembers,
|
|
10354
11302
|
typeParam,
|
|
10355
11303
|
unify,
|
|
10356
11304
|
union,
|