@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.cjs
CHANGED
|
@@ -23,6 +23,7 @@ __export(index_exports, {
|
|
|
23
23
|
BinaryOperators: () => BinaryOperators,
|
|
24
24
|
CONFIG_FILE_NAMES: () => CONFIG_FILE_NAMES,
|
|
25
25
|
Keywords: () => Keywords,
|
|
26
|
+
LANGUAGE_GLOBALS: () => LANGUAGE_GLOBALS,
|
|
26
27
|
LexError: () => LexError,
|
|
27
28
|
Operators: () => Operators,
|
|
28
29
|
PRELUDE_SOURCE: () => PRELUDE_SOURCE,
|
|
@@ -91,6 +92,7 @@ __export(index_exports, {
|
|
|
91
92
|
tilua: () => tilua,
|
|
92
93
|
tokenize: () => tokenize,
|
|
93
94
|
tuple: () => tuple,
|
|
95
|
+
tupleMembers: () => tupleMembers,
|
|
94
96
|
typeParam: () => typeParam,
|
|
95
97
|
unify: () => unify,
|
|
96
98
|
union: () => union,
|
|
@@ -698,6 +700,9 @@ function applyDirectives(directives, diagnostics, lineOf) {
|
|
|
698
700
|
var UNUSED_EXPECT_ERROR = "Unused '@tilua-expect-error' directive";
|
|
699
701
|
|
|
700
702
|
// src/ast/builders.ts
|
|
703
|
+
var NO_DOTS = "tilua has no bare '...': take a rest parameter, '...args: T[]', or 'scriptArgs' for the script's own";
|
|
704
|
+
var ACCESSIBILITIES = /* @__PURE__ */ new Set(["public", "private", "protected"]);
|
|
705
|
+
var CLASS_MODIFIERS = /* @__PURE__ */ new Set([...ACCESSIBILITIES, "static", "abstract", "override", "readonly"]);
|
|
701
706
|
function bindThis(func, at) {
|
|
702
707
|
bindThisParam(func.params, at);
|
|
703
708
|
func.isMethod = true;
|
|
@@ -965,11 +970,16 @@ var Parser = class {
|
|
|
965
970
|
return this.attempt(() => this.parseExpression(), stop, (start, from) => this.errorExpression(start, from));
|
|
966
971
|
}
|
|
967
972
|
/** A comma-separated list of values: a `return`'s, a declaration's, an
|
|
968
|
-
* assignment's.
|
|
969
|
-
* arguments
|
|
973
|
+
* assignment's. Each is one value; a spread belongs in a call's
|
|
974
|
+
* arguments or an array, and is reported here — then read as one, so
|
|
975
|
+
* the rest still means something. */
|
|
970
976
|
expressionListOr(stop) {
|
|
971
977
|
const until = () => stop() || this.checkPunctuator(",");
|
|
972
|
-
const item = () =>
|
|
978
|
+
const item = () => {
|
|
979
|
+
if (!(this.checkOperator("...") && this.startsSpread())) return this.expressionOr(until);
|
|
980
|
+
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'");
|
|
981
|
+
return this.parseSpreadArgument(until);
|
|
982
|
+
};
|
|
973
983
|
const list = [item()];
|
|
974
984
|
while (this.matchPunctuator(",")) list.push(item());
|
|
975
985
|
return list;
|
|
@@ -1334,7 +1344,7 @@ var Parser = class {
|
|
|
1334
1344
|
if (t.type === "Identifier" && t.value === "type" && this.peek(1).type === "Identifier") {
|
|
1335
1345
|
return this.parseTypeAliasStatement();
|
|
1336
1346
|
}
|
|
1337
|
-
if (
|
|
1347
|
+
if (this.startsClassDeclaration()) {
|
|
1338
1348
|
return this.parseClassDeclaration();
|
|
1339
1349
|
}
|
|
1340
1350
|
if (t.type === "Identifier" && t.value === "declare") {
|
|
@@ -1371,8 +1381,6 @@ var Parser = class {
|
|
|
1371
1381
|
type: "FunctionTypeNode",
|
|
1372
1382
|
generics: head.generics,
|
|
1373
1383
|
params,
|
|
1374
|
-
hasVarargs: head.hasVarargs,
|
|
1375
|
-
varargType: head.varargTypeAnnotation,
|
|
1376
1384
|
returnType: head.returnType ?? { type: "TypeReference", base: head.predicate ? "boolean" : "unknown", typeArguments: [], ...spanFrom(start, start) },
|
|
1377
1385
|
predicate: head.predicate,
|
|
1378
1386
|
...spanFrom(start, this.previous())
|
|
@@ -1511,7 +1519,7 @@ var Parser = class {
|
|
|
1511
1519
|
this.advance();
|
|
1512
1520
|
if (this.checkIdentifierValue("default")) {
|
|
1513
1521
|
this.advance();
|
|
1514
|
-
const declaration = this.
|
|
1522
|
+
const declaration = this.startsClassDeclaration() ? this.parseClassDeclaration() : this.parseExpression(0);
|
|
1515
1523
|
return { type: "ExportDefaultStatement", declaration, ...spanFrom(start, this.previous()) };
|
|
1516
1524
|
}
|
|
1517
1525
|
if (this.checkIdentifierValue("type") && this.peek(1).type === "Identifier") {
|
|
@@ -1522,7 +1530,7 @@ var Parser = class {
|
|
|
1522
1530
|
const declaration = this.parseVariableDeclaration();
|
|
1523
1531
|
return { type: "ExportStatement", declaration, ...spanFrom(start, this.previous()) };
|
|
1524
1532
|
}
|
|
1525
|
-
if (this.
|
|
1533
|
+
if (this.startsClassDeclaration()) {
|
|
1526
1534
|
const declaration = this.parseClassDeclaration();
|
|
1527
1535
|
return { type: "ExportStatement", declaration, ...spanFrom(start, this.previous()) };
|
|
1528
1536
|
}
|
|
@@ -1637,9 +1645,11 @@ var Parser = class {
|
|
|
1637
1645
|
const start = this.current();
|
|
1638
1646
|
this.expectKeyword("for");
|
|
1639
1647
|
this.expectPunctuator("(");
|
|
1648
|
+
const declared = this.checkKeyword("const") || this.checkKeyword("let") ? this.advance().value : void 0;
|
|
1640
1649
|
const first = this.parseBindingTarget(true);
|
|
1641
1650
|
const untilDo = () => this.checkPunctuator(")");
|
|
1642
1651
|
if (first.type === "IdentifierPattern" && this.matchOperator("=")) {
|
|
1652
|
+
if (declared) this.problemAt(start, `A counting loop names its variable without '${declared}': 'for (i = 1, 10)'`);
|
|
1643
1653
|
const from = this.expressionOr(() => untilDo() || this.checkPunctuator(","));
|
|
1644
1654
|
this.expectPunctuator(",");
|
|
1645
1655
|
const to = this.expressionOr(() => untilDo() || this.checkPunctuator(","));
|
|
@@ -1658,17 +1668,24 @@ var Parser = class {
|
|
|
1658
1668
|
...spanFrom(start, this.previous())
|
|
1659
1669
|
};
|
|
1660
1670
|
}
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1671
|
+
if (this.checkPunctuator(",")) {
|
|
1672
|
+
this.problemAt(this.current(), "A loop takes one value each time: take its parts with a destructuring, 'for (const [k, v] in pairs(t))'");
|
|
1673
|
+
while (this.matchPunctuator(",")) this.parseBindingTarget(true);
|
|
1674
|
+
} else if (!declared) {
|
|
1675
|
+
this.problemAt(start, "A loop over a source names its value with 'const' or 'let': 'for (const item in source)'");
|
|
1664
1676
|
}
|
|
1665
1677
|
this.expectKeyword("in");
|
|
1666
|
-
const
|
|
1678
|
+
const iterator = this.expressionOr(() => untilDo() || this.checkPunctuator(","));
|
|
1679
|
+
if (this.checkPunctuator(",")) {
|
|
1680
|
+
this.problemAt(this.current(), "A loop has one source: an array, a table, an iterator function or an iteration '[step, state, first]'");
|
|
1681
|
+
while (this.matchPunctuator(",")) this.expressionOr(() => untilDo() || this.checkPunctuator(","));
|
|
1682
|
+
}
|
|
1667
1683
|
const body = this.parseForBody();
|
|
1668
1684
|
return {
|
|
1669
1685
|
type: "GenericForStatement",
|
|
1670
|
-
|
|
1671
|
-
|
|
1686
|
+
kind: declared ?? "const",
|
|
1687
|
+
variable: first,
|
|
1688
|
+
iterator,
|
|
1672
1689
|
body,
|
|
1673
1690
|
...spanFrom(start, this.previous())
|
|
1674
1691
|
};
|
|
@@ -1738,7 +1755,10 @@ var Parser = class {
|
|
|
1738
1755
|
* the compiler must not accept it, and recorded inside. The message says
|
|
1739
1756
|
* what is wrong on its own — no token is appended. */
|
|
1740
1757
|
problem(message) {
|
|
1741
|
-
|
|
1758
|
+
this.problemAt(this.current(), message);
|
|
1759
|
+
}
|
|
1760
|
+
/** `problem`, pointed at `t` rather than at where parsing has got to. */
|
|
1761
|
+
problemAt(t, message) {
|
|
1742
1762
|
const error = new ParseError(message, t.line.start, t.column.start);
|
|
1743
1763
|
if (!this.recover) throw error;
|
|
1744
1764
|
this.record(error);
|
|
@@ -1751,10 +1771,13 @@ var Parser = class {
|
|
|
1751
1771
|
* like a function (`function m() ... end`). */
|
|
1752
1772
|
parseClassDeclaration() {
|
|
1753
1773
|
const start = this.current();
|
|
1774
|
+
const isAbstract = this.checkIdentifierValue("abstract");
|
|
1775
|
+
if (isAbstract) this.advance();
|
|
1754
1776
|
this.advance();
|
|
1755
1777
|
const name = this.parseIdentifier();
|
|
1756
1778
|
const typeParams = this.checkOperator("<") ? this.parseGenericTypeParameterList() : [];
|
|
1757
1779
|
const { superclass, superArguments } = this.parseExtends();
|
|
1780
|
+
const shapes = this.parseImplements();
|
|
1758
1781
|
const members = this.parseClassBody(start);
|
|
1759
1782
|
return {
|
|
1760
1783
|
type: "ClassDeclaration",
|
|
@@ -1762,10 +1785,21 @@ var Parser = class {
|
|
|
1762
1785
|
typeParams,
|
|
1763
1786
|
superclass,
|
|
1764
1787
|
superArguments,
|
|
1788
|
+
...shapes ? { implements: shapes } : {},
|
|
1789
|
+
...isAbstract ? { isAbstract } : {},
|
|
1765
1790
|
members,
|
|
1766
1791
|
...spanFrom(start, this.previous())
|
|
1767
1792
|
};
|
|
1768
1793
|
}
|
|
1794
|
+
/** `class Name` or `abstract class Name` — all three words soft, so a name
|
|
1795
|
+
* has to follow for either to be a declaration: `class: T`, `class = 1`
|
|
1796
|
+
* and `abstract(x)` are names. */
|
|
1797
|
+
startsClassDeclaration() {
|
|
1798
|
+
const offset = this.checkIdentifierValue("abstract") ? 1 : 0;
|
|
1799
|
+
const word = this.peek(offset);
|
|
1800
|
+
if (word.type !== "Identifier" || word.value !== "class") return false;
|
|
1801
|
+
return this.peek(offset + 1).type === "Identifier" && !this.punctuatorAt(offset + 2, ":") && !this.operatorAt(offset + 2, "=") && !this.punctuatorAt(offset + 2, "(");
|
|
1802
|
+
}
|
|
1769
1803
|
/** `class ... end` as a value. It may be named — the name is for the class
|
|
1770
1804
|
* itself, not for the scope around it — and takes no type parameters,
|
|
1771
1805
|
* since nothing could write the arguments. */
|
|
@@ -1777,8 +1811,25 @@ var Parser = class {
|
|
|
1777
1811
|
this.error("A class written as a value takes no type parameters: nothing could write the arguments");
|
|
1778
1812
|
}
|
|
1779
1813
|
const { superclass, superArguments } = this.parseExtends();
|
|
1814
|
+
const shapes = this.parseImplements();
|
|
1780
1815
|
const members = this.parseClassBody(start);
|
|
1781
|
-
return {
|
|
1816
|
+
return {
|
|
1817
|
+
type: "ClassExpression",
|
|
1818
|
+
name,
|
|
1819
|
+
superclass,
|
|
1820
|
+
superArguments,
|
|
1821
|
+
...shapes ? { implements: shapes } : {},
|
|
1822
|
+
members,
|
|
1823
|
+
...spanFrom(start, this.previous())
|
|
1824
|
+
};
|
|
1825
|
+
}
|
|
1826
|
+
/** `implements Shape, Named` — each a type, checked against the instance. */
|
|
1827
|
+
parseImplements() {
|
|
1828
|
+
if (!this.checkIdentifierValue("implements")) return void 0;
|
|
1829
|
+
this.advance();
|
|
1830
|
+
const shapes = [this.parseType()];
|
|
1831
|
+
while (this.matchPunctuator(",")) shapes.push(this.parseType());
|
|
1832
|
+
return shapes;
|
|
1782
1833
|
}
|
|
1783
1834
|
/** `extends Base` / `extends Box<number>`. */
|
|
1784
1835
|
parseExtends() {
|
|
@@ -1831,23 +1882,66 @@ var Parser = class {
|
|
|
1831
1882
|
parseClassMember() {
|
|
1832
1883
|
const start = this.current();
|
|
1833
1884
|
let accessibility;
|
|
1834
|
-
|
|
1835
|
-
|
|
1885
|
+
const modifiers = /* @__PURE__ */ new Set();
|
|
1886
|
+
while (this.startsClassModifier()) {
|
|
1887
|
+
const token = this.advance();
|
|
1888
|
+
const word = token.value;
|
|
1889
|
+
if (ACCESSIBILITIES.has(word)) {
|
|
1890
|
+
if (accessibility) this.problemAt(token, `A member has one accessibility: '${accessibility}' is already written`);
|
|
1891
|
+
accessibility = word;
|
|
1892
|
+
} else {
|
|
1893
|
+
if (modifiers.has(word)) this.problemAt(token, `'${word}' is written twice`);
|
|
1894
|
+
modifiers.add(word);
|
|
1895
|
+
}
|
|
1836
1896
|
}
|
|
1837
|
-
const member = this.
|
|
1838
|
-
if (!
|
|
1897
|
+
const member = this.parseClassMemberAfterModifiers(start, modifiers);
|
|
1898
|
+
if (!member) return member;
|
|
1899
|
+
const misplaced = (message) => this.problemAt(start, message);
|
|
1839
1900
|
if (member.type === "ClassConstructor") {
|
|
1840
|
-
|
|
1901
|
+
const written = [...accessibility ? [accessibility] : [], ...modifiers];
|
|
1902
|
+
if (written.length) misplaced(`A constructor cannot be '${written[0]}'`);
|
|
1841
1903
|
return member;
|
|
1842
1904
|
}
|
|
1843
|
-
|
|
1905
|
+
if (modifiers.has("readonly") && member.type !== "ClassField") misplaced("Only a field can be 'readonly'");
|
|
1906
|
+
if (modifiers.has("abstract") && member.type !== "ClassMethod") misplaced("Only a method can be 'abstract'");
|
|
1907
|
+
if (modifiers.has("abstract") && member.isStatic) misplaced("A static method cannot be 'abstract'");
|
|
1908
|
+
if (modifiers.has("override") && member.isStatic) misplaced("A static member cannot be 'override'");
|
|
1909
|
+
return {
|
|
1910
|
+
...member,
|
|
1911
|
+
...accessibility ? { accessibility } : {},
|
|
1912
|
+
...modifiers.has("readonly") && member.type === "ClassField" ? { isReadonly: true } : {},
|
|
1913
|
+
...modifiers.has("override") ? { isOverride: true } : {}
|
|
1914
|
+
};
|
|
1844
1915
|
}
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1916
|
+
/** Is the word here a member modifier, rather than a member's name? */
|
|
1917
|
+
startsClassModifier() {
|
|
1918
|
+
const t = this.current();
|
|
1919
|
+
if (t.type !== "Identifier" || !CLASS_MODIFIERS.has(t.value)) return false;
|
|
1920
|
+
const next = this.peek(1);
|
|
1921
|
+
return next.type === "Identifier" || next.type === "Keyword" && next.value === "function";
|
|
1922
|
+
}
|
|
1923
|
+
parseClassMemberAfterModifiers(start, modifiers) {
|
|
1924
|
+
const isStatic = modifiers.has("static");
|
|
1848
1925
|
if (this.checkKeyword("function")) {
|
|
1849
1926
|
this.advance();
|
|
1850
1927
|
const memberName = this.parseIdentifier();
|
|
1928
|
+
if (modifiers.has("abstract")) {
|
|
1929
|
+
const head = this.parseFunctionHead();
|
|
1930
|
+
const func = {
|
|
1931
|
+
type: "FunctionBody",
|
|
1932
|
+
generics: head.generics,
|
|
1933
|
+
params: head.params,
|
|
1934
|
+
hasVarargs: head.hasVarargs,
|
|
1935
|
+
returnType: head.returnType,
|
|
1936
|
+
predicate: head.predicate,
|
|
1937
|
+
body: { type: "Block", statements: [], ...spanFrom(head.start, this.previous()) },
|
|
1938
|
+
...spanFrom(head.start, this.previous())
|
|
1939
|
+
};
|
|
1940
|
+
if (!isStatic) bindThis(func, start);
|
|
1941
|
+
if (!head.returnType) this.problem("An abstract method has no body to infer from: write its return type");
|
|
1942
|
+
if (this.checkPunctuator("{")) this.problem("An abstract method has no body");
|
|
1943
|
+
return { type: "ClassMethod", name: memberName, isStatic, func, isAbstract: true, ...spanFrom(start, this.previous()) };
|
|
1944
|
+
}
|
|
1851
1945
|
const signatures = [];
|
|
1852
1946
|
let written = memberName;
|
|
1853
1947
|
while (true) {
|
|
@@ -1965,12 +2059,17 @@ var Parser = class {
|
|
|
1965
2059
|
parseReturnStatement(stopAtNewline = false) {
|
|
1966
2060
|
const start = this.current();
|
|
1967
2061
|
this.expectKeyword("return");
|
|
1968
|
-
let
|
|
2062
|
+
let argument;
|
|
1969
2063
|
const pastLine = () => stopAtNewline && this.current().line.start > start.line.start;
|
|
1970
2064
|
if (!pastLine() && this.isExpressionStart()) {
|
|
1971
|
-
|
|
2065
|
+
const values = this.expressionListOr(pastLine);
|
|
2066
|
+
argument = values[0];
|
|
2067
|
+
if (values.length > 1) {
|
|
2068
|
+
this.problemAt(start, "A function returns one value: return several as an array, 'return [a, b]'");
|
|
2069
|
+
argument = { type: "ArrayExpression", elements: values, ...spanFrom(values[0], values[values.length - 1]) };
|
|
2070
|
+
}
|
|
1972
2071
|
}
|
|
1973
|
-
return { type: "ReturnStatement",
|
|
2072
|
+
return { type: "ReturnStatement", argument, ...spanFrom(start, this.previous()) };
|
|
1974
2073
|
}
|
|
1975
2074
|
parseTypeAliasStatement() {
|
|
1976
2075
|
const start = this.current();
|
|
@@ -2029,7 +2128,7 @@ var Parser = class {
|
|
|
2029
2128
|
...spanFrom(start, this.previous())
|
|
2030
2129
|
};
|
|
2031
2130
|
}
|
|
2032
|
-
if (first.type === "CallExpression" || first.type === "MethodCallExpression"
|
|
2131
|
+
if (first.type === "CallExpression" || first.type === "MethodCallExpression") {
|
|
2033
2132
|
return { type: "CallStatement", expression: first, ...spanFrom(start, this.previous()) };
|
|
2034
2133
|
}
|
|
2035
2134
|
let expression = first;
|
|
@@ -2082,6 +2181,15 @@ var Parser = class {
|
|
|
2082
2181
|
}
|
|
2083
2182
|
return false;
|
|
2084
2183
|
}
|
|
2184
|
+
/** A string written straight before a method call — `"a":upper()`, or
|
|
2185
|
+
* `` `{n}`:rep(2) ``. Luau only takes `("a"):upper()`; the compiler adds
|
|
2186
|
+
* the parentheses. Anything else after a string (`"a".x`, `"a"(1)`) is
|
|
2187
|
+
* not a prefix expression here either. */
|
|
2188
|
+
startsStringMethodCall() {
|
|
2189
|
+
const t = this.current();
|
|
2190
|
+
const isString = t.type === "InterpolatedString" || t.type === "Literal" && t.kind === "string";
|
|
2191
|
+
return isString && this.punctuatorAt(1, ":") && this.startsMethodCall(1);
|
|
2192
|
+
}
|
|
2085
2193
|
/** Does the next token start right where the current one ends? */
|
|
2086
2194
|
touchesNext() {
|
|
2087
2195
|
const current = this.current();
|
|
@@ -2268,6 +2376,7 @@ var Parser = class {
|
|
|
2268
2376
|
}
|
|
2269
2377
|
parseAtom() {
|
|
2270
2378
|
const t = this.current();
|
|
2379
|
+
if (this.startsStringMethodCall()) return this.parsePrefixExpression();
|
|
2271
2380
|
if (t.type === "Literal") {
|
|
2272
2381
|
this.advance();
|
|
2273
2382
|
const lit = t;
|
|
@@ -2287,8 +2396,9 @@ var Parser = class {
|
|
|
2287
2396
|
return this.buildInterpolatedString(t);
|
|
2288
2397
|
}
|
|
2289
2398
|
if (t.type === "Operator" && t.value === "...") {
|
|
2399
|
+
this.problem(NO_DOTS);
|
|
2290
2400
|
this.advance();
|
|
2291
|
-
return { type: "
|
|
2401
|
+
return { type: "ErrorExpression", ...spanFrom(t, t) };
|
|
2292
2402
|
}
|
|
2293
2403
|
if (t.type === "Keyword" && t.value === "function") {
|
|
2294
2404
|
this.advance();
|
|
@@ -2364,12 +2474,17 @@ var Parser = class {
|
|
|
2364
2474
|
const start = this.current();
|
|
2365
2475
|
let base;
|
|
2366
2476
|
if (this.startsNew()) {
|
|
2367
|
-
|
|
2368
|
-
|
|
2477
|
+
this.problem("tilua has no 'new' operator: construct with 'Name.new(...)'");
|
|
2478
|
+
this.advance();
|
|
2479
|
+
}
|
|
2480
|
+
if (this.classDepth > 0 && this.checkIdentifierValue("super") && this.startsSuperUse()) {
|
|
2369
2481
|
this.advance();
|
|
2370
2482
|
base = { type: "SuperExpression", ...spanFrom(start, start) };
|
|
2371
2483
|
} else if (this.checkType("Identifier")) {
|
|
2372
2484
|
base = this.parseIdentifier();
|
|
2485
|
+
} else if (this.startsStringMethodCall()) {
|
|
2486
|
+
const t = this.advance();
|
|
2487
|
+
base = t.type === "InterpolatedString" ? this.buildInterpolatedString(t) : { type: "StringLiteral", value: t.value, raw: t.raw, ...spanFrom(t, t) };
|
|
2373
2488
|
} else if (this.matchPunctuator("(")) {
|
|
2374
2489
|
const inner = this.inBrackets(() => this.parseExpression());
|
|
2375
2490
|
this.expectPunctuator(")");
|
|
@@ -2388,6 +2503,21 @@ var Parser = class {
|
|
|
2388
2503
|
base = { type: "MemberExpression", object: base, property: prop, optional: true, ...spanFrom(base, prop) };
|
|
2389
2504
|
continue;
|
|
2390
2505
|
}
|
|
2506
|
+
if (punct === "." && this.punctuatorAt(2, "[")) {
|
|
2507
|
+
this.advance();
|
|
2508
|
+
this.advance();
|
|
2509
|
+
this.expectPunctuator("[");
|
|
2510
|
+
const index = this.inBrackets(() => this.parseExpression());
|
|
2511
|
+
this.expectPunctuator("]");
|
|
2512
|
+
base = {
|
|
2513
|
+
type: "IndexExpression",
|
|
2514
|
+
object: base,
|
|
2515
|
+
index,
|
|
2516
|
+
optional: true,
|
|
2517
|
+
...spanFrom(base, this.previous())
|
|
2518
|
+
};
|
|
2519
|
+
continue;
|
|
2520
|
+
}
|
|
2391
2521
|
if (punct === "." && this.punctuatorAt(2, "(")) {
|
|
2392
2522
|
this.advance();
|
|
2393
2523
|
this.advance();
|
|
@@ -2480,9 +2610,9 @@ var Parser = class {
|
|
|
2480
2610
|
}
|
|
2481
2611
|
return base;
|
|
2482
2612
|
}
|
|
2483
|
-
/** `new
|
|
2484
|
-
*
|
|
2485
|
-
*
|
|
2613
|
+
/** `new Name(...)`, the way other languages build an instance — only to
|
|
2614
|
+
* say so. A function or field called `new` — `Instance.new(x)`, and
|
|
2615
|
+
* `Vec.new(1)` itself — is untouched. */
|
|
2486
2616
|
startsNew() {
|
|
2487
2617
|
return this.checkIdentifierValue("new") && this.peek(1).type === "Identifier";
|
|
2488
2618
|
}
|
|
@@ -2492,25 +2622,6 @@ var Parser = class {
|
|
|
2492
2622
|
startsSuperUse() {
|
|
2493
2623
|
return this.punctuatorAt(1, "(") || this.punctuatorAt(1, ".") && this.peek(2).type === "Identifier";
|
|
2494
2624
|
}
|
|
2495
|
-
/** `new Name(args)` / `new Module.Name(args)`. The callee is a name, or a
|
|
2496
|
-
* name reached through a module — never an arbitrary expression, so the
|
|
2497
|
-
* arguments are unambiguously the constructor's. */
|
|
2498
|
-
parseNewExpression() {
|
|
2499
|
-
const start = this.current();
|
|
2500
|
-
this.advance();
|
|
2501
|
-
let callee = this.parseIdentifier();
|
|
2502
|
-
while (this.checkPunctuator(".") && this.peek(1).type === "Identifier") {
|
|
2503
|
-
this.advance();
|
|
2504
|
-
const property = this.parseIdentifier();
|
|
2505
|
-
callee = { type: "MemberExpression", object: callee, property, ...spanFrom(start, property) };
|
|
2506
|
-
}
|
|
2507
|
-
const typeArguments = this.tryCallTypeArguments();
|
|
2508
|
-
if (!this.checkPunctuator("(")) {
|
|
2509
|
-
this.error("Expected '(' after the class being constructed: 'new Name(...)'");
|
|
2510
|
-
}
|
|
2511
|
-
const args = this.parseCallArguments();
|
|
2512
|
-
return { type: "NewExpression", callee, arguments: args, typeArguments, ...spanFrom(start, this.previous()) };
|
|
2513
|
-
}
|
|
2514
2625
|
/** After `...`, is there something to spread? Nothing following it means
|
|
2515
2626
|
* the vararg pack, which is what `f(...)` has always passed on. */
|
|
2516
2627
|
startsSpread() {
|
|
@@ -2552,8 +2663,8 @@ var Parser = class {
|
|
|
2552
2663
|
const errors = this.errors.length;
|
|
2553
2664
|
try {
|
|
2554
2665
|
this.advance();
|
|
2555
|
-
const list = [this.
|
|
2556
|
-
while (this.matchPunctuator(",") && !this.checkOperator(">")) list.push(this.
|
|
2666
|
+
const list = [this.parseType()];
|
|
2667
|
+
while (this.matchPunctuator(",") && !this.checkOperator(">")) list.push(this.parseType());
|
|
2557
2668
|
this.expectOperator(">");
|
|
2558
2669
|
if (!this.startsCallArguments()) throw new ParseRecover("not a call");
|
|
2559
2670
|
return list;
|
|
@@ -2685,7 +2796,7 @@ var Parser = class {
|
|
|
2685
2796
|
if (this.checkOperator("...")) {
|
|
2686
2797
|
const dots = this.advance();
|
|
2687
2798
|
if (this.checkPunctuator("]") || this.checkPunctuator(",")) {
|
|
2688
|
-
|
|
2799
|
+
this.problemAt(dots, NO_DOTS);
|
|
2689
2800
|
} else {
|
|
2690
2801
|
const argument = this.expressionOr(stop);
|
|
2691
2802
|
elements.push({ type: "SpreadElement", argument, ...spanFrom(dots, argument) });
|
|
@@ -2839,35 +2950,16 @@ var Parser = class {
|
|
|
2839
2950
|
column: p.column
|
|
2840
2951
|
};
|
|
2841
2952
|
}
|
|
2842
|
-
|
|
2953
|
+
/** `T...` written where a type goes: a pack, which tilua does not have. */
|
|
2954
|
+
rejectPackReference() {
|
|
2843
2955
|
if (this.checkType("Identifier") && this.peek(1).type === "Operator" && this.peek(1).value === "...") {
|
|
2844
|
-
|
|
2845
|
-
|
|
2956
|
+
this.problemAt(
|
|
2957
|
+
this.peek(1),
|
|
2958
|
+
"tilua has no type packs: several values are a tuple, '[A, B]', and a varying number an array, 'T[]'"
|
|
2959
|
+
);
|
|
2960
|
+
this.advance();
|
|
2846
2961
|
this.advance();
|
|
2847
|
-
const packRef = { type: "TypeReference", base, typeArguments: [], ...spanFrom(start, start) };
|
|
2848
|
-
return {
|
|
2849
|
-
type: "TypePackNode",
|
|
2850
|
-
types: [],
|
|
2851
|
-
hasVarargs: true,
|
|
2852
|
-
// Wrap in `VariadicTypeNode`, matching the convention used by
|
|
2853
|
-
// `parseFunctionTypeAfterParen`'s identifier-pack-reference
|
|
2854
|
-
// branch, so the printer can tell `A...` (name-first, this
|
|
2855
|
-
// case) apart from `...T` (dots-first) and append rather
|
|
2856
|
-
// than prepend the `...`.
|
|
2857
|
-
varargType: { type: "VariadicTypeNode", typeAnnotation: packRef, ...spanFrom(start, this.previous()) },
|
|
2858
|
-
...spanFrom(start, this.previous())
|
|
2859
|
-
};
|
|
2860
|
-
}
|
|
2861
|
-
return this.parseType();
|
|
2862
|
-
}
|
|
2863
|
-
parseTypeArgument() {
|
|
2864
|
-
if (this.checkOperator("...")) {
|
|
2865
|
-
return this.parseTypePack();
|
|
2866
|
-
}
|
|
2867
|
-
if (this.checkType("Identifier") && this.peek(1).type === "Operator" && this.peek(1).value === "...") {
|
|
2868
|
-
return this.parseTypeOrTypePackReference();
|
|
2869
2962
|
}
|
|
2870
|
-
return this.parseType();
|
|
2871
2963
|
}
|
|
2872
2964
|
/** The part shared by a real function body and an overload signature:
|
|
2873
2965
|
* `<generics>(params): ReturnType`, up to (but not including) the block. */
|
|
@@ -2880,7 +2972,6 @@ var Parser = class {
|
|
|
2880
2972
|
this.expectPunctuator("(");
|
|
2881
2973
|
const params = [];
|
|
2882
2974
|
let hasVarargs = false;
|
|
2883
|
-
let varargTypeAnnotation;
|
|
2884
2975
|
if (!this.checkPunctuator(")")) {
|
|
2885
2976
|
while (true) {
|
|
2886
2977
|
if (this.checkOperator("...")) {
|
|
@@ -2888,14 +2979,14 @@ var Parser = class {
|
|
|
2888
2979
|
hasVarargs = true;
|
|
2889
2980
|
if (this.checkType("Identifier")) {
|
|
2890
2981
|
const nameTok = this.expectIdentifier();
|
|
2891
|
-
let
|
|
2982
|
+
let typeAnnotation3;
|
|
2892
2983
|
if (this.matchPunctuator(":")) {
|
|
2893
|
-
|
|
2984
|
+
typeAnnotation3 = this.typeOr(() => this.checkPunctuator(")"));
|
|
2894
2985
|
}
|
|
2895
2986
|
params.push({
|
|
2896
2987
|
type: "FunctionParameter",
|
|
2897
2988
|
name: nameTok.value,
|
|
2898
|
-
typeAnnotation:
|
|
2989
|
+
typeAnnotation: typeAnnotation3,
|
|
2899
2990
|
rest: true,
|
|
2900
2991
|
...spanFrom(dots, this.previous())
|
|
2901
2992
|
});
|
|
@@ -2904,9 +2995,20 @@ var Parser = class {
|
|
|
2904
2995
|
}
|
|
2905
2996
|
break;
|
|
2906
2997
|
}
|
|
2998
|
+
this.problemAt(dots, NO_DOTS);
|
|
2999
|
+
let typeAnnotation2;
|
|
2907
3000
|
if (this.matchPunctuator(":")) {
|
|
2908
|
-
|
|
3001
|
+
this.rejectPackReference();
|
|
3002
|
+
const element = this.parseType();
|
|
3003
|
+
typeAnnotation2 = { type: "ArrayTypeNode", element, ...spanFrom(element, element) };
|
|
2909
3004
|
}
|
|
3005
|
+
params.push({
|
|
3006
|
+
type: "FunctionParameter",
|
|
3007
|
+
name: "args",
|
|
3008
|
+
typeAnnotation: typeAnnotation2,
|
|
3009
|
+
rest: true,
|
|
3010
|
+
...spanFrom(dots, this.previous())
|
|
3011
|
+
});
|
|
2910
3012
|
break;
|
|
2911
3013
|
}
|
|
2912
3014
|
const paramStart = this.current();
|
|
@@ -2948,10 +3050,16 @@ var Parser = class {
|
|
|
2948
3050
|
if (this.matchPunctuator(":")) {
|
|
2949
3051
|
predicate = this.tryParseTypePredicate();
|
|
2950
3052
|
if (!predicate) {
|
|
2951
|
-
returnType = this.attempt(() => this.
|
|
3053
|
+
returnType = this.attempt(() => this.parseReturnType(), () => false, () => void 0);
|
|
2952
3054
|
}
|
|
2953
3055
|
}
|
|
2954
|
-
return { start, generics, params, hasVarargs,
|
|
3056
|
+
return { start, generics, params, hasVarargs, returnType, predicate };
|
|
3057
|
+
}
|
|
3058
|
+
/** What a function returns: one type. `T...` is reported as the pack it
|
|
3059
|
+
* is, and the type after it read. */
|
|
3060
|
+
parseReturnType() {
|
|
3061
|
+
this.rejectPackReference();
|
|
3062
|
+
return this.parseType();
|
|
2955
3063
|
}
|
|
2956
3064
|
/** TypeScript-style type-guard return annotations, in return position only:
|
|
2957
3065
|
*
|
|
@@ -3021,7 +3129,6 @@ var Parser = class {
|
|
|
3021
3129
|
generics: [],
|
|
3022
3130
|
params: [{ type: "FunctionParameter", name: name.value, ...spanFrom(name, name) }],
|
|
3023
3131
|
hasVarargs: false,
|
|
3024
|
-
varargTypeAnnotation: void 0,
|
|
3025
3132
|
returnType: void 0,
|
|
3026
3133
|
predicate: void 0
|
|
3027
3134
|
};
|
|
@@ -3033,7 +3140,6 @@ var Parser = class {
|
|
|
3033
3140
|
generics: head.generics,
|
|
3034
3141
|
params: head.params,
|
|
3035
3142
|
hasVarargs: head.hasVarargs,
|
|
3036
|
-
varargTypeAnnotation: head.varargTypeAnnotation,
|
|
3037
3143
|
returnType: head.returnType,
|
|
3038
3144
|
predicate: head.predicate,
|
|
3039
3145
|
body,
|
|
@@ -3045,7 +3151,7 @@ var Parser = class {
|
|
|
3045
3151
|
returnOf(expression) {
|
|
3046
3152
|
const statement = {
|
|
3047
3153
|
type: "ReturnStatement",
|
|
3048
|
-
|
|
3154
|
+
argument: expression,
|
|
3049
3155
|
...spanFrom(expression, expression)
|
|
3050
3156
|
};
|
|
3051
3157
|
return { type: "Block", statements: [statement], ...spanFrom(expression, expression) };
|
|
@@ -3058,7 +3164,6 @@ var Parser = class {
|
|
|
3058
3164
|
generics: head.generics,
|
|
3059
3165
|
params: head.params,
|
|
3060
3166
|
hasVarargs: head.hasVarargs,
|
|
3061
|
-
varargTypeAnnotation: head.varargTypeAnnotation,
|
|
3062
3167
|
returnType: head.returnType,
|
|
3063
3168
|
predicate: head.predicate,
|
|
3064
3169
|
body,
|
|
@@ -3071,7 +3176,6 @@ var Parser = class {
|
|
|
3071
3176
|
generics: head.generics,
|
|
3072
3177
|
params: head.params,
|
|
3073
3178
|
hasVarargs: head.hasVarargs,
|
|
3074
|
-
varargTypeAnnotation: head.varargTypeAnnotation,
|
|
3075
3179
|
returnType: head.returnType,
|
|
3076
3180
|
predicate: head.predicate,
|
|
3077
3181
|
...spanFrom(head.start, this.previous())
|
|
@@ -3089,7 +3193,6 @@ var Parser = class {
|
|
|
3089
3193
|
generics: head.generics,
|
|
3090
3194
|
params: head.params,
|
|
3091
3195
|
hasVarargs: head.hasVarargs,
|
|
3092
|
-
varargTypeAnnotation: head.varargTypeAnnotation,
|
|
3093
3196
|
returnType: head.returnType,
|
|
3094
3197
|
predicate: head.predicate,
|
|
3095
3198
|
body,
|
|
@@ -3168,8 +3271,12 @@ var Parser = class {
|
|
|
3168
3271
|
* `name?: T` for an optional property or parameter. */
|
|
3169
3272
|
parseSuffixType() {
|
|
3170
3273
|
if (this.checkIdentifierValue("readonly")) {
|
|
3274
|
+
const start = this.current();
|
|
3171
3275
|
this.advance();
|
|
3172
|
-
|
|
3276
|
+
const t2 = this.parseSuffixType();
|
|
3277
|
+
if (t2.type === "ArrayTypeNode" || t2.type === "TupleTypeNode") return { ...t2, isReadonly: true };
|
|
3278
|
+
this.problemAt(start, "'readonly' belongs on an array or a tuple: a property is marked where it is written");
|
|
3279
|
+
return t2;
|
|
3173
3280
|
}
|
|
3174
3281
|
let t = this.parsePrimaryType();
|
|
3175
3282
|
while (true) {
|
|
@@ -3230,9 +3337,10 @@ var Parser = class {
|
|
|
3230
3337
|
return this.parseFunctionTypeAfterParen(t, []);
|
|
3231
3338
|
}
|
|
3232
3339
|
if (t.type === "Operator" && t.value === "...") {
|
|
3340
|
+
this.problem("'...' in a type goes before a rest parameter, '...args: T[]', or at the end of a tuple, '[A, ...B[]]'");
|
|
3233
3341
|
this.advance();
|
|
3234
|
-
const
|
|
3235
|
-
return { type: "
|
|
3342
|
+
const element = this.parseType();
|
|
3343
|
+
return { type: "ArrayTypeNode", element, ...spanFrom(t, this.previous()) };
|
|
3236
3344
|
}
|
|
3237
3345
|
if (t.type === "Punctuator" && t.value === "{") {
|
|
3238
3346
|
return this.parseTableType();
|
|
@@ -3240,13 +3348,22 @@ var Parser = class {
|
|
|
3240
3348
|
if (t.type === "Punctuator" && t.value === "[") {
|
|
3241
3349
|
this.advance();
|
|
3242
3350
|
const elements = [];
|
|
3351
|
+
let rest;
|
|
3243
3352
|
while (!this.checkPunctuator("]")) {
|
|
3353
|
+
if (this.matchOperator("...")) {
|
|
3354
|
+
rest = this.parseType();
|
|
3355
|
+
if (rest.type !== "ArrayTypeNode" && !(rest.type === "TypeReference" && rest.base !== "nil")) {
|
|
3356
|
+
this.problem("The rest of a tuple is an array type: '...T[]'");
|
|
3357
|
+
}
|
|
3358
|
+
this.matchPunctuator(",");
|
|
3359
|
+
break;
|
|
3360
|
+
}
|
|
3244
3361
|
elements.push(this.parseType());
|
|
3245
3362
|
if (this.matchPunctuator(",")) continue;
|
|
3246
3363
|
break;
|
|
3247
3364
|
}
|
|
3248
3365
|
this.expectPunctuator("]");
|
|
3249
|
-
return { type: "TupleTypeNode", elements, ...spanFrom(t, this.previous()) };
|
|
3366
|
+
return { type: "TupleTypeNode", elements, ...rest ? { rest } : {}, ...spanFrom(t, this.previous()) };
|
|
3250
3367
|
}
|
|
3251
3368
|
if (t.type === "Identifier" && t.value === "typeof" && this.peek(1).type === "Punctuator" && this.peek(1).value === "(") {
|
|
3252
3369
|
this.advance();
|
|
@@ -3293,9 +3410,9 @@ var Parser = class {
|
|
|
3293
3410
|
if (this.checkOperator("<")) {
|
|
3294
3411
|
this.advance();
|
|
3295
3412
|
if (!this.checkOperator(">")) {
|
|
3296
|
-
typeArguments.push(this.
|
|
3413
|
+
typeArguments.push(this.parseType());
|
|
3297
3414
|
while (this.matchPunctuator(",") && !this.checkOperator(">")) {
|
|
3298
|
-
typeArguments.push(this.
|
|
3415
|
+
typeArguments.push(this.parseType());
|
|
3299
3416
|
}
|
|
3300
3417
|
}
|
|
3301
3418
|
this.expectOperator(">");
|
|
@@ -3306,13 +3423,12 @@ var Parser = class {
|
|
|
3306
3423
|
}
|
|
3307
3424
|
parseFunctionTypeAfterParen(start, generics) {
|
|
3308
3425
|
const params = [];
|
|
3309
|
-
let
|
|
3310
|
-
let varargType;
|
|
3426
|
+
let rested = false;
|
|
3311
3427
|
if (!this.checkPunctuator(")")) {
|
|
3312
3428
|
while (true) {
|
|
3313
3429
|
if (this.checkOperator("...")) {
|
|
3314
3430
|
const dots = this.advance();
|
|
3315
|
-
|
|
3431
|
+
rested = true;
|
|
3316
3432
|
if (this.checkType("Identifier") && this.punctuatorAt(1, ":")) {
|
|
3317
3433
|
const nameTok2 = this.expectIdentifier();
|
|
3318
3434
|
this.advance();
|
|
@@ -3326,16 +3442,21 @@ var Parser = class {
|
|
|
3326
3442
|
});
|
|
3327
3443
|
break;
|
|
3328
3444
|
}
|
|
3329
|
-
|
|
3445
|
+
this.problemAt(dots, "A function type takes the rest of its arguments as an array: '(...args: T[]) => R'");
|
|
3446
|
+
this.rejectPackReference();
|
|
3447
|
+
const element = this.parseType();
|
|
3448
|
+
params.push({
|
|
3449
|
+
type: "FunctionTypeParameter",
|
|
3450
|
+
name: "args",
|
|
3451
|
+
rest: true,
|
|
3452
|
+
typeAnnotation: { type: "ArrayTypeNode", element, ...spanFrom(element, element) },
|
|
3453
|
+
...spanFrom(dots, this.previous())
|
|
3454
|
+
});
|
|
3330
3455
|
break;
|
|
3331
3456
|
}
|
|
3332
3457
|
if (this.checkType("Identifier") && this.peek(1).type === "Operator" && this.peek(1).value === "...") {
|
|
3333
|
-
|
|
3334
|
-
|
|
3335
|
-
this.advance();
|
|
3336
|
-
hasVarargs = true;
|
|
3337
|
-
varargType = { type: "VariadicTypeNode", typeAnnotation: packRef, ...spanFrom(packStart, this.previous()) };
|
|
3338
|
-
break;
|
|
3458
|
+
this.rejectPackReference();
|
|
3459
|
+
continue;
|
|
3339
3460
|
}
|
|
3340
3461
|
let name;
|
|
3341
3462
|
let optional2 = false;
|
|
@@ -3365,19 +3486,17 @@ var Parser = class {
|
|
|
3365
3486
|
this.expectPunctuator(")");
|
|
3366
3487
|
if (this.matchPunctuator("=>") || this.mistypedArrow()) {
|
|
3367
3488
|
const predicate = this.tryParseTypePredicate();
|
|
3368
|
-
const returnType = predicate ? { type: "TypeReference", base: "boolean", typeArguments: [], ...spanFrom(start, this.previous()) } : this.
|
|
3489
|
+
const returnType = predicate ? { type: "TypeReference", base: "boolean", typeArguments: [], ...spanFrom(start, this.previous()) } : this.parseReturnType();
|
|
3369
3490
|
return {
|
|
3370
3491
|
type: "FunctionTypeNode",
|
|
3371
3492
|
generics,
|
|
3372
3493
|
params,
|
|
3373
|
-
hasVarargs,
|
|
3374
|
-
varargType,
|
|
3375
3494
|
returnType,
|
|
3376
3495
|
predicate,
|
|
3377
3496
|
...spanFrom(start, this.previous())
|
|
3378
3497
|
};
|
|
3379
3498
|
}
|
|
3380
|
-
if (params.length === 1 && !params[0].name && !
|
|
3499
|
+
if (params.length === 1 && !params[0].name && !rested) {
|
|
3381
3500
|
return {
|
|
3382
3501
|
type: "ParenthesizedTypeNode",
|
|
3383
3502
|
typeAnnotation: params[0].typeAnnotation,
|
|
@@ -3387,11 +3506,14 @@ var Parser = class {
|
|
|
3387
3506
|
if (params.some((p) => p.name !== void 0)) {
|
|
3388
3507
|
this.error("Expected '=>' for function type");
|
|
3389
3508
|
}
|
|
3509
|
+
if (!params.length) {
|
|
3510
|
+
this.problemAt(start, "'()' is not a type: a function that returns nothing returns 'nil'");
|
|
3511
|
+
return { type: "TypeReference", base: "nil", typeArguments: [], ...spanFrom(start, this.previous()) };
|
|
3512
|
+
}
|
|
3513
|
+
this.problemAt(start, "Several types in parentheses are a pack, which tilua does not have: write a tuple, '[A, B]'");
|
|
3390
3514
|
return {
|
|
3391
|
-
type: "
|
|
3392
|
-
|
|
3393
|
-
hasVarargs,
|
|
3394
|
-
varargType,
|
|
3515
|
+
type: "TupleTypeNode",
|
|
3516
|
+
elements: params.map((p) => p.typeAnnotation),
|
|
3395
3517
|
...spanFrom(start, this.previous())
|
|
3396
3518
|
};
|
|
3397
3519
|
}
|
|
@@ -3529,40 +3651,15 @@ var Parser = class {
|
|
|
3529
3651
|
this.expectPunctuator("}");
|
|
3530
3652
|
return { type: "TableTypeNode", properties, ...spanFrom(start, this.previous()) };
|
|
3531
3653
|
}
|
|
3532
|
-
parseTypePack() {
|
|
3533
|
-
const start = this.current();
|
|
3534
|
-
if (this.matchOperator("...")) {
|
|
3535
|
-
const varargType2 = this.parseType();
|
|
3536
|
-
return { type: "TypePackNode", types: [], hasVarargs: true, varargType: varargType2, ...spanFrom(start, this.previous()) };
|
|
3537
|
-
}
|
|
3538
|
-
this.expectPunctuator("(");
|
|
3539
|
-
const types = [];
|
|
3540
|
-
let hasVarargs = false;
|
|
3541
|
-
let varargType;
|
|
3542
|
-
if (!(this.current().type === "Punctuator" && this.current().value === ")")) {
|
|
3543
|
-
while (true) {
|
|
3544
|
-
if (this.matchOperator("...")) {
|
|
3545
|
-
hasVarargs = true;
|
|
3546
|
-
varargType = this.parseType();
|
|
3547
|
-
break;
|
|
3548
|
-
}
|
|
3549
|
-
types.push(this.parseType());
|
|
3550
|
-
if (this.matchPunctuator(",")) continue;
|
|
3551
|
-
break;
|
|
3552
|
-
}
|
|
3553
|
-
}
|
|
3554
|
-
this.expectPunctuator(")");
|
|
3555
|
-
return { type: "TypePackNode", types, hasVarargs, varargType, ...spanFrom(start, this.previous()) };
|
|
3556
|
-
}
|
|
3557
3654
|
parseGenericTypeParameterList() {
|
|
3558
3655
|
const list = [];
|
|
3559
3656
|
this.expectOperator("<");
|
|
3560
3657
|
while (true) {
|
|
3561
3658
|
const isConst = this.matchKeyword("const");
|
|
3562
3659
|
const nameTok = this.expectIdentifier();
|
|
3563
|
-
|
|
3564
|
-
if (
|
|
3565
|
-
|
|
3660
|
+
const dots = this.checkOperator("...") ? this.advance() : void 0;
|
|
3661
|
+
if (dots) {
|
|
3662
|
+
this.problemAt(dots, "tilua has no pack parameters: write '<T extends unknown[]>', and take it as '...args: T'");
|
|
3566
3663
|
}
|
|
3567
3664
|
let constraint;
|
|
3568
3665
|
if (this.checkIdentifierValue("extends")) {
|
|
@@ -3571,17 +3668,13 @@ var Parser = class {
|
|
|
3571
3668
|
}
|
|
3572
3669
|
let def;
|
|
3573
3670
|
if (this.matchOperator("=")) {
|
|
3574
|
-
|
|
3575
|
-
|
|
3576
|
-
} else {
|
|
3577
|
-
def = this.parseType();
|
|
3578
|
-
}
|
|
3671
|
+
this.rejectPackReference();
|
|
3672
|
+
def = this.parseType();
|
|
3579
3673
|
}
|
|
3580
3674
|
list.push({
|
|
3581
3675
|
type: "GenericTypeParameter",
|
|
3582
3676
|
name: nameTok.value,
|
|
3583
3677
|
id: tokenIdentifier(nameTok),
|
|
3584
|
-
isPack,
|
|
3585
3678
|
isConst: isConst || void 0,
|
|
3586
3679
|
constraint,
|
|
3587
3680
|
default: def,
|
|
@@ -3672,7 +3765,7 @@ function childScope(parent) {
|
|
|
3672
3765
|
var Analyzer = class {
|
|
3673
3766
|
constructor(options) {
|
|
3674
3767
|
this.options = options;
|
|
3675
|
-
for (const name of options.builtinGlobals ?? []) {
|
|
3768
|
+
for (const name of [...LANGUAGE_GLOBALS, ...options.builtinGlobals ?? []]) {
|
|
3676
3769
|
const id = this.getOrCreateGlobalBinding(name);
|
|
3677
3770
|
this.bindings.get(id).isBuiltin = true;
|
|
3678
3771
|
}
|
|
@@ -3734,31 +3827,34 @@ var Analyzer = class {
|
|
|
3734
3827
|
* can call it, but a call straight in the block before the declaration
|
|
3735
3828
|
* finds nothing there yet. At a module's top level the whole function is
|
|
3736
3829
|
* hoisted, and this does not apply. */
|
|
3737
|
-
checkUseBeforeDefine(identifier, id) {
|
|
3830
|
+
checkUseBeforeDefine(identifier, id, at = this.functionDepth) {
|
|
3738
3831
|
const binding = this.bindings.get(id);
|
|
3739
|
-
if (
|
|
3832
|
+
if (this.typeQueryDepth > 0) return;
|
|
3740
3833
|
const declaration = binding.declarationNode;
|
|
3741
|
-
|
|
3742
|
-
|
|
3834
|
+
if (!declaration) return;
|
|
3835
|
+
const hoisted = binding.declaredBy === "function" || binding.declaredBy === "class";
|
|
3836
|
+
const depth = hoisted ? this.hoisted.get(declaration) : this.declaredDepth.get(id);
|
|
3837
|
+
const readable = hoisted || binding.kind === "local";
|
|
3838
|
+
if (!readable || depth === void 0 || depth !== at) return;
|
|
3743
3839
|
const before = identifier.line.start < declaration.line.start || identifier.line.start === declaration.line.start && identifier.column.start < declaration.column.start;
|
|
3744
3840
|
if (!before) return;
|
|
3745
3841
|
this.diagnostics.push({
|
|
3746
3842
|
node: identifier,
|
|
3747
|
-
message: `'${binding.name}' is used before its definition: inside a function, a function declared further down is only there once its declaration has run`,
|
|
3843
|
+
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`,
|
|
3748
3844
|
kind: "use-before-define"
|
|
3749
3845
|
});
|
|
3750
3846
|
}
|
|
3751
3847
|
noteDeferred(identifier, scope, id, assignment) {
|
|
3752
3848
|
if (this.functionDepth === 0 && this.typeQueryDepth === 0) return;
|
|
3753
3849
|
if (this.bindings.get(id).kind !== "global") return;
|
|
3754
|
-
this.deferredGlobals.push({ node: identifier, scope, assignment });
|
|
3850
|
+
this.deferredGlobals.push({ node: identifier, scope, assignment, depth: this.functionDepth });
|
|
3755
3851
|
}
|
|
3756
3852
|
/** Point each deferred read of a global at the declaration of that name
|
|
3757
3853
|
* that turned up later — in the module, or in any block around the code
|
|
3758
3854
|
* that reads it. Such code runs after the declaration has: a closure
|
|
3759
3855
|
* written inside a value reads the name the value is bound to. */
|
|
3760
3856
|
resolveForwardReferences() {
|
|
3761
|
-
for (const { node, scope, assignment } of this.deferredGlobals) {
|
|
3857
|
+
for (const { node, scope, assignment, depth } of this.deferredGlobals) {
|
|
3762
3858
|
const localId = this.lookup(scope, node.name);
|
|
3763
3859
|
const globalId = this.bindingOf.get(node);
|
|
3764
3860
|
if (localId === void 0 || globalId === void 0 || localId === globalId) continue;
|
|
@@ -3773,7 +3869,10 @@ var Analyzer = class {
|
|
|
3773
3869
|
this.bindingOf.set(node, localId);
|
|
3774
3870
|
this.bindings.get(localId).references.push(node);
|
|
3775
3871
|
if (assignment) this.checkConstAssign(localId, node);
|
|
3776
|
-
else if (this.typeQueryDepth === 0)
|
|
3872
|
+
else if (this.typeQueryDepth === 0) {
|
|
3873
|
+
this.checkTypeOnly(localId, node);
|
|
3874
|
+
this.checkUseBeforeDefine(node, localId, depth);
|
|
3875
|
+
}
|
|
3777
3876
|
}
|
|
3778
3877
|
}
|
|
3779
3878
|
/** Every read of a global nothing declares. A global assigned somewhere
|
|
@@ -3804,9 +3903,13 @@ var Analyzer = class {
|
|
|
3804
3903
|
}
|
|
3805
3904
|
const id = this.nextId++;
|
|
3806
3905
|
this.bindings.set(id, { id, name, kind, declarationNode: node, references: [], isConst, declaredBy });
|
|
3906
|
+
this.declaredDepth.set(id, this.functionDepth);
|
|
3807
3907
|
scope.declarations.set(name, id);
|
|
3808
3908
|
return id;
|
|
3809
3909
|
}
|
|
3910
|
+
/** Which function body each name was declared in. A read of a `const` from
|
|
3911
|
+
* the same body, but above it, is a read of nothing. */
|
|
3912
|
+
declaredDepth = /* @__PURE__ */ new Map();
|
|
3810
3913
|
resolve(scope, name) {
|
|
3811
3914
|
for (let s = scope; s; s = s.parent) {
|
|
3812
3915
|
const id = s.declarations.get(name);
|
|
@@ -4074,14 +4177,14 @@ var Analyzer = class {
|
|
|
4074
4177
|
return;
|
|
4075
4178
|
}
|
|
4076
4179
|
case "GenericForStatement": {
|
|
4077
|
-
|
|
4180
|
+
this.visitExpression(stmt.iterator, scope);
|
|
4078
4181
|
const bodyScope = childScope(scope);
|
|
4079
|
-
|
|
4182
|
+
this.declarePattern(bodyScope, stmt.variable, "for-generic", scope, stmt.kind === "const");
|
|
4080
4183
|
this.visitBlock(stmt.body, bodyScope);
|
|
4081
4184
|
return;
|
|
4082
4185
|
}
|
|
4083
4186
|
case "ReturnStatement":
|
|
4084
|
-
|
|
4187
|
+
if (stmt.argument) this.visitExpression(stmt.argument, scope);
|
|
4085
4188
|
return;
|
|
4086
4189
|
case "BreakStatement":
|
|
4087
4190
|
case "ContinueStatement":
|
|
@@ -4149,7 +4252,6 @@ var Analyzer = class {
|
|
|
4149
4252
|
this.declare(fnScope, param.name, kind, param);
|
|
4150
4253
|
}
|
|
4151
4254
|
});
|
|
4152
|
-
this.visitType(func.varargTypeAnnotation, fnScope);
|
|
4153
4255
|
this.visitType(func.returnType, fnScope);
|
|
4154
4256
|
this.functionDepth++;
|
|
4155
4257
|
try {
|
|
@@ -4177,6 +4279,7 @@ var Analyzer = class {
|
|
|
4177
4279
|
this.visitGenerics(node.typeParams, scope);
|
|
4178
4280
|
if (node.superclass) this.reference(outer, node.superclass);
|
|
4179
4281
|
for (const argument of node.superArguments ?? []) this.visitType(argument, scope);
|
|
4282
|
+
for (const shape of node.implements ?? []) this.visitType(shape, scope);
|
|
4180
4283
|
for (const member of node.members) {
|
|
4181
4284
|
switch (member.type) {
|
|
4182
4285
|
case "ClassField":
|
|
@@ -4236,7 +4339,6 @@ var Analyzer = class {
|
|
|
4236
4339
|
case "BooleanLiteral":
|
|
4237
4340
|
case "NumberLiteral":
|
|
4238
4341
|
case "StringLiteral":
|
|
4239
|
-
case "VarargExpression":
|
|
4240
4342
|
case "ErrorExpression":
|
|
4241
4343
|
return;
|
|
4242
4344
|
case "InterpolatedStringExpression":
|
|
@@ -4276,11 +4378,6 @@ var Analyzer = class {
|
|
|
4276
4378
|
this.visitExpression(expr.callee, scope);
|
|
4277
4379
|
for (const arg of expr.arguments) this.visitExpression(arg, scope);
|
|
4278
4380
|
return;
|
|
4279
|
-
case "NewExpression":
|
|
4280
|
-
this.visitExpression(expr.callee, scope);
|
|
4281
|
-
for (const argument of expr.arguments) this.visitExpression(argument, scope);
|
|
4282
|
-
for (const argument of expr.typeArguments ?? []) this.visitType(argument, scope);
|
|
4283
|
-
return;
|
|
4284
4381
|
case "SuperExpression":
|
|
4285
4382
|
return;
|
|
4286
4383
|
case "SpreadElement":
|
|
@@ -4334,6 +4431,7 @@ var Analyzer = class {
|
|
|
4334
4431
|
}
|
|
4335
4432
|
}
|
|
4336
4433
|
};
|
|
4434
|
+
var LANGUAGE_GLOBALS = ["scriptArgs"];
|
|
4337
4435
|
function analyzeScopes(program, options = {}) {
|
|
4338
4436
|
return new Analyzer(options).run(program);
|
|
4339
4437
|
}
|
|
@@ -4352,8 +4450,8 @@ type Exclude<T, U> = T - U
|
|
|
4352
4450
|
type Extract<T, U> = T extends U ? T : never
|
|
4353
4451
|
type NonNullable<T> = T - nil
|
|
4354
4452
|
|
|
4355
|
-
type ReturnType<T> = T extends (...unknown) => infer R ? R : never
|
|
4356
|
-
type Parameters<T> = T extends (...infer P) => unknown ? P : never
|
|
4453
|
+
type ReturnType<T> = T extends (...args: unknown[]) => infer R ? R : never
|
|
4454
|
+
type Parameters<T> = T extends (...args: infer P) => unknown ? P : never
|
|
4357
4455
|
|
|
4358
4456
|
type Partial<T> = { [K in keyof T]?: T[K] }
|
|
4359
4457
|
type Required<T> = { [K in keyof T]-?: T[K] }
|
|
@@ -4438,8 +4536,11 @@ function literal(value) {
|
|
|
4438
4536
|
function arrayOf(element) {
|
|
4439
4537
|
return { kind: "array", element };
|
|
4440
4538
|
}
|
|
4441
|
-
function tuple(elements,
|
|
4442
|
-
return { kind: "tuple", elements,
|
|
4539
|
+
function tuple(elements, rest) {
|
|
4540
|
+
return rest ? { kind: "tuple", elements, rest } : { kind: "tuple", elements };
|
|
4541
|
+
}
|
|
4542
|
+
function tupleMembers(t) {
|
|
4543
|
+
return t.rest ? [...t.elements, t.rest] : t.elements;
|
|
4443
4544
|
}
|
|
4444
4545
|
function objectType(entries, indexer, frozen) {
|
|
4445
4546
|
return { kind: "object", properties: new Map(entries), indexer, frozen };
|
|
@@ -4461,9 +4562,9 @@ function substitute(t, subst) {
|
|
|
4461
4562
|
case "typeParam":
|
|
4462
4563
|
return subst.get(t.name) ?? t;
|
|
4463
4564
|
case "array":
|
|
4464
|
-
return arrayOf(substitute(t.element, subst));
|
|
4565
|
+
return { ...arrayOf(substitute(t.element, subst)), readonly: t.readonly };
|
|
4465
4566
|
case "tuple":
|
|
4466
|
-
return tuple(t.elements.map((e) => substitute(e, subst)), t.
|
|
4567
|
+
return { ...tuple(t.elements.map((e) => substitute(e, subst)), t.rest && substitute(t.rest, subst)), readonly: t.readonly };
|
|
4467
4568
|
case "object": {
|
|
4468
4569
|
const entries = [];
|
|
4469
4570
|
for (const [k, v] of t.properties) entries.push([k, { ...v, type: substitute(v.type, subst) }]);
|
|
@@ -4487,14 +4588,20 @@ function substitute(t, subst) {
|
|
|
4487
4588
|
const inner = t.typeParams ? new Map([...subst].filter(([k]) => !t.typeParams.includes(k))) : subst;
|
|
4488
4589
|
let params = t.params.map((p) => ({ ...p, type: substitute(p.type, inner) }));
|
|
4489
4590
|
let varargs = t.varargs && substitute(t.varargs, inner);
|
|
4490
|
-
|
|
4591
|
+
let restIsWhole = t.restIsWhole;
|
|
4592
|
+
if (restIsWhole && varargs?.kind === "tuple") {
|
|
4491
4593
|
params = [...params, ...varargs.elements.map((type) => ({ type }))];
|
|
4492
|
-
varargs =
|
|
4594
|
+
varargs = varargs.rest;
|
|
4595
|
+
restIsWhole = void 0;
|
|
4596
|
+
} else if (restIsWhole && varargs?.kind === "array") {
|
|
4597
|
+
varargs = varargs.element;
|
|
4598
|
+
restIsWhole = void 0;
|
|
4493
4599
|
}
|
|
4494
4600
|
return {
|
|
4495
4601
|
kind: "function",
|
|
4496
4602
|
params,
|
|
4497
4603
|
varargs,
|
|
4604
|
+
...restIsWhole ? { restIsWhole } : {},
|
|
4498
4605
|
returns: substitute(t.returns, inner),
|
|
4499
4606
|
typeParams: t.typeParams,
|
|
4500
4607
|
typeParamDefaults: t.typeParamDefaults,
|
|
@@ -4577,11 +4684,19 @@ function unify(param, arg, vars, out) {
|
|
|
4577
4684
|
switch (param.kind) {
|
|
4578
4685
|
case "array":
|
|
4579
4686
|
if (arg.kind === "array") unify(param.element, arg.element, vars, out);
|
|
4580
|
-
else if (arg.kind === "tuple") for (const e of arg
|
|
4687
|
+
else if (arg.kind === "tuple") for (const e of tupleMembers(arg)) unify(param.element, e, vars, out);
|
|
4581
4688
|
return;
|
|
4582
4689
|
case "tuple":
|
|
4583
|
-
if (arg.kind === "tuple")
|
|
4584
|
-
|
|
4690
|
+
if (arg.kind === "tuple") {
|
|
4691
|
+
param.elements.forEach((p, i) => {
|
|
4692
|
+
const a = arg.elements[i] ?? arg.rest;
|
|
4693
|
+
if (a) unify(p, a, vars, out);
|
|
4694
|
+
});
|
|
4695
|
+
if (param.rest) for (const a of arg.elements.slice(param.elements.length)) unify(param.rest, a, vars, out);
|
|
4696
|
+
if (param.rest && arg.rest) unify(param.rest, arg.rest, vars, out);
|
|
4697
|
+
} else if (arg.kind === "array") {
|
|
4698
|
+
for (const p of tupleMembers(param)) unify(p, arg.element, vars, out);
|
|
4699
|
+
}
|
|
4585
4700
|
return;
|
|
4586
4701
|
case "function":
|
|
4587
4702
|
if (arg.kind === "function") {
|
|
@@ -4614,6 +4729,20 @@ function unify(param, arg, vars, out) {
|
|
|
4614
4729
|
return;
|
|
4615
4730
|
}
|
|
4616
4731
|
}
|
|
4732
|
+
function memberKey(t) {
|
|
4733
|
+
switch (t.kind) {
|
|
4734
|
+
case "literal":
|
|
4735
|
+
return `l${t.base}:${String(t.value)}`;
|
|
4736
|
+
case "primitive":
|
|
4737
|
+
return `p${t.name}`;
|
|
4738
|
+
case "any":
|
|
4739
|
+
case "unknown":
|
|
4740
|
+
case "never":
|
|
4741
|
+
return t.kind;
|
|
4742
|
+
default:
|
|
4743
|
+
return formatType(t);
|
|
4744
|
+
}
|
|
4745
|
+
}
|
|
4617
4746
|
function union(types) {
|
|
4618
4747
|
const flat = [];
|
|
4619
4748
|
for (const t of types) {
|
|
@@ -4625,7 +4754,7 @@ function union(types) {
|
|
|
4625
4754
|
const seen = /* @__PURE__ */ new Map();
|
|
4626
4755
|
for (const t of flat) {
|
|
4627
4756
|
if (t.kind === "never") continue;
|
|
4628
|
-
const key =
|
|
4757
|
+
const key = memberKey(t);
|
|
4629
4758
|
if (!seen.has(key)) seen.set(key, t);
|
|
4630
4759
|
}
|
|
4631
4760
|
let members = reduceSubtypes([...seen.values()]);
|
|
@@ -4654,7 +4783,7 @@ function intersection(types) {
|
|
|
4654
4783
|
const seen = /* @__PURE__ */ new Map();
|
|
4655
4784
|
for (const t of flat) {
|
|
4656
4785
|
if (t.kind === "unknown") continue;
|
|
4657
|
-
seen.set(
|
|
4786
|
+
seen.set(memberKey(t), t);
|
|
4658
4787
|
}
|
|
4659
4788
|
const members = [...seen.values()];
|
|
4660
4789
|
if (members.length === 0) return unknownType;
|
|
@@ -4681,9 +4810,9 @@ function widen(t) {
|
|
|
4681
4810
|
case "literal":
|
|
4682
4811
|
return primitive(t.base);
|
|
4683
4812
|
case "array":
|
|
4684
|
-
return arrayOf(widen(t.element));
|
|
4813
|
+
return { ...arrayOf(widen(t.element)), readonly: t.readonly };
|
|
4685
4814
|
case "tuple":
|
|
4686
|
-
return tuple(t.elements.map(widen), t.
|
|
4815
|
+
return { ...tuple(t.elements.map(widen), t.rest && widen(t.rest)), readonly: t.readonly };
|
|
4687
4816
|
case "object": {
|
|
4688
4817
|
if (t.frozen || t.class) return t;
|
|
4689
4818
|
const entries = [];
|
|
@@ -4781,15 +4910,21 @@ function isAssignableInner(a, b) {
|
|
|
4781
4910
|
return b.kind === "primitive" && b.name === "string";
|
|
4782
4911
|
}
|
|
4783
4912
|
if (a.kind === "primitive") return b.kind === "primitive" && b.name === a.name;
|
|
4913
|
+
if ((a.kind === "array" || a.kind === "tuple") && a.readonly && (b.kind === "array" || b.kind === "tuple") && !b.readonly) return false;
|
|
4784
4914
|
if (a.kind === "array") {
|
|
4785
4915
|
if (b.kind === "array") return isAssignable(a.element, b.element);
|
|
4916
|
+
if (b.kind === "tuple") return b.elements.length === 0 && !!b.rest && isAssignable(a.element, b.rest);
|
|
4786
4917
|
return false;
|
|
4787
4918
|
}
|
|
4788
4919
|
if (a.kind === "tuple") {
|
|
4789
4920
|
if (b.kind === "tuple") {
|
|
4790
|
-
|
|
4921
|
+
if (a.elements.length < b.elements.length) return false;
|
|
4922
|
+
if (!b.elements.every((t, i) => isAssignable(a.elements[i], t))) return false;
|
|
4923
|
+
const extra = [...a.elements.slice(b.elements.length), ...a.rest ? [a.rest] : []];
|
|
4924
|
+
if (!b.rest) return extra.length === 0;
|
|
4925
|
+
return extra.every((t) => isAssignable(t, b.rest));
|
|
4791
4926
|
}
|
|
4792
|
-
if (b.kind === "array") return a.
|
|
4927
|
+
if (b.kind === "array") return tupleMembers(a).every((t) => isAssignable(t, b.element));
|
|
4793
4928
|
return false;
|
|
4794
4929
|
}
|
|
4795
4930
|
if (a.kind === "object") {
|
|
@@ -4838,7 +4973,7 @@ function sameRefTarget(a, b) {
|
|
|
4838
4973
|
return a.name === b.name && (!a.origin || !b.origin || a.origin === b.origin);
|
|
4839
4974
|
}
|
|
4840
4975
|
function isNoValue(t) {
|
|
4841
|
-
return t.kind === "tuple" && t.elements.length === 0;
|
|
4976
|
+
return t.kind === "tuple" && t.elements.length === 0 && !t.rest;
|
|
4842
4977
|
}
|
|
4843
4978
|
function equalTypes(a, b) {
|
|
4844
4979
|
return formatType(a) === formatType(b);
|
|
@@ -4945,7 +5080,7 @@ function containsFreeTypeParam(t, seen, bound) {
|
|
|
4945
5080
|
case "array":
|
|
4946
5081
|
return containsTypeParam(t.element, seen, bound);
|
|
4947
5082
|
case "tuple":
|
|
4948
|
-
return t.
|
|
5083
|
+
return tupleMembers(t).some((e) => containsTypeParam(e, seen, bound));
|
|
4949
5084
|
case "union":
|
|
4950
5085
|
case "intersection":
|
|
4951
5086
|
return t.types.some((m) => containsTypeParam(m, seen, bound));
|
|
@@ -4990,15 +5125,19 @@ function matchInfer(arg, pattern, out) {
|
|
|
4990
5125
|
switch (pattern.kind) {
|
|
4991
5126
|
case "array":
|
|
4992
5127
|
if (arg.kind === "array") return matchInfer(arg.element, pattern.element, out);
|
|
4993
|
-
if (arg.kind === "tuple") return matchInfer(union(arg
|
|
5128
|
+
if (arg.kind === "tuple") return matchInfer(union(tupleMembers(arg)), pattern.element, out);
|
|
4994
5129
|
return false;
|
|
4995
5130
|
case "tuple":
|
|
4996
5131
|
if (arg.kind !== "tuple" || arg.elements.length !== pattern.elements.length) return false;
|
|
5132
|
+
if (pattern.rest && !matchInfer(arg.rest ?? neverType, pattern.rest, out)) return false;
|
|
4997
5133
|
return pattern.elements.every((pt, i) => matchInfer(arg.elements[i], pt, out));
|
|
4998
5134
|
case "function": {
|
|
4999
5135
|
if (arg.kind !== "function") return false;
|
|
5000
5136
|
if (pattern.varargs?.kind === "infer" && !pattern.params.length) {
|
|
5001
|
-
out.set(pattern.varargs.name, tuple(
|
|
5137
|
+
out.set(pattern.varargs.name, tuple(
|
|
5138
|
+
arg.params.map((p) => p.type),
|
|
5139
|
+
arg.varargs && !arg.restIsWhole ? arg.varargs : void 0
|
|
5140
|
+
));
|
|
5002
5141
|
} else {
|
|
5003
5142
|
for (let i = 0; i < pattern.params.length; i++) {
|
|
5004
5143
|
if (!arg.params[i]) return false;
|
|
@@ -5072,7 +5211,7 @@ function collectTypeParams(t, out, seen = /* @__PURE__ */ new Set()) {
|
|
|
5072
5211
|
collectTypeParams(t.element, out, seen);
|
|
5073
5212
|
return;
|
|
5074
5213
|
case "tuple":
|
|
5075
|
-
for (const e of t
|
|
5214
|
+
for (const e of tupleMembers(t)) collectTypeParams(e, out, seen);
|
|
5076
5215
|
return;
|
|
5077
5216
|
case "union":
|
|
5078
5217
|
case "intersection":
|
|
@@ -5117,11 +5256,10 @@ function formatTypeUncached(t) {
|
|
|
5117
5256
|
case "literal":
|
|
5118
5257
|
return t.base === "string" ? JSON.stringify(t.value) : String(t.value);
|
|
5119
5258
|
case "array":
|
|
5120
|
-
return `${formatAtom(t.element)}[]`;
|
|
5259
|
+
return `${t.readonly ? "readonly " : ""}${formatAtom(t.element)}[]`;
|
|
5121
5260
|
case "tuple": {
|
|
5122
|
-
|
|
5123
|
-
|
|
5124
|
-
return t.isPack ? `(${inner})` : `[${inner}]`;
|
|
5261
|
+
const inner = [...t.elements.map(formatType), ...t.rest ? [`...${formatAtom(t.rest)}[]`] : []];
|
|
5262
|
+
return `${t.readonly ? "readonly " : ""}[${inner.join(", ")}]`;
|
|
5125
5263
|
}
|
|
5126
5264
|
case "object": {
|
|
5127
5265
|
if (t.name) {
|
|
@@ -5145,7 +5283,7 @@ function formatTypeUncached(t) {
|
|
|
5145
5283
|
return `${consts.has(n) ? "const " : ""}${n}${constraint ? ` extends ${briefConstraint(constraint)}` : ""}`;
|
|
5146
5284
|
}).join(", ")}>` : "";
|
|
5147
5285
|
const ps = t.params.map((p) => `${p.name ? p.name + ": " : ""}${formatType(p.type)}`);
|
|
5148
|
-
if (t.varargs) ps.push(
|
|
5286
|
+
if (t.varargs) ps.push(t.restIsWhole ? `...args: ${formatType(t.varargs)}` : `...args: ${formatAtom(t.varargs)}[]`);
|
|
5149
5287
|
return `${gen}(${ps.join(", ")}) => ${formatPredicate(t) ?? formatType(t.returns)}`;
|
|
5150
5288
|
}
|
|
5151
5289
|
case "typeParam":
|
|
@@ -5349,6 +5487,11 @@ function moduleExports(program, scopes, types, resolveModule) {
|
|
|
5349
5487
|
}
|
|
5350
5488
|
return { values, types: exportedTypes, default: defaultType };
|
|
5351
5489
|
}
|
|
5490
|
+
function isBelow(k, key) {
|
|
5491
|
+
if (!k.startsWith(key) || k.length === key.length) return false;
|
|
5492
|
+
const next = k[key.length];
|
|
5493
|
+
return next === "." || next === "#" || next === "[";
|
|
5494
|
+
}
|
|
5352
5495
|
function bindKey(id) {
|
|
5353
5496
|
return `$${id}`;
|
|
5354
5497
|
}
|
|
@@ -5366,9 +5509,6 @@ function mergeEnv(a, b, base) {
|
|
|
5366
5509
|
function mergeIndexer(a, b) {
|
|
5367
5510
|
return a ? { key: union([a.key, b.key]), value: union([a.value, b.value]) } : b;
|
|
5368
5511
|
}
|
|
5369
|
-
function producesMultipleValues(e) {
|
|
5370
|
-
return e.type === "CallExpression" || e.type === "MethodCallExpression" || e.type === "VarargExpression";
|
|
5371
|
-
}
|
|
5372
5512
|
function isFreshLiteralExpr(e) {
|
|
5373
5513
|
if (!e) return false;
|
|
5374
5514
|
switch (e.type) {
|
|
@@ -5405,11 +5545,11 @@ function collectInferNames(node) {
|
|
|
5405
5545
|
walk(n.element);
|
|
5406
5546
|
return;
|
|
5407
5547
|
case "ParenthesizedTypeNode":
|
|
5408
|
-
case "VariadicTypeNode":
|
|
5409
5548
|
walk(n.typeAnnotation);
|
|
5410
5549
|
return;
|
|
5411
5550
|
case "TupleTypeNode":
|
|
5412
5551
|
n.elements.forEach(walk);
|
|
5552
|
+
walk(n.rest);
|
|
5413
5553
|
return;
|
|
5414
5554
|
case "UnionTypeNode":
|
|
5415
5555
|
case "IntersectionTypeNode":
|
|
@@ -5427,7 +5567,6 @@ function collectInferNames(node) {
|
|
|
5427
5567
|
return;
|
|
5428
5568
|
case "FunctionTypeNode":
|
|
5429
5569
|
n.params.forEach((pp) => walk(pp.typeAnnotation));
|
|
5430
|
-
walk(n.varargType);
|
|
5431
5570
|
walk(n.returnType);
|
|
5432
5571
|
return;
|
|
5433
5572
|
case "TableTypeNode":
|
|
@@ -5472,6 +5611,75 @@ function callsSuper(block) {
|
|
|
5472
5611
|
});
|
|
5473
5612
|
return found;
|
|
5474
5613
|
}
|
|
5614
|
+
var ALL_FIELDS = /* @__PURE__ */ new Set();
|
|
5615
|
+
function definitelyAssigned(block) {
|
|
5616
|
+
const names = /* @__PURE__ */ new Set();
|
|
5617
|
+
for (const statement of block.statements) {
|
|
5618
|
+
switch (statement.type) {
|
|
5619
|
+
case "AssignmentStatement":
|
|
5620
|
+
case "CompoundAssignmentStatement":
|
|
5621
|
+
for (const name of assignedFields({ ...block, statements: [statement] })) names.add(name);
|
|
5622
|
+
break;
|
|
5623
|
+
case "DoStatement": {
|
|
5624
|
+
const inner = definitelyAssigned(statement.body);
|
|
5625
|
+
if (!inner) return void 0;
|
|
5626
|
+
for (const name of inner) names.add(name);
|
|
5627
|
+
break;
|
|
5628
|
+
}
|
|
5629
|
+
case "IfStatement": {
|
|
5630
|
+
if (!statement.alternate) break;
|
|
5631
|
+
const arms = [...statement.clauses.map((c) => c.body), statement.alternate].map(definitelyAssigned);
|
|
5632
|
+
const finishing = arms.filter((arm) => arm !== void 0);
|
|
5633
|
+
if (!finishing.length) return void 0;
|
|
5634
|
+
for (const name of finishing[0]) {
|
|
5635
|
+
if (finishing.every((arm) => arm.has(name))) names.add(name);
|
|
5636
|
+
}
|
|
5637
|
+
break;
|
|
5638
|
+
}
|
|
5639
|
+
case "ReturnStatement":
|
|
5640
|
+
return void 0;
|
|
5641
|
+
case "CallStatement": {
|
|
5642
|
+
const call = statement.expression;
|
|
5643
|
+
if (call.type === "CallExpression" && call.callee.type === "Identifier" && call.callee.name === "error") {
|
|
5644
|
+
return void 0;
|
|
5645
|
+
}
|
|
5646
|
+
break;
|
|
5647
|
+
}
|
|
5648
|
+
}
|
|
5649
|
+
}
|
|
5650
|
+
return names;
|
|
5651
|
+
}
|
|
5652
|
+
function withoutReceiver(t) {
|
|
5653
|
+
if (t.kind === "intersection") return intersection(t.types.map(withoutReceiver));
|
|
5654
|
+
if (t.kind !== "function") return t;
|
|
5655
|
+
const first = t.params[0]?.name;
|
|
5656
|
+
if (first !== "this" && first !== "self") return t;
|
|
5657
|
+
return { ...t, params: t.params.slice(1) };
|
|
5658
|
+
}
|
|
5659
|
+
var METAMETHOD_OPERANDS = {
|
|
5660
|
+
__add: 1,
|
|
5661
|
+
__sub: 1,
|
|
5662
|
+
__mul: 1,
|
|
5663
|
+
__div: 1,
|
|
5664
|
+
__idiv: 1,
|
|
5665
|
+
__mod: 1,
|
|
5666
|
+
__pow: 1,
|
|
5667
|
+
__concat: 1,
|
|
5668
|
+
__eq: 1,
|
|
5669
|
+
__lt: 1,
|
|
5670
|
+
__le: 1,
|
|
5671
|
+
__unm: 0,
|
|
5672
|
+
__len: 0,
|
|
5673
|
+
__tostring: 0,
|
|
5674
|
+
__iter: 0,
|
|
5675
|
+
__call: void 0
|
|
5676
|
+
};
|
|
5677
|
+
var METAMETHOD_RETURNS = {
|
|
5678
|
+
__tostring: stringType,
|
|
5679
|
+
__eq: booleanType,
|
|
5680
|
+
__lt: booleanType,
|
|
5681
|
+
__le: booleanType
|
|
5682
|
+
};
|
|
5475
5683
|
function assignedFields(block) {
|
|
5476
5684
|
const names = /* @__PURE__ */ new Set();
|
|
5477
5685
|
walkNodes(block, (node) => {
|
|
@@ -5497,7 +5705,7 @@ function walkNodes(root, visit) {
|
|
|
5497
5705
|
if (key !== "line" && key !== "column" && value && typeof value === "object") walkNodes(value, visit);
|
|
5498
5706
|
}
|
|
5499
5707
|
}
|
|
5500
|
-
var
|
|
5708
|
+
var LazyMap = class extends Map {
|
|
5501
5709
|
pending = /* @__PURE__ */ new Map();
|
|
5502
5710
|
defer(name, resolve5) {
|
|
5503
5711
|
super.delete(name);
|
|
@@ -5574,7 +5782,7 @@ function expressionLabel(e, depth = 0) {
|
|
|
5574
5782
|
case "IndexExpression": {
|
|
5575
5783
|
const o = expressionLabel(e.object, depth + 1);
|
|
5576
5784
|
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 : "...";
|
|
5577
|
-
return o === void 0 ? void 0 : `${o}[${i}]`;
|
|
5785
|
+
return o === void 0 ? void 0 : `${o}${e.optional ? "?." : ""}[${i}]`;
|
|
5578
5786
|
}
|
|
5579
5787
|
case "ParenthesizedExpression": {
|
|
5580
5788
|
const inner = expressionLabel(e.expression, depth + 1);
|
|
@@ -5612,20 +5820,21 @@ var TypeAnalyzer = class {
|
|
|
5612
5820
|
scopes;
|
|
5613
5821
|
options;
|
|
5614
5822
|
typeOf = /* @__PURE__ */ new Map();
|
|
5615
|
-
bindingType =
|
|
5823
|
+
bindingType = new LazyMap();
|
|
5616
5824
|
narrowedTypeOf = /* @__PURE__ */ new Map();
|
|
5617
5825
|
typeOfTypeNode = /* @__PURE__ */ new Map();
|
|
5618
5826
|
expectedTypeOf = /* @__PURE__ */ new Map();
|
|
5619
|
-
|
|
5827
|
+
loops = /* @__PURE__ */ new Map();
|
|
5620
5828
|
/** Public: each alias resolved once (generic aliases keep their params as
|
|
5621
5829
|
* `typeParam` nodes in the body). */
|
|
5622
|
-
aliases = new
|
|
5830
|
+
aliases = new LazyMap();
|
|
5623
5831
|
/** Uninstantiated alias definitions, for `Name<Args>` instantiation. */
|
|
5624
5832
|
aliasDefs = /* @__PURE__ */ new Map();
|
|
5625
5833
|
/** Put on every ref to one of `aliasDefs`, so a module that imports the
|
|
5626
5834
|
* type expands the ref here rather than by its name there. */
|
|
5627
5835
|
origin = { expand: (t) => this.expand(t) };
|
|
5628
|
-
/** See `resolveClass`.
|
|
5836
|
+
/** See `resolveClass`. Shared with other analyses over the same libraries
|
|
5837
|
+
* when none of their names is redefined here; see `shareableClasses`. */
|
|
5629
5838
|
classTypes = /* @__PURE__ */ new WeakMap();
|
|
5630
5839
|
/** See `instanceType` — one instance type per `class ... end`. */
|
|
5631
5840
|
instanceTypes = /* @__PURE__ */ new WeakMap();
|
|
@@ -5633,8 +5842,12 @@ var TypeAnalyzer = class {
|
|
|
5633
5842
|
/** Generic parameters currently in lexical scope (alias body / generic fn),
|
|
5634
5843
|
* with their `extends` constraints resolved. */
|
|
5635
5844
|
typeParamScope = [];
|
|
5636
|
-
/** Global types contributed by `declare` statements (libs, then this program).
|
|
5637
|
-
|
|
5845
|
+
/** Global types contributed by `declare` statements (libs, then this program).
|
|
5846
|
+
* A library's are resolved when the name is first used: an engine's
|
|
5847
|
+
* definitions declare hundreds of globals, and a script uses a few. */
|
|
5848
|
+
libGlobalTypes = new LazyMap();
|
|
5849
|
+
/** Each library `declare` of a name, in order, until it is resolved. */
|
|
5850
|
+
libDeclares = /* @__PURE__ */ new Map();
|
|
5638
5851
|
/** Declaration node -> binding, built once so `bindingIdByName` is O(1)
|
|
5639
5852
|
* instead of a scan of every binding per declaration site. */
|
|
5640
5853
|
bindingByDecl = /* @__PURE__ */ new Map();
|
|
@@ -5667,6 +5880,11 @@ var TypeAnalyzer = class {
|
|
|
5667
5880
|
/** Recursion guard for `preVisitBody`. */
|
|
5668
5881
|
preVisitDepth = 0;
|
|
5669
5882
|
run() {
|
|
5883
|
+
const shared = shareableClasses(this.options.libs ?? [], this.program);
|
|
5884
|
+
if (shared) {
|
|
5885
|
+
this.classTypes = shared.classTypes;
|
|
5886
|
+
this.classMembers = shared.classMembers;
|
|
5887
|
+
}
|
|
5670
5888
|
this.registerAliasDefs(preludeProgram().body, true);
|
|
5671
5889
|
for (const lib of this.options.libs ?? []) this.registerAliasDefs(lib.body, true);
|
|
5672
5890
|
this.registerAliasDefs(this.program.body);
|
|
@@ -5679,8 +5897,11 @@ var TypeAnalyzer = class {
|
|
|
5679
5897
|
this.indexDeclarations();
|
|
5680
5898
|
for (const [name, id] of this.scopes.globalsByName) {
|
|
5681
5899
|
if (this.deferredDeclares.has(name) && !this.options.globalTypes?.[name]) continue;
|
|
5682
|
-
const
|
|
5683
|
-
this.bindingType.set(id,
|
|
5900
|
+
const given = this.options.globalTypes?.[name];
|
|
5901
|
+
if (given) this.bindingType.set(id, given);
|
|
5902
|
+
else if (this.libGlobalTypes.has(name)) this.bindingType.defer(id, () => this.libGlobalTypes.get(name) ?? anyType);
|
|
5903
|
+
else if (LANGUAGE_GLOBALS.includes(name)) this.bindingType.set(id, arrayOf(unknownType));
|
|
5904
|
+
else this.bindingType.set(id, anyType);
|
|
5684
5905
|
}
|
|
5685
5906
|
setAliasExpander((t) => this.expand(t));
|
|
5686
5907
|
setDeferredBound((t) => this.deferredBound(t));
|
|
@@ -5700,7 +5921,7 @@ var TypeAnalyzer = class {
|
|
|
5700
5921
|
typeOfTypeNode: this.typeOfTypeNode,
|
|
5701
5922
|
expectedTypeOf: this.expectedTypeOf,
|
|
5702
5923
|
aliases: this.resolveDeferredAliases(),
|
|
5703
|
-
|
|
5924
|
+
loops: this.loops,
|
|
5704
5925
|
diagnostics: this.diagnostics
|
|
5705
5926
|
};
|
|
5706
5927
|
}
|
|
@@ -6052,28 +6273,55 @@ var TypeAnalyzer = class {
|
|
|
6052
6273
|
type.name = `typeof ${this.className(stmt)}`;
|
|
6053
6274
|
const shape = this.shapeOf(stmt);
|
|
6054
6275
|
const local = this.superDecl(stmt);
|
|
6055
|
-
const parent = local ? this.classValueType(local) : stmt.superclass ? this.
|
|
6276
|
+
const parent = local ? this.classValueType(local) : stmt.superclass ? this.classStaticsOf(stmt.superclass) : void 0;
|
|
6056
6277
|
if (parent?.kind === "object") {
|
|
6057
6278
|
for (const [key, property] of parent.properties) {
|
|
6058
6279
|
if (!CLASS_LINKS.has(key)) type.properties.set(key, property);
|
|
6059
6280
|
}
|
|
6060
6281
|
}
|
|
6061
6282
|
for (const [key, property] of shape.statics) type.properties.set(key, property);
|
|
6062
|
-
|
|
6283
|
+
if (shape.filling) {
|
|
6284
|
+
(shape.filled ??= []).push(() => {
|
|
6285
|
+
for (const [key, property] of shape.statics) type.properties.set(key, property);
|
|
6286
|
+
});
|
|
6287
|
+
}
|
|
6063
6288
|
const params = this.classTypeParams(stmt);
|
|
6064
|
-
|
|
6065
|
-
|
|
6066
|
-
constructor?.params.filter((p) => p.name !== "this") ?? [],
|
|
6067
|
-
this.selfTypeOf(stmt),
|
|
6068
|
-
constructor?.varargs,
|
|
6069
|
-
params.map((p) => p.name)
|
|
6070
|
-
),
|
|
6289
|
+
let settled;
|
|
6290
|
+
const construct = {
|
|
6071
6291
|
optional: false,
|
|
6072
|
-
readonly: true
|
|
6292
|
+
readonly: true,
|
|
6293
|
+
// Kept, so a class extending it can read what it takes; reaching
|
|
6294
|
+
// it from outside is the error.
|
|
6295
|
+
...stmt.isAbstract ? { abstract: true } : {}
|
|
6296
|
+
};
|
|
6297
|
+
Object.defineProperty(construct, "type", {
|
|
6298
|
+
enumerable: true,
|
|
6299
|
+
get: () => {
|
|
6300
|
+
if (settled) return settled;
|
|
6301
|
+
const constructor = this.constructorType(stmt);
|
|
6302
|
+
const made = fn(
|
|
6303
|
+
constructor?.params.filter((p) => p.name !== "this") ?? [],
|
|
6304
|
+
this.selfTypeOf(stmt),
|
|
6305
|
+
constructor?.varargs,
|
|
6306
|
+
params.map((p) => p.name)
|
|
6307
|
+
);
|
|
6308
|
+
if (!this.shapeOf(stmt).filling) settled = made;
|
|
6309
|
+
return made;
|
|
6310
|
+
}
|
|
6073
6311
|
});
|
|
6312
|
+
type.properties.set("new", construct);
|
|
6074
6313
|
type.properties.set("ParentClass", { type: parent ?? nilType, optional: false, readonly: true });
|
|
6075
6314
|
return type;
|
|
6076
6315
|
}
|
|
6316
|
+
/** The value side of the class `superclass` names when it is not
|
|
6317
|
+
* declared in this file: what the name it refers to holds — an import
|
|
6318
|
+
* brings the class table with it — or else a class of that name. */
|
|
6319
|
+
classStaticsOf(superclass) {
|
|
6320
|
+
const id = this.bindingIdOf(superclass);
|
|
6321
|
+
const bound = id !== void 0 ? this.expand(this.bindingType.get(id) ?? unknownType) : void 0;
|
|
6322
|
+
if (bound?.kind === "object") return bound;
|
|
6323
|
+
return this.classStaticsByNameType(superclass.name);
|
|
6324
|
+
}
|
|
6077
6325
|
/** The value side of a class named by a binding rather than by a
|
|
6078
6326
|
* declaration in this file — an imported one. */
|
|
6079
6327
|
classStaticsByNameType(name) {
|
|
@@ -6134,20 +6382,26 @@ var TypeAnalyzer = class {
|
|
|
6134
6382
|
} finally {
|
|
6135
6383
|
this.emitDiagnostics = wasEmitting;
|
|
6136
6384
|
shape.filling = false;
|
|
6385
|
+
for (const then of shape.filled ?? []) then();
|
|
6386
|
+
shape.filled = void 0;
|
|
6137
6387
|
}
|
|
6138
6388
|
return shape;
|
|
6139
6389
|
}
|
|
6140
6390
|
fillShape(stmt, shape) {
|
|
6141
6391
|
const className = this.className(stmt);
|
|
6142
6392
|
const put = (isStatic, name, property) => {
|
|
6143
|
-
const member = stmt.members.find((m) => m.type !== "ClassConstructor" && m.isStatic === isStatic && m.name.name === name && m.accessibility === "private");
|
|
6144
|
-
const marked = member ? { ...property, private: { owner: stmt, className } } : property;
|
|
6393
|
+
const member = stmt.members.find((m) => m.type !== "ClassConstructor" && m.isStatic === isStatic && m.name.name === name && (m.accessibility === "private" || m.accessibility === "protected"));
|
|
6394
|
+
const marked = member ? { ...property, private: { owner: stmt, className, ...member.accessibility === "protected" ? { protected: true } : {} } } : property;
|
|
6145
6395
|
(isStatic ? shape.statics : shape.instance).set(name, marked);
|
|
6146
6396
|
};
|
|
6147
6397
|
for (const member of stmt.members) {
|
|
6148
6398
|
if (member.type !== "ClassField") continue;
|
|
6149
6399
|
const type = member.typeAnnotation ? this.resolveType(member.typeAnnotation) : member.init ? widen(this.infer(member.init, /* @__PURE__ */ new Map())) : anyType;
|
|
6150
|
-
put(member.isStatic, member.name.name, {
|
|
6400
|
+
put(member.isStatic, member.name.name, {
|
|
6401
|
+
type,
|
|
6402
|
+
optional: false,
|
|
6403
|
+
...member.isReadonly ? { readonly: true } : {}
|
|
6404
|
+
});
|
|
6151
6405
|
}
|
|
6152
6406
|
for (const member of stmt.members) {
|
|
6153
6407
|
switch (member.type) {
|
|
@@ -6156,7 +6410,11 @@ var TypeAnalyzer = class {
|
|
|
6156
6410
|
case "ClassMethod": {
|
|
6157
6411
|
this.paramsFromSignatures(member.func, member.signatures);
|
|
6158
6412
|
const type = member.signatures?.length ? intersection(member.signatures.map((sig) => this.signatureToFnType(sig))) : this.inferFunctionBody(member.func, /* @__PURE__ */ new Map());
|
|
6159
|
-
put(member.isStatic, member.name.name, {
|
|
6413
|
+
put(member.isStatic, member.name.name, {
|
|
6414
|
+
type,
|
|
6415
|
+
optional: false,
|
|
6416
|
+
...member.isAbstract ? { abstract: true } : {}
|
|
6417
|
+
});
|
|
6160
6418
|
break;
|
|
6161
6419
|
}
|
|
6162
6420
|
case "ClassAccessor": {
|
|
@@ -6212,7 +6470,14 @@ var TypeAnalyzer = class {
|
|
|
6212
6470
|
for (const signature of member.signatures ?? []) {
|
|
6213
6471
|
this.checkParamOrder(signature.params, member);
|
|
6214
6472
|
}
|
|
6215
|
-
|
|
6473
|
+
if (member.type === "ClassMethod" && member.isAbstract) continue;
|
|
6474
|
+
const constructing = this.constructing;
|
|
6475
|
+
if (member.type === "ClassConstructor") this.constructing = stmt;
|
|
6476
|
+
try {
|
|
6477
|
+
this.visitFunctionBody(member.func, env);
|
|
6478
|
+
} finally {
|
|
6479
|
+
this.constructing = constructing;
|
|
6480
|
+
}
|
|
6216
6481
|
}
|
|
6217
6482
|
});
|
|
6218
6483
|
}));
|
|
@@ -6259,13 +6524,148 @@ var TypeAnalyzer = class {
|
|
|
6259
6524
|
if (stmt.superclass && this.baseInstance(stmt) && constructor && !callsSuper(constructor.func.body)) {
|
|
6260
6525
|
report(constructor, `'${name}' extends '${stmt.superclass.name}', so its constructor must call 'super(...)'`);
|
|
6261
6526
|
}
|
|
6262
|
-
|
|
6527
|
+
this.checkAbstractMembers(stmt);
|
|
6528
|
+
this.checkOverrides(stmt);
|
|
6529
|
+
this.checkImplements(stmt);
|
|
6530
|
+
this.checkMetamethods(stmt);
|
|
6531
|
+
const assigned = constructor ? definitelyAssigned(constructor.func.body) ?? ALL_FIELDS : /* @__PURE__ */ new Set();
|
|
6263
6532
|
for (const member of stmt.members) {
|
|
6264
6533
|
if (member.type !== "ClassField" || member.isStatic || member.init) continue;
|
|
6265
|
-
if (assigned.has(member.name.name)) continue;
|
|
6534
|
+
if (assigned === ALL_FIELDS || assigned.has(member.name.name)) continue;
|
|
6266
6535
|
const type = member.typeAnnotation ? this.withTypeParams(this.classTypeParams(stmt), () => this.resolveType(member.typeAnnotation)) : anyType;
|
|
6267
6536
|
if (isAssignable(nilType, type)) continue;
|
|
6268
|
-
report(member.name, `'${member.name.name}' has no value: give it one, assign it in the constructor, or let its type admit nil`);
|
|
6537
|
+
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`);
|
|
6538
|
+
}
|
|
6539
|
+
}
|
|
6540
|
+
/** The class whose constructor is being checked: it, and only it, may
|
|
6541
|
+
* assign the `readonly` fields it declares. */
|
|
6542
|
+
constructing;
|
|
6543
|
+
/** A class that is not abstract has a `new`, so everything its instances
|
|
6544
|
+
* promise has to be there: no abstract member of its own, and every one
|
|
6545
|
+
* it inherits written. */
|
|
6546
|
+
checkAbstractMembers(stmt) {
|
|
6547
|
+
if (stmt.isAbstract) return;
|
|
6548
|
+
const name = this.className(stmt);
|
|
6549
|
+
for (const member of stmt.members) {
|
|
6550
|
+
if (member.type === "ClassMethod" && member.isAbstract) {
|
|
6551
|
+
this.diagnostics.push({
|
|
6552
|
+
node: member.name,
|
|
6553
|
+
message: `'${member.name.name}' is abstract, so '${name}' has to be an 'abstract class'`
|
|
6554
|
+
});
|
|
6555
|
+
}
|
|
6556
|
+
}
|
|
6557
|
+
const own = this.shapeOf(stmt).instance;
|
|
6558
|
+
const missing = [...this.instanceType(stmt).properties].filter(([key, property]) => property.abstract && !own.has(key)).map(([key]) => `'${key}'`);
|
|
6559
|
+
if (missing.length) {
|
|
6560
|
+
this.diagnostics.push({
|
|
6561
|
+
node: stmt.name ?? stmt,
|
|
6562
|
+
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`
|
|
6563
|
+
});
|
|
6564
|
+
}
|
|
6565
|
+
}
|
|
6566
|
+
/** A member written again in a class that extends another has to fit
|
|
6567
|
+
* where the base's stood — anything reading an instance as the base
|
|
6568
|
+
* would otherwise be told wrong. `override` says a member is such a one,
|
|
6569
|
+
* and is wrong on a member that is not. */
|
|
6570
|
+
checkOverrides(stmt) {
|
|
6571
|
+
const base = this.baseInstance(stmt);
|
|
6572
|
+
const name = this.className(stmt);
|
|
6573
|
+
const own = this.shapeOf(stmt).instance;
|
|
6574
|
+
for (const member of stmt.members) {
|
|
6575
|
+
if (member.type === "ClassConstructor" || member.isStatic) continue;
|
|
6576
|
+
const key = member.name.name;
|
|
6577
|
+
const inherited = CLASS_LINKS.has(key) ? void 0 : base?.properties.get(key);
|
|
6578
|
+
if (member.isOverride && !inherited) {
|
|
6579
|
+
this.diagnostics.push({
|
|
6580
|
+
node: member.name,
|
|
6581
|
+
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`
|
|
6582
|
+
});
|
|
6583
|
+
continue;
|
|
6584
|
+
}
|
|
6585
|
+
const mine = own.get(key);
|
|
6586
|
+
if (!inherited || !mine || inherited.private && !inherited.private.protected) continue;
|
|
6587
|
+
const written = withoutReceiver(mine.type);
|
|
6588
|
+
const expected = withoutReceiver(inherited.type);
|
|
6589
|
+
if (isAssignable(written, expected)) continue;
|
|
6590
|
+
this.diagnostics.push({
|
|
6591
|
+
node: member.name,
|
|
6592
|
+
message: `'${key}' in '${name}' does not fit the '${key}' of '${stmt.superclass.name}' it replaces: '${briefType(written)}' is not assignable to '${briefType(expected)}'`
|
|
6593
|
+
});
|
|
6594
|
+
}
|
|
6595
|
+
}
|
|
6596
|
+
/** `implements Shape`: every member the shape names, of a type that fits.
|
|
6597
|
+
* A method's own `this` is left out of the comparison on both sides. */
|
|
6598
|
+
checkImplements(stmt) {
|
|
6599
|
+
if (!stmt.implements?.length) return;
|
|
6600
|
+
const name = this.className(stmt);
|
|
6601
|
+
const instance = this.instanceType(stmt).properties;
|
|
6602
|
+
for (const node of stmt.implements) {
|
|
6603
|
+
const shown = node.type === "TypeReference" ? node.namespace ? `${node.namespace}.${node.base}` : node.base : void 0;
|
|
6604
|
+
const target = this.expand(this.withTypeParams(this.classTypeParams(stmt), () => this.resolveType(node)));
|
|
6605
|
+
if (target.kind === "any") continue;
|
|
6606
|
+
if (target.kind !== "object") {
|
|
6607
|
+
this.diagnostics.push({
|
|
6608
|
+
node,
|
|
6609
|
+
message: `A class can only implement an object type or a class, not '${briefType(target)}'`
|
|
6610
|
+
});
|
|
6611
|
+
continue;
|
|
6612
|
+
}
|
|
6613
|
+
const label = shown ?? briefType(target);
|
|
6614
|
+
const missing = [];
|
|
6615
|
+
for (const [key, wanted] of target.properties) {
|
|
6616
|
+
const have = instance.get(key);
|
|
6617
|
+
if (!have) {
|
|
6618
|
+
if (!wanted.optional) missing.push(`'${key}'`);
|
|
6619
|
+
continue;
|
|
6620
|
+
}
|
|
6621
|
+
const written = withoutReceiver(have.type);
|
|
6622
|
+
const expected = withoutReceiver(wanted.optional ? optional(wanted.type) : wanted.type);
|
|
6623
|
+
if (isAssignable(written, expected)) continue;
|
|
6624
|
+
this.diagnostics.push({
|
|
6625
|
+
node,
|
|
6626
|
+
message: `'${name}' does not implement '${label}': its '${key}' is '${briefType(written)}', not '${briefType(expected)}'`
|
|
6627
|
+
});
|
|
6628
|
+
}
|
|
6629
|
+
if (missing.length) {
|
|
6630
|
+
this.diagnostics.push({
|
|
6631
|
+
node,
|
|
6632
|
+
message: `'${name}' does not implement '${label}': ${missing.join(", ")} ${missing.length === 1 ? "is" : "are"} missing`
|
|
6633
|
+
});
|
|
6634
|
+
}
|
|
6635
|
+
}
|
|
6636
|
+
}
|
|
6637
|
+
/** A method named for a metamethod *is* one — the class table is its
|
|
6638
|
+
* instances' metatable — so it has to take what Luau hands it and give
|
|
6639
|
+
* back what Luau expects. */
|
|
6640
|
+
checkMetamethods(stmt) {
|
|
6641
|
+
const instance = this.shapeOf(stmt).instance;
|
|
6642
|
+
for (const member of stmt.members) {
|
|
6643
|
+
if (member.type !== "ClassMethod" || !(member.name.name in METAMETHOD_OPERANDS)) continue;
|
|
6644
|
+
const key = member.name.name;
|
|
6645
|
+
if (member.isStatic) {
|
|
6646
|
+
this.diagnostics.push({
|
|
6647
|
+
node: member.name,
|
|
6648
|
+
message: `'${key}' is a metamethod of the instances; it cannot be static`
|
|
6649
|
+
});
|
|
6650
|
+
continue;
|
|
6651
|
+
}
|
|
6652
|
+
const operands = METAMETHOD_OPERANDS[key];
|
|
6653
|
+
const written = member.func.params.filter((p) => p.name !== "this").length;
|
|
6654
|
+
if (operands !== void 0 && (written !== operands || member.func.hasVarargs)) {
|
|
6655
|
+
this.diagnostics.push({
|
|
6656
|
+
node: member.name,
|
|
6657
|
+
message: operands === 0 ? `'${key}' takes nothing besides 'this'` : `'${key}' takes one operand besides 'this'`
|
|
6658
|
+
});
|
|
6659
|
+
continue;
|
|
6660
|
+
}
|
|
6661
|
+
const required = METAMETHOD_RETURNS[key];
|
|
6662
|
+
const returns = this.overloadsOf(instance.get(key)?.type ?? anyType)[0]?.returns;
|
|
6663
|
+
if (required && returns && !isAssignable(returns, required)) {
|
|
6664
|
+
this.diagnostics.push({
|
|
6665
|
+
node: member.name,
|
|
6666
|
+
message: `'${key}' has to return ${briefType(required)}, not '${briefType(returns)}'`
|
|
6667
|
+
});
|
|
6668
|
+
}
|
|
6269
6669
|
}
|
|
6270
6670
|
}
|
|
6271
6671
|
/** `extends` must name a class, and the chain must end. */
|
|
@@ -6323,19 +6723,42 @@ var TypeAnalyzer = class {
|
|
|
6323
6723
|
harvestDeclares(block, own = false) {
|
|
6324
6724
|
for (const stmt of block.statements) {
|
|
6325
6725
|
if (stmt.type !== "DeclareStatement") continue;
|
|
6326
|
-
if (own
|
|
6726
|
+
if (!own) {
|
|
6727
|
+
const declares = this.libDeclares.get(stmt.name);
|
|
6728
|
+
if (declares) declares.push(stmt);
|
|
6729
|
+
else {
|
|
6730
|
+
this.libDeclares.set(stmt.name, [stmt]);
|
|
6731
|
+
this.libGlobalTypes.defer(stmt.name, () => this.resolveLibDeclares(stmt.name));
|
|
6732
|
+
}
|
|
6733
|
+
continue;
|
|
6734
|
+
}
|
|
6735
|
+
if (containsTypeQuery(stmt.valueType) || referencedTypeNames(stmt.valueType).some((name) => this.dependsOnTypeQuery(name))) {
|
|
6327
6736
|
this.deferredDeclares.set(stmt.name, stmt);
|
|
6328
6737
|
continue;
|
|
6329
6738
|
}
|
|
6330
|
-
|
|
6331
|
-
const prev = this.libGlobalTypes.get(stmt.name);
|
|
6332
|
-
const overload = prev && stmt.valueType.type === "FunctionTypeNode" && (prev.kind === "function" || prev.kind === "intersection");
|
|
6333
|
-
this.libGlobalTypes.set(
|
|
6334
|
-
stmt.name,
|
|
6335
|
-
overload ? intersection([prev, t]) : this.mergeDeclared(prev, t)
|
|
6336
|
-
);
|
|
6739
|
+
this.libGlobalTypes.set(stmt.name, this.declaredOver(this.libGlobalTypes.get(stmt.name), stmt));
|
|
6337
6740
|
}
|
|
6338
6741
|
}
|
|
6742
|
+
/** Every library `declare` of `name`, in order. They are written at the
|
|
6743
|
+
* top level of their files, so they are read as if there — outside any
|
|
6744
|
+
* generic the use that asked for them happens to sit in. */
|
|
6745
|
+
resolveLibDeclares(name) {
|
|
6746
|
+
const scope = this.typeParamScope.splice(0);
|
|
6747
|
+
try {
|
|
6748
|
+
let type;
|
|
6749
|
+
for (const stmt of this.libDeclares.get(name) ?? []) type = this.declaredOver(type, stmt);
|
|
6750
|
+
return type ?? anyType;
|
|
6751
|
+
} finally {
|
|
6752
|
+
this.typeParamScope.push(...scope);
|
|
6753
|
+
}
|
|
6754
|
+
}
|
|
6755
|
+
/** A `declare` on top of what earlier ones said about the same name: a
|
|
6756
|
+
* function adds an overload, a table adds members. */
|
|
6757
|
+
declaredOver(prev, stmt) {
|
|
6758
|
+
const t = this.resolveType(stmt.valueType);
|
|
6759
|
+
const overload = prev && stmt.valueType.type === "FunctionTypeNode" && (prev.kind === "function" || prev.kind === "intersection");
|
|
6760
|
+
return overload ? intersection([prev, t]) : this.mergeDeclared(prev, t);
|
|
6761
|
+
}
|
|
6339
6762
|
resolveAllAliases() {
|
|
6340
6763
|
for (const [name, def] of this.aliasDefs) {
|
|
6341
6764
|
if (def.class && !this.program.body.statements.includes(def.class)) {
|
|
@@ -6381,11 +6804,10 @@ var TypeAnalyzer = class {
|
|
|
6381
6804
|
}
|
|
6382
6805
|
imported;
|
|
6383
6806
|
/** What a `return` gives, against what the function declared. */
|
|
6384
|
-
checkReturn(stmt, declared,
|
|
6807
|
+
checkReturn(stmt, declared, actual, env) {
|
|
6385
6808
|
if (!declared || !this.emitDiagnostics) return;
|
|
6386
6809
|
if (declared.kind === "any" || declared.kind === "unknown" || this.namesNothing(declared)) return;
|
|
6387
|
-
const
|
|
6388
|
-
const source = stmt.arguments.length === 1 ? sources[0] : void 0;
|
|
6810
|
+
const source = stmt.argument;
|
|
6389
6811
|
const fits = source ? this.fitsAnnotation(source, declared, actual, env) : isAssignable(actual, declared) || isAssignable(widen(actual), declared);
|
|
6390
6812
|
if (fits) return;
|
|
6391
6813
|
this.diagnostics.push({
|
|
@@ -6533,21 +6955,12 @@ var TypeAnalyzer = class {
|
|
|
6533
6955
|
this.instantiationDepth--;
|
|
6534
6956
|
}
|
|
6535
6957
|
}
|
|
6536
|
-
/** Pair written type arguments with the parameters they instantiate.
|
|
6537
|
-
*
|
|
6538
|
-
* one pack: `Signal<Instance, string>` binds `T` to `(Instance, string)`,
|
|
6539
|
-
* and `Signal<()>` to the empty pack. Left out, a parameter takes its
|
|
6540
|
-
* default (`T... = ...any` is `any`), or `unknown`. */
|
|
6958
|
+
/** Pair written type arguments with the parameters they instantiate.
|
|
6959
|
+
* Left out, a parameter takes its default, or `unknown`. */
|
|
6541
6960
|
bindTypeArguments(params, args) {
|
|
6542
6961
|
const subst = /* @__PURE__ */ new Map();
|
|
6543
6962
|
params.forEach((p, i) => {
|
|
6544
|
-
|
|
6545
|
-
if (p.isPack && i < args.length) {
|
|
6546
|
-
const rest = args.slice(i);
|
|
6547
|
-
const single = rest.length === 1 ? rest[0] : void 0;
|
|
6548
|
-
arg = single && (single.kind === "tuple" && single.isPack || single.kind === "typeParam" || single.kind === "any") ? single : tuple([...rest], true);
|
|
6549
|
-
}
|
|
6550
|
-
subst.set(p.name, arg ?? (p.default ? this.resolveType(p.default) : unknownType));
|
|
6963
|
+
subst.set(p.name, args[i] ?? (p.default ? this.resolveType(p.default) : unknownType));
|
|
6551
6964
|
});
|
|
6552
6965
|
return subst;
|
|
6553
6966
|
}
|
|
@@ -6637,10 +7050,18 @@ var TypeAnalyzer = class {
|
|
|
6637
7050
|
return literal(node.value);
|
|
6638
7051
|
case "TypeLiteralNumber":
|
|
6639
7052
|
return literal(node.value);
|
|
6640
|
-
case "ArrayTypeNode":
|
|
6641
|
-
|
|
6642
|
-
|
|
6643
|
-
|
|
7053
|
+
case "ArrayTypeNode": {
|
|
7054
|
+
const made = arrayOf(this.resolveType(node.element));
|
|
7055
|
+
return node.isReadonly ? { ...made, readonly: true } : made;
|
|
7056
|
+
}
|
|
7057
|
+
case "TupleTypeNode": {
|
|
7058
|
+
const rest = node.rest && this.expand(this.resolveType(node.rest));
|
|
7059
|
+
const made = tuple(
|
|
7060
|
+
node.elements.map((e) => this.resolveType(e)),
|
|
7061
|
+
rest ? rest.kind === "array" ? rest.element : rest.kind === "any" ? anyType : unknownType : void 0
|
|
7062
|
+
);
|
|
7063
|
+
return node.isReadonly && made.kind === "tuple" ? { ...made, readonly: true } : made;
|
|
7064
|
+
}
|
|
6644
7065
|
case "UnionTypeNode":
|
|
6645
7066
|
return union(node.types.map((t) => this.resolveType(t)));
|
|
6646
7067
|
case "IntersectionTypeNode":
|
|
@@ -6676,15 +7097,13 @@ var TypeAnalyzer = class {
|
|
|
6676
7097
|
type: p.optional ? optional(this.resolveType(p.typeAnnotation)) : this.resolveType(p.typeAnnotation),
|
|
6677
7098
|
optional: p.optional
|
|
6678
7099
|
}));
|
|
6679
|
-
|
|
6680
|
-
const restElement = restParam ? this.resolveType(restParam.typeAnnotation) : void 0;
|
|
6681
|
-
return this.withTypeParamDefaults(fn(
|
|
7100
|
+
return this.withTypeParamDefaults(this.withRest(fn(
|
|
6682
7101
|
params,
|
|
6683
7102
|
this.resolveType(node.returnType),
|
|
6684
|
-
|
|
7103
|
+
void 0,
|
|
6685
7104
|
names,
|
|
6686
7105
|
this.resolvePredicate(node.predicate, params)
|
|
6687
|
-
), node.generics);
|
|
7106
|
+
), node.params), node.generics);
|
|
6688
7107
|
});
|
|
6689
7108
|
}
|
|
6690
7109
|
case "TypeofTypeNode": {
|
|
@@ -6748,13 +7167,6 @@ var TypeAnalyzer = class {
|
|
|
6748
7167
|
})
|
|
6749
7168
|
);
|
|
6750
7169
|
}
|
|
6751
|
-
case "VariadicTypeNode":
|
|
6752
|
-
return this.resolveType(node.typeAnnotation);
|
|
6753
|
-
case "TypePackNode": {
|
|
6754
|
-
if (node.types.length === 1 && !node.hasVarargs) return this.resolveType(node.types[0]);
|
|
6755
|
-
if (!node.types.length && node.varargType) return this.resolveType(node.varargType);
|
|
6756
|
-
return tuple(node.types.map((t) => this.resolveType(t)), true);
|
|
6757
|
-
}
|
|
6758
7170
|
}
|
|
6759
7171
|
}
|
|
6760
7172
|
// --------------------------------------------------------
|
|
@@ -6820,15 +7232,18 @@ var TypeAnalyzer = class {
|
|
|
6820
7232
|
case "array":
|
|
6821
7233
|
return arrayOf(this.reduceType(t.element));
|
|
6822
7234
|
case "tuple":
|
|
6823
|
-
return tuple(t.elements.map((e) => this.reduceType(e)), t.
|
|
7235
|
+
return tuple(t.elements.map((e) => this.reduceType(e)), t.rest && this.reduceType(t.rest));
|
|
6824
7236
|
case "function":
|
|
6825
|
-
return
|
|
6826
|
-
|
|
6827
|
-
|
|
6828
|
-
|
|
6829
|
-
|
|
6830
|
-
|
|
6831
|
-
|
|
7237
|
+
return {
|
|
7238
|
+
...fn(
|
|
7239
|
+
t.params.map((p) => ({ ...p, type: this.reduceType(p.type) })),
|
|
7240
|
+
this.reduceType(t.returns),
|
|
7241
|
+
t.varargs && this.reduceType(t.varargs),
|
|
7242
|
+
t.typeParams,
|
|
7243
|
+
t.predicate
|
|
7244
|
+
),
|
|
7245
|
+
...t.restIsWhole ? { restIsWhole: true } : {}
|
|
7246
|
+
};
|
|
6832
7247
|
case "object": {
|
|
6833
7248
|
if (t.class) return t;
|
|
6834
7249
|
const entries = [];
|
|
@@ -7043,7 +7458,8 @@ var TypeAnalyzer = class {
|
|
|
7043
7458
|
this.applyContext(stmt.init[i], this.resolveType(target.typeAnnotation));
|
|
7044
7459
|
}
|
|
7045
7460
|
});
|
|
7046
|
-
const { types: valueTypes, sources } = this.valueList(stmt.init, env
|
|
7461
|
+
const { types: valueTypes, sources } = this.valueList(stmt.init, env);
|
|
7462
|
+
this.checkValueCount(stmt, stmt.names.length, stmt.init, valueTypes);
|
|
7047
7463
|
stmt.names.forEach((target, i) => {
|
|
7048
7464
|
const inferred = valueTypes[i] ?? (stmt.init.length ? unknownType : nilType);
|
|
7049
7465
|
const source = sources[i];
|
|
@@ -7132,7 +7548,8 @@ var TypeAnalyzer = class {
|
|
|
7132
7548
|
if (id !== void 0 && this.annotated.has(id)) this.applyContext(value, this.bindingType.get(id));
|
|
7133
7549
|
}
|
|
7134
7550
|
});
|
|
7135
|
-
const { types: valueTypes, sources } = this.valueList(stmt.values, env
|
|
7551
|
+
const { types: valueTypes, sources } = this.valueList(stmt.values, env);
|
|
7552
|
+
this.checkValueCount(stmt, stmt.targets.length, stmt.values, valueTypes);
|
|
7136
7553
|
stmt.targets.forEach((target, i) => {
|
|
7137
7554
|
const vt = valueTypes[i] ?? unknownType;
|
|
7138
7555
|
const source = sources[i];
|
|
@@ -7219,23 +7636,26 @@ var TypeAnalyzer = class {
|
|
|
7219
7636
|
return;
|
|
7220
7637
|
}
|
|
7221
7638
|
case "GenericForStatement": {
|
|
7222
|
-
const
|
|
7639
|
+
const sourceType = this.infer(stmt.iterator, env);
|
|
7223
7640
|
const bodyEnv = forkEnv(env);
|
|
7224
|
-
const rows =
|
|
7641
|
+
const rows = this.iterationRows(stmt.iterator, sourceType);
|
|
7225
7642
|
if (rows) {
|
|
7226
|
-
|
|
7227
|
-
|
|
7228
|
-
|
|
7229
|
-
|
|
7230
|
-
|
|
7231
|
-
|
|
7232
|
-
|
|
7643
|
+
this.bindPattern(
|
|
7644
|
+
stmt.variable,
|
|
7645
|
+
union(rows.map((row) => tuple(row))),
|
|
7646
|
+
bodyEnv,
|
|
7647
|
+
stmt.kind === "const" ? "keep" : "widen"
|
|
7648
|
+
);
|
|
7649
|
+
const parts = stmt.variable.type === "ArrayPattern" && !stmt.variable.rest ? stmt.variable.elements.slice(0, 2).map((el) => el?.value) : [];
|
|
7650
|
+
const ids = parts.map((p) => p?.type === "IdentifierPattern" ? this.bindingIdByName(p.name, p) : void 0);
|
|
7651
|
+
if (ids.length === 2 && ids.every((id) => id !== void 0)) {
|
|
7652
|
+
this.correlateBindings(bodyEnv, ids, rows);
|
|
7653
|
+
}
|
|
7654
|
+
this.loops.set(stmt, { walks: "iteration", viaIter: false });
|
|
7233
7655
|
} else {
|
|
7234
|
-
const [
|
|
7235
|
-
stmt.
|
|
7236
|
-
|
|
7237
|
-
});
|
|
7238
|
-
if (iteratesValues && stmt.iterators.length === 1) this.iteratesValues.add(stmt);
|
|
7656
|
+
const [item, form] = this.iterationOf(stmt.iterator, sourceType);
|
|
7657
|
+
this.bindPattern(stmt.variable, item, bodyEnv, "widen");
|
|
7658
|
+
this.loops.set(stmt, form);
|
|
7239
7659
|
}
|
|
7240
7660
|
this.visitBlock(stmt.body, bodyEnv);
|
|
7241
7661
|
return;
|
|
@@ -7243,19 +7663,10 @@ var TypeAnalyzer = class {
|
|
|
7243
7663
|
case "ReturnStatement": {
|
|
7244
7664
|
const declared = this.declaredReturns[this.declaredReturns.length - 1];
|
|
7245
7665
|
const wanted = declared ?? this.contextualReturns[this.contextualReturns.length - 1];
|
|
7246
|
-
if (wanted)
|
|
7247
|
-
|
|
7248
|
-
|
|
7249
|
-
|
|
7250
|
-
stmt.arguments.forEach((a, i) => this.applyContext(a, wanted.elements[i]));
|
|
7251
|
-
}
|
|
7252
|
-
}
|
|
7253
|
-
const want = declared?.kind === "tuple" && declared.isPack ? declared.elements.length : 0;
|
|
7254
|
-
const { types, sources } = this.valueList(stmt.arguments, env, want);
|
|
7255
|
-
this.checkReturn(stmt, declared, types, sources, env);
|
|
7256
|
-
if (this.returnTypes) {
|
|
7257
|
-
this.returnTypes.push(stmt.arguments.length === 0 ? nilType : types.length === 1 ? types[0] : tuple([...types], true));
|
|
7258
|
-
}
|
|
7666
|
+
if (wanted && stmt.argument) this.applyContext(stmt.argument, wanted);
|
|
7667
|
+
const actual = stmt.argument ? this.infer(stmt.argument, env) : nilType;
|
|
7668
|
+
this.checkReturn(stmt, declared, actual, env);
|
|
7669
|
+
if (this.returnTypes) this.returnTypes.push(actual);
|
|
7259
7670
|
return;
|
|
7260
7671
|
}
|
|
7261
7672
|
case "ExportStatement":
|
|
@@ -7342,7 +7753,7 @@ var TypeAnalyzer = class {
|
|
|
7342
7753
|
* `sources` maps each produced value back to the expression it came from
|
|
7343
7754
|
* (undefined for the 2nd and later values of a multi-value call), so the
|
|
7344
7755
|
* caller can still do contextual typing against the written expression. */
|
|
7345
|
-
valueList(exprs, env
|
|
7756
|
+
valueList(exprs, env) {
|
|
7346
7757
|
const types = [];
|
|
7347
7758
|
const sources = [];
|
|
7348
7759
|
exprs.forEach((e, i) => {
|
|
@@ -7358,31 +7769,29 @@ var TypeAnalyzer = class {
|
|
|
7358
7769
|
}
|
|
7359
7770
|
return;
|
|
7360
7771
|
}
|
|
7361
|
-
|
|
7362
|
-
|
|
7363
|
-
sources.push(e);
|
|
7364
|
-
} while (last && types.length < want);
|
|
7365
|
-
return;
|
|
7366
|
-
}
|
|
7367
|
-
if (last && e.type === "VarargExpression") {
|
|
7368
|
-
do {
|
|
7369
|
-
types.push(t);
|
|
7370
|
-
sources.push(types.length - 1 === i ? e : void 0);
|
|
7371
|
-
} while (types.length < want);
|
|
7372
|
-
return;
|
|
7373
|
-
}
|
|
7374
|
-
if (last && t.kind === "tuple" && t.isPack && producesMultipleValues(e)) {
|
|
7375
|
-
t.elements.forEach((el, j) => {
|
|
7376
|
-
types.push(el);
|
|
7377
|
-
sources.push(j === 0 ? e : void 0);
|
|
7378
|
-
});
|
|
7772
|
+
types.push(t);
|
|
7773
|
+
sources.push(e);
|
|
7379
7774
|
return;
|
|
7380
7775
|
}
|
|
7381
|
-
|
|
7776
|
+
void last;
|
|
7777
|
+
types.push(t);
|
|
7382
7778
|
sources.push(e);
|
|
7383
7779
|
});
|
|
7384
7780
|
return { types, sources };
|
|
7385
7781
|
}
|
|
7782
|
+
/** `const a, b = x, y` pairs names with values, one each. A call is one
|
|
7783
|
+
* value — several come back as an array — so `const a, b = f()` is a
|
|
7784
|
+
* name short of a value, and says how to take them apart. */
|
|
7785
|
+
checkValueCount(node, names, values, types) {
|
|
7786
|
+
if (!this.emitDiagnostics || !values.length || types.length >= names) return;
|
|
7787
|
+
const last = values[values.length - 1];
|
|
7788
|
+
const lastType = types[types.length - 1] && this.expand(types[types.length - 1]);
|
|
7789
|
+
const call = last.type === "CallExpression" || last.type === "MethodCallExpression";
|
|
7790
|
+
this.diagnostics.push({
|
|
7791
|
+
node,
|
|
7792
|
+
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`
|
|
7793
|
+
});
|
|
7794
|
+
}
|
|
7386
7795
|
/** Run `visit` with a fresh place to collect `break` states, and return
|
|
7387
7796
|
* what it collected. */
|
|
7388
7797
|
withBreakScope(visit) {
|
|
@@ -7471,8 +7880,19 @@ var TypeAnalyzer = class {
|
|
|
7471
7880
|
if (p.rest) return arrayOf(unknownType);
|
|
7472
7881
|
if (p.pattern) return this.patternToType(p.pattern, env);
|
|
7473
7882
|
if (p.default) return widen(this.infer(p.default, env));
|
|
7474
|
-
|
|
7883
|
+
const contextual = this.contextualParams.get(p);
|
|
7884
|
+
if (contextual) return contextual;
|
|
7885
|
+
if (this.emitDiagnostics && !receiver && p.name && p.line && !this.untypedParamReported.has(p)) {
|
|
7886
|
+
this.untypedParamReported.add(p);
|
|
7887
|
+
this.diagnostics.push({
|
|
7888
|
+
node: p,
|
|
7889
|
+
message: `Parameter '${p.name}' has no type, so it is 'any': give it one, or a default to read it from`
|
|
7890
|
+
});
|
|
7891
|
+
}
|
|
7892
|
+
return anyType;
|
|
7475
7893
|
}
|
|
7894
|
+
/** Parameters already spoken about: a body is visited more than once. */
|
|
7895
|
+
untypedParamReported = /* @__PURE__ */ new WeakSet();
|
|
7476
7896
|
/** What a function expression's unannotated parameters are, from where
|
|
7477
7897
|
* it is written — see `applyContext`. */
|
|
7478
7898
|
contextualParams = /* @__PURE__ */ new WeakMap();
|
|
@@ -7531,18 +7951,29 @@ var TypeAnalyzer = class {
|
|
|
7531
7951
|
* context. */
|
|
7532
7952
|
contextualArrays = /* @__PURE__ */ new WeakMap();
|
|
7533
7953
|
applyArrayContext(e, expected) {
|
|
7534
|
-
const
|
|
7954
|
+
const members = this.expectedMembers(expected);
|
|
7955
|
+
const tuples = members.filter((m) => m.kind === "tuple");
|
|
7956
|
+
const spread = e.elements.some((el) => el.type === "SpreadElement");
|
|
7957
|
+
const length = e.elements.length;
|
|
7958
|
+
const fitting = tuples.find((t) => !spread && !t.rest && t.elements.length === length) ?? tuples.find((t) => t.rest !== void 0 && (spread || length >= t.elements.length));
|
|
7959
|
+
const target = fitting ?? members.find((m) => m.kind === "array") ?? tuples[0];
|
|
7535
7960
|
if (!target) return;
|
|
7961
|
+
if (target.kind === "tuple" && e.elements.length) this.tupleArrays.set(e, target);
|
|
7536
7962
|
if (!e.elements.length) {
|
|
7537
7963
|
this.contextualArrays.set(e, target);
|
|
7538
7964
|
return;
|
|
7539
7965
|
}
|
|
7540
7966
|
e.elements.forEach((element, i) => {
|
|
7541
7967
|
if (element.type === "SpreadElement") return;
|
|
7542
|
-
const
|
|
7968
|
+
const tupleTarget = target;
|
|
7969
|
+
const elementType = target.kind === "array" ? target.element : tupleTarget.elements[i] ?? tupleTarget.rest;
|
|
7543
7970
|
this.applyContext(element, elementType);
|
|
7544
7971
|
});
|
|
7545
7972
|
}
|
|
7973
|
+
/** Array literals written where a tuple is wanted — a `return` of a
|
|
7974
|
+
* function that returns one, an argument, an annotated `const` — and
|
|
7975
|
+
* the tuple each is to be. */
|
|
7976
|
+
tupleArrays = /* @__PURE__ */ new WeakMap();
|
|
7546
7977
|
/** `{ list: [] }` where `{ list: thread[] }` is expected: each field's
|
|
7547
7978
|
* value gets its property's type as context. */
|
|
7548
7979
|
applyTableContext(e, expected) {
|
|
@@ -7598,32 +8029,33 @@ var TypeAnalyzer = class {
|
|
|
7598
8029
|
}
|
|
7599
8030
|
return tuple(target.elements.map((el) => el ? leaf(el.value, el.default) : anyType));
|
|
7600
8031
|
}
|
|
7601
|
-
/**
|
|
7602
|
-
*
|
|
7603
|
-
*
|
|
7604
|
-
|
|
7605
|
-
|
|
7606
|
-
|
|
7607
|
-
|
|
7608
|
-
|
|
7609
|
-
const
|
|
7610
|
-
if (
|
|
7611
|
-
|
|
7612
|
-
|
|
7613
|
-
|
|
7614
|
-
|
|
7615
|
-
|
|
8032
|
+
/** A function type with the rest parameter among `params` written in:
|
|
8033
|
+
* `...args: T[]` collects `T`s, and `...args: A` — `A` a tuple, or a
|
|
8034
|
+
* type parameter standing for one — is exactly `A`'s elements. */
|
|
8035
|
+
withRest(f, params) {
|
|
8036
|
+
const rest = params.find((p) => p.rest);
|
|
8037
|
+
if (!rest) return f;
|
|
8038
|
+
if (!rest.typeAnnotation) return { ...f, varargs: unknownType };
|
|
8039
|
+
const declared = this.resolveType(rest.typeAnnotation);
|
|
8040
|
+
const t = this.expand(declared);
|
|
8041
|
+
if (t.kind === "array") return { ...f, varargs: t.element };
|
|
8042
|
+
if (t.kind === "any") return { ...f, varargs: anyType };
|
|
8043
|
+
if (declared.kind === "infer") return { ...f, varargs: declared, restIsWhole: true };
|
|
8044
|
+
if (t.kind === "tuple") {
|
|
8045
|
+
return { ...f, params: [...f.params, ...t.elements.map((type) => ({ type }))], varargs: t.rest };
|
|
8046
|
+
}
|
|
8047
|
+
if (declared.kind === "typeParam") return { ...f, varargs: declared, restIsWhole: true };
|
|
8048
|
+
return { ...f, varargs: unknownType };
|
|
8049
|
+
}
|
|
7616
8050
|
/** What each function body being walked declared it returns. */
|
|
7617
8051
|
declaredReturns = [];
|
|
7618
|
-
/** Run `body` with
|
|
8052
|
+
/** Run `body` with `return` as `func` declares it. */
|
|
7619
8053
|
withVarargs(func, body) {
|
|
7620
|
-
this.varargs.push(this.varargElement(func));
|
|
7621
8054
|
this.declaredReturns.push(func.predicate ? booleanType : func.returnType ? this.resolveType(func.returnType) : void 0);
|
|
7622
8055
|
this.contextualReturns.push(this.contextualReturnOf.get(func));
|
|
7623
8056
|
try {
|
|
7624
8057
|
return body();
|
|
7625
8058
|
} finally {
|
|
7626
|
-
this.varargs.pop();
|
|
7627
8059
|
this.declaredReturns.pop();
|
|
7628
8060
|
this.contextualReturns.pop();
|
|
7629
8061
|
}
|
|
@@ -7682,7 +8114,7 @@ var TypeAnalyzer = class {
|
|
|
7682
8114
|
if (type.kind !== "function") return type;
|
|
7683
8115
|
const defaults = {};
|
|
7684
8116
|
for (const generic of generics) {
|
|
7685
|
-
if (generic.default
|
|
8117
|
+
if (generic.default) defaults[generic.name] = this.resolveType(generic.default);
|
|
7686
8118
|
}
|
|
7687
8119
|
return Object.keys(defaults).length ? { ...type, typeParamDefaults: defaults } : type;
|
|
7688
8120
|
}
|
|
@@ -7701,7 +8133,10 @@ var TypeAnalyzer = class {
|
|
|
7701
8133
|
const preserve = keepsLiterals(param) || param.kind !== "typeParam" && containsTypeParam(param);
|
|
7702
8134
|
unify(p.type, preserve ? arg : widen(arg), vars, subst);
|
|
7703
8135
|
});
|
|
7704
|
-
if (f.varargs) {
|
|
8136
|
+
if (f.varargs && f.restIsWhole) {
|
|
8137
|
+
const rest = argTypes.slice(f.params.length).map((a) => a === void 0 ? unknownType : widen(a));
|
|
8138
|
+
unify(f.varargs, tuple(rest), vars, subst);
|
|
8139
|
+
} else if (f.varargs) {
|
|
7705
8140
|
const keeps = keepsLiterals(f.varargs);
|
|
7706
8141
|
for (let i = f.params.length; i < argTypes.length; i++) {
|
|
7707
8142
|
const arg = argTypes[i];
|
|
@@ -7752,6 +8187,11 @@ var TypeAnalyzer = class {
|
|
|
7752
8187
|
* this returns `undefined` and the ordinary pick stands. */
|
|
7753
8188
|
distributedReturn(fns, argTypes, picked, argsFor) {
|
|
7754
8189
|
if (fns.length < 2) return void 0;
|
|
8190
|
+
if (argTypes.some((t) => this.expand(t).kind === "any")) {
|
|
8191
|
+
const taken = fns.filter((f) => this.overloadAccepts(f, argsFor(f, argTypes)));
|
|
8192
|
+
if (taken.length < 2) return void 0;
|
|
8193
|
+
return union(taken.map((f) => this.callReturn(f, argsFor(f, argTypes))));
|
|
8194
|
+
}
|
|
7755
8195
|
const position = argTypes.findIndex((t) => this.expand(t).kind === "union");
|
|
7756
8196
|
if (position < 0) return void 0;
|
|
7757
8197
|
const members = this.expand(argTypes[position]).types;
|
|
@@ -7800,6 +8240,8 @@ var TypeAnalyzer = class {
|
|
|
7800
8240
|
* lone argument cannot be checked against without false errors. */
|
|
7801
8241
|
boundParams(f) {
|
|
7802
8242
|
if (!f.typeParams?.length) return f.params.map((p) => p.type);
|
|
8243
|
+
const known = this.boundParamsCache.get(f);
|
|
8244
|
+
if (known) return known;
|
|
7803
8245
|
const bounds = new Map(f.typeParams.map((name) => [name, anyType]));
|
|
7804
8246
|
const seen = /* @__PURE__ */ new WeakSet();
|
|
7805
8247
|
const walk = (value) => {
|
|
@@ -7817,8 +8259,11 @@ var TypeAnalyzer = class {
|
|
|
7817
8259
|
for (const child of Object.values(value)) walk(child);
|
|
7818
8260
|
};
|
|
7819
8261
|
for (const p of f.params) if (containsTypeParam(p.type)) walk(p.type);
|
|
7820
|
-
|
|
8262
|
+
const bound = f.params.map((p) => this.reduceType(substitute(p.type, bounds)));
|
|
8263
|
+
this.boundParamsCache.set(f, bound);
|
|
8264
|
+
return bound;
|
|
7821
8265
|
}
|
|
8266
|
+
boundParamsCache = /* @__PURE__ */ new WeakMap();
|
|
7822
8267
|
/** Record what each written argument is expected to be — see
|
|
7823
8268
|
* `TypeAnalysis.expectedTypeOf`. */
|
|
7824
8269
|
recordExpected(written, fns, selfOf, argsOf = () => []) {
|
|
@@ -7870,7 +8315,12 @@ var TypeAnalyzer = class {
|
|
|
7870
8315
|
const arg = argTypes[i];
|
|
7871
8316
|
const declared = declaredAt(i);
|
|
7872
8317
|
if (arg === void 0 || declared === void 0 || !containsTypeParam(declared)) continue;
|
|
7873
|
-
|
|
8318
|
+
let expected = this.reduceType(substitute(declared, subst));
|
|
8319
|
+
if (f.restIsWhole && i >= f.params.length) {
|
|
8320
|
+
const whole = this.expand(expected);
|
|
8321
|
+
expected = whole.kind === "tuple" ? whole.elements[i - f.params.length] ?? whole.rest : whole.kind === "array" ? whole.element : void 0;
|
|
8322
|
+
if (!expected) continue;
|
|
8323
|
+
}
|
|
7874
8324
|
if (containsTypeParam(expected) || expected.kind === "any" || expected.kind === "unknown") continue;
|
|
7875
8325
|
if (isAssignable(arg, expected) || isAssignable(widen(arg), expected)) continue;
|
|
7876
8326
|
this.diagnostics.push({
|
|
@@ -7998,13 +8448,13 @@ var TypeAnalyzer = class {
|
|
|
7998
8448
|
type: this.paramType(p, /* @__PURE__ */ new Map()),
|
|
7999
8449
|
optional: p.optional || p.default !== void 0
|
|
8000
8450
|
}));
|
|
8001
|
-
return fn(
|
|
8451
|
+
return this.withRest(fn(
|
|
8002
8452
|
params,
|
|
8003
8453
|
sig.returnType ? this.resolveType(sig.returnType) : sig.predicate ? booleanType : anyType,
|
|
8004
|
-
|
|
8454
|
+
void 0,
|
|
8005
8455
|
names,
|
|
8006
8456
|
this.resolvePredicate(sig.predicate, params)
|
|
8007
|
-
);
|
|
8457
|
+
), sig.params);
|
|
8008
8458
|
}));
|
|
8009
8459
|
}
|
|
8010
8460
|
/** `...rest: T[]` holds every argument from its position on, so its type
|
|
@@ -8012,7 +8462,7 @@ var TypeAnalyzer = class {
|
|
|
8012
8462
|
checkRestType(p, node) {
|
|
8013
8463
|
if (!this.emitDiagnostics || !p.typeAnnotation) return;
|
|
8014
8464
|
const declared = this.resolveType(p.typeAnnotation);
|
|
8015
|
-
if (declared.kind === "array" || declared.kind === "any" || declared.kind === "typeParam") return;
|
|
8465
|
+
if (declared.kind === "array" || declared.kind === "tuple" || declared.kind === "any" || declared.kind === "typeParam") return;
|
|
8016
8466
|
this.diagnostics.push({
|
|
8017
8467
|
node,
|
|
8018
8468
|
message: `A rest parameter holds every argument from its position on, so '${p.name ?? "..."}' is an array: '${formatType(declared)}[]', not '${formatType(declared)}'`
|
|
@@ -8068,13 +8518,13 @@ var TypeAnalyzer = class {
|
|
|
8068
8518
|
return collected.length ? union(collected) : this.inferReturnType(func.body, bodyEnv);
|
|
8069
8519
|
}));
|
|
8070
8520
|
}
|
|
8071
|
-
return fn(
|
|
8521
|
+
return this.withRest(fn(
|
|
8072
8522
|
params,
|
|
8073
8523
|
returns,
|
|
8074
|
-
|
|
8524
|
+
void 0,
|
|
8075
8525
|
names,
|
|
8076
8526
|
this.resolvePredicate(func.predicate, params)
|
|
8077
|
-
);
|
|
8527
|
+
), func.params);
|
|
8078
8528
|
});
|
|
8079
8529
|
}
|
|
8080
8530
|
/** Where the return types of the function being walked are collected, so
|
|
@@ -8116,17 +8566,16 @@ var TypeAnalyzer = class {
|
|
|
8116
8566
|
this.preVisitDepth--;
|
|
8117
8567
|
}
|
|
8118
8568
|
}
|
|
8119
|
-
/** The `[key, value]` pairs
|
|
8120
|
-
*
|
|
8121
|
-
*
|
|
8122
|
-
* iterator function), whose keys have no names to list. */
|
|
8569
|
+
/** The `[key, value]` items `pairs(record)` hands out, one per property.
|
|
8570
|
+
* `undefined` for anything else (an array, a dictionary, an iterator
|
|
8571
|
+
* function), whose keys have no names to list. */
|
|
8123
8572
|
iterationRows(iterNode, iterType) {
|
|
8124
8573
|
let source;
|
|
8125
8574
|
if (iterNode?.type === "CallExpression" && iterNode.callee.type === "Identifier" && iterNode.arguments[0]) {
|
|
8126
|
-
if (iterNode.callee.name !== "pairs"
|
|
8575
|
+
if (iterNode.callee.name !== "pairs") return void 0;
|
|
8127
8576
|
source = this.typeOf.get(iterNode.arguments[0]);
|
|
8128
8577
|
} else {
|
|
8129
|
-
|
|
8578
|
+
return void 0;
|
|
8130
8579
|
}
|
|
8131
8580
|
const t = source && this.expand(source);
|
|
8132
8581
|
if (!t || t.kind !== "object" || t.class || t.indexer || !t.properties.size) return void 0;
|
|
@@ -8267,54 +8716,76 @@ var TypeAnalyzer = class {
|
|
|
8267
8716
|
if (ids.length < 2) return;
|
|
8268
8717
|
this.correlateBindings(env, ids, objects.map((member) => names.map((name) => this.propertyType(member, name))));
|
|
8269
8718
|
}
|
|
8270
|
-
/** `
|
|
8271
|
-
*
|
|
8272
|
-
*
|
|
8273
|
-
|
|
8274
|
-
|
|
8275
|
-
|
|
8276
|
-
|
|
8277
|
-
|
|
8278
|
-
|
|
8279
|
-
|
|
8280
|
-
|
|
8281
|
-
|
|
8719
|
+
/** `[step, state, first]` — what a call such as `pairs(t)` answers — as
|
|
8720
|
+
* the `step` function, or `undefined` for an array that is just an
|
|
8721
|
+
* array. Three places exactly, the first a function. */
|
|
8722
|
+
iterationTriple(t) {
|
|
8723
|
+
if (t.kind !== "tuple" || t.rest || t.elements.length !== 3) return void 0;
|
|
8724
|
+
const step = this.expand(t.elements[0]);
|
|
8725
|
+
return step.kind === "function" ? step : void 0;
|
|
8726
|
+
}
|
|
8727
|
+
/** What one `for (const item in source)` hands over each time, and how it
|
|
8728
|
+
* walks the source. `pairs`/`ipairs` are read from the table they are
|
|
8729
|
+
* given, so a record's keys stay the literals they are. */
|
|
8730
|
+
iterationOf(source, sourceType, report = true) {
|
|
8731
|
+
if (source.type === "CallExpression" && source.callee.type === "Identifier" && source.arguments[0]) {
|
|
8732
|
+
const name = source.callee.name;
|
|
8733
|
+
const src = this.expand(this.typeOf.get(source.arguments[0]) ?? unknownType);
|
|
8734
|
+
const iteration = { walks: "iteration", viaIter: false };
|
|
8735
|
+
if (name === "ipairs") return [tuple([numberType, this.elementType(src, 0)]), iteration];
|
|
8736
|
+
if (name === "pairs") {
|
|
8282
8737
|
if (src.kind === "object") {
|
|
8283
|
-
return [
|
|
8738
|
+
return [tuple([
|
|
8284
8739
|
src.indexer?.key ?? stringType,
|
|
8285
|
-
src.indexer?.value ?? union([...src.properties.values()].map((p) => p.type))
|
|
8286
|
-
|
|
8287
|
-
];
|
|
8740
|
+
src.indexer?.value ?? union([...src.properties.values()].map((p) => p.type))
|
|
8741
|
+
]), iteration];
|
|
8288
8742
|
}
|
|
8289
|
-
if (src.kind === "array") return [numberType, src.element,
|
|
8743
|
+
if (src.kind === "array") return [tuple([numberType, src.element]), iteration];
|
|
8290
8744
|
}
|
|
8291
8745
|
}
|
|
8292
|
-
const
|
|
8293
|
-
|
|
8294
|
-
|
|
8295
|
-
const
|
|
8296
|
-
|
|
8297
|
-
return
|
|
8746
|
+
const handsOut = (t2, viaIter) => {
|
|
8747
|
+
const x = this.expand(t2);
|
|
8748
|
+
if (x.kind === "function") return [withoutNil(this.expand(x.returns)), { walks: "function", viaIter }];
|
|
8749
|
+
const step = this.iterationTriple(x);
|
|
8750
|
+
if (step) return [withoutNil(this.expand(step.returns)), { walks: "iteration", viaIter }];
|
|
8751
|
+
return void 0;
|
|
8752
|
+
};
|
|
8753
|
+
const direct = handsOut(sourceType, false);
|
|
8754
|
+
if (direct) return direct;
|
|
8755
|
+
const values = { walks: "values", viaIter: false };
|
|
8756
|
+
const t = this.expand(sourceType);
|
|
8757
|
+
if (t.kind === "any") return [anyType, values];
|
|
8758
|
+
const iter = t.kind === "object" ? t.properties.get("__iter") : void 0;
|
|
8759
|
+
if (iter) {
|
|
8760
|
+
const made = this.overloadsOf(iter.type)[0]?.returns ?? unknownType;
|
|
8761
|
+
return handsOut(made, true) ?? [unknownType, { walks: "values", viaIter: true }];
|
|
8762
|
+
}
|
|
8763
|
+
if (t.kind === "array") return [t.element, values];
|
|
8764
|
+
if (t.kind === "tuple") return [union([...t.elements, ...t.rest ? [t.rest] : []]), values];
|
|
8765
|
+
if (t.kind === "object") {
|
|
8766
|
+
return [t.indexer?.value ?? union([...t.properties.values()].map((p) => p.type)), values];
|
|
8298
8767
|
}
|
|
8299
|
-
|
|
8300
|
-
|
|
8301
|
-
|
|
8768
|
+
if (t.kind === "union") {
|
|
8769
|
+
const each = t.types.map((member) => this.iterationOf(source, member, false));
|
|
8770
|
+
const [first] = each;
|
|
8771
|
+
if (first && each.every(([, form]) => form.walks === first[1].walks && form.viaIter === first[1].viaIter)) {
|
|
8772
|
+
return [union(each.map(([item]) => item)), first[1]];
|
|
8773
|
+
}
|
|
8302
8774
|
}
|
|
8303
|
-
if (t.kind
|
|
8304
|
-
|
|
8305
|
-
|
|
8306
|
-
|
|
8775
|
+
if (t.kind !== "never" && report && this.emitDiagnostics) {
|
|
8776
|
+
this.diagnostics.push({
|
|
8777
|
+
node: source,
|
|
8778
|
+
message: `Cannot loop over '${formatType(sourceType)}': a loop walks an array, a table, an iterator function or an iteration such as 'pairs(t)'`
|
|
8779
|
+
});
|
|
8307
8780
|
}
|
|
8308
|
-
return [unknownType,
|
|
8781
|
+
return [unknownType, values];
|
|
8309
8782
|
}
|
|
8310
8783
|
inferReturnType(body, env) {
|
|
8311
8784
|
const returns = [];
|
|
8312
8785
|
const walk = (block) => {
|
|
8313
8786
|
for (const s of block.statements) {
|
|
8314
8787
|
if (s.type === "ReturnStatement") {
|
|
8315
|
-
|
|
8316
|
-
else if (s.arguments.length === 1) returns.push(this.infer(s.arguments[0], env));
|
|
8317
|
-
else returns.push(tuple(s.arguments.map((a) => this.infer(a, env)), true));
|
|
8788
|
+
returns.push(s.argument ? this.infer(s.argument, env) : nilType);
|
|
8318
8789
|
} else if (s.type === "IfStatement") {
|
|
8319
8790
|
for (const c of s.clauses) walk(c.body);
|
|
8320
8791
|
if (s.alternate) walk(s.alternate);
|
|
@@ -8336,8 +8807,9 @@ var TypeAnalyzer = class {
|
|
|
8336
8807
|
* `infer` would have widened it to an array type). */
|
|
8337
8808
|
fitsAnnotation(init, declared, inferred, env) {
|
|
8338
8809
|
if (declared.kind === "tuple" && init.type === "ArrayExpression" && !init.elements.some((e) => e.type === "SpreadElement")) {
|
|
8339
|
-
|
|
8340
|
-
|
|
8810
|
+
const fits = declared.rest ? init.elements.length >= declared.elements.length : init.elements.length === declared.elements.length;
|
|
8811
|
+
if (!fits) return false;
|
|
8812
|
+
return init.elements.every((el, i) => isAssignable(widen(this.infer(el, env)), declared.elements[i] ?? declared.rest));
|
|
8341
8813
|
}
|
|
8342
8814
|
if (isAssignable(inferred, declared) || isAssignable(widen(inferred), declared)) return true;
|
|
8343
8815
|
if (init.type === "TableExpression") return isAssignable(this.inferObject(init, env, true), declared);
|
|
@@ -8394,7 +8866,7 @@ var TypeAnalyzer = class {
|
|
|
8394
8866
|
target.elements.forEach((el, i) => {
|
|
8395
8867
|
if (el) this.reassignPattern(el.value, this.withDefault(this.elementType(valueType, i), el.default, env), env);
|
|
8396
8868
|
});
|
|
8397
|
-
if (target.rest) this.reassignPattern(target.rest,
|
|
8869
|
+
if (target.rest) this.reassignPattern(target.rest, this.restAfter(valueType, target.elements.length), env);
|
|
8398
8870
|
return;
|
|
8399
8871
|
}
|
|
8400
8872
|
}
|
|
@@ -8430,7 +8902,7 @@ var TypeAnalyzer = class {
|
|
|
8430
8902
|
if (!el) return;
|
|
8431
8903
|
this.bindPattern(el.value, this.withDefault(this.elementType(valueType, i), el.default, env), env, mode);
|
|
8432
8904
|
});
|
|
8433
|
-
if (target.rest) this.bindPattern(target.rest,
|
|
8905
|
+
if (target.rest) this.bindPattern(target.rest, this.restAfter(valueType, target.elements.length), env, mode);
|
|
8434
8906
|
return;
|
|
8435
8907
|
}
|
|
8436
8908
|
}
|
|
@@ -8587,7 +9059,7 @@ var TypeAnalyzer = class {
|
|
|
8587
9059
|
* not one of them is a mistake worth reporting, rather than the nil Lua
|
|
8588
9060
|
* would hand back. */
|
|
8589
9061
|
checkStringMember(node, object, key) {
|
|
8590
|
-
if (!this.emitDiagnostics) return;
|
|
9062
|
+
if (!this.emitDiagnostics || !this.aliasDefs.has("StringMethods")) return;
|
|
8591
9063
|
const parts = this.stringParts(object);
|
|
8592
9064
|
if (!parts) return;
|
|
8593
9065
|
const keys = (key.kind === "union" ? key.types : [key]).map((m) => this.expand(m));
|
|
@@ -8599,6 +9071,127 @@ var TypeAnalyzer = class {
|
|
|
8599
9071
|
message: names.length === 1 ? `'${names[0]}' does not exist on a string` : `'${briefType(key)}' does not name a member of a string`
|
|
8600
9072
|
});
|
|
8601
9073
|
}
|
|
9074
|
+
/** `game.Anything` on a `{ GetService: ... }`, or `a.a` on a number: a
|
|
9075
|
+
* value whose members are all known, and none of them is this one. Only
|
|
9076
|
+
* a type that is known through and through is reported — an indexer, an
|
|
9077
|
+
* `any`, or a part still waiting on a type parameter could each supply
|
|
9078
|
+
* the name. An empty `{}` is a table still being filled in, so it is left
|
|
9079
|
+
* alone; an array's and a string's members come from a library, so
|
|
9080
|
+
* without one they say nothing. A string on its own is `checkStringMember`'s. */
|
|
9081
|
+
checkMissingMember(node, object, name) {
|
|
9082
|
+
if (!this.emitDiagnostics || this.missingMemberReported.has(node)) return;
|
|
9083
|
+
if (!this.memberMissing(object, name)) return;
|
|
9084
|
+
this.missingMemberReported.add(node);
|
|
9085
|
+
this.diagnostics.push({
|
|
9086
|
+
node,
|
|
9087
|
+
message: `Property '${name}' does not exist on type '${briefType(withoutNil(this.expand(object)))}'`
|
|
9088
|
+
});
|
|
9089
|
+
}
|
|
9090
|
+
/** Is `name` certainly not a member of `object`? See `checkMissingMember`. */
|
|
9091
|
+
memberMissing(object, name) {
|
|
9092
|
+
if (this.stringParts(object)) return false;
|
|
9093
|
+
const raw = this.expand(object);
|
|
9094
|
+
const parts = (raw.kind === "union" ? raw.types : [raw]).map((m) => this.deferredAccess(this.expand(m)));
|
|
9095
|
+
let missing = false;
|
|
9096
|
+
for (const part of parts) {
|
|
9097
|
+
switch (part.kind) {
|
|
9098
|
+
case "primitive":
|
|
9099
|
+
if (part.name === "nil") continue;
|
|
9100
|
+
if (part.name === "string" && !this.aliasDefs.has("StringMethods")) return false;
|
|
9101
|
+
if (part.name === "string" && this.builtInMethod(part, name)) continue;
|
|
9102
|
+
break;
|
|
9103
|
+
case "literal":
|
|
9104
|
+
case "templateLiteral":
|
|
9105
|
+
if (part.kind === "templateLiteral" || part.base === "string") {
|
|
9106
|
+
if (!this.aliasDefs.has("StringMethods")) return false;
|
|
9107
|
+
}
|
|
9108
|
+
if (this.builtInMethod(part, name)) continue;
|
|
9109
|
+
break;
|
|
9110
|
+
case "array":
|
|
9111
|
+
case "tuple":
|
|
9112
|
+
if (!this.aliasDefs.has("ArrayMethods")) return false;
|
|
9113
|
+
if (this.builtInMethod(part, name)) continue;
|
|
9114
|
+
break;
|
|
9115
|
+
case "object":
|
|
9116
|
+
if (part.properties.has(name)) continue;
|
|
9117
|
+
if (part.indexer || part.properties.size === 0 && !part.class || this.builtInMethod(part, name)) return false;
|
|
9118
|
+
break;
|
|
9119
|
+
default:
|
|
9120
|
+
return false;
|
|
9121
|
+
}
|
|
9122
|
+
missing = true;
|
|
9123
|
+
}
|
|
9124
|
+
return missing;
|
|
9125
|
+
}
|
|
9126
|
+
/** `t[k]` read with a `k` that cannot be a key of `t`: an `unknown`, a
|
|
9127
|
+
* type none of `t`'s keys has (a number into a record, a string into an
|
|
9128
|
+
* array or a class), or a name `t` does not have — which is `t.name`'s
|
|
9129
|
+
* error. An `any` on either side says nothing, and neither does a type
|
|
9130
|
+
* parameter still to be decided. A plain `string` into a record is
|
|
9131
|
+
* allowed: it reads as one of the values or nil, and the nil has to be
|
|
9132
|
+
* dealt with. An empty `{}` is a table still being filled in, and is
|
|
9133
|
+
* left alone. Reports, and answers whether it did. */
|
|
9134
|
+
checkIndexKey(node, object, key) {
|
|
9135
|
+
if (!this.emitDiagnostics || this.badIndexReported.has(node)) return false;
|
|
9136
|
+
const index = this.expand(key);
|
|
9137
|
+
if (index.kind === "any" || index.kind === "never" || containsTypeParam(index)) return false;
|
|
9138
|
+
const keys = index.kind === "union" ? index.types.map((k) => this.expand(k)) : [index];
|
|
9139
|
+
const nameOf = (k) => k.kind === "literal" && typeof k.value === "string" ? k.value : void 0;
|
|
9140
|
+
const shown = briefType(withoutNil(this.expand(object)));
|
|
9141
|
+
const names = keys.map(nameOf);
|
|
9142
|
+
const others = keys.filter((_, i) => names[i] === void 0);
|
|
9143
|
+
let message;
|
|
9144
|
+
const listOnly = (t) => {
|
|
9145
|
+
const x = this.expand(t);
|
|
9146
|
+
if (x.kind === "union") return x.types.every(listOnly);
|
|
9147
|
+
return x.kind === "array" || x.kind === "tuple";
|
|
9148
|
+
};
|
|
9149
|
+
if (names.some((name) => name !== void 0) && listOnly(withoutNil(this.expand(object)))) {
|
|
9150
|
+
message = `Type '${briefType(index)}' cannot be used to index type '${shown}'`;
|
|
9151
|
+
} else if (others.length && this.cannotIndex(object, others)) {
|
|
9152
|
+
message = `Type '${briefType(index)}' cannot be used to index type '${shown}'`;
|
|
9153
|
+
} else if (!others.length && names.every((name) => this.memberMissing(object, name))) {
|
|
9154
|
+
message = keys.length === 1 ? `Property '${names[0]}' does not exist on type '${shown}'` : `None of ${briefType(index)} is a property of type '${shown}'`;
|
|
9155
|
+
}
|
|
9156
|
+
if (!message) return false;
|
|
9157
|
+
this.badIndexReported.add(node);
|
|
9158
|
+
this.diagnostics.push({ node, message });
|
|
9159
|
+
return true;
|
|
9160
|
+
}
|
|
9161
|
+
/** Can one of `keys` (string literals aside) not index some part of
|
|
9162
|
+
* `object`? `false` as soon as a part cannot be judged. */
|
|
9163
|
+
cannotIndex(object, keys) {
|
|
9164
|
+
const raw = this.expand(object);
|
|
9165
|
+
const parts = (raw.kind === "union" ? raw.types : [raw]).map((m) => this.deferredAccess(this.expand(m)));
|
|
9166
|
+
const isNumber = (k) => k.kind === "primitive" && k.name === "number" || k.kind === "literal" && typeof k.value === "number";
|
|
9167
|
+
const isString = (k) => k.kind === "primitive" && k.name === "string" || k.kind === "templateLiteral";
|
|
9168
|
+
let bad = false;
|
|
9169
|
+
for (const part of parts) {
|
|
9170
|
+
switch (part.kind) {
|
|
9171
|
+
case "primitive":
|
|
9172
|
+
if (part.name === "nil") continue;
|
|
9173
|
+
return false;
|
|
9174
|
+
case "array":
|
|
9175
|
+
case "tuple":
|
|
9176
|
+
if (!keys.every(isNumber)) bad = true;
|
|
9177
|
+
continue;
|
|
9178
|
+
case "object": {
|
|
9179
|
+
if (!part.indexer && part.properties.size === 0 && !part.class) return false;
|
|
9180
|
+
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));
|
|
9181
|
+
if (!keys.every(fits)) bad = true;
|
|
9182
|
+
continue;
|
|
9183
|
+
}
|
|
9184
|
+
default:
|
|
9185
|
+
return false;
|
|
9186
|
+
}
|
|
9187
|
+
}
|
|
9188
|
+
return bad;
|
|
9189
|
+
}
|
|
9190
|
+
/** Indexes already reported: a loop body is visited more than once. */
|
|
9191
|
+
badIndexReported = /* @__PURE__ */ new WeakSet();
|
|
9192
|
+
/** Members already reported as missing: a loop body, or an assignment
|
|
9193
|
+
* target, is visited more than once. */
|
|
9194
|
+
missingMemberReported = /* @__PURE__ */ new WeakSet();
|
|
8602
9195
|
/** A `private` member read from outside the class that declared it —
|
|
8603
9196
|
* a subclass counts as outside, as in TypeScript. */
|
|
8604
9197
|
checkPrivateMember(node, object, name) {
|
|
@@ -8609,18 +9202,46 @@ var TypeAnalyzer = class {
|
|
|
8609
9202
|
if (t.kind !== "object") continue;
|
|
8610
9203
|
const access = t.properties.get(name)?.private;
|
|
8611
9204
|
if (!access || this.insideClass(access.owner)) continue;
|
|
9205
|
+
if (access.protected && this.insideSubclassOf(access.owner)) continue;
|
|
8612
9206
|
this.diagnostics.push({
|
|
8613
9207
|
node,
|
|
8614
|
-
message: `Property '${name}' is private and only accessible within class '${access.className}'`
|
|
9208
|
+
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}'`
|
|
8615
9209
|
});
|
|
8616
9210
|
return;
|
|
8617
9211
|
}
|
|
8618
9212
|
}
|
|
9213
|
+
/** `Shape.new` on an abstract class, and `super.area` on an abstract
|
|
9214
|
+
* method: each reaches something with nothing behind it. */
|
|
9215
|
+
checkAbstractAccess(expr, object) {
|
|
9216
|
+
if (!this.emitDiagnostics) return;
|
|
9217
|
+
const t = this.expand(object);
|
|
9218
|
+
if (t.kind !== "object") return;
|
|
9219
|
+
const name = expr.property.name;
|
|
9220
|
+
const property = t.properties.get(name);
|
|
9221
|
+
if (!property?.abstract) return;
|
|
9222
|
+
if (name === "new" && expr.object.type !== "SuperExpression") {
|
|
9223
|
+
const label = expressionLabel(expr.object) ?? formatType(t);
|
|
9224
|
+
this.diagnostics.push({
|
|
9225
|
+
node: expr,
|
|
9226
|
+
message: `'${label}' is an abstract class: build one of the classes extending it instead`
|
|
9227
|
+
});
|
|
9228
|
+
} else if (expr.object.type === "SuperExpression") {
|
|
9229
|
+
this.diagnostics.push({
|
|
9230
|
+
node: expr.property,
|
|
9231
|
+
message: `'${name}' is abstract in the class this one extends: there is nothing to call`
|
|
9232
|
+
});
|
|
9233
|
+
}
|
|
9234
|
+
}
|
|
8619
9235
|
/** Is the code being checked written inside `owner`'s body — its own
|
|
8620
9236
|
* methods, or a class or function nested in them? */
|
|
8621
9237
|
insideClass(owner) {
|
|
8622
9238
|
return this.enclosingClasses.includes(owner);
|
|
8623
9239
|
}
|
|
9240
|
+
/** Is the code being checked inside a class that extends `owner`? */
|
|
9241
|
+
insideSubclassOf(owner) {
|
|
9242
|
+
const identity = this.classIdentity(owner);
|
|
9243
|
+
return this.enclosingClasses.some((cls) => this.instanceType(cls).class?.ancestors.includes(identity));
|
|
9244
|
+
}
|
|
8624
9245
|
propertyType(raw, name) {
|
|
8625
9246
|
const t = this.deferredAccess(this.expand(raw));
|
|
8626
9247
|
if (t.kind === "object") {
|
|
@@ -8675,8 +9296,12 @@ var TypeAnalyzer = class {
|
|
|
8675
9296
|
}
|
|
8676
9297
|
if (containsTypeParam(index)) return this.reduceType({ kind: "indexedAccess", objectType: t, indexType: index });
|
|
8677
9298
|
if (t.indexer) return t.indexer.value;
|
|
9299
|
+
const anyString = index.kind === "primitive" && index.name === "string" || index.kind === "templateLiteral";
|
|
9300
|
+
if (anyString && !t.class && t.properties.size > 0) {
|
|
9301
|
+
return union([...[...t.properties.values()].map((p) => p.type), nilType]);
|
|
9302
|
+
}
|
|
8678
9303
|
}
|
|
8679
|
-
return unknownType;
|
|
9304
|
+
return index.kind === "any" ? anyType : unknownType;
|
|
8680
9305
|
}
|
|
8681
9306
|
/** What a deferred `T[K]` can be: every property its index could name.
|
|
8682
9307
|
* Reading a member of one, or calling it, sees that. */
|
|
@@ -8686,10 +9311,21 @@ var TypeAnalyzer = class {
|
|
|
8686
9311
|
if (containsTypeParam(index)) return unknownType;
|
|
8687
9312
|
return this.accessType(t.objectType, index);
|
|
8688
9313
|
}
|
|
9314
|
+
/** `[a, ...rest]`: what `rest` holds — an array of whatever comes after
|
|
9315
|
+
* the first `from` places. */
|
|
9316
|
+
restAfter(raw, from) {
|
|
9317
|
+
const t = this.expand(raw);
|
|
9318
|
+
if (t.kind === "tuple") {
|
|
9319
|
+
const left = [...t.elements.slice(from), ...t.rest ? [t.rest] : []];
|
|
9320
|
+
return arrayOf(left.length ? union(left) : neverType);
|
|
9321
|
+
}
|
|
9322
|
+
if (t.kind === "union") return union(t.types.map((m) => this.restAfter(m, from)));
|
|
9323
|
+
return arrayOf(this.elementType(t, from));
|
|
9324
|
+
}
|
|
8689
9325
|
elementType(raw, index) {
|
|
8690
9326
|
const t = this.expand(raw);
|
|
8691
9327
|
if (t.kind === "array") return t.element;
|
|
8692
|
-
if (t.kind === "tuple") return t.elements[index] ?? unknownType;
|
|
9328
|
+
if (t.kind === "tuple") return t.elements[index] ?? t.rest ?? unknownType;
|
|
8693
9329
|
if (t.kind === "union") return union(t.types.map((m) => this.elementType(m, index)));
|
|
8694
9330
|
if (t.kind === "difference") return this.elementType(t.base, index);
|
|
8695
9331
|
if (t.kind === "typeParam" && t.constraint) return this.elementType(t.constraint, index);
|
|
@@ -8719,8 +9355,6 @@ var TypeAnalyzer = class {
|
|
|
8719
9355
|
return stringType;
|
|
8720
9356
|
}
|
|
8721
9357
|
// `...` holds what the function declared it takes.
|
|
8722
|
-
case "VarargExpression":
|
|
8723
|
-
return this.varargs[this.varargs.length - 1] ?? anyType;
|
|
8724
9358
|
// `f(a, ...rest)` — every value the array holds, one after
|
|
8725
9359
|
// another. Each of them is an element, so that is what the
|
|
8726
9360
|
// parameters it fills are checked against.
|
|
@@ -8753,10 +9387,8 @@ var TypeAnalyzer = class {
|
|
|
8753
9387
|
this.checkParamOrder(expr.func.params, expr);
|
|
8754
9388
|
this.visitFunctionBody(expr.func, env);
|
|
8755
9389
|
return this.inferFunctionBody(expr.func, env);
|
|
8756
|
-
case "ParenthesizedExpression":
|
|
8757
|
-
|
|
8758
|
-
return inner.kind === "tuple" && inner.isPack && producesMultipleValues(expr.expression) ? inner.elements[0] ?? nilType : inner;
|
|
8759
|
-
}
|
|
9390
|
+
case "ParenthesizedExpression":
|
|
9391
|
+
return this.infer(expr.expression, env);
|
|
8760
9392
|
case "TypeAssertionExpression": {
|
|
8761
9393
|
this.infer(expr.expression, env);
|
|
8762
9394
|
return this.resolveType(expr.typeAnnotation);
|
|
@@ -8788,10 +9420,18 @@ var TypeAnalyzer = class {
|
|
|
8788
9420
|
switch (expr.operator) {
|
|
8789
9421
|
case "not":
|
|
8790
9422
|
return booleanType;
|
|
8791
|
-
case "-":
|
|
8792
|
-
|
|
8793
|
-
|
|
8794
|
-
|
|
9423
|
+
case "-": {
|
|
9424
|
+
const answered = this.operatorResult(expr, "-", arg, void 0);
|
|
9425
|
+
if (answered) return answered;
|
|
9426
|
+
this.checkOperands(expr, "-", arg);
|
|
9427
|
+
return this.arithmeticOn([arg]);
|
|
9428
|
+
}
|
|
9429
|
+
case "#": {
|
|
9430
|
+
const answered = this.operatorResult(expr, "#", arg, void 0);
|
|
9431
|
+
if (answered) return answered;
|
|
9432
|
+
this.checkOperands(expr, "#", arg);
|
|
9433
|
+
return numberType;
|
|
9434
|
+
}
|
|
8795
9435
|
}
|
|
8796
9436
|
return arg;
|
|
8797
9437
|
}
|
|
@@ -8807,7 +9447,11 @@ var TypeAnalyzer = class {
|
|
|
8807
9447
|
const left = this.infer(expr.left, env);
|
|
8808
9448
|
const { whenFalse } = this.narrowFromCondition(expr.left, env);
|
|
8809
9449
|
const right = this.infer(expr.right, whenFalse);
|
|
8810
|
-
|
|
9450
|
+
const fallback = unwrapParens(expr.right);
|
|
9451
|
+
const empty = fallback.type === "TableExpression" && fallback.fields.length === 0 || fallback.type === "ArrayExpression" && fallback.elements.length === 0;
|
|
9452
|
+
const truthy = narrowTruthy(left);
|
|
9453
|
+
if (empty && truthy.kind !== "never" && truthy.kind !== "any" && isAssignable(right, truthy)) return truthy;
|
|
9454
|
+
return union([truthy, right]);
|
|
8811
9455
|
}
|
|
8812
9456
|
const l = this.infer(expr.left, env);
|
|
8813
9457
|
const r = this.infer(expr.right, env);
|
|
@@ -8816,14 +9460,21 @@ var TypeAnalyzer = class {
|
|
|
8816
9460
|
if (unwrapParens(expr.left).type === "StringLiteral") this.expectedTypeOf.set(unwrapParens(expr.left), r);
|
|
8817
9461
|
}
|
|
8818
9462
|
switch (op) {
|
|
8819
|
-
case "..":
|
|
8820
|
-
|
|
8821
|
-
|
|
8822
|
-
|
|
9463
|
+
case "..": {
|
|
9464
|
+
const answered = this.operatorResult(expr, op, l, r);
|
|
9465
|
+
if (answered) return answered;
|
|
9466
|
+
this.checkOperands(expr, op, l, r);
|
|
9467
|
+
return stringType;
|
|
9468
|
+
}
|
|
8823
9469
|
case "<":
|
|
8824
9470
|
case ">":
|
|
8825
9471
|
case "<=":
|
|
8826
9472
|
case ">=":
|
|
9473
|
+
if (!this.checkComparison(expr, op, l, r)) this.checkOperands(expr, op, l, r);
|
|
9474
|
+
return booleanType;
|
|
9475
|
+
case "==":
|
|
9476
|
+
case "~=":
|
|
9477
|
+
this.checkOverlap(expr, op, l, r);
|
|
8827
9478
|
return booleanType;
|
|
8828
9479
|
case "+":
|
|
8829
9480
|
case "-":
|
|
@@ -8831,17 +9482,23 @@ var TypeAnalyzer = class {
|
|
|
8831
9482
|
case "/":
|
|
8832
9483
|
case "//":
|
|
8833
9484
|
case "%":
|
|
8834
|
-
case "^":
|
|
8835
|
-
|
|
9485
|
+
case "^": {
|
|
9486
|
+
const answered = this.operatorResult(expr, op, l, r);
|
|
9487
|
+
if (answered) return answered;
|
|
9488
|
+
this.checkOperands(expr, op, l, r);
|
|
9489
|
+
return this.arithmeticOn([l, r]);
|
|
9490
|
+
}
|
|
8836
9491
|
}
|
|
8837
9492
|
return union([l, r]);
|
|
8838
9493
|
}
|
|
8839
9494
|
case "MemberExpression": {
|
|
8840
9495
|
const { type: obj, shortCircuits } = this.chainObject(expr, expr.object, env);
|
|
8841
9496
|
this.checkPrivateMember(expr.property, obj, expr.property.name);
|
|
9497
|
+
this.checkAbstractAccess(expr, obj);
|
|
8842
9498
|
const key = this.refKeyOf(expr);
|
|
8843
9499
|
const narrowed = key === void 0 ? void 0 : env.get(key);
|
|
8844
9500
|
this.checkStringMember(expr, obj, literal(expr.property.name));
|
|
9501
|
+
if (narrowed === void 0) this.checkMissingMember(expr.property, obj, expr.property.name);
|
|
8845
9502
|
return this.chainResult(expr, narrowed ?? this.propertyType(obj, expr.property.name), shortCircuits);
|
|
8846
9503
|
}
|
|
8847
9504
|
case "IndexExpression": {
|
|
@@ -8850,6 +9507,7 @@ var TypeAnalyzer = class {
|
|
|
8850
9507
|
const key = this.refKeyOf(expr);
|
|
8851
9508
|
const narrowed = key === void 0 ? void 0 : env.get(key);
|
|
8852
9509
|
this.checkStringMember(expr, obj, this.expand(idx));
|
|
9510
|
+
if (this.checkIndexKey(expr, obj, idx)) return this.chainResult(expr, anyType, shortCircuits);
|
|
8853
9511
|
const member = this.stringMember(obj, this.expand(idx));
|
|
8854
9512
|
if (member) return this.chainResult(expr, member, shortCircuits);
|
|
8855
9513
|
return this.chainResult(expr, narrowed ?? this.indexedType(obj, idx), shortCircuits);
|
|
@@ -8859,8 +9517,6 @@ var TypeAnalyzer = class {
|
|
|
8859
9517
|
const { type: callee, shortCircuits } = this.chainObject(expr, expr.callee, env);
|
|
8860
9518
|
return this.chainResult(expr, this.inferCall(expr, callee, env), shortCircuits);
|
|
8861
9519
|
}
|
|
8862
|
-
case "NewExpression":
|
|
8863
|
-
return this.inferNew(expr, env);
|
|
8864
9520
|
case "ClassExpression":
|
|
8865
9521
|
return this.visitClass(expr, env);
|
|
8866
9522
|
case "SuperExpression": {
|
|
@@ -8897,18 +9553,14 @@ var TypeAnalyzer = class {
|
|
|
8897
9553
|
/** `new Name(args)` is `Name.new(args)` — the same function, and the
|
|
8898
9554
|
* same check. Saying so here rather than rewriting the tree keeps the
|
|
8899
9555
|
* error messages pointing at what was written. */
|
|
8900
|
-
|
|
8901
|
-
|
|
8902
|
-
const
|
|
8903
|
-
if (
|
|
8904
|
-
|
|
8905
|
-
|
|
8906
|
-
|
|
8907
|
-
|
|
8908
|
-
for (const argument of expr.arguments) this.infer(argument, env);
|
|
8909
|
-
return anyType;
|
|
8910
|
-
}
|
|
8911
|
-
return this.inferCall(expr, constructor, env);
|
|
9556
|
+
/** `__call` of a value with one, as what calling the value calls. */
|
|
9557
|
+
callMetamethod(raw) {
|
|
9558
|
+
const t = this.expand(raw);
|
|
9559
|
+
if (t.kind !== "object" || this.overloadsOf(t).length) return void 0;
|
|
9560
|
+
const method = t.properties.get("__call");
|
|
9561
|
+
if (!method) return void 0;
|
|
9562
|
+
const bound = this.overloadsOf(method.type).map((f) => this.takesSelf(f) ? fn(f.params.slice(1), f.returns, f.varargs, f.typeParams) : f);
|
|
9563
|
+
return bound.length ? intersection(bound) : void 0;
|
|
8912
9564
|
}
|
|
8913
9565
|
/** `super(...)` — the base constructor, run on the instance being built. */
|
|
8914
9566
|
inferSuperCall(expr, env) {
|
|
@@ -8932,8 +9584,9 @@ var TypeAnalyzer = class {
|
|
|
8932
9584
|
this.inferCall(expr, callable, env);
|
|
8933
9585
|
return nilType;
|
|
8934
9586
|
}
|
|
8935
|
-
inferCall(expr,
|
|
9587
|
+
inferCall(expr, called, env) {
|
|
8936
9588
|
this.checkAmbiguousCall(expr);
|
|
9589
|
+
const callee = this.callMetamethod(called) ?? called;
|
|
8937
9590
|
const united = this.unionSignatures(callee);
|
|
8938
9591
|
const fns = united ?? this.overloadsOf(callee);
|
|
8939
9592
|
const explicit = this.explicitTypeArguments(expr, fns);
|
|
@@ -8956,8 +9609,31 @@ var TypeAnalyzer = class {
|
|
|
8956
9609
|
if (arityFits && !picked) this.reportArguments(expr, expr.arguments, fns, () => argTypes, () => 0);
|
|
8957
9610
|
return union(fns.map((f) => this.callReturn(f, argTypes, explicit)));
|
|
8958
9611
|
}
|
|
9612
|
+
this.checkCallable(expr.callee, callee);
|
|
8959
9613
|
return callee.kind === "any" ? anyType : unknownType;
|
|
8960
9614
|
}
|
|
9615
|
+
/** `a()` where `a` is `1`: a value whose type is known and has no call
|
|
9616
|
+
* signature. A union is reported when any member cannot be called, but
|
|
9617
|
+
* only when every member is decided — `unknown`, `any` and anything still
|
|
9618
|
+
* generic say too little. */
|
|
9619
|
+
checkCallable(node, callee) {
|
|
9620
|
+
if (!this.emitDiagnostics || this.notCallableReported.has(node)) return;
|
|
9621
|
+
const raw = this.expand(callee);
|
|
9622
|
+
const parts = (raw.kind === "union" ? raw.types : [raw]).map((m) => this.expand(m));
|
|
9623
|
+
const notCallable = (m) => m.kind === "primitive" && m.name !== "nil" || m.kind === "literal" || m.kind === "templateLiteral" || m.kind === "array" || m.kind === "tuple" || m.kind === "object";
|
|
9624
|
+
const callable = (m) => this.overloadsOf(m).length > 0;
|
|
9625
|
+
const concrete = parts.filter((m) => !(m.kind === "primitive" && m.name === "nil"));
|
|
9626
|
+
if (!concrete.some(notCallable) || !concrete.every((m) => notCallable(m) || callable(m))) return;
|
|
9627
|
+
this.notCallableReported.add(node);
|
|
9628
|
+
const label = node.type === "Identifier" ? node.name : expressionLabel(node);
|
|
9629
|
+
this.diagnostics.push({
|
|
9630
|
+
node,
|
|
9631
|
+
message: `This expression is not callable: ${label === void 0 ? "" : `'${label}' `}is of type '${briefType(withoutNil(raw))}'`
|
|
9632
|
+
});
|
|
9633
|
+
}
|
|
9634
|
+
/** Callees already reported as not callable: a loop body is visited more
|
|
9635
|
+
* than once. */
|
|
9636
|
+
notCallableReported = /* @__PURE__ */ new WeakSet();
|
|
8961
9637
|
/** A `(` on a line of its own continues the statement above it:
|
|
8962
9638
|
*
|
|
8963
9639
|
* const value = map[key]
|
|
@@ -8975,6 +9651,8 @@ var TypeAnalyzer = class {
|
|
|
8975
9651
|
}
|
|
8976
9652
|
inferMethodCall(expr, objType, env) {
|
|
8977
9653
|
this.checkPrivateMember(expr.method, objType, expr.method.name);
|
|
9654
|
+
this.checkStringMember(expr.method, objType, literal(expr.method.name));
|
|
9655
|
+
this.checkMissingMember(expr.method, objType, expr.method.name);
|
|
8978
9656
|
const method = this.propertyType(objType, expr.method.name);
|
|
8979
9657
|
const united = this.unionSignatures(method);
|
|
8980
9658
|
const fns = united ?? this.overloadsOf(method);
|
|
@@ -9008,6 +9686,7 @@ var TypeAnalyzer = class {
|
|
|
9008
9686
|
if (arityFits && !picked) this.reportArguments(expr, expr.arguments, fns, withSelf, selfOf);
|
|
9009
9687
|
return union(fns.map((f) => this.callReturn(f, withSelf(f), explicit)));
|
|
9010
9688
|
}
|
|
9689
|
+
this.checkCallable(expr.method, method);
|
|
9011
9690
|
return objType.kind === "any" ? anyType : unknownType;
|
|
9012
9691
|
}
|
|
9013
9692
|
// --------------------------------------------------------
|
|
@@ -9056,6 +9735,14 @@ var TypeAnalyzer = class {
|
|
|
9056
9735
|
name = target.property.name;
|
|
9057
9736
|
object = target.object;
|
|
9058
9737
|
} else if (target.type === "IndexExpression") {
|
|
9738
|
+
const list = this.expand(this.infer(target.object, env));
|
|
9739
|
+
if ((list.kind === "array" || list.kind === "tuple") && list.readonly) {
|
|
9740
|
+
this.diagnostics.push({
|
|
9741
|
+
node: target,
|
|
9742
|
+
message: `Cannot assign to an element of '${formatType(list)}': it is read-only`
|
|
9743
|
+
});
|
|
9744
|
+
return;
|
|
9745
|
+
}
|
|
9059
9746
|
const index = this.expand(this.infer(target.index, env));
|
|
9060
9747
|
if (index.kind !== "literal" || typeof index.value !== "string") return;
|
|
9061
9748
|
name = index.value;
|
|
@@ -9068,6 +9755,8 @@ var TypeAnalyzer = class {
|
|
|
9068
9755
|
if (id !== void 0 && this.scopes.bindings.get(id)?.declaredBy === "namespace") return;
|
|
9069
9756
|
}
|
|
9070
9757
|
if (!this.isReadonlyProperty(this.expand(this.infer(object, env)), name)) return;
|
|
9758
|
+
const constructing = this.constructing;
|
|
9759
|
+
if (constructing && object.type === "Identifier" && object.name === "this" && constructing.members.some((m) => m.type === "ClassField" && !m.isStatic && m.isReadonly && m.name.name === name)) return;
|
|
9071
9760
|
this.diagnostics.push({
|
|
9072
9761
|
node: target,
|
|
9073
9762
|
message: `Cannot assign to '${name}' because it is a read-only property`
|
|
@@ -9127,6 +9816,8 @@ var TypeAnalyzer = class {
|
|
|
9127
9816
|
inferArray(expr, env, asConst) {
|
|
9128
9817
|
const contextual = this.contextualArrays.get(expr);
|
|
9129
9818
|
if (contextual && !asConst) return contextual;
|
|
9819
|
+
const wantedTuple = this.tupleArrays.get(expr);
|
|
9820
|
+
if (wantedTuple && !asConst) return this.inferTupleLiteral(expr, env, wantedTuple);
|
|
9130
9821
|
const elems = [];
|
|
9131
9822
|
let hadSpread = false;
|
|
9132
9823
|
for (const el of expr.elements) {
|
|
@@ -9146,6 +9837,28 @@ var TypeAnalyzer = class {
|
|
|
9146
9837
|
return asConst || !element || element.type === "SpreadElement" ? t : this.widenUnlessAsked(t, element);
|
|
9147
9838
|
})) : unknownType);
|
|
9148
9839
|
}
|
|
9840
|
+
/** `[a, b, ...more]` where a tuple is wanted: the places it names are the
|
|
9841
|
+
* tuple's, and what comes after them — more elements, or a spread — is
|
|
9842
|
+
* its rest. */
|
|
9843
|
+
inferTupleLiteral(expr, env, wanted) {
|
|
9844
|
+
const fixed = [];
|
|
9845
|
+
const tail = [];
|
|
9846
|
+
let spread = false;
|
|
9847
|
+
expr.elements.forEach((element, i) => {
|
|
9848
|
+
if (element.type === "SpreadElement") {
|
|
9849
|
+
spread = true;
|
|
9850
|
+
const held = this.expand(this.infer(element.argument, env));
|
|
9851
|
+
if (held.kind === "array") tail.push(held.element);
|
|
9852
|
+
else if (held.kind === "tuple") tail.push(...tupleMembers(held));
|
|
9853
|
+
else tail.push(unknownType);
|
|
9854
|
+
return;
|
|
9855
|
+
}
|
|
9856
|
+
const t = this.widenUnlessAsked(this.infer(element, env), element);
|
|
9857
|
+
if (!spread && i < wanted.elements.length) fixed.push(t);
|
|
9858
|
+
else tail.push(t);
|
|
9859
|
+
});
|
|
9860
|
+
return tuple(fixed, tail.length ? union(tail) : void 0);
|
|
9861
|
+
}
|
|
9149
9862
|
/** A literal written inside a fresh table or array widens — `{ n = 1 }` is
|
|
9150
9863
|
* `{ n: number }` — unless the surroundings said a literal belongs there.
|
|
9151
9864
|
* `request({ Method: "GET" })` keeps `"GET"` when `Method` is a union of
|
|
@@ -9202,9 +9915,27 @@ var TypeAnalyzer = class {
|
|
|
9202
9915
|
if (s.indexer) indexer = mergeIndexer(indexer, s.indexer);
|
|
9203
9916
|
}
|
|
9204
9917
|
}
|
|
9918
|
+
this.checkDuplicateKey(expr, field, entries);
|
|
9205
9919
|
}
|
|
9206
9920
|
return objectType(entries, indexer, asConst || void 0);
|
|
9207
9921
|
}
|
|
9922
|
+
/** `{ a: 1, a: 2 }` — the second wins and the first was written for
|
|
9923
|
+
* nothing, which is a typo far more often than a decision. A spread is
|
|
9924
|
+
* not one: overriding what it brought is exactly what it is for. */
|
|
9925
|
+
checkDuplicateKey(expr, field, entries) {
|
|
9926
|
+
if (!this.emitDiagnostics || this.duplicateKeyReported.has(field)) return;
|
|
9927
|
+
const written = field.type === "TableFieldNamed" ? field.key.type === "Identifier" ? field.key.name : field.key.value : field.type === "TableFieldShorthand" ? field.name.name : void 0;
|
|
9928
|
+
if (written === void 0) return;
|
|
9929
|
+
const spreadBefore = expr.fields.slice(0, expr.fields.indexOf(field)).some((f) => f.type === "TableFieldSpread");
|
|
9930
|
+
if (spreadBefore) return;
|
|
9931
|
+
if (entries.filter(([k]) => k === written).length < 2) return;
|
|
9932
|
+
this.duplicateKeyReported.add(field);
|
|
9933
|
+
this.diagnostics.push({
|
|
9934
|
+
node: field.type === "TableFieldNamed" ? field.key : field.name,
|
|
9935
|
+
message: `'${written}' is given twice in this table; only the last one is kept`
|
|
9936
|
+
});
|
|
9937
|
+
}
|
|
9938
|
+
duplicateKeyReported = /* @__PURE__ */ new WeakSet();
|
|
9208
9939
|
/** A value inferred `as const`, widened back wherever `context` does not
|
|
9209
9940
|
* ask for a literal: `satisfies`' result type. A property keeps `"circle"`
|
|
9210
9941
|
* when the contract's property admits string literals, and becomes
|
|
@@ -9231,7 +9962,7 @@ var TypeAnalyzer = class {
|
|
|
9231
9962
|
case "tuple": {
|
|
9232
9963
|
const tupleContext = ctx && this.membersOf(ctx).find((m) => m.kind === "tuple");
|
|
9233
9964
|
if (tupleContext?.kind === "tuple") {
|
|
9234
|
-
return tuple(value.elements.map((e, i) => this.keepContextualLiterals(e, tupleContext.elements[i])), value.
|
|
9965
|
+
return tuple(value.elements.map((e, i) => this.keepContextualLiterals(e, tupleContext.elements[i])), value.rest);
|
|
9235
9966
|
}
|
|
9236
9967
|
const arrayContext = ctx && this.membersOf(ctx).find((m) => m.kind === "array");
|
|
9237
9968
|
const element = arrayContext?.kind === "array" ? arrayContext.element : void 0;
|
|
@@ -9488,7 +10219,8 @@ var TypeAnalyzer = class {
|
|
|
9488
10219
|
for (const f of candidates) {
|
|
9489
10220
|
if (!f.predicate) continue;
|
|
9490
10221
|
const arg = args[f.predicate.param];
|
|
9491
|
-
|
|
10222
|
+
const onCondition = f.predicate.asserts && !f.predicate.type;
|
|
10223
|
+
if (!arg || this.refKeyOf(arg) === void 0 && !onCondition) continue;
|
|
9492
10224
|
if (f.typeParams?.length && f.predicate.type) {
|
|
9493
10225
|
const subst = this.inferTypeArgs(f, argTypes);
|
|
9494
10226
|
return {
|
|
@@ -9509,6 +10241,10 @@ var TypeAnalyzer = class {
|
|
|
9509
10241
|
const found = this.predicateCallTarget(call, env);
|
|
9510
10242
|
if (!found || !found.predicate.asserts) return;
|
|
9511
10243
|
const filter = found.predicate.type;
|
|
10244
|
+
if (this.refKeyOf(found.arg) === void 0) {
|
|
10245
|
+
if (!filter) this.applyNarrowing(found.arg, env, env, forkEnv(env));
|
|
10246
|
+
return;
|
|
10247
|
+
}
|
|
9512
10248
|
this.narrowRef(found.arg, env, env, forkEnv(env), (cur) => filter ? { yes: narrowTo(cur, filter), no: narrowExclude(cur, filter) } : { yes: narrowTruthy(cur), no: narrowFalsy(cur) });
|
|
9513
10249
|
}
|
|
9514
10250
|
// --------------------------------------------------------
|
|
@@ -9533,6 +10269,10 @@ var TypeAnalyzer = class {
|
|
|
9533
10269
|
if (base === void 0) return void 0;
|
|
9534
10270
|
if (expr.index.type === "StringLiteral") return `${base}.${expr.index.value}`;
|
|
9535
10271
|
if (expr.index.type === "NumberLiteral") return `${base}#${expr.index.value}`;
|
|
10272
|
+
if (expr.index.type === "Identifier") {
|
|
10273
|
+
const id = this.bindingIdOf(expr.index);
|
|
10274
|
+
return id === void 0 ? void 0 : `${base}[${bindKey(id)}]`;
|
|
10275
|
+
}
|
|
9536
10276
|
return void 0;
|
|
9537
10277
|
}
|
|
9538
10278
|
default:
|
|
@@ -9628,26 +10368,45 @@ var TypeAnalyzer = class {
|
|
|
9628
10368
|
* that copied it no longer holds that value: forget the alias. */
|
|
9629
10369
|
unalias(key) {
|
|
9630
10370
|
for (const k of [...this.refAliases.keys()]) {
|
|
9631
|
-
if (k !== key && !k
|
|
10371
|
+
if (k !== key && !isBelow(k, key) && !k.includes(`[${key}]`)) continue;
|
|
9632
10372
|
for (const other of this.refAliases.get(k) ?? []) this.refAliases.get(other)?.delete(k);
|
|
9633
10373
|
this.refAliases.delete(k);
|
|
9634
10374
|
}
|
|
9635
10375
|
}
|
|
9636
|
-
/** Drop every narrowing recorded for a path strictly under `key
|
|
10376
|
+
/** Drop every narrowing recorded for a path strictly under `key` — and,
|
|
10377
|
+
* when `key` is a whole variable, for every `t[key]` indexed by it: that
|
|
10378
|
+
* now names a different slot. */
|
|
9637
10379
|
invalidateBelow(env, key) {
|
|
10380
|
+
const asIndex = `[${key}]`;
|
|
9638
10381
|
for (const k of [...env.keys()]) {
|
|
9639
|
-
if (k
|
|
10382
|
+
if (isBelow(k, key) || k.includes(asIndex)) env.delete(k);
|
|
9640
10383
|
}
|
|
9641
10384
|
}
|
|
9642
10385
|
/** An assignment through a reference invalidates it and everything under
|
|
9643
10386
|
* it, then records the assigned type. */
|
|
9644
10387
|
assignToRef(expr, value, env) {
|
|
10388
|
+
this.invalidateSiblings(expr, env);
|
|
9645
10389
|
const key = this.refKeyOf(expr);
|
|
9646
10390
|
if (key === void 0) return;
|
|
9647
10391
|
this.invalidateBelow(env, key);
|
|
9648
10392
|
this.unalias(key);
|
|
9649
10393
|
env.set(key, value);
|
|
9650
10394
|
}
|
|
10395
|
+
/** A write to one slot of a table can be a write to a slot narrowed under
|
|
10396
|
+
* another name: `t.x = nil` is `t[k] = nil` when `k` is `"x"`. So a
|
|
10397
|
+
* write by name forgets what is known through variable keys, and a write
|
|
10398
|
+
* through any computed key forgets everything known about the table's
|
|
10399
|
+
* slots. */
|
|
10400
|
+
invalidateSiblings(expr, env) {
|
|
10401
|
+
const inner = expr.type === "ParenthesizedExpression" ? expr.expression : expr;
|
|
10402
|
+
if (inner.type !== "MemberExpression" && inner.type !== "IndexExpression") return;
|
|
10403
|
+
const parent = this.refKeyOf(inner.object);
|
|
10404
|
+
if (parent === void 0) return;
|
|
10405
|
+
const computed = inner.type === "IndexExpression" && inner.index.type !== "StringLiteral" && inner.index.type !== "NumberLiteral";
|
|
10406
|
+
for (const k of [...env.keys()]) {
|
|
10407
|
+
if (computed ? isBelow(k, parent) : k.startsWith(`${parent}[`)) env.delete(k);
|
|
10408
|
+
}
|
|
10409
|
+
}
|
|
9651
10410
|
// --------------------------------------------------------
|
|
9652
10411
|
// Small helpers
|
|
9653
10412
|
// --------------------------------------------------------
|
|
@@ -9662,6 +10421,92 @@ var TypeAnalyzer = class {
|
|
|
9662
10421
|
this.selfType = saved;
|
|
9663
10422
|
}
|
|
9664
10423
|
}
|
|
10424
|
+
/** Arithmetic no declared metamethod answers: a number — unless an
|
|
10425
|
+
* operand is `any`, which may be a value with metamethods of its own.
|
|
10426
|
+
* `(a.Position - b.Position).Magnitude` on an `a` nothing typed is a
|
|
10427
|
+
* `Vector3` as far as anyone knows, not a number with no `Magnitude`. */
|
|
10428
|
+
arithmeticOn(operands) {
|
|
10429
|
+
return operands.some((t) => this.expand(t).kind === "any") ? anyType : numberType;
|
|
10430
|
+
}
|
|
10431
|
+
/** `a == b` where nothing `a` can hold is anything `b` can: the answer is
|
|
10432
|
+
* always the same, so the comparison was not the one meant. Only types
|
|
10433
|
+
* that say what they hold take part — `any`, `unknown` and a type
|
|
10434
|
+
* parameter say nothing, and a table compares by identity, so two of them
|
|
10435
|
+
* may well be the same table. */
|
|
10436
|
+
checkOverlap(node, op, l, r) {
|
|
10437
|
+
if (!this.emitDiagnostics) return;
|
|
10438
|
+
const left = this.expand(l);
|
|
10439
|
+
const right = this.expand(r);
|
|
10440
|
+
const judged = (t) => {
|
|
10441
|
+
const parts = t.kind === "union" ? t.types.map((m) => this.expand(m)) : [t];
|
|
10442
|
+
return parts.every((p) => p.kind === "literal" || p.kind === "primitive");
|
|
10443
|
+
};
|
|
10444
|
+
const isNil = (t) => t.kind === "primitive" && t.name === "nil";
|
|
10445
|
+
if (isNil(left) || isNil(right)) return;
|
|
10446
|
+
if (!judged(left) || !judged(right)) return;
|
|
10447
|
+
if (isAssignable(left, right) || isAssignable(right, left)) return;
|
|
10448
|
+
this.diagnostics.push({
|
|
10449
|
+
node,
|
|
10450
|
+
message: `This comparison is always ${op === "==" ? "false" : "true"}: '${formatType(l)}' and '${formatType(r)}' have no value in common`
|
|
10451
|
+
});
|
|
10452
|
+
}
|
|
10453
|
+
/** What an operator takes, where no metamethod answered for it. Luau
|
|
10454
|
+
* raises on the rest — `n * 2` with `n` nil is an error at the line it
|
|
10455
|
+
* runs, and a nil `n` is exactly what `number | nil` says may happen — so
|
|
10456
|
+
* it is said here instead. `any` passes: nothing is known about it. */
|
|
10457
|
+
checkOperands(node, op, left, right) {
|
|
10458
|
+
if (!this.emitDiagnostics) return;
|
|
10459
|
+
const fits = (t) => {
|
|
10460
|
+
const x = this.expand(t);
|
|
10461
|
+
if (x.kind === "any" || x.kind === "never") return true;
|
|
10462
|
+
switch (op) {
|
|
10463
|
+
// `..` joins strings, and Luau writes a number into one.
|
|
10464
|
+
case "..":
|
|
10465
|
+
return isAssignable(x, stringType) || isAssignable(x, numberType);
|
|
10466
|
+
// `#` counts a string's bytes or a table's entries.
|
|
10467
|
+
case "#":
|
|
10468
|
+
return x.kind === "array" || x.kind === "tuple" || x.kind === "object" || isAssignable(x, stringType);
|
|
10469
|
+
default:
|
|
10470
|
+
return isAssignable(x, numberType);
|
|
10471
|
+
}
|
|
10472
|
+
};
|
|
10473
|
+
if (op === "<" || op === ">" || op === "<=" || op === ">=") {
|
|
10474
|
+
const both = (t) => this.expand(t);
|
|
10475
|
+
const [l, r] = [both(left), both(right)];
|
|
10476
|
+
const loose = (t) => t.kind === "any" || t.kind === "never";
|
|
10477
|
+
const alike = (as) => isAssignable(l, as) && isAssignable(r, as);
|
|
10478
|
+
if (loose(l) || loose(r) || alike(numberType) || alike(stringType)) return;
|
|
10479
|
+
} else if (fits(left) && (right === void 0 || fits(right))) {
|
|
10480
|
+
return;
|
|
10481
|
+
}
|
|
10482
|
+
this.diagnostics.push({
|
|
10483
|
+
node,
|
|
10484
|
+
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)}'`
|
|
10485
|
+
});
|
|
10486
|
+
}
|
|
10487
|
+
/** `a < b` between class instances: Luau asks `__lt` (`__le` for `<=`),
|
|
10488
|
+
* with the operands swapped for `>` and `>=`, and an instance without
|
|
10489
|
+
* one cannot be compared at all. */
|
|
10490
|
+
checkComparison(node, op, l, r) {
|
|
10491
|
+
if (!this.emitDiagnostics) return false;
|
|
10492
|
+
const left = this.expand(l);
|
|
10493
|
+
const right = this.expand(r);
|
|
10494
|
+
const instance = (t) => t.kind === "object" && t.class !== void 0;
|
|
10495
|
+
if (!instance(left) && !instance(right)) return false;
|
|
10496
|
+
const name = op === "<" || op === ">" ? "__lt" : "__le";
|
|
10497
|
+
const operands = op === ">" || op === ">=" ? [right, left] : [left, right];
|
|
10498
|
+
for (const receiver of operands) {
|
|
10499
|
+
const method = receiver.kind === "object" ? receiver.properties.get(name) : void 0;
|
|
10500
|
+
if (!method) continue;
|
|
10501
|
+
if (this.pickOverload(this.overloadsOf(method.type), operands)) return true;
|
|
10502
|
+
break;
|
|
10503
|
+
}
|
|
10504
|
+
this.diagnostics.push({
|
|
10505
|
+
node,
|
|
10506
|
+
message: `Operator '${op}' cannot be applied to types '${formatType(l)}' and '${formatType(r)}'`
|
|
10507
|
+
});
|
|
10508
|
+
return true;
|
|
10509
|
+
}
|
|
9665
10510
|
/** What an operator on a value with metamethods gives: `a + b` calls
|
|
9666
10511
|
* `__add` on `a`, or failing that on `b` with the operands swapped — the
|
|
9667
10512
|
* order Luau tries them in. That is how `Vector3 + Vector3`, `CFrame *
|
|
@@ -9947,7 +10792,56 @@ var TypeAnalyzer = class {
|
|
|
9947
10792
|
return this.bindingByDecl.get(node) ?? this.bindingByPos.get(posKey(name, node.line.start, node.column.start));
|
|
9948
10793
|
}
|
|
9949
10794
|
};
|
|
9950
|
-
|
|
10795
|
+
var libraryNames = /* @__PURE__ */ new WeakMap();
|
|
10796
|
+
function namesDeclaredIn(program) {
|
|
10797
|
+
const known = libraryNames.get(program);
|
|
10798
|
+
if (known) return known;
|
|
10799
|
+
const names = /* @__PURE__ */ new Set();
|
|
10800
|
+
for (const stmt of program.body.statements) {
|
|
10801
|
+
const declaration = stmt.type === "ExportStatement" || stmt.type === "ExportDefaultStatement" ? stmt.declaration : stmt;
|
|
10802
|
+
if (declaration.type === "TypeAliasStatement") names.add(declaration.name.name);
|
|
10803
|
+
else if (declaration.type === "ExportTypeAliasStatement") names.add(declaration.alias.name.name);
|
|
10804
|
+
else if (declaration.type === "DeclareClassStatement") names.add(declaration.name.name);
|
|
10805
|
+
else if (declaration.type === "ClassDeclaration" && declaration.name) names.add(declaration.name.name);
|
|
10806
|
+
else if (stmt.type === "ImportStatement") {
|
|
10807
|
+
for (const s of stmt.specifiers) names.add(s.local.name);
|
|
10808
|
+
if (stmt.defaultImport) names.add(stmt.defaultImport.name);
|
|
10809
|
+
if (stmt.namespaceImport) names.add(stmt.namespaceImport.name);
|
|
10810
|
+
}
|
|
10811
|
+
}
|
|
10812
|
+
libraryNames.set(program, names);
|
|
10813
|
+
return names;
|
|
10814
|
+
}
|
|
10815
|
+
var sharedClassesByLib = /* @__PURE__ */ new WeakMap();
|
|
10816
|
+
function shareableClasses(libs, program) {
|
|
10817
|
+
if (!libs.length) return void 0;
|
|
10818
|
+
const mine = namesDeclaredIn(program);
|
|
10819
|
+
if (mine.size) {
|
|
10820
|
+
for (const lib of libs) {
|
|
10821
|
+
for (const name of namesDeclaredIn(lib)) if (mine.has(name)) return void 0;
|
|
10822
|
+
}
|
|
10823
|
+
}
|
|
10824
|
+
const existing = sharedClassesByLib.get(libs[0]);
|
|
10825
|
+
if (existing) {
|
|
10826
|
+
const same = existing.libs.length === libs.length && existing.libs.every((lib, i) => lib === libs[i]);
|
|
10827
|
+
if (same) return existing;
|
|
10828
|
+
return void 0;
|
|
10829
|
+
}
|
|
10830
|
+
const store = { libs: [...libs], classTypes: /* @__PURE__ */ new WeakMap(), classMembers: /* @__PURE__ */ new WeakMap() };
|
|
10831
|
+
sharedClassesByLib.set(libs[0], store);
|
|
10832
|
+
return store;
|
|
10833
|
+
}
|
|
10834
|
+
var referencedNamesOf = /* @__PURE__ */ new WeakMap();
|
|
10835
|
+
var typeQueryIn = /* @__PURE__ */ new WeakMap();
|
|
10836
|
+
function referencedTypeNames(node, out) {
|
|
10837
|
+
if (out === void 0 && node && typeof node === "object") {
|
|
10838
|
+
const known = referencedNamesOf.get(node);
|
|
10839
|
+
if (known) return known;
|
|
10840
|
+
const found = referencedTypeNames(node, []);
|
|
10841
|
+
referencedNamesOf.set(node, found);
|
|
10842
|
+
return found;
|
|
10843
|
+
}
|
|
10844
|
+
out = out ?? [];
|
|
9951
10845
|
if (!node || typeof node !== "object") return out;
|
|
9952
10846
|
if (Array.isArray(node)) {
|
|
9953
10847
|
for (const item of node) referencedTypeNames(item, out);
|
|
@@ -9964,9 +10858,11 @@ function referencedTypeNames(node, out = []) {
|
|
|
9964
10858
|
}
|
|
9965
10859
|
function containsTypeQuery(node) {
|
|
9966
10860
|
if (!node || typeof node !== "object") return false;
|
|
9967
|
-
|
|
9968
|
-
if (
|
|
9969
|
-
|
|
10861
|
+
const known = typeQueryIn.get(node);
|
|
10862
|
+
if (known !== void 0) return known;
|
|
10863
|
+
const found = Array.isArray(node) ? node.some(containsTypeQuery) : node.type === "TypeofTypeNode" || Object.values(node).some(containsTypeQuery);
|
|
10864
|
+
typeQueryIn.set(node, found);
|
|
10865
|
+
return found;
|
|
9970
10866
|
}
|
|
9971
10867
|
var STRING_INTRINSICS = /* @__PURE__ */ new Set(["Uppercase", "Lowercase", "Capitalize", "Uncapitalize"]);
|
|
9972
10868
|
function briefType(t) {
|
|
@@ -9994,6 +10890,13 @@ var nodeHost = {
|
|
|
9994
10890
|
} catch {
|
|
9995
10891
|
return void 0;
|
|
9996
10892
|
}
|
|
10893
|
+
},
|
|
10894
|
+
readDirectory(path) {
|
|
10895
|
+
try {
|
|
10896
|
+
return (0, import_node_fs.readdirSync)(path, { withFileTypes: true }).filter((entry) => entry.isDirectory() || entry.isSymbolicLink()).map((entry) => entry.name);
|
|
10897
|
+
} catch {
|
|
10898
|
+
return void 0;
|
|
10899
|
+
}
|
|
9997
10900
|
}
|
|
9998
10901
|
};
|
|
9999
10902
|
|
|
@@ -10185,13 +11088,31 @@ function resolveTypeLibraries(config, host = nodeHost) {
|
|
|
10185
11088
|
else problems.push({ file: config.path, message: `'${entry}' has no ${ENTRY_FILE} (or 'tilua.types' in its package.json)`, ...entryPosition(config, entry) });
|
|
10186
11089
|
continue;
|
|
10187
11090
|
}
|
|
10188
|
-
|
|
10189
|
-
|
|
11091
|
+
if (entry.includes("*")) {
|
|
11092
|
+
const matched = matchPackages(entry, config.directory, host);
|
|
11093
|
+
if (matched === void 0) {
|
|
11094
|
+
problems.push({
|
|
11095
|
+
file: config.path,
|
|
11096
|
+
message: `'${entry}' needs the folders listed, and this tool cannot list them; name the libraries one by one`,
|
|
11097
|
+
...entryPosition(config, entry)
|
|
11098
|
+
});
|
|
11099
|
+
} else if (!matched.length) {
|
|
11100
|
+
problems.push({
|
|
11101
|
+
file: config.path,
|
|
11102
|
+
message: `No installed type library matches '${entry}'`,
|
|
11103
|
+
...entryPosition(config, entry)
|
|
11104
|
+
});
|
|
11105
|
+
}
|
|
11106
|
+
for (const found2 of matched ?? []) addPackage(found2.directory, found2.file, /* @__PURE__ */ new Set());
|
|
11107
|
+
continue;
|
|
11108
|
+
}
|
|
11109
|
+
const names = entry.startsWith("@") ? [entry] : [`@tilua-types/${entry}`, entry];
|
|
11110
|
+
const found = names.map((name) => findPackage(name, config.directory, host)).find(Boolean);
|
|
10190
11111
|
if (found) addPackage(found.directory, found.file, /* @__PURE__ */ new Set());
|
|
10191
11112
|
else {
|
|
10192
11113
|
problems.push({
|
|
10193
11114
|
file: config.path,
|
|
10194
|
-
message: `Cannot find type library '${
|
|
11115
|
+
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]}`,
|
|
10195
11116
|
...entryPosition(config, entry)
|
|
10196
11117
|
});
|
|
10197
11118
|
}
|
|
@@ -10228,6 +11149,32 @@ function findPackage(name, from, host) {
|
|
|
10228
11149
|
directory = parent;
|
|
10229
11150
|
}
|
|
10230
11151
|
}
|
|
11152
|
+
function matchPackages(pattern, from, host) {
|
|
11153
|
+
if (!host.readDirectory) return void 0;
|
|
11154
|
+
const slash = pattern.lastIndexOf("/");
|
|
11155
|
+
const scope = slash < 0 ? "" : pattern.slice(0, slash);
|
|
11156
|
+
const last = pattern.slice(slash + 1);
|
|
11157
|
+
const matches = new RegExp(`^${last.split("*").map(escapeRegExp2).join(".*")}$`);
|
|
11158
|
+
const found = /* @__PURE__ */ new Map();
|
|
11159
|
+
let directory = (0, import_node_path2.resolve)(from);
|
|
11160
|
+
for (; ; ) {
|
|
11161
|
+
const folder = (0, import_node_path2.join)(directory, "node_modules", ...scope ? scope.split("/") : []);
|
|
11162
|
+
for (const name of host.readDirectory(folder) ?? []) {
|
|
11163
|
+
const full = scope ? `${scope}/${name}` : name;
|
|
11164
|
+
if (!matches.test(name) || found.has(full)) continue;
|
|
11165
|
+
const candidate = (0, import_node_path2.join)(folder, name);
|
|
11166
|
+
const file = packageEntry(candidate, host);
|
|
11167
|
+
if (file) found.set(full, { directory: candidate, file });
|
|
11168
|
+
}
|
|
11169
|
+
const parent = (0, import_node_path2.dirname)(directory);
|
|
11170
|
+
if (parent === directory) break;
|
|
11171
|
+
directory = parent;
|
|
11172
|
+
}
|
|
11173
|
+
return [...found.keys()].sort().map((name) => found.get(name));
|
|
11174
|
+
}
|
|
11175
|
+
function escapeRegExp2(text) {
|
|
11176
|
+
return text.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
|
|
11177
|
+
}
|
|
10231
11178
|
function dependencyNames(directory, host) {
|
|
10232
11179
|
const manifest = readJson((0, import_node_path2.join)(directory, "package.json"), host);
|
|
10233
11180
|
const names = /* @__PURE__ */ new Set();
|
|
@@ -10326,7 +11273,8 @@ function sourceMapTypes(text, path, options) {
|
|
|
10326
11273
|
for (const child of node.children ?? []) {
|
|
10327
11274
|
if (!isNode(child)) continue;
|
|
10328
11275
|
const childAlias = visit(child, [...segments, child.name], alias);
|
|
10329
|
-
|
|
11276
|
+
const service = child.name === child.className && options.classes.has(child.className);
|
|
11277
|
+
if (!IDENTIFIER.test(child.name) || taken.has(child.name) && !service || named.has(child.name)) continue;
|
|
10330
11278
|
named.add(child.name);
|
|
10331
11279
|
members.push(`${child.name}: ${childAlias}`);
|
|
10332
11280
|
}
|
|
@@ -10387,6 +11335,7 @@ var index_default = tilua;
|
|
|
10387
11335
|
BinaryOperators,
|
|
10388
11336
|
CONFIG_FILE_NAMES,
|
|
10389
11337
|
Keywords,
|
|
11338
|
+
LANGUAGE_GLOBALS,
|
|
10390
11339
|
LexError,
|
|
10391
11340
|
Operators,
|
|
10392
11341
|
PRELUDE_SOURCE,
|
|
@@ -10454,6 +11403,7 @@ var index_default = tilua;
|
|
|
10454
11403
|
tilua,
|
|
10455
11404
|
tokenize,
|
|
10456
11405
|
tuple,
|
|
11406
|
+
tupleMembers,
|
|
10457
11407
|
typeParam,
|
|
10458
11408
|
unify,
|
|
10459
11409
|
union,
|