luaut-parser 2.0.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +33 -0
- package/dist/index.cjs +360 -33
- package/dist/index.d.cts +37 -4
- package/dist/index.d.ts +37 -4
- package/dist/index.js +359 -33
- 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,
|
|
@@ -944,6 +945,9 @@ var Parser = class {
|
|
|
944
945
|
}
|
|
945
946
|
if (t.type === "Identifier" && t.value === "declare") {
|
|
946
947
|
const p1 = this.peek(1);
|
|
948
|
+
if (p1.type === "Identifier" && p1.value === "class" && this.peek(2).type === "Identifier") {
|
|
949
|
+
return this.parseDeclareClassStatement();
|
|
950
|
+
}
|
|
947
951
|
if (p1.type === "Identifier" || p1.type === "Keyword" && p1.value === "function") {
|
|
948
952
|
return this.parseDeclareStatement();
|
|
949
953
|
}
|
|
@@ -985,6 +989,36 @@ var Parser = class {
|
|
|
985
989
|
const valueType = this.parseType();
|
|
986
990
|
return { type: "DeclareStatement", name: nameTok.value, id: tokenIdentifier(nameTok), valueType, ...spanFrom(start, this.previous()) };
|
|
987
991
|
}
|
|
992
|
+
/** A declared type's name. It may be qualified once — `Enum.Material` —
|
|
993
|
+
* which is how a definitions file names types under a namespace, and how
|
|
994
|
+
* they are then written (`const m: Enum.Material`). */
|
|
995
|
+
parseTypeName() {
|
|
996
|
+
const first = this.expectIdentifier();
|
|
997
|
+
if (this.checkPunctuator(".") && this.peek(1).type === "Identifier") {
|
|
998
|
+
this.advance();
|
|
999
|
+
const second = this.expectIdentifier();
|
|
1000
|
+
return { type: "Identifier", name: `${first.value}.${second.value}`, ...spanFrom(first, second) };
|
|
1001
|
+
}
|
|
1002
|
+
return tokenIdentifier(first);
|
|
1003
|
+
}
|
|
1004
|
+
// `declare class Name extends Base { member: T, ... }`
|
|
1005
|
+
parseDeclareClassStatement() {
|
|
1006
|
+
const start = this.current();
|
|
1007
|
+
this.advance();
|
|
1008
|
+
this.advance();
|
|
1009
|
+
const name = this.parseTypeName();
|
|
1010
|
+
let superclass;
|
|
1011
|
+
if (this.checkIdentifierValue("extends")) {
|
|
1012
|
+
this.advance();
|
|
1013
|
+
const base = this.parseType();
|
|
1014
|
+
if (base.type !== "TypeReference") this.error("A class can only extend another class, written by name");
|
|
1015
|
+
superclass = base;
|
|
1016
|
+
}
|
|
1017
|
+
if (!this.checkPunctuator("{")) this.error("Expected '{' to start the class body");
|
|
1018
|
+
const body = this.parseTableType();
|
|
1019
|
+
if (body.type !== "TableTypeNode") this.error("A class body lists members ('name: T'), not a mapped type");
|
|
1020
|
+
return { type: "DeclareClassStatement", name, superclass, body, ...spanFrom(start, this.previous()) };
|
|
1021
|
+
}
|
|
988
1022
|
// `import { a, b as c } from '...'` / `import Default from '...'` /
|
|
989
1023
|
// `import Default, { a } from '...'`. Compiled away entirely by the
|
|
990
1024
|
// bundler — never survives into emitted Luau.
|
|
@@ -1315,8 +1349,7 @@ var Parser = class {
|
|
|
1315
1349
|
parseTypeAliasStatement() {
|
|
1316
1350
|
const start = this.current();
|
|
1317
1351
|
this.advance();
|
|
1318
|
-
const
|
|
1319
|
-
const name = { type: "Identifier", name: nameTok.value, ...spanFrom(nameTok, nameTok) };
|
|
1352
|
+
const name = this.parseTypeName();
|
|
1320
1353
|
let generics = [];
|
|
1321
1354
|
if (this.checkOperator("<")) {
|
|
1322
1355
|
generics = this.parseGenericTypeParameterList();
|
|
@@ -2818,6 +2851,9 @@ var Analyzer = class {
|
|
|
2818
2851
|
case "DeclareStatement":
|
|
2819
2852
|
this.visitType(stmt.valueType, scope);
|
|
2820
2853
|
return;
|
|
2854
|
+
case "DeclareClassStatement":
|
|
2855
|
+
this.visitType(stmt.body, scope);
|
|
2856
|
+
return;
|
|
2821
2857
|
case "TypeAliasStatement":
|
|
2822
2858
|
case "ExportTypeAliasStatement":
|
|
2823
2859
|
this.visitType(stmt.definition, scope);
|
|
@@ -2991,6 +3027,9 @@ function analyzeScopes(program, options = {}) {
|
|
|
2991
3027
|
}
|
|
2992
3028
|
|
|
2993
3029
|
// src/ast/typeModel.ts
|
|
3030
|
+
function isClassType(t) {
|
|
3031
|
+
return t.kind === "object" && t.class !== void 0;
|
|
3032
|
+
}
|
|
2994
3033
|
function typeParam(name, constraint, isConst) {
|
|
2995
3034
|
return { kind: "typeParam", name, constraint, isConst };
|
|
2996
3035
|
}
|
|
@@ -3050,10 +3089,16 @@ function substitute(t, subst) {
|
|
|
3050
3089
|
}
|
|
3051
3090
|
case "function": {
|
|
3052
3091
|
const inner = t.typeParams ? new Map([...subst].filter(([k]) => !t.typeParams.includes(k))) : subst;
|
|
3092
|
+
let params = t.params.map((p) => ({ ...p, type: substitute(p.type, inner) }));
|
|
3093
|
+
let varargs = t.varargs && substitute(t.varargs, inner);
|
|
3094
|
+
if (varargs?.kind === "tuple" && varargs.isPack) {
|
|
3095
|
+
params = [...params, ...varargs.elements.map((type) => ({ type }))];
|
|
3096
|
+
varargs = void 0;
|
|
3097
|
+
}
|
|
3053
3098
|
return {
|
|
3054
3099
|
kind: "function",
|
|
3055
|
-
params
|
|
3056
|
-
varargs
|
|
3100
|
+
params,
|
|
3101
|
+
varargs,
|
|
3057
3102
|
returns: substitute(t.returns, inner),
|
|
3058
3103
|
typeParams: t.typeParams,
|
|
3059
3104
|
predicate: t.predicate && {
|
|
@@ -3133,7 +3178,8 @@ function unify(param, arg, vars, out) {
|
|
|
3133
3178
|
}
|
|
3134
3179
|
return;
|
|
3135
3180
|
case "object":
|
|
3136
|
-
if (
|
|
3181
|
+
if (param.class) return;
|
|
3182
|
+
if (arg.kind === "object" && !arg.class) {
|
|
3137
3183
|
for (const [k, pv] of param.properties) {
|
|
3138
3184
|
const av = arg.properties.get(k);
|
|
3139
3185
|
if (av) unify(pv.type, av.type, vars, out);
|
|
@@ -3216,7 +3262,7 @@ function widen(t) {
|
|
|
3216
3262
|
case "tuple":
|
|
3217
3263
|
return tuple(t.elements.map(widen), t.isPack);
|
|
3218
3264
|
case "object": {
|
|
3219
|
-
if (t.frozen) return t;
|
|
3265
|
+
if (t.frozen || t.class) return t;
|
|
3220
3266
|
const entries = [];
|
|
3221
3267
|
for (const [k, v] of t.properties) entries.push([k, { ...v, type: widen(v.type) }]);
|
|
3222
3268
|
const w = objectType(entries, t.indexer && { key: t.indexer.key, value: widen(t.indexer.value) });
|
|
@@ -3243,6 +3289,10 @@ function isAssignable(rawA, rawB) {
|
|
|
3243
3289
|
if (expandAlias) {
|
|
3244
3290
|
if (a.kind === "genericRef" && b.kind !== "genericRef") a = expandAlias(a);
|
|
3245
3291
|
else if (b.kind === "genericRef" && a.kind !== "genericRef") b = expandAlias(b);
|
|
3292
|
+
else if (a.kind === "genericRef" && b.kind === "genericRef" && a.name !== b.name) {
|
|
3293
|
+
a = expandAlias(a);
|
|
3294
|
+
b = expandAlias(b);
|
|
3295
|
+
}
|
|
3246
3296
|
if (a === b) return true;
|
|
3247
3297
|
}
|
|
3248
3298
|
for (let i = 0; i < comparing.length; i += 2) {
|
|
@@ -3298,6 +3348,8 @@ function isAssignableInner(a, b) {
|
|
|
3298
3348
|
}
|
|
3299
3349
|
if (a.kind === "object") {
|
|
3300
3350
|
if (b.kind !== "object") return false;
|
|
3351
|
+
if (b.class) return a.class !== void 0 && a.class.ancestors.includes(b.class.name);
|
|
3352
|
+
if (a.class && (b.indexer || b.properties.size === 0)) return false;
|
|
3301
3353
|
for (const [name, bp] of b.properties) {
|
|
3302
3354
|
const ap = a.properties.get(name);
|
|
3303
3355
|
if (!ap) {
|
|
@@ -3440,6 +3492,7 @@ function containsFreeTypeParam(t, seen, bound) {
|
|
|
3440
3492
|
case "intersection":
|
|
3441
3493
|
return t.types.some((m) => containsTypeParam(m, seen, bound));
|
|
3442
3494
|
case "object":
|
|
3495
|
+
if (t.class) return false;
|
|
3443
3496
|
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
3497
|
case "function": {
|
|
3445
3498
|
const inner = t.typeParams?.length ? /* @__PURE__ */ new Set([...bound, ...t.typeParams]) : bound;
|
|
@@ -3638,7 +3691,7 @@ function mergeObjectMembers(types) {
|
|
|
3638
3691
|
return expanded !== void 0 && expanded !== t && collect(expanded);
|
|
3639
3692
|
}
|
|
3640
3693
|
if (t.kind === "intersection") return t.types.every(collect);
|
|
3641
|
-
if (t.kind === "object") {
|
|
3694
|
+
if (t.kind === "object" && !t.class) {
|
|
3642
3695
|
objects.push(t);
|
|
3643
3696
|
return true;
|
|
3644
3697
|
}
|
|
@@ -3850,6 +3903,62 @@ function keepsLiterals(paramType) {
|
|
|
3850
3903
|
const members = paramType.constraint.kind === "union" ? paramType.constraint.types : [paramType.constraint];
|
|
3851
3904
|
return members.some((m) => m.kind === "literal");
|
|
3852
3905
|
}
|
|
3906
|
+
var AliasMap = class extends Map {
|
|
3907
|
+
pending = /* @__PURE__ */ new Map();
|
|
3908
|
+
defer(name, resolve5) {
|
|
3909
|
+
super.delete(name);
|
|
3910
|
+
this.pending.set(name, resolve5);
|
|
3911
|
+
}
|
|
3912
|
+
get(name) {
|
|
3913
|
+
const resolved = super.get(name);
|
|
3914
|
+
if (resolved !== void 0) return resolved;
|
|
3915
|
+
const resolve5 = this.pending.get(name);
|
|
3916
|
+
if (!resolve5) return void 0;
|
|
3917
|
+
this.pending.delete(name);
|
|
3918
|
+
const type = resolve5();
|
|
3919
|
+
super.set(name, type);
|
|
3920
|
+
return type;
|
|
3921
|
+
}
|
|
3922
|
+
has(name) {
|
|
3923
|
+
return super.has(name) || (this.pending?.has(name) ?? false);
|
|
3924
|
+
}
|
|
3925
|
+
set(name, type) {
|
|
3926
|
+
this.pending?.delete(name);
|
|
3927
|
+
return super.set(name, type);
|
|
3928
|
+
}
|
|
3929
|
+
delete(name) {
|
|
3930
|
+
const deferred = this.pending?.delete(name) ?? false;
|
|
3931
|
+
return super.delete(name) || deferred;
|
|
3932
|
+
}
|
|
3933
|
+
get size() {
|
|
3934
|
+
return super.size + (this.pending?.size ?? 0);
|
|
3935
|
+
}
|
|
3936
|
+
keys() {
|
|
3937
|
+
return [...super.keys(), ...this.pending?.keys() ?? []][Symbol.iterator]();
|
|
3938
|
+
}
|
|
3939
|
+
entries() {
|
|
3940
|
+
return [...this.keys()].map((name) => [name, this.get(name)])[Symbol.iterator]();
|
|
3941
|
+
}
|
|
3942
|
+
values() {
|
|
3943
|
+
return [...this.keys()].map((name) => this.get(name))[Symbol.iterator]();
|
|
3944
|
+
}
|
|
3945
|
+
forEach(callback, thisArg) {
|
|
3946
|
+
for (const [name, type] of this.entries()) callback.call(thisArg, type, name, this);
|
|
3947
|
+
}
|
|
3948
|
+
[Symbol.iterator]() {
|
|
3949
|
+
return this.entries();
|
|
3950
|
+
}
|
|
3951
|
+
};
|
|
3952
|
+
var METAMETHODS = {
|
|
3953
|
+
"+": "__add",
|
|
3954
|
+
"-": "__sub",
|
|
3955
|
+
"*": "__mul",
|
|
3956
|
+
"/": "__div",
|
|
3957
|
+
"//": "__idiv",
|
|
3958
|
+
"%": "__mod",
|
|
3959
|
+
"^": "__pow",
|
|
3960
|
+
"..": "__concat"
|
|
3961
|
+
};
|
|
3853
3962
|
function posKey(name, line, column) {
|
|
3854
3963
|
return `${name}@${line}:${column}`;
|
|
3855
3964
|
}
|
|
@@ -3870,9 +3979,12 @@ var TypeAnalyzer = class {
|
|
|
3870
3979
|
expectedTypeOf = /* @__PURE__ */ new Map();
|
|
3871
3980
|
/** Public: each alias resolved once (generic aliases keep their params as
|
|
3872
3981
|
* `typeParam` nodes in the body). */
|
|
3873
|
-
aliases =
|
|
3982
|
+
aliases = new AliasMap();
|
|
3874
3983
|
/** Uninstantiated alias definitions, for `Name<Args>` instantiation. */
|
|
3875
3984
|
aliasDefs = /* @__PURE__ */ new Map();
|
|
3985
|
+
/** See `resolveClass`. */
|
|
3986
|
+
classTypes = /* @__PURE__ */ new WeakMap();
|
|
3987
|
+
classMembers = /* @__PURE__ */ new WeakMap();
|
|
3876
3988
|
/** Generic parameters currently in lexical scope (alias body / generic fn),
|
|
3877
3989
|
* with their `extends` constraints resolved. */
|
|
3878
3990
|
typeParamScope = [];
|
|
@@ -3982,24 +4094,123 @@ var TypeAnalyzer = class {
|
|
|
3982
4094
|
for (const stmt of block.statements) {
|
|
3983
4095
|
const alias = stmt.type === "TypeAliasStatement" ? stmt : stmt.type === "ExportTypeAliasStatement" ? stmt.alias : void 0;
|
|
3984
4096
|
if (alias) this.aliasDefs.set(alias.name.name, { params: alias.generics, node: alias.definition });
|
|
4097
|
+
if (stmt.type === "DeclareClassStatement") {
|
|
4098
|
+
this.aliasDefs.set(stmt.name.name, { params: [], node: stmt.body, class: stmt });
|
|
4099
|
+
}
|
|
3985
4100
|
}
|
|
3986
4101
|
}
|
|
3987
|
-
/**
|
|
3988
|
-
|
|
3989
|
-
|
|
4102
|
+
/** A non-generic definition's type. */
|
|
4103
|
+
resolveDef(def) {
|
|
4104
|
+
return def.class ? this.classType(def.class) : this.resolveType(def.node);
|
|
4105
|
+
}
|
|
4106
|
+
/** One type per class declaration, so every mention of a class is the same
|
|
4107
|
+
* object — its own members included, which refer back to it. */
|
|
4108
|
+
classType(stmt) {
|
|
4109
|
+
return this.classTypes.get(stmt) ?? this.resolveClass(stmt);
|
|
4110
|
+
}
|
|
4111
|
+
/** A class's members are resolved the first time anyone asks for
|
|
4112
|
+
* `properties` — its own from its body, the inherited ones from its
|
|
4113
|
+
* superclass.
|
|
4114
|
+
*
|
|
4115
|
+
* Both have to wait. A definitions file for a whole engine declares
|
|
4116
|
+
* thousands of classes that all refer to one another; resolving each body
|
|
4117
|
+
* as soon as the class is named would resolve every class on every
|
|
4118
|
+
* analysis, when a script touches a handful. And classes refer to one
|
|
4119
|
+
* another constantly — `Object.IsA` mentions a map of every class, each
|
|
4120
|
+
* of which extends `Object` — so while one class resolves, one it extends
|
|
4121
|
+
* may itself be half-resolved; copying its members then would miss some
|
|
4122
|
+
* for good. */
|
|
4123
|
+
resolveClass(stmt) {
|
|
4124
|
+
const name = stmt.name.name;
|
|
4125
|
+
const { ancestors, cyclic } = this.classChain(stmt);
|
|
4126
|
+
const superclass = !cyclic && ancestors.length > 1 ? this.aliasDefs.get(ancestors[1])?.class : void 0;
|
|
4127
|
+
let own;
|
|
4128
|
+
let resolvingOwn = false;
|
|
4129
|
+
const ownMembers = () => {
|
|
4130
|
+
if (own || resolvingOwn) return own;
|
|
4131
|
+
resolvingOwn = true;
|
|
4132
|
+
try {
|
|
4133
|
+
own = this.resolveType(stmt.body);
|
|
4134
|
+
} finally {
|
|
4135
|
+
resolvingOwn = false;
|
|
4136
|
+
}
|
|
4137
|
+
return own;
|
|
4138
|
+
};
|
|
4139
|
+
let complete;
|
|
4140
|
+
const members = () => {
|
|
4141
|
+
if (complete) return complete;
|
|
4142
|
+
const mine = ownMembers();
|
|
4143
|
+
if (!mine) return void 0;
|
|
4144
|
+
const base = superclass ? this.classMembers.get(this.classType(superclass))?.() : void 0;
|
|
4145
|
+
if (superclass && !base) return void 0;
|
|
4146
|
+
return complete = {
|
|
4147
|
+
properties: new Map([...base?.properties ?? [], ...mine.properties]),
|
|
4148
|
+
indexer: mine.indexer ?? base?.indexer
|
|
4149
|
+
};
|
|
4150
|
+
};
|
|
4151
|
+
const type = { kind: "object", name, class: { name, superclass: superclass?.name.name, ancestors } };
|
|
4152
|
+
Object.defineProperties(type, {
|
|
4153
|
+
properties: { enumerable: true, get: () => members()?.properties ?? own?.properties ?? /* @__PURE__ */ new Map() },
|
|
4154
|
+
indexer: { enumerable: true, get: () => members()?.indexer ?? own?.indexer }
|
|
4155
|
+
});
|
|
4156
|
+
this.classTypes.set(stmt, type);
|
|
4157
|
+
this.classMembers.set(type, members);
|
|
4158
|
+
if (this.program.body.statements.includes(stmt)) ownMembers();
|
|
4159
|
+
return type;
|
|
4160
|
+
}
|
|
4161
|
+
/** `extends` must name a class, and the chain must end. */
|
|
4162
|
+
checkClass(stmt) {
|
|
4163
|
+
if (!stmt.superclass || !this.emitDiagnostics) return;
|
|
4164
|
+
const base = stmt.superclass.base;
|
|
4165
|
+
if (!this.aliasDefs.get(base)?.class) {
|
|
4166
|
+
const known = this.aliasDefs.has(base) || this.importedTypes.has(base);
|
|
4167
|
+
this.diagnostics.push({
|
|
4168
|
+
node: stmt.superclass,
|
|
4169
|
+
message: known ? `'${base}' is not a class; a class can only extend another class` : `Cannot find class '${base}'`
|
|
4170
|
+
});
|
|
4171
|
+
} else if (this.classChain(stmt).cyclic) {
|
|
4172
|
+
this.diagnostics.push({ node: stmt.superclass, message: `'${stmt.name.name}' cannot extend itself` });
|
|
4173
|
+
}
|
|
4174
|
+
}
|
|
4175
|
+
/** The class and the classes it extends, nearest first, read from the
|
|
4176
|
+
* declarations — no type has to be resolved to know them. The walk stops
|
|
4177
|
+
* at a superclass that is not a class. */
|
|
4178
|
+
classChain(stmt) {
|
|
4179
|
+
const ancestors = [stmt.name.name];
|
|
4180
|
+
for (let cls = stmt; cls?.superclass; ) {
|
|
4181
|
+
const base = cls.superclass.base;
|
|
4182
|
+
if (ancestors.includes(base)) return { ancestors, cyclic: true };
|
|
4183
|
+
cls = this.aliasDefs.get(base)?.class;
|
|
4184
|
+
if (!cls) break;
|
|
4185
|
+
ancestors.push(base);
|
|
4186
|
+
}
|
|
4187
|
+
return { ancestors, cyclic: false };
|
|
4188
|
+
}
|
|
4189
|
+
/** Seed global types from `declare` statements. Repeating a function name
|
|
4190
|
+
* builds an *overload set* (an intersection, in declaration order) rather
|
|
4191
|
+
* than replacing — which is how `typeof` gets one signature per result
|
|
4192
|
+
* string. Any other value is simply redeclared: a sourcemap's
|
|
4193
|
+
* `declare script: <this file's instance>` replaces the library's
|
|
4194
|
+
* `declare script: LuaSourceContainer`. */
|
|
3990
4195
|
harvestDeclares(block) {
|
|
3991
4196
|
for (const stmt of block.statements) {
|
|
3992
4197
|
if (stmt.type !== "DeclareStatement") continue;
|
|
3993
4198
|
const t = this.resolveType(stmt.valueType);
|
|
3994
4199
|
const prev = this.libGlobalTypes.get(stmt.name);
|
|
3995
|
-
|
|
4200
|
+
const overload = prev && stmt.valueType.type === "FunctionTypeNode" && (prev.kind === "function" || prev.kind === "intersection");
|
|
4201
|
+
this.libGlobalTypes.set(stmt.name, overload ? intersection([prev, t]) : t);
|
|
3996
4202
|
}
|
|
3997
4203
|
}
|
|
3998
4204
|
resolveAllAliases() {
|
|
3999
4205
|
for (const [name, def] of this.aliasDefs) {
|
|
4206
|
+
if (def.class && !this.program.body.statements.includes(def.class)) {
|
|
4207
|
+
const cls = def.class;
|
|
4208
|
+
this.aliases.defer(name, () => this.classType(cls));
|
|
4209
|
+
continue;
|
|
4210
|
+
}
|
|
4000
4211
|
if (containsTypeQuery(def.node)) continue;
|
|
4001
4212
|
this.withTypeParams(def.params, () => {
|
|
4002
|
-
this.aliases.set(name, this.
|
|
4213
|
+
this.aliases.set(name, this.resolveDef(def));
|
|
4003
4214
|
});
|
|
4004
4215
|
}
|
|
4005
4216
|
}
|
|
@@ -4009,7 +4220,7 @@ var TypeAnalyzer = class {
|
|
|
4009
4220
|
for (const [name, def] of this.aliasDefs) {
|
|
4010
4221
|
if (this.aliases.has(name)) continue;
|
|
4011
4222
|
this.withTypeParams(def.params, () => {
|
|
4012
|
-
this.aliases.set(name, this.
|
|
4223
|
+
this.aliases.set(name, this.resolveDef(def));
|
|
4013
4224
|
});
|
|
4014
4225
|
}
|
|
4015
4226
|
return this.aliases;
|
|
@@ -4037,10 +4248,7 @@ var TypeAnalyzer = class {
|
|
|
4037
4248
|
/** Instantiate a generic alias: `Box<number>` -> `{ value: number }`. */
|
|
4038
4249
|
instantiateAlias(def, args) {
|
|
4039
4250
|
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
|
-
});
|
|
4251
|
+
const subst = this.bindTypeArguments(def.params, args);
|
|
4044
4252
|
this.instantiationDepth++;
|
|
4045
4253
|
try {
|
|
4046
4254
|
const body = this.withTypeParams(def.params, () => this.resolveType(def.node));
|
|
@@ -4049,6 +4257,24 @@ var TypeAnalyzer = class {
|
|
|
4049
4257
|
this.instantiationDepth--;
|
|
4050
4258
|
}
|
|
4051
4259
|
}
|
|
4260
|
+
/** Pair written type arguments with the parameters they instantiate. A
|
|
4261
|
+
* pack parameter (`T...`) takes every argument from its position on, as
|
|
4262
|
+
* one pack: `Signal<Instance, string>` binds `T` to `(Instance, string)`,
|
|
4263
|
+
* and `Signal<()>` to the empty pack. Left out, a parameter takes its
|
|
4264
|
+
* default (`T... = ...any` is `any`), or `unknown`. */
|
|
4265
|
+
bindTypeArguments(params, args) {
|
|
4266
|
+
const subst = /* @__PURE__ */ new Map();
|
|
4267
|
+
params.forEach((p, i) => {
|
|
4268
|
+
let arg = args[i];
|
|
4269
|
+
if (p.isPack && i < args.length) {
|
|
4270
|
+
const rest = args.slice(i);
|
|
4271
|
+
const single = rest.length === 1 ? rest[0] : void 0;
|
|
4272
|
+
arg = single && (single.kind === "tuple" && single.isPack || single.kind === "typeParam" || single.kind === "any") ? single : tuple([...rest], true);
|
|
4273
|
+
}
|
|
4274
|
+
subst.set(p.name, arg ?? (p.default ? this.resolveType(p.default) : unknownType));
|
|
4275
|
+
});
|
|
4276
|
+
return subst;
|
|
4277
|
+
}
|
|
4052
4278
|
// --------------------------------------------------------
|
|
4053
4279
|
// TypeNode -> Type
|
|
4054
4280
|
// --------------------------------------------------------
|
|
@@ -4110,6 +4336,12 @@ var TypeAnalyzer = class {
|
|
|
4110
4336
|
}
|
|
4111
4337
|
const lib = this.options.libTypes?.[node.base];
|
|
4112
4338
|
if (lib) return lib;
|
|
4339
|
+
} else if (this.aliasDefs.has(name)) {
|
|
4340
|
+
return this.expand({
|
|
4341
|
+
kind: "genericRef",
|
|
4342
|
+
name,
|
|
4343
|
+
typeArguments: node.typeArguments.map((a) => this.resolveType(a))
|
|
4344
|
+
});
|
|
4113
4345
|
}
|
|
4114
4346
|
return {
|
|
4115
4347
|
kind: "genericRef",
|
|
@@ -4236,6 +4468,7 @@ var TypeAnalyzer = class {
|
|
|
4236
4468
|
return this.resolveType(node.typeAnnotation);
|
|
4237
4469
|
case "TypePackNode": {
|
|
4238
4470
|
if (node.types.length === 1 && !node.hasVarargs) return this.resolveType(node.types[0]);
|
|
4471
|
+
if (!node.types.length && node.varargType) return this.resolveType(node.varargType);
|
|
4239
4472
|
return tuple(node.types.map((t) => this.resolveType(t)), true);
|
|
4240
4473
|
}
|
|
4241
4474
|
}
|
|
@@ -4259,7 +4492,7 @@ var TypeAnalyzer = class {
|
|
|
4259
4492
|
this.reduceDepth++;
|
|
4260
4493
|
try {
|
|
4261
4494
|
const result = this.reduceTypeInner(t);
|
|
4262
|
-
this.reduceCache.set(t, result);
|
|
4495
|
+
if (result.kind !== "keyof") this.reduceCache.set(t, result);
|
|
4263
4496
|
return result;
|
|
4264
4497
|
} finally {
|
|
4265
4498
|
this.reduceDepth--;
|
|
@@ -4271,6 +4504,7 @@ var TypeAnalyzer = class {
|
|
|
4271
4504
|
case "keyof": {
|
|
4272
4505
|
const target = this.reduceType(t.target);
|
|
4273
4506
|
if (containsTypeParam(target)) return { kind: "keyof", target };
|
|
4507
|
+
if (target.kind === "genericRef" && this.resolvingAliases.has(target.name)) return t;
|
|
4274
4508
|
return this.keysOf(target);
|
|
4275
4509
|
}
|
|
4276
4510
|
case "indexedAccess": {
|
|
@@ -4312,6 +4546,7 @@ var TypeAnalyzer = class {
|
|
|
4312
4546
|
t.predicate
|
|
4313
4547
|
);
|
|
4314
4548
|
case "object": {
|
|
4549
|
+
if (t.class) return t;
|
|
4315
4550
|
const entries = [];
|
|
4316
4551
|
for (const [k, v] of t.properties) entries.push([k, { ...v, type: this.reduceType(v.type) }]);
|
|
4317
4552
|
const reduced = objectType(entries, t.indexer && {
|
|
@@ -4451,6 +4686,7 @@ var TypeAnalyzer = class {
|
|
|
4451
4686
|
t.typeParams
|
|
4452
4687
|
);
|
|
4453
4688
|
case "object": {
|
|
4689
|
+
if (t.class) return t;
|
|
4454
4690
|
const entries = [];
|
|
4455
4691
|
for (const [k, v] of t.properties) entries.push([k, { ...v, type: this.stripInfer(v.type, bindings) }]);
|
|
4456
4692
|
return objectType(entries, t.indexer && {
|
|
@@ -4504,6 +4740,11 @@ var TypeAnalyzer = class {
|
|
|
4504
4740
|
visitStatement(stmt, env) {
|
|
4505
4741
|
switch (stmt.type) {
|
|
4506
4742
|
case "VariableDeclaration": {
|
|
4743
|
+
stmt.names.forEach((target, i) => {
|
|
4744
|
+
if (target.type === "IdentifierPattern" && target.typeAnnotation && stmt.init[i]) {
|
|
4745
|
+
this.applyContext(stmt.init[i], this.resolveType(target.typeAnnotation));
|
|
4746
|
+
}
|
|
4747
|
+
});
|
|
4507
4748
|
const { types: valueTypes, sources } = this.valueList(stmt.init, env);
|
|
4508
4749
|
stmt.names.forEach((target, i) => {
|
|
4509
4750
|
const inferred = valueTypes[i] ?? (stmt.init.length ? unknownType : nilType);
|
|
@@ -4564,6 +4805,16 @@ var TypeAnalyzer = class {
|
|
|
4564
4805
|
return;
|
|
4565
4806
|
}
|
|
4566
4807
|
case "AssignmentStatement": {
|
|
4808
|
+
stmt.targets.forEach((target, i) => {
|
|
4809
|
+
const value = stmt.values[i];
|
|
4810
|
+
if (!value) return;
|
|
4811
|
+
if (target.type === "MemberExpression" || target.type === "IndexExpression") {
|
|
4812
|
+
this.applyContext(value, this.infer(target, env));
|
|
4813
|
+
} else if (target.type === "Identifier") {
|
|
4814
|
+
const id = this.bindingIdOf(target);
|
|
4815
|
+
if (id !== void 0 && this.annotated.has(id)) this.applyContext(value, this.bindingType.get(id));
|
|
4816
|
+
}
|
|
4817
|
+
});
|
|
4567
4818
|
const { types: valueTypes, sources } = this.valueList(stmt.values, env);
|
|
4568
4819
|
stmt.targets.forEach((target, i) => {
|
|
4569
4820
|
const vt = valueTypes[i] ?? unknownType;
|
|
@@ -4708,6 +4959,9 @@ var TypeAnalyzer = class {
|
|
|
4708
4959
|
case "BreakStatement":
|
|
4709
4960
|
this.breakStates[this.breakStates.length - 1]?.push(forkEnv(env));
|
|
4710
4961
|
return;
|
|
4962
|
+
case "DeclareClassStatement":
|
|
4963
|
+
this.checkClass(stmt);
|
|
4964
|
+
return;
|
|
4711
4965
|
case "ContinueStatement":
|
|
4712
4966
|
case "TypeAliasStatement":
|
|
4713
4967
|
case "ExportTypeAliasStatement":
|
|
@@ -4829,7 +5083,46 @@ var TypeAnalyzer = class {
|
|
|
4829
5083
|
}
|
|
4830
5084
|
if (p.pattern) return this.patternToType(p.pattern, env);
|
|
4831
5085
|
if (p.default) return widen(this.infer(p.default, env));
|
|
4832
|
-
return anyType;
|
|
5086
|
+
return this.contextualParams.get(p) ?? anyType;
|
|
5087
|
+
}
|
|
5088
|
+
/** What a function expression's unannotated parameters are, from where
|
|
5089
|
+
* it is written — see `applyContext`. */
|
|
5090
|
+
contextualParams = /* @__PURE__ */ new WeakMap();
|
|
5091
|
+
/** `expected` is the type the surroundings want for `expr`. A function
|
|
5092
|
+
* expression written there takes its unannotated parameters' types from
|
|
5093
|
+
* it, as in TypeScript: `signal:Connect(function(player) ... end)` knows
|
|
5094
|
+
* `player` from `Connect`'s callback type. Anything else is inferred as
|
|
5095
|
+
* usual. */
|
|
5096
|
+
applyContext(expr, expected) {
|
|
5097
|
+
let e = expr;
|
|
5098
|
+
while (e.type === "ParenthesizedExpression") e = e.expression;
|
|
5099
|
+
if (e.type !== "FunctionExpression" || !expected) return;
|
|
5100
|
+
const members = expected.kind === "union" ? expected.types : [expected];
|
|
5101
|
+
const signatures = members.flatMap((m) => this.overloadsOf(this.expand(m)));
|
|
5102
|
+
if (!signatures.length) return;
|
|
5103
|
+
e.func.params.forEach((p, k) => {
|
|
5104
|
+
if (p.typeAnnotation || p.pattern || p.default) return;
|
|
5105
|
+
const candidates = [];
|
|
5106
|
+
for (const signature of signatures) {
|
|
5107
|
+
const t2 = signature.params[k]?.type ?? signature.varargs;
|
|
5108
|
+
if (t2) candidates.push(t2);
|
|
5109
|
+
}
|
|
5110
|
+
if (!candidates.length) return;
|
|
5111
|
+
const t = union(candidates);
|
|
5112
|
+
this.contextualParams.set(p, containsTypeParam(t) ? anyType : t);
|
|
5113
|
+
});
|
|
5114
|
+
}
|
|
5115
|
+
/** The parameter type each written argument lands on, across `fns`. */
|
|
5116
|
+
expectedArguments(written, fns, selfOf) {
|
|
5117
|
+
return written.map((_, j) => {
|
|
5118
|
+
const candidates = [];
|
|
5119
|
+
for (const f of fns) {
|
|
5120
|
+
const i = j + selfOf(f);
|
|
5121
|
+
const param = i < f.params.length ? this.boundParams(f)[i] : f.varargs;
|
|
5122
|
+
if (param) candidates.push(param);
|
|
5123
|
+
}
|
|
5124
|
+
return candidates.length ? union(candidates) : void 0;
|
|
5125
|
+
});
|
|
4833
5126
|
}
|
|
4834
5127
|
/** Synthesize a type from a destructuring pattern used without an
|
|
4835
5128
|
* annotation (`function f({ a, b = 1 })`). */
|
|
@@ -4881,7 +5174,8 @@ var TypeAnalyzer = class {
|
|
|
4881
5174
|
f.params.forEach((p, i) => {
|
|
4882
5175
|
const arg = argTypes[i];
|
|
4883
5176
|
if (arg === void 0) return;
|
|
4884
|
-
|
|
5177
|
+
const param = p.type.kind === "typeParam" && p.type.constraint ? { ...p.type, constraint: this.reduceType(p.type.constraint) } : p.type;
|
|
5178
|
+
unify(p.type, keepsLiterals(param) ? arg : widen(arg), vars, subst);
|
|
4885
5179
|
});
|
|
4886
5180
|
for (const name of f.typeParams ?? []) if (!subst.has(name)) subst.set(name, unknownType);
|
|
4887
5181
|
return subst;
|
|
@@ -4942,12 +5236,13 @@ var TypeAnalyzer = class {
|
|
|
4942
5236
|
return;
|
|
4943
5237
|
}
|
|
4944
5238
|
const t = value;
|
|
5239
|
+
if (t.kind === "object" && t.class) return;
|
|
4945
5240
|
if (t.kind === "typeParam" && typeof t.name === "string" && bounds.has(t.name) && t.constraint && !containsTypeParam(t.constraint)) {
|
|
4946
5241
|
bounds.set(t.name, this.reduceType(t.constraint));
|
|
4947
5242
|
}
|
|
4948
5243
|
for (const child of Object.values(value)) walk(child);
|
|
4949
5244
|
};
|
|
4950
|
-
for (const p of f.params) walk(p.type);
|
|
5245
|
+
for (const p of f.params) if (containsTypeParam(p.type)) walk(p.type);
|
|
4951
5246
|
return f.params.map((p) => substitute(p.type, bounds));
|
|
4952
5247
|
}
|
|
4953
5248
|
/** Record what each written argument is expected to be — see
|
|
@@ -5286,7 +5581,7 @@ var TypeAnalyzer = class {
|
|
|
5286
5581
|
this.expandCache.set(key, t);
|
|
5287
5582
|
this.resolvingAliases.add(t.name);
|
|
5288
5583
|
try {
|
|
5289
|
-
const r = def.params.length ? this.instantiateAlias(def, t.typeArguments) : this.
|
|
5584
|
+
const r = def.params.length ? this.instantiateAlias(def, t.typeArguments) : this.resolveDef(def);
|
|
5290
5585
|
const named = def.params.length === 0 && (r.kind === "object" || r.kind === "intersection") && !r.name ? { ...r, name: t.name } : r;
|
|
5291
5586
|
this.expandCache.set(key, named);
|
|
5292
5587
|
return named;
|
|
@@ -5409,9 +5704,9 @@ var TypeAnalyzer = class {
|
|
|
5409
5704
|
case "not":
|
|
5410
5705
|
return booleanType;
|
|
5411
5706
|
case "-":
|
|
5412
|
-
return numberType;
|
|
5707
|
+
return this.operatorResult(expr, "-", arg, void 0) ?? numberType;
|
|
5413
5708
|
case "#":
|
|
5414
|
-
return numberType;
|
|
5709
|
+
return this.operatorResult(expr, "#", arg, void 0) ?? numberType;
|
|
5415
5710
|
}
|
|
5416
5711
|
return arg;
|
|
5417
5712
|
}
|
|
@@ -5433,7 +5728,7 @@ var TypeAnalyzer = class {
|
|
|
5433
5728
|
const r = this.infer(expr.right, env);
|
|
5434
5729
|
switch (op) {
|
|
5435
5730
|
case "..":
|
|
5436
|
-
return stringType;
|
|
5731
|
+
return this.operatorResult(expr, op, l, r) ?? stringType;
|
|
5437
5732
|
case "==":
|
|
5438
5733
|
case "~=":
|
|
5439
5734
|
case "<":
|
|
@@ -5448,7 +5743,7 @@ var TypeAnalyzer = class {
|
|
|
5448
5743
|
case "//":
|
|
5449
5744
|
case "%":
|
|
5450
5745
|
case "^":
|
|
5451
|
-
return numberType;
|
|
5746
|
+
return this.operatorResult(expr, op, l, r) ?? numberType;
|
|
5452
5747
|
}
|
|
5453
5748
|
return union([l, r]);
|
|
5454
5749
|
}
|
|
@@ -5467,8 +5762,10 @@ var TypeAnalyzer = class {
|
|
|
5467
5762
|
}
|
|
5468
5763
|
case "CallExpression": {
|
|
5469
5764
|
const callee = this.infer(expr.callee, env);
|
|
5470
|
-
const argTypes = expr.arguments.map((a) => this.infer(a, env));
|
|
5471
5765
|
const fns = this.overloadsOf(callee);
|
|
5766
|
+
const expected = this.expectedArguments(expr.arguments, fns, () => 0);
|
|
5767
|
+
expr.arguments.forEach((a, i) => this.applyContext(a, expected[i]));
|
|
5768
|
+
const argTypes = expr.arguments.map((a) => this.infer(a, env));
|
|
5472
5769
|
if (fns.length) {
|
|
5473
5770
|
this.recordExpected(expr.arguments, fns, () => 0);
|
|
5474
5771
|
const arityFits = this.checkArity(expr, fns, argTypes.length, 0);
|
|
@@ -5483,8 +5780,10 @@ var TypeAnalyzer = class {
|
|
|
5483
5780
|
}
|
|
5484
5781
|
case "MethodCallExpression": {
|
|
5485
5782
|
const objType = this.infer(expr.object, env);
|
|
5486
|
-
const argTypes = expr.arguments.map((a) => this.infer(a, env));
|
|
5487
5783
|
const fns = this.overloadsOf(this.propertyType(objType, expr.method.name));
|
|
5784
|
+
const expected = this.expectedArguments(expr.arguments, fns, (f) => this.takesSelf(f) ? 1 : 0);
|
|
5785
|
+
expr.arguments.forEach((a, i) => this.applyContext(a, expected[i]));
|
|
5786
|
+
const argTypes = expr.arguments.map((a) => this.infer(a, env));
|
|
5488
5787
|
if (fns.length) {
|
|
5489
5788
|
const withSelf = (f) => this.takesSelf(f) ? [objType, ...argTypes] : argTypes;
|
|
5490
5789
|
const selfOf = (f) => this.takesSelf(f) ? 1 : 0;
|
|
@@ -5876,6 +6175,35 @@ var TypeAnalyzer = class {
|
|
|
5876
6175
|
this.selfType = saved;
|
|
5877
6176
|
}
|
|
5878
6177
|
}
|
|
6178
|
+
/** What an operator on a value with metamethods gives: `a + b` calls
|
|
6179
|
+
* `__add` on `a`, or failing that on `b` with the operands swapped — the
|
|
6180
|
+
* order Luau tries them in. That is how `Vector3 + Vector3`, `CFrame *
|
|
6181
|
+
* Vector3` and `2 * vector` get their types from the declarations.
|
|
6182
|
+
* `undefined` when neither operand declares the metamethod; an operand
|
|
6183
|
+
* that declares it but accepts neither argument is reported. */
|
|
6184
|
+
operatorResult(node, op, left, right) {
|
|
6185
|
+
const name = right === void 0 ? op === "-" ? "__unm" : "__len" : METAMETHODS[op];
|
|
6186
|
+
if (!name) return void 0;
|
|
6187
|
+
const candidates = right === void 0 ? [[left, void 0]] : [[left, right], [right, left]];
|
|
6188
|
+
let declared;
|
|
6189
|
+
for (const [receiver, other] of candidates) {
|
|
6190
|
+
const t = this.expand(receiver);
|
|
6191
|
+
const method = t.kind === "object" ? t.properties.get(name) : void 0;
|
|
6192
|
+
if (!method) continue;
|
|
6193
|
+
declared ??= receiver;
|
|
6194
|
+
const args = other === void 0 ? [receiver] : [receiver, other];
|
|
6195
|
+
const picked = this.pickOverload(this.overloadsOf(method.type), args);
|
|
6196
|
+
if (picked) return this.callReturn(picked, args);
|
|
6197
|
+
}
|
|
6198
|
+
if (declared && this.emitDiagnostics) {
|
|
6199
|
+
this.diagnostics.push({
|
|
6200
|
+
node,
|
|
6201
|
+
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)}'`
|
|
6202
|
+
});
|
|
6203
|
+
return anyType;
|
|
6204
|
+
}
|
|
6205
|
+
return void 0;
|
|
6206
|
+
}
|
|
5879
6207
|
/** Does this signature take the receiver as its first parameter?
|
|
5880
6208
|
*
|
|
5881
6209
|
* Luau's `:` is sugar both ways: `function T:m(a)` declares
|
|
@@ -6249,7 +6577,6 @@ function sourceMapTypes(text, path, options) {
|
|
|
6249
6577
|
const lines = [];
|
|
6250
6578
|
const aliasOfFile = /* @__PURE__ */ new Map();
|
|
6251
6579
|
const used = /* @__PURE__ */ new Set();
|
|
6252
|
-
const canOmit = options.classes.has("Omit");
|
|
6253
6580
|
const aliasOfNode = /* @__PURE__ */ new Map();
|
|
6254
6581
|
const aliasFor = (segments) => {
|
|
6255
6582
|
const base = `SourceMap_${segments.map((s) => s.replace(/[^A-Za-z0-9_]/g, "_")).join("_")}`;
|
|
@@ -6265,7 +6592,7 @@ function sourceMapTypes(text, path, options) {
|
|
|
6265
6592
|
const className = IDENTIFIER.test(node.className) && options.classes.has(node.className) ? node.className : "Instance";
|
|
6266
6593
|
const taken = options.membersOf?.(className) ?? INSTANCE_MEMBERS;
|
|
6267
6594
|
const members = [];
|
|
6268
|
-
if (parent
|
|
6595
|
+
if (parent) members.push(`Parent: ${parent}`);
|
|
6269
6596
|
const named = /* @__PURE__ */ new Set();
|
|
6270
6597
|
for (const child of node.children ?? []) {
|
|
6271
6598
|
if (!isNode(child)) continue;
|
|
@@ -6274,8 +6601,7 @@ function sourceMapTypes(text, path, options) {
|
|
|
6274
6601
|
named.add(child.name);
|
|
6275
6602
|
members.push(`${child.name}: ${childAlias}`);
|
|
6276
6603
|
}
|
|
6277
|
-
|
|
6278
|
-
lines.push(`type ${alias} = ${members.length ? `${base} & { ${members.join(", ")} }` : base}`);
|
|
6604
|
+
lines.push(`declare class ${alias} extends ${className} { ${members.join(", ")} }`);
|
|
6279
6605
|
return alias;
|
|
6280
6606
|
};
|
|
6281
6607
|
const rootAlias = visit(root, [root.name], void 0);
|
|
@@ -6353,6 +6679,7 @@ var index_default = luautparser;
|
|
|
6353
6679
|
getBinding,
|
|
6354
6680
|
intersection,
|
|
6355
6681
|
isAssignable,
|
|
6682
|
+
isClassType,
|
|
6356
6683
|
isGlobal,
|
|
6357
6684
|
isPossiblyFalsy,
|
|
6358
6685
|
isPossiblyTruthy,
|