@tilua/parser 3.0.0 → 3.0.1
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/dist/index.cjs +218 -23
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +218 -23
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -3893,6 +3893,10 @@ var Analyzer = class {
|
|
|
3893
3893
|
moduleScope = this.globalScope;
|
|
3894
3894
|
/** How many function bodies enclose the walk. */
|
|
3895
3895
|
functionDepth = 0;
|
|
3896
|
+
/** How many loops enclose the walk inside the nearest function body. A
|
|
3897
|
+
* function starts again at 0: `continue` in `xs:map(x => ...)` has no
|
|
3898
|
+
* loop to go on with, whatever loop the call is written in. */
|
|
3899
|
+
loopDepth = 0;
|
|
3896
3900
|
/** Function declarations already declared by their block's hoisting, with
|
|
3897
3901
|
* the function depth of that block. */
|
|
3898
3902
|
hoisted = /* @__PURE__ */ new Map();
|
|
@@ -4245,11 +4249,11 @@ var Analyzer = class {
|
|
|
4245
4249
|
return;
|
|
4246
4250
|
case "WhileStatement":
|
|
4247
4251
|
this.visitExpression(stmt.condition, scope);
|
|
4248
|
-
this.visitBlockInNewScope(stmt.body, scope);
|
|
4252
|
+
this.inLoop(() => this.visitBlockInNewScope(stmt.body, scope));
|
|
4249
4253
|
return;
|
|
4250
4254
|
case "RepeatStatement": {
|
|
4251
4255
|
const bodyScope = childScope(scope);
|
|
4252
|
-
this.visitBlock(stmt.body, bodyScope);
|
|
4256
|
+
this.inLoop(() => this.visitBlock(stmt.body, bodyScope));
|
|
4253
4257
|
this.visitExpression(stmt.condition, bodyScope);
|
|
4254
4258
|
return;
|
|
4255
4259
|
}
|
|
@@ -4267,14 +4271,14 @@ var Analyzer = class {
|
|
|
4267
4271
|
if (stmt.step) this.visitExpression(stmt.step, scope);
|
|
4268
4272
|
const bodyScope = childScope(scope);
|
|
4269
4273
|
this.declare(bodyScope, stmt.variable.name, "for-numeric", stmt.variable);
|
|
4270
|
-
this.visitBlock(stmt.body, bodyScope);
|
|
4274
|
+
this.inLoop(() => this.visitBlock(stmt.body, bodyScope));
|
|
4271
4275
|
return;
|
|
4272
4276
|
}
|
|
4273
4277
|
case "GenericForStatement": {
|
|
4274
4278
|
this.visitExpression(stmt.iterator, scope);
|
|
4275
4279
|
const bodyScope = childScope(scope);
|
|
4276
4280
|
this.declarePattern(bodyScope, stmt.variable, "for-generic", scope, stmt.kind === "const");
|
|
4277
|
-
this.visitBlock(stmt.body, bodyScope);
|
|
4281
|
+
this.inLoop(() => this.visitBlock(stmt.body, bodyScope));
|
|
4278
4282
|
return;
|
|
4279
4283
|
}
|
|
4280
4284
|
case "ReturnStatement":
|
|
@@ -4282,6 +4286,15 @@ var Analyzer = class {
|
|
|
4282
4286
|
return;
|
|
4283
4287
|
case "BreakStatement":
|
|
4284
4288
|
case "ContinueStatement":
|
|
4289
|
+
if (this.loopDepth === 0) {
|
|
4290
|
+
const word = stmt.type === "BreakStatement" ? "break" : "continue";
|
|
4291
|
+
this.diagnostics.push({
|
|
4292
|
+
node: stmt,
|
|
4293
|
+
message: this.functionDepth > 0 ? `'${word}' is not inside a loop of this function; to skip a value in a callback, 'return' from it` : `'${word}' is not inside a loop`,
|
|
4294
|
+
kind: "outside-loop"
|
|
4295
|
+
});
|
|
4296
|
+
}
|
|
4297
|
+
return;
|
|
4285
4298
|
case "ErrorStatement":
|
|
4286
4299
|
return;
|
|
4287
4300
|
case "DeclareStatement":
|
|
@@ -4337,6 +4350,15 @@ var Analyzer = class {
|
|
|
4337
4350
|
return;
|
|
4338
4351
|
}
|
|
4339
4352
|
}
|
|
4353
|
+
/** Visit a loop's body, where `break` and `continue` have a loop. */
|
|
4354
|
+
inLoop(visit) {
|
|
4355
|
+
this.loopDepth++;
|
|
4356
|
+
try {
|
|
4357
|
+
visit();
|
|
4358
|
+
} finally {
|
|
4359
|
+
this.loopDepth--;
|
|
4360
|
+
}
|
|
4361
|
+
}
|
|
4340
4362
|
// ---------------- functions ----------------
|
|
4341
4363
|
visitFunctionBody(func, outerScope, isMethod = false) {
|
|
4342
4364
|
const fnScope = childScope(outerScope);
|
|
@@ -4352,11 +4374,14 @@ var Analyzer = class {
|
|
|
4352
4374
|
}
|
|
4353
4375
|
});
|
|
4354
4376
|
this.visitType(func.returnType, fnScope);
|
|
4377
|
+
const loops = this.loopDepth;
|
|
4355
4378
|
this.functionDepth++;
|
|
4379
|
+
this.loopDepth = 0;
|
|
4356
4380
|
try {
|
|
4357
4381
|
this.visitBlock(func.body, fnScope);
|
|
4358
4382
|
} finally {
|
|
4359
4383
|
this.functionDepth--;
|
|
4384
|
+
this.loopDepth = loops;
|
|
4360
4385
|
}
|
|
4361
4386
|
}
|
|
4362
4387
|
/** An overload signature: no body and no bindings, but its types can hold
|
|
@@ -4610,11 +4635,14 @@ declare metatable string: {
|
|
|
4610
4635
|
-- answer \`nil\` rather than JavaScript's -1. \`sort\` takes Lua's comparator
|
|
4611
4636
|
-- (true when \`a\` comes first). \`push\`, \`pop\`, \`shift\`, \`unshift\`,
|
|
4612
4637
|
-- \`sort\` and \`reverse\` change the array; everything else returns a new one.
|
|
4638
|
+
-- A \`filter\` whose test is a guard \u2014 \`v is S\`, or \`v => v ~= nil\` \u2014
|
|
4639
|
+
-- answers an array of what it let through.
|
|
4613
4640
|
declare metatable<T> T[]: {
|
|
4614
4641
|
__index: {
|
|
4615
4642
|
find: (self: T[], test: (value: T, index: number) => boolean) => T | nil,
|
|
4616
4643
|
findIndex: (self: T[], test: (value: T, index: number) => boolean) => number | nil,
|
|
4617
|
-
filter: (self: T[], test: (value: T, index: number) =>
|
|
4644
|
+
filter: (<S extends T>(self: T[], test: (value: T, index: number) => value is S) => S[])
|
|
4645
|
+
& ((self: T[], test: (value: T, index: number) => boolean) => T[]),
|
|
4618
4646
|
map: <U>(self: T[], transform: (value: T, index: number) => U) => U[],
|
|
4619
4647
|
forEach: (self: T[], visit: (value: T, index: number) => nil) => nil,
|
|
4620
4648
|
some: (self: T[], test: (value: T, index: number) => boolean) => boolean,
|
|
@@ -4753,8 +4781,14 @@ function substitute(t, subst) {
|
|
|
4753
4781
|
if (subst.size === 0) return t;
|
|
4754
4782
|
if (!containsTypeParam(t)) return t;
|
|
4755
4783
|
switch (t.kind) {
|
|
4756
|
-
case "typeParam":
|
|
4757
|
-
|
|
4784
|
+
case "typeParam": {
|
|
4785
|
+
const bound = subst.get(t.name);
|
|
4786
|
+
if (bound) return bound;
|
|
4787
|
+
if (t.constraint && containsTypeParam(t.constraint)) {
|
|
4788
|
+
return { ...t, constraint: substitute(t.constraint, new Map([...subst, [t.name, t]])) };
|
|
4789
|
+
}
|
|
4790
|
+
return t;
|
|
4791
|
+
}
|
|
4758
4792
|
case "array":
|
|
4759
4793
|
return { ...arrayOf(substitute(t.element, subst)), readonly: t.readonly };
|
|
4760
4794
|
case "tuple":
|
|
@@ -4899,6 +4933,10 @@ function unify(param, arg, vars, out, how = "union") {
|
|
|
4899
4933
|
if (arg.kind === "function") {
|
|
4900
4934
|
param.params.forEach((p, i) => arg.params[i] && unify(p.type, arg.params[i].type, vars, out));
|
|
4901
4935
|
unify(param.returns, arg.returns, vars, out);
|
|
4936
|
+
const wanted = param.predicate, given = arg.predicate;
|
|
4937
|
+
if (wanted?.type && given?.type && wanted.param === given.param && !wanted.asserts && !given.asserts) {
|
|
4938
|
+
unify(wanted.type, given.type, vars, out);
|
|
4939
|
+
}
|
|
4902
4940
|
}
|
|
4903
4941
|
return;
|
|
4904
4942
|
case "object":
|
|
@@ -5154,6 +5192,11 @@ function isAssignableInner(a, b) {
|
|
|
5154
5192
|
for (let i = 0; i < n; i++) {
|
|
5155
5193
|
if (!isAssignable(a.params[i].type, b.params[i].type) && !isAssignable(b.params[i].type, a.params[i].type)) return false;
|
|
5156
5194
|
}
|
|
5195
|
+
const wanted = b.predicate;
|
|
5196
|
+
if (wanted?.type && !wanted.asserts) {
|
|
5197
|
+
const given = a.predicate;
|
|
5198
|
+
if (!given?.type || given.asserts || given.param !== wanted.param || !isAssignable(given.type, wanted.type)) return false;
|
|
5199
|
+
}
|
|
5157
5200
|
return isAssignable(a.returns, b.returns);
|
|
5158
5201
|
}
|
|
5159
5202
|
if (b.kind === "typeParam") return false;
|
|
@@ -5288,7 +5331,7 @@ function containsFreeTypeParam(t, seen, bound) {
|
|
|
5288
5331
|
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));
|
|
5289
5332
|
case "function": {
|
|
5290
5333
|
const inner = t.typeParams?.length ? /* @__PURE__ */ new Set([...bound, ...t.typeParams]) : bound;
|
|
5291
|
-
return t.params.some((p) => containsTypeParam(p.type, seen, inner)) || !!t.varargs && containsTypeParam(t.varargs, seen, inner) || containsTypeParam(t.returns, seen, inner);
|
|
5334
|
+
return t.params.some((p) => containsTypeParam(p.type, seen, inner)) || !!t.varargs && containsTypeParam(t.varargs, seen, inner) || containsTypeParam(t.returns, seen, inner) || !!t.predicate?.type && containsTypeParam(t.predicate.type, seen, inner);
|
|
5292
5335
|
}
|
|
5293
5336
|
case "genericRef":
|
|
5294
5337
|
return t.typeArguments.some((a) => containsTypeParam(a, seen, bound));
|
|
@@ -5713,9 +5756,14 @@ function isFreshLiteralExpr(e) {
|
|
|
5713
5756
|
case "StringLiteral":
|
|
5714
5757
|
case "BooleanLiteral":
|
|
5715
5758
|
case "InterpolatedStringExpression":
|
|
5759
|
+
return true;
|
|
5760
|
+
// A table or array literal widened the literals written in it as it
|
|
5761
|
+
// was read (`widenUnlessAsked`), and keeps what it read from
|
|
5762
|
+
// elsewhere — `{ key: name }`, `[...names]` — so there is nothing
|
|
5763
|
+
// left for the binding to widen.
|
|
5716
5764
|
case "TableExpression":
|
|
5717
5765
|
case "ArrayExpression":
|
|
5718
|
-
return
|
|
5766
|
+
return false;
|
|
5719
5767
|
case "ParenthesizedExpression":
|
|
5720
5768
|
return isFreshLiteralExpr(e.expression);
|
|
5721
5769
|
case "UnaryExpression":
|
|
@@ -5730,6 +5778,30 @@ function isFreshLiteralExpr(e) {
|
|
|
5730
5778
|
return false;
|
|
5731
5779
|
}
|
|
5732
5780
|
}
|
|
5781
|
+
function overloadTier(f) {
|
|
5782
|
+
const takesGuard = f.params.some((p) => p.type.kind === "function" && p.type.predicate?.type && !p.type.predicate.asserts);
|
|
5783
|
+
if (takesGuard) return 0;
|
|
5784
|
+
return (f.typeParams?.length ?? 0) > 0 ? 2 : 1;
|
|
5785
|
+
}
|
|
5786
|
+
function writesLiteral(e) {
|
|
5787
|
+
switch (e.type) {
|
|
5788
|
+
case "NumberLiteral":
|
|
5789
|
+
case "StringLiteral":
|
|
5790
|
+
case "BooleanLiteral":
|
|
5791
|
+
case "InterpolatedStringExpression":
|
|
5792
|
+
return true;
|
|
5793
|
+
case "ParenthesizedExpression":
|
|
5794
|
+
return writesLiteral(e.expression);
|
|
5795
|
+
case "UnaryExpression":
|
|
5796
|
+
return writesLiteral(e.argument);
|
|
5797
|
+
case "IfElseExpression":
|
|
5798
|
+
return e.clauses.some((c) => writesLiteral(c.body)) || writesLiteral(e.alternate);
|
|
5799
|
+
case "BinaryExpression":
|
|
5800
|
+
return (e.operator === "or" || e.operator === "and") && (writesLiteral(e.left) || writesLiteral(e.right));
|
|
5801
|
+
default:
|
|
5802
|
+
return false;
|
|
5803
|
+
}
|
|
5804
|
+
}
|
|
5733
5805
|
function collectInferNames(node) {
|
|
5734
5806
|
const out = [];
|
|
5735
5807
|
const walk = (n) => {
|
|
@@ -8112,7 +8184,9 @@ var TypeAnalyzer = class {
|
|
|
8112
8184
|
const waits = written.map((a, i) => this.waitsOnInference(a, expected[i]));
|
|
8113
8185
|
if (!waits.some(Boolean)) return written.map((a) => this.infer(a, env));
|
|
8114
8186
|
const argTypes = written.map((a, i) => waits[i] ? this.quietly(() => this.infer(a, env)) : this.infer(a, env));
|
|
8115
|
-
const
|
|
8187
|
+
const spread = this.spreadOf(written, selfOf(fns[0]));
|
|
8188
|
+
const provisional = argTypes.map((t, i) => waits[i] ? anyType : t);
|
|
8189
|
+
const picked = this.pickOverload(fns, argTypes, (f) => extra(f, argTypes), spread) ?? this.pickOverload(fns, provisional, (f) => extra(f, provisional), spread);
|
|
8116
8190
|
const subst = picked?.typeParams?.length ? this.inferTypeArgs(picked, extra(picked, argTypes)) : void 0;
|
|
8117
8191
|
written.forEach((a, i) => {
|
|
8118
8192
|
if (!waits[i]) return;
|
|
@@ -8303,6 +8377,16 @@ var TypeAnalyzer = class {
|
|
|
8303
8377
|
}
|
|
8304
8378
|
const id = this.bindingIdByName(p.name, p);
|
|
8305
8379
|
const t = this.paramType(p, env);
|
|
8380
|
+
if (p.default && p.typeAnnotation) {
|
|
8381
|
+
const given = this.infer(p.default, env);
|
|
8382
|
+
const declared = this.resolveType(p.typeAnnotation);
|
|
8383
|
+
if (this.emitDiagnostics && !isAssignable(given, declared)) {
|
|
8384
|
+
this.diagnostics.push({
|
|
8385
|
+
node: p.default,
|
|
8386
|
+
message: `Type '${briefType(given)}' is not assignable to '${briefType(declared)}'` + this.explainMismatch(given, declared)
|
|
8387
|
+
});
|
|
8388
|
+
}
|
|
8389
|
+
}
|
|
8306
8390
|
if (id !== void 0) {
|
|
8307
8391
|
this.bindingType.set(id, t);
|
|
8308
8392
|
this.setBinding(env, id, t);
|
|
@@ -8400,9 +8484,9 @@ var TypeAnalyzer = class {
|
|
|
8400
8484
|
* lets `typeof` declare `(v: number) -> "number"` alongside a trailing
|
|
8401
8485
|
* `<T>(v: T) -> string` and still pick the precise one. */
|
|
8402
8486
|
pickOverload(fns, argTypes, argsFor, spread) {
|
|
8403
|
-
for (const
|
|
8487
|
+
for (const tier of [0, 1, 2]) {
|
|
8404
8488
|
for (const f of fns) {
|
|
8405
|
-
if ((f
|
|
8489
|
+
if (overloadTier(f) !== tier) continue;
|
|
8406
8490
|
if (this.overloadAccepts(f, argsFor ? argsFor(f) : argTypes, spread)) return f;
|
|
8407
8491
|
}
|
|
8408
8492
|
}
|
|
@@ -8428,7 +8512,7 @@ var TypeAnalyzer = class {
|
|
|
8428
8512
|
if (position < 0) return void 0;
|
|
8429
8513
|
const members = this.expand(argTypes[position]).types;
|
|
8430
8514
|
if (members.length > 32) return void 0;
|
|
8431
|
-
const rank = (f) => (
|
|
8515
|
+
const rank = (f) => overloadTier(f) * fns.length + fns.indexOf(f);
|
|
8432
8516
|
const limit = picked ? rank(picked) : Infinity;
|
|
8433
8517
|
const results = [];
|
|
8434
8518
|
for (const member of members) {
|
|
@@ -8821,10 +8905,38 @@ var TypeAnalyzer = class {
|
|
|
8821
8905
|
returns,
|
|
8822
8906
|
void 0,
|
|
8823
8907
|
names,
|
|
8824
|
-
this.resolvePredicate(func.predicate, params)
|
|
8908
|
+
func.predicate || func.returnType ? this.resolvePredicate(func.predicate, params) : this.inferPredicate(func, params, returns, bodyEnv)
|
|
8825
8909
|
), func.params);
|
|
8826
8910
|
});
|
|
8827
8911
|
}
|
|
8912
|
+
/** `v => v ~= nil` is a guard without saying so, as TypeScript reads it:
|
|
8913
|
+
* a body that is one `return` of a boolean which, true, narrows a
|
|
8914
|
+
* parameter — and, false, leaves it exactly what that excludes. Then
|
|
8915
|
+
* `list:filter(v => v ~= nil)` knows what it kept. */
|
|
8916
|
+
inferPredicate(func, params, returns, env) {
|
|
8917
|
+
const only = func.body.statements.length === 1 ? func.body.statements[0] : void 0;
|
|
8918
|
+
if (only?.type !== "ReturnStatement" || !only.argument) return void 0;
|
|
8919
|
+
if (returns.kind === "any" || returns.kind === "never" || !isAssignable(returns, booleanType)) return void 0;
|
|
8920
|
+
const cond = only.argument;
|
|
8921
|
+
for (let i = 0; i < params.length; i++) {
|
|
8922
|
+
const p = func.params[i];
|
|
8923
|
+
if (p.pattern || p.rest) continue;
|
|
8924
|
+
const id = this.bindingIdByName(p.name, p);
|
|
8925
|
+
if (id === void 0) continue;
|
|
8926
|
+
const declared = params[i].type;
|
|
8927
|
+
const key = bindKey(id);
|
|
8928
|
+
const t = forkEnv(env);
|
|
8929
|
+
const f = forkEnv(env);
|
|
8930
|
+
this.silently(() => this.applyNarrowing(cond, env, t, f));
|
|
8931
|
+
const yes = t.get(key);
|
|
8932
|
+
if (!yes || equalTypes(yes, declared) || !isAssignable(yes, declared)) continue;
|
|
8933
|
+
const no = this.expand(f.get(key) ?? declared);
|
|
8934
|
+
const rest = no.kind === "union" ? no.types : [no];
|
|
8935
|
+
if (rest.some((m) => m.kind !== "never" && isAssignable(m, yes))) continue;
|
|
8936
|
+
return { param: i, type: yes, asserts: false };
|
|
8937
|
+
}
|
|
8938
|
+
return void 0;
|
|
8939
|
+
}
|
|
8828
8940
|
/** Where the return types of the function being walked are collected, so
|
|
8829
8941
|
* each is read where it is written — inside the branch that narrowed it —
|
|
8830
8942
|
* rather than in whatever state the body ends in. */
|
|
@@ -9946,6 +10058,27 @@ var TypeAnalyzer = class {
|
|
|
9946
10058
|
elseEnv = whenFalse;
|
|
9947
10059
|
}
|
|
9948
10060
|
branches.push(this.infer(expr.alternate, elseEnv));
|
|
10061
|
+
const bodies = [...expr.clauses.map((c) => c.body), expr.alternate].map(unwrapParens);
|
|
10062
|
+
const isEmpty = (e) => e.type === "TableExpression" && e.fields.length === 0 || e.type === "ArrayExpression" && e.elements.length === 0;
|
|
10063
|
+
const others = union(branches.filter((_, i) => !isEmpty(bodies[i])));
|
|
10064
|
+
const parts = (others.kind === "union" ? others.types : [others]).map((m) => this.expand(m)).filter((m) => !(m.kind === "primitive" && m.name === "nil"));
|
|
10065
|
+
const fits = (e) => {
|
|
10066
|
+
if (!parts.length) return void 0;
|
|
10067
|
+
if (e.type === "ArrayExpression") {
|
|
10068
|
+
if (parts.every((m) => m.kind === "array")) return others;
|
|
10069
|
+
return parts.every((m) => m.kind === "array" || m.kind === "tuple") ? tuple([]) : void 0;
|
|
10070
|
+
}
|
|
10071
|
+
return parts.every((m) => m.kind === "object" && !m.class && [...m.properties.values()].every((prop) => prop.optional)) ? others : void 0;
|
|
10072
|
+
};
|
|
10073
|
+
if (bodies.some(isEmpty) && others.kind !== "never" && others.kind !== "any" && bodies.every((body) => !isEmpty(body) || fits(body))) {
|
|
10074
|
+
const typed = bodies.map((body, i) => {
|
|
10075
|
+
if (!isEmpty(body)) return branches[i];
|
|
10076
|
+
const t = fits(body);
|
|
10077
|
+
this.typeOf.set(body, t);
|
|
10078
|
+
return t;
|
|
10079
|
+
});
|
|
10080
|
+
return union(typed);
|
|
10081
|
+
}
|
|
9949
10082
|
return union(branches);
|
|
9950
10083
|
}
|
|
9951
10084
|
}
|
|
@@ -10252,23 +10385,87 @@ var TypeAnalyzer = class {
|
|
|
10252
10385
|
if (wantedTuple && !asConst) return this.inferTupleLiteral(expr, env, wantedTuple);
|
|
10253
10386
|
const elems = [];
|
|
10254
10387
|
let hadSpread = false;
|
|
10388
|
+
const written = [];
|
|
10255
10389
|
for (const el of expr.elements) {
|
|
10256
10390
|
if (el.type === "SpreadElement") {
|
|
10257
10391
|
hadSpread = true;
|
|
10258
|
-
const
|
|
10259
|
-
|
|
10260
|
-
|
|
10261
|
-
else elems.push(unknownType);
|
|
10392
|
+
const spread = this.infer(el.argument, env);
|
|
10393
|
+
written.push({ spread: true, type: spread });
|
|
10394
|
+
elems.push(...this.spreadElements(spread));
|
|
10262
10395
|
} else {
|
|
10263
|
-
|
|
10396
|
+
const t = asConst ? this.inferAsConst(el, env) : this.infer(el, env);
|
|
10397
|
+
written.push({ spread: false, type: asConst ? t : this.widenUnlessAsked(t, el) });
|
|
10398
|
+
elems.push(t);
|
|
10264
10399
|
}
|
|
10265
10400
|
}
|
|
10266
10401
|
if (asConst && !hadSpread) return tuple(elems);
|
|
10402
|
+
if (hadSpread) {
|
|
10403
|
+
const tuples = this.spreadTuples(written);
|
|
10404
|
+
if (tuples) return union(tuples.map((elements) => tuple(elements)));
|
|
10405
|
+
}
|
|
10267
10406
|
return arrayOf(elems.length ? union(elems.map((t, i) => {
|
|
10268
10407
|
const element = expr.elements[i];
|
|
10269
10408
|
return asConst || !element || element.type === "SpreadElement" ? t : this.widenUnlessAsked(t, element);
|
|
10270
10409
|
})) : unknownType);
|
|
10271
10410
|
}
|
|
10411
|
+
/** The tuples an array literal with spreads is, when every spread is a
|
|
10412
|
+
* tuple of known length or a union of them: one per way the unions go.
|
|
10413
|
+
* `undefined` when a spread's length is not known (an array, a tuple
|
|
10414
|
+
* with a rest), or the ways multiply past what is worth listing. */
|
|
10415
|
+
spreadTuples(written) {
|
|
10416
|
+
const MAX = 32;
|
|
10417
|
+
let variants = [[]];
|
|
10418
|
+
for (const { spread, type } of written) {
|
|
10419
|
+
if (!spread) {
|
|
10420
|
+
variants = variants.map((v) => [...v, type]);
|
|
10421
|
+
continue;
|
|
10422
|
+
}
|
|
10423
|
+
const options = this.fixedTuples(type);
|
|
10424
|
+
if (!options) return void 0;
|
|
10425
|
+
const next = [];
|
|
10426
|
+
for (const v of variants) for (const option of options) next.push([...v, ...option]);
|
|
10427
|
+
if (next.length > MAX) return void 0;
|
|
10428
|
+
variants = next;
|
|
10429
|
+
}
|
|
10430
|
+
return variants;
|
|
10431
|
+
}
|
|
10432
|
+
/** A tuple of known length's members, or a union of such tuples' — each
|
|
10433
|
+
* a way the value can be. `nil` in a union spreads nothing. */
|
|
10434
|
+
fixedTuples(t, depth = 0) {
|
|
10435
|
+
if (depth > 8) return void 0;
|
|
10436
|
+
const held = this.expand(t);
|
|
10437
|
+
if (held.kind === "tuple") return held.rest ? void 0 : [held.elements];
|
|
10438
|
+
if (held.kind !== "union") return void 0;
|
|
10439
|
+
const out = [];
|
|
10440
|
+
for (const member of held.types) {
|
|
10441
|
+
if (member.kind === "primitive" && member.name === "nil") continue;
|
|
10442
|
+
const options = this.fixedTuples(member, depth + 1);
|
|
10443
|
+
if (!options) return void 0;
|
|
10444
|
+
out.push(...options);
|
|
10445
|
+
}
|
|
10446
|
+
return out.length ? out : void 0;
|
|
10447
|
+
}
|
|
10448
|
+
/** What `...xs` puts into an array: an array's element, a tuple's members,
|
|
10449
|
+
* and for a union each member's — `...(host ? extra : [])` adds `extra`'s
|
|
10450
|
+
* elements and the empty tuple nothing. Anything else, `unknown`. */
|
|
10451
|
+
spreadElements(t, depth = 0) {
|
|
10452
|
+
const held = this.expand(t);
|
|
10453
|
+
if (depth > 8) return [unknownType];
|
|
10454
|
+
switch (held.kind) {
|
|
10455
|
+
case "array":
|
|
10456
|
+
return [held.element];
|
|
10457
|
+
case "tuple":
|
|
10458
|
+
return tupleMembers(held);
|
|
10459
|
+
case "union":
|
|
10460
|
+
return held.types.filter((m) => !(m.kind === "primitive" && m.name === "nil")).flatMap((m) => this.spreadElements(m, depth + 1));
|
|
10461
|
+
case "typeParam":
|
|
10462
|
+
return held.constraint ? this.spreadElements(held.constraint, depth + 1) : [unknownType];
|
|
10463
|
+
case "any":
|
|
10464
|
+
return [anyType];
|
|
10465
|
+
default:
|
|
10466
|
+
return [unknownType];
|
|
10467
|
+
}
|
|
10468
|
+
}
|
|
10272
10469
|
/** `[a, b, ...more]` where a tuple is wanted: the places it names are the
|
|
10273
10470
|
* tuple's, and what comes after them — more elements, or a spread — is
|
|
10274
10471
|
* its rest. */
|
|
@@ -10279,10 +10476,7 @@ var TypeAnalyzer = class {
|
|
|
10279
10476
|
expr.elements.forEach((element, i) => {
|
|
10280
10477
|
if (element.type === "SpreadElement") {
|
|
10281
10478
|
spread = true;
|
|
10282
|
-
|
|
10283
|
-
if (held.kind === "array") tail.push(held.element);
|
|
10284
|
-
else if (held.kind === "tuple") tail.push(...tupleMembers(held));
|
|
10285
|
-
else tail.push(unknownType);
|
|
10479
|
+
tail.push(...this.spreadElements(this.infer(element.argument, env)));
|
|
10286
10480
|
return;
|
|
10287
10481
|
}
|
|
10288
10482
|
const t = this.widenUnlessAsked(this.infer(element, env), element);
|
|
@@ -10299,6 +10493,7 @@ var TypeAnalyzer = class {
|
|
|
10299
10493
|
* The context was recorded by `applyContext` before the value was
|
|
10300
10494
|
* inferred, so this is a lookup rather than a second pass. */
|
|
10301
10495
|
widenUnlessAsked(value, at) {
|
|
10496
|
+
if (!writesLiteral(at)) return value;
|
|
10302
10497
|
const wanted = this.expectedTypeOf.get(at);
|
|
10303
10498
|
if (value.kind === "literal") {
|
|
10304
10499
|
const arms = this.contextArms.get(at);
|
package/dist/index.d.cts
CHANGED
|
@@ -1064,7 +1064,7 @@ interface ScopeDiagnostic {
|
|
|
1064
1064
|
};
|
|
1065
1065
|
};
|
|
1066
1066
|
message: string;
|
|
1067
|
-
kind: "redeclare" | "const-assign" | "type-only" | "undeclared" | "use-before-define";
|
|
1067
|
+
kind: "redeclare" | "const-assign" | "type-only" | "undeclared" | "use-before-define" | "outside-loop";
|
|
1068
1068
|
}
|
|
1069
1069
|
interface ScopeAnalysis {
|
|
1070
1070
|
/** Every Identifier that appears in a variable *usage* position (i.e.
|
|
@@ -1601,7 +1601,7 @@ resolveModule?: (specifier: string) => ModuleExports | undefined): ModuleExports
|
|
|
1601
1601
|
* `ObjectValues<T>` and `ObjectEntries<T>`, an object's members as tuples in
|
|
1602
1602
|
* the order they were written.
|
|
1603
1603
|
*/
|
|
1604
|
-
declare const PRELUDE_SOURCE = "\n-- In Luau only `nil` and `false` are falsy: `0` and `\"\"` are truthy.\n-- These are what truthiness narrowing computes, made available to write down.\ntype Falsy = nil | false\ntype Truthy<T> = T - Falsy\n\n-- `-` is set difference. Over a union it drops members; over a concrete type\n-- it simplifies away; over an opaque type (`unknown`, an unresolved parameter)\n-- it is kept, so `Exclude<unknown, 1>` stays `unknown - 1`.\ntype Exclude<T, U> = T - U\ntype Extract<T, U> = T extends U ? T : never\ntype NonNullable<T> = T - nil\n\ntype ReturnType<T> = T extends (...args: unknown[]) => infer R ? R : never\ntype Parameters<T> = T extends (...args: infer P) => unknown ? P : never\n\ntype Partial<T> = { [K in keyof T]?: T[K] }\ntype Required<T> = { [K in keyof T]-?: T[K] }\ntype Readonly<T> = { readonly [K in keyof T]: T[K] }\ntype Mutable<T> = { -readonly [K in keyof T]: T[K] }\n\ntype Pick<T, K> = { [P in K]: T[P] }\ntype Omit<T, K> = Pick<T, Exclude<keyof T, K>>\ntype Record<K, V> = { [P in K]: V }\n\n-- ============================================================\n-- The metatables of the language's own values\n-- ============================================================\n\n-- A string's methods. The first group is Lua's string library, which a\n-- string really answers to; where Lua answers several values, tilua answers\n-- one, as the global functions do (`find` is an array, `gsub` the new\n-- string). The second group is written the way JavaScript writes it.\n--\n-- Positions are Lua's: `sub` and `slice` count from 1, and `indexOf` is\n-- `nil` when the text is not there.\ndeclare metatable string: {\n __index: {\n upper: (self: string) => string,\n lower: (self: string) => string,\n len: (self: string) => number,\n sub: (self: string, i: number, j?: number) => string,\n rep: (self: string, n: number, separator?: string) => string,\n reverse: (self: string) => string,\n format: (self: string, ...args: unknown[]) => string,\n byte: ((self: string, i?: number) => number) & ((self: string, i: number, j: number) => number[]),\n find: (self: string, pattern: string, init?: number, plain?: boolean) => [number, number, ...string[]] | nil,\n match: (self: string, pattern: string, init?: number) => [...string[]] | nil,\n gmatch: (self: string, pattern: string) => () => [string, ...string[]] | nil,\n gsub: (self: string, pattern: string, replacement: unknown, n?: number) => string,\n\n trim: (self: string) => string,\n trimStart: (self: string) => string,\n trimEnd: (self: string) => string,\n startsWith: (self: string, text: string) => boolean,\n endsWith: (self: string, text: string) => boolean,\n includes: (self: string, text: string) => boolean,\n indexOf: (self: string, text: string, start?: number) => number | nil,\n slice: (self: string, start?: number, stop?: number) => string,\n replace: (self: string, text: string, replacement: string) => string,\n replaceAll: (self: string, text: string, replacement: string) => string,\n padStart: (self: string, length: number, padding?: string) => string,\n padEnd: (self: string, length: number, padding?: string) => string,\n },\n}\n\n-- An array's methods. Lua gives an array no metatable: the compiler lowers\n-- each call to a plain function, so they work on any array, one a Lua\n-- library made included.\n--\n-- Indices are Lua's: the first element is 1, and `indexOf` / `findIndex`\n-- answer `nil` rather than JavaScript's -1. `sort` takes Lua's comparator\n-- (true when `a` comes first). `push`, `pop`, `shift`, `unshift`,\n-- `sort` and `reverse` change the array; everything else returns a new one.\ndeclare metatable<T> T[]: {\n __index: {\n find: (self: T[], test: (value: T, index: number) => boolean) => T | nil,\n findIndex: (self: T[], test: (value: T, index: number) => boolean) => number | nil,\n filter: (self: T[], test: (value: T, index: number) => boolean) => T[],\n map: <U>(self: T[], transform: (value: T, index: number) => U) => U[],\n forEach: (self: T[], visit: (value: T, index: number) => nil) => nil,\n some: (self: T[], test: (value: T, index: number) => boolean) => boolean,\n every: (self: T[], test: (value: T, index: number) => boolean) => boolean,\n reduce: <U>(self: T[], step: (total: U, value: T, index: number) => U, initial: U) => U,\n includes: (self: T[], value: T) => boolean,\n indexOf: (self: T[], value: T, start?: number) => number | nil,\n join: (self: T[], separator?: string) => string,\n concat: (self: T[], ...others: T[][]) => T[],\n slice: (self: T[], start?: number, stop?: number) => T[],\n flat: (self: T[]) => T[],\n reverse: (self: T[]) => T[],\n sort: (self: T[], compare?: (a: T, b: T) => boolean) => T[],\n push: (self: T[], ...args: T[]) => number,\n pop: (self: T[]) => T | nil,\n shift: (self: T[]) => T | nil,\n unshift: (self: T[], ...args: T[]) => number,\n },\n}\n\n-- A table's methods. Unlike JavaScript's `Object.keys`, they answer a\n-- tuple, in the order the table's type wrote its members:\n--\n-- const point = { x: 1, y: \"up\" }\n-- point:keys() -- [\"x\", \"y\"]\n-- point:values() -- [number, string]\n-- point:entries() -- [[\"x\", number], [\"y\", string]]\n--\n-- A Lua table has no order of its own, so the compiler hands its runtime the\n-- keys as the type lists them. An optional member keeps its place, holding\n-- `nil` when it is missing; an indexer's keys follow the named ones. A\n-- member of the table's own with one of these names is what a call reaches,\n-- and a class instance, which has a metatable of its own, has none of them.\ndeclare metatable<T extends {}> T: {\n __index: {\n keys: (self: T) => ObjectKeys<T>,\n values: (self: T) => ObjectValues<T>,\n entries: (self: T) => ObjectEntries<T>,\n },\n}\n";
|
|
1604
|
+
declare const PRELUDE_SOURCE = "\n-- In Luau only `nil` and `false` are falsy: `0` and `\"\"` are truthy.\n-- These are what truthiness narrowing computes, made available to write down.\ntype Falsy = nil | false\ntype Truthy<T> = T - Falsy\n\n-- `-` is set difference. Over a union it drops members; over a concrete type\n-- it simplifies away; over an opaque type (`unknown`, an unresolved parameter)\n-- it is kept, so `Exclude<unknown, 1>` stays `unknown - 1`.\ntype Exclude<T, U> = T - U\ntype Extract<T, U> = T extends U ? T : never\ntype NonNullable<T> = T - nil\n\ntype ReturnType<T> = T extends (...args: unknown[]) => infer R ? R : never\ntype Parameters<T> = T extends (...args: infer P) => unknown ? P : never\n\ntype Partial<T> = { [K in keyof T]?: T[K] }\ntype Required<T> = { [K in keyof T]-?: T[K] }\ntype Readonly<T> = { readonly [K in keyof T]: T[K] }\ntype Mutable<T> = { -readonly [K in keyof T]: T[K] }\n\ntype Pick<T, K> = { [P in K]: T[P] }\ntype Omit<T, K> = Pick<T, Exclude<keyof T, K>>\ntype Record<K, V> = { [P in K]: V }\n\n-- ============================================================\n-- The metatables of the language's own values\n-- ============================================================\n\n-- A string's methods. The first group is Lua's string library, which a\n-- string really answers to; where Lua answers several values, tilua answers\n-- one, as the global functions do (`find` is an array, `gsub` the new\n-- string). The second group is written the way JavaScript writes it.\n--\n-- Positions are Lua's: `sub` and `slice` count from 1, and `indexOf` is\n-- `nil` when the text is not there.\ndeclare metatable string: {\n __index: {\n upper: (self: string) => string,\n lower: (self: string) => string,\n len: (self: string) => number,\n sub: (self: string, i: number, j?: number) => string,\n rep: (self: string, n: number, separator?: string) => string,\n reverse: (self: string) => string,\n format: (self: string, ...args: unknown[]) => string,\n byte: ((self: string, i?: number) => number) & ((self: string, i: number, j: number) => number[]),\n find: (self: string, pattern: string, init?: number, plain?: boolean) => [number, number, ...string[]] | nil,\n match: (self: string, pattern: string, init?: number) => [...string[]] | nil,\n gmatch: (self: string, pattern: string) => () => [string, ...string[]] | nil,\n gsub: (self: string, pattern: string, replacement: unknown, n?: number) => string,\n\n trim: (self: string) => string,\n trimStart: (self: string) => string,\n trimEnd: (self: string) => string,\n startsWith: (self: string, text: string) => boolean,\n endsWith: (self: string, text: string) => boolean,\n includes: (self: string, text: string) => boolean,\n indexOf: (self: string, text: string, start?: number) => number | nil,\n slice: (self: string, start?: number, stop?: number) => string,\n replace: (self: string, text: string, replacement: string) => string,\n replaceAll: (self: string, text: string, replacement: string) => string,\n padStart: (self: string, length: number, padding?: string) => string,\n padEnd: (self: string, length: number, padding?: string) => string,\n },\n}\n\n-- An array's methods. Lua gives an array no metatable: the compiler lowers\n-- each call to a plain function, so they work on any array, one a Lua\n-- library made included.\n--\n-- Indices are Lua's: the first element is 1, and `indexOf` / `findIndex`\n-- answer `nil` rather than JavaScript's -1. `sort` takes Lua's comparator\n-- (true when `a` comes first). `push`, `pop`, `shift`, `unshift`,\n-- `sort` and `reverse` change the array; everything else returns a new one.\n-- A `filter` whose test is a guard \u2014 `v is S`, or `v => v ~= nil` \u2014\n-- answers an array of what it let through.\ndeclare metatable<T> T[]: {\n __index: {\n find: (self: T[], test: (value: T, index: number) => boolean) => T | nil,\n findIndex: (self: T[], test: (value: T, index: number) => boolean) => number | nil,\n filter: (<S extends T>(self: T[], test: (value: T, index: number) => value is S) => S[])\n & ((self: T[], test: (value: T, index: number) => boolean) => T[]),\n map: <U>(self: T[], transform: (value: T, index: number) => U) => U[],\n forEach: (self: T[], visit: (value: T, index: number) => nil) => nil,\n some: (self: T[], test: (value: T, index: number) => boolean) => boolean,\n every: (self: T[], test: (value: T, index: number) => boolean) => boolean,\n reduce: <U>(self: T[], step: (total: U, value: T, index: number) => U, initial: U) => U,\n includes: (self: T[], value: T) => boolean,\n indexOf: (self: T[], value: T, start?: number) => number | nil,\n join: (self: T[], separator?: string) => string,\n concat: (self: T[], ...others: T[][]) => T[],\n slice: (self: T[], start?: number, stop?: number) => T[],\n flat: (self: T[]) => T[],\n reverse: (self: T[]) => T[],\n sort: (self: T[], compare?: (a: T, b: T) => boolean) => T[],\n push: (self: T[], ...args: T[]) => number,\n pop: (self: T[]) => T | nil,\n shift: (self: T[]) => T | nil,\n unshift: (self: T[], ...args: T[]) => number,\n },\n}\n\n-- A table's methods. Unlike JavaScript's `Object.keys`, they answer a\n-- tuple, in the order the table's type wrote its members:\n--\n-- const point = { x: 1, y: \"up\" }\n-- point:keys() -- [\"x\", \"y\"]\n-- point:values() -- [number, string]\n-- point:entries() -- [[\"x\", number], [\"y\", string]]\n--\n-- A Lua table has no order of its own, so the compiler hands its runtime the\n-- keys as the type lists them. An optional member keeps its place, holding\n-- `nil` when it is missing; an indexer's keys follow the named ones. A\n-- member of the table's own with one of these names is what a call reaches,\n-- and a class instance, which has a metatable of its own, has none of them.\ndeclare metatable<T extends {}> T: {\n __index: {\n keys: (self: T) => ObjectKeys<T>,\n values: (self: T) => ObjectValues<T>,\n entries: (self: T) => ObjectEntries<T>,\n },\n}\n";
|
|
1605
1605
|
|
|
1606
1606
|
interface ProjectHost {
|
|
1607
1607
|
/** A file's text, or `undefined` when there is no such file. */
|
package/dist/index.d.ts
CHANGED
|
@@ -1064,7 +1064,7 @@ interface ScopeDiagnostic {
|
|
|
1064
1064
|
};
|
|
1065
1065
|
};
|
|
1066
1066
|
message: string;
|
|
1067
|
-
kind: "redeclare" | "const-assign" | "type-only" | "undeclared" | "use-before-define";
|
|
1067
|
+
kind: "redeclare" | "const-assign" | "type-only" | "undeclared" | "use-before-define" | "outside-loop";
|
|
1068
1068
|
}
|
|
1069
1069
|
interface ScopeAnalysis {
|
|
1070
1070
|
/** Every Identifier that appears in a variable *usage* position (i.e.
|
|
@@ -1601,7 +1601,7 @@ resolveModule?: (specifier: string) => ModuleExports | undefined): ModuleExports
|
|
|
1601
1601
|
* `ObjectValues<T>` and `ObjectEntries<T>`, an object's members as tuples in
|
|
1602
1602
|
* the order they were written.
|
|
1603
1603
|
*/
|
|
1604
|
-
declare const PRELUDE_SOURCE = "\n-- In Luau only `nil` and `false` are falsy: `0` and `\"\"` are truthy.\n-- These are what truthiness narrowing computes, made available to write down.\ntype Falsy = nil | false\ntype Truthy<T> = T - Falsy\n\n-- `-` is set difference. Over a union it drops members; over a concrete type\n-- it simplifies away; over an opaque type (`unknown`, an unresolved parameter)\n-- it is kept, so `Exclude<unknown, 1>` stays `unknown - 1`.\ntype Exclude<T, U> = T - U\ntype Extract<T, U> = T extends U ? T : never\ntype NonNullable<T> = T - nil\n\ntype ReturnType<T> = T extends (...args: unknown[]) => infer R ? R : never\ntype Parameters<T> = T extends (...args: infer P) => unknown ? P : never\n\ntype Partial<T> = { [K in keyof T]?: T[K] }\ntype Required<T> = { [K in keyof T]-?: T[K] }\ntype Readonly<T> = { readonly [K in keyof T]: T[K] }\ntype Mutable<T> = { -readonly [K in keyof T]: T[K] }\n\ntype Pick<T, K> = { [P in K]: T[P] }\ntype Omit<T, K> = Pick<T, Exclude<keyof T, K>>\ntype Record<K, V> = { [P in K]: V }\n\n-- ============================================================\n-- The metatables of the language's own values\n-- ============================================================\n\n-- A string's methods. The first group is Lua's string library, which a\n-- string really answers to; where Lua answers several values, tilua answers\n-- one, as the global functions do (`find` is an array, `gsub` the new\n-- string). The second group is written the way JavaScript writes it.\n--\n-- Positions are Lua's: `sub` and `slice` count from 1, and `indexOf` is\n-- `nil` when the text is not there.\ndeclare metatable string: {\n __index: {\n upper: (self: string) => string,\n lower: (self: string) => string,\n len: (self: string) => number,\n sub: (self: string, i: number, j?: number) => string,\n rep: (self: string, n: number, separator?: string) => string,\n reverse: (self: string) => string,\n format: (self: string, ...args: unknown[]) => string,\n byte: ((self: string, i?: number) => number) & ((self: string, i: number, j: number) => number[]),\n find: (self: string, pattern: string, init?: number, plain?: boolean) => [number, number, ...string[]] | nil,\n match: (self: string, pattern: string, init?: number) => [...string[]] | nil,\n gmatch: (self: string, pattern: string) => () => [string, ...string[]] | nil,\n gsub: (self: string, pattern: string, replacement: unknown, n?: number) => string,\n\n trim: (self: string) => string,\n trimStart: (self: string) => string,\n trimEnd: (self: string) => string,\n startsWith: (self: string, text: string) => boolean,\n endsWith: (self: string, text: string) => boolean,\n includes: (self: string, text: string) => boolean,\n indexOf: (self: string, text: string, start?: number) => number | nil,\n slice: (self: string, start?: number, stop?: number) => string,\n replace: (self: string, text: string, replacement: string) => string,\n replaceAll: (self: string, text: string, replacement: string) => string,\n padStart: (self: string, length: number, padding?: string) => string,\n padEnd: (self: string, length: number, padding?: string) => string,\n },\n}\n\n-- An array's methods. Lua gives an array no metatable: the compiler lowers\n-- each call to a plain function, so they work on any array, one a Lua\n-- library made included.\n--\n-- Indices are Lua's: the first element is 1, and `indexOf` / `findIndex`\n-- answer `nil` rather than JavaScript's -1. `sort` takes Lua's comparator\n-- (true when `a` comes first). `push`, `pop`, `shift`, `unshift`,\n-- `sort` and `reverse` change the array; everything else returns a new one.\ndeclare metatable<T> T[]: {\n __index: {\n find: (self: T[], test: (value: T, index: number) => boolean) => T | nil,\n findIndex: (self: T[], test: (value: T, index: number) => boolean) => number | nil,\n filter: (self: T[], test: (value: T, index: number) => boolean) => T[],\n map: <U>(self: T[], transform: (value: T, index: number) => U) => U[],\n forEach: (self: T[], visit: (value: T, index: number) => nil) => nil,\n some: (self: T[], test: (value: T, index: number) => boolean) => boolean,\n every: (self: T[], test: (value: T, index: number) => boolean) => boolean,\n reduce: <U>(self: T[], step: (total: U, value: T, index: number) => U, initial: U) => U,\n includes: (self: T[], value: T) => boolean,\n indexOf: (self: T[], value: T, start?: number) => number | nil,\n join: (self: T[], separator?: string) => string,\n concat: (self: T[], ...others: T[][]) => T[],\n slice: (self: T[], start?: number, stop?: number) => T[],\n flat: (self: T[]) => T[],\n reverse: (self: T[]) => T[],\n sort: (self: T[], compare?: (a: T, b: T) => boolean) => T[],\n push: (self: T[], ...args: T[]) => number,\n pop: (self: T[]) => T | nil,\n shift: (self: T[]) => T | nil,\n unshift: (self: T[], ...args: T[]) => number,\n },\n}\n\n-- A table's methods. Unlike JavaScript's `Object.keys`, they answer a\n-- tuple, in the order the table's type wrote its members:\n--\n-- const point = { x: 1, y: \"up\" }\n-- point:keys() -- [\"x\", \"y\"]\n-- point:values() -- [number, string]\n-- point:entries() -- [[\"x\", number], [\"y\", string]]\n--\n-- A Lua table has no order of its own, so the compiler hands its runtime the\n-- keys as the type lists them. An optional member keeps its place, holding\n-- `nil` when it is missing; an indexer's keys follow the named ones. A\n-- member of the table's own with one of these names is what a call reaches,\n-- and a class instance, which has a metatable of its own, has none of them.\ndeclare metatable<T extends {}> T: {\n __index: {\n keys: (self: T) => ObjectKeys<T>,\n values: (self: T) => ObjectValues<T>,\n entries: (self: T) => ObjectEntries<T>,\n },\n}\n";
|
|
1604
|
+
declare const PRELUDE_SOURCE = "\n-- In Luau only `nil` and `false` are falsy: `0` and `\"\"` are truthy.\n-- These are what truthiness narrowing computes, made available to write down.\ntype Falsy = nil | false\ntype Truthy<T> = T - Falsy\n\n-- `-` is set difference. Over a union it drops members; over a concrete type\n-- it simplifies away; over an opaque type (`unknown`, an unresolved parameter)\n-- it is kept, so `Exclude<unknown, 1>` stays `unknown - 1`.\ntype Exclude<T, U> = T - U\ntype Extract<T, U> = T extends U ? T : never\ntype NonNullable<T> = T - nil\n\ntype ReturnType<T> = T extends (...args: unknown[]) => infer R ? R : never\ntype Parameters<T> = T extends (...args: infer P) => unknown ? P : never\n\ntype Partial<T> = { [K in keyof T]?: T[K] }\ntype Required<T> = { [K in keyof T]-?: T[K] }\ntype Readonly<T> = { readonly [K in keyof T]: T[K] }\ntype Mutable<T> = { -readonly [K in keyof T]: T[K] }\n\ntype Pick<T, K> = { [P in K]: T[P] }\ntype Omit<T, K> = Pick<T, Exclude<keyof T, K>>\ntype Record<K, V> = { [P in K]: V }\n\n-- ============================================================\n-- The metatables of the language's own values\n-- ============================================================\n\n-- A string's methods. The first group is Lua's string library, which a\n-- string really answers to; where Lua answers several values, tilua answers\n-- one, as the global functions do (`find` is an array, `gsub` the new\n-- string). The second group is written the way JavaScript writes it.\n--\n-- Positions are Lua's: `sub` and `slice` count from 1, and `indexOf` is\n-- `nil` when the text is not there.\ndeclare metatable string: {\n __index: {\n upper: (self: string) => string,\n lower: (self: string) => string,\n len: (self: string) => number,\n sub: (self: string, i: number, j?: number) => string,\n rep: (self: string, n: number, separator?: string) => string,\n reverse: (self: string) => string,\n format: (self: string, ...args: unknown[]) => string,\n byte: ((self: string, i?: number) => number) & ((self: string, i: number, j: number) => number[]),\n find: (self: string, pattern: string, init?: number, plain?: boolean) => [number, number, ...string[]] | nil,\n match: (self: string, pattern: string, init?: number) => [...string[]] | nil,\n gmatch: (self: string, pattern: string) => () => [string, ...string[]] | nil,\n gsub: (self: string, pattern: string, replacement: unknown, n?: number) => string,\n\n trim: (self: string) => string,\n trimStart: (self: string) => string,\n trimEnd: (self: string) => string,\n startsWith: (self: string, text: string) => boolean,\n endsWith: (self: string, text: string) => boolean,\n includes: (self: string, text: string) => boolean,\n indexOf: (self: string, text: string, start?: number) => number | nil,\n slice: (self: string, start?: number, stop?: number) => string,\n replace: (self: string, text: string, replacement: string) => string,\n replaceAll: (self: string, text: string, replacement: string) => string,\n padStart: (self: string, length: number, padding?: string) => string,\n padEnd: (self: string, length: number, padding?: string) => string,\n },\n}\n\n-- An array's methods. Lua gives an array no metatable: the compiler lowers\n-- each call to a plain function, so they work on any array, one a Lua\n-- library made included.\n--\n-- Indices are Lua's: the first element is 1, and `indexOf` / `findIndex`\n-- answer `nil` rather than JavaScript's -1. `sort` takes Lua's comparator\n-- (true when `a` comes first). `push`, `pop`, `shift`, `unshift`,\n-- `sort` and `reverse` change the array; everything else returns a new one.\n-- A `filter` whose test is a guard \u2014 `v is S`, or `v => v ~= nil` \u2014\n-- answers an array of what it let through.\ndeclare metatable<T> T[]: {\n __index: {\n find: (self: T[], test: (value: T, index: number) => boolean) => T | nil,\n findIndex: (self: T[], test: (value: T, index: number) => boolean) => number | nil,\n filter: (<S extends T>(self: T[], test: (value: T, index: number) => value is S) => S[])\n & ((self: T[], test: (value: T, index: number) => boolean) => T[]),\n map: <U>(self: T[], transform: (value: T, index: number) => U) => U[],\n forEach: (self: T[], visit: (value: T, index: number) => nil) => nil,\n some: (self: T[], test: (value: T, index: number) => boolean) => boolean,\n every: (self: T[], test: (value: T, index: number) => boolean) => boolean,\n reduce: <U>(self: T[], step: (total: U, value: T, index: number) => U, initial: U) => U,\n includes: (self: T[], value: T) => boolean,\n indexOf: (self: T[], value: T, start?: number) => number | nil,\n join: (self: T[], separator?: string) => string,\n concat: (self: T[], ...others: T[][]) => T[],\n slice: (self: T[], start?: number, stop?: number) => T[],\n flat: (self: T[]) => T[],\n reverse: (self: T[]) => T[],\n sort: (self: T[], compare?: (a: T, b: T) => boolean) => T[],\n push: (self: T[], ...args: T[]) => number,\n pop: (self: T[]) => T | nil,\n shift: (self: T[]) => T | nil,\n unshift: (self: T[], ...args: T[]) => number,\n },\n}\n\n-- A table's methods. Unlike JavaScript's `Object.keys`, they answer a\n-- tuple, in the order the table's type wrote its members:\n--\n-- const point = { x: 1, y: \"up\" }\n-- point:keys() -- [\"x\", \"y\"]\n-- point:values() -- [number, string]\n-- point:entries() -- [[\"x\", number], [\"y\", string]]\n--\n-- A Lua table has no order of its own, so the compiler hands its runtime the\n-- keys as the type lists them. An optional member keeps its place, holding\n-- `nil` when it is missing; an indexer's keys follow the named ones. A\n-- member of the table's own with one of these names is what a call reaches,\n-- and a class instance, which has a metatable of its own, has none of them.\ndeclare metatable<T extends {}> T: {\n __index: {\n keys: (self: T) => ObjectKeys<T>,\n values: (self: T) => ObjectValues<T>,\n entries: (self: T) => ObjectEntries<T>,\n },\n}\n";
|
|
1605
1605
|
|
|
1606
1606
|
interface ProjectHost {
|
|
1607
1607
|
/** A file's text, or `undefined` when there is no such file. */
|
package/dist/index.js
CHANGED
|
@@ -3788,6 +3788,10 @@ var Analyzer = class {
|
|
|
3788
3788
|
moduleScope = this.globalScope;
|
|
3789
3789
|
/** How many function bodies enclose the walk. */
|
|
3790
3790
|
functionDepth = 0;
|
|
3791
|
+
/** How many loops enclose the walk inside the nearest function body. A
|
|
3792
|
+
* function starts again at 0: `continue` in `xs:map(x => ...)` has no
|
|
3793
|
+
* loop to go on with, whatever loop the call is written in. */
|
|
3794
|
+
loopDepth = 0;
|
|
3791
3795
|
/** Function declarations already declared by their block's hoisting, with
|
|
3792
3796
|
* the function depth of that block. */
|
|
3793
3797
|
hoisted = /* @__PURE__ */ new Map();
|
|
@@ -4140,11 +4144,11 @@ var Analyzer = class {
|
|
|
4140
4144
|
return;
|
|
4141
4145
|
case "WhileStatement":
|
|
4142
4146
|
this.visitExpression(stmt.condition, scope);
|
|
4143
|
-
this.visitBlockInNewScope(stmt.body, scope);
|
|
4147
|
+
this.inLoop(() => this.visitBlockInNewScope(stmt.body, scope));
|
|
4144
4148
|
return;
|
|
4145
4149
|
case "RepeatStatement": {
|
|
4146
4150
|
const bodyScope = childScope(scope);
|
|
4147
|
-
this.visitBlock(stmt.body, bodyScope);
|
|
4151
|
+
this.inLoop(() => this.visitBlock(stmt.body, bodyScope));
|
|
4148
4152
|
this.visitExpression(stmt.condition, bodyScope);
|
|
4149
4153
|
return;
|
|
4150
4154
|
}
|
|
@@ -4162,14 +4166,14 @@ var Analyzer = class {
|
|
|
4162
4166
|
if (stmt.step) this.visitExpression(stmt.step, scope);
|
|
4163
4167
|
const bodyScope = childScope(scope);
|
|
4164
4168
|
this.declare(bodyScope, stmt.variable.name, "for-numeric", stmt.variable);
|
|
4165
|
-
this.visitBlock(stmt.body, bodyScope);
|
|
4169
|
+
this.inLoop(() => this.visitBlock(stmt.body, bodyScope));
|
|
4166
4170
|
return;
|
|
4167
4171
|
}
|
|
4168
4172
|
case "GenericForStatement": {
|
|
4169
4173
|
this.visitExpression(stmt.iterator, scope);
|
|
4170
4174
|
const bodyScope = childScope(scope);
|
|
4171
4175
|
this.declarePattern(bodyScope, stmt.variable, "for-generic", scope, stmt.kind === "const");
|
|
4172
|
-
this.visitBlock(stmt.body, bodyScope);
|
|
4176
|
+
this.inLoop(() => this.visitBlock(stmt.body, bodyScope));
|
|
4173
4177
|
return;
|
|
4174
4178
|
}
|
|
4175
4179
|
case "ReturnStatement":
|
|
@@ -4177,6 +4181,15 @@ var Analyzer = class {
|
|
|
4177
4181
|
return;
|
|
4178
4182
|
case "BreakStatement":
|
|
4179
4183
|
case "ContinueStatement":
|
|
4184
|
+
if (this.loopDepth === 0) {
|
|
4185
|
+
const word = stmt.type === "BreakStatement" ? "break" : "continue";
|
|
4186
|
+
this.diagnostics.push({
|
|
4187
|
+
node: stmt,
|
|
4188
|
+
message: this.functionDepth > 0 ? `'${word}' is not inside a loop of this function; to skip a value in a callback, 'return' from it` : `'${word}' is not inside a loop`,
|
|
4189
|
+
kind: "outside-loop"
|
|
4190
|
+
});
|
|
4191
|
+
}
|
|
4192
|
+
return;
|
|
4180
4193
|
case "ErrorStatement":
|
|
4181
4194
|
return;
|
|
4182
4195
|
case "DeclareStatement":
|
|
@@ -4232,6 +4245,15 @@ var Analyzer = class {
|
|
|
4232
4245
|
return;
|
|
4233
4246
|
}
|
|
4234
4247
|
}
|
|
4248
|
+
/** Visit a loop's body, where `break` and `continue` have a loop. */
|
|
4249
|
+
inLoop(visit) {
|
|
4250
|
+
this.loopDepth++;
|
|
4251
|
+
try {
|
|
4252
|
+
visit();
|
|
4253
|
+
} finally {
|
|
4254
|
+
this.loopDepth--;
|
|
4255
|
+
}
|
|
4256
|
+
}
|
|
4235
4257
|
// ---------------- functions ----------------
|
|
4236
4258
|
visitFunctionBody(func, outerScope, isMethod = false) {
|
|
4237
4259
|
const fnScope = childScope(outerScope);
|
|
@@ -4247,11 +4269,14 @@ var Analyzer = class {
|
|
|
4247
4269
|
}
|
|
4248
4270
|
});
|
|
4249
4271
|
this.visitType(func.returnType, fnScope);
|
|
4272
|
+
const loops = this.loopDepth;
|
|
4250
4273
|
this.functionDepth++;
|
|
4274
|
+
this.loopDepth = 0;
|
|
4251
4275
|
try {
|
|
4252
4276
|
this.visitBlock(func.body, fnScope);
|
|
4253
4277
|
} finally {
|
|
4254
4278
|
this.functionDepth--;
|
|
4279
|
+
this.loopDepth = loops;
|
|
4255
4280
|
}
|
|
4256
4281
|
}
|
|
4257
4282
|
/** An overload signature: no body and no bindings, but its types can hold
|
|
@@ -4505,11 +4530,14 @@ declare metatable string: {
|
|
|
4505
4530
|
-- answer \`nil\` rather than JavaScript's -1. \`sort\` takes Lua's comparator
|
|
4506
4531
|
-- (true when \`a\` comes first). \`push\`, \`pop\`, \`shift\`, \`unshift\`,
|
|
4507
4532
|
-- \`sort\` and \`reverse\` change the array; everything else returns a new one.
|
|
4533
|
+
-- A \`filter\` whose test is a guard \u2014 \`v is S\`, or \`v => v ~= nil\` \u2014
|
|
4534
|
+
-- answers an array of what it let through.
|
|
4508
4535
|
declare metatable<T> T[]: {
|
|
4509
4536
|
__index: {
|
|
4510
4537
|
find: (self: T[], test: (value: T, index: number) => boolean) => T | nil,
|
|
4511
4538
|
findIndex: (self: T[], test: (value: T, index: number) => boolean) => number | nil,
|
|
4512
|
-
filter: (self: T[], test: (value: T, index: number) =>
|
|
4539
|
+
filter: (<S extends T>(self: T[], test: (value: T, index: number) => value is S) => S[])
|
|
4540
|
+
& ((self: T[], test: (value: T, index: number) => boolean) => T[]),
|
|
4513
4541
|
map: <U>(self: T[], transform: (value: T, index: number) => U) => U[],
|
|
4514
4542
|
forEach: (self: T[], visit: (value: T, index: number) => nil) => nil,
|
|
4515
4543
|
some: (self: T[], test: (value: T, index: number) => boolean) => boolean,
|
|
@@ -4648,8 +4676,14 @@ function substitute(t, subst) {
|
|
|
4648
4676
|
if (subst.size === 0) return t;
|
|
4649
4677
|
if (!containsTypeParam(t)) return t;
|
|
4650
4678
|
switch (t.kind) {
|
|
4651
|
-
case "typeParam":
|
|
4652
|
-
|
|
4679
|
+
case "typeParam": {
|
|
4680
|
+
const bound = subst.get(t.name);
|
|
4681
|
+
if (bound) return bound;
|
|
4682
|
+
if (t.constraint && containsTypeParam(t.constraint)) {
|
|
4683
|
+
return { ...t, constraint: substitute(t.constraint, new Map([...subst, [t.name, t]])) };
|
|
4684
|
+
}
|
|
4685
|
+
return t;
|
|
4686
|
+
}
|
|
4653
4687
|
case "array":
|
|
4654
4688
|
return { ...arrayOf(substitute(t.element, subst)), readonly: t.readonly };
|
|
4655
4689
|
case "tuple":
|
|
@@ -4794,6 +4828,10 @@ function unify(param, arg, vars, out, how = "union") {
|
|
|
4794
4828
|
if (arg.kind === "function") {
|
|
4795
4829
|
param.params.forEach((p, i) => arg.params[i] && unify(p.type, arg.params[i].type, vars, out));
|
|
4796
4830
|
unify(param.returns, arg.returns, vars, out);
|
|
4831
|
+
const wanted = param.predicate, given = arg.predicate;
|
|
4832
|
+
if (wanted?.type && given?.type && wanted.param === given.param && !wanted.asserts && !given.asserts) {
|
|
4833
|
+
unify(wanted.type, given.type, vars, out);
|
|
4834
|
+
}
|
|
4797
4835
|
}
|
|
4798
4836
|
return;
|
|
4799
4837
|
case "object":
|
|
@@ -5049,6 +5087,11 @@ function isAssignableInner(a, b) {
|
|
|
5049
5087
|
for (let i = 0; i < n; i++) {
|
|
5050
5088
|
if (!isAssignable(a.params[i].type, b.params[i].type) && !isAssignable(b.params[i].type, a.params[i].type)) return false;
|
|
5051
5089
|
}
|
|
5090
|
+
const wanted = b.predicate;
|
|
5091
|
+
if (wanted?.type && !wanted.asserts) {
|
|
5092
|
+
const given = a.predicate;
|
|
5093
|
+
if (!given?.type || given.asserts || given.param !== wanted.param || !isAssignable(given.type, wanted.type)) return false;
|
|
5094
|
+
}
|
|
5052
5095
|
return isAssignable(a.returns, b.returns);
|
|
5053
5096
|
}
|
|
5054
5097
|
if (b.kind === "typeParam") return false;
|
|
@@ -5183,7 +5226,7 @@ function containsFreeTypeParam(t, seen, bound) {
|
|
|
5183
5226
|
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));
|
|
5184
5227
|
case "function": {
|
|
5185
5228
|
const inner = t.typeParams?.length ? /* @__PURE__ */ new Set([...bound, ...t.typeParams]) : bound;
|
|
5186
|
-
return t.params.some((p) => containsTypeParam(p.type, seen, inner)) || !!t.varargs && containsTypeParam(t.varargs, seen, inner) || containsTypeParam(t.returns, seen, inner);
|
|
5229
|
+
return t.params.some((p) => containsTypeParam(p.type, seen, inner)) || !!t.varargs && containsTypeParam(t.varargs, seen, inner) || containsTypeParam(t.returns, seen, inner) || !!t.predicate?.type && containsTypeParam(t.predicate.type, seen, inner);
|
|
5187
5230
|
}
|
|
5188
5231
|
case "genericRef":
|
|
5189
5232
|
return t.typeArguments.some((a) => containsTypeParam(a, seen, bound));
|
|
@@ -5608,9 +5651,14 @@ function isFreshLiteralExpr(e) {
|
|
|
5608
5651
|
case "StringLiteral":
|
|
5609
5652
|
case "BooleanLiteral":
|
|
5610
5653
|
case "InterpolatedStringExpression":
|
|
5654
|
+
return true;
|
|
5655
|
+
// A table or array literal widened the literals written in it as it
|
|
5656
|
+
// was read (`widenUnlessAsked`), and keeps what it read from
|
|
5657
|
+
// elsewhere — `{ key: name }`, `[...names]` — so there is nothing
|
|
5658
|
+
// left for the binding to widen.
|
|
5611
5659
|
case "TableExpression":
|
|
5612
5660
|
case "ArrayExpression":
|
|
5613
|
-
return
|
|
5661
|
+
return false;
|
|
5614
5662
|
case "ParenthesizedExpression":
|
|
5615
5663
|
return isFreshLiteralExpr(e.expression);
|
|
5616
5664
|
case "UnaryExpression":
|
|
@@ -5625,6 +5673,30 @@ function isFreshLiteralExpr(e) {
|
|
|
5625
5673
|
return false;
|
|
5626
5674
|
}
|
|
5627
5675
|
}
|
|
5676
|
+
function overloadTier(f) {
|
|
5677
|
+
const takesGuard = f.params.some((p) => p.type.kind === "function" && p.type.predicate?.type && !p.type.predicate.asserts);
|
|
5678
|
+
if (takesGuard) return 0;
|
|
5679
|
+
return (f.typeParams?.length ?? 0) > 0 ? 2 : 1;
|
|
5680
|
+
}
|
|
5681
|
+
function writesLiteral(e) {
|
|
5682
|
+
switch (e.type) {
|
|
5683
|
+
case "NumberLiteral":
|
|
5684
|
+
case "StringLiteral":
|
|
5685
|
+
case "BooleanLiteral":
|
|
5686
|
+
case "InterpolatedStringExpression":
|
|
5687
|
+
return true;
|
|
5688
|
+
case "ParenthesizedExpression":
|
|
5689
|
+
return writesLiteral(e.expression);
|
|
5690
|
+
case "UnaryExpression":
|
|
5691
|
+
return writesLiteral(e.argument);
|
|
5692
|
+
case "IfElseExpression":
|
|
5693
|
+
return e.clauses.some((c) => writesLiteral(c.body)) || writesLiteral(e.alternate);
|
|
5694
|
+
case "BinaryExpression":
|
|
5695
|
+
return (e.operator === "or" || e.operator === "and") && (writesLiteral(e.left) || writesLiteral(e.right));
|
|
5696
|
+
default:
|
|
5697
|
+
return false;
|
|
5698
|
+
}
|
|
5699
|
+
}
|
|
5628
5700
|
function collectInferNames(node) {
|
|
5629
5701
|
const out = [];
|
|
5630
5702
|
const walk = (n) => {
|
|
@@ -8007,7 +8079,9 @@ var TypeAnalyzer = class {
|
|
|
8007
8079
|
const waits = written.map((a, i) => this.waitsOnInference(a, expected[i]));
|
|
8008
8080
|
if (!waits.some(Boolean)) return written.map((a) => this.infer(a, env));
|
|
8009
8081
|
const argTypes = written.map((a, i) => waits[i] ? this.quietly(() => this.infer(a, env)) : this.infer(a, env));
|
|
8010
|
-
const
|
|
8082
|
+
const spread = this.spreadOf(written, selfOf(fns[0]));
|
|
8083
|
+
const provisional = argTypes.map((t, i) => waits[i] ? anyType : t);
|
|
8084
|
+
const picked = this.pickOverload(fns, argTypes, (f) => extra(f, argTypes), spread) ?? this.pickOverload(fns, provisional, (f) => extra(f, provisional), spread);
|
|
8011
8085
|
const subst = picked?.typeParams?.length ? this.inferTypeArgs(picked, extra(picked, argTypes)) : void 0;
|
|
8012
8086
|
written.forEach((a, i) => {
|
|
8013
8087
|
if (!waits[i]) return;
|
|
@@ -8198,6 +8272,16 @@ var TypeAnalyzer = class {
|
|
|
8198
8272
|
}
|
|
8199
8273
|
const id = this.bindingIdByName(p.name, p);
|
|
8200
8274
|
const t = this.paramType(p, env);
|
|
8275
|
+
if (p.default && p.typeAnnotation) {
|
|
8276
|
+
const given = this.infer(p.default, env);
|
|
8277
|
+
const declared = this.resolveType(p.typeAnnotation);
|
|
8278
|
+
if (this.emitDiagnostics && !isAssignable(given, declared)) {
|
|
8279
|
+
this.diagnostics.push({
|
|
8280
|
+
node: p.default,
|
|
8281
|
+
message: `Type '${briefType(given)}' is not assignable to '${briefType(declared)}'` + this.explainMismatch(given, declared)
|
|
8282
|
+
});
|
|
8283
|
+
}
|
|
8284
|
+
}
|
|
8201
8285
|
if (id !== void 0) {
|
|
8202
8286
|
this.bindingType.set(id, t);
|
|
8203
8287
|
this.setBinding(env, id, t);
|
|
@@ -8295,9 +8379,9 @@ var TypeAnalyzer = class {
|
|
|
8295
8379
|
* lets `typeof` declare `(v: number) -> "number"` alongside a trailing
|
|
8296
8380
|
* `<T>(v: T) -> string` and still pick the precise one. */
|
|
8297
8381
|
pickOverload(fns, argTypes, argsFor, spread) {
|
|
8298
|
-
for (const
|
|
8382
|
+
for (const tier of [0, 1, 2]) {
|
|
8299
8383
|
for (const f of fns) {
|
|
8300
|
-
if ((f
|
|
8384
|
+
if (overloadTier(f) !== tier) continue;
|
|
8301
8385
|
if (this.overloadAccepts(f, argsFor ? argsFor(f) : argTypes, spread)) return f;
|
|
8302
8386
|
}
|
|
8303
8387
|
}
|
|
@@ -8323,7 +8407,7 @@ var TypeAnalyzer = class {
|
|
|
8323
8407
|
if (position < 0) return void 0;
|
|
8324
8408
|
const members = this.expand(argTypes[position]).types;
|
|
8325
8409
|
if (members.length > 32) return void 0;
|
|
8326
|
-
const rank = (f) => (
|
|
8410
|
+
const rank = (f) => overloadTier(f) * fns.length + fns.indexOf(f);
|
|
8327
8411
|
const limit = picked ? rank(picked) : Infinity;
|
|
8328
8412
|
const results = [];
|
|
8329
8413
|
for (const member of members) {
|
|
@@ -8716,10 +8800,38 @@ var TypeAnalyzer = class {
|
|
|
8716
8800
|
returns,
|
|
8717
8801
|
void 0,
|
|
8718
8802
|
names,
|
|
8719
|
-
this.resolvePredicate(func.predicate, params)
|
|
8803
|
+
func.predicate || func.returnType ? this.resolvePredicate(func.predicate, params) : this.inferPredicate(func, params, returns, bodyEnv)
|
|
8720
8804
|
), func.params);
|
|
8721
8805
|
});
|
|
8722
8806
|
}
|
|
8807
|
+
/** `v => v ~= nil` is a guard without saying so, as TypeScript reads it:
|
|
8808
|
+
* a body that is one `return` of a boolean which, true, narrows a
|
|
8809
|
+
* parameter — and, false, leaves it exactly what that excludes. Then
|
|
8810
|
+
* `list:filter(v => v ~= nil)` knows what it kept. */
|
|
8811
|
+
inferPredicate(func, params, returns, env) {
|
|
8812
|
+
const only = func.body.statements.length === 1 ? func.body.statements[0] : void 0;
|
|
8813
|
+
if (only?.type !== "ReturnStatement" || !only.argument) return void 0;
|
|
8814
|
+
if (returns.kind === "any" || returns.kind === "never" || !isAssignable(returns, booleanType)) return void 0;
|
|
8815
|
+
const cond = only.argument;
|
|
8816
|
+
for (let i = 0; i < params.length; i++) {
|
|
8817
|
+
const p = func.params[i];
|
|
8818
|
+
if (p.pattern || p.rest) continue;
|
|
8819
|
+
const id = this.bindingIdByName(p.name, p);
|
|
8820
|
+
if (id === void 0) continue;
|
|
8821
|
+
const declared = params[i].type;
|
|
8822
|
+
const key = bindKey(id);
|
|
8823
|
+
const t = forkEnv(env);
|
|
8824
|
+
const f = forkEnv(env);
|
|
8825
|
+
this.silently(() => this.applyNarrowing(cond, env, t, f));
|
|
8826
|
+
const yes = t.get(key);
|
|
8827
|
+
if (!yes || equalTypes(yes, declared) || !isAssignable(yes, declared)) continue;
|
|
8828
|
+
const no = this.expand(f.get(key) ?? declared);
|
|
8829
|
+
const rest = no.kind === "union" ? no.types : [no];
|
|
8830
|
+
if (rest.some((m) => m.kind !== "never" && isAssignable(m, yes))) continue;
|
|
8831
|
+
return { param: i, type: yes, asserts: false };
|
|
8832
|
+
}
|
|
8833
|
+
return void 0;
|
|
8834
|
+
}
|
|
8723
8835
|
/** Where the return types of the function being walked are collected, so
|
|
8724
8836
|
* each is read where it is written — inside the branch that narrowed it —
|
|
8725
8837
|
* rather than in whatever state the body ends in. */
|
|
@@ -9841,6 +9953,27 @@ var TypeAnalyzer = class {
|
|
|
9841
9953
|
elseEnv = whenFalse;
|
|
9842
9954
|
}
|
|
9843
9955
|
branches.push(this.infer(expr.alternate, elseEnv));
|
|
9956
|
+
const bodies = [...expr.clauses.map((c) => c.body), expr.alternate].map(unwrapParens);
|
|
9957
|
+
const isEmpty = (e) => e.type === "TableExpression" && e.fields.length === 0 || e.type === "ArrayExpression" && e.elements.length === 0;
|
|
9958
|
+
const others = union(branches.filter((_, i) => !isEmpty(bodies[i])));
|
|
9959
|
+
const parts = (others.kind === "union" ? others.types : [others]).map((m) => this.expand(m)).filter((m) => !(m.kind === "primitive" && m.name === "nil"));
|
|
9960
|
+
const fits = (e) => {
|
|
9961
|
+
if (!parts.length) return void 0;
|
|
9962
|
+
if (e.type === "ArrayExpression") {
|
|
9963
|
+
if (parts.every((m) => m.kind === "array")) return others;
|
|
9964
|
+
return parts.every((m) => m.kind === "array" || m.kind === "tuple") ? tuple([]) : void 0;
|
|
9965
|
+
}
|
|
9966
|
+
return parts.every((m) => m.kind === "object" && !m.class && [...m.properties.values()].every((prop) => prop.optional)) ? others : void 0;
|
|
9967
|
+
};
|
|
9968
|
+
if (bodies.some(isEmpty) && others.kind !== "never" && others.kind !== "any" && bodies.every((body) => !isEmpty(body) || fits(body))) {
|
|
9969
|
+
const typed = bodies.map((body, i) => {
|
|
9970
|
+
if (!isEmpty(body)) return branches[i];
|
|
9971
|
+
const t = fits(body);
|
|
9972
|
+
this.typeOf.set(body, t);
|
|
9973
|
+
return t;
|
|
9974
|
+
});
|
|
9975
|
+
return union(typed);
|
|
9976
|
+
}
|
|
9844
9977
|
return union(branches);
|
|
9845
9978
|
}
|
|
9846
9979
|
}
|
|
@@ -10147,23 +10280,87 @@ var TypeAnalyzer = class {
|
|
|
10147
10280
|
if (wantedTuple && !asConst) return this.inferTupleLiteral(expr, env, wantedTuple);
|
|
10148
10281
|
const elems = [];
|
|
10149
10282
|
let hadSpread = false;
|
|
10283
|
+
const written = [];
|
|
10150
10284
|
for (const el of expr.elements) {
|
|
10151
10285
|
if (el.type === "SpreadElement") {
|
|
10152
10286
|
hadSpread = true;
|
|
10153
|
-
const
|
|
10154
|
-
|
|
10155
|
-
|
|
10156
|
-
else elems.push(unknownType);
|
|
10287
|
+
const spread = this.infer(el.argument, env);
|
|
10288
|
+
written.push({ spread: true, type: spread });
|
|
10289
|
+
elems.push(...this.spreadElements(spread));
|
|
10157
10290
|
} else {
|
|
10158
|
-
|
|
10291
|
+
const t = asConst ? this.inferAsConst(el, env) : this.infer(el, env);
|
|
10292
|
+
written.push({ spread: false, type: asConst ? t : this.widenUnlessAsked(t, el) });
|
|
10293
|
+
elems.push(t);
|
|
10159
10294
|
}
|
|
10160
10295
|
}
|
|
10161
10296
|
if (asConst && !hadSpread) return tuple(elems);
|
|
10297
|
+
if (hadSpread) {
|
|
10298
|
+
const tuples = this.spreadTuples(written);
|
|
10299
|
+
if (tuples) return union(tuples.map((elements) => tuple(elements)));
|
|
10300
|
+
}
|
|
10162
10301
|
return arrayOf(elems.length ? union(elems.map((t, i) => {
|
|
10163
10302
|
const element = expr.elements[i];
|
|
10164
10303
|
return asConst || !element || element.type === "SpreadElement" ? t : this.widenUnlessAsked(t, element);
|
|
10165
10304
|
})) : unknownType);
|
|
10166
10305
|
}
|
|
10306
|
+
/** The tuples an array literal with spreads is, when every spread is a
|
|
10307
|
+
* tuple of known length or a union of them: one per way the unions go.
|
|
10308
|
+
* `undefined` when a spread's length is not known (an array, a tuple
|
|
10309
|
+
* with a rest), or the ways multiply past what is worth listing. */
|
|
10310
|
+
spreadTuples(written) {
|
|
10311
|
+
const MAX = 32;
|
|
10312
|
+
let variants = [[]];
|
|
10313
|
+
for (const { spread, type } of written) {
|
|
10314
|
+
if (!spread) {
|
|
10315
|
+
variants = variants.map((v) => [...v, type]);
|
|
10316
|
+
continue;
|
|
10317
|
+
}
|
|
10318
|
+
const options = this.fixedTuples(type);
|
|
10319
|
+
if (!options) return void 0;
|
|
10320
|
+
const next = [];
|
|
10321
|
+
for (const v of variants) for (const option of options) next.push([...v, ...option]);
|
|
10322
|
+
if (next.length > MAX) return void 0;
|
|
10323
|
+
variants = next;
|
|
10324
|
+
}
|
|
10325
|
+
return variants;
|
|
10326
|
+
}
|
|
10327
|
+
/** A tuple of known length's members, or a union of such tuples' — each
|
|
10328
|
+
* a way the value can be. `nil` in a union spreads nothing. */
|
|
10329
|
+
fixedTuples(t, depth = 0) {
|
|
10330
|
+
if (depth > 8) return void 0;
|
|
10331
|
+
const held = this.expand(t);
|
|
10332
|
+
if (held.kind === "tuple") return held.rest ? void 0 : [held.elements];
|
|
10333
|
+
if (held.kind !== "union") return void 0;
|
|
10334
|
+
const out = [];
|
|
10335
|
+
for (const member of held.types) {
|
|
10336
|
+
if (member.kind === "primitive" && member.name === "nil") continue;
|
|
10337
|
+
const options = this.fixedTuples(member, depth + 1);
|
|
10338
|
+
if (!options) return void 0;
|
|
10339
|
+
out.push(...options);
|
|
10340
|
+
}
|
|
10341
|
+
return out.length ? out : void 0;
|
|
10342
|
+
}
|
|
10343
|
+
/** What `...xs` puts into an array: an array's element, a tuple's members,
|
|
10344
|
+
* and for a union each member's — `...(host ? extra : [])` adds `extra`'s
|
|
10345
|
+
* elements and the empty tuple nothing. Anything else, `unknown`. */
|
|
10346
|
+
spreadElements(t, depth = 0) {
|
|
10347
|
+
const held = this.expand(t);
|
|
10348
|
+
if (depth > 8) return [unknownType];
|
|
10349
|
+
switch (held.kind) {
|
|
10350
|
+
case "array":
|
|
10351
|
+
return [held.element];
|
|
10352
|
+
case "tuple":
|
|
10353
|
+
return tupleMembers(held);
|
|
10354
|
+
case "union":
|
|
10355
|
+
return held.types.filter((m) => !(m.kind === "primitive" && m.name === "nil")).flatMap((m) => this.spreadElements(m, depth + 1));
|
|
10356
|
+
case "typeParam":
|
|
10357
|
+
return held.constraint ? this.spreadElements(held.constraint, depth + 1) : [unknownType];
|
|
10358
|
+
case "any":
|
|
10359
|
+
return [anyType];
|
|
10360
|
+
default:
|
|
10361
|
+
return [unknownType];
|
|
10362
|
+
}
|
|
10363
|
+
}
|
|
10167
10364
|
/** `[a, b, ...more]` where a tuple is wanted: the places it names are the
|
|
10168
10365
|
* tuple's, and what comes after them — more elements, or a spread — is
|
|
10169
10366
|
* its rest. */
|
|
@@ -10174,10 +10371,7 @@ var TypeAnalyzer = class {
|
|
|
10174
10371
|
expr.elements.forEach((element, i) => {
|
|
10175
10372
|
if (element.type === "SpreadElement") {
|
|
10176
10373
|
spread = true;
|
|
10177
|
-
|
|
10178
|
-
if (held.kind === "array") tail.push(held.element);
|
|
10179
|
-
else if (held.kind === "tuple") tail.push(...tupleMembers(held));
|
|
10180
|
-
else tail.push(unknownType);
|
|
10374
|
+
tail.push(...this.spreadElements(this.infer(element.argument, env)));
|
|
10181
10375
|
return;
|
|
10182
10376
|
}
|
|
10183
10377
|
const t = this.widenUnlessAsked(this.infer(element, env), element);
|
|
@@ -10194,6 +10388,7 @@ var TypeAnalyzer = class {
|
|
|
10194
10388
|
* The context was recorded by `applyContext` before the value was
|
|
10195
10389
|
* inferred, so this is a lookup rather than a second pass. */
|
|
10196
10390
|
widenUnlessAsked(value, at) {
|
|
10391
|
+
if (!writesLiteral(at)) return value;
|
|
10197
10392
|
const wanted = this.expectedTypeOf.get(at);
|
|
10198
10393
|
if (value.kind === "literal") {
|
|
10199
10394
|
const arms = this.contextArms.get(at);
|