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.js
CHANGED
|
@@ -852,6 +852,9 @@ var Parser = class {
|
|
|
852
852
|
}
|
|
853
853
|
if (t.type === "Identifier" && t.value === "declare") {
|
|
854
854
|
const p1 = this.peek(1);
|
|
855
|
+
if (p1.type === "Identifier" && p1.value === "class" && this.peek(2).type === "Identifier") {
|
|
856
|
+
return this.parseDeclareClassStatement();
|
|
857
|
+
}
|
|
855
858
|
if (p1.type === "Identifier" || p1.type === "Keyword" && p1.value === "function") {
|
|
856
859
|
return this.parseDeclareStatement();
|
|
857
860
|
}
|
|
@@ -893,6 +896,36 @@ var Parser = class {
|
|
|
893
896
|
const valueType = this.parseType();
|
|
894
897
|
return { type: "DeclareStatement", name: nameTok.value, id: tokenIdentifier(nameTok), valueType, ...spanFrom(start, this.previous()) };
|
|
895
898
|
}
|
|
899
|
+
/** A declared type's name. It may be qualified once — `Enum.Material` —
|
|
900
|
+
* which is how a definitions file names types under a namespace, and how
|
|
901
|
+
* they are then written (`const m: Enum.Material`). */
|
|
902
|
+
parseTypeName() {
|
|
903
|
+
const first = this.expectIdentifier();
|
|
904
|
+
if (this.checkPunctuator(".") && this.peek(1).type === "Identifier") {
|
|
905
|
+
this.advance();
|
|
906
|
+
const second = this.expectIdentifier();
|
|
907
|
+
return { type: "Identifier", name: `${first.value}.${second.value}`, ...spanFrom(first, second) };
|
|
908
|
+
}
|
|
909
|
+
return tokenIdentifier(first);
|
|
910
|
+
}
|
|
911
|
+
// `declare class Name extends Base { member: T, ... }`
|
|
912
|
+
parseDeclareClassStatement() {
|
|
913
|
+
const start = this.current();
|
|
914
|
+
this.advance();
|
|
915
|
+
this.advance();
|
|
916
|
+
const name = this.parseTypeName();
|
|
917
|
+
let superclass;
|
|
918
|
+
if (this.checkIdentifierValue("extends")) {
|
|
919
|
+
this.advance();
|
|
920
|
+
const base = this.parseType();
|
|
921
|
+
if (base.type !== "TypeReference") this.error("A class can only extend another class, written by name");
|
|
922
|
+
superclass = base;
|
|
923
|
+
}
|
|
924
|
+
if (!this.checkPunctuator("{")) this.error("Expected '{' to start the class body");
|
|
925
|
+
const body = this.parseTableType();
|
|
926
|
+
if (body.type !== "TableTypeNode") this.error("A class body lists members ('name: T'), not a mapped type");
|
|
927
|
+
return { type: "DeclareClassStatement", name, superclass, body, ...spanFrom(start, this.previous()) };
|
|
928
|
+
}
|
|
896
929
|
// `import { a, b as c } from '...'` / `import Default from '...'` /
|
|
897
930
|
// `import Default, { a } from '...'`. Compiled away entirely by the
|
|
898
931
|
// bundler — never survives into emitted Luau.
|
|
@@ -1223,8 +1256,7 @@ var Parser = class {
|
|
|
1223
1256
|
parseTypeAliasStatement() {
|
|
1224
1257
|
const start = this.current();
|
|
1225
1258
|
this.advance();
|
|
1226
|
-
const
|
|
1227
|
-
const name = { type: "Identifier", name: nameTok.value, ...spanFrom(nameTok, nameTok) };
|
|
1259
|
+
const name = this.parseTypeName();
|
|
1228
1260
|
let generics = [];
|
|
1229
1261
|
if (this.checkOperator("<")) {
|
|
1230
1262
|
generics = this.parseGenericTypeParameterList();
|
|
@@ -2726,6 +2758,9 @@ var Analyzer = class {
|
|
|
2726
2758
|
case "DeclareStatement":
|
|
2727
2759
|
this.visitType(stmt.valueType, scope);
|
|
2728
2760
|
return;
|
|
2761
|
+
case "DeclareClassStatement":
|
|
2762
|
+
this.visitType(stmt.body, scope);
|
|
2763
|
+
return;
|
|
2729
2764
|
case "TypeAliasStatement":
|
|
2730
2765
|
case "ExportTypeAliasStatement":
|
|
2731
2766
|
this.visitType(stmt.definition, scope);
|
|
@@ -2899,6 +2934,9 @@ function analyzeScopes(program, options = {}) {
|
|
|
2899
2934
|
}
|
|
2900
2935
|
|
|
2901
2936
|
// src/ast/typeModel.ts
|
|
2937
|
+
function isClassType(t) {
|
|
2938
|
+
return t.kind === "object" && t.class !== void 0;
|
|
2939
|
+
}
|
|
2902
2940
|
function typeParam(name, constraint, isConst) {
|
|
2903
2941
|
return { kind: "typeParam", name, constraint, isConst };
|
|
2904
2942
|
}
|
|
@@ -2958,10 +2996,16 @@ function substitute(t, subst) {
|
|
|
2958
2996
|
}
|
|
2959
2997
|
case "function": {
|
|
2960
2998
|
const inner = t.typeParams ? new Map([...subst].filter(([k]) => !t.typeParams.includes(k))) : subst;
|
|
2999
|
+
let params = t.params.map((p) => ({ ...p, type: substitute(p.type, inner) }));
|
|
3000
|
+
let varargs = t.varargs && substitute(t.varargs, inner);
|
|
3001
|
+
if (varargs?.kind === "tuple" && varargs.isPack) {
|
|
3002
|
+
params = [...params, ...varargs.elements.map((type) => ({ type }))];
|
|
3003
|
+
varargs = void 0;
|
|
3004
|
+
}
|
|
2961
3005
|
return {
|
|
2962
3006
|
kind: "function",
|
|
2963
|
-
params
|
|
2964
|
-
varargs
|
|
3007
|
+
params,
|
|
3008
|
+
varargs,
|
|
2965
3009
|
returns: substitute(t.returns, inner),
|
|
2966
3010
|
typeParams: t.typeParams,
|
|
2967
3011
|
predicate: t.predicate && {
|
|
@@ -3041,7 +3085,8 @@ function unify(param, arg, vars, out) {
|
|
|
3041
3085
|
}
|
|
3042
3086
|
return;
|
|
3043
3087
|
case "object":
|
|
3044
|
-
if (
|
|
3088
|
+
if (param.class) return;
|
|
3089
|
+
if (arg.kind === "object" && !arg.class) {
|
|
3045
3090
|
for (const [k, pv] of param.properties) {
|
|
3046
3091
|
const av = arg.properties.get(k);
|
|
3047
3092
|
if (av) unify(pv.type, av.type, vars, out);
|
|
@@ -3124,7 +3169,7 @@ function widen(t) {
|
|
|
3124
3169
|
case "tuple":
|
|
3125
3170
|
return tuple(t.elements.map(widen), t.isPack);
|
|
3126
3171
|
case "object": {
|
|
3127
|
-
if (t.frozen) return t;
|
|
3172
|
+
if (t.frozen || t.class) return t;
|
|
3128
3173
|
const entries = [];
|
|
3129
3174
|
for (const [k, v] of t.properties) entries.push([k, { ...v, type: widen(v.type) }]);
|
|
3130
3175
|
const w = objectType(entries, t.indexer && { key: t.indexer.key, value: widen(t.indexer.value) });
|
|
@@ -3151,6 +3196,10 @@ function isAssignable(rawA, rawB) {
|
|
|
3151
3196
|
if (expandAlias) {
|
|
3152
3197
|
if (a.kind === "genericRef" && b.kind !== "genericRef") a = expandAlias(a);
|
|
3153
3198
|
else if (b.kind === "genericRef" && a.kind !== "genericRef") b = expandAlias(b);
|
|
3199
|
+
else if (a.kind === "genericRef" && b.kind === "genericRef" && a.name !== b.name) {
|
|
3200
|
+
a = expandAlias(a);
|
|
3201
|
+
b = expandAlias(b);
|
|
3202
|
+
}
|
|
3154
3203
|
if (a === b) return true;
|
|
3155
3204
|
}
|
|
3156
3205
|
for (let i = 0; i < comparing.length; i += 2) {
|
|
@@ -3206,6 +3255,8 @@ function isAssignableInner(a, b) {
|
|
|
3206
3255
|
}
|
|
3207
3256
|
if (a.kind === "object") {
|
|
3208
3257
|
if (b.kind !== "object") return false;
|
|
3258
|
+
if (b.class) return a.class !== void 0 && a.class.ancestors.includes(b.class.name);
|
|
3259
|
+
if (a.class && (b.indexer || b.properties.size === 0)) return false;
|
|
3209
3260
|
for (const [name, bp] of b.properties) {
|
|
3210
3261
|
const ap = a.properties.get(name);
|
|
3211
3262
|
if (!ap) {
|
|
@@ -3348,6 +3399,7 @@ function containsFreeTypeParam(t, seen, bound) {
|
|
|
3348
3399
|
case "intersection":
|
|
3349
3400
|
return t.types.some((m) => containsTypeParam(m, seen, bound));
|
|
3350
3401
|
case "object":
|
|
3402
|
+
if (t.class) return false;
|
|
3351
3403
|
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));
|
|
3352
3404
|
case "function": {
|
|
3353
3405
|
const inner = t.typeParams?.length ? /* @__PURE__ */ new Set([...bound, ...t.typeParams]) : bound;
|
|
@@ -3546,7 +3598,7 @@ function mergeObjectMembers(types) {
|
|
|
3546
3598
|
return expanded !== void 0 && expanded !== t && collect(expanded);
|
|
3547
3599
|
}
|
|
3548
3600
|
if (t.kind === "intersection") return t.types.every(collect);
|
|
3549
|
-
if (t.kind === "object") {
|
|
3601
|
+
if (t.kind === "object" && !t.class) {
|
|
3550
3602
|
objects.push(t);
|
|
3551
3603
|
return true;
|
|
3552
3604
|
}
|
|
@@ -3758,6 +3810,62 @@ function keepsLiterals(paramType) {
|
|
|
3758
3810
|
const members = paramType.constraint.kind === "union" ? paramType.constraint.types : [paramType.constraint];
|
|
3759
3811
|
return members.some((m) => m.kind === "literal");
|
|
3760
3812
|
}
|
|
3813
|
+
var AliasMap = class extends Map {
|
|
3814
|
+
pending = /* @__PURE__ */ new Map();
|
|
3815
|
+
defer(name, resolve5) {
|
|
3816
|
+
super.delete(name);
|
|
3817
|
+
this.pending.set(name, resolve5);
|
|
3818
|
+
}
|
|
3819
|
+
get(name) {
|
|
3820
|
+
const resolved = super.get(name);
|
|
3821
|
+
if (resolved !== void 0) return resolved;
|
|
3822
|
+
const resolve5 = this.pending.get(name);
|
|
3823
|
+
if (!resolve5) return void 0;
|
|
3824
|
+
this.pending.delete(name);
|
|
3825
|
+
const type = resolve5();
|
|
3826
|
+
super.set(name, type);
|
|
3827
|
+
return type;
|
|
3828
|
+
}
|
|
3829
|
+
has(name) {
|
|
3830
|
+
return super.has(name) || (this.pending?.has(name) ?? false);
|
|
3831
|
+
}
|
|
3832
|
+
set(name, type) {
|
|
3833
|
+
this.pending?.delete(name);
|
|
3834
|
+
return super.set(name, type);
|
|
3835
|
+
}
|
|
3836
|
+
delete(name) {
|
|
3837
|
+
const deferred = this.pending?.delete(name) ?? false;
|
|
3838
|
+
return super.delete(name) || deferred;
|
|
3839
|
+
}
|
|
3840
|
+
get size() {
|
|
3841
|
+
return super.size + (this.pending?.size ?? 0);
|
|
3842
|
+
}
|
|
3843
|
+
keys() {
|
|
3844
|
+
return [...super.keys(), ...this.pending?.keys() ?? []][Symbol.iterator]();
|
|
3845
|
+
}
|
|
3846
|
+
entries() {
|
|
3847
|
+
return [...this.keys()].map((name) => [name, this.get(name)])[Symbol.iterator]();
|
|
3848
|
+
}
|
|
3849
|
+
values() {
|
|
3850
|
+
return [...this.keys()].map((name) => this.get(name))[Symbol.iterator]();
|
|
3851
|
+
}
|
|
3852
|
+
forEach(callback, thisArg) {
|
|
3853
|
+
for (const [name, type] of this.entries()) callback.call(thisArg, type, name, this);
|
|
3854
|
+
}
|
|
3855
|
+
[Symbol.iterator]() {
|
|
3856
|
+
return this.entries();
|
|
3857
|
+
}
|
|
3858
|
+
};
|
|
3859
|
+
var METAMETHODS = {
|
|
3860
|
+
"+": "__add",
|
|
3861
|
+
"-": "__sub",
|
|
3862
|
+
"*": "__mul",
|
|
3863
|
+
"/": "__div",
|
|
3864
|
+
"//": "__idiv",
|
|
3865
|
+
"%": "__mod",
|
|
3866
|
+
"^": "__pow",
|
|
3867
|
+
"..": "__concat"
|
|
3868
|
+
};
|
|
3761
3869
|
function posKey(name, line, column) {
|
|
3762
3870
|
return `${name}@${line}:${column}`;
|
|
3763
3871
|
}
|
|
@@ -3778,9 +3886,12 @@ var TypeAnalyzer = class {
|
|
|
3778
3886
|
expectedTypeOf = /* @__PURE__ */ new Map();
|
|
3779
3887
|
/** Public: each alias resolved once (generic aliases keep their params as
|
|
3780
3888
|
* `typeParam` nodes in the body). */
|
|
3781
|
-
aliases =
|
|
3889
|
+
aliases = new AliasMap();
|
|
3782
3890
|
/** Uninstantiated alias definitions, for `Name<Args>` instantiation. */
|
|
3783
3891
|
aliasDefs = /* @__PURE__ */ new Map();
|
|
3892
|
+
/** See `resolveClass`. */
|
|
3893
|
+
classTypes = /* @__PURE__ */ new WeakMap();
|
|
3894
|
+
classMembers = /* @__PURE__ */ new WeakMap();
|
|
3784
3895
|
/** Generic parameters currently in lexical scope (alias body / generic fn),
|
|
3785
3896
|
* with their `extends` constraints resolved. */
|
|
3786
3897
|
typeParamScope = [];
|
|
@@ -3890,24 +4001,123 @@ var TypeAnalyzer = class {
|
|
|
3890
4001
|
for (const stmt of block.statements) {
|
|
3891
4002
|
const alias = stmt.type === "TypeAliasStatement" ? stmt : stmt.type === "ExportTypeAliasStatement" ? stmt.alias : void 0;
|
|
3892
4003
|
if (alias) this.aliasDefs.set(alias.name.name, { params: alias.generics, node: alias.definition });
|
|
4004
|
+
if (stmt.type === "DeclareClassStatement") {
|
|
4005
|
+
this.aliasDefs.set(stmt.name.name, { params: [], node: stmt.body, class: stmt });
|
|
4006
|
+
}
|
|
3893
4007
|
}
|
|
3894
4008
|
}
|
|
3895
|
-
/**
|
|
3896
|
-
|
|
3897
|
-
|
|
4009
|
+
/** A non-generic definition's type. */
|
|
4010
|
+
resolveDef(def) {
|
|
4011
|
+
return def.class ? this.classType(def.class) : this.resolveType(def.node);
|
|
4012
|
+
}
|
|
4013
|
+
/** One type per class declaration, so every mention of a class is the same
|
|
4014
|
+
* object — its own members included, which refer back to it. */
|
|
4015
|
+
classType(stmt) {
|
|
4016
|
+
return this.classTypes.get(stmt) ?? this.resolveClass(stmt);
|
|
4017
|
+
}
|
|
4018
|
+
/** A class's members are resolved the first time anyone asks for
|
|
4019
|
+
* `properties` — its own from its body, the inherited ones from its
|
|
4020
|
+
* superclass.
|
|
4021
|
+
*
|
|
4022
|
+
* Both have to wait. A definitions file for a whole engine declares
|
|
4023
|
+
* thousands of classes that all refer to one another; resolving each body
|
|
4024
|
+
* as soon as the class is named would resolve every class on every
|
|
4025
|
+
* analysis, when a script touches a handful. And classes refer to one
|
|
4026
|
+
* another constantly — `Object.IsA` mentions a map of every class, each
|
|
4027
|
+
* of which extends `Object` — so while one class resolves, one it extends
|
|
4028
|
+
* may itself be half-resolved; copying its members then would miss some
|
|
4029
|
+
* for good. */
|
|
4030
|
+
resolveClass(stmt) {
|
|
4031
|
+
const name = stmt.name.name;
|
|
4032
|
+
const { ancestors, cyclic } = this.classChain(stmt);
|
|
4033
|
+
const superclass = !cyclic && ancestors.length > 1 ? this.aliasDefs.get(ancestors[1])?.class : void 0;
|
|
4034
|
+
let own;
|
|
4035
|
+
let resolvingOwn = false;
|
|
4036
|
+
const ownMembers = () => {
|
|
4037
|
+
if (own || resolvingOwn) return own;
|
|
4038
|
+
resolvingOwn = true;
|
|
4039
|
+
try {
|
|
4040
|
+
own = this.resolveType(stmt.body);
|
|
4041
|
+
} finally {
|
|
4042
|
+
resolvingOwn = false;
|
|
4043
|
+
}
|
|
4044
|
+
return own;
|
|
4045
|
+
};
|
|
4046
|
+
let complete;
|
|
4047
|
+
const members = () => {
|
|
4048
|
+
if (complete) return complete;
|
|
4049
|
+
const mine = ownMembers();
|
|
4050
|
+
if (!mine) return void 0;
|
|
4051
|
+
const base = superclass ? this.classMembers.get(this.classType(superclass))?.() : void 0;
|
|
4052
|
+
if (superclass && !base) return void 0;
|
|
4053
|
+
return complete = {
|
|
4054
|
+
properties: new Map([...base?.properties ?? [], ...mine.properties]),
|
|
4055
|
+
indexer: mine.indexer ?? base?.indexer
|
|
4056
|
+
};
|
|
4057
|
+
};
|
|
4058
|
+
const type = { kind: "object", name, class: { name, superclass: superclass?.name.name, ancestors } };
|
|
4059
|
+
Object.defineProperties(type, {
|
|
4060
|
+
properties: { enumerable: true, get: () => members()?.properties ?? own?.properties ?? /* @__PURE__ */ new Map() },
|
|
4061
|
+
indexer: { enumerable: true, get: () => members()?.indexer ?? own?.indexer }
|
|
4062
|
+
});
|
|
4063
|
+
this.classTypes.set(stmt, type);
|
|
4064
|
+
this.classMembers.set(type, members);
|
|
4065
|
+
if (this.program.body.statements.includes(stmt)) ownMembers();
|
|
4066
|
+
return type;
|
|
4067
|
+
}
|
|
4068
|
+
/** `extends` must name a class, and the chain must end. */
|
|
4069
|
+
checkClass(stmt) {
|
|
4070
|
+
if (!stmt.superclass || !this.emitDiagnostics) return;
|
|
4071
|
+
const base = stmt.superclass.base;
|
|
4072
|
+
if (!this.aliasDefs.get(base)?.class) {
|
|
4073
|
+
const known = this.aliasDefs.has(base) || this.importedTypes.has(base);
|
|
4074
|
+
this.diagnostics.push({
|
|
4075
|
+
node: stmt.superclass,
|
|
4076
|
+
message: known ? `'${base}' is not a class; a class can only extend another class` : `Cannot find class '${base}'`
|
|
4077
|
+
});
|
|
4078
|
+
} else if (this.classChain(stmt).cyclic) {
|
|
4079
|
+
this.diagnostics.push({ node: stmt.superclass, message: `'${stmt.name.name}' cannot extend itself` });
|
|
4080
|
+
}
|
|
4081
|
+
}
|
|
4082
|
+
/** The class and the classes it extends, nearest first, read from the
|
|
4083
|
+
* declarations — no type has to be resolved to know them. The walk stops
|
|
4084
|
+
* at a superclass that is not a class. */
|
|
4085
|
+
classChain(stmt) {
|
|
4086
|
+
const ancestors = [stmt.name.name];
|
|
4087
|
+
for (let cls = stmt; cls?.superclass; ) {
|
|
4088
|
+
const base = cls.superclass.base;
|
|
4089
|
+
if (ancestors.includes(base)) return { ancestors, cyclic: true };
|
|
4090
|
+
cls = this.aliasDefs.get(base)?.class;
|
|
4091
|
+
if (!cls) break;
|
|
4092
|
+
ancestors.push(base);
|
|
4093
|
+
}
|
|
4094
|
+
return { ancestors, cyclic: false };
|
|
4095
|
+
}
|
|
4096
|
+
/** Seed global types from `declare` statements. Repeating a function name
|
|
4097
|
+
* builds an *overload set* (an intersection, in declaration order) rather
|
|
4098
|
+
* than replacing — which is how `typeof` gets one signature per result
|
|
4099
|
+
* string. Any other value is simply redeclared: a sourcemap's
|
|
4100
|
+
* `declare script: <this file's instance>` replaces the library's
|
|
4101
|
+
* `declare script: LuaSourceContainer`. */
|
|
3898
4102
|
harvestDeclares(block) {
|
|
3899
4103
|
for (const stmt of block.statements) {
|
|
3900
4104
|
if (stmt.type !== "DeclareStatement") continue;
|
|
3901
4105
|
const t = this.resolveType(stmt.valueType);
|
|
3902
4106
|
const prev = this.libGlobalTypes.get(stmt.name);
|
|
3903
|
-
|
|
4107
|
+
const overload = prev && stmt.valueType.type === "FunctionTypeNode" && (prev.kind === "function" || prev.kind === "intersection");
|
|
4108
|
+
this.libGlobalTypes.set(stmt.name, overload ? intersection([prev, t]) : t);
|
|
3904
4109
|
}
|
|
3905
4110
|
}
|
|
3906
4111
|
resolveAllAliases() {
|
|
3907
4112
|
for (const [name, def] of this.aliasDefs) {
|
|
4113
|
+
if (def.class && !this.program.body.statements.includes(def.class)) {
|
|
4114
|
+
const cls = def.class;
|
|
4115
|
+
this.aliases.defer(name, () => this.classType(cls));
|
|
4116
|
+
continue;
|
|
4117
|
+
}
|
|
3908
4118
|
if (containsTypeQuery(def.node)) continue;
|
|
3909
4119
|
this.withTypeParams(def.params, () => {
|
|
3910
|
-
this.aliases.set(name, this.
|
|
4120
|
+
this.aliases.set(name, this.resolveDef(def));
|
|
3911
4121
|
});
|
|
3912
4122
|
}
|
|
3913
4123
|
}
|
|
@@ -3917,7 +4127,7 @@ var TypeAnalyzer = class {
|
|
|
3917
4127
|
for (const [name, def] of this.aliasDefs) {
|
|
3918
4128
|
if (this.aliases.has(name)) continue;
|
|
3919
4129
|
this.withTypeParams(def.params, () => {
|
|
3920
|
-
this.aliases.set(name, this.
|
|
4130
|
+
this.aliases.set(name, this.resolveDef(def));
|
|
3921
4131
|
});
|
|
3922
4132
|
}
|
|
3923
4133
|
return this.aliases;
|
|
@@ -3945,10 +4155,7 @@ var TypeAnalyzer = class {
|
|
|
3945
4155
|
/** Instantiate a generic alias: `Box<number>` -> `{ value: number }`. */
|
|
3946
4156
|
instantiateAlias(def, args) {
|
|
3947
4157
|
if (this.instantiationDepth > 20) return unknownType;
|
|
3948
|
-
const subst =
|
|
3949
|
-
def.params.forEach((p, i) => {
|
|
3950
|
-
subst.set(p.name, args[i] ?? (p.default ? this.resolveType(p.default) : unknownType));
|
|
3951
|
-
});
|
|
4158
|
+
const subst = this.bindTypeArguments(def.params, args);
|
|
3952
4159
|
this.instantiationDepth++;
|
|
3953
4160
|
try {
|
|
3954
4161
|
const body = this.withTypeParams(def.params, () => this.resolveType(def.node));
|
|
@@ -3957,6 +4164,24 @@ var TypeAnalyzer = class {
|
|
|
3957
4164
|
this.instantiationDepth--;
|
|
3958
4165
|
}
|
|
3959
4166
|
}
|
|
4167
|
+
/** Pair written type arguments with the parameters they instantiate. A
|
|
4168
|
+
* pack parameter (`T...`) takes every argument from its position on, as
|
|
4169
|
+
* one pack: `Signal<Instance, string>` binds `T` to `(Instance, string)`,
|
|
4170
|
+
* and `Signal<()>` to the empty pack. Left out, a parameter takes its
|
|
4171
|
+
* default (`T... = ...any` is `any`), or `unknown`. */
|
|
4172
|
+
bindTypeArguments(params, args) {
|
|
4173
|
+
const subst = /* @__PURE__ */ new Map();
|
|
4174
|
+
params.forEach((p, i) => {
|
|
4175
|
+
let arg = args[i];
|
|
4176
|
+
if (p.isPack && i < args.length) {
|
|
4177
|
+
const rest = args.slice(i);
|
|
4178
|
+
const single = rest.length === 1 ? rest[0] : void 0;
|
|
4179
|
+
arg = single && (single.kind === "tuple" && single.isPack || single.kind === "typeParam" || single.kind === "any") ? single : tuple([...rest], true);
|
|
4180
|
+
}
|
|
4181
|
+
subst.set(p.name, arg ?? (p.default ? this.resolveType(p.default) : unknownType));
|
|
4182
|
+
});
|
|
4183
|
+
return subst;
|
|
4184
|
+
}
|
|
3960
4185
|
// --------------------------------------------------------
|
|
3961
4186
|
// TypeNode -> Type
|
|
3962
4187
|
// --------------------------------------------------------
|
|
@@ -4018,6 +4243,12 @@ var TypeAnalyzer = class {
|
|
|
4018
4243
|
}
|
|
4019
4244
|
const lib = this.options.libTypes?.[node.base];
|
|
4020
4245
|
if (lib) return lib;
|
|
4246
|
+
} else if (this.aliasDefs.has(name)) {
|
|
4247
|
+
return this.expand({
|
|
4248
|
+
kind: "genericRef",
|
|
4249
|
+
name,
|
|
4250
|
+
typeArguments: node.typeArguments.map((a) => this.resolveType(a))
|
|
4251
|
+
});
|
|
4021
4252
|
}
|
|
4022
4253
|
return {
|
|
4023
4254
|
kind: "genericRef",
|
|
@@ -4144,6 +4375,7 @@ var TypeAnalyzer = class {
|
|
|
4144
4375
|
return this.resolveType(node.typeAnnotation);
|
|
4145
4376
|
case "TypePackNode": {
|
|
4146
4377
|
if (node.types.length === 1 && !node.hasVarargs) return this.resolveType(node.types[0]);
|
|
4378
|
+
if (!node.types.length && node.varargType) return this.resolveType(node.varargType);
|
|
4147
4379
|
return tuple(node.types.map((t) => this.resolveType(t)), true);
|
|
4148
4380
|
}
|
|
4149
4381
|
}
|
|
@@ -4167,7 +4399,7 @@ var TypeAnalyzer = class {
|
|
|
4167
4399
|
this.reduceDepth++;
|
|
4168
4400
|
try {
|
|
4169
4401
|
const result = this.reduceTypeInner(t);
|
|
4170
|
-
this.reduceCache.set(t, result);
|
|
4402
|
+
if (result.kind !== "keyof") this.reduceCache.set(t, result);
|
|
4171
4403
|
return result;
|
|
4172
4404
|
} finally {
|
|
4173
4405
|
this.reduceDepth--;
|
|
@@ -4179,6 +4411,7 @@ var TypeAnalyzer = class {
|
|
|
4179
4411
|
case "keyof": {
|
|
4180
4412
|
const target = this.reduceType(t.target);
|
|
4181
4413
|
if (containsTypeParam(target)) return { kind: "keyof", target };
|
|
4414
|
+
if (target.kind === "genericRef" && this.resolvingAliases.has(target.name)) return t;
|
|
4182
4415
|
return this.keysOf(target);
|
|
4183
4416
|
}
|
|
4184
4417
|
case "indexedAccess": {
|
|
@@ -4220,6 +4453,7 @@ var TypeAnalyzer = class {
|
|
|
4220
4453
|
t.predicate
|
|
4221
4454
|
);
|
|
4222
4455
|
case "object": {
|
|
4456
|
+
if (t.class) return t;
|
|
4223
4457
|
const entries = [];
|
|
4224
4458
|
for (const [k, v] of t.properties) entries.push([k, { ...v, type: this.reduceType(v.type) }]);
|
|
4225
4459
|
const reduced = objectType(entries, t.indexer && {
|
|
@@ -4359,6 +4593,7 @@ var TypeAnalyzer = class {
|
|
|
4359
4593
|
t.typeParams
|
|
4360
4594
|
);
|
|
4361
4595
|
case "object": {
|
|
4596
|
+
if (t.class) return t;
|
|
4362
4597
|
const entries = [];
|
|
4363
4598
|
for (const [k, v] of t.properties) entries.push([k, { ...v, type: this.stripInfer(v.type, bindings) }]);
|
|
4364
4599
|
return objectType(entries, t.indexer && {
|
|
@@ -4412,6 +4647,11 @@ var TypeAnalyzer = class {
|
|
|
4412
4647
|
visitStatement(stmt, env) {
|
|
4413
4648
|
switch (stmt.type) {
|
|
4414
4649
|
case "VariableDeclaration": {
|
|
4650
|
+
stmt.names.forEach((target, i) => {
|
|
4651
|
+
if (target.type === "IdentifierPattern" && target.typeAnnotation && stmt.init[i]) {
|
|
4652
|
+
this.applyContext(stmt.init[i], this.resolveType(target.typeAnnotation));
|
|
4653
|
+
}
|
|
4654
|
+
});
|
|
4415
4655
|
const { types: valueTypes, sources } = this.valueList(stmt.init, env);
|
|
4416
4656
|
stmt.names.forEach((target, i) => {
|
|
4417
4657
|
const inferred = valueTypes[i] ?? (stmt.init.length ? unknownType : nilType);
|
|
@@ -4472,6 +4712,16 @@ var TypeAnalyzer = class {
|
|
|
4472
4712
|
return;
|
|
4473
4713
|
}
|
|
4474
4714
|
case "AssignmentStatement": {
|
|
4715
|
+
stmt.targets.forEach((target, i) => {
|
|
4716
|
+
const value = stmt.values[i];
|
|
4717
|
+
if (!value) return;
|
|
4718
|
+
if (target.type === "MemberExpression" || target.type === "IndexExpression") {
|
|
4719
|
+
this.applyContext(value, this.infer(target, env));
|
|
4720
|
+
} else if (target.type === "Identifier") {
|
|
4721
|
+
const id = this.bindingIdOf(target);
|
|
4722
|
+
if (id !== void 0 && this.annotated.has(id)) this.applyContext(value, this.bindingType.get(id));
|
|
4723
|
+
}
|
|
4724
|
+
});
|
|
4475
4725
|
const { types: valueTypes, sources } = this.valueList(stmt.values, env);
|
|
4476
4726
|
stmt.targets.forEach((target, i) => {
|
|
4477
4727
|
const vt = valueTypes[i] ?? unknownType;
|
|
@@ -4616,6 +4866,9 @@ var TypeAnalyzer = class {
|
|
|
4616
4866
|
case "BreakStatement":
|
|
4617
4867
|
this.breakStates[this.breakStates.length - 1]?.push(forkEnv(env));
|
|
4618
4868
|
return;
|
|
4869
|
+
case "DeclareClassStatement":
|
|
4870
|
+
this.checkClass(stmt);
|
|
4871
|
+
return;
|
|
4619
4872
|
case "ContinueStatement":
|
|
4620
4873
|
case "TypeAliasStatement":
|
|
4621
4874
|
case "ExportTypeAliasStatement":
|
|
@@ -4737,7 +4990,46 @@ var TypeAnalyzer = class {
|
|
|
4737
4990
|
}
|
|
4738
4991
|
if (p.pattern) return this.patternToType(p.pattern, env);
|
|
4739
4992
|
if (p.default) return widen(this.infer(p.default, env));
|
|
4740
|
-
return anyType;
|
|
4993
|
+
return this.contextualParams.get(p) ?? anyType;
|
|
4994
|
+
}
|
|
4995
|
+
/** What a function expression's unannotated parameters are, from where
|
|
4996
|
+
* it is written — see `applyContext`. */
|
|
4997
|
+
contextualParams = /* @__PURE__ */ new WeakMap();
|
|
4998
|
+
/** `expected` is the type the surroundings want for `expr`. A function
|
|
4999
|
+
* expression written there takes its unannotated parameters' types from
|
|
5000
|
+
* it, as in TypeScript: `signal:Connect(function(player) ... end)` knows
|
|
5001
|
+
* `player` from `Connect`'s callback type. Anything else is inferred as
|
|
5002
|
+
* usual. */
|
|
5003
|
+
applyContext(expr, expected) {
|
|
5004
|
+
let e = expr;
|
|
5005
|
+
while (e.type === "ParenthesizedExpression") e = e.expression;
|
|
5006
|
+
if (e.type !== "FunctionExpression" || !expected) return;
|
|
5007
|
+
const members = expected.kind === "union" ? expected.types : [expected];
|
|
5008
|
+
const signatures = members.flatMap((m) => this.overloadsOf(this.expand(m)));
|
|
5009
|
+
if (!signatures.length) return;
|
|
5010
|
+
e.func.params.forEach((p, k) => {
|
|
5011
|
+
if (p.typeAnnotation || p.pattern || p.default) return;
|
|
5012
|
+
const candidates = [];
|
|
5013
|
+
for (const signature of signatures) {
|
|
5014
|
+
const t2 = signature.params[k]?.type ?? signature.varargs;
|
|
5015
|
+
if (t2) candidates.push(t2);
|
|
5016
|
+
}
|
|
5017
|
+
if (!candidates.length) return;
|
|
5018
|
+
const t = union(candidates);
|
|
5019
|
+
this.contextualParams.set(p, containsTypeParam(t) ? anyType : t);
|
|
5020
|
+
});
|
|
5021
|
+
}
|
|
5022
|
+
/** The parameter type each written argument lands on, across `fns`. */
|
|
5023
|
+
expectedArguments(written, fns, selfOf) {
|
|
5024
|
+
return written.map((_, j) => {
|
|
5025
|
+
const candidates = [];
|
|
5026
|
+
for (const f of fns) {
|
|
5027
|
+
const i = j + selfOf(f);
|
|
5028
|
+
const param = i < f.params.length ? this.boundParams(f)[i] : f.varargs;
|
|
5029
|
+
if (param) candidates.push(param);
|
|
5030
|
+
}
|
|
5031
|
+
return candidates.length ? union(candidates) : void 0;
|
|
5032
|
+
});
|
|
4741
5033
|
}
|
|
4742
5034
|
/** Synthesize a type from a destructuring pattern used without an
|
|
4743
5035
|
* annotation (`function f({ a, b = 1 })`). */
|
|
@@ -4789,7 +5081,8 @@ var TypeAnalyzer = class {
|
|
|
4789
5081
|
f.params.forEach((p, i) => {
|
|
4790
5082
|
const arg = argTypes[i];
|
|
4791
5083
|
if (arg === void 0) return;
|
|
4792
|
-
|
|
5084
|
+
const param = p.type.kind === "typeParam" && p.type.constraint ? { ...p.type, constraint: this.reduceType(p.type.constraint) } : p.type;
|
|
5085
|
+
unify(p.type, keepsLiterals(param) ? arg : widen(arg), vars, subst);
|
|
4793
5086
|
});
|
|
4794
5087
|
for (const name of f.typeParams ?? []) if (!subst.has(name)) subst.set(name, unknownType);
|
|
4795
5088
|
return subst;
|
|
@@ -4850,12 +5143,13 @@ var TypeAnalyzer = class {
|
|
|
4850
5143
|
return;
|
|
4851
5144
|
}
|
|
4852
5145
|
const t = value;
|
|
5146
|
+
if (t.kind === "object" && t.class) return;
|
|
4853
5147
|
if (t.kind === "typeParam" && typeof t.name === "string" && bounds.has(t.name) && t.constraint && !containsTypeParam(t.constraint)) {
|
|
4854
5148
|
bounds.set(t.name, this.reduceType(t.constraint));
|
|
4855
5149
|
}
|
|
4856
5150
|
for (const child of Object.values(value)) walk(child);
|
|
4857
5151
|
};
|
|
4858
|
-
for (const p of f.params) walk(p.type);
|
|
5152
|
+
for (const p of f.params) if (containsTypeParam(p.type)) walk(p.type);
|
|
4859
5153
|
return f.params.map((p) => substitute(p.type, bounds));
|
|
4860
5154
|
}
|
|
4861
5155
|
/** Record what each written argument is expected to be — see
|
|
@@ -5194,7 +5488,7 @@ var TypeAnalyzer = class {
|
|
|
5194
5488
|
this.expandCache.set(key, t);
|
|
5195
5489
|
this.resolvingAliases.add(t.name);
|
|
5196
5490
|
try {
|
|
5197
|
-
const r = def.params.length ? this.instantiateAlias(def, t.typeArguments) : this.
|
|
5491
|
+
const r = def.params.length ? this.instantiateAlias(def, t.typeArguments) : this.resolveDef(def);
|
|
5198
5492
|
const named = def.params.length === 0 && (r.kind === "object" || r.kind === "intersection") && !r.name ? { ...r, name: t.name } : r;
|
|
5199
5493
|
this.expandCache.set(key, named);
|
|
5200
5494
|
return named;
|
|
@@ -5317,9 +5611,9 @@ var TypeAnalyzer = class {
|
|
|
5317
5611
|
case "not":
|
|
5318
5612
|
return booleanType;
|
|
5319
5613
|
case "-":
|
|
5320
|
-
return numberType;
|
|
5614
|
+
return this.operatorResult(expr, "-", arg, void 0) ?? numberType;
|
|
5321
5615
|
case "#":
|
|
5322
|
-
return numberType;
|
|
5616
|
+
return this.operatorResult(expr, "#", arg, void 0) ?? numberType;
|
|
5323
5617
|
}
|
|
5324
5618
|
return arg;
|
|
5325
5619
|
}
|
|
@@ -5341,7 +5635,7 @@ var TypeAnalyzer = class {
|
|
|
5341
5635
|
const r = this.infer(expr.right, env);
|
|
5342
5636
|
switch (op) {
|
|
5343
5637
|
case "..":
|
|
5344
|
-
return stringType;
|
|
5638
|
+
return this.operatorResult(expr, op, l, r) ?? stringType;
|
|
5345
5639
|
case "==":
|
|
5346
5640
|
case "~=":
|
|
5347
5641
|
case "<":
|
|
@@ -5356,7 +5650,7 @@ var TypeAnalyzer = class {
|
|
|
5356
5650
|
case "//":
|
|
5357
5651
|
case "%":
|
|
5358
5652
|
case "^":
|
|
5359
|
-
return numberType;
|
|
5653
|
+
return this.operatorResult(expr, op, l, r) ?? numberType;
|
|
5360
5654
|
}
|
|
5361
5655
|
return union([l, r]);
|
|
5362
5656
|
}
|
|
@@ -5375,8 +5669,10 @@ var TypeAnalyzer = class {
|
|
|
5375
5669
|
}
|
|
5376
5670
|
case "CallExpression": {
|
|
5377
5671
|
const callee = this.infer(expr.callee, env);
|
|
5378
|
-
const argTypes = expr.arguments.map((a) => this.infer(a, env));
|
|
5379
5672
|
const fns = this.overloadsOf(callee);
|
|
5673
|
+
const expected = this.expectedArguments(expr.arguments, fns, () => 0);
|
|
5674
|
+
expr.arguments.forEach((a, i) => this.applyContext(a, expected[i]));
|
|
5675
|
+
const argTypes = expr.arguments.map((a) => this.infer(a, env));
|
|
5380
5676
|
if (fns.length) {
|
|
5381
5677
|
this.recordExpected(expr.arguments, fns, () => 0);
|
|
5382
5678
|
const arityFits = this.checkArity(expr, fns, argTypes.length, 0);
|
|
@@ -5391,8 +5687,10 @@ var TypeAnalyzer = class {
|
|
|
5391
5687
|
}
|
|
5392
5688
|
case "MethodCallExpression": {
|
|
5393
5689
|
const objType = this.infer(expr.object, env);
|
|
5394
|
-
const argTypes = expr.arguments.map((a) => this.infer(a, env));
|
|
5395
5690
|
const fns = this.overloadsOf(this.propertyType(objType, expr.method.name));
|
|
5691
|
+
const expected = this.expectedArguments(expr.arguments, fns, (f) => this.takesSelf(f) ? 1 : 0);
|
|
5692
|
+
expr.arguments.forEach((a, i) => this.applyContext(a, expected[i]));
|
|
5693
|
+
const argTypes = expr.arguments.map((a) => this.infer(a, env));
|
|
5396
5694
|
if (fns.length) {
|
|
5397
5695
|
const withSelf = (f) => this.takesSelf(f) ? [objType, ...argTypes] : argTypes;
|
|
5398
5696
|
const selfOf = (f) => this.takesSelf(f) ? 1 : 0;
|
|
@@ -5784,6 +6082,35 @@ var TypeAnalyzer = class {
|
|
|
5784
6082
|
this.selfType = saved;
|
|
5785
6083
|
}
|
|
5786
6084
|
}
|
|
6085
|
+
/** What an operator on a value with metamethods gives: `a + b` calls
|
|
6086
|
+
* `__add` on `a`, or failing that on `b` with the operands swapped — the
|
|
6087
|
+
* order Luau tries them in. That is how `Vector3 + Vector3`, `CFrame *
|
|
6088
|
+
* Vector3` and `2 * vector` get their types from the declarations.
|
|
6089
|
+
* `undefined` when neither operand declares the metamethod; an operand
|
|
6090
|
+
* that declares it but accepts neither argument is reported. */
|
|
6091
|
+
operatorResult(node, op, left, right) {
|
|
6092
|
+
const name = right === void 0 ? op === "-" ? "__unm" : "__len" : METAMETHODS[op];
|
|
6093
|
+
if (!name) return void 0;
|
|
6094
|
+
const candidates = right === void 0 ? [[left, void 0]] : [[left, right], [right, left]];
|
|
6095
|
+
let declared;
|
|
6096
|
+
for (const [receiver, other] of candidates) {
|
|
6097
|
+
const t = this.expand(receiver);
|
|
6098
|
+
const method = t.kind === "object" ? t.properties.get(name) : void 0;
|
|
6099
|
+
if (!method) continue;
|
|
6100
|
+
declared ??= receiver;
|
|
6101
|
+
const args = other === void 0 ? [receiver] : [receiver, other];
|
|
6102
|
+
const picked = this.pickOverload(this.overloadsOf(method.type), args);
|
|
6103
|
+
if (picked) return this.callReturn(picked, args);
|
|
6104
|
+
}
|
|
6105
|
+
if (declared && this.emitDiagnostics) {
|
|
6106
|
+
this.diagnostics.push({
|
|
6107
|
+
node,
|
|
6108
|
+
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)}'`
|
|
6109
|
+
});
|
|
6110
|
+
return anyType;
|
|
6111
|
+
}
|
|
6112
|
+
return void 0;
|
|
6113
|
+
}
|
|
5787
6114
|
/** Does this signature take the receiver as its first parameter?
|
|
5788
6115
|
*
|
|
5789
6116
|
* Luau's `:` is sugar both ways: `function T:m(a)` declares
|
|
@@ -6157,7 +6484,6 @@ function sourceMapTypes(text, path, options) {
|
|
|
6157
6484
|
const lines = [];
|
|
6158
6485
|
const aliasOfFile = /* @__PURE__ */ new Map();
|
|
6159
6486
|
const used = /* @__PURE__ */ new Set();
|
|
6160
|
-
const canOmit = options.classes.has("Omit");
|
|
6161
6487
|
const aliasOfNode = /* @__PURE__ */ new Map();
|
|
6162
6488
|
const aliasFor = (segments) => {
|
|
6163
6489
|
const base = `SourceMap_${segments.map((s) => s.replace(/[^A-Za-z0-9_]/g, "_")).join("_")}`;
|
|
@@ -6173,7 +6499,7 @@ function sourceMapTypes(text, path, options) {
|
|
|
6173
6499
|
const className = IDENTIFIER.test(node.className) && options.classes.has(node.className) ? node.className : "Instance";
|
|
6174
6500
|
const taken = options.membersOf?.(className) ?? INSTANCE_MEMBERS;
|
|
6175
6501
|
const members = [];
|
|
6176
|
-
if (parent
|
|
6502
|
+
if (parent) members.push(`Parent: ${parent}`);
|
|
6177
6503
|
const named = /* @__PURE__ */ new Set();
|
|
6178
6504
|
for (const child of node.children ?? []) {
|
|
6179
6505
|
if (!isNode(child)) continue;
|
|
@@ -6182,8 +6508,7 @@ function sourceMapTypes(text, path, options) {
|
|
|
6182
6508
|
named.add(child.name);
|
|
6183
6509
|
members.push(`${child.name}: ${childAlias}`);
|
|
6184
6510
|
}
|
|
6185
|
-
|
|
6186
|
-
lines.push(`type ${alias} = ${members.length ? `${base} & { ${members.join(", ")} }` : base}`);
|
|
6511
|
+
lines.push(`declare class ${alias} extends ${className} { ${members.join(", ")} }`);
|
|
6187
6512
|
return alias;
|
|
6188
6513
|
};
|
|
6189
6514
|
const rootAlias = visit(root, [root.name], void 0);
|
|
@@ -6261,6 +6586,7 @@ export {
|
|
|
6261
6586
|
getBinding,
|
|
6262
6587
|
intersection,
|
|
6263
6588
|
isAssignable,
|
|
6589
|
+
isClassType,
|
|
6264
6590
|
isGlobal,
|
|
6265
6591
|
isPossiblyFalsy,
|
|
6266
6592
|
isPossiblyTruthy,
|