@tilua/parser 2.0.0 → 2.2.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 +16 -3
- package/dist/index.cjs +363 -61
- package/dist/index.d.cts +19 -2
- package/dist/index.d.ts +19 -2
- package/dist/index.js +363 -61
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -2503,6 +2503,21 @@ var Parser = class {
|
|
|
2503
2503
|
base = { type: "MemberExpression", object: base, property: prop, optional: true, ...spanFrom(base, prop) };
|
|
2504
2504
|
continue;
|
|
2505
2505
|
}
|
|
2506
|
+
if (punct === "." && this.punctuatorAt(2, "[")) {
|
|
2507
|
+
this.advance();
|
|
2508
|
+
this.advance();
|
|
2509
|
+
this.expectPunctuator("[");
|
|
2510
|
+
const index = this.inBrackets(() => this.parseExpression());
|
|
2511
|
+
this.expectPunctuator("]");
|
|
2512
|
+
base = {
|
|
2513
|
+
type: "IndexExpression",
|
|
2514
|
+
object: base,
|
|
2515
|
+
index,
|
|
2516
|
+
optional: true,
|
|
2517
|
+
...spanFrom(base, this.previous())
|
|
2518
|
+
};
|
|
2519
|
+
continue;
|
|
2520
|
+
}
|
|
2506
2521
|
if (punct === "." && this.punctuatorAt(2, "(")) {
|
|
2507
2522
|
this.advance();
|
|
2508
2523
|
this.advance();
|
|
@@ -3256,8 +3271,12 @@ var Parser = class {
|
|
|
3256
3271
|
* `name?: T` for an optional property or parameter. */
|
|
3257
3272
|
parseSuffixType() {
|
|
3258
3273
|
if (this.checkIdentifierValue("readonly")) {
|
|
3274
|
+
const start = this.current();
|
|
3259
3275
|
this.advance();
|
|
3260
|
-
|
|
3276
|
+
const t2 = this.parseSuffixType();
|
|
3277
|
+
if (t2.type === "ArrayTypeNode" || t2.type === "TupleTypeNode") return { ...t2, isReadonly: true };
|
|
3278
|
+
this.problemAt(start, "'readonly' belongs on an array or a tuple: a property is marked where it is written");
|
|
3279
|
+
return t2;
|
|
3261
3280
|
}
|
|
3262
3281
|
let t = this.parsePrimaryType();
|
|
3263
3282
|
while (true) {
|
|
@@ -3808,31 +3827,34 @@ var Analyzer = class {
|
|
|
3808
3827
|
* can call it, but a call straight in the block before the declaration
|
|
3809
3828
|
* finds nothing there yet. At a module's top level the whole function is
|
|
3810
3829
|
* hoisted, and this does not apply. */
|
|
3811
|
-
checkUseBeforeDefine(identifier, id) {
|
|
3830
|
+
checkUseBeforeDefine(identifier, id, at = this.functionDepth) {
|
|
3812
3831
|
const binding = this.bindings.get(id);
|
|
3813
|
-
if (
|
|
3832
|
+
if (this.typeQueryDepth > 0) return;
|
|
3814
3833
|
const declaration = binding.declarationNode;
|
|
3815
|
-
|
|
3816
|
-
|
|
3834
|
+
if (!declaration) return;
|
|
3835
|
+
const hoisted = binding.declaredBy === "function" || binding.declaredBy === "class";
|
|
3836
|
+
const depth = hoisted ? this.hoisted.get(declaration) : this.declaredDepth.get(id);
|
|
3837
|
+
const readable = hoisted || binding.kind === "local";
|
|
3838
|
+
if (!readable || depth === void 0 || depth !== at) return;
|
|
3817
3839
|
const before = identifier.line.start < declaration.line.start || identifier.line.start === declaration.line.start && identifier.column.start < declaration.column.start;
|
|
3818
3840
|
if (!before) return;
|
|
3819
3841
|
this.diagnostics.push({
|
|
3820
3842
|
node: identifier,
|
|
3821
|
-
message: `'${binding.name}' is used before its definition: inside a function, a function declared further down is only there once its declaration has run`,
|
|
3843
|
+
message: hoisted ? `'${binding.name}' is used before its definition: inside a function, a function declared further down is only there once its declaration has run` : `'${binding.name}' is used before its declaration, and holds nothing until that line has run`,
|
|
3822
3844
|
kind: "use-before-define"
|
|
3823
3845
|
});
|
|
3824
3846
|
}
|
|
3825
3847
|
noteDeferred(identifier, scope, id, assignment) {
|
|
3826
3848
|
if (this.functionDepth === 0 && this.typeQueryDepth === 0) return;
|
|
3827
3849
|
if (this.bindings.get(id).kind !== "global") return;
|
|
3828
|
-
this.deferredGlobals.push({ node: identifier, scope, assignment });
|
|
3850
|
+
this.deferredGlobals.push({ node: identifier, scope, assignment, depth: this.functionDepth });
|
|
3829
3851
|
}
|
|
3830
3852
|
/** Point each deferred read of a global at the declaration of that name
|
|
3831
3853
|
* that turned up later — in the module, or in any block around the code
|
|
3832
3854
|
* that reads it. Such code runs after the declaration has: a closure
|
|
3833
3855
|
* written inside a value reads the name the value is bound to. */
|
|
3834
3856
|
resolveForwardReferences() {
|
|
3835
|
-
for (const { node, scope, assignment } of this.deferredGlobals) {
|
|
3857
|
+
for (const { node, scope, assignment, depth } of this.deferredGlobals) {
|
|
3836
3858
|
const localId = this.lookup(scope, node.name);
|
|
3837
3859
|
const globalId = this.bindingOf.get(node);
|
|
3838
3860
|
if (localId === void 0 || globalId === void 0 || localId === globalId) continue;
|
|
@@ -3847,7 +3869,10 @@ var Analyzer = class {
|
|
|
3847
3869
|
this.bindingOf.set(node, localId);
|
|
3848
3870
|
this.bindings.get(localId).references.push(node);
|
|
3849
3871
|
if (assignment) this.checkConstAssign(localId, node);
|
|
3850
|
-
else if (this.typeQueryDepth === 0)
|
|
3872
|
+
else if (this.typeQueryDepth === 0) {
|
|
3873
|
+
this.checkTypeOnly(localId, node);
|
|
3874
|
+
this.checkUseBeforeDefine(node, localId, depth);
|
|
3875
|
+
}
|
|
3851
3876
|
}
|
|
3852
3877
|
}
|
|
3853
3878
|
/** Every read of a global nothing declares. A global assigned somewhere
|
|
@@ -3878,9 +3903,13 @@ var Analyzer = class {
|
|
|
3878
3903
|
}
|
|
3879
3904
|
const id = this.nextId++;
|
|
3880
3905
|
this.bindings.set(id, { id, name, kind, declarationNode: node, references: [], isConst, declaredBy });
|
|
3906
|
+
this.declaredDepth.set(id, this.functionDepth);
|
|
3881
3907
|
scope.declarations.set(name, id);
|
|
3882
3908
|
return id;
|
|
3883
3909
|
}
|
|
3910
|
+
/** Which function body each name was declared in. A read of a `const` from
|
|
3911
|
+
* the same body, but above it, is a read of nothing. */
|
|
3912
|
+
declaredDepth = /* @__PURE__ */ new Map();
|
|
3884
3913
|
resolve(scope, name) {
|
|
3885
3914
|
for (let s = scope; s; s = s.parent) {
|
|
3886
3915
|
const id = s.declarations.get(name);
|
|
@@ -4533,9 +4562,9 @@ function substitute(t, subst) {
|
|
|
4533
4562
|
case "typeParam":
|
|
4534
4563
|
return subst.get(t.name) ?? t;
|
|
4535
4564
|
case "array":
|
|
4536
|
-
return arrayOf(substitute(t.element, subst));
|
|
4565
|
+
return { ...arrayOf(substitute(t.element, subst)), readonly: t.readonly };
|
|
4537
4566
|
case "tuple":
|
|
4538
|
-
return tuple(t.elements.map((e) => substitute(e, subst)), t.rest && substitute(t.rest, subst));
|
|
4567
|
+
return { ...tuple(t.elements.map((e) => substitute(e, subst)), t.rest && substitute(t.rest, subst)), readonly: t.readonly };
|
|
4539
4568
|
case "object": {
|
|
4540
4569
|
const entries = [];
|
|
4541
4570
|
for (const [k, v] of t.properties) entries.push([k, { ...v, type: substitute(v.type, subst) }]);
|
|
@@ -4641,14 +4670,17 @@ function classArguments(t, name) {
|
|
|
4641
4670
|
if (t.kind === "object") return t.class?.typeArguments?.get(name);
|
|
4642
4671
|
return void 0;
|
|
4643
4672
|
}
|
|
4644
|
-
function unify(param, arg, vars, out) {
|
|
4673
|
+
function unify(param, arg, vars, out, how = "union") {
|
|
4645
4674
|
if (param.kind === "typeParam" && param.constraint && !vars.has(param.name)) {
|
|
4646
|
-
unify(param.constraint, arg, vars, out);
|
|
4675
|
+
unify(param.constraint, arg, vars, out, how);
|
|
4647
4676
|
return;
|
|
4648
4677
|
}
|
|
4649
4678
|
if (param.kind === "typeParam" && vars.has(param.name)) {
|
|
4650
4679
|
const prev = out.get(param.name);
|
|
4651
|
-
out.set(param.name,
|
|
4680
|
+
if (!prev) out.set(param.name, arg);
|
|
4681
|
+
else if (isAssignable(arg, prev)) {
|
|
4682
|
+
} else if (isAssignable(prev, arg)) out.set(param.name, arg);
|
|
4683
|
+
else if (how === "union") out.set(param.name, union([prev, arg]));
|
|
4652
4684
|
return;
|
|
4653
4685
|
}
|
|
4654
4686
|
if (arg.kind === "any" || arg.kind === "never") return;
|
|
@@ -4700,6 +4732,20 @@ function unify(param, arg, vars, out) {
|
|
|
4700
4732
|
return;
|
|
4701
4733
|
}
|
|
4702
4734
|
}
|
|
4735
|
+
function memberKey(t) {
|
|
4736
|
+
switch (t.kind) {
|
|
4737
|
+
case "literal":
|
|
4738
|
+
return `l${t.base}:${String(t.value)}`;
|
|
4739
|
+
case "primitive":
|
|
4740
|
+
return `p${t.name}`;
|
|
4741
|
+
case "any":
|
|
4742
|
+
case "unknown":
|
|
4743
|
+
case "never":
|
|
4744
|
+
return t.kind;
|
|
4745
|
+
default:
|
|
4746
|
+
return formatType(t);
|
|
4747
|
+
}
|
|
4748
|
+
}
|
|
4703
4749
|
function union(types) {
|
|
4704
4750
|
const flat = [];
|
|
4705
4751
|
for (const t of types) {
|
|
@@ -4711,7 +4757,7 @@ function union(types) {
|
|
|
4711
4757
|
const seen = /* @__PURE__ */ new Map();
|
|
4712
4758
|
for (const t of flat) {
|
|
4713
4759
|
if (t.kind === "never") continue;
|
|
4714
|
-
const key =
|
|
4760
|
+
const key = memberKey(t);
|
|
4715
4761
|
if (!seen.has(key)) seen.set(key, t);
|
|
4716
4762
|
}
|
|
4717
4763
|
let members = reduceSubtypes([...seen.values()]);
|
|
@@ -4740,7 +4786,7 @@ function intersection(types) {
|
|
|
4740
4786
|
const seen = /* @__PURE__ */ new Map();
|
|
4741
4787
|
for (const t of flat) {
|
|
4742
4788
|
if (t.kind === "unknown") continue;
|
|
4743
|
-
seen.set(
|
|
4789
|
+
seen.set(memberKey(t), t);
|
|
4744
4790
|
}
|
|
4745
4791
|
const members = [...seen.values()];
|
|
4746
4792
|
if (members.length === 0) return unknownType;
|
|
@@ -4767,9 +4813,9 @@ function widen(t) {
|
|
|
4767
4813
|
case "literal":
|
|
4768
4814
|
return primitive(t.base);
|
|
4769
4815
|
case "array":
|
|
4770
|
-
return arrayOf(widen(t.element));
|
|
4816
|
+
return { ...arrayOf(widen(t.element)), readonly: t.readonly };
|
|
4771
4817
|
case "tuple":
|
|
4772
|
-
return tuple(t.elements.map(widen), t.rest && widen(t.rest));
|
|
4818
|
+
return { ...tuple(t.elements.map(widen), t.rest && widen(t.rest)), readonly: t.readonly };
|
|
4773
4819
|
case "object": {
|
|
4774
4820
|
if (t.frozen || t.class) return t;
|
|
4775
4821
|
const entries = [];
|
|
@@ -4867,6 +4913,7 @@ function isAssignableInner(a, b) {
|
|
|
4867
4913
|
return b.kind === "primitive" && b.name === "string";
|
|
4868
4914
|
}
|
|
4869
4915
|
if (a.kind === "primitive") return b.kind === "primitive" && b.name === a.name;
|
|
4916
|
+
if ((a.kind === "array" || a.kind === "tuple") && a.readonly && (b.kind === "array" || b.kind === "tuple") && !b.readonly) return false;
|
|
4870
4917
|
if (a.kind === "array") {
|
|
4871
4918
|
if (b.kind === "array") return isAssignable(a.element, b.element);
|
|
4872
4919
|
if (b.kind === "tuple") return b.elements.length === 0 && !!b.rest && isAssignable(a.element, b.rest);
|
|
@@ -5212,10 +5259,10 @@ function formatTypeUncached(t) {
|
|
|
5212
5259
|
case "literal":
|
|
5213
5260
|
return t.base === "string" ? JSON.stringify(t.value) : String(t.value);
|
|
5214
5261
|
case "array":
|
|
5215
|
-
return `${formatAtom(t.element)}[]`;
|
|
5262
|
+
return `${t.readonly ? "readonly " : ""}${formatAtom(t.element)}[]`;
|
|
5216
5263
|
case "tuple": {
|
|
5217
5264
|
const inner = [...t.elements.map(formatType), ...t.rest ? [`...${formatAtom(t.rest)}[]`] : []];
|
|
5218
|
-
return
|
|
5265
|
+
return `${t.readonly ? "readonly " : ""}[${inner.join(", ")}]`;
|
|
5219
5266
|
}
|
|
5220
5267
|
case "object": {
|
|
5221
5268
|
if (t.name) {
|
|
@@ -5738,7 +5785,7 @@ function expressionLabel(e, depth = 0) {
|
|
|
5738
5785
|
case "IndexExpression": {
|
|
5739
5786
|
const o = expressionLabel(e.object, depth + 1);
|
|
5740
5787
|
const i = e.index.type === "StringLiteral" ? JSON.stringify(e.index.value) : e.index.type === "NumberLiteral" ? e.index.raw : e.index.type === "Identifier" ? e.index.name : "...";
|
|
5741
|
-
return o === void 0 ? void 0 : `${o}[${i}]`;
|
|
5788
|
+
return o === void 0 ? void 0 : `${o}${e.optional ? "?." : ""}[${i}]`;
|
|
5742
5789
|
}
|
|
5743
5790
|
case "ParenthesizedExpression": {
|
|
5744
5791
|
const inner = expressionLabel(e.expression, depth + 1);
|
|
@@ -5789,7 +5836,8 @@ var TypeAnalyzer = class {
|
|
|
5789
5836
|
/** Put on every ref to one of `aliasDefs`, so a module that imports the
|
|
5790
5837
|
* type expands the ref here rather than by its name there. */
|
|
5791
5838
|
origin = { expand: (t) => this.expand(t) };
|
|
5792
|
-
/** See `resolveClass`.
|
|
5839
|
+
/** See `resolveClass`. Shared with other analyses over the same libraries
|
|
5840
|
+
* when none of their names is redefined here; see `shareableClasses`. */
|
|
5793
5841
|
classTypes = /* @__PURE__ */ new WeakMap();
|
|
5794
5842
|
/** See `instanceType` — one instance type per `class ... end`. */
|
|
5795
5843
|
instanceTypes = /* @__PURE__ */ new WeakMap();
|
|
@@ -5835,6 +5883,11 @@ var TypeAnalyzer = class {
|
|
|
5835
5883
|
/** Recursion guard for `preVisitBody`. */
|
|
5836
5884
|
preVisitDepth = 0;
|
|
5837
5885
|
run() {
|
|
5886
|
+
const shared = shareableClasses(this.options.libs ?? [], this.program);
|
|
5887
|
+
if (shared) {
|
|
5888
|
+
this.classTypes = shared.classTypes;
|
|
5889
|
+
this.classMembers = shared.classMembers;
|
|
5890
|
+
}
|
|
5838
5891
|
this.registerAliasDefs(preludeProgram().body, true);
|
|
5839
5892
|
for (const lib of this.options.libs ?? []) this.registerAliasDefs(lib.body, true);
|
|
5840
5893
|
this.registerAliasDefs(this.program.body);
|
|
@@ -7000,14 +7053,17 @@ var TypeAnalyzer = class {
|
|
|
7000
7053
|
return literal(node.value);
|
|
7001
7054
|
case "TypeLiteralNumber":
|
|
7002
7055
|
return literal(node.value);
|
|
7003
|
-
case "ArrayTypeNode":
|
|
7004
|
-
|
|
7056
|
+
case "ArrayTypeNode": {
|
|
7057
|
+
const made = arrayOf(this.resolveType(node.element));
|
|
7058
|
+
return node.isReadonly ? { ...made, readonly: true } : made;
|
|
7059
|
+
}
|
|
7005
7060
|
case "TupleTypeNode": {
|
|
7006
7061
|
const rest = node.rest && this.expand(this.resolveType(node.rest));
|
|
7007
|
-
|
|
7062
|
+
const made = tuple(
|
|
7008
7063
|
node.elements.map((e) => this.resolveType(e)),
|
|
7009
7064
|
rest ? rest.kind === "array" ? rest.element : rest.kind === "any" ? anyType : unknownType : void 0
|
|
7010
7065
|
);
|
|
7066
|
+
return node.isReadonly && made.kind === "tuple" ? { ...made, readonly: true } : made;
|
|
7011
7067
|
}
|
|
7012
7068
|
case "UnionTypeNode":
|
|
7013
7069
|
return union(node.types.map((t) => this.resolveType(t)));
|
|
@@ -7809,8 +7865,10 @@ var TypeAnalyzer = class {
|
|
|
7809
7865
|
// --------------------------------------------------------
|
|
7810
7866
|
// Functions
|
|
7811
7867
|
// --------------------------------------------------------
|
|
7812
|
-
|
|
7813
|
-
|
|
7868
|
+
/** `returns`, when given, collects the type of each `return` the walk
|
|
7869
|
+
* reads — see `inferFunctionBody`. */
|
|
7870
|
+
visitFunctionBody(func, outerEnv, returns) {
|
|
7871
|
+
this.withTypeParams(func.generics, () => this.visitFunctionBodyInner(func, outerEnv, returns));
|
|
7814
7872
|
}
|
|
7815
7873
|
/** A parameter's type: annotation, else a shape synthesized from a
|
|
7816
7874
|
* destructuring pattern, else inferred from its default, else `any`. */
|
|
@@ -7827,8 +7885,19 @@ var TypeAnalyzer = class {
|
|
|
7827
7885
|
if (p.rest) return arrayOf(unknownType);
|
|
7828
7886
|
if (p.pattern) return this.patternToType(p.pattern, env);
|
|
7829
7887
|
if (p.default) return widen(this.infer(p.default, env));
|
|
7830
|
-
|
|
7888
|
+
const contextual = this.contextualParams.get(p);
|
|
7889
|
+
if (contextual) return contextual;
|
|
7890
|
+
if (this.emitDiagnostics && !receiver && p.name && p.line && !this.untypedParamReported.has(p)) {
|
|
7891
|
+
this.untypedParamReported.add(p);
|
|
7892
|
+
this.diagnostics.push({
|
|
7893
|
+
node: p,
|
|
7894
|
+
message: `Parameter '${p.name}' has no type, so it is 'any': give it one, or a default to read it from`
|
|
7895
|
+
});
|
|
7896
|
+
}
|
|
7897
|
+
return anyType;
|
|
7831
7898
|
}
|
|
7899
|
+
/** Parameters already spoken about: a body is visited more than once. */
|
|
7900
|
+
untypedParamReported = /* @__PURE__ */ new WeakSet();
|
|
7832
7901
|
/** What a function expression's unannotated parameters are, from where
|
|
7833
7902
|
* it is written — see `applyContext`. */
|
|
7834
7903
|
contextualParams = /* @__PURE__ */ new WeakMap();
|
|
@@ -7848,6 +7917,53 @@ var TypeAnalyzer = class {
|
|
|
7848
7917
|
* it, as in TypeScript: `signal:Connect(function(player) ... end)` knows
|
|
7849
7918
|
* `player` from `Connect`'s callback type. Anything else is inferred as
|
|
7850
7919
|
* usual. */
|
|
7920
|
+
/** The arguments of a call, in two passes where one is needed.
|
|
7921
|
+
*
|
|
7922
|
+
* A function written at the call site with a parameter nothing annotates
|
|
7923
|
+
* reads that parameter from the signature — and where the signature says
|
|
7924
|
+
* `(v: T) => U`, `T` is only known once the other arguments have been
|
|
7925
|
+
* read. So such an argument is read once quietly, to get a shape to pick
|
|
7926
|
+
* a signature with, and then again with `T` in hand: `map(xs, v => v + 1)`
|
|
7927
|
+
* has its `v` a number by then, and `U` follows from what the body
|
|
7928
|
+
* answers. Everything else is read once, as it always was. */
|
|
7929
|
+
inferArguments(written, fns, selfOf, extra, env) {
|
|
7930
|
+
const expected = this.expectedArguments(written, fns, selfOf);
|
|
7931
|
+
written.forEach((a, i) => this.applyContext(a, expected[i]));
|
|
7932
|
+
const waits = written.map((a, i) => this.waitsOnInference(a, expected[i]));
|
|
7933
|
+
if (!waits.some(Boolean)) return written.map((a) => this.infer(a, env));
|
|
7934
|
+
const argTypes = written.map((a, i) => waits[i] ? this.quietly(() => this.infer(a, env)) : this.infer(a, env));
|
|
7935
|
+
const picked = this.pickOverload(fns, argTypes, (f) => extra(f, argTypes), this.spreadOf(written, selfOf(fns[0])));
|
|
7936
|
+
const subst = picked?.typeParams?.length ? this.inferTypeArgs(picked, extra(picked, argTypes)) : void 0;
|
|
7937
|
+
written.forEach((a, i) => {
|
|
7938
|
+
if (!waits[i]) return;
|
|
7939
|
+
if (picked && subst) {
|
|
7940
|
+
const self = selfOf(picked);
|
|
7941
|
+
const declared = i + self < picked.params.length ? picked.params[i + self].type : picked.varargs;
|
|
7942
|
+
if (declared) this.applyContext(a, this.reduceType(substitute(declared, subst)));
|
|
7943
|
+
}
|
|
7944
|
+
argTypes[i] = this.infer(a, env);
|
|
7945
|
+
});
|
|
7946
|
+
return argTypes;
|
|
7947
|
+
}
|
|
7948
|
+
/** Is this argument a function whose own type waits on the call's? */
|
|
7949
|
+
waitsOnInference(arg, expected) {
|
|
7950
|
+
if (!expected || !containsTypeParam(expected)) return false;
|
|
7951
|
+
let e = arg;
|
|
7952
|
+
while (e.type === "ParenthesizedExpression") e = e.expression;
|
|
7953
|
+
if (e.type !== "FunctionExpression") return false;
|
|
7954
|
+
return e.func.params.some((p) => !p.typeAnnotation && !p.default && !p.pattern && p.name !== "self" && p.name !== "this");
|
|
7955
|
+
}
|
|
7956
|
+
/** Read something without saying anything about it: a first pass whose
|
|
7957
|
+
* only purpose is a shape to go on. */
|
|
7958
|
+
quietly(read) {
|
|
7959
|
+
const was = this.emitDiagnostics;
|
|
7960
|
+
this.emitDiagnostics = false;
|
|
7961
|
+
try {
|
|
7962
|
+
return read();
|
|
7963
|
+
} finally {
|
|
7964
|
+
this.emitDiagnostics = was;
|
|
7965
|
+
}
|
|
7966
|
+
}
|
|
7851
7967
|
applyContext(expr, expected) {
|
|
7852
7968
|
let e = expr;
|
|
7853
7969
|
while (e.type === "ParenthesizedExpression") e = e.expression;
|
|
@@ -7996,7 +8112,7 @@ var TypeAnalyzer = class {
|
|
|
7996
8112
|
this.contextualReturns.pop();
|
|
7997
8113
|
}
|
|
7998
8114
|
}
|
|
7999
|
-
visitFunctionBodyInner(func, outerEnv) {
|
|
8115
|
+
visitFunctionBodyInner(func, outerEnv, returns) {
|
|
8000
8116
|
const env = forkEnv(outerEnv);
|
|
8001
8117
|
for (const p of func.params) {
|
|
8002
8118
|
if (p.pattern) {
|
|
@@ -8013,7 +8129,7 @@ var TypeAnalyzer = class {
|
|
|
8013
8129
|
if (p.typeAnnotation) this.annotated.add(id);
|
|
8014
8130
|
}
|
|
8015
8131
|
}
|
|
8016
|
-
this.withVarargs(func, () => this.collectReturns(
|
|
8132
|
+
this.withVarargs(func, () => this.collectReturns(returns, () => {
|
|
8017
8133
|
this.visitBlock(func.body, env);
|
|
8018
8134
|
this.checkReturnsAtAll(func, this.declaredReturns[this.declaredReturns.length - 1]);
|
|
8019
8135
|
}));
|
|
@@ -8067,7 +8183,7 @@ var TypeAnalyzer = class {
|
|
|
8067
8183
|
if (arg === void 0) return;
|
|
8068
8184
|
const param = p.type.kind === "typeParam" && p.type.constraint ? { ...p.type, constraint: this.reduceType(p.type.constraint) } : p.type;
|
|
8069
8185
|
const preserve = keepsLiterals(param) || param.kind !== "typeParam" && containsTypeParam(param);
|
|
8070
|
-
unify(p.type, preserve ? arg : widen(arg), vars, subst);
|
|
8186
|
+
unify(p.type, preserve ? arg : widen(arg), vars, subst, "first");
|
|
8071
8187
|
});
|
|
8072
8188
|
if (f.varargs && f.restIsWhole) {
|
|
8073
8189
|
const rest = argTypes.slice(f.params.length).map((a) => a === void 0 ? unknownType : widen(a));
|
|
@@ -8123,6 +8239,11 @@ var TypeAnalyzer = class {
|
|
|
8123
8239
|
* this returns `undefined` and the ordinary pick stands. */
|
|
8124
8240
|
distributedReturn(fns, argTypes, picked, argsFor) {
|
|
8125
8241
|
if (fns.length < 2) return void 0;
|
|
8242
|
+
if (argTypes.some((t) => this.expand(t).kind === "any")) {
|
|
8243
|
+
const taken = fns.filter((f) => this.overloadAccepts(f, argsFor(f, argTypes)));
|
|
8244
|
+
if (taken.length < 2) return void 0;
|
|
8245
|
+
return union(taken.map((f) => this.callReturn(f, argsFor(f, argTypes))));
|
|
8246
|
+
}
|
|
8126
8247
|
const position = argTypes.findIndex((t) => this.expand(t).kind === "union");
|
|
8127
8248
|
if (position < 0) return void 0;
|
|
8128
8249
|
const members = this.expand(argTypes[position]).types;
|
|
@@ -8171,6 +8292,8 @@ var TypeAnalyzer = class {
|
|
|
8171
8292
|
* lone argument cannot be checked against without false errors. */
|
|
8172
8293
|
boundParams(f) {
|
|
8173
8294
|
if (!f.typeParams?.length) return f.params.map((p) => p.type);
|
|
8295
|
+
const known = this.boundParamsCache.get(f);
|
|
8296
|
+
if (known) return known;
|
|
8174
8297
|
const bounds = new Map(f.typeParams.map((name) => [name, anyType]));
|
|
8175
8298
|
const seen = /* @__PURE__ */ new WeakSet();
|
|
8176
8299
|
const walk = (value) => {
|
|
@@ -8188,8 +8311,11 @@ var TypeAnalyzer = class {
|
|
|
8188
8311
|
for (const child of Object.values(value)) walk(child);
|
|
8189
8312
|
};
|
|
8190
8313
|
for (const p of f.params) if (containsTypeParam(p.type)) walk(p.type);
|
|
8191
|
-
|
|
8314
|
+
const bound = f.params.map((p) => this.reduceType(substitute(p.type, bounds)));
|
|
8315
|
+
this.boundParamsCache.set(f, bound);
|
|
8316
|
+
return bound;
|
|
8192
8317
|
}
|
|
8318
|
+
boundParamsCache = /* @__PURE__ */ new WeakMap();
|
|
8193
8319
|
/** Record what each written argument is expected to be — see
|
|
8194
8320
|
* `TypeAnalysis.expectedTypeOf`. */
|
|
8195
8321
|
recordExpected(written, fns, selfOf, argsOf = () => []) {
|
|
@@ -8408,7 +8534,12 @@ var TypeAnalyzer = class {
|
|
|
8408
8534
|
asserts: node.asserts
|
|
8409
8535
|
};
|
|
8410
8536
|
}
|
|
8411
|
-
|
|
8537
|
+
/** `visited`, when given, is what the real walk of the body just
|
|
8538
|
+
* collected from its `return`s: a function expression is walked before
|
|
8539
|
+
* its type is asked for, so there is nothing left to guess, and walking
|
|
8540
|
+
* the body a second time only to read them made every callback nested in
|
|
8541
|
+
* another cost twice its parent's. */
|
|
8542
|
+
inferFunctionBody(func, env, visited) {
|
|
8412
8543
|
const names = func.generics.map((g) => g.name);
|
|
8413
8544
|
return this.withTypeParams(func.generics, () => {
|
|
8414
8545
|
const params = func.params.flatMap((p) => {
|
|
@@ -8437,6 +8568,8 @@ var TypeAnalyzer = class {
|
|
|
8437
8568
|
returns = this.resolveType(func.returnType);
|
|
8438
8569
|
} else if (func.predicate) {
|
|
8439
8570
|
returns = booleanType;
|
|
8571
|
+
} else if (visited) {
|
|
8572
|
+
returns = visited.length ? union([...visited]) : this.withVarargs(func, () => this.silently(() => this.inferReturnType(func.body, bodyEnv)));
|
|
8440
8573
|
} else {
|
|
8441
8574
|
const collected = [];
|
|
8442
8575
|
returns = this.withVarargs(func, () => this.silently(() => {
|
|
@@ -8653,7 +8786,7 @@ var TypeAnalyzer = class {
|
|
|
8653
8786
|
/** What one `for (const item in source)` hands over each time, and how it
|
|
8654
8787
|
* walks the source. `pairs`/`ipairs` are read from the table they are
|
|
8655
8788
|
* given, so a record's keys stay the literals they are. */
|
|
8656
|
-
iterationOf(source, sourceType) {
|
|
8789
|
+
iterationOf(source, sourceType, report = true) {
|
|
8657
8790
|
if (source.type === "CallExpression" && source.callee.type === "Identifier" && source.arguments[0]) {
|
|
8658
8791
|
const name = source.callee.name;
|
|
8659
8792
|
const src = this.expand(this.typeOf.get(source.arguments[0]) ?? unknownType);
|
|
@@ -8691,7 +8824,14 @@ var TypeAnalyzer = class {
|
|
|
8691
8824
|
if (t.kind === "object") {
|
|
8692
8825
|
return [t.indexer?.value ?? union([...t.properties.values()].map((p) => p.type)), values];
|
|
8693
8826
|
}
|
|
8694
|
-
if (t.kind
|
|
8827
|
+
if (t.kind === "union") {
|
|
8828
|
+
const each = t.types.map((member) => this.iterationOf(source, member, false));
|
|
8829
|
+
const [first] = each;
|
|
8830
|
+
if (first && each.every(([, form]) => form.walks === first[1].walks && form.viaIter === first[1].viaIter)) {
|
|
8831
|
+
return [union(each.map(([item]) => item)), first[1]];
|
|
8832
|
+
}
|
|
8833
|
+
}
|
|
8834
|
+
if (t.kind !== "never" && report && this.emitDiagnostics) {
|
|
8695
8835
|
this.diagnostics.push({
|
|
8696
8836
|
node: source,
|
|
8697
8837
|
message: `Cannot loop over '${formatType(sourceType)}': a loop walks an array, a table, an iterator function or an iteration such as 'pairs(t)'`
|
|
@@ -9060,7 +9200,14 @@ var TypeAnalyzer = class {
|
|
|
9060
9200
|
const names = keys.map(nameOf);
|
|
9061
9201
|
const others = keys.filter((_, i) => names[i] === void 0);
|
|
9062
9202
|
let message;
|
|
9063
|
-
|
|
9203
|
+
const listOnly = (t) => {
|
|
9204
|
+
const x = this.expand(t);
|
|
9205
|
+
if (x.kind === "union") return x.types.every(listOnly);
|
|
9206
|
+
return x.kind === "array" || x.kind === "tuple";
|
|
9207
|
+
};
|
|
9208
|
+
if (names.some((name) => name !== void 0) && listOnly(withoutNil(this.expand(object)))) {
|
|
9209
|
+
message = `Type '${briefType(index)}' cannot be used to index type '${shown}'`;
|
|
9210
|
+
} else if (others.length && this.cannotIndex(object, others)) {
|
|
9064
9211
|
message = `Type '${briefType(index)}' cannot be used to index type '${shown}'`;
|
|
9065
9212
|
} else if (!others.length && names.every((name) => this.memberMissing(object, name))) {
|
|
9066
9213
|
message = keys.length === 1 ? `Property '${names[0]}' does not exist on type '${shown}'` : `None of ${briefType(index)} is a property of type '${shown}'`;
|
|
@@ -9295,10 +9442,12 @@ var TypeAnalyzer = class {
|
|
|
9295
9442
|
return this.inferArray(expr, env, false);
|
|
9296
9443
|
case "TableExpression":
|
|
9297
9444
|
return this.inferObject(expr, env, false);
|
|
9298
|
-
case "FunctionExpression":
|
|
9445
|
+
case "FunctionExpression": {
|
|
9299
9446
|
this.checkParamOrder(expr.func.params, expr);
|
|
9300
|
-
|
|
9301
|
-
|
|
9447
|
+
const returned = [];
|
|
9448
|
+
this.visitFunctionBody(expr.func, env, returned);
|
|
9449
|
+
return this.inferFunctionBody(expr.func, env, returned);
|
|
9450
|
+
}
|
|
9302
9451
|
case "ParenthesizedExpression":
|
|
9303
9452
|
return this.infer(expr.expression, env);
|
|
9304
9453
|
case "TypeAssertionExpression": {
|
|
@@ -9332,10 +9481,18 @@ var TypeAnalyzer = class {
|
|
|
9332
9481
|
switch (expr.operator) {
|
|
9333
9482
|
case "not":
|
|
9334
9483
|
return booleanType;
|
|
9335
|
-
case "-":
|
|
9336
|
-
|
|
9337
|
-
|
|
9338
|
-
|
|
9484
|
+
case "-": {
|
|
9485
|
+
const answered = this.operatorResult(expr, "-", arg, void 0);
|
|
9486
|
+
if (answered) return answered;
|
|
9487
|
+
this.checkOperands(expr, "-", arg);
|
|
9488
|
+
return this.arithmeticOn([arg]);
|
|
9489
|
+
}
|
|
9490
|
+
case "#": {
|
|
9491
|
+
const answered = this.operatorResult(expr, "#", arg, void 0);
|
|
9492
|
+
if (answered) return answered;
|
|
9493
|
+
this.checkOperands(expr, "#", arg);
|
|
9494
|
+
return numberType;
|
|
9495
|
+
}
|
|
9339
9496
|
}
|
|
9340
9497
|
return arg;
|
|
9341
9498
|
}
|
|
@@ -9364,16 +9521,21 @@ var TypeAnalyzer = class {
|
|
|
9364
9521
|
if (unwrapParens(expr.left).type === "StringLiteral") this.expectedTypeOf.set(unwrapParens(expr.left), r);
|
|
9365
9522
|
}
|
|
9366
9523
|
switch (op) {
|
|
9367
|
-
case "..":
|
|
9368
|
-
|
|
9524
|
+
case "..": {
|
|
9525
|
+
const answered = this.operatorResult(expr, op, l, r);
|
|
9526
|
+
if (answered) return answered;
|
|
9527
|
+
this.checkOperands(expr, op, l, r);
|
|
9528
|
+
return stringType;
|
|
9529
|
+
}
|
|
9369
9530
|
case "<":
|
|
9370
9531
|
case ">":
|
|
9371
9532
|
case "<=":
|
|
9372
9533
|
case ">=":
|
|
9373
|
-
this.checkComparison(expr, op, l, r);
|
|
9534
|
+
if (!this.checkComparison(expr, op, l, r)) this.checkOperands(expr, op, l, r);
|
|
9374
9535
|
return booleanType;
|
|
9375
9536
|
case "==":
|
|
9376
9537
|
case "~=":
|
|
9538
|
+
this.checkOverlap(expr, op, l, r);
|
|
9377
9539
|
return booleanType;
|
|
9378
9540
|
case "+":
|
|
9379
9541
|
case "-":
|
|
@@ -9381,8 +9543,12 @@ var TypeAnalyzer = class {
|
|
|
9381
9543
|
case "/":
|
|
9382
9544
|
case "//":
|
|
9383
9545
|
case "%":
|
|
9384
|
-
case "^":
|
|
9385
|
-
|
|
9546
|
+
case "^": {
|
|
9547
|
+
const answered = this.operatorResult(expr, op, l, r);
|
|
9548
|
+
if (answered) return answered;
|
|
9549
|
+
this.checkOperands(expr, op, l, r);
|
|
9550
|
+
return this.arithmeticOn([l, r]);
|
|
9551
|
+
}
|
|
9386
9552
|
}
|
|
9387
9553
|
return union([l, r]);
|
|
9388
9554
|
}
|
|
@@ -9485,9 +9651,7 @@ var TypeAnalyzer = class {
|
|
|
9485
9651
|
const united = this.unionSignatures(callee);
|
|
9486
9652
|
const fns = united ?? this.overloadsOf(callee);
|
|
9487
9653
|
const explicit = this.explicitTypeArguments(expr, fns);
|
|
9488
|
-
const
|
|
9489
|
-
expr.arguments.forEach((a, i) => this.applyContext(a, expected[i]));
|
|
9490
|
-
const argTypes = expr.arguments.map((a) => this.infer(a, env));
|
|
9654
|
+
const argTypes = this.inferArguments(expr.arguments, fns, () => 0, (_, args) => args, env);
|
|
9491
9655
|
if (fns.length) {
|
|
9492
9656
|
this.recordExpected(expr.arguments, fns, () => 0, () => argTypes);
|
|
9493
9657
|
const spread = this.spreadOf(expr.arguments);
|
|
@@ -9552,9 +9716,13 @@ var TypeAnalyzer = class {
|
|
|
9552
9716
|
const united = this.unionSignatures(method);
|
|
9553
9717
|
const fns = united ?? this.overloadsOf(method);
|
|
9554
9718
|
const explicit = this.explicitTypeArguments(expr, fns);
|
|
9555
|
-
const
|
|
9556
|
-
|
|
9557
|
-
|
|
9719
|
+
const argTypes = this.inferArguments(
|
|
9720
|
+
expr.arguments,
|
|
9721
|
+
fns,
|
|
9722
|
+
(f) => this.takesSelf(f) ? 1 : 0,
|
|
9723
|
+
(f, args) => this.takesSelf(f) ? [objType, ...args] : args,
|
|
9724
|
+
env
|
|
9725
|
+
);
|
|
9558
9726
|
if (fns.length) {
|
|
9559
9727
|
const withSelf = (f) => this.takesSelf(f) ? [objType, ...argTypes] : argTypes;
|
|
9560
9728
|
const selfOf = (f) => this.takesSelf(f) ? 1 : 0;
|
|
@@ -9630,6 +9798,14 @@ var TypeAnalyzer = class {
|
|
|
9630
9798
|
name = target.property.name;
|
|
9631
9799
|
object = target.object;
|
|
9632
9800
|
} else if (target.type === "IndexExpression") {
|
|
9801
|
+
const list = this.expand(this.infer(target.object, env));
|
|
9802
|
+
if ((list.kind === "array" || list.kind === "tuple") && list.readonly) {
|
|
9803
|
+
this.diagnostics.push({
|
|
9804
|
+
node: target,
|
|
9805
|
+
message: `Cannot assign to an element of '${formatType(list)}': it is read-only`
|
|
9806
|
+
});
|
|
9807
|
+
return;
|
|
9808
|
+
}
|
|
9633
9809
|
const index = this.expand(this.infer(target.index, env));
|
|
9634
9810
|
if (index.kind !== "literal" || typeof index.value !== "string") return;
|
|
9635
9811
|
name = index.value;
|
|
@@ -9802,9 +9978,27 @@ var TypeAnalyzer = class {
|
|
|
9802
9978
|
if (s.indexer) indexer = mergeIndexer(indexer, s.indexer);
|
|
9803
9979
|
}
|
|
9804
9980
|
}
|
|
9981
|
+
this.checkDuplicateKey(expr, field, entries);
|
|
9805
9982
|
}
|
|
9806
9983
|
return objectType(entries, indexer, asConst || void 0);
|
|
9807
9984
|
}
|
|
9985
|
+
/** `{ a: 1, a: 2 }` — the second wins and the first was written for
|
|
9986
|
+
* nothing, which is a typo far more often than a decision. A spread is
|
|
9987
|
+
* not one: overriding what it brought is exactly what it is for. */
|
|
9988
|
+
checkDuplicateKey(expr, field, entries) {
|
|
9989
|
+
if (!this.emitDiagnostics || this.duplicateKeyReported.has(field)) return;
|
|
9990
|
+
const written = field.type === "TableFieldNamed" ? field.key.type === "Identifier" ? field.key.name : field.key.value : field.type === "TableFieldShorthand" ? field.name.name : void 0;
|
|
9991
|
+
if (written === void 0) return;
|
|
9992
|
+
const spreadBefore = expr.fields.slice(0, expr.fields.indexOf(field)).some((f) => f.type === "TableFieldSpread");
|
|
9993
|
+
if (spreadBefore) return;
|
|
9994
|
+
if (entries.filter(([k]) => k === written).length < 2) return;
|
|
9995
|
+
this.duplicateKeyReported.add(field);
|
|
9996
|
+
this.diagnostics.push({
|
|
9997
|
+
node: field.type === "TableFieldNamed" ? field.key : field.name,
|
|
9998
|
+
message: `'${written}' is given twice in this table; only the last one is kept`
|
|
9999
|
+
});
|
|
10000
|
+
}
|
|
10001
|
+
duplicateKeyReported = /* @__PURE__ */ new WeakSet();
|
|
9808
10002
|
/** A value inferred `as const`, widened back wherever `context` does not
|
|
9809
10003
|
* ask for a literal: `satisfies`' result type. A property keeps `"circle"`
|
|
9810
10004
|
* when the contract's property admits string literals, and becomes
|
|
@@ -10297,27 +10491,84 @@ var TypeAnalyzer = class {
|
|
|
10297
10491
|
arithmeticOn(operands) {
|
|
10298
10492
|
return operands.some((t) => this.expand(t).kind === "any") ? anyType : numberType;
|
|
10299
10493
|
}
|
|
10494
|
+
/** `a == b` where nothing `a` can hold is anything `b` can: the answer is
|
|
10495
|
+
* always the same, so the comparison was not the one meant. Only types
|
|
10496
|
+
* that say what they hold take part — `any`, `unknown` and a type
|
|
10497
|
+
* parameter say nothing, and a table compares by identity, so two of them
|
|
10498
|
+
* may well be the same table. */
|
|
10499
|
+
checkOverlap(node, op, l, r) {
|
|
10500
|
+
if (!this.emitDiagnostics) return;
|
|
10501
|
+
const left = this.expand(l);
|
|
10502
|
+
const right = this.expand(r);
|
|
10503
|
+
const judged = (t) => {
|
|
10504
|
+
const parts = t.kind === "union" ? t.types.map((m) => this.expand(m)) : [t];
|
|
10505
|
+
return parts.every((p) => p.kind === "literal" || p.kind === "primitive");
|
|
10506
|
+
};
|
|
10507
|
+
const isNil = (t) => t.kind === "primitive" && t.name === "nil";
|
|
10508
|
+
if (isNil(left) || isNil(right)) return;
|
|
10509
|
+
if (!judged(left) || !judged(right)) return;
|
|
10510
|
+
if (isAssignable(left, right) || isAssignable(right, left)) return;
|
|
10511
|
+
this.diagnostics.push({
|
|
10512
|
+
node,
|
|
10513
|
+
message: `This comparison is always ${op === "==" ? "false" : "true"}: '${formatType(l)}' and '${formatType(r)}' have no value in common`
|
|
10514
|
+
});
|
|
10515
|
+
}
|
|
10516
|
+
/** What an operator takes, where no metamethod answered for it. Luau
|
|
10517
|
+
* raises on the rest — `n * 2` with `n` nil is an error at the line it
|
|
10518
|
+
* runs, and a nil `n` is exactly what `number | nil` says may happen — so
|
|
10519
|
+
* it is said here instead. `any` passes: nothing is known about it. */
|
|
10520
|
+
checkOperands(node, op, left, right) {
|
|
10521
|
+
if (!this.emitDiagnostics) return;
|
|
10522
|
+
const fits = (t) => {
|
|
10523
|
+
const x = this.expand(t);
|
|
10524
|
+
if (x.kind === "any" || x.kind === "never") return true;
|
|
10525
|
+
switch (op) {
|
|
10526
|
+
// `..` joins strings, and Luau writes a number into one.
|
|
10527
|
+
case "..":
|
|
10528
|
+
return isAssignable(x, stringType) || isAssignable(x, numberType);
|
|
10529
|
+
// `#` counts a string's bytes or a table's entries.
|
|
10530
|
+
case "#":
|
|
10531
|
+
return x.kind === "array" || x.kind === "tuple" || x.kind === "object" || isAssignable(x, stringType);
|
|
10532
|
+
default:
|
|
10533
|
+
return isAssignable(x, numberType);
|
|
10534
|
+
}
|
|
10535
|
+
};
|
|
10536
|
+
if (op === "<" || op === ">" || op === "<=" || op === ">=") {
|
|
10537
|
+
const both = (t) => this.expand(t);
|
|
10538
|
+
const [l, r] = [both(left), both(right)];
|
|
10539
|
+
const loose = (t) => t.kind === "any" || t.kind === "never";
|
|
10540
|
+
const alike = (as) => isAssignable(l, as) && isAssignable(r, as);
|
|
10541
|
+
if (loose(l) || loose(r) || alike(numberType) || alike(stringType)) return;
|
|
10542
|
+
} else if (fits(left) && (right === void 0 || fits(right))) {
|
|
10543
|
+
return;
|
|
10544
|
+
}
|
|
10545
|
+
this.diagnostics.push({
|
|
10546
|
+
node,
|
|
10547
|
+
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)}'`
|
|
10548
|
+
});
|
|
10549
|
+
}
|
|
10300
10550
|
/** `a < b` between class instances: Luau asks `__lt` (`__le` for `<=`),
|
|
10301
10551
|
* with the operands swapped for `>` and `>=`, and an instance without
|
|
10302
10552
|
* one cannot be compared at all. */
|
|
10303
10553
|
checkComparison(node, op, l, r) {
|
|
10304
|
-
if (!this.emitDiagnostics) return;
|
|
10554
|
+
if (!this.emitDiagnostics) return false;
|
|
10305
10555
|
const left = this.expand(l);
|
|
10306
10556
|
const right = this.expand(r);
|
|
10307
10557
|
const instance = (t) => t.kind === "object" && t.class !== void 0;
|
|
10308
|
-
if (!instance(left) && !instance(right)) return;
|
|
10558
|
+
if (!instance(left) && !instance(right)) return false;
|
|
10309
10559
|
const name = op === "<" || op === ">" ? "__lt" : "__le";
|
|
10310
10560
|
const operands = op === ">" || op === ">=" ? [right, left] : [left, right];
|
|
10311
10561
|
for (const receiver of operands) {
|
|
10312
10562
|
const method = receiver.kind === "object" ? receiver.properties.get(name) : void 0;
|
|
10313
10563
|
if (!method) continue;
|
|
10314
|
-
if (this.pickOverload(this.overloadsOf(method.type), operands)) return;
|
|
10564
|
+
if (this.pickOverload(this.overloadsOf(method.type), operands)) return true;
|
|
10315
10565
|
break;
|
|
10316
10566
|
}
|
|
10317
10567
|
this.diagnostics.push({
|
|
10318
10568
|
node,
|
|
10319
10569
|
message: `Operator '${op}' cannot be applied to types '${formatType(l)}' and '${formatType(r)}'`
|
|
10320
10570
|
});
|
|
10571
|
+
return true;
|
|
10321
10572
|
}
|
|
10322
10573
|
/** What an operator on a value with metamethods gives: `a + b` calls
|
|
10323
10574
|
* `__add` on `a`, or failing that on `b` with the operands swapped — the
|
|
@@ -10604,7 +10855,56 @@ var TypeAnalyzer = class {
|
|
|
10604
10855
|
return this.bindingByDecl.get(node) ?? this.bindingByPos.get(posKey(name, node.line.start, node.column.start));
|
|
10605
10856
|
}
|
|
10606
10857
|
};
|
|
10607
|
-
|
|
10858
|
+
var libraryNames = /* @__PURE__ */ new WeakMap();
|
|
10859
|
+
function namesDeclaredIn(program) {
|
|
10860
|
+
const known = libraryNames.get(program);
|
|
10861
|
+
if (known) return known;
|
|
10862
|
+
const names = /* @__PURE__ */ new Set();
|
|
10863
|
+
for (const stmt of program.body.statements) {
|
|
10864
|
+
const declaration = stmt.type === "ExportStatement" || stmt.type === "ExportDefaultStatement" ? stmt.declaration : stmt;
|
|
10865
|
+
if (declaration.type === "TypeAliasStatement") names.add(declaration.name.name);
|
|
10866
|
+
else if (declaration.type === "ExportTypeAliasStatement") names.add(declaration.alias.name.name);
|
|
10867
|
+
else if (declaration.type === "DeclareClassStatement") names.add(declaration.name.name);
|
|
10868
|
+
else if (declaration.type === "ClassDeclaration" && declaration.name) names.add(declaration.name.name);
|
|
10869
|
+
else if (stmt.type === "ImportStatement") {
|
|
10870
|
+
for (const s of stmt.specifiers) names.add(s.local.name);
|
|
10871
|
+
if (stmt.defaultImport) names.add(stmt.defaultImport.name);
|
|
10872
|
+
if (stmt.namespaceImport) names.add(stmt.namespaceImport.name);
|
|
10873
|
+
}
|
|
10874
|
+
}
|
|
10875
|
+
libraryNames.set(program, names);
|
|
10876
|
+
return names;
|
|
10877
|
+
}
|
|
10878
|
+
var sharedClassesByLib = /* @__PURE__ */ new WeakMap();
|
|
10879
|
+
function shareableClasses(libs, program) {
|
|
10880
|
+
if (!libs.length) return void 0;
|
|
10881
|
+
const mine = namesDeclaredIn(program);
|
|
10882
|
+
if (mine.size) {
|
|
10883
|
+
for (const lib of libs) {
|
|
10884
|
+
for (const name of namesDeclaredIn(lib)) if (mine.has(name)) return void 0;
|
|
10885
|
+
}
|
|
10886
|
+
}
|
|
10887
|
+
const existing = sharedClassesByLib.get(libs[0]);
|
|
10888
|
+
if (existing) {
|
|
10889
|
+
const same = existing.libs.length === libs.length && existing.libs.every((lib, i) => lib === libs[i]);
|
|
10890
|
+
if (same) return existing;
|
|
10891
|
+
return void 0;
|
|
10892
|
+
}
|
|
10893
|
+
const store = { libs: [...libs], classTypes: /* @__PURE__ */ new WeakMap(), classMembers: /* @__PURE__ */ new WeakMap() };
|
|
10894
|
+
sharedClassesByLib.set(libs[0], store);
|
|
10895
|
+
return store;
|
|
10896
|
+
}
|
|
10897
|
+
var referencedNamesOf = /* @__PURE__ */ new WeakMap();
|
|
10898
|
+
var typeQueryIn = /* @__PURE__ */ new WeakMap();
|
|
10899
|
+
function referencedTypeNames(node, out) {
|
|
10900
|
+
if (out === void 0 && node && typeof node === "object") {
|
|
10901
|
+
const known = referencedNamesOf.get(node);
|
|
10902
|
+
if (known) return known;
|
|
10903
|
+
const found = referencedTypeNames(node, []);
|
|
10904
|
+
referencedNamesOf.set(node, found);
|
|
10905
|
+
return found;
|
|
10906
|
+
}
|
|
10907
|
+
out = out ?? [];
|
|
10608
10908
|
if (!node || typeof node !== "object") return out;
|
|
10609
10909
|
if (Array.isArray(node)) {
|
|
10610
10910
|
for (const item of node) referencedTypeNames(item, out);
|
|
@@ -10621,9 +10921,11 @@ function referencedTypeNames(node, out = []) {
|
|
|
10621
10921
|
}
|
|
10622
10922
|
function containsTypeQuery(node) {
|
|
10623
10923
|
if (!node || typeof node !== "object") return false;
|
|
10624
|
-
|
|
10625
|
-
if (
|
|
10626
|
-
|
|
10924
|
+
const known = typeQueryIn.get(node);
|
|
10925
|
+
if (known !== void 0) return known;
|
|
10926
|
+
const found = Array.isArray(node) ? node.some(containsTypeQuery) : node.type === "TypeofTypeNode" || Object.values(node).some(containsTypeQuery);
|
|
10927
|
+
typeQueryIn.set(node, found);
|
|
10928
|
+
return found;
|
|
10627
10929
|
}
|
|
10628
10930
|
var STRING_INTRINSICS = /* @__PURE__ */ new Set(["Uppercase", "Lowercase", "Capitalize", "Uncapitalize"]);
|
|
10629
10931
|
function briefType(t) {
|