luaut-parser 2.1.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -4
- package/dist/index.cjs +190 -79
- package/dist/index.d.cts +19 -7
- package/dist/index.d.ts +19 -7
- package/dist/index.js +190 -79
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -71,10 +71,10 @@ npm i -D @luaut/roblox # or just @luaut/luau
|
|
|
71
71
|
- **Which config applies** — the nearest one in the file's folder or above.
|
|
72
72
|
`luaut.config.json` and `luaut.config.jsonc` in the same folder is an error.
|
|
73
73
|
Both forms accept comments and trailing commas.
|
|
74
|
-
- **`types`** —
|
|
75
|
-
`
|
|
76
|
-
relative path (`"./types"`, `"./defs.d.luaut"`)
|
|
77
|
-
own type-library dependencies load first.
|
|
74
|
+
- **`types`** — any name, looked up as the package `@luaut/<name>` in
|
|
75
|
+
`node_modules` from the config upward; one that is not installed is an
|
|
76
|
+
error. A relative path (`"./types"`, `"./defs.d.luaut"`) loads the project's
|
|
77
|
+
own definitions. A type library's own type-library dependencies load first.
|
|
78
78
|
- **`paths`** — tsconfig rules: an exact pattern wins, then the `*` pattern
|
|
79
79
|
with the longest prefix; targets resolve from `baseUrl` (default: the
|
|
80
80
|
config's folder).
|
|
@@ -106,6 +106,22 @@ must.
|
|
|
106
106
|
|
|
107
107
|
**Declarations** — `const` and `let` only; Lua's `local` is gone.
|
|
108
108
|
|
|
109
|
+
**Functions** — `function name() ... end` declares `name` in the enclosing
|
|
110
|
+
scope; like a TypeScript function declaration it cannot be reassigned.
|
|
111
|
+
`const` and `let` do not apply to functions. `function T.name()` and
|
|
112
|
+
`function T:name()` define a member.
|
|
113
|
+
|
|
114
|
+
**Modules** — `import { a, b as c } from "./m"`, `import D from "./m"` and
|
|
115
|
+
`import * as M from "./m"`; `export const`, `export function`, `export default`,
|
|
116
|
+
`export { a as b }`, `export { a } from "./m"` and `export * from "./m"`.
|
|
117
|
+
Imports are read-only: assigning to an imported name, or to a member of a
|
|
118
|
+
namespace (`M.x = 1`), is an error.
|
|
119
|
+
|
|
120
|
+
`import type { A } from "./m"` (also `import type D` and `import type * as M`)
|
|
121
|
+
brings in names that are types and nothing else: unlike TypeScript, using one
|
|
122
|
+
as a value is an error, and only type positions — `typeof A` included — may
|
|
123
|
+
name it. Compiled code keeps no trace of it.
|
|
124
|
+
|
|
109
125
|
**Optionality** — there is no `T?` shorthand. `?` in type position always
|
|
110
126
|
belongs to a conditional type, and in expression position to a ternary.
|
|
111
127
|
|
|
@@ -150,6 +166,10 @@ operand and then the right one, as Luau does. So `Vector3 + Vector3` and
|
|
|
150
166
|
(`declare class Enum.Material extends EnumItem {}`), and code writes it the
|
|
151
167
|
same way.
|
|
152
168
|
|
|
169
|
+
**Contextual typing** — an expression takes its type from where it is
|
|
170
|
+
written, as in TypeScript: `let queue: thread[] = []` is a `thread[]`, and so
|
|
171
|
+
is `[]` passed where one is expected, including inside an object literal.
|
|
172
|
+
|
|
153
173
|
**Calls** — every argument is checked against its parameter, and a generic
|
|
154
174
|
parameter against its constraint (`GetService<K extends keyof Services>`
|
|
155
175
|
rejects `""`).
|
package/dist/index.cjs
CHANGED
|
@@ -889,23 +889,14 @@ var Parser = class {
|
|
|
889
889
|
if (t.type === "Punctuator" && t.value === "@") {
|
|
890
890
|
const { attributes, start } = this.parseAttributes();
|
|
891
891
|
const next = this.current();
|
|
892
|
-
if (next.type === "Keyword" && (next.value === "const" || next.value === "let")) {
|
|
893
|
-
const stmt = this.parseVariableDeclaration();
|
|
894
|
-
if (stmt.type === "FunctionDeclaration") {
|
|
895
|
-
stmt.attributes = attributes;
|
|
896
|
-
stmt.line.start = start.line.start;
|
|
897
|
-
stmt.column.start = start.column.start;
|
|
898
|
-
}
|
|
899
|
-
return stmt;
|
|
900
|
-
}
|
|
901
892
|
if (next.type === "Keyword" && next.value === "function") {
|
|
902
|
-
const stmt = this.
|
|
893
|
+
const stmt = this.parseFunctionStatement();
|
|
903
894
|
stmt.attributes = attributes;
|
|
904
895
|
stmt.line.start = start.line.start;
|
|
905
896
|
stmt.column.start = start.column.start;
|
|
906
897
|
return stmt;
|
|
907
898
|
}
|
|
908
|
-
throw new ParseError("Expected 'function'
|
|
899
|
+
throw new ParseError("Expected 'function' after an attribute", next.line.start, next.column.start);
|
|
909
900
|
}
|
|
910
901
|
if (t.type === "Keyword") {
|
|
911
902
|
switch (t.value) {
|
|
@@ -923,7 +914,7 @@ var Parser = class {
|
|
|
923
914
|
case "for":
|
|
924
915
|
return this.parseForStatement();
|
|
925
916
|
case "function":
|
|
926
|
-
return this.
|
|
917
|
+
return this.parseFunctionStatement();
|
|
927
918
|
case "return":
|
|
928
919
|
return this.parseReturnStatement();
|
|
929
920
|
case "import":
|
|
@@ -1025,20 +1016,30 @@ var Parser = class {
|
|
|
1025
1016
|
parseImportStatement() {
|
|
1026
1017
|
const start = this.current();
|
|
1027
1018
|
this.advance();
|
|
1019
|
+
const next = this.peek(1);
|
|
1020
|
+
const isTypeOnly = this.checkIdentifierValue("type") && (next.type === "Punctuator" && next.value === "{" || next.type === "Operator" && next.value === "*" || next.type === "Identifier");
|
|
1021
|
+
if (isTypeOnly) this.advance();
|
|
1028
1022
|
let defaultImport;
|
|
1029
1023
|
const specifiers = [];
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
this.
|
|
1035
|
-
this.
|
|
1036
|
-
this.
|
|
1024
|
+
let namespaceImport;
|
|
1025
|
+
const parseBindings = () => {
|
|
1026
|
+
if (this.checkOperator("*")) {
|
|
1027
|
+
this.advance();
|
|
1028
|
+
if (!this.checkKeyword("as")) this.error("Expected 'as' after 'import *'");
|
|
1029
|
+
this.advance();
|
|
1030
|
+
namespaceImport = this.parseIdentifier();
|
|
1031
|
+
return;
|
|
1037
1032
|
}
|
|
1038
|
-
} else {
|
|
1039
1033
|
this.expectPunctuator("{");
|
|
1040
1034
|
this.parseImportSpecifierList(specifiers);
|
|
1041
1035
|
this.expectPunctuator("}");
|
|
1036
|
+
};
|
|
1037
|
+
if (this.checkType("Identifier")) {
|
|
1038
|
+
const nameTok = this.expectIdentifier();
|
|
1039
|
+
defaultImport = { type: "Identifier", name: nameTok.value, ...spanFrom(nameTok, nameTok) };
|
|
1040
|
+
if (this.matchPunctuator(",")) parseBindings();
|
|
1041
|
+
} else {
|
|
1042
|
+
parseBindings();
|
|
1042
1043
|
}
|
|
1043
1044
|
if (!this.checkKeyword("from")) {
|
|
1044
1045
|
this.error("Expected 'from' in import statement");
|
|
@@ -1055,7 +1056,15 @@ var Parser = class {
|
|
|
1055
1056
|
raw: sourceTok.raw,
|
|
1056
1057
|
...spanFrom(sourceTok, sourceTok)
|
|
1057
1058
|
};
|
|
1058
|
-
return {
|
|
1059
|
+
return {
|
|
1060
|
+
type: "ImportStatement",
|
|
1061
|
+
defaultImport,
|
|
1062
|
+
namespaceImport,
|
|
1063
|
+
specifiers,
|
|
1064
|
+
source,
|
|
1065
|
+
isTypeOnly: isTypeOnly || void 0,
|
|
1066
|
+
...spanFrom(start, this.previous())
|
|
1067
|
+
};
|
|
1059
1068
|
}
|
|
1060
1069
|
parseImportSpecifierList(out) {
|
|
1061
1070
|
if (this.checkPunctuator("}")) return;
|
|
@@ -1091,7 +1100,7 @@ var Parser = class {
|
|
|
1091
1100
|
...spanFrom(sourceTok, sourceTok)
|
|
1092
1101
|
};
|
|
1093
1102
|
}
|
|
1094
|
-
// `export const ...` / `export let ...` / `export
|
|
1103
|
+
// `export const ...` / `export let ...` / `export function ...` /
|
|
1095
1104
|
// `export type ...` / `export default <expr>`
|
|
1096
1105
|
parseExportStatement() {
|
|
1097
1106
|
const start = this.current();
|
|
@@ -1109,6 +1118,11 @@ var Parser = class {
|
|
|
1109
1118
|
const declaration = this.parseVariableDeclaration();
|
|
1110
1119
|
return { type: "ExportStatement", declaration, ...spanFrom(start, this.previous()) };
|
|
1111
1120
|
}
|
|
1121
|
+
if (this.checkKeyword("function")) {
|
|
1122
|
+
const declaration = this.parseFunctionStatement();
|
|
1123
|
+
if (declaration.type !== "FunctionDeclaration") this.error("An exported function needs a plain name: 'export function name()'");
|
|
1124
|
+
return { type: "ExportStatement", declaration, ...spanFrom(start, this.previous()) };
|
|
1125
|
+
}
|
|
1112
1126
|
if (this.checkPunctuator("{")) {
|
|
1113
1127
|
this.advance();
|
|
1114
1128
|
const specifiers = [];
|
|
@@ -1132,15 +1146,15 @@ var Parser = class {
|
|
|
1132
1146
|
const source = this.parseModuleSource();
|
|
1133
1147
|
return { type: "ExportAllStatement", source, ...spanFrom(start, this.previous()) };
|
|
1134
1148
|
}
|
|
1135
|
-
this.error("Expected 'const', 'let', 'type', 'default', '{' or '*' after 'export'");
|
|
1149
|
+
this.error("Expected 'const', 'let', 'function', 'type', 'default', '{' or '*' after 'export'");
|
|
1136
1150
|
}
|
|
1137
|
-
// `const x = ...` / `let x, y =
|
|
1151
|
+
// `const x = ...` / `let x, y = ...`.
|
|
1138
1152
|
// luaut has no `local` — `const` bindings are immutable, `let` mutable.
|
|
1139
1153
|
parseVariableDeclaration() {
|
|
1140
1154
|
const start = this.current();
|
|
1141
1155
|
const kind = this.advance().value;
|
|
1142
|
-
if (this.
|
|
1143
|
-
|
|
1156
|
+
if (this.checkKeyword("function")) {
|
|
1157
|
+
this.error(`A function is declared as 'function name()'; '${kind}' does not apply to functions`);
|
|
1144
1158
|
}
|
|
1145
1159
|
const names = [this.parseBindingTarget(true)];
|
|
1146
1160
|
while (this.matchPunctuator(",")) {
|
|
@@ -1154,31 +1168,6 @@ var Parser = class {
|
|
|
1154
1168
|
}
|
|
1155
1169
|
return { type: "VariableDeclaration", kind, names, init, ...spanFrom(start, this.previous()) };
|
|
1156
1170
|
}
|
|
1157
|
-
/** `const/let function` — `function` already consumed. Collects TS-style
|
|
1158
|
-
* overload signatures. */
|
|
1159
|
-
parseFunctionDeclarationRest(start, kind) {
|
|
1160
|
-
const name = this.parseIdentifier();
|
|
1161
|
-
const signatures = [];
|
|
1162
|
-
while (true) {
|
|
1163
|
-
const head = this.parseFunctionHead();
|
|
1164
|
-
if (this.isOverloadContinuation(name.name, kind)) {
|
|
1165
|
-
signatures.push(this.headToSignature(head));
|
|
1166
|
-
this.advance();
|
|
1167
|
-
this.expectKeyword("function");
|
|
1168
|
-
this.parseIdentifier();
|
|
1169
|
-
continue;
|
|
1170
|
-
}
|
|
1171
|
-
const func = this.headToBody(head);
|
|
1172
|
-
return {
|
|
1173
|
-
type: "FunctionDeclaration",
|
|
1174
|
-
kind,
|
|
1175
|
-
name,
|
|
1176
|
-
func,
|
|
1177
|
-
signatures: signatures.length ? signatures : void 0,
|
|
1178
|
-
...spanFrom(start, this.previous())
|
|
1179
|
-
};
|
|
1180
|
-
}
|
|
1181
|
-
}
|
|
1182
1171
|
parseIfStatement() {
|
|
1183
1172
|
const start = this.current();
|
|
1184
1173
|
this.expectKeyword("if");
|
|
@@ -1268,7 +1257,9 @@ var Parser = class {
|
|
|
1268
1257
|
...spanFrom(start, this.previous())
|
|
1269
1258
|
};
|
|
1270
1259
|
}
|
|
1271
|
-
|
|
1260
|
+
/** `function name() end` declares `name`; `function a.b() end` and
|
|
1261
|
+
* `function T:m() end` define a member. */
|
|
1262
|
+
parseFunctionStatement() {
|
|
1272
1263
|
const start = this.current();
|
|
1273
1264
|
this.expectKeyword("function");
|
|
1274
1265
|
const target = this.parseFunctionName();
|
|
@@ -1284,6 +1275,15 @@ var Parser = class {
|
|
|
1284
1275
|
continue;
|
|
1285
1276
|
}
|
|
1286
1277
|
const func = this.headToBody(head);
|
|
1278
|
+
if (simpleName !== void 0) {
|
|
1279
|
+
return {
|
|
1280
|
+
type: "FunctionDeclaration",
|
|
1281
|
+
name: target.base,
|
|
1282
|
+
func,
|
|
1283
|
+
signatures: signatures.length ? signatures : void 0,
|
|
1284
|
+
...spanFrom(start, this.previous())
|
|
1285
|
+
};
|
|
1286
|
+
}
|
|
1287
1287
|
if (isMethod) {
|
|
1288
1288
|
func.params.unshift({ type: "FunctionParameter", name: "self", ...spanFrom(target, target) });
|
|
1289
1289
|
func.isMethod = true;
|
|
@@ -1300,12 +1300,8 @@ var Parser = class {
|
|
|
1300
1300
|
}
|
|
1301
1301
|
/** After a bodyless function head, is the next token the start of another
|
|
1302
1302
|
* declaration for the same simple `name` (making the head an overload
|
|
1303
|
-
* signature rather than an implementation)?
|
|
1304
|
-
|
|
1305
|
-
isOverloadContinuation(name, kind) {
|
|
1306
|
-
if (kind) {
|
|
1307
|
-
return this.checkKeyword(kind) && this.peek(1).type === "Keyword" && this.peek(1).value === "function" && this.peek(2).type === "Identifier" && this.peek(2).value === name;
|
|
1308
|
-
}
|
|
1303
|
+
* signature rather than an implementation)? */
|
|
1304
|
+
isOverloadContinuation(name) {
|
|
1309
1305
|
return this.checkKeyword("function") && this.peek(1).type === "Identifier" && this.peek(1).value === name;
|
|
1310
1306
|
}
|
|
1311
1307
|
parseFunctionName() {
|
|
@@ -2612,7 +2608,7 @@ var Analyzer = class {
|
|
|
2612
2608
|
};
|
|
2613
2609
|
}
|
|
2614
2610
|
// ---------------- declaration / resolution primitives ----------------
|
|
2615
|
-
declare(scope, name, kind, node, isConst = false) {
|
|
2611
|
+
declare(scope, name, kind, node, isConst = false, declaredBy) {
|
|
2616
2612
|
if (scope.declarations.has(name) && scope !== this.globalScope) {
|
|
2617
2613
|
this.diagnostics.push({
|
|
2618
2614
|
node,
|
|
@@ -2621,7 +2617,7 @@ var Analyzer = class {
|
|
|
2621
2617
|
});
|
|
2622
2618
|
}
|
|
2623
2619
|
const id = this.nextId++;
|
|
2624
|
-
this.bindings.set(id, { id, name, kind, declarationNode: node, references: [], isConst });
|
|
2620
|
+
this.bindings.set(id, { id, name, kind, declarationNode: node, references: [], isConst, declaredBy });
|
|
2625
2621
|
scope.declarations.set(name, id);
|
|
2626
2622
|
return id;
|
|
2627
2623
|
}
|
|
@@ -2655,6 +2651,19 @@ var Analyzer = class {
|
|
|
2655
2651
|
const id = this.resolve(scope, identifier.name);
|
|
2656
2652
|
this.bindingOf.set(identifier, id);
|
|
2657
2653
|
this.bindings.get(id).references.push(identifier);
|
|
2654
|
+
if (this.typeQueryDepth === 0) this.checkTypeOnly(id, identifier);
|
|
2655
|
+
}
|
|
2656
|
+
/** Inside `typeof x` in a type, where a type-only import may be named. */
|
|
2657
|
+
typeQueryDepth = 0;
|
|
2658
|
+
/** A name from `import type` used as a value. */
|
|
2659
|
+
checkTypeOnly(id, node) {
|
|
2660
|
+
const b = this.bindings.get(id);
|
|
2661
|
+
if (b.declaredBy !== "type") return;
|
|
2662
|
+
this.diagnostics.push({
|
|
2663
|
+
node,
|
|
2664
|
+
message: `'${b.name}' is imported with 'import type' and can only be used as a type`,
|
|
2665
|
+
kind: "type-only"
|
|
2666
|
+
});
|
|
2658
2667
|
}
|
|
2659
2668
|
/** For assignment-like targets (`x = ...`, `function foo() end`): if
|
|
2660
2669
|
* this resolved to a global with no declaration site yet, treat this
|
|
@@ -2671,14 +2680,34 @@ var Analyzer = class {
|
|
|
2671
2680
|
this.bindingOf.set(identifier, id);
|
|
2672
2681
|
this.bindings.get(id).references.push(identifier);
|
|
2673
2682
|
this.recordPossibleGlobalDefinition(id, identifier);
|
|
2683
|
+
this.checkTypeOnly(id, identifier);
|
|
2674
2684
|
this.checkConstAssign(id, identifier);
|
|
2675
2685
|
}
|
|
2686
|
+
/** `Module.x = 1` through `import * as Module`: a module's exports belong
|
|
2687
|
+
* to it and are read-only, as in ES modules. Deeper writes (`Module.x.y`)
|
|
2688
|
+
* change the value, not the module, and are fine. */
|
|
2689
|
+
checkModuleWrite(target) {
|
|
2690
|
+
if (target.type !== "MemberExpression" && target.type !== "IndexExpression") return;
|
|
2691
|
+
if (target.object.type !== "Identifier") return;
|
|
2692
|
+
const id = this.bindingOf.get(target.object);
|
|
2693
|
+
if (id !== void 0 && this.bindings.get(id).declaredBy === "namespace") {
|
|
2694
|
+
this.moduleWriteError(target.object.name, target);
|
|
2695
|
+
}
|
|
2696
|
+
}
|
|
2697
|
+
moduleWriteError(name, node) {
|
|
2698
|
+
this.diagnostics.push({
|
|
2699
|
+
node,
|
|
2700
|
+
message: `Cannot assign to a member of '${name}' \u2014 a module's exports are read-only`,
|
|
2701
|
+
kind: "const-assign"
|
|
2702
|
+
});
|
|
2703
|
+
}
|
|
2676
2704
|
checkConstAssign(id, node) {
|
|
2677
2705
|
const b = this.bindings.get(id);
|
|
2706
|
+
if (b.declaredBy === "type") return;
|
|
2678
2707
|
if (b.isConst) {
|
|
2679
2708
|
this.diagnostics.push({
|
|
2680
2709
|
node,
|
|
2681
|
-
message: `Cannot assign to '${b.name}' \u2014 it is a const`,
|
|
2710
|
+
message: `Cannot assign to '${b.name}' \u2014 it is ${b.declaredBy === "import" || b.declaredBy === "namespace" ? "an import" : b.declaredBy === "function" ? "a function" : "a const"}`,
|
|
2682
2711
|
kind: "const-assign"
|
|
2683
2712
|
});
|
|
2684
2713
|
}
|
|
@@ -2719,6 +2748,7 @@ var Analyzer = class {
|
|
|
2719
2748
|
const id = this.resolve(scope, t.name);
|
|
2720
2749
|
this.bindingOf.set(t, id);
|
|
2721
2750
|
this.recordPossibleGlobalDefinition(id, t);
|
|
2751
|
+
this.checkTypeOnly(id, t);
|
|
2722
2752
|
this.checkConstAssign(id, t);
|
|
2723
2753
|
return;
|
|
2724
2754
|
}
|
|
@@ -2762,7 +2792,7 @@ var Analyzer = class {
|
|
|
2762
2792
|
return;
|
|
2763
2793
|
}
|
|
2764
2794
|
case "FunctionDeclaration": {
|
|
2765
|
-
this.declare(scope, stmt.name.name, "local", stmt.name,
|
|
2795
|
+
this.declare(scope, stmt.name.name, "local", stmt.name, true, "function");
|
|
2766
2796
|
for (const signature of stmt.signatures ?? []) this.visitSignature(signature, scope);
|
|
2767
2797
|
this.visitFunctionBody(stmt.func, scope);
|
|
2768
2798
|
return;
|
|
@@ -2772,6 +2802,11 @@ var Analyzer = class {
|
|
|
2772
2802
|
this.referenceAsAssignmentTarget(scope, stmt.target.base);
|
|
2773
2803
|
} else {
|
|
2774
2804
|
this.reference(scope, stmt.target.base);
|
|
2805
|
+
const id = this.bindingOf.get(stmt.target.base);
|
|
2806
|
+
const depth = stmt.target.path.length + (stmt.target.method ? 1 : 0);
|
|
2807
|
+
if (id !== void 0 && depth === 1 && this.bindings.get(id).declaredBy === "namespace") {
|
|
2808
|
+
this.moduleWriteError(stmt.target.base.name, stmt.target);
|
|
2809
|
+
}
|
|
2775
2810
|
}
|
|
2776
2811
|
for (const signature of stmt.signatures ?? []) this.visitSignature(signature, scope);
|
|
2777
2812
|
this.visitFunctionBody(stmt.func, scope, stmt.isMethod);
|
|
@@ -2786,6 +2821,7 @@ var Analyzer = class {
|
|
|
2786
2821
|
this.assignPattern(scope, target);
|
|
2787
2822
|
} else {
|
|
2788
2823
|
this.visitExpression(target, scope);
|
|
2824
|
+
this.checkModuleWrite(target);
|
|
2789
2825
|
}
|
|
2790
2826
|
}
|
|
2791
2827
|
return;
|
|
@@ -2798,6 +2834,7 @@ var Analyzer = class {
|
|
|
2798
2834
|
if (id !== void 0) this.checkConstAssign(id, stmt.target);
|
|
2799
2835
|
} else {
|
|
2800
2836
|
this.visitExpression(stmt.target, scope);
|
|
2837
|
+
this.checkModuleWrite(stmt.target);
|
|
2801
2838
|
}
|
|
2802
2839
|
return;
|
|
2803
2840
|
}
|
|
@@ -2859,11 +2896,15 @@ var Analyzer = class {
|
|
|
2859
2896
|
this.visitType(stmt.definition, scope);
|
|
2860
2897
|
return;
|
|
2861
2898
|
case "ImportStatement": {
|
|
2899
|
+
const typeOnly = stmt.isTypeOnly ? "type" : void 0;
|
|
2862
2900
|
if (stmt.defaultImport) {
|
|
2863
|
-
this.declare(scope, stmt.defaultImport.name, "local", stmt.defaultImport);
|
|
2901
|
+
this.declare(scope, stmt.defaultImport.name, "local", stmt.defaultImport, true, typeOnly ?? "import");
|
|
2902
|
+
}
|
|
2903
|
+
if (stmt.namespaceImport) {
|
|
2904
|
+
this.declare(scope, stmt.namespaceImport.name, "local", stmt.namespaceImport, true, typeOnly ?? "namespace");
|
|
2864
2905
|
}
|
|
2865
2906
|
for (const spec of stmt.specifiers) {
|
|
2866
|
-
this.declare(scope, spec.local.name, "local", spec.local);
|
|
2907
|
+
this.declare(scope, spec.local.name, "local", spec.local, true, typeOnly ?? "import");
|
|
2867
2908
|
}
|
|
2868
2909
|
return;
|
|
2869
2910
|
}
|
|
@@ -2922,7 +2963,12 @@ var Analyzer = class {
|
|
|
2922
2963
|
return;
|
|
2923
2964
|
}
|
|
2924
2965
|
if (value.type === "TypeofTypeNode") {
|
|
2925
|
-
this.
|
|
2966
|
+
this.typeQueryDepth++;
|
|
2967
|
+
try {
|
|
2968
|
+
this.visitExpression(value.expression, scope);
|
|
2969
|
+
} finally {
|
|
2970
|
+
this.typeQueryDepth--;
|
|
2971
|
+
}
|
|
2926
2972
|
return;
|
|
2927
2973
|
}
|
|
2928
2974
|
for (const key of Object.keys(value)) {
|
|
@@ -4081,6 +4127,13 @@ var TypeAnalyzer = class {
|
|
|
4081
4127
|
if (stmt.type !== "ImportStatement") continue;
|
|
4082
4128
|
const exports2 = this.moduleFor(stmt.source.value);
|
|
4083
4129
|
if (!exports2) continue;
|
|
4130
|
+
if (stmt.namespaceImport) {
|
|
4131
|
+
for (const [name, exported] of exports2.types) {
|
|
4132
|
+
const qualified = `${stmt.namespaceImport.name}.${name}`;
|
|
4133
|
+
this.importedTypes.set(qualified, exported);
|
|
4134
|
+
this.aliases.set(qualified, exported.type);
|
|
4135
|
+
}
|
|
4136
|
+
}
|
|
4084
4137
|
for (const s of stmt.specifiers) {
|
|
4085
4138
|
const exported = exports2.types.get(s.imported.name);
|
|
4086
4139
|
if (exported) {
|
|
@@ -4275,6 +4328,16 @@ var TypeAnalyzer = class {
|
|
|
4275
4328
|
});
|
|
4276
4329
|
return subst;
|
|
4277
4330
|
}
|
|
4331
|
+
/** An imported type, with its type arguments applied. */
|
|
4332
|
+
importedType(imported, typeArguments) {
|
|
4333
|
+
if (!imported.params.length) return imported.type;
|
|
4334
|
+
const subst = /* @__PURE__ */ new Map();
|
|
4335
|
+
imported.params.forEach((name, i) => {
|
|
4336
|
+
const arg = typeArguments[i];
|
|
4337
|
+
subst.set(name, arg ? this.resolveType(arg) : unknownType);
|
|
4338
|
+
});
|
|
4339
|
+
return this.reduceType(substitute(imported.type, subst));
|
|
4340
|
+
}
|
|
4278
4341
|
// --------------------------------------------------------
|
|
4279
4342
|
// TypeNode -> Type
|
|
4280
4343
|
// --------------------------------------------------------
|
|
@@ -4325,17 +4388,11 @@ var TypeAnalyzer = class {
|
|
|
4325
4388
|
});
|
|
4326
4389
|
}
|
|
4327
4390
|
const imported = this.importedTypes.get(node.base);
|
|
4328
|
-
if (imported)
|
|
4329
|
-
if (!imported.params.length) return imported.type;
|
|
4330
|
-
const subst = /* @__PURE__ */ new Map();
|
|
4331
|
-
imported.params.forEach((name2, i) => {
|
|
4332
|
-
const arg = node.typeArguments[i];
|
|
4333
|
-
subst.set(name2, arg ? this.resolveType(arg) : unknownType);
|
|
4334
|
-
});
|
|
4335
|
-
return this.reduceType(substitute(imported.type, subst));
|
|
4336
|
-
}
|
|
4391
|
+
if (imported) return this.importedType(imported, node.typeArguments);
|
|
4337
4392
|
const lib = this.options.libTypes?.[node.base];
|
|
4338
4393
|
if (lib) return lib;
|
|
4394
|
+
} else if (this.importedTypes.has(name)) {
|
|
4395
|
+
return this.importedType(this.importedTypes.get(name), node.typeArguments);
|
|
4339
4396
|
} else if (this.aliasDefs.has(name)) {
|
|
4340
4397
|
return this.expand({
|
|
4341
4398
|
kind: "genericRef",
|
|
@@ -4939,6 +4996,14 @@ var TypeAnalyzer = class {
|
|
|
4939
4996
|
};
|
|
4940
4997
|
if (resolving && !exports2) report(stmt.source, `Cannot find module '${specifier}'`);
|
|
4941
4998
|
const usable = exports2 && !exports2.partial ? exports2 : void 0;
|
|
4999
|
+
if (stmt.namespaceImport) {
|
|
5000
|
+
const id = this.bindingIdByName(stmt.namespaceImport.name, stmt.namespaceImport);
|
|
5001
|
+
if (id !== void 0) {
|
|
5002
|
+
const members = [...usable?.values ?? []].map(([name, type]) => [name, { type, optional: false, readonly: true }]);
|
|
5003
|
+
if (usable?.default) members.push(["default", { type: usable.default, optional: false, readonly: true }]);
|
|
5004
|
+
this.bindingType.set(id, usable ? objectType(members) : anyType);
|
|
5005
|
+
}
|
|
5006
|
+
}
|
|
4942
5007
|
if (stmt.defaultImport) {
|
|
4943
5008
|
if (usable && usable.default === void 0) {
|
|
4944
5009
|
report(stmt.defaultImport, `Module '${specifier}' has no default export`);
|
|
@@ -5096,7 +5161,10 @@ var TypeAnalyzer = class {
|
|
|
5096
5161
|
applyContext(expr, expected) {
|
|
5097
5162
|
let e = expr;
|
|
5098
5163
|
while (e.type === "ParenthesizedExpression") e = e.expression;
|
|
5099
|
-
if (
|
|
5164
|
+
if (!expected) return;
|
|
5165
|
+
if (e.type === "ArrayExpression") return this.applyArrayContext(e, expected);
|
|
5166
|
+
if (e.type === "TableExpression") return this.applyTableContext(e, expected);
|
|
5167
|
+
if (e.type !== "FunctionExpression") return;
|
|
5100
5168
|
const members = expected.kind === "union" ? expected.types : [expected];
|
|
5101
5169
|
const signatures = members.flatMap((m) => this.overloadsOf(this.expand(m)));
|
|
5102
5170
|
if (!signatures.length) return;
|
|
@@ -5112,6 +5180,46 @@ var TypeAnalyzer = class {
|
|
|
5112
5180
|
this.contextualParams.set(p, containsTypeParam(t) ? anyType : t);
|
|
5113
5181
|
});
|
|
5114
5182
|
}
|
|
5183
|
+
/** What an array literal is expected to be: an empty one takes that type
|
|
5184
|
+
* outright — `let queue: thread[] = []` is a `thread[]`, as in TypeScript
|
|
5185
|
+
* — and the elements of any other get the element type as their own
|
|
5186
|
+
* context. */
|
|
5187
|
+
contextualArrays = /* @__PURE__ */ new WeakMap();
|
|
5188
|
+
applyArrayContext(e, expected) {
|
|
5189
|
+
const target = this.expectedMembers(expected).find((m) => m.kind === "array" || m.kind === "tuple");
|
|
5190
|
+
if (!target) return;
|
|
5191
|
+
if (!e.elements.length) {
|
|
5192
|
+
if (!containsTypeParam(target)) this.contextualArrays.set(e, target);
|
|
5193
|
+
return;
|
|
5194
|
+
}
|
|
5195
|
+
e.elements.forEach((element, i) => {
|
|
5196
|
+
if (element.type === "SpreadElement") return;
|
|
5197
|
+
const elementType = target.kind === "array" ? target.element : target.elements[i];
|
|
5198
|
+
this.applyContext(element, elementType);
|
|
5199
|
+
});
|
|
5200
|
+
}
|
|
5201
|
+
/** `{ list: [] }` where `{ list: thread[] }` is expected: each field's
|
|
5202
|
+
* value gets its property's type as context. */
|
|
5203
|
+
applyTableContext(e, expected) {
|
|
5204
|
+
const objects = this.expectedMembers(expected).filter((m) => m.kind === "object");
|
|
5205
|
+
if (!objects.length) return;
|
|
5206
|
+
for (const field of e.fields) {
|
|
5207
|
+
if (field.type !== "TableFieldNamed") continue;
|
|
5208
|
+
const key = field.key.type === "Identifier" ? field.key.name : field.key.value;
|
|
5209
|
+
const types = objects.flatMap((o) => {
|
|
5210
|
+
const property = o.properties.get(key);
|
|
5211
|
+
return property ? [property.type] : o.indexer ? [o.indexer.value] : [];
|
|
5212
|
+
});
|
|
5213
|
+
if (types.length) this.applyContext(field.value, union(types));
|
|
5214
|
+
}
|
|
5215
|
+
}
|
|
5216
|
+
/** The members of an expected type worth matching a literal against:
|
|
5217
|
+
* aliases seen through, `nil` left out. */
|
|
5218
|
+
expectedMembers(expected) {
|
|
5219
|
+
const t = this.expand(expected);
|
|
5220
|
+
const members = t.kind === "union" ? t.types : [t];
|
|
5221
|
+
return members.map((m) => this.expand(m)).filter((m) => !(m.kind === "primitive" && m.name === "nil"));
|
|
5222
|
+
}
|
|
5115
5223
|
/** The parameter type each written argument lands on, across `fns`. */
|
|
5116
5224
|
expectedArguments(written, fns, selfOf) {
|
|
5117
5225
|
return written.map((_, j) => {
|
|
@@ -5686,8 +5794,9 @@ var TypeAnalyzer = class {
|
|
|
5686
5794
|
return this.resolveType(expr.typeAnnotation);
|
|
5687
5795
|
}
|
|
5688
5796
|
case "SatisfiesExpression": {
|
|
5689
|
-
const actual = this.infer(expr.expression, env);
|
|
5690
5797
|
const declared = this.resolveType(expr.typeAnnotation);
|
|
5798
|
+
this.applyContext(expr.expression, declared);
|
|
5799
|
+
const actual = this.infer(expr.expression, env);
|
|
5691
5800
|
if (this.emitDiagnostics && declared.kind !== "any" && !this.fitsAnnotation(expr.expression, declared, actual, env)) {
|
|
5692
5801
|
this.diagnostics.push({
|
|
5693
5802
|
node: expr,
|
|
@@ -5815,6 +5924,8 @@ var TypeAnalyzer = class {
|
|
|
5815
5924
|
}
|
|
5816
5925
|
}
|
|
5817
5926
|
inferArray(expr, env, asConst) {
|
|
5927
|
+
const contextual = this.contextualArrays.get(expr);
|
|
5928
|
+
if (contextual && !asConst) return contextual;
|
|
5818
5929
|
const elems = [];
|
|
5819
5930
|
let hadSpread = false;
|
|
5820
5931
|
for (const el of expr.elements) {
|
|
@@ -6468,13 +6579,13 @@ function resolveTypeLibraries(config, host = nodeHost) {
|
|
|
6468
6579
|
else problems.push({ file: config.path, message: `'${entry}' has no ${ENTRY_FILE} (or 'luaut.types' in its package.json)`, ...entryPosition(config, entry) });
|
|
6469
6580
|
continue;
|
|
6470
6581
|
}
|
|
6471
|
-
const
|
|
6472
|
-
const found =
|
|
6582
|
+
const name = entry.startsWith("@luaut/") ? entry : `@luaut/${entry}`;
|
|
6583
|
+
const found = findPackage(name, config.directory, host);
|
|
6473
6584
|
if (found) addPackage(found.directory, found.file, /* @__PURE__ */ new Set());
|
|
6474
6585
|
else {
|
|
6475
6586
|
problems.push({
|
|
6476
6587
|
file: config.path,
|
|
6477
|
-
message: `Cannot find type library '${
|
|
6588
|
+
message: `Cannot find type library '${name}'. Install it with: npm i -D ${name}`,
|
|
6478
6589
|
...entryPosition(config, entry)
|
|
6479
6590
|
});
|
|
6480
6591
|
}
|
package/dist/index.d.cts
CHANGED
|
@@ -99,11 +99,17 @@ interface ImportStatement extends BaseNode {
|
|
|
99
99
|
type: "ImportStatement";
|
|
100
100
|
/** `import Default from '...'` */
|
|
101
101
|
defaultImport?: Identifier;
|
|
102
|
+
/** `import * as Module from '...'` — the module's exports as one value. */
|
|
103
|
+
namespaceImport?: Identifier;
|
|
104
|
+
/** `import type { A } from '...'`: every name it brings in is a type and
|
|
105
|
+
* may only be used as one — never as a value. It exists for the type
|
|
106
|
+
* checker alone, and leaves nothing in compiled code. */
|
|
107
|
+
isTypeOnly?: boolean;
|
|
102
108
|
/** `import { a, b as c } from '...'` */
|
|
103
109
|
specifiers: ImportSpecifier[];
|
|
104
110
|
source: StringLiteral;
|
|
105
111
|
}
|
|
106
|
-
/** `export const x = 1`, `export let y = 2`, `export
|
|
112
|
+
/** `export const x = 1`, `export let y = 2`, `export function f() end` */
|
|
107
113
|
interface ExportStatement extends BaseNode {
|
|
108
114
|
type: "ExportStatement";
|
|
109
115
|
declaration: VariableDeclaration | FunctionDeclaration;
|
|
@@ -214,17 +220,19 @@ interface ArrayPatternElement extends BaseNode {
|
|
|
214
220
|
value: BindingTarget;
|
|
215
221
|
default?: Expression;
|
|
216
222
|
}
|
|
217
|
-
/** `
|
|
218
|
-
*
|
|
223
|
+
/** `function f() ... end` — declares `f` in the enclosing scope, visible to
|
|
224
|
+
* its own body (so it can recurse). Like TypeScript's function declaration,
|
|
225
|
+
* the name cannot be reassigned. `function a.b() end` and `function T:m() end`
|
|
226
|
+
* assign to a member instead: see `FunctionDeclarationStatement`. */
|
|
219
227
|
interface FunctionDeclaration extends BaseNode {
|
|
220
228
|
type: "FunctionDeclaration";
|
|
221
|
-
kind: "const" | "let";
|
|
222
229
|
name: Identifier;
|
|
223
230
|
func: FunctionBody;
|
|
224
231
|
attributes?: string[];
|
|
225
232
|
/** TS-style overload signatures preceding the implementation (`func`). */
|
|
226
233
|
signatures?: FunctionSignature[];
|
|
227
234
|
}
|
|
235
|
+
/** `function a.b() end` / `function T:m() end` — defines a member. */
|
|
228
236
|
interface FunctionDeclarationStatement extends BaseNode {
|
|
229
237
|
type: "FunctionDeclarationStatement";
|
|
230
238
|
target: FunctionName;
|
|
@@ -758,8 +766,12 @@ interface Binding {
|
|
|
758
766
|
* bindings are never given a `declarationNode` from assignment
|
|
759
767
|
* inference, since they're not really "defined" in this file. */
|
|
760
768
|
isBuiltin?: boolean;
|
|
761
|
-
/** True for a
|
|
769
|
+
/** True for a binding that cannot be reassigned: a `const`, an import, or
|
|
770
|
+
* a function declaration. */
|
|
762
771
|
isConst?: boolean;
|
|
772
|
+
/** Set when the binding comes from something other than `const` / `let`,
|
|
773
|
+
* which is also what an error about reassigning it names. */
|
|
774
|
+
declaredBy?: "import" | "namespace" | "function" | "type";
|
|
763
775
|
}
|
|
764
776
|
interface ScopeDiagnostic {
|
|
765
777
|
/** the offending node (redeclaration site, or assignment target) */
|
|
@@ -774,7 +786,7 @@ interface ScopeDiagnostic {
|
|
|
774
786
|
};
|
|
775
787
|
};
|
|
776
788
|
message: string;
|
|
777
|
-
kind: "redeclare" | "const-assign";
|
|
789
|
+
kind: "redeclare" | "const-assign" | "type-only";
|
|
778
790
|
}
|
|
779
791
|
interface ScopeAnalysis {
|
|
780
792
|
/** Every Identifier that appears in a variable *usage* position (i.e.
|
|
@@ -1132,7 +1144,7 @@ interface ExportedType {
|
|
|
1132
1144
|
}
|
|
1133
1145
|
/** What a module makes available to `import`. See `moduleExports`. */
|
|
1134
1146
|
interface ModuleExports {
|
|
1135
|
-
/** `export const` / `export let` / `export
|
|
1147
|
+
/** `export const` / `export let` / `export function` names. */
|
|
1136
1148
|
readonly values: ReadonlyMap<string, Type>;
|
|
1137
1149
|
/** `export type` names. */
|
|
1138
1150
|
readonly types: ReadonlyMap<string, ExportedType>;
|
package/dist/index.d.ts
CHANGED
|
@@ -99,11 +99,17 @@ interface ImportStatement extends BaseNode {
|
|
|
99
99
|
type: "ImportStatement";
|
|
100
100
|
/** `import Default from '...'` */
|
|
101
101
|
defaultImport?: Identifier;
|
|
102
|
+
/** `import * as Module from '...'` — the module's exports as one value. */
|
|
103
|
+
namespaceImport?: Identifier;
|
|
104
|
+
/** `import type { A } from '...'`: every name it brings in is a type and
|
|
105
|
+
* may only be used as one — never as a value. It exists for the type
|
|
106
|
+
* checker alone, and leaves nothing in compiled code. */
|
|
107
|
+
isTypeOnly?: boolean;
|
|
102
108
|
/** `import { a, b as c } from '...'` */
|
|
103
109
|
specifiers: ImportSpecifier[];
|
|
104
110
|
source: StringLiteral;
|
|
105
111
|
}
|
|
106
|
-
/** `export const x = 1`, `export let y = 2`, `export
|
|
112
|
+
/** `export const x = 1`, `export let y = 2`, `export function f() end` */
|
|
107
113
|
interface ExportStatement extends BaseNode {
|
|
108
114
|
type: "ExportStatement";
|
|
109
115
|
declaration: VariableDeclaration | FunctionDeclaration;
|
|
@@ -214,17 +220,19 @@ interface ArrayPatternElement extends BaseNode {
|
|
|
214
220
|
value: BindingTarget;
|
|
215
221
|
default?: Expression;
|
|
216
222
|
}
|
|
217
|
-
/** `
|
|
218
|
-
*
|
|
223
|
+
/** `function f() ... end` — declares `f` in the enclosing scope, visible to
|
|
224
|
+
* its own body (so it can recurse). Like TypeScript's function declaration,
|
|
225
|
+
* the name cannot be reassigned. `function a.b() end` and `function T:m() end`
|
|
226
|
+
* assign to a member instead: see `FunctionDeclarationStatement`. */
|
|
219
227
|
interface FunctionDeclaration extends BaseNode {
|
|
220
228
|
type: "FunctionDeclaration";
|
|
221
|
-
kind: "const" | "let";
|
|
222
229
|
name: Identifier;
|
|
223
230
|
func: FunctionBody;
|
|
224
231
|
attributes?: string[];
|
|
225
232
|
/** TS-style overload signatures preceding the implementation (`func`). */
|
|
226
233
|
signatures?: FunctionSignature[];
|
|
227
234
|
}
|
|
235
|
+
/** `function a.b() end` / `function T:m() end` — defines a member. */
|
|
228
236
|
interface FunctionDeclarationStatement extends BaseNode {
|
|
229
237
|
type: "FunctionDeclarationStatement";
|
|
230
238
|
target: FunctionName;
|
|
@@ -758,8 +766,12 @@ interface Binding {
|
|
|
758
766
|
* bindings are never given a `declarationNode` from assignment
|
|
759
767
|
* inference, since they're not really "defined" in this file. */
|
|
760
768
|
isBuiltin?: boolean;
|
|
761
|
-
/** True for a
|
|
769
|
+
/** True for a binding that cannot be reassigned: a `const`, an import, or
|
|
770
|
+
* a function declaration. */
|
|
762
771
|
isConst?: boolean;
|
|
772
|
+
/** Set when the binding comes from something other than `const` / `let`,
|
|
773
|
+
* which is also what an error about reassigning it names. */
|
|
774
|
+
declaredBy?: "import" | "namespace" | "function" | "type";
|
|
763
775
|
}
|
|
764
776
|
interface ScopeDiagnostic {
|
|
765
777
|
/** the offending node (redeclaration site, or assignment target) */
|
|
@@ -774,7 +786,7 @@ interface ScopeDiagnostic {
|
|
|
774
786
|
};
|
|
775
787
|
};
|
|
776
788
|
message: string;
|
|
777
|
-
kind: "redeclare" | "const-assign";
|
|
789
|
+
kind: "redeclare" | "const-assign" | "type-only";
|
|
778
790
|
}
|
|
779
791
|
interface ScopeAnalysis {
|
|
780
792
|
/** Every Identifier that appears in a variable *usage* position (i.e.
|
|
@@ -1132,7 +1144,7 @@ interface ExportedType {
|
|
|
1132
1144
|
}
|
|
1133
1145
|
/** What a module makes available to `import`. See `moduleExports`. */
|
|
1134
1146
|
interface ModuleExports {
|
|
1135
|
-
/** `export const` / `export let` / `export
|
|
1147
|
+
/** `export const` / `export let` / `export function` names. */
|
|
1136
1148
|
readonly values: ReadonlyMap<string, Type>;
|
|
1137
1149
|
/** `export type` names. */
|
|
1138
1150
|
readonly types: ReadonlyMap<string, ExportedType>;
|
package/dist/index.js
CHANGED
|
@@ -796,23 +796,14 @@ var Parser = class {
|
|
|
796
796
|
if (t.type === "Punctuator" && t.value === "@") {
|
|
797
797
|
const { attributes, start } = this.parseAttributes();
|
|
798
798
|
const next = this.current();
|
|
799
|
-
if (next.type === "Keyword" && (next.value === "const" || next.value === "let")) {
|
|
800
|
-
const stmt = this.parseVariableDeclaration();
|
|
801
|
-
if (stmt.type === "FunctionDeclaration") {
|
|
802
|
-
stmt.attributes = attributes;
|
|
803
|
-
stmt.line.start = start.line.start;
|
|
804
|
-
stmt.column.start = start.column.start;
|
|
805
|
-
}
|
|
806
|
-
return stmt;
|
|
807
|
-
}
|
|
808
799
|
if (next.type === "Keyword" && next.value === "function") {
|
|
809
|
-
const stmt = this.
|
|
800
|
+
const stmt = this.parseFunctionStatement();
|
|
810
801
|
stmt.attributes = attributes;
|
|
811
802
|
stmt.line.start = start.line.start;
|
|
812
803
|
stmt.column.start = start.column.start;
|
|
813
804
|
return stmt;
|
|
814
805
|
}
|
|
815
|
-
throw new ParseError("Expected 'function'
|
|
806
|
+
throw new ParseError("Expected 'function' after an attribute", next.line.start, next.column.start);
|
|
816
807
|
}
|
|
817
808
|
if (t.type === "Keyword") {
|
|
818
809
|
switch (t.value) {
|
|
@@ -830,7 +821,7 @@ var Parser = class {
|
|
|
830
821
|
case "for":
|
|
831
822
|
return this.parseForStatement();
|
|
832
823
|
case "function":
|
|
833
|
-
return this.
|
|
824
|
+
return this.parseFunctionStatement();
|
|
834
825
|
case "return":
|
|
835
826
|
return this.parseReturnStatement();
|
|
836
827
|
case "import":
|
|
@@ -932,20 +923,30 @@ var Parser = class {
|
|
|
932
923
|
parseImportStatement() {
|
|
933
924
|
const start = this.current();
|
|
934
925
|
this.advance();
|
|
926
|
+
const next = this.peek(1);
|
|
927
|
+
const isTypeOnly = this.checkIdentifierValue("type") && (next.type === "Punctuator" && next.value === "{" || next.type === "Operator" && next.value === "*" || next.type === "Identifier");
|
|
928
|
+
if (isTypeOnly) this.advance();
|
|
935
929
|
let defaultImport;
|
|
936
930
|
const specifiers = [];
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
this.
|
|
942
|
-
this.
|
|
943
|
-
this.
|
|
931
|
+
let namespaceImport;
|
|
932
|
+
const parseBindings = () => {
|
|
933
|
+
if (this.checkOperator("*")) {
|
|
934
|
+
this.advance();
|
|
935
|
+
if (!this.checkKeyword("as")) this.error("Expected 'as' after 'import *'");
|
|
936
|
+
this.advance();
|
|
937
|
+
namespaceImport = this.parseIdentifier();
|
|
938
|
+
return;
|
|
944
939
|
}
|
|
945
|
-
} else {
|
|
946
940
|
this.expectPunctuator("{");
|
|
947
941
|
this.parseImportSpecifierList(specifiers);
|
|
948
942
|
this.expectPunctuator("}");
|
|
943
|
+
};
|
|
944
|
+
if (this.checkType("Identifier")) {
|
|
945
|
+
const nameTok = this.expectIdentifier();
|
|
946
|
+
defaultImport = { type: "Identifier", name: nameTok.value, ...spanFrom(nameTok, nameTok) };
|
|
947
|
+
if (this.matchPunctuator(",")) parseBindings();
|
|
948
|
+
} else {
|
|
949
|
+
parseBindings();
|
|
949
950
|
}
|
|
950
951
|
if (!this.checkKeyword("from")) {
|
|
951
952
|
this.error("Expected 'from' in import statement");
|
|
@@ -962,7 +963,15 @@ var Parser = class {
|
|
|
962
963
|
raw: sourceTok.raw,
|
|
963
964
|
...spanFrom(sourceTok, sourceTok)
|
|
964
965
|
};
|
|
965
|
-
return {
|
|
966
|
+
return {
|
|
967
|
+
type: "ImportStatement",
|
|
968
|
+
defaultImport,
|
|
969
|
+
namespaceImport,
|
|
970
|
+
specifiers,
|
|
971
|
+
source,
|
|
972
|
+
isTypeOnly: isTypeOnly || void 0,
|
|
973
|
+
...spanFrom(start, this.previous())
|
|
974
|
+
};
|
|
966
975
|
}
|
|
967
976
|
parseImportSpecifierList(out) {
|
|
968
977
|
if (this.checkPunctuator("}")) return;
|
|
@@ -998,7 +1007,7 @@ var Parser = class {
|
|
|
998
1007
|
...spanFrom(sourceTok, sourceTok)
|
|
999
1008
|
};
|
|
1000
1009
|
}
|
|
1001
|
-
// `export const ...` / `export let ...` / `export
|
|
1010
|
+
// `export const ...` / `export let ...` / `export function ...` /
|
|
1002
1011
|
// `export type ...` / `export default <expr>`
|
|
1003
1012
|
parseExportStatement() {
|
|
1004
1013
|
const start = this.current();
|
|
@@ -1016,6 +1025,11 @@ var Parser = class {
|
|
|
1016
1025
|
const declaration = this.parseVariableDeclaration();
|
|
1017
1026
|
return { type: "ExportStatement", declaration, ...spanFrom(start, this.previous()) };
|
|
1018
1027
|
}
|
|
1028
|
+
if (this.checkKeyword("function")) {
|
|
1029
|
+
const declaration = this.parseFunctionStatement();
|
|
1030
|
+
if (declaration.type !== "FunctionDeclaration") this.error("An exported function needs a plain name: 'export function name()'");
|
|
1031
|
+
return { type: "ExportStatement", declaration, ...spanFrom(start, this.previous()) };
|
|
1032
|
+
}
|
|
1019
1033
|
if (this.checkPunctuator("{")) {
|
|
1020
1034
|
this.advance();
|
|
1021
1035
|
const specifiers = [];
|
|
@@ -1039,15 +1053,15 @@ var Parser = class {
|
|
|
1039
1053
|
const source = this.parseModuleSource();
|
|
1040
1054
|
return { type: "ExportAllStatement", source, ...spanFrom(start, this.previous()) };
|
|
1041
1055
|
}
|
|
1042
|
-
this.error("Expected 'const', 'let', 'type', 'default', '{' or '*' after 'export'");
|
|
1056
|
+
this.error("Expected 'const', 'let', 'function', 'type', 'default', '{' or '*' after 'export'");
|
|
1043
1057
|
}
|
|
1044
|
-
// `const x = ...` / `let x, y =
|
|
1058
|
+
// `const x = ...` / `let x, y = ...`.
|
|
1045
1059
|
// luaut has no `local` — `const` bindings are immutable, `let` mutable.
|
|
1046
1060
|
parseVariableDeclaration() {
|
|
1047
1061
|
const start = this.current();
|
|
1048
1062
|
const kind = this.advance().value;
|
|
1049
|
-
if (this.
|
|
1050
|
-
|
|
1063
|
+
if (this.checkKeyword("function")) {
|
|
1064
|
+
this.error(`A function is declared as 'function name()'; '${kind}' does not apply to functions`);
|
|
1051
1065
|
}
|
|
1052
1066
|
const names = [this.parseBindingTarget(true)];
|
|
1053
1067
|
while (this.matchPunctuator(",")) {
|
|
@@ -1061,31 +1075,6 @@ var Parser = class {
|
|
|
1061
1075
|
}
|
|
1062
1076
|
return { type: "VariableDeclaration", kind, names, init, ...spanFrom(start, this.previous()) };
|
|
1063
1077
|
}
|
|
1064
|
-
/** `const/let function` — `function` already consumed. Collects TS-style
|
|
1065
|
-
* overload signatures. */
|
|
1066
|
-
parseFunctionDeclarationRest(start, kind) {
|
|
1067
|
-
const name = this.parseIdentifier();
|
|
1068
|
-
const signatures = [];
|
|
1069
|
-
while (true) {
|
|
1070
|
-
const head = this.parseFunctionHead();
|
|
1071
|
-
if (this.isOverloadContinuation(name.name, kind)) {
|
|
1072
|
-
signatures.push(this.headToSignature(head));
|
|
1073
|
-
this.advance();
|
|
1074
|
-
this.expectKeyword("function");
|
|
1075
|
-
this.parseIdentifier();
|
|
1076
|
-
continue;
|
|
1077
|
-
}
|
|
1078
|
-
const func = this.headToBody(head);
|
|
1079
|
-
return {
|
|
1080
|
-
type: "FunctionDeclaration",
|
|
1081
|
-
kind,
|
|
1082
|
-
name,
|
|
1083
|
-
func,
|
|
1084
|
-
signatures: signatures.length ? signatures : void 0,
|
|
1085
|
-
...spanFrom(start, this.previous())
|
|
1086
|
-
};
|
|
1087
|
-
}
|
|
1088
|
-
}
|
|
1089
1078
|
parseIfStatement() {
|
|
1090
1079
|
const start = this.current();
|
|
1091
1080
|
this.expectKeyword("if");
|
|
@@ -1175,7 +1164,9 @@ var Parser = class {
|
|
|
1175
1164
|
...spanFrom(start, this.previous())
|
|
1176
1165
|
};
|
|
1177
1166
|
}
|
|
1178
|
-
|
|
1167
|
+
/** `function name() end` declares `name`; `function a.b() end` and
|
|
1168
|
+
* `function T:m() end` define a member. */
|
|
1169
|
+
parseFunctionStatement() {
|
|
1179
1170
|
const start = this.current();
|
|
1180
1171
|
this.expectKeyword("function");
|
|
1181
1172
|
const target = this.parseFunctionName();
|
|
@@ -1191,6 +1182,15 @@ var Parser = class {
|
|
|
1191
1182
|
continue;
|
|
1192
1183
|
}
|
|
1193
1184
|
const func = this.headToBody(head);
|
|
1185
|
+
if (simpleName !== void 0) {
|
|
1186
|
+
return {
|
|
1187
|
+
type: "FunctionDeclaration",
|
|
1188
|
+
name: target.base,
|
|
1189
|
+
func,
|
|
1190
|
+
signatures: signatures.length ? signatures : void 0,
|
|
1191
|
+
...spanFrom(start, this.previous())
|
|
1192
|
+
};
|
|
1193
|
+
}
|
|
1194
1194
|
if (isMethod) {
|
|
1195
1195
|
func.params.unshift({ type: "FunctionParameter", name: "self", ...spanFrom(target, target) });
|
|
1196
1196
|
func.isMethod = true;
|
|
@@ -1207,12 +1207,8 @@ var Parser = class {
|
|
|
1207
1207
|
}
|
|
1208
1208
|
/** After a bodyless function head, is the next token the start of another
|
|
1209
1209
|
* declaration for the same simple `name` (making the head an overload
|
|
1210
|
-
* signature rather than an implementation)?
|
|
1211
|
-
|
|
1212
|
-
isOverloadContinuation(name, kind) {
|
|
1213
|
-
if (kind) {
|
|
1214
|
-
return this.checkKeyword(kind) && this.peek(1).type === "Keyword" && this.peek(1).value === "function" && this.peek(2).type === "Identifier" && this.peek(2).value === name;
|
|
1215
|
-
}
|
|
1210
|
+
* signature rather than an implementation)? */
|
|
1211
|
+
isOverloadContinuation(name) {
|
|
1216
1212
|
return this.checkKeyword("function") && this.peek(1).type === "Identifier" && this.peek(1).value === name;
|
|
1217
1213
|
}
|
|
1218
1214
|
parseFunctionName() {
|
|
@@ -2519,7 +2515,7 @@ var Analyzer = class {
|
|
|
2519
2515
|
};
|
|
2520
2516
|
}
|
|
2521
2517
|
// ---------------- declaration / resolution primitives ----------------
|
|
2522
|
-
declare(scope, name, kind, node, isConst = false) {
|
|
2518
|
+
declare(scope, name, kind, node, isConst = false, declaredBy) {
|
|
2523
2519
|
if (scope.declarations.has(name) && scope !== this.globalScope) {
|
|
2524
2520
|
this.diagnostics.push({
|
|
2525
2521
|
node,
|
|
@@ -2528,7 +2524,7 @@ var Analyzer = class {
|
|
|
2528
2524
|
});
|
|
2529
2525
|
}
|
|
2530
2526
|
const id = this.nextId++;
|
|
2531
|
-
this.bindings.set(id, { id, name, kind, declarationNode: node, references: [], isConst });
|
|
2527
|
+
this.bindings.set(id, { id, name, kind, declarationNode: node, references: [], isConst, declaredBy });
|
|
2532
2528
|
scope.declarations.set(name, id);
|
|
2533
2529
|
return id;
|
|
2534
2530
|
}
|
|
@@ -2562,6 +2558,19 @@ var Analyzer = class {
|
|
|
2562
2558
|
const id = this.resolve(scope, identifier.name);
|
|
2563
2559
|
this.bindingOf.set(identifier, id);
|
|
2564
2560
|
this.bindings.get(id).references.push(identifier);
|
|
2561
|
+
if (this.typeQueryDepth === 0) this.checkTypeOnly(id, identifier);
|
|
2562
|
+
}
|
|
2563
|
+
/** Inside `typeof x` in a type, where a type-only import may be named. */
|
|
2564
|
+
typeQueryDepth = 0;
|
|
2565
|
+
/** A name from `import type` used as a value. */
|
|
2566
|
+
checkTypeOnly(id, node) {
|
|
2567
|
+
const b = this.bindings.get(id);
|
|
2568
|
+
if (b.declaredBy !== "type") return;
|
|
2569
|
+
this.diagnostics.push({
|
|
2570
|
+
node,
|
|
2571
|
+
message: `'${b.name}' is imported with 'import type' and can only be used as a type`,
|
|
2572
|
+
kind: "type-only"
|
|
2573
|
+
});
|
|
2565
2574
|
}
|
|
2566
2575
|
/** For assignment-like targets (`x = ...`, `function foo() end`): if
|
|
2567
2576
|
* this resolved to a global with no declaration site yet, treat this
|
|
@@ -2578,14 +2587,34 @@ var Analyzer = class {
|
|
|
2578
2587
|
this.bindingOf.set(identifier, id);
|
|
2579
2588
|
this.bindings.get(id).references.push(identifier);
|
|
2580
2589
|
this.recordPossibleGlobalDefinition(id, identifier);
|
|
2590
|
+
this.checkTypeOnly(id, identifier);
|
|
2581
2591
|
this.checkConstAssign(id, identifier);
|
|
2582
2592
|
}
|
|
2593
|
+
/** `Module.x = 1` through `import * as Module`: a module's exports belong
|
|
2594
|
+
* to it and are read-only, as in ES modules. Deeper writes (`Module.x.y`)
|
|
2595
|
+
* change the value, not the module, and are fine. */
|
|
2596
|
+
checkModuleWrite(target) {
|
|
2597
|
+
if (target.type !== "MemberExpression" && target.type !== "IndexExpression") return;
|
|
2598
|
+
if (target.object.type !== "Identifier") return;
|
|
2599
|
+
const id = this.bindingOf.get(target.object);
|
|
2600
|
+
if (id !== void 0 && this.bindings.get(id).declaredBy === "namespace") {
|
|
2601
|
+
this.moduleWriteError(target.object.name, target);
|
|
2602
|
+
}
|
|
2603
|
+
}
|
|
2604
|
+
moduleWriteError(name, node) {
|
|
2605
|
+
this.diagnostics.push({
|
|
2606
|
+
node,
|
|
2607
|
+
message: `Cannot assign to a member of '${name}' \u2014 a module's exports are read-only`,
|
|
2608
|
+
kind: "const-assign"
|
|
2609
|
+
});
|
|
2610
|
+
}
|
|
2583
2611
|
checkConstAssign(id, node) {
|
|
2584
2612
|
const b = this.bindings.get(id);
|
|
2613
|
+
if (b.declaredBy === "type") return;
|
|
2585
2614
|
if (b.isConst) {
|
|
2586
2615
|
this.diagnostics.push({
|
|
2587
2616
|
node,
|
|
2588
|
-
message: `Cannot assign to '${b.name}' \u2014 it is a const`,
|
|
2617
|
+
message: `Cannot assign to '${b.name}' \u2014 it is ${b.declaredBy === "import" || b.declaredBy === "namespace" ? "an import" : b.declaredBy === "function" ? "a function" : "a const"}`,
|
|
2589
2618
|
kind: "const-assign"
|
|
2590
2619
|
});
|
|
2591
2620
|
}
|
|
@@ -2626,6 +2655,7 @@ var Analyzer = class {
|
|
|
2626
2655
|
const id = this.resolve(scope, t.name);
|
|
2627
2656
|
this.bindingOf.set(t, id);
|
|
2628
2657
|
this.recordPossibleGlobalDefinition(id, t);
|
|
2658
|
+
this.checkTypeOnly(id, t);
|
|
2629
2659
|
this.checkConstAssign(id, t);
|
|
2630
2660
|
return;
|
|
2631
2661
|
}
|
|
@@ -2669,7 +2699,7 @@ var Analyzer = class {
|
|
|
2669
2699
|
return;
|
|
2670
2700
|
}
|
|
2671
2701
|
case "FunctionDeclaration": {
|
|
2672
|
-
this.declare(scope, stmt.name.name, "local", stmt.name,
|
|
2702
|
+
this.declare(scope, stmt.name.name, "local", stmt.name, true, "function");
|
|
2673
2703
|
for (const signature of stmt.signatures ?? []) this.visitSignature(signature, scope);
|
|
2674
2704
|
this.visitFunctionBody(stmt.func, scope);
|
|
2675
2705
|
return;
|
|
@@ -2679,6 +2709,11 @@ var Analyzer = class {
|
|
|
2679
2709
|
this.referenceAsAssignmentTarget(scope, stmt.target.base);
|
|
2680
2710
|
} else {
|
|
2681
2711
|
this.reference(scope, stmt.target.base);
|
|
2712
|
+
const id = this.bindingOf.get(stmt.target.base);
|
|
2713
|
+
const depth = stmt.target.path.length + (stmt.target.method ? 1 : 0);
|
|
2714
|
+
if (id !== void 0 && depth === 1 && this.bindings.get(id).declaredBy === "namespace") {
|
|
2715
|
+
this.moduleWriteError(stmt.target.base.name, stmt.target);
|
|
2716
|
+
}
|
|
2682
2717
|
}
|
|
2683
2718
|
for (const signature of stmt.signatures ?? []) this.visitSignature(signature, scope);
|
|
2684
2719
|
this.visitFunctionBody(stmt.func, scope, stmt.isMethod);
|
|
@@ -2693,6 +2728,7 @@ var Analyzer = class {
|
|
|
2693
2728
|
this.assignPattern(scope, target);
|
|
2694
2729
|
} else {
|
|
2695
2730
|
this.visitExpression(target, scope);
|
|
2731
|
+
this.checkModuleWrite(target);
|
|
2696
2732
|
}
|
|
2697
2733
|
}
|
|
2698
2734
|
return;
|
|
@@ -2705,6 +2741,7 @@ var Analyzer = class {
|
|
|
2705
2741
|
if (id !== void 0) this.checkConstAssign(id, stmt.target);
|
|
2706
2742
|
} else {
|
|
2707
2743
|
this.visitExpression(stmt.target, scope);
|
|
2744
|
+
this.checkModuleWrite(stmt.target);
|
|
2708
2745
|
}
|
|
2709
2746
|
return;
|
|
2710
2747
|
}
|
|
@@ -2766,11 +2803,15 @@ var Analyzer = class {
|
|
|
2766
2803
|
this.visitType(stmt.definition, scope);
|
|
2767
2804
|
return;
|
|
2768
2805
|
case "ImportStatement": {
|
|
2806
|
+
const typeOnly = stmt.isTypeOnly ? "type" : void 0;
|
|
2769
2807
|
if (stmt.defaultImport) {
|
|
2770
|
-
this.declare(scope, stmt.defaultImport.name, "local", stmt.defaultImport);
|
|
2808
|
+
this.declare(scope, stmt.defaultImport.name, "local", stmt.defaultImport, true, typeOnly ?? "import");
|
|
2809
|
+
}
|
|
2810
|
+
if (stmt.namespaceImport) {
|
|
2811
|
+
this.declare(scope, stmt.namespaceImport.name, "local", stmt.namespaceImport, true, typeOnly ?? "namespace");
|
|
2771
2812
|
}
|
|
2772
2813
|
for (const spec of stmt.specifiers) {
|
|
2773
|
-
this.declare(scope, spec.local.name, "local", spec.local);
|
|
2814
|
+
this.declare(scope, spec.local.name, "local", spec.local, true, typeOnly ?? "import");
|
|
2774
2815
|
}
|
|
2775
2816
|
return;
|
|
2776
2817
|
}
|
|
@@ -2829,7 +2870,12 @@ var Analyzer = class {
|
|
|
2829
2870
|
return;
|
|
2830
2871
|
}
|
|
2831
2872
|
if (value.type === "TypeofTypeNode") {
|
|
2832
|
-
this.
|
|
2873
|
+
this.typeQueryDepth++;
|
|
2874
|
+
try {
|
|
2875
|
+
this.visitExpression(value.expression, scope);
|
|
2876
|
+
} finally {
|
|
2877
|
+
this.typeQueryDepth--;
|
|
2878
|
+
}
|
|
2833
2879
|
return;
|
|
2834
2880
|
}
|
|
2835
2881
|
for (const key of Object.keys(value)) {
|
|
@@ -3988,6 +4034,13 @@ var TypeAnalyzer = class {
|
|
|
3988
4034
|
if (stmt.type !== "ImportStatement") continue;
|
|
3989
4035
|
const exports = this.moduleFor(stmt.source.value);
|
|
3990
4036
|
if (!exports) continue;
|
|
4037
|
+
if (stmt.namespaceImport) {
|
|
4038
|
+
for (const [name, exported] of exports.types) {
|
|
4039
|
+
const qualified = `${stmt.namespaceImport.name}.${name}`;
|
|
4040
|
+
this.importedTypes.set(qualified, exported);
|
|
4041
|
+
this.aliases.set(qualified, exported.type);
|
|
4042
|
+
}
|
|
4043
|
+
}
|
|
3991
4044
|
for (const s of stmt.specifiers) {
|
|
3992
4045
|
const exported = exports.types.get(s.imported.name);
|
|
3993
4046
|
if (exported) {
|
|
@@ -4182,6 +4235,16 @@ var TypeAnalyzer = class {
|
|
|
4182
4235
|
});
|
|
4183
4236
|
return subst;
|
|
4184
4237
|
}
|
|
4238
|
+
/** An imported type, with its type arguments applied. */
|
|
4239
|
+
importedType(imported, typeArguments) {
|
|
4240
|
+
if (!imported.params.length) return imported.type;
|
|
4241
|
+
const subst = /* @__PURE__ */ new Map();
|
|
4242
|
+
imported.params.forEach((name, i) => {
|
|
4243
|
+
const arg = typeArguments[i];
|
|
4244
|
+
subst.set(name, arg ? this.resolveType(arg) : unknownType);
|
|
4245
|
+
});
|
|
4246
|
+
return this.reduceType(substitute(imported.type, subst));
|
|
4247
|
+
}
|
|
4185
4248
|
// --------------------------------------------------------
|
|
4186
4249
|
// TypeNode -> Type
|
|
4187
4250
|
// --------------------------------------------------------
|
|
@@ -4232,17 +4295,11 @@ var TypeAnalyzer = class {
|
|
|
4232
4295
|
});
|
|
4233
4296
|
}
|
|
4234
4297
|
const imported = this.importedTypes.get(node.base);
|
|
4235
|
-
if (imported)
|
|
4236
|
-
if (!imported.params.length) return imported.type;
|
|
4237
|
-
const subst = /* @__PURE__ */ new Map();
|
|
4238
|
-
imported.params.forEach((name2, i) => {
|
|
4239
|
-
const arg = node.typeArguments[i];
|
|
4240
|
-
subst.set(name2, arg ? this.resolveType(arg) : unknownType);
|
|
4241
|
-
});
|
|
4242
|
-
return this.reduceType(substitute(imported.type, subst));
|
|
4243
|
-
}
|
|
4298
|
+
if (imported) return this.importedType(imported, node.typeArguments);
|
|
4244
4299
|
const lib = this.options.libTypes?.[node.base];
|
|
4245
4300
|
if (lib) return lib;
|
|
4301
|
+
} else if (this.importedTypes.has(name)) {
|
|
4302
|
+
return this.importedType(this.importedTypes.get(name), node.typeArguments);
|
|
4246
4303
|
} else if (this.aliasDefs.has(name)) {
|
|
4247
4304
|
return this.expand({
|
|
4248
4305
|
kind: "genericRef",
|
|
@@ -4846,6 +4903,14 @@ var TypeAnalyzer = class {
|
|
|
4846
4903
|
};
|
|
4847
4904
|
if (resolving && !exports) report(stmt.source, `Cannot find module '${specifier}'`);
|
|
4848
4905
|
const usable = exports && !exports.partial ? exports : void 0;
|
|
4906
|
+
if (stmt.namespaceImport) {
|
|
4907
|
+
const id = this.bindingIdByName(stmt.namespaceImport.name, stmt.namespaceImport);
|
|
4908
|
+
if (id !== void 0) {
|
|
4909
|
+
const members = [...usable?.values ?? []].map(([name, type]) => [name, { type, optional: false, readonly: true }]);
|
|
4910
|
+
if (usable?.default) members.push(["default", { type: usable.default, optional: false, readonly: true }]);
|
|
4911
|
+
this.bindingType.set(id, usable ? objectType(members) : anyType);
|
|
4912
|
+
}
|
|
4913
|
+
}
|
|
4849
4914
|
if (stmt.defaultImport) {
|
|
4850
4915
|
if (usable && usable.default === void 0) {
|
|
4851
4916
|
report(stmt.defaultImport, `Module '${specifier}' has no default export`);
|
|
@@ -5003,7 +5068,10 @@ var TypeAnalyzer = class {
|
|
|
5003
5068
|
applyContext(expr, expected) {
|
|
5004
5069
|
let e = expr;
|
|
5005
5070
|
while (e.type === "ParenthesizedExpression") e = e.expression;
|
|
5006
|
-
if (
|
|
5071
|
+
if (!expected) return;
|
|
5072
|
+
if (e.type === "ArrayExpression") return this.applyArrayContext(e, expected);
|
|
5073
|
+
if (e.type === "TableExpression") return this.applyTableContext(e, expected);
|
|
5074
|
+
if (e.type !== "FunctionExpression") return;
|
|
5007
5075
|
const members = expected.kind === "union" ? expected.types : [expected];
|
|
5008
5076
|
const signatures = members.flatMap((m) => this.overloadsOf(this.expand(m)));
|
|
5009
5077
|
if (!signatures.length) return;
|
|
@@ -5019,6 +5087,46 @@ var TypeAnalyzer = class {
|
|
|
5019
5087
|
this.contextualParams.set(p, containsTypeParam(t) ? anyType : t);
|
|
5020
5088
|
});
|
|
5021
5089
|
}
|
|
5090
|
+
/** What an array literal is expected to be: an empty one takes that type
|
|
5091
|
+
* outright — `let queue: thread[] = []` is a `thread[]`, as in TypeScript
|
|
5092
|
+
* — and the elements of any other get the element type as their own
|
|
5093
|
+
* context. */
|
|
5094
|
+
contextualArrays = /* @__PURE__ */ new WeakMap();
|
|
5095
|
+
applyArrayContext(e, expected) {
|
|
5096
|
+
const target = this.expectedMembers(expected).find((m) => m.kind === "array" || m.kind === "tuple");
|
|
5097
|
+
if (!target) return;
|
|
5098
|
+
if (!e.elements.length) {
|
|
5099
|
+
if (!containsTypeParam(target)) this.contextualArrays.set(e, target);
|
|
5100
|
+
return;
|
|
5101
|
+
}
|
|
5102
|
+
e.elements.forEach((element, i) => {
|
|
5103
|
+
if (element.type === "SpreadElement") return;
|
|
5104
|
+
const elementType = target.kind === "array" ? target.element : target.elements[i];
|
|
5105
|
+
this.applyContext(element, elementType);
|
|
5106
|
+
});
|
|
5107
|
+
}
|
|
5108
|
+
/** `{ list: [] }` where `{ list: thread[] }` is expected: each field's
|
|
5109
|
+
* value gets its property's type as context. */
|
|
5110
|
+
applyTableContext(e, expected) {
|
|
5111
|
+
const objects = this.expectedMembers(expected).filter((m) => m.kind === "object");
|
|
5112
|
+
if (!objects.length) return;
|
|
5113
|
+
for (const field of e.fields) {
|
|
5114
|
+
if (field.type !== "TableFieldNamed") continue;
|
|
5115
|
+
const key = field.key.type === "Identifier" ? field.key.name : field.key.value;
|
|
5116
|
+
const types = objects.flatMap((o) => {
|
|
5117
|
+
const property = o.properties.get(key);
|
|
5118
|
+
return property ? [property.type] : o.indexer ? [o.indexer.value] : [];
|
|
5119
|
+
});
|
|
5120
|
+
if (types.length) this.applyContext(field.value, union(types));
|
|
5121
|
+
}
|
|
5122
|
+
}
|
|
5123
|
+
/** The members of an expected type worth matching a literal against:
|
|
5124
|
+
* aliases seen through, `nil` left out. */
|
|
5125
|
+
expectedMembers(expected) {
|
|
5126
|
+
const t = this.expand(expected);
|
|
5127
|
+
const members = t.kind === "union" ? t.types : [t];
|
|
5128
|
+
return members.map((m) => this.expand(m)).filter((m) => !(m.kind === "primitive" && m.name === "nil"));
|
|
5129
|
+
}
|
|
5022
5130
|
/** The parameter type each written argument lands on, across `fns`. */
|
|
5023
5131
|
expectedArguments(written, fns, selfOf) {
|
|
5024
5132
|
return written.map((_, j) => {
|
|
@@ -5593,8 +5701,9 @@ var TypeAnalyzer = class {
|
|
|
5593
5701
|
return this.resolveType(expr.typeAnnotation);
|
|
5594
5702
|
}
|
|
5595
5703
|
case "SatisfiesExpression": {
|
|
5596
|
-
const actual = this.infer(expr.expression, env);
|
|
5597
5704
|
const declared = this.resolveType(expr.typeAnnotation);
|
|
5705
|
+
this.applyContext(expr.expression, declared);
|
|
5706
|
+
const actual = this.infer(expr.expression, env);
|
|
5598
5707
|
if (this.emitDiagnostics && declared.kind !== "any" && !this.fitsAnnotation(expr.expression, declared, actual, env)) {
|
|
5599
5708
|
this.diagnostics.push({
|
|
5600
5709
|
node: expr,
|
|
@@ -5722,6 +5831,8 @@ var TypeAnalyzer = class {
|
|
|
5722
5831
|
}
|
|
5723
5832
|
}
|
|
5724
5833
|
inferArray(expr, env, asConst) {
|
|
5834
|
+
const contextual = this.contextualArrays.get(expr);
|
|
5835
|
+
if (contextual && !asConst) return contextual;
|
|
5725
5836
|
const elems = [];
|
|
5726
5837
|
let hadSpread = false;
|
|
5727
5838
|
for (const el of expr.elements) {
|
|
@@ -6375,13 +6486,13 @@ function resolveTypeLibraries(config, host = nodeHost) {
|
|
|
6375
6486
|
else problems.push({ file: config.path, message: `'${entry}' has no ${ENTRY_FILE} (or 'luaut.types' in its package.json)`, ...entryPosition(config, entry) });
|
|
6376
6487
|
continue;
|
|
6377
6488
|
}
|
|
6378
|
-
const
|
|
6379
|
-
const found =
|
|
6489
|
+
const name = entry.startsWith("@luaut/") ? entry : `@luaut/${entry}`;
|
|
6490
|
+
const found = findPackage(name, config.directory, host);
|
|
6380
6491
|
if (found) addPackage(found.directory, found.file, /* @__PURE__ */ new Set());
|
|
6381
6492
|
else {
|
|
6382
6493
|
problems.push({
|
|
6383
6494
|
file: config.path,
|
|
6384
|
-
message: `Cannot find type library '${
|
|
6495
|
+
message: `Cannot find type library '${name}'. Install it with: npm i -D ${name}`,
|
|
6385
6496
|
...entryPosition(config, entry)
|
|
6386
6497
|
});
|
|
6387
6498
|
}
|