luaut-parser 2.0.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 +57 -4
- package/dist/index.cjs +549 -111
- package/dist/index.d.cts +56 -11
- package/dist/index.d.ts +56 -11
- package/dist/index.js +548 -111
- package/package.json +1 -3
package/dist/index.cjs
CHANGED
|
@@ -45,6 +45,7 @@ __export(index_exports, {
|
|
|
45
45
|
getBinding: () => getBinding,
|
|
46
46
|
intersection: () => intersection,
|
|
47
47
|
isAssignable: () => isAssignable,
|
|
48
|
+
isClassType: () => isClassType,
|
|
48
49
|
isGlobal: () => isGlobal,
|
|
49
50
|
isPossiblyFalsy: () => isPossiblyFalsy,
|
|
50
51
|
isPossiblyTruthy: () => isPossiblyTruthy,
|
|
@@ -888,23 +889,14 @@ var Parser = class {
|
|
|
888
889
|
if (t.type === "Punctuator" && t.value === "@") {
|
|
889
890
|
const { attributes, start } = this.parseAttributes();
|
|
890
891
|
const next = this.current();
|
|
891
|
-
if (next.type === "Keyword" && (next.value === "const" || next.value === "let")) {
|
|
892
|
-
const stmt = this.parseVariableDeclaration();
|
|
893
|
-
if (stmt.type === "FunctionDeclaration") {
|
|
894
|
-
stmt.attributes = attributes;
|
|
895
|
-
stmt.line.start = start.line.start;
|
|
896
|
-
stmt.column.start = start.column.start;
|
|
897
|
-
}
|
|
898
|
-
return stmt;
|
|
899
|
-
}
|
|
900
892
|
if (next.type === "Keyword" && next.value === "function") {
|
|
901
|
-
const stmt = this.
|
|
893
|
+
const stmt = this.parseFunctionStatement();
|
|
902
894
|
stmt.attributes = attributes;
|
|
903
895
|
stmt.line.start = start.line.start;
|
|
904
896
|
stmt.column.start = start.column.start;
|
|
905
897
|
return stmt;
|
|
906
898
|
}
|
|
907
|
-
throw new ParseError("Expected 'function'
|
|
899
|
+
throw new ParseError("Expected 'function' after an attribute", next.line.start, next.column.start);
|
|
908
900
|
}
|
|
909
901
|
if (t.type === "Keyword") {
|
|
910
902
|
switch (t.value) {
|
|
@@ -922,7 +914,7 @@ var Parser = class {
|
|
|
922
914
|
case "for":
|
|
923
915
|
return this.parseForStatement();
|
|
924
916
|
case "function":
|
|
925
|
-
return this.
|
|
917
|
+
return this.parseFunctionStatement();
|
|
926
918
|
case "return":
|
|
927
919
|
return this.parseReturnStatement();
|
|
928
920
|
case "import":
|
|
@@ -944,6 +936,9 @@ var Parser = class {
|
|
|
944
936
|
}
|
|
945
937
|
if (t.type === "Identifier" && t.value === "declare") {
|
|
946
938
|
const p1 = this.peek(1);
|
|
939
|
+
if (p1.type === "Identifier" && p1.value === "class" && this.peek(2).type === "Identifier") {
|
|
940
|
+
return this.parseDeclareClassStatement();
|
|
941
|
+
}
|
|
947
942
|
if (p1.type === "Identifier" || p1.type === "Keyword" && p1.value === "function") {
|
|
948
943
|
return this.parseDeclareStatement();
|
|
949
944
|
}
|
|
@@ -985,26 +980,66 @@ var Parser = class {
|
|
|
985
980
|
const valueType = this.parseType();
|
|
986
981
|
return { type: "DeclareStatement", name: nameTok.value, id: tokenIdentifier(nameTok), valueType, ...spanFrom(start, this.previous()) };
|
|
987
982
|
}
|
|
983
|
+
/** A declared type's name. It may be qualified once — `Enum.Material` —
|
|
984
|
+
* which is how a definitions file names types under a namespace, and how
|
|
985
|
+
* they are then written (`const m: Enum.Material`). */
|
|
986
|
+
parseTypeName() {
|
|
987
|
+
const first = this.expectIdentifier();
|
|
988
|
+
if (this.checkPunctuator(".") && this.peek(1).type === "Identifier") {
|
|
989
|
+
this.advance();
|
|
990
|
+
const second = this.expectIdentifier();
|
|
991
|
+
return { type: "Identifier", name: `${first.value}.${second.value}`, ...spanFrom(first, second) };
|
|
992
|
+
}
|
|
993
|
+
return tokenIdentifier(first);
|
|
994
|
+
}
|
|
995
|
+
// `declare class Name extends Base { member: T, ... }`
|
|
996
|
+
parseDeclareClassStatement() {
|
|
997
|
+
const start = this.current();
|
|
998
|
+
this.advance();
|
|
999
|
+
this.advance();
|
|
1000
|
+
const name = this.parseTypeName();
|
|
1001
|
+
let superclass;
|
|
1002
|
+
if (this.checkIdentifierValue("extends")) {
|
|
1003
|
+
this.advance();
|
|
1004
|
+
const base = this.parseType();
|
|
1005
|
+
if (base.type !== "TypeReference") this.error("A class can only extend another class, written by name");
|
|
1006
|
+
superclass = base;
|
|
1007
|
+
}
|
|
1008
|
+
if (!this.checkPunctuator("{")) this.error("Expected '{' to start the class body");
|
|
1009
|
+
const body = this.parseTableType();
|
|
1010
|
+
if (body.type !== "TableTypeNode") this.error("A class body lists members ('name: T'), not a mapped type");
|
|
1011
|
+
return { type: "DeclareClassStatement", name, superclass, body, ...spanFrom(start, this.previous()) };
|
|
1012
|
+
}
|
|
988
1013
|
// `import { a, b as c } from '...'` / `import Default from '...'` /
|
|
989
1014
|
// `import Default, { a } from '...'`. Compiled away entirely by the
|
|
990
1015
|
// bundler — never survives into emitted Luau.
|
|
991
1016
|
parseImportStatement() {
|
|
992
1017
|
const start = this.current();
|
|
993
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();
|
|
994
1022
|
let defaultImport;
|
|
995
1023
|
const specifiers = [];
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
this.
|
|
1001
|
-
this.
|
|
1002
|
-
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;
|
|
1003
1032
|
}
|
|
1004
|
-
} else {
|
|
1005
1033
|
this.expectPunctuator("{");
|
|
1006
1034
|
this.parseImportSpecifierList(specifiers);
|
|
1007
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();
|
|
1008
1043
|
}
|
|
1009
1044
|
if (!this.checkKeyword("from")) {
|
|
1010
1045
|
this.error("Expected 'from' in import statement");
|
|
@@ -1021,7 +1056,15 @@ var Parser = class {
|
|
|
1021
1056
|
raw: sourceTok.raw,
|
|
1022
1057
|
...spanFrom(sourceTok, sourceTok)
|
|
1023
1058
|
};
|
|
1024
|
-
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
|
+
};
|
|
1025
1068
|
}
|
|
1026
1069
|
parseImportSpecifierList(out) {
|
|
1027
1070
|
if (this.checkPunctuator("}")) return;
|
|
@@ -1057,7 +1100,7 @@ var Parser = class {
|
|
|
1057
1100
|
...spanFrom(sourceTok, sourceTok)
|
|
1058
1101
|
};
|
|
1059
1102
|
}
|
|
1060
|
-
// `export const ...` / `export let ...` / `export
|
|
1103
|
+
// `export const ...` / `export let ...` / `export function ...` /
|
|
1061
1104
|
// `export type ...` / `export default <expr>`
|
|
1062
1105
|
parseExportStatement() {
|
|
1063
1106
|
const start = this.current();
|
|
@@ -1075,6 +1118,11 @@ var Parser = class {
|
|
|
1075
1118
|
const declaration = this.parseVariableDeclaration();
|
|
1076
1119
|
return { type: "ExportStatement", declaration, ...spanFrom(start, this.previous()) };
|
|
1077
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
|
+
}
|
|
1078
1126
|
if (this.checkPunctuator("{")) {
|
|
1079
1127
|
this.advance();
|
|
1080
1128
|
const specifiers = [];
|
|
@@ -1098,15 +1146,15 @@ var Parser = class {
|
|
|
1098
1146
|
const source = this.parseModuleSource();
|
|
1099
1147
|
return { type: "ExportAllStatement", source, ...spanFrom(start, this.previous()) };
|
|
1100
1148
|
}
|
|
1101
|
-
this.error("Expected 'const', 'let', 'type', 'default', '{' or '*' after 'export'");
|
|
1149
|
+
this.error("Expected 'const', 'let', 'function', 'type', 'default', '{' or '*' after 'export'");
|
|
1102
1150
|
}
|
|
1103
|
-
// `const x = ...` / `let x, y =
|
|
1151
|
+
// `const x = ...` / `let x, y = ...`.
|
|
1104
1152
|
// luaut has no `local` — `const` bindings are immutable, `let` mutable.
|
|
1105
1153
|
parseVariableDeclaration() {
|
|
1106
1154
|
const start = this.current();
|
|
1107
1155
|
const kind = this.advance().value;
|
|
1108
|
-
if (this.
|
|
1109
|
-
|
|
1156
|
+
if (this.checkKeyword("function")) {
|
|
1157
|
+
this.error(`A function is declared as 'function name()'; '${kind}' does not apply to functions`);
|
|
1110
1158
|
}
|
|
1111
1159
|
const names = [this.parseBindingTarget(true)];
|
|
1112
1160
|
while (this.matchPunctuator(",")) {
|
|
@@ -1120,31 +1168,6 @@ var Parser = class {
|
|
|
1120
1168
|
}
|
|
1121
1169
|
return { type: "VariableDeclaration", kind, names, init, ...spanFrom(start, this.previous()) };
|
|
1122
1170
|
}
|
|
1123
|
-
/** `const/let function` — `function` already consumed. Collects TS-style
|
|
1124
|
-
* overload signatures. */
|
|
1125
|
-
parseFunctionDeclarationRest(start, kind) {
|
|
1126
|
-
const name = this.parseIdentifier();
|
|
1127
|
-
const signatures = [];
|
|
1128
|
-
while (true) {
|
|
1129
|
-
const head = this.parseFunctionHead();
|
|
1130
|
-
if (this.isOverloadContinuation(name.name, kind)) {
|
|
1131
|
-
signatures.push(this.headToSignature(head));
|
|
1132
|
-
this.advance();
|
|
1133
|
-
this.expectKeyword("function");
|
|
1134
|
-
this.parseIdentifier();
|
|
1135
|
-
continue;
|
|
1136
|
-
}
|
|
1137
|
-
const func = this.headToBody(head);
|
|
1138
|
-
return {
|
|
1139
|
-
type: "FunctionDeclaration",
|
|
1140
|
-
kind,
|
|
1141
|
-
name,
|
|
1142
|
-
func,
|
|
1143
|
-
signatures: signatures.length ? signatures : void 0,
|
|
1144
|
-
...spanFrom(start, this.previous())
|
|
1145
|
-
};
|
|
1146
|
-
}
|
|
1147
|
-
}
|
|
1148
1171
|
parseIfStatement() {
|
|
1149
1172
|
const start = this.current();
|
|
1150
1173
|
this.expectKeyword("if");
|
|
@@ -1234,7 +1257,9 @@ var Parser = class {
|
|
|
1234
1257
|
...spanFrom(start, this.previous())
|
|
1235
1258
|
};
|
|
1236
1259
|
}
|
|
1237
|
-
|
|
1260
|
+
/** `function name() end` declares `name`; `function a.b() end` and
|
|
1261
|
+
* `function T:m() end` define a member. */
|
|
1262
|
+
parseFunctionStatement() {
|
|
1238
1263
|
const start = this.current();
|
|
1239
1264
|
this.expectKeyword("function");
|
|
1240
1265
|
const target = this.parseFunctionName();
|
|
@@ -1250,6 +1275,15 @@ var Parser = class {
|
|
|
1250
1275
|
continue;
|
|
1251
1276
|
}
|
|
1252
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
|
+
}
|
|
1253
1287
|
if (isMethod) {
|
|
1254
1288
|
func.params.unshift({ type: "FunctionParameter", name: "self", ...spanFrom(target, target) });
|
|
1255
1289
|
func.isMethod = true;
|
|
@@ -1266,12 +1300,8 @@ var Parser = class {
|
|
|
1266
1300
|
}
|
|
1267
1301
|
/** After a bodyless function head, is the next token the start of another
|
|
1268
1302
|
* declaration for the same simple `name` (making the head an overload
|
|
1269
|
-
* signature rather than an implementation)?
|
|
1270
|
-
|
|
1271
|
-
isOverloadContinuation(name, kind) {
|
|
1272
|
-
if (kind) {
|
|
1273
|
-
return this.checkKeyword(kind) && this.peek(1).type === "Keyword" && this.peek(1).value === "function" && this.peek(2).type === "Identifier" && this.peek(2).value === name;
|
|
1274
|
-
}
|
|
1303
|
+
* signature rather than an implementation)? */
|
|
1304
|
+
isOverloadContinuation(name) {
|
|
1275
1305
|
return this.checkKeyword("function") && this.peek(1).type === "Identifier" && this.peek(1).value === name;
|
|
1276
1306
|
}
|
|
1277
1307
|
parseFunctionName() {
|
|
@@ -1315,8 +1345,7 @@ var Parser = class {
|
|
|
1315
1345
|
parseTypeAliasStatement() {
|
|
1316
1346
|
const start = this.current();
|
|
1317
1347
|
this.advance();
|
|
1318
|
-
const
|
|
1319
|
-
const name = { type: "Identifier", name: nameTok.value, ...spanFrom(nameTok, nameTok) };
|
|
1348
|
+
const name = this.parseTypeName();
|
|
1320
1349
|
let generics = [];
|
|
1321
1350
|
if (this.checkOperator("<")) {
|
|
1322
1351
|
generics = this.parseGenericTypeParameterList();
|
|
@@ -2579,7 +2608,7 @@ var Analyzer = class {
|
|
|
2579
2608
|
};
|
|
2580
2609
|
}
|
|
2581
2610
|
// ---------------- declaration / resolution primitives ----------------
|
|
2582
|
-
declare(scope, name, kind, node, isConst = false) {
|
|
2611
|
+
declare(scope, name, kind, node, isConst = false, declaredBy) {
|
|
2583
2612
|
if (scope.declarations.has(name) && scope !== this.globalScope) {
|
|
2584
2613
|
this.diagnostics.push({
|
|
2585
2614
|
node,
|
|
@@ -2588,7 +2617,7 @@ var Analyzer = class {
|
|
|
2588
2617
|
});
|
|
2589
2618
|
}
|
|
2590
2619
|
const id = this.nextId++;
|
|
2591
|
-
this.bindings.set(id, { id, name, kind, declarationNode: node, references: [], isConst });
|
|
2620
|
+
this.bindings.set(id, { id, name, kind, declarationNode: node, references: [], isConst, declaredBy });
|
|
2592
2621
|
scope.declarations.set(name, id);
|
|
2593
2622
|
return id;
|
|
2594
2623
|
}
|
|
@@ -2622,6 +2651,19 @@ var Analyzer = class {
|
|
|
2622
2651
|
const id = this.resolve(scope, identifier.name);
|
|
2623
2652
|
this.bindingOf.set(identifier, id);
|
|
2624
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
|
+
});
|
|
2625
2667
|
}
|
|
2626
2668
|
/** For assignment-like targets (`x = ...`, `function foo() end`): if
|
|
2627
2669
|
* this resolved to a global with no declaration site yet, treat this
|
|
@@ -2638,14 +2680,34 @@ var Analyzer = class {
|
|
|
2638
2680
|
this.bindingOf.set(identifier, id);
|
|
2639
2681
|
this.bindings.get(id).references.push(identifier);
|
|
2640
2682
|
this.recordPossibleGlobalDefinition(id, identifier);
|
|
2683
|
+
this.checkTypeOnly(id, identifier);
|
|
2641
2684
|
this.checkConstAssign(id, identifier);
|
|
2642
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
|
+
}
|
|
2643
2704
|
checkConstAssign(id, node) {
|
|
2644
2705
|
const b = this.bindings.get(id);
|
|
2706
|
+
if (b.declaredBy === "type") return;
|
|
2645
2707
|
if (b.isConst) {
|
|
2646
2708
|
this.diagnostics.push({
|
|
2647
2709
|
node,
|
|
2648
|
-
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"}`,
|
|
2649
2711
|
kind: "const-assign"
|
|
2650
2712
|
});
|
|
2651
2713
|
}
|
|
@@ -2686,6 +2748,7 @@ var Analyzer = class {
|
|
|
2686
2748
|
const id = this.resolve(scope, t.name);
|
|
2687
2749
|
this.bindingOf.set(t, id);
|
|
2688
2750
|
this.recordPossibleGlobalDefinition(id, t);
|
|
2751
|
+
this.checkTypeOnly(id, t);
|
|
2689
2752
|
this.checkConstAssign(id, t);
|
|
2690
2753
|
return;
|
|
2691
2754
|
}
|
|
@@ -2729,7 +2792,7 @@ var Analyzer = class {
|
|
|
2729
2792
|
return;
|
|
2730
2793
|
}
|
|
2731
2794
|
case "FunctionDeclaration": {
|
|
2732
|
-
this.declare(scope, stmt.name.name, "local", stmt.name,
|
|
2795
|
+
this.declare(scope, stmt.name.name, "local", stmt.name, true, "function");
|
|
2733
2796
|
for (const signature of stmt.signatures ?? []) this.visitSignature(signature, scope);
|
|
2734
2797
|
this.visitFunctionBody(stmt.func, scope);
|
|
2735
2798
|
return;
|
|
@@ -2739,6 +2802,11 @@ var Analyzer = class {
|
|
|
2739
2802
|
this.referenceAsAssignmentTarget(scope, stmt.target.base);
|
|
2740
2803
|
} else {
|
|
2741
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
|
+
}
|
|
2742
2810
|
}
|
|
2743
2811
|
for (const signature of stmt.signatures ?? []) this.visitSignature(signature, scope);
|
|
2744
2812
|
this.visitFunctionBody(stmt.func, scope, stmt.isMethod);
|
|
@@ -2753,6 +2821,7 @@ var Analyzer = class {
|
|
|
2753
2821
|
this.assignPattern(scope, target);
|
|
2754
2822
|
} else {
|
|
2755
2823
|
this.visitExpression(target, scope);
|
|
2824
|
+
this.checkModuleWrite(target);
|
|
2756
2825
|
}
|
|
2757
2826
|
}
|
|
2758
2827
|
return;
|
|
@@ -2765,6 +2834,7 @@ var Analyzer = class {
|
|
|
2765
2834
|
if (id !== void 0) this.checkConstAssign(id, stmt.target);
|
|
2766
2835
|
} else {
|
|
2767
2836
|
this.visitExpression(stmt.target, scope);
|
|
2837
|
+
this.checkModuleWrite(stmt.target);
|
|
2768
2838
|
}
|
|
2769
2839
|
return;
|
|
2770
2840
|
}
|
|
@@ -2818,16 +2888,23 @@ var Analyzer = class {
|
|
|
2818
2888
|
case "DeclareStatement":
|
|
2819
2889
|
this.visitType(stmt.valueType, scope);
|
|
2820
2890
|
return;
|
|
2891
|
+
case "DeclareClassStatement":
|
|
2892
|
+
this.visitType(stmt.body, scope);
|
|
2893
|
+
return;
|
|
2821
2894
|
case "TypeAliasStatement":
|
|
2822
2895
|
case "ExportTypeAliasStatement":
|
|
2823
2896
|
this.visitType(stmt.definition, scope);
|
|
2824
2897
|
return;
|
|
2825
2898
|
case "ImportStatement": {
|
|
2899
|
+
const typeOnly = stmt.isTypeOnly ? "type" : void 0;
|
|
2826
2900
|
if (stmt.defaultImport) {
|
|
2827
|
-
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");
|
|
2828
2905
|
}
|
|
2829
2906
|
for (const spec of stmt.specifiers) {
|
|
2830
|
-
this.declare(scope, spec.local.name, "local", spec.local);
|
|
2907
|
+
this.declare(scope, spec.local.name, "local", spec.local, true, typeOnly ?? "import");
|
|
2831
2908
|
}
|
|
2832
2909
|
return;
|
|
2833
2910
|
}
|
|
@@ -2886,7 +2963,12 @@ var Analyzer = class {
|
|
|
2886
2963
|
return;
|
|
2887
2964
|
}
|
|
2888
2965
|
if (value.type === "TypeofTypeNode") {
|
|
2889
|
-
this.
|
|
2966
|
+
this.typeQueryDepth++;
|
|
2967
|
+
try {
|
|
2968
|
+
this.visitExpression(value.expression, scope);
|
|
2969
|
+
} finally {
|
|
2970
|
+
this.typeQueryDepth--;
|
|
2971
|
+
}
|
|
2890
2972
|
return;
|
|
2891
2973
|
}
|
|
2892
2974
|
for (const key of Object.keys(value)) {
|
|
@@ -2991,6 +3073,9 @@ function analyzeScopes(program, options = {}) {
|
|
|
2991
3073
|
}
|
|
2992
3074
|
|
|
2993
3075
|
// src/ast/typeModel.ts
|
|
3076
|
+
function isClassType(t) {
|
|
3077
|
+
return t.kind === "object" && t.class !== void 0;
|
|
3078
|
+
}
|
|
2994
3079
|
function typeParam(name, constraint, isConst) {
|
|
2995
3080
|
return { kind: "typeParam", name, constraint, isConst };
|
|
2996
3081
|
}
|
|
@@ -3050,10 +3135,16 @@ function substitute(t, subst) {
|
|
|
3050
3135
|
}
|
|
3051
3136
|
case "function": {
|
|
3052
3137
|
const inner = t.typeParams ? new Map([...subst].filter(([k]) => !t.typeParams.includes(k))) : subst;
|
|
3138
|
+
let params = t.params.map((p) => ({ ...p, type: substitute(p.type, inner) }));
|
|
3139
|
+
let varargs = t.varargs && substitute(t.varargs, inner);
|
|
3140
|
+
if (varargs?.kind === "tuple" && varargs.isPack) {
|
|
3141
|
+
params = [...params, ...varargs.elements.map((type) => ({ type }))];
|
|
3142
|
+
varargs = void 0;
|
|
3143
|
+
}
|
|
3053
3144
|
return {
|
|
3054
3145
|
kind: "function",
|
|
3055
|
-
params
|
|
3056
|
-
varargs
|
|
3146
|
+
params,
|
|
3147
|
+
varargs,
|
|
3057
3148
|
returns: substitute(t.returns, inner),
|
|
3058
3149
|
typeParams: t.typeParams,
|
|
3059
3150
|
predicate: t.predicate && {
|
|
@@ -3133,7 +3224,8 @@ function unify(param, arg, vars, out) {
|
|
|
3133
3224
|
}
|
|
3134
3225
|
return;
|
|
3135
3226
|
case "object":
|
|
3136
|
-
if (
|
|
3227
|
+
if (param.class) return;
|
|
3228
|
+
if (arg.kind === "object" && !arg.class) {
|
|
3137
3229
|
for (const [k, pv] of param.properties) {
|
|
3138
3230
|
const av = arg.properties.get(k);
|
|
3139
3231
|
if (av) unify(pv.type, av.type, vars, out);
|
|
@@ -3216,7 +3308,7 @@ function widen(t) {
|
|
|
3216
3308
|
case "tuple":
|
|
3217
3309
|
return tuple(t.elements.map(widen), t.isPack);
|
|
3218
3310
|
case "object": {
|
|
3219
|
-
if (t.frozen) return t;
|
|
3311
|
+
if (t.frozen || t.class) return t;
|
|
3220
3312
|
const entries = [];
|
|
3221
3313
|
for (const [k, v] of t.properties) entries.push([k, { ...v, type: widen(v.type) }]);
|
|
3222
3314
|
const w = objectType(entries, t.indexer && { key: t.indexer.key, value: widen(t.indexer.value) });
|
|
@@ -3243,6 +3335,10 @@ function isAssignable(rawA, rawB) {
|
|
|
3243
3335
|
if (expandAlias) {
|
|
3244
3336
|
if (a.kind === "genericRef" && b.kind !== "genericRef") a = expandAlias(a);
|
|
3245
3337
|
else if (b.kind === "genericRef" && a.kind !== "genericRef") b = expandAlias(b);
|
|
3338
|
+
else if (a.kind === "genericRef" && b.kind === "genericRef" && a.name !== b.name) {
|
|
3339
|
+
a = expandAlias(a);
|
|
3340
|
+
b = expandAlias(b);
|
|
3341
|
+
}
|
|
3246
3342
|
if (a === b) return true;
|
|
3247
3343
|
}
|
|
3248
3344
|
for (let i = 0; i < comparing.length; i += 2) {
|
|
@@ -3298,6 +3394,8 @@ function isAssignableInner(a, b) {
|
|
|
3298
3394
|
}
|
|
3299
3395
|
if (a.kind === "object") {
|
|
3300
3396
|
if (b.kind !== "object") return false;
|
|
3397
|
+
if (b.class) return a.class !== void 0 && a.class.ancestors.includes(b.class.name);
|
|
3398
|
+
if (a.class && (b.indexer || b.properties.size === 0)) return false;
|
|
3301
3399
|
for (const [name, bp] of b.properties) {
|
|
3302
3400
|
const ap = a.properties.get(name);
|
|
3303
3401
|
if (!ap) {
|
|
@@ -3440,6 +3538,7 @@ function containsFreeTypeParam(t, seen, bound) {
|
|
|
3440
3538
|
case "intersection":
|
|
3441
3539
|
return t.types.some((m) => containsTypeParam(m, seen, bound));
|
|
3442
3540
|
case "object":
|
|
3541
|
+
if (t.class) return false;
|
|
3443
3542
|
return [...t.properties.values()].some((v) => containsTypeParam(v.type, seen, bound)) || !!t.indexer && (containsTypeParam(t.indexer.key, seen, bound) || containsTypeParam(t.indexer.value, seen, bound));
|
|
3444
3543
|
case "function": {
|
|
3445
3544
|
const inner = t.typeParams?.length ? /* @__PURE__ */ new Set([...bound, ...t.typeParams]) : bound;
|
|
@@ -3638,7 +3737,7 @@ function mergeObjectMembers(types) {
|
|
|
3638
3737
|
return expanded !== void 0 && expanded !== t && collect(expanded);
|
|
3639
3738
|
}
|
|
3640
3739
|
if (t.kind === "intersection") return t.types.every(collect);
|
|
3641
|
-
if (t.kind === "object") {
|
|
3740
|
+
if (t.kind === "object" && !t.class) {
|
|
3642
3741
|
objects.push(t);
|
|
3643
3742
|
return true;
|
|
3644
3743
|
}
|
|
@@ -3850,6 +3949,62 @@ function keepsLiterals(paramType) {
|
|
|
3850
3949
|
const members = paramType.constraint.kind === "union" ? paramType.constraint.types : [paramType.constraint];
|
|
3851
3950
|
return members.some((m) => m.kind === "literal");
|
|
3852
3951
|
}
|
|
3952
|
+
var AliasMap = class extends Map {
|
|
3953
|
+
pending = /* @__PURE__ */ new Map();
|
|
3954
|
+
defer(name, resolve5) {
|
|
3955
|
+
super.delete(name);
|
|
3956
|
+
this.pending.set(name, resolve5);
|
|
3957
|
+
}
|
|
3958
|
+
get(name) {
|
|
3959
|
+
const resolved = super.get(name);
|
|
3960
|
+
if (resolved !== void 0) return resolved;
|
|
3961
|
+
const resolve5 = this.pending.get(name);
|
|
3962
|
+
if (!resolve5) return void 0;
|
|
3963
|
+
this.pending.delete(name);
|
|
3964
|
+
const type = resolve5();
|
|
3965
|
+
super.set(name, type);
|
|
3966
|
+
return type;
|
|
3967
|
+
}
|
|
3968
|
+
has(name) {
|
|
3969
|
+
return super.has(name) || (this.pending?.has(name) ?? false);
|
|
3970
|
+
}
|
|
3971
|
+
set(name, type) {
|
|
3972
|
+
this.pending?.delete(name);
|
|
3973
|
+
return super.set(name, type);
|
|
3974
|
+
}
|
|
3975
|
+
delete(name) {
|
|
3976
|
+
const deferred = this.pending?.delete(name) ?? false;
|
|
3977
|
+
return super.delete(name) || deferred;
|
|
3978
|
+
}
|
|
3979
|
+
get size() {
|
|
3980
|
+
return super.size + (this.pending?.size ?? 0);
|
|
3981
|
+
}
|
|
3982
|
+
keys() {
|
|
3983
|
+
return [...super.keys(), ...this.pending?.keys() ?? []][Symbol.iterator]();
|
|
3984
|
+
}
|
|
3985
|
+
entries() {
|
|
3986
|
+
return [...this.keys()].map((name) => [name, this.get(name)])[Symbol.iterator]();
|
|
3987
|
+
}
|
|
3988
|
+
values() {
|
|
3989
|
+
return [...this.keys()].map((name) => this.get(name))[Symbol.iterator]();
|
|
3990
|
+
}
|
|
3991
|
+
forEach(callback, thisArg) {
|
|
3992
|
+
for (const [name, type] of this.entries()) callback.call(thisArg, type, name, this);
|
|
3993
|
+
}
|
|
3994
|
+
[Symbol.iterator]() {
|
|
3995
|
+
return this.entries();
|
|
3996
|
+
}
|
|
3997
|
+
};
|
|
3998
|
+
var METAMETHODS = {
|
|
3999
|
+
"+": "__add",
|
|
4000
|
+
"-": "__sub",
|
|
4001
|
+
"*": "__mul",
|
|
4002
|
+
"/": "__div",
|
|
4003
|
+
"//": "__idiv",
|
|
4004
|
+
"%": "__mod",
|
|
4005
|
+
"^": "__pow",
|
|
4006
|
+
"..": "__concat"
|
|
4007
|
+
};
|
|
3853
4008
|
function posKey(name, line, column) {
|
|
3854
4009
|
return `${name}@${line}:${column}`;
|
|
3855
4010
|
}
|
|
@@ -3870,9 +4025,12 @@ var TypeAnalyzer = class {
|
|
|
3870
4025
|
expectedTypeOf = /* @__PURE__ */ new Map();
|
|
3871
4026
|
/** Public: each alias resolved once (generic aliases keep their params as
|
|
3872
4027
|
* `typeParam` nodes in the body). */
|
|
3873
|
-
aliases =
|
|
4028
|
+
aliases = new AliasMap();
|
|
3874
4029
|
/** Uninstantiated alias definitions, for `Name<Args>` instantiation. */
|
|
3875
4030
|
aliasDefs = /* @__PURE__ */ new Map();
|
|
4031
|
+
/** See `resolveClass`. */
|
|
4032
|
+
classTypes = /* @__PURE__ */ new WeakMap();
|
|
4033
|
+
classMembers = /* @__PURE__ */ new WeakMap();
|
|
3876
4034
|
/** Generic parameters currently in lexical scope (alias body / generic fn),
|
|
3877
4035
|
* with their `extends` constraints resolved. */
|
|
3878
4036
|
typeParamScope = [];
|
|
@@ -3969,6 +4127,13 @@ var TypeAnalyzer = class {
|
|
|
3969
4127
|
if (stmt.type !== "ImportStatement") continue;
|
|
3970
4128
|
const exports2 = this.moduleFor(stmt.source.value);
|
|
3971
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
|
+
}
|
|
3972
4137
|
for (const s of stmt.specifiers) {
|
|
3973
4138
|
const exported = exports2.types.get(s.imported.name);
|
|
3974
4139
|
if (exported) {
|
|
@@ -3982,24 +4147,123 @@ var TypeAnalyzer = class {
|
|
|
3982
4147
|
for (const stmt of block.statements) {
|
|
3983
4148
|
const alias = stmt.type === "TypeAliasStatement" ? stmt : stmt.type === "ExportTypeAliasStatement" ? stmt.alias : void 0;
|
|
3984
4149
|
if (alias) this.aliasDefs.set(alias.name.name, { params: alias.generics, node: alias.definition });
|
|
4150
|
+
if (stmt.type === "DeclareClassStatement") {
|
|
4151
|
+
this.aliasDefs.set(stmt.name.name, { params: [], node: stmt.body, class: stmt });
|
|
4152
|
+
}
|
|
3985
4153
|
}
|
|
3986
4154
|
}
|
|
3987
|
-
/**
|
|
3988
|
-
|
|
3989
|
-
|
|
4155
|
+
/** A non-generic definition's type. */
|
|
4156
|
+
resolveDef(def) {
|
|
4157
|
+
return def.class ? this.classType(def.class) : this.resolveType(def.node);
|
|
4158
|
+
}
|
|
4159
|
+
/** One type per class declaration, so every mention of a class is the same
|
|
4160
|
+
* object — its own members included, which refer back to it. */
|
|
4161
|
+
classType(stmt) {
|
|
4162
|
+
return this.classTypes.get(stmt) ?? this.resolveClass(stmt);
|
|
4163
|
+
}
|
|
4164
|
+
/** A class's members are resolved the first time anyone asks for
|
|
4165
|
+
* `properties` — its own from its body, the inherited ones from its
|
|
4166
|
+
* superclass.
|
|
4167
|
+
*
|
|
4168
|
+
* Both have to wait. A definitions file for a whole engine declares
|
|
4169
|
+
* thousands of classes that all refer to one another; resolving each body
|
|
4170
|
+
* as soon as the class is named would resolve every class on every
|
|
4171
|
+
* analysis, when a script touches a handful. And classes refer to one
|
|
4172
|
+
* another constantly — `Object.IsA` mentions a map of every class, each
|
|
4173
|
+
* of which extends `Object` — so while one class resolves, one it extends
|
|
4174
|
+
* may itself be half-resolved; copying its members then would miss some
|
|
4175
|
+
* for good. */
|
|
4176
|
+
resolveClass(stmt) {
|
|
4177
|
+
const name = stmt.name.name;
|
|
4178
|
+
const { ancestors, cyclic } = this.classChain(stmt);
|
|
4179
|
+
const superclass = !cyclic && ancestors.length > 1 ? this.aliasDefs.get(ancestors[1])?.class : void 0;
|
|
4180
|
+
let own;
|
|
4181
|
+
let resolvingOwn = false;
|
|
4182
|
+
const ownMembers = () => {
|
|
4183
|
+
if (own || resolvingOwn) return own;
|
|
4184
|
+
resolvingOwn = true;
|
|
4185
|
+
try {
|
|
4186
|
+
own = this.resolveType(stmt.body);
|
|
4187
|
+
} finally {
|
|
4188
|
+
resolvingOwn = false;
|
|
4189
|
+
}
|
|
4190
|
+
return own;
|
|
4191
|
+
};
|
|
4192
|
+
let complete;
|
|
4193
|
+
const members = () => {
|
|
4194
|
+
if (complete) return complete;
|
|
4195
|
+
const mine = ownMembers();
|
|
4196
|
+
if (!mine) return void 0;
|
|
4197
|
+
const base = superclass ? this.classMembers.get(this.classType(superclass))?.() : void 0;
|
|
4198
|
+
if (superclass && !base) return void 0;
|
|
4199
|
+
return complete = {
|
|
4200
|
+
properties: new Map([...base?.properties ?? [], ...mine.properties]),
|
|
4201
|
+
indexer: mine.indexer ?? base?.indexer
|
|
4202
|
+
};
|
|
4203
|
+
};
|
|
4204
|
+
const type = { kind: "object", name, class: { name, superclass: superclass?.name.name, ancestors } };
|
|
4205
|
+
Object.defineProperties(type, {
|
|
4206
|
+
properties: { enumerable: true, get: () => members()?.properties ?? own?.properties ?? /* @__PURE__ */ new Map() },
|
|
4207
|
+
indexer: { enumerable: true, get: () => members()?.indexer ?? own?.indexer }
|
|
4208
|
+
});
|
|
4209
|
+
this.classTypes.set(stmt, type);
|
|
4210
|
+
this.classMembers.set(type, members);
|
|
4211
|
+
if (this.program.body.statements.includes(stmt)) ownMembers();
|
|
4212
|
+
return type;
|
|
4213
|
+
}
|
|
4214
|
+
/** `extends` must name a class, and the chain must end. */
|
|
4215
|
+
checkClass(stmt) {
|
|
4216
|
+
if (!stmt.superclass || !this.emitDiagnostics) return;
|
|
4217
|
+
const base = stmt.superclass.base;
|
|
4218
|
+
if (!this.aliasDefs.get(base)?.class) {
|
|
4219
|
+
const known = this.aliasDefs.has(base) || this.importedTypes.has(base);
|
|
4220
|
+
this.diagnostics.push({
|
|
4221
|
+
node: stmt.superclass,
|
|
4222
|
+
message: known ? `'${base}' is not a class; a class can only extend another class` : `Cannot find class '${base}'`
|
|
4223
|
+
});
|
|
4224
|
+
} else if (this.classChain(stmt).cyclic) {
|
|
4225
|
+
this.diagnostics.push({ node: stmt.superclass, message: `'${stmt.name.name}' cannot extend itself` });
|
|
4226
|
+
}
|
|
4227
|
+
}
|
|
4228
|
+
/** The class and the classes it extends, nearest first, read from the
|
|
4229
|
+
* declarations — no type has to be resolved to know them. The walk stops
|
|
4230
|
+
* at a superclass that is not a class. */
|
|
4231
|
+
classChain(stmt) {
|
|
4232
|
+
const ancestors = [stmt.name.name];
|
|
4233
|
+
for (let cls = stmt; cls?.superclass; ) {
|
|
4234
|
+
const base = cls.superclass.base;
|
|
4235
|
+
if (ancestors.includes(base)) return { ancestors, cyclic: true };
|
|
4236
|
+
cls = this.aliasDefs.get(base)?.class;
|
|
4237
|
+
if (!cls) break;
|
|
4238
|
+
ancestors.push(base);
|
|
4239
|
+
}
|
|
4240
|
+
return { ancestors, cyclic: false };
|
|
4241
|
+
}
|
|
4242
|
+
/** Seed global types from `declare` statements. Repeating a function name
|
|
4243
|
+
* builds an *overload set* (an intersection, in declaration order) rather
|
|
4244
|
+
* than replacing — which is how `typeof` gets one signature per result
|
|
4245
|
+
* string. Any other value is simply redeclared: a sourcemap's
|
|
4246
|
+
* `declare script: <this file's instance>` replaces the library's
|
|
4247
|
+
* `declare script: LuaSourceContainer`. */
|
|
3990
4248
|
harvestDeclares(block) {
|
|
3991
4249
|
for (const stmt of block.statements) {
|
|
3992
4250
|
if (stmt.type !== "DeclareStatement") continue;
|
|
3993
4251
|
const t = this.resolveType(stmt.valueType);
|
|
3994
4252
|
const prev = this.libGlobalTypes.get(stmt.name);
|
|
3995
|
-
|
|
4253
|
+
const overload = prev && stmt.valueType.type === "FunctionTypeNode" && (prev.kind === "function" || prev.kind === "intersection");
|
|
4254
|
+
this.libGlobalTypes.set(stmt.name, overload ? intersection([prev, t]) : t);
|
|
3996
4255
|
}
|
|
3997
4256
|
}
|
|
3998
4257
|
resolveAllAliases() {
|
|
3999
4258
|
for (const [name, def] of this.aliasDefs) {
|
|
4259
|
+
if (def.class && !this.program.body.statements.includes(def.class)) {
|
|
4260
|
+
const cls = def.class;
|
|
4261
|
+
this.aliases.defer(name, () => this.classType(cls));
|
|
4262
|
+
continue;
|
|
4263
|
+
}
|
|
4000
4264
|
if (containsTypeQuery(def.node)) continue;
|
|
4001
4265
|
this.withTypeParams(def.params, () => {
|
|
4002
|
-
this.aliases.set(name, this.
|
|
4266
|
+
this.aliases.set(name, this.resolveDef(def));
|
|
4003
4267
|
});
|
|
4004
4268
|
}
|
|
4005
4269
|
}
|
|
@@ -4009,7 +4273,7 @@ var TypeAnalyzer = class {
|
|
|
4009
4273
|
for (const [name, def] of this.aliasDefs) {
|
|
4010
4274
|
if (this.aliases.has(name)) continue;
|
|
4011
4275
|
this.withTypeParams(def.params, () => {
|
|
4012
|
-
this.aliases.set(name, this.
|
|
4276
|
+
this.aliases.set(name, this.resolveDef(def));
|
|
4013
4277
|
});
|
|
4014
4278
|
}
|
|
4015
4279
|
return this.aliases;
|
|
@@ -4037,10 +4301,7 @@ var TypeAnalyzer = class {
|
|
|
4037
4301
|
/** Instantiate a generic alias: `Box<number>` -> `{ value: number }`. */
|
|
4038
4302
|
instantiateAlias(def, args) {
|
|
4039
4303
|
if (this.instantiationDepth > 20) return unknownType;
|
|
4040
|
-
const subst =
|
|
4041
|
-
def.params.forEach((p, i) => {
|
|
4042
|
-
subst.set(p.name, args[i] ?? (p.default ? this.resolveType(p.default) : unknownType));
|
|
4043
|
-
});
|
|
4304
|
+
const subst = this.bindTypeArguments(def.params, args);
|
|
4044
4305
|
this.instantiationDepth++;
|
|
4045
4306
|
try {
|
|
4046
4307
|
const body = this.withTypeParams(def.params, () => this.resolveType(def.node));
|
|
@@ -4049,6 +4310,34 @@ var TypeAnalyzer = class {
|
|
|
4049
4310
|
this.instantiationDepth--;
|
|
4050
4311
|
}
|
|
4051
4312
|
}
|
|
4313
|
+
/** Pair written type arguments with the parameters they instantiate. A
|
|
4314
|
+
* pack parameter (`T...`) takes every argument from its position on, as
|
|
4315
|
+
* one pack: `Signal<Instance, string>` binds `T` to `(Instance, string)`,
|
|
4316
|
+
* and `Signal<()>` to the empty pack. Left out, a parameter takes its
|
|
4317
|
+
* default (`T... = ...any` is `any`), or `unknown`. */
|
|
4318
|
+
bindTypeArguments(params, args) {
|
|
4319
|
+
const subst = /* @__PURE__ */ new Map();
|
|
4320
|
+
params.forEach((p, i) => {
|
|
4321
|
+
let arg = args[i];
|
|
4322
|
+
if (p.isPack && i < args.length) {
|
|
4323
|
+
const rest = args.slice(i);
|
|
4324
|
+
const single = rest.length === 1 ? rest[0] : void 0;
|
|
4325
|
+
arg = single && (single.kind === "tuple" && single.isPack || single.kind === "typeParam" || single.kind === "any") ? single : tuple([...rest], true);
|
|
4326
|
+
}
|
|
4327
|
+
subst.set(p.name, arg ?? (p.default ? this.resolveType(p.default) : unknownType));
|
|
4328
|
+
});
|
|
4329
|
+
return subst;
|
|
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
|
+
}
|
|
4052
4341
|
// --------------------------------------------------------
|
|
4053
4342
|
// TypeNode -> Type
|
|
4054
4343
|
// --------------------------------------------------------
|
|
@@ -4099,17 +4388,17 @@ var TypeAnalyzer = class {
|
|
|
4099
4388
|
});
|
|
4100
4389
|
}
|
|
4101
4390
|
const imported = this.importedTypes.get(node.base);
|
|
4102
|
-
if (imported)
|
|
4103
|
-
if (!imported.params.length) return imported.type;
|
|
4104
|
-
const subst = /* @__PURE__ */ new Map();
|
|
4105
|
-
imported.params.forEach((name2, i) => {
|
|
4106
|
-
const arg = node.typeArguments[i];
|
|
4107
|
-
subst.set(name2, arg ? this.resolveType(arg) : unknownType);
|
|
4108
|
-
});
|
|
4109
|
-
return this.reduceType(substitute(imported.type, subst));
|
|
4110
|
-
}
|
|
4391
|
+
if (imported) return this.importedType(imported, node.typeArguments);
|
|
4111
4392
|
const lib = this.options.libTypes?.[node.base];
|
|
4112
4393
|
if (lib) return lib;
|
|
4394
|
+
} else if (this.importedTypes.has(name)) {
|
|
4395
|
+
return this.importedType(this.importedTypes.get(name), node.typeArguments);
|
|
4396
|
+
} else if (this.aliasDefs.has(name)) {
|
|
4397
|
+
return this.expand({
|
|
4398
|
+
kind: "genericRef",
|
|
4399
|
+
name,
|
|
4400
|
+
typeArguments: node.typeArguments.map((a) => this.resolveType(a))
|
|
4401
|
+
});
|
|
4113
4402
|
}
|
|
4114
4403
|
return {
|
|
4115
4404
|
kind: "genericRef",
|
|
@@ -4236,6 +4525,7 @@ var TypeAnalyzer = class {
|
|
|
4236
4525
|
return this.resolveType(node.typeAnnotation);
|
|
4237
4526
|
case "TypePackNode": {
|
|
4238
4527
|
if (node.types.length === 1 && !node.hasVarargs) return this.resolveType(node.types[0]);
|
|
4528
|
+
if (!node.types.length && node.varargType) return this.resolveType(node.varargType);
|
|
4239
4529
|
return tuple(node.types.map((t) => this.resolveType(t)), true);
|
|
4240
4530
|
}
|
|
4241
4531
|
}
|
|
@@ -4259,7 +4549,7 @@ var TypeAnalyzer = class {
|
|
|
4259
4549
|
this.reduceDepth++;
|
|
4260
4550
|
try {
|
|
4261
4551
|
const result = this.reduceTypeInner(t);
|
|
4262
|
-
this.reduceCache.set(t, result);
|
|
4552
|
+
if (result.kind !== "keyof") this.reduceCache.set(t, result);
|
|
4263
4553
|
return result;
|
|
4264
4554
|
} finally {
|
|
4265
4555
|
this.reduceDepth--;
|
|
@@ -4271,6 +4561,7 @@ var TypeAnalyzer = class {
|
|
|
4271
4561
|
case "keyof": {
|
|
4272
4562
|
const target = this.reduceType(t.target);
|
|
4273
4563
|
if (containsTypeParam(target)) return { kind: "keyof", target };
|
|
4564
|
+
if (target.kind === "genericRef" && this.resolvingAliases.has(target.name)) return t;
|
|
4274
4565
|
return this.keysOf(target);
|
|
4275
4566
|
}
|
|
4276
4567
|
case "indexedAccess": {
|
|
@@ -4312,6 +4603,7 @@ var TypeAnalyzer = class {
|
|
|
4312
4603
|
t.predicate
|
|
4313
4604
|
);
|
|
4314
4605
|
case "object": {
|
|
4606
|
+
if (t.class) return t;
|
|
4315
4607
|
const entries = [];
|
|
4316
4608
|
for (const [k, v] of t.properties) entries.push([k, { ...v, type: this.reduceType(v.type) }]);
|
|
4317
4609
|
const reduced = objectType(entries, t.indexer && {
|
|
@@ -4451,6 +4743,7 @@ var TypeAnalyzer = class {
|
|
|
4451
4743
|
t.typeParams
|
|
4452
4744
|
);
|
|
4453
4745
|
case "object": {
|
|
4746
|
+
if (t.class) return t;
|
|
4454
4747
|
const entries = [];
|
|
4455
4748
|
for (const [k, v] of t.properties) entries.push([k, { ...v, type: this.stripInfer(v.type, bindings) }]);
|
|
4456
4749
|
return objectType(entries, t.indexer && {
|
|
@@ -4504,6 +4797,11 @@ var TypeAnalyzer = class {
|
|
|
4504
4797
|
visitStatement(stmt, env) {
|
|
4505
4798
|
switch (stmt.type) {
|
|
4506
4799
|
case "VariableDeclaration": {
|
|
4800
|
+
stmt.names.forEach((target, i) => {
|
|
4801
|
+
if (target.type === "IdentifierPattern" && target.typeAnnotation && stmt.init[i]) {
|
|
4802
|
+
this.applyContext(stmt.init[i], this.resolveType(target.typeAnnotation));
|
|
4803
|
+
}
|
|
4804
|
+
});
|
|
4507
4805
|
const { types: valueTypes, sources } = this.valueList(stmt.init, env);
|
|
4508
4806
|
stmt.names.forEach((target, i) => {
|
|
4509
4807
|
const inferred = valueTypes[i] ?? (stmt.init.length ? unknownType : nilType);
|
|
@@ -4564,6 +4862,16 @@ var TypeAnalyzer = class {
|
|
|
4564
4862
|
return;
|
|
4565
4863
|
}
|
|
4566
4864
|
case "AssignmentStatement": {
|
|
4865
|
+
stmt.targets.forEach((target, i) => {
|
|
4866
|
+
const value = stmt.values[i];
|
|
4867
|
+
if (!value) return;
|
|
4868
|
+
if (target.type === "MemberExpression" || target.type === "IndexExpression") {
|
|
4869
|
+
this.applyContext(value, this.infer(target, env));
|
|
4870
|
+
} else if (target.type === "Identifier") {
|
|
4871
|
+
const id = this.bindingIdOf(target);
|
|
4872
|
+
if (id !== void 0 && this.annotated.has(id)) this.applyContext(value, this.bindingType.get(id));
|
|
4873
|
+
}
|
|
4874
|
+
});
|
|
4567
4875
|
const { types: valueTypes, sources } = this.valueList(stmt.values, env);
|
|
4568
4876
|
stmt.targets.forEach((target, i) => {
|
|
4569
4877
|
const vt = valueTypes[i] ?? unknownType;
|
|
@@ -4688,6 +4996,14 @@ var TypeAnalyzer = class {
|
|
|
4688
4996
|
};
|
|
4689
4997
|
if (resolving && !exports2) report(stmt.source, `Cannot find module '${specifier}'`);
|
|
4690
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
|
+
}
|
|
4691
5007
|
if (stmt.defaultImport) {
|
|
4692
5008
|
if (usable && usable.default === void 0) {
|
|
4693
5009
|
report(stmt.defaultImport, `Module '${specifier}' has no default export`);
|
|
@@ -4708,6 +5024,9 @@ var TypeAnalyzer = class {
|
|
|
4708
5024
|
case "BreakStatement":
|
|
4709
5025
|
this.breakStates[this.breakStates.length - 1]?.push(forkEnv(env));
|
|
4710
5026
|
return;
|
|
5027
|
+
case "DeclareClassStatement":
|
|
5028
|
+
this.checkClass(stmt);
|
|
5029
|
+
return;
|
|
4711
5030
|
case "ContinueStatement":
|
|
4712
5031
|
case "TypeAliasStatement":
|
|
4713
5032
|
case "ExportTypeAliasStatement":
|
|
@@ -4829,7 +5148,89 @@ var TypeAnalyzer = class {
|
|
|
4829
5148
|
}
|
|
4830
5149
|
if (p.pattern) return this.patternToType(p.pattern, env);
|
|
4831
5150
|
if (p.default) return widen(this.infer(p.default, env));
|
|
4832
|
-
return anyType;
|
|
5151
|
+
return this.contextualParams.get(p) ?? anyType;
|
|
5152
|
+
}
|
|
5153
|
+
/** What a function expression's unannotated parameters are, from where
|
|
5154
|
+
* it is written — see `applyContext`. */
|
|
5155
|
+
contextualParams = /* @__PURE__ */ new WeakMap();
|
|
5156
|
+
/** `expected` is the type the surroundings want for `expr`. A function
|
|
5157
|
+
* expression written there takes its unannotated parameters' types from
|
|
5158
|
+
* it, as in TypeScript: `signal:Connect(function(player) ... end)` knows
|
|
5159
|
+
* `player` from `Connect`'s callback type. Anything else is inferred as
|
|
5160
|
+
* usual. */
|
|
5161
|
+
applyContext(expr, expected) {
|
|
5162
|
+
let e = expr;
|
|
5163
|
+
while (e.type === "ParenthesizedExpression") e = e.expression;
|
|
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;
|
|
5168
|
+
const members = expected.kind === "union" ? expected.types : [expected];
|
|
5169
|
+
const signatures = members.flatMap((m) => this.overloadsOf(this.expand(m)));
|
|
5170
|
+
if (!signatures.length) return;
|
|
5171
|
+
e.func.params.forEach((p, k) => {
|
|
5172
|
+
if (p.typeAnnotation || p.pattern || p.default) return;
|
|
5173
|
+
const candidates = [];
|
|
5174
|
+
for (const signature of signatures) {
|
|
5175
|
+
const t2 = signature.params[k]?.type ?? signature.varargs;
|
|
5176
|
+
if (t2) candidates.push(t2);
|
|
5177
|
+
}
|
|
5178
|
+
if (!candidates.length) return;
|
|
5179
|
+
const t = union(candidates);
|
|
5180
|
+
this.contextualParams.set(p, containsTypeParam(t) ? anyType : t);
|
|
5181
|
+
});
|
|
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
|
+
}
|
|
5223
|
+
/** The parameter type each written argument lands on, across `fns`. */
|
|
5224
|
+
expectedArguments(written, fns, selfOf) {
|
|
5225
|
+
return written.map((_, j) => {
|
|
5226
|
+
const candidates = [];
|
|
5227
|
+
for (const f of fns) {
|
|
5228
|
+
const i = j + selfOf(f);
|
|
5229
|
+
const param = i < f.params.length ? this.boundParams(f)[i] : f.varargs;
|
|
5230
|
+
if (param) candidates.push(param);
|
|
5231
|
+
}
|
|
5232
|
+
return candidates.length ? union(candidates) : void 0;
|
|
5233
|
+
});
|
|
4833
5234
|
}
|
|
4834
5235
|
/** Synthesize a type from a destructuring pattern used without an
|
|
4835
5236
|
* annotation (`function f({ a, b = 1 })`). */
|
|
@@ -4881,7 +5282,8 @@ var TypeAnalyzer = class {
|
|
|
4881
5282
|
f.params.forEach((p, i) => {
|
|
4882
5283
|
const arg = argTypes[i];
|
|
4883
5284
|
if (arg === void 0) return;
|
|
4884
|
-
|
|
5285
|
+
const param = p.type.kind === "typeParam" && p.type.constraint ? { ...p.type, constraint: this.reduceType(p.type.constraint) } : p.type;
|
|
5286
|
+
unify(p.type, keepsLiterals(param) ? arg : widen(arg), vars, subst);
|
|
4885
5287
|
});
|
|
4886
5288
|
for (const name of f.typeParams ?? []) if (!subst.has(name)) subst.set(name, unknownType);
|
|
4887
5289
|
return subst;
|
|
@@ -4942,12 +5344,13 @@ var TypeAnalyzer = class {
|
|
|
4942
5344
|
return;
|
|
4943
5345
|
}
|
|
4944
5346
|
const t = value;
|
|
5347
|
+
if (t.kind === "object" && t.class) return;
|
|
4945
5348
|
if (t.kind === "typeParam" && typeof t.name === "string" && bounds.has(t.name) && t.constraint && !containsTypeParam(t.constraint)) {
|
|
4946
5349
|
bounds.set(t.name, this.reduceType(t.constraint));
|
|
4947
5350
|
}
|
|
4948
5351
|
for (const child of Object.values(value)) walk(child);
|
|
4949
5352
|
};
|
|
4950
|
-
for (const p of f.params) walk(p.type);
|
|
5353
|
+
for (const p of f.params) if (containsTypeParam(p.type)) walk(p.type);
|
|
4951
5354
|
return f.params.map((p) => substitute(p.type, bounds));
|
|
4952
5355
|
}
|
|
4953
5356
|
/** Record what each written argument is expected to be — see
|
|
@@ -5286,7 +5689,7 @@ var TypeAnalyzer = class {
|
|
|
5286
5689
|
this.expandCache.set(key, t);
|
|
5287
5690
|
this.resolvingAliases.add(t.name);
|
|
5288
5691
|
try {
|
|
5289
|
-
const r = def.params.length ? this.instantiateAlias(def, t.typeArguments) : this.
|
|
5692
|
+
const r = def.params.length ? this.instantiateAlias(def, t.typeArguments) : this.resolveDef(def);
|
|
5290
5693
|
const named = def.params.length === 0 && (r.kind === "object" || r.kind === "intersection") && !r.name ? { ...r, name: t.name } : r;
|
|
5291
5694
|
this.expandCache.set(key, named);
|
|
5292
5695
|
return named;
|
|
@@ -5391,8 +5794,9 @@ var TypeAnalyzer = class {
|
|
|
5391
5794
|
return this.resolveType(expr.typeAnnotation);
|
|
5392
5795
|
}
|
|
5393
5796
|
case "SatisfiesExpression": {
|
|
5394
|
-
const actual = this.infer(expr.expression, env);
|
|
5395
5797
|
const declared = this.resolveType(expr.typeAnnotation);
|
|
5798
|
+
this.applyContext(expr.expression, declared);
|
|
5799
|
+
const actual = this.infer(expr.expression, env);
|
|
5396
5800
|
if (this.emitDiagnostics && declared.kind !== "any" && !this.fitsAnnotation(expr.expression, declared, actual, env)) {
|
|
5397
5801
|
this.diagnostics.push({
|
|
5398
5802
|
node: expr,
|
|
@@ -5409,9 +5813,9 @@ var TypeAnalyzer = class {
|
|
|
5409
5813
|
case "not":
|
|
5410
5814
|
return booleanType;
|
|
5411
5815
|
case "-":
|
|
5412
|
-
return numberType;
|
|
5816
|
+
return this.operatorResult(expr, "-", arg, void 0) ?? numberType;
|
|
5413
5817
|
case "#":
|
|
5414
|
-
return numberType;
|
|
5818
|
+
return this.operatorResult(expr, "#", arg, void 0) ?? numberType;
|
|
5415
5819
|
}
|
|
5416
5820
|
return arg;
|
|
5417
5821
|
}
|
|
@@ -5433,7 +5837,7 @@ var TypeAnalyzer = class {
|
|
|
5433
5837
|
const r = this.infer(expr.right, env);
|
|
5434
5838
|
switch (op) {
|
|
5435
5839
|
case "..":
|
|
5436
|
-
return stringType;
|
|
5840
|
+
return this.operatorResult(expr, op, l, r) ?? stringType;
|
|
5437
5841
|
case "==":
|
|
5438
5842
|
case "~=":
|
|
5439
5843
|
case "<":
|
|
@@ -5448,7 +5852,7 @@ var TypeAnalyzer = class {
|
|
|
5448
5852
|
case "//":
|
|
5449
5853
|
case "%":
|
|
5450
5854
|
case "^":
|
|
5451
|
-
return numberType;
|
|
5855
|
+
return this.operatorResult(expr, op, l, r) ?? numberType;
|
|
5452
5856
|
}
|
|
5453
5857
|
return union([l, r]);
|
|
5454
5858
|
}
|
|
@@ -5467,8 +5871,10 @@ var TypeAnalyzer = class {
|
|
|
5467
5871
|
}
|
|
5468
5872
|
case "CallExpression": {
|
|
5469
5873
|
const callee = this.infer(expr.callee, env);
|
|
5470
|
-
const argTypes = expr.arguments.map((a) => this.infer(a, env));
|
|
5471
5874
|
const fns = this.overloadsOf(callee);
|
|
5875
|
+
const expected = this.expectedArguments(expr.arguments, fns, () => 0);
|
|
5876
|
+
expr.arguments.forEach((a, i) => this.applyContext(a, expected[i]));
|
|
5877
|
+
const argTypes = expr.arguments.map((a) => this.infer(a, env));
|
|
5472
5878
|
if (fns.length) {
|
|
5473
5879
|
this.recordExpected(expr.arguments, fns, () => 0);
|
|
5474
5880
|
const arityFits = this.checkArity(expr, fns, argTypes.length, 0);
|
|
@@ -5483,8 +5889,10 @@ var TypeAnalyzer = class {
|
|
|
5483
5889
|
}
|
|
5484
5890
|
case "MethodCallExpression": {
|
|
5485
5891
|
const objType = this.infer(expr.object, env);
|
|
5486
|
-
const argTypes = expr.arguments.map((a) => this.infer(a, env));
|
|
5487
5892
|
const fns = this.overloadsOf(this.propertyType(objType, expr.method.name));
|
|
5893
|
+
const expected = this.expectedArguments(expr.arguments, fns, (f) => this.takesSelf(f) ? 1 : 0);
|
|
5894
|
+
expr.arguments.forEach((a, i) => this.applyContext(a, expected[i]));
|
|
5895
|
+
const argTypes = expr.arguments.map((a) => this.infer(a, env));
|
|
5488
5896
|
if (fns.length) {
|
|
5489
5897
|
const withSelf = (f) => this.takesSelf(f) ? [objType, ...argTypes] : argTypes;
|
|
5490
5898
|
const selfOf = (f) => this.takesSelf(f) ? 1 : 0;
|
|
@@ -5516,6 +5924,8 @@ var TypeAnalyzer = class {
|
|
|
5516
5924
|
}
|
|
5517
5925
|
}
|
|
5518
5926
|
inferArray(expr, env, asConst) {
|
|
5927
|
+
const contextual = this.contextualArrays.get(expr);
|
|
5928
|
+
if (contextual && !asConst) return contextual;
|
|
5519
5929
|
const elems = [];
|
|
5520
5930
|
let hadSpread = false;
|
|
5521
5931
|
for (const el of expr.elements) {
|
|
@@ -5876,6 +6286,35 @@ var TypeAnalyzer = class {
|
|
|
5876
6286
|
this.selfType = saved;
|
|
5877
6287
|
}
|
|
5878
6288
|
}
|
|
6289
|
+
/** What an operator on a value with metamethods gives: `a + b` calls
|
|
6290
|
+
* `__add` on `a`, or failing that on `b` with the operands swapped — the
|
|
6291
|
+
* order Luau tries them in. That is how `Vector3 + Vector3`, `CFrame *
|
|
6292
|
+
* Vector3` and `2 * vector` get their types from the declarations.
|
|
6293
|
+
* `undefined` when neither operand declares the metamethod; an operand
|
|
6294
|
+
* that declares it but accepts neither argument is reported. */
|
|
6295
|
+
operatorResult(node, op, left, right) {
|
|
6296
|
+
const name = right === void 0 ? op === "-" ? "__unm" : "__len" : METAMETHODS[op];
|
|
6297
|
+
if (!name) return void 0;
|
|
6298
|
+
const candidates = right === void 0 ? [[left, void 0]] : [[left, right], [right, left]];
|
|
6299
|
+
let declared;
|
|
6300
|
+
for (const [receiver, other] of candidates) {
|
|
6301
|
+
const t = this.expand(receiver);
|
|
6302
|
+
const method = t.kind === "object" ? t.properties.get(name) : void 0;
|
|
6303
|
+
if (!method) continue;
|
|
6304
|
+
declared ??= receiver;
|
|
6305
|
+
const args = other === void 0 ? [receiver] : [receiver, other];
|
|
6306
|
+
const picked = this.pickOverload(this.overloadsOf(method.type), args);
|
|
6307
|
+
if (picked) return this.callReturn(picked, args);
|
|
6308
|
+
}
|
|
6309
|
+
if (declared && this.emitDiagnostics) {
|
|
6310
|
+
this.diagnostics.push({
|
|
6311
|
+
node,
|
|
6312
|
+
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)}'`
|
|
6313
|
+
});
|
|
6314
|
+
return anyType;
|
|
6315
|
+
}
|
|
6316
|
+
return void 0;
|
|
6317
|
+
}
|
|
5879
6318
|
/** Does this signature take the receiver as its first parameter?
|
|
5880
6319
|
*
|
|
5881
6320
|
* Luau's `:` is sugar both ways: `function T:m(a)` declares
|
|
@@ -6140,13 +6579,13 @@ function resolveTypeLibraries(config, host = nodeHost) {
|
|
|
6140
6579
|
else problems.push({ file: config.path, message: `'${entry}' has no ${ENTRY_FILE} (or 'luaut.types' in its package.json)`, ...entryPosition(config, entry) });
|
|
6141
6580
|
continue;
|
|
6142
6581
|
}
|
|
6143
|
-
const
|
|
6144
|
-
const found =
|
|
6582
|
+
const name = entry.startsWith("@luaut/") ? entry : `@luaut/${entry}`;
|
|
6583
|
+
const found = findPackage(name, config.directory, host);
|
|
6145
6584
|
if (found) addPackage(found.directory, found.file, /* @__PURE__ */ new Set());
|
|
6146
6585
|
else {
|
|
6147
6586
|
problems.push({
|
|
6148
6587
|
file: config.path,
|
|
6149
|
-
message: `Cannot find type library '${
|
|
6588
|
+
message: `Cannot find type library '${name}'. Install it with: npm i -D ${name}`,
|
|
6150
6589
|
...entryPosition(config, entry)
|
|
6151
6590
|
});
|
|
6152
6591
|
}
|
|
@@ -6249,7 +6688,6 @@ function sourceMapTypes(text, path, options) {
|
|
|
6249
6688
|
const lines = [];
|
|
6250
6689
|
const aliasOfFile = /* @__PURE__ */ new Map();
|
|
6251
6690
|
const used = /* @__PURE__ */ new Set();
|
|
6252
|
-
const canOmit = options.classes.has("Omit");
|
|
6253
6691
|
const aliasOfNode = /* @__PURE__ */ new Map();
|
|
6254
6692
|
const aliasFor = (segments) => {
|
|
6255
6693
|
const base = `SourceMap_${segments.map((s) => s.replace(/[^A-Za-z0-9_]/g, "_")).join("_")}`;
|
|
@@ -6265,7 +6703,7 @@ function sourceMapTypes(text, path, options) {
|
|
|
6265
6703
|
const className = IDENTIFIER.test(node.className) && options.classes.has(node.className) ? node.className : "Instance";
|
|
6266
6704
|
const taken = options.membersOf?.(className) ?? INSTANCE_MEMBERS;
|
|
6267
6705
|
const members = [];
|
|
6268
|
-
if (parent
|
|
6706
|
+
if (parent) members.push(`Parent: ${parent}`);
|
|
6269
6707
|
const named = /* @__PURE__ */ new Set();
|
|
6270
6708
|
for (const child of node.children ?? []) {
|
|
6271
6709
|
if (!isNode(child)) continue;
|
|
@@ -6274,8 +6712,7 @@ function sourceMapTypes(text, path, options) {
|
|
|
6274
6712
|
named.add(child.name);
|
|
6275
6713
|
members.push(`${child.name}: ${childAlias}`);
|
|
6276
6714
|
}
|
|
6277
|
-
|
|
6278
|
-
lines.push(`type ${alias} = ${members.length ? `${base} & { ${members.join(", ")} }` : base}`);
|
|
6715
|
+
lines.push(`declare class ${alias} extends ${className} { ${members.join(", ")} }`);
|
|
6279
6716
|
return alias;
|
|
6280
6717
|
};
|
|
6281
6718
|
const rootAlias = visit(root, [root.name], void 0);
|
|
@@ -6353,6 +6790,7 @@ var index_default = luautparser;
|
|
|
6353
6790
|
getBinding,
|
|
6354
6791
|
intersection,
|
|
6355
6792
|
isAssignable,
|
|
6793
|
+
isClassType,
|
|
6356
6794
|
isGlobal,
|
|
6357
6795
|
isPossiblyFalsy,
|
|
6358
6796
|
isPossiblyTruthy,
|