@tilua/parser 3.1.0 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -10
- package/dist/index.cjs +310 -33
- package/dist/index.d.cts +12 -3
- package/dist/index.d.ts +12 -3
- package/dist/index.js +310 -33
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -124,14 +124,31 @@ Every problem comes back as `{ file, message, line, column }`, pointing into
|
|
|
124
124
|
the config (or sourcemap) it is about. `host` reads files — pass your own to
|
|
125
125
|
read unsaved editor buffers or to record what was read.
|
|
126
126
|
|
|
127
|
-
`
|
|
128
|
-
|
|
129
|
-
|
|
127
|
+
`typeof v` is the language's own, an operator like `not` and `#`: it needs no
|
|
128
|
+
library, and Lua's `type()` and Luau's `typeof()` functions are declared by
|
|
129
|
+
none. It answers Luau's names — `"nil"`, `"boolean"`, `"number"`, `"string"`,
|
|
130
|
+
`"function"`, `"thread"`, `"buffer"`, `"userdata"` — except for a table, which
|
|
131
|
+
is an `"array"` or an `"object"`. `typeof v == "array"` narrows `v`, and
|
|
132
|
+
`== "table"` is an error: it is never the answer. There is no `typeof(v)`
|
|
133
|
+
function: written that way it is an error, and a parenthesised operand stands
|
|
134
|
+
apart from the operator — `typeof (a or b)`.
|
|
135
|
+
|
|
136
|
+
What a runtime's own values answer is a library's to say, in one alias:
|
|
137
|
+
|
|
138
|
+
```ts
|
|
139
|
+
type TypeofNames = { Vector3: Vector3, Instance: Instance }
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Each property is the name `typeof` gives a value of the property's type; a
|
|
143
|
+
value several fit (a `Part` is an `Instance`) is the first one's.
|
|
144
|
+
`TypeAnalysis.typeofTables` lists each `typeof v` whose `v` is, by its type,
|
|
145
|
+
an array or an object wherever it is a table — what a compiler need not find
|
|
146
|
+
out as the program runs.
|
|
130
147
|
|
|
131
148
|
Libraries stack: a name declared again *adds* to what an earlier library gave
|
|
132
149
|
it — overloads of a function accumulate, and the members of a declared table
|
|
133
|
-
merge. That is how `@tilua-types/roblox` extends Lua's `table`
|
|
134
|
-
restating
|
|
150
|
+
merge. That is how `@tilua-types/roblox` extends Lua's `table` without
|
|
151
|
+
restating it.
|
|
135
152
|
|
|
136
153
|
## The language, in brief
|
|
137
154
|
|
|
@@ -340,7 +357,8 @@ such as `print(x)`, and `globalValue` for a global read as a value, such as
|
|
|
340
357
|
`local p = print`. A local with the same name belongs to the author, and no
|
|
341
358
|
hook is asked about it. A runtime can read `__LINES__`, the bundle's line map
|
|
342
359
|
(`nil` outside a bundle), to turn a line Luau reports back into a place in the
|
|
343
|
-
project.
|
|
360
|
+
project. One that answers with a new array wraps it in `__ARRAY__(...)`, which
|
|
361
|
+
writes it down as an array for `typeof`. `console:log`, `print` and `error` in `@tilua-types/lua` are built on
|
|
344
362
|
these hooks.
|
|
345
363
|
|
|
346
364
|
**A `(` that starts a line** continues the statement above it, as in Lua and
|
|
@@ -383,8 +401,8 @@ declare class Part extends BasePart { Shape: EnumItem }
|
|
|
383
401
|
A `Part` is a `BasePart` and an `Instance` because it extends them. A
|
|
384
402
|
`ReplicatedStorage` is not a `Part`, and no table literal is an `Instance`,
|
|
385
403
|
however alike their members. A class still fits a shape that names members it
|
|
386
|
-
has (`{ Name: string }`). It is not a table, though, so `typeof
|
|
387
|
-
|
|
404
|
+
has (`{ Name: string }`). It is not a table, though, so `typeof part` is
|
|
405
|
+
`"Instance"`, as the library's `TypeofNames` says, not `"object"`. Members are inherited, and a subclass
|
|
388
406
|
may narrow one (`Parent: SomeFolder`).
|
|
389
407
|
|
|
390
408
|
**`class ... { }`** — a class written in code, rather than declared in a
|
|
@@ -804,8 +822,9 @@ three operators: only a call after the `>` makes it type arguments.
|
|
|
804
822
|
|
|
805
823
|
`typeof x` in a type is TypeScript's type query — the type of a value
|
|
806
824
|
(`typeof config`, `typeof config.volume`, `ReturnType<typeof f>`). Luau's
|
|
807
|
-
`typeof(expr)` spelling
|
|
808
|
-
`typeof
|
|
825
|
+
`typeof(expr)` spelling is an error here as well. It is compile-time only, unrelated to the
|
|
826
|
+
`typeof v` operator in an expression, which answers a string as the program
|
|
827
|
+
runs.
|
|
809
828
|
|
|
810
829
|
**Modules** — `import` / `export`, export lists, re-exports and `export *`.
|
|
811
830
|
|
package/dist/index.cjs
CHANGED
|
@@ -711,6 +711,8 @@ var UNUSED_EXPECT_ERROR = "Unused '@tilua-expect-error' directive";
|
|
|
711
711
|
|
|
712
712
|
// src/ast/builders.ts
|
|
713
713
|
var NO_DOTS = "tilua has no bare '...': take a rest parameter, '...args: T[]', or 'scriptArgs' for the script's own";
|
|
714
|
+
var NO_MEMBER_NAME = "Expected a name after '.'";
|
|
715
|
+
var NO_TYPEOF_CALL = "'typeof' is an operator, not a function: write 'typeof v'";
|
|
714
716
|
var ACCESSIBILITIES = /* @__PURE__ */ new Set(["public", "private", "protected"]);
|
|
715
717
|
var CLASS_MODIFIERS = /* @__PURE__ */ new Set([...ACCESSIBILITIES, "static", "abstract", "override", "readonly"]);
|
|
716
718
|
function bindThis(func, at) {
|
|
@@ -2248,6 +2250,7 @@ var Parser = class {
|
|
|
2248
2250
|
* `:` Identifier followed by one of Lua's call forms. */
|
|
2249
2251
|
startsMethodCall(offset = 0) {
|
|
2250
2252
|
if (this.peek(offset + 1).type !== "Identifier") return false;
|
|
2253
|
+
if (this.startsOwnLine(this.peek(offset), this.peek(offset + 1))) return false;
|
|
2251
2254
|
const after = this.peek(offset + 2);
|
|
2252
2255
|
if (after.type === "Punctuator") {
|
|
2253
2256
|
const v = String(after.value);
|
|
@@ -2273,6 +2276,23 @@ var Parser = class {
|
|
|
2273
2276
|
const isString = t.type === "InterpolatedString" || t.type === "Literal" && t.kind === "string";
|
|
2274
2277
|
return isString && this.punctuatorAt(1, ":") && this.startsMethodCall(1);
|
|
2275
2278
|
}
|
|
2279
|
+
/** Is there no member name at `offset`, after the `.` or `:` before it? */
|
|
2280
|
+
namelessAfter(offset) {
|
|
2281
|
+
const name = this.peek(offset);
|
|
2282
|
+
if (name.type !== "Identifier") return true;
|
|
2283
|
+
const before = offset === 0 ? this.previous() : this.peek(offset - 1);
|
|
2284
|
+
return before !== void 0 && this.startsOwnLine(before, name);
|
|
2285
|
+
}
|
|
2286
|
+
/** Recovery only. `obj.` may go on to its name on a later line, as in
|
|
2287
|
+
* JavaScript — but while `obj.` is still being typed, the line below is
|
|
2288
|
+
* whatever statement was already there, and reading it as the name turns
|
|
2289
|
+
* one unfinished line into errors about the next. So, as a missing `}`
|
|
2290
|
+
* is placed, the indentation says which: a line that carries the access
|
|
2291
|
+
* on is indented deeper than the line it carries on from. */
|
|
2292
|
+
startsOwnLine(before, name) {
|
|
2293
|
+
if (!this.recover || name.line.start <= before.line.end) return false;
|
|
2294
|
+
return this.indentOf(name) <= this.indentOf(before);
|
|
2295
|
+
}
|
|
2276
2296
|
/** Does the next token start right where the current one ends? */
|
|
2277
2297
|
touchesNext() {
|
|
2278
2298
|
const current = this.current();
|
|
@@ -2314,6 +2334,7 @@ var Parser = class {
|
|
|
2314
2334
|
const t = this.current();
|
|
2315
2335
|
if (t.type === "Keyword" && t.value === "not") return "not";
|
|
2316
2336
|
if (t.type === "Operator" && (t.value === "-" || t.value === "#")) return t.value;
|
|
2337
|
+
if (t.type === "Identifier" && t.value === "typeof") return "typeof";
|
|
2317
2338
|
return null;
|
|
2318
2339
|
}
|
|
2319
2340
|
/** Runs `fn` inside a bracket, where a `:` can only ever be a method
|
|
@@ -2413,6 +2434,10 @@ var Parser = class {
|
|
|
2413
2434
|
const op = this.isUnaryOperator();
|
|
2414
2435
|
if (op) {
|
|
2415
2436
|
const opTok = this.advance();
|
|
2437
|
+
const next = this.current();
|
|
2438
|
+
if (op === "typeof" && this.checkPunctuator("(") && next.line.start === opTok.line.end && next.column.start === opTok.column.end) {
|
|
2439
|
+
this.problemAt(next, NO_TYPEOF_CALL);
|
|
2440
|
+
}
|
|
2416
2441
|
const argument = this.parseExpression(UNARY_PRECEDENCE);
|
|
2417
2442
|
return {
|
|
2418
2443
|
type: "UnaryExpression",
|
|
@@ -2582,6 +2607,13 @@ var Parser = class {
|
|
|
2582
2607
|
if (this.checkPunctuator("?") && this.touchesNext()) {
|
|
2583
2608
|
const next = this.peek(1);
|
|
2584
2609
|
const punct = next.type === "Punctuator" ? String(next.value) : void 0;
|
|
2610
|
+
if (punct === "." && this.recover && this.namelessAfter(2) && !this.punctuatorAt(2, "[") && !this.punctuatorAt(2, "(")) {
|
|
2611
|
+
this.advance();
|
|
2612
|
+
this.advance();
|
|
2613
|
+
this.problemAt(this.previous(), NO_MEMBER_NAME);
|
|
2614
|
+
base = { type: "ErrorExpression", expression: base, ...spanFrom(base, this.previous()) };
|
|
2615
|
+
break;
|
|
2616
|
+
}
|
|
2585
2617
|
if (punct === "." && this.peek(2).type === "Identifier") {
|
|
2586
2618
|
this.advance();
|
|
2587
2619
|
this.advance();
|
|
@@ -2636,9 +2668,9 @@ var Parser = class {
|
|
|
2636
2668
|
}
|
|
2637
2669
|
}
|
|
2638
2670
|
if (this.matchPunctuator(".")) {
|
|
2639
|
-
if (this.
|
|
2640
|
-
this.
|
|
2641
|
-
base = { type: "ErrorExpression", ...spanFrom(base, this.previous()) };
|
|
2671
|
+
if (this.namelessAfter(0)) {
|
|
2672
|
+
this.problemAt(this.previous(), NO_MEMBER_NAME);
|
|
2673
|
+
base = { type: "ErrorExpression", expression: base, ...spanFrom(base, this.previous()) };
|
|
2642
2674
|
break;
|
|
2643
2675
|
}
|
|
2644
2676
|
const prop = this.parseIdentifier();
|
|
@@ -2651,6 +2683,12 @@ var Parser = class {
|
|
|
2651
2683
|
base = { type: "IndexExpression", object: base, index, ...spanFrom(base, this.previous()) };
|
|
2652
2684
|
continue;
|
|
2653
2685
|
}
|
|
2686
|
+
if (this.recover && !this.ternaryColons && this.checkPunctuator(":") && this.peek(1).line.start > this.current().line.end && !this.startsMethodCall()) {
|
|
2687
|
+
this.advance();
|
|
2688
|
+
this.problemAt(this.previous(), "Expected a name after ':'");
|
|
2689
|
+
base = { type: "ErrorExpression", expression: base, ...spanFrom(base, this.previous()) };
|
|
2690
|
+
break;
|
|
2691
|
+
}
|
|
2654
2692
|
if (this.checkPunctuator(":") && this.startsMethodCall() && this.takeMethodColon()) {
|
|
2655
2693
|
this.advance();
|
|
2656
2694
|
const method = this.parseIdentifier();
|
|
@@ -3494,6 +3532,7 @@ var Parser = class {
|
|
|
3494
3532
|
return { type: "TupleTypeNode", elements, ...rest ? { rest } : {}, ...spanFrom(t, this.previous()) };
|
|
3495
3533
|
}
|
|
3496
3534
|
if (t.type === "Identifier" && t.value === "typeof" && this.peek(1).type === "Punctuator" && this.peek(1).value === "(") {
|
|
3535
|
+
this.problemAt(this.peek(1), NO_TYPEOF_CALL);
|
|
3497
3536
|
this.advance();
|
|
3498
3537
|
this.advance();
|
|
3499
3538
|
const expression = this.parseExpression();
|
|
@@ -3874,7 +3913,7 @@ var BinaryOperators = [
|
|
|
3874
3913
|
"and",
|
|
3875
3914
|
"or"
|
|
3876
3915
|
];
|
|
3877
|
-
var UnaryOperators = ["-", "not", "#"];
|
|
3916
|
+
var UnaryOperators = ["-", "not", "#", "typeof"];
|
|
3878
3917
|
|
|
3879
3918
|
// src/ast/analyzeScopes.ts
|
|
3880
3919
|
function getBinding(analysis, id) {
|
|
@@ -4501,7 +4540,9 @@ var Analyzer = class {
|
|
|
4501
4540
|
case "BooleanLiteral":
|
|
4502
4541
|
case "NumberLiteral":
|
|
4503
4542
|
case "StringLiteral":
|
|
4543
|
+
return;
|
|
4504
4544
|
case "ErrorExpression":
|
|
4545
|
+
if (expr.expression) this.visitExpression(expr.expression, scope);
|
|
4505
4546
|
return;
|
|
4506
4547
|
case "InterpolatedStringExpression":
|
|
4507
4548
|
for (const part of expr.parts) {
|
|
@@ -5807,6 +5848,13 @@ function mergeEnv(a, b, base) {
|
|
|
5807
5848
|
}
|
|
5808
5849
|
return out;
|
|
5809
5850
|
}
|
|
5851
|
+
function joinNarrowings(into, a, aFrom, b, bFrom) {
|
|
5852
|
+
for (const [key, left] of a) {
|
|
5853
|
+
const right = b.get(key);
|
|
5854
|
+
if (right === void 0 || left === aFrom.get(key) || right === bFrom.get(key)) continue;
|
|
5855
|
+
into.set(key, union([left, right]));
|
|
5856
|
+
}
|
|
5857
|
+
}
|
|
5810
5858
|
function mergeIndexer(a, b) {
|
|
5811
5859
|
return a ? { key: union([a.key, b.key]), value: union([a.value, b.value]) } : b;
|
|
5812
5860
|
}
|
|
@@ -6086,6 +6134,10 @@ function unwrapParens(e) {
|
|
|
6086
6134
|
while (e.type === "ParenthesizedExpression") e = e.expression;
|
|
6087
6135
|
return e;
|
|
6088
6136
|
}
|
|
6137
|
+
function memberLabel(label, key) {
|
|
6138
|
+
if (label === void 0 || key === void 0) return void 0;
|
|
6139
|
+
return typeof key === "number" ? `${label}[${key}]` : `${label}.${key}`;
|
|
6140
|
+
}
|
|
6089
6141
|
function expressionLabel(e, depth = 0) {
|
|
6090
6142
|
if (depth > 6) return void 0;
|
|
6091
6143
|
const args = (list) => {
|
|
@@ -6157,6 +6209,7 @@ var TypeAnalyzer = class {
|
|
|
6157
6209
|
metatables = [];
|
|
6158
6210
|
metatableCache = /* @__PURE__ */ new WeakMap();
|
|
6159
6211
|
methodSources = /* @__PURE__ */ new Map();
|
|
6212
|
+
typeofTables = /* @__PURE__ */ new Map();
|
|
6160
6213
|
/** Public: each alias resolved once (generic aliases keep their params as
|
|
6161
6214
|
* `typeParam` nodes in the body). */
|
|
6162
6215
|
aliases = new LazyMap();
|
|
@@ -6235,7 +6288,7 @@ var TypeAnalyzer = class {
|
|
|
6235
6288
|
const given = this.options.globalTypes?.[name];
|
|
6236
6289
|
if (given) this.bindingType.set(id, given);
|
|
6237
6290
|
else if (this.libGlobalTypes.has(name)) this.bindingType.defer(id, () => this.libGlobalTypes.get(name) ?? anyType);
|
|
6238
|
-
else if (LANGUAGE_GLOBALS.includes(name)) this.bindingType.set(id, arrayOf(
|
|
6291
|
+
else if (LANGUAGE_GLOBALS.includes(name)) this.bindingType.set(id, arrayOf(declaredUnknownType));
|
|
6239
6292
|
else this.bindingType.set(id, anyType);
|
|
6240
6293
|
}
|
|
6241
6294
|
setAliasExpander((t) => this.expand(t));
|
|
@@ -6258,6 +6311,7 @@ var TypeAnalyzer = class {
|
|
|
6258
6311
|
aliases: this.resolveDeferredAliases(),
|
|
6259
6312
|
loops: this.loops,
|
|
6260
6313
|
methodSources: this.methodSources,
|
|
6314
|
+
typeofTables: this.typeofTables,
|
|
6261
6315
|
metatableMembers: (type) => this.metatableMembers(type),
|
|
6262
6316
|
diagnostics: this.diagnostics
|
|
6263
6317
|
};
|
|
@@ -7108,7 +7162,12 @@ var TypeAnalyzer = class {
|
|
|
7108
7162
|
this.aliases.defer(name, () => this.classType(cls));
|
|
7109
7163
|
continue;
|
|
7110
7164
|
}
|
|
7111
|
-
if (this.dependsOnTypeQuery(name))
|
|
7165
|
+
if (this.dependsOnTypeQuery(name)) {
|
|
7166
|
+
if (def.runtimeClass) {
|
|
7167
|
+
this.aliases.defer(name, () => this.withTypeParams(def.params, () => this.resolveDef(def)));
|
|
7168
|
+
}
|
|
7169
|
+
continue;
|
|
7170
|
+
}
|
|
7112
7171
|
this.withTypeParams(def.params, () => {
|
|
7113
7172
|
this.aliases.set(name, this.resolveDef(def));
|
|
7114
7173
|
});
|
|
@@ -7116,14 +7175,16 @@ var TypeAnalyzer = class {
|
|
|
7116
7175
|
}
|
|
7117
7176
|
typeQueryDependents = /* @__PURE__ */ new Map();
|
|
7118
7177
|
/** Does alias `name` contain a `typeof`, itself or through an alias it
|
|
7119
|
-
* names?
|
|
7178
|
+
* names? Its parameters count: `class C<K extends Config["key"]>`
|
|
7179
|
+
* resolves the bound along with the class. */
|
|
7120
7180
|
dependsOnTypeQuery(name, visiting = /* @__PURE__ */ new Set()) {
|
|
7121
7181
|
const known = this.typeQueryDependents.get(name);
|
|
7122
7182
|
if (known !== void 0) return known;
|
|
7123
7183
|
const def = this.aliasDefs.get(name);
|
|
7124
7184
|
if (!def || def.class || visiting.has(name)) return false;
|
|
7125
7185
|
visiting.add(name);
|
|
7126
|
-
const
|
|
7186
|
+
const nodes = [def.node, ...def.params.flatMap((p) => [p.constraint, p.default])];
|
|
7187
|
+
const result = nodes.some(containsTypeQuery) || referencedTypeNames(nodes).some((other) => other !== name && this.dependsOnTypeQuery(other, visiting));
|
|
7127
7188
|
visiting.delete(name);
|
|
7128
7189
|
this.typeQueryDependents.set(name, result);
|
|
7129
7190
|
return result;
|
|
@@ -7848,7 +7909,7 @@ var TypeAnalyzer = class {
|
|
|
7848
7909
|
}
|
|
7849
7910
|
}
|
|
7850
7911
|
const mode = this.initIsAsConst(source) ? "asconst" : !isFreshLiteralExpr(source) ? "keep" : stmt.kind === "const" ? "const" : "widen";
|
|
7851
|
-
this.bindPattern(target, inferred, env, mode);
|
|
7912
|
+
this.bindPattern(target, inferred, env, mode, source && expressionLabel(source));
|
|
7852
7913
|
if (stmt.kind === "const") {
|
|
7853
7914
|
this.correlateDestructuring(target, inferred, env);
|
|
7854
7915
|
this.correlateIndexed(target, source, env);
|
|
@@ -7941,9 +8002,10 @@ var TypeAnalyzer = class {
|
|
|
7941
8002
|
} else if (target.type === "MemberExpression" || target.type === "IndexExpression") {
|
|
7942
8003
|
this.infer(target, env);
|
|
7943
8004
|
this.checkReadonlyAssign(target, env);
|
|
8005
|
+
this.checkMemberAssign(stmt, target, vt, env);
|
|
7944
8006
|
this.assignToRef(target, isFreshLiteralExpr(source) ? widen(vt) : vt, env);
|
|
7945
8007
|
} else if (target.type === "ObjectPattern" || target.type === "ArrayPattern") {
|
|
7946
|
-
this.reassignPattern(target, vt, env);
|
|
8008
|
+
this.reassignPattern(target, vt, env, expressionLabel(source));
|
|
7947
8009
|
}
|
|
7948
8010
|
return;
|
|
7949
8011
|
}
|
|
@@ -9309,7 +9371,8 @@ var TypeAnalyzer = class {
|
|
|
9309
9371
|
}
|
|
9310
9372
|
/** Like `bindPattern`, but the leaves are *existing* bindings (a `{a} = t`
|
|
9311
9373
|
* assignment). Updates their flow type; keeps a declared annotation. */
|
|
9312
|
-
reassignPattern(target, valueType, env) {
|
|
9374
|
+
reassignPattern(target, valueType, env, label) {
|
|
9375
|
+
if (target.type === "ObjectPattern" || target.type === "ArrayPattern") valueType = this.destructured(target, valueType, label);
|
|
9313
9376
|
switch (target.type) {
|
|
9314
9377
|
case "IdentifierPattern": {
|
|
9315
9378
|
const id = this.scopes.bindingOf.get(target);
|
|
@@ -9334,21 +9397,43 @@ var TypeAnalyzer = class {
|
|
|
9334
9397
|
for (const p of target.properties) {
|
|
9335
9398
|
const key = !p.computed && p.key.type === "Identifier" ? p.key.name : !p.computed && p.key.type === "StringLiteral" ? p.key.value : void 0;
|
|
9336
9399
|
const pt = key !== void 0 ? this.propertyType(valueType, key) : unknownType;
|
|
9337
|
-
this.reassignPattern(p.value, this.withDefault(pt, p.default, env), env);
|
|
9400
|
+
this.reassignPattern(p.value, this.withDefault(pt, p.default, env), env, memberLabel(label, key));
|
|
9338
9401
|
}
|
|
9339
9402
|
if (target.rest) this.reassignPattern(target.rest, this.withoutKeys(valueType, target.properties), env);
|
|
9340
9403
|
return;
|
|
9341
9404
|
}
|
|
9342
9405
|
case "ArrayPattern": {
|
|
9343
9406
|
target.elements.forEach((el, i) => {
|
|
9344
|
-
if (el) this.reassignPattern(el.value, this.withDefault(this.elementType(valueType, i), el.default, env), env);
|
|
9407
|
+
if (el) this.reassignPattern(el.value, this.withDefault(this.elementType(valueType, i), el.default, env), env, memberLabel(label, i + 1));
|
|
9345
9408
|
});
|
|
9346
9409
|
if (target.rest) this.reassignPattern(target.rest, this.restAfter(valueType, target.elements.length), env);
|
|
9347
9410
|
return;
|
|
9348
9411
|
}
|
|
9349
9412
|
}
|
|
9350
9413
|
}
|
|
9351
|
-
|
|
9414
|
+
destructuredReported = /* @__PURE__ */ new WeakSet();
|
|
9415
|
+
/** What a pattern reads from. `const { a } = v` is `v.a`, and fails where
|
|
9416
|
+
* that would: on a `v` that may be nil, and on one that is `unknown`.
|
|
9417
|
+
* `label` is the value as it would be written — `args[1]` for the inner
|
|
9418
|
+
* pattern of `const [{ a }] = args`. */
|
|
9419
|
+
destructured(pattern, raw, label) {
|
|
9420
|
+
let type = raw;
|
|
9421
|
+
const report = (message) => {
|
|
9422
|
+
if (!this.emitDiagnostics || this.destructuredReported.has(pattern)) return;
|
|
9423
|
+
this.destructuredReported.add(pattern);
|
|
9424
|
+
this.diagnostics.push({ node: pattern, message: `${label === void 0 ? "Object" : `'${label}'`} ${message}` });
|
|
9425
|
+
};
|
|
9426
|
+
if (this.includesNil(type)) {
|
|
9427
|
+
const t = this.expand(type);
|
|
9428
|
+
report(t.kind === "primitive" ? "is nil" : "is possibly nil. Check it first, or give it a default");
|
|
9429
|
+
type = withoutNil(t);
|
|
9430
|
+
}
|
|
9431
|
+
const read = this.expand(type);
|
|
9432
|
+
if (read.kind === "unknown" && read.declared) report("is of type 'unknown'");
|
|
9433
|
+
return type;
|
|
9434
|
+
}
|
|
9435
|
+
bindPattern(target, valueType, env, mode, label) {
|
|
9436
|
+
if (target.type === "ObjectPattern" || target.type === "ArrayPattern") valueType = this.destructured(target, valueType, label);
|
|
9352
9437
|
switch (target.type) {
|
|
9353
9438
|
case "IdentifierPattern": {
|
|
9354
9439
|
const id = this.bindingIdByName(target.name, target);
|
|
@@ -9369,7 +9454,7 @@ var TypeAnalyzer = class {
|
|
|
9369
9454
|
for (const p of target.properties) {
|
|
9370
9455
|
const key = !p.computed && p.key.type === "Identifier" ? p.key.name : !p.computed && p.key.type === "StringLiteral" ? p.key.value : void 0;
|
|
9371
9456
|
const propType = key !== void 0 ? this.propertyType(valueType, key) : unknownType;
|
|
9372
|
-
this.bindPattern(p.value, this.withDefault(propType, p.default, env), env, mode);
|
|
9457
|
+
this.bindPattern(p.value, this.withDefault(propType, p.default, env), env, mode, memberLabel(label, key));
|
|
9373
9458
|
}
|
|
9374
9459
|
if (target.rest) this.bindPattern(target.rest, this.withoutKeys(valueType, target.properties), env, mode);
|
|
9375
9460
|
return;
|
|
@@ -9377,7 +9462,7 @@ var TypeAnalyzer = class {
|
|
|
9377
9462
|
case "ArrayPattern": {
|
|
9378
9463
|
target.elements.forEach((el, i) => {
|
|
9379
9464
|
if (!el) return;
|
|
9380
|
-
this.bindPattern(el.value, this.withDefault(this.elementType(valueType, i), el.default, env), env, mode);
|
|
9465
|
+
this.bindPattern(el.value, this.withDefault(this.elementType(valueType, i), el.default, env), env, mode, memberLabel(label, i + 1));
|
|
9381
9466
|
});
|
|
9382
9467
|
if (target.rest) this.bindPattern(target.rest, this.restAfter(valueType, target.elements.length), env, mode);
|
|
9383
9468
|
return;
|
|
@@ -9942,6 +10027,7 @@ var TypeAnalyzer = class {
|
|
|
9942
10027
|
}
|
|
9943
10028
|
// Broken syntax is reported by the parser; nothing more to say.
|
|
9944
10029
|
case "ErrorExpression":
|
|
10030
|
+
if (expr.expression) this.infer(expr.expression, env);
|
|
9945
10031
|
return anyType;
|
|
9946
10032
|
case "Identifier": {
|
|
9947
10033
|
const id = this.bindingIdOf(expr);
|
|
@@ -10005,6 +10091,14 @@ var TypeAnalyzer = class {
|
|
|
10005
10091
|
this.checkOperands(expr, "#", arg);
|
|
10006
10092
|
return numberType;
|
|
10007
10093
|
}
|
|
10094
|
+
case "typeof": {
|
|
10095
|
+
const names = this.typeofNamesOf(arg);
|
|
10096
|
+
const tables = names && [...names].filter((name) => name === "array" || name === "object");
|
|
10097
|
+
if (tables?.length === 1) this.typeofTables.set(expr, tables[0]);
|
|
10098
|
+
else this.typeofTables.delete(expr);
|
|
10099
|
+
const answers = names?.size ? [...names] : [...this.typeofTypes().keys()];
|
|
10100
|
+
return union(answers.map((name) => literal(name)));
|
|
10101
|
+
}
|
|
10008
10102
|
}
|
|
10009
10103
|
return arg;
|
|
10010
10104
|
}
|
|
@@ -10047,7 +10141,9 @@ var TypeAnalyzer = class {
|
|
|
10047
10141
|
return booleanType;
|
|
10048
10142
|
case "==":
|
|
10049
10143
|
case "~=":
|
|
10050
|
-
this.
|
|
10144
|
+
if (!this.checkTypeofName(expr, expr.left, expr.right) && !this.checkTypeofName(expr, expr.right, expr.left)) {
|
|
10145
|
+
this.checkOverlap(expr, op, l, r);
|
|
10146
|
+
}
|
|
10051
10147
|
return booleanType;
|
|
10052
10148
|
case "+":
|
|
10053
10149
|
case "-":
|
|
@@ -10248,6 +10344,7 @@ var TypeAnalyzer = class {
|
|
|
10248
10344
|
const fromMetatable = this.receiverMetatableMember(objType, expr.method.name);
|
|
10249
10345
|
if (fromMetatable) this.methodSources.set(expr, fromMetatable.source);
|
|
10250
10346
|
const method = fromMetatable?.type ?? this.propertyType(objType, expr.method.name);
|
|
10347
|
+
this.typeOf.set(expr.method, method);
|
|
10251
10348
|
const united = this.unionSignatures(method);
|
|
10252
10349
|
const fns = united ?? this.overloadsOf(method);
|
|
10253
10350
|
const explicit = this.explicitTypeArguments(expr, fns);
|
|
@@ -10388,6 +10485,43 @@ var TypeAnalyzer = class {
|
|
|
10388
10485
|
message: `Cannot assign to '${name}' because it is a read-only property`
|
|
10389
10486
|
});
|
|
10390
10487
|
}
|
|
10488
|
+
/** `t.x = v` against what `x` was declared as. Only a property the
|
|
10489
|
+
* table's type names is checked: a key it does not name is the Lua
|
|
10490
|
+
* table growing, which `checkMissingMember` is not about. */
|
|
10491
|
+
checkMemberAssign(stmt, target, value, env) {
|
|
10492
|
+
if (!this.emitDiagnostics) return;
|
|
10493
|
+
let name;
|
|
10494
|
+
if (target.type === "MemberExpression") {
|
|
10495
|
+
name = target.property.name;
|
|
10496
|
+
} else {
|
|
10497
|
+
const index = this.expand(this.infer(target.index, env));
|
|
10498
|
+
if (index.kind !== "literal" || typeof index.value !== "string") return;
|
|
10499
|
+
name = index.value;
|
|
10500
|
+
}
|
|
10501
|
+
if (target.object.type === "Identifier") {
|
|
10502
|
+
const id = this.bindingIdOf(target.object);
|
|
10503
|
+
if (id !== void 0 && this.scopes.bindings.get(id)?.declaredBy === "namespace") return;
|
|
10504
|
+
}
|
|
10505
|
+
const declared = this.declaredProperty(this.expand(this.infer(target.object, env)), name);
|
|
10506
|
+
if (!declared || declared.kind === "any" || declared.kind === "unknown") return;
|
|
10507
|
+
if (this.fitsAnnotation(stmt.value, declared, value, env)) return;
|
|
10508
|
+
this.diagnostics.push({
|
|
10509
|
+
node: stmt,
|
|
10510
|
+
message: `Type '${briefType(value)}' is not assignable to '${briefType(declared)}'` + this.explainMismatch(value, declared)
|
|
10511
|
+
});
|
|
10512
|
+
}
|
|
10513
|
+
/** The type a property was declared with, where the type names it. */
|
|
10514
|
+
declaredProperty(t, name) {
|
|
10515
|
+
if (t.kind === "object") {
|
|
10516
|
+
const p = t.properties.get(name);
|
|
10517
|
+
return p && (p.optional ? optional(p.type) : p.type);
|
|
10518
|
+
}
|
|
10519
|
+
if (t.kind === "intersection") {
|
|
10520
|
+
const parts = t.types.flatMap((m) => this.declaredProperty(this.expand(m), name) ?? []);
|
|
10521
|
+
return parts.length ? intersection(parts) : void 0;
|
|
10522
|
+
}
|
|
10523
|
+
return void 0;
|
|
10524
|
+
}
|
|
10391
10525
|
isReadonlyProperty(t, name) {
|
|
10392
10526
|
if (t.kind === "object") return t.properties.get(name)?.readonly === true;
|
|
10393
10527
|
if (t.kind === "intersection" || t.kind === "union") {
|
|
@@ -10783,13 +10917,23 @@ var TypeAnalyzer = class {
|
|
|
10783
10917
|
if (cond.type === "BinaryExpression") {
|
|
10784
10918
|
const { operator: op, left, right } = cond;
|
|
10785
10919
|
if (op === "and") {
|
|
10786
|
-
|
|
10787
|
-
|
|
10920
|
+
const before = forkEnv(env);
|
|
10921
|
+
const leftFailed = forkEnv(env);
|
|
10922
|
+
this.applyNarrowing(left, env, t, leftFailed);
|
|
10923
|
+
const between = forkEnv(t);
|
|
10924
|
+
const rightFailed = forkEnv(t);
|
|
10925
|
+
this.applyNarrowing(right, t, t, rightFailed);
|
|
10926
|
+
joinNarrowings(f, leftFailed, before, rightFailed, between);
|
|
10788
10927
|
return;
|
|
10789
10928
|
}
|
|
10790
10929
|
if (op === "or") {
|
|
10791
|
-
|
|
10792
|
-
|
|
10930
|
+
const before = forkEnv(env);
|
|
10931
|
+
const leftHeld = forkEnv(env);
|
|
10932
|
+
this.applyNarrowing(left, env, leftHeld, f);
|
|
10933
|
+
const between = forkEnv(f);
|
|
10934
|
+
const rightHeld = forkEnv(f);
|
|
10935
|
+
this.applyNarrowing(right, f, rightHeld, f);
|
|
10936
|
+
joinNarrowings(t, leftHeld, before, rightHeld, between);
|
|
10793
10937
|
return;
|
|
10794
10938
|
}
|
|
10795
10939
|
if (op === "==" || op === "~=") {
|
|
@@ -10808,11 +10952,13 @@ var TypeAnalyzer = class {
|
|
|
10808
10952
|
this.narrowOptionalLinks(cond, env, t);
|
|
10809
10953
|
}
|
|
10810
10954
|
/** `a == b` / `a ~= b`. Handles, in order: a declaration-driven
|
|
10811
|
-
* `typeof
|
|
10955
|
+
* `typeof x == "..."` test, a literal/`nil` comparison against a
|
|
10812
10956
|
* reference, and a reference-to-reference comparison. */
|
|
10813
10957
|
narrowByComparison(left, right, eq, env, t, f) {
|
|
10814
10958
|
const yes = eq ? t : f;
|
|
10815
10959
|
const no = eq ? f : t;
|
|
10960
|
+
if (this.narrowByTypeof(left, right, yes, no, env)) return;
|
|
10961
|
+
if (this.narrowByTypeof(right, left, yes, no, env)) return;
|
|
10816
10962
|
if (this.narrowByCallResult(left, right, yes, no, env)) return;
|
|
10817
10963
|
if (this.narrowByCallResult(right, left, yes, no, env)) return;
|
|
10818
10964
|
const litOf = (e) => {
|
|
@@ -10839,18 +10985,149 @@ var TypeAnalyzer = class {
|
|
|
10839
10985
|
this.narrowRef(right, env, yes, no, (cur) => ({ yes: narrowTo(cur, lt), no: cur }));
|
|
10840
10986
|
}
|
|
10841
10987
|
}
|
|
10842
|
-
|
|
10843
|
-
|
|
10844
|
-
|
|
10845
|
-
|
|
10846
|
-
|
|
10847
|
-
|
|
10848
|
-
|
|
10849
|
-
|
|
10988
|
+
// --------------------------------------------------------
|
|
10989
|
+
// typeof
|
|
10990
|
+
// --------------------------------------------------------
|
|
10991
|
+
//
|
|
10992
|
+
// `typeof v` answers what Luau's `typeof` does, except for a table: that
|
|
10993
|
+
// is an `"array"` or an `"object"`. The names of a runtime's own values —
|
|
10994
|
+
// Roblox's `"Instance"`, `"Vector3"` — are a library's to give: it
|
|
10995
|
+
// declares `type TypeofNames = { Vector3: Vector3, Instance: Instance }`,
|
|
10996
|
+
// each property the name `typeof` answers for a value of that type. A
|
|
10997
|
+
// value several of them fit is the first one's.
|
|
10998
|
+
typeofTypesCache;
|
|
10999
|
+
/** Every name `typeof` answers, and what a value it answered that for
|
|
11000
|
+
* is — `unknown` where nothing more is known. */
|
|
11001
|
+
typeofTypes() {
|
|
11002
|
+
if (this.typeofTypesCache) return this.typeofTypesCache;
|
|
11003
|
+
const types = /* @__PURE__ */ new Map([
|
|
11004
|
+
["nil", nilType],
|
|
11005
|
+
["boolean", booleanType],
|
|
11006
|
+
["number", numberType],
|
|
11007
|
+
["string", stringType],
|
|
11008
|
+
["function", fn([], declaredUnknownType, declaredUnknownType)],
|
|
11009
|
+
["array", arrayOf(declaredUnknownType)],
|
|
11010
|
+
["object", objectType([], { key: declaredUnknownType, value: declaredUnknownType })],
|
|
11011
|
+
["thread", threadType],
|
|
11012
|
+
["buffer", bufferType],
|
|
11013
|
+
["userdata", unknownType]
|
|
11014
|
+
]);
|
|
11015
|
+
for (const [name, type] of this.typeofLibraryNames()) if (!types.has(name)) types.set(name, type);
|
|
11016
|
+
return this.typeofTypesCache = types;
|
|
11017
|
+
}
|
|
11018
|
+
typeofLibraryCache;
|
|
11019
|
+
typeofLibraryNames() {
|
|
11020
|
+
if (this.typeofLibraryCache) return this.typeofLibraryCache;
|
|
11021
|
+
const declared = this.aliases.get("TypeofNames");
|
|
11022
|
+
const names = declared && this.expand(declared);
|
|
11023
|
+
return this.typeofLibraryCache = names?.kind === "object" ? [...names.properties].map(([name, property]) => [name, property.type]) : [];
|
|
11024
|
+
}
|
|
11025
|
+
/** The names `typeof` can answer for a value of type `raw`, or
|
|
11026
|
+
* `undefined` where the type does not say. */
|
|
11027
|
+
typeofNamesOf(raw, depth = 0) {
|
|
11028
|
+
if (depth > 8) return void 0;
|
|
11029
|
+
const t = this.expand(raw);
|
|
11030
|
+
switch (t.kind) {
|
|
11031
|
+
case "never":
|
|
11032
|
+
return /* @__PURE__ */ new Set();
|
|
11033
|
+
case "primitive":
|
|
11034
|
+
return /* @__PURE__ */ new Set([t.name]);
|
|
11035
|
+
case "literal":
|
|
11036
|
+
return /* @__PURE__ */ new Set([t.base]);
|
|
11037
|
+
case "templateLiteral":
|
|
11038
|
+
return /* @__PURE__ */ new Set(["string"]);
|
|
11039
|
+
case "array":
|
|
11040
|
+
case "tuple":
|
|
11041
|
+
return /* @__PURE__ */ new Set(["array"]);
|
|
11042
|
+
case "function":
|
|
11043
|
+
return /* @__PURE__ */ new Set(["function"]);
|
|
11044
|
+
case "object": {
|
|
11045
|
+
const named = t.class && this.typeofLibraryNames().find(([, type]) => isAssignable(t, type));
|
|
11046
|
+
return /* @__PURE__ */ new Set([named ? named[0] : "object"]);
|
|
11047
|
+
}
|
|
11048
|
+
case "union": {
|
|
11049
|
+
const all = /* @__PURE__ */ new Set();
|
|
11050
|
+
for (const member of t.types) {
|
|
11051
|
+
const names = this.typeofNamesOf(member, depth + 1);
|
|
11052
|
+
if (!names) return void 0;
|
|
11053
|
+
for (const name of names) all.add(name);
|
|
11054
|
+
}
|
|
11055
|
+
return all;
|
|
11056
|
+
}
|
|
11057
|
+
case "intersection": {
|
|
11058
|
+
const known = t.types.map((part) => this.typeofNamesOf(part, depth + 1)).filter((names) => names !== void 0);
|
|
11059
|
+
if (!known.length) return void 0;
|
|
11060
|
+
const common = [...known[0]].filter((name) => known.every((names) => names.has(name)));
|
|
11061
|
+
return new Set(common.length ? common : known[0]);
|
|
11062
|
+
}
|
|
11063
|
+
case "typeParam":
|
|
11064
|
+
return t.constraint ? this.typeofNamesOf(t.constraint, depth + 1) : void 0;
|
|
11065
|
+
case "difference":
|
|
11066
|
+
return this.typeofNamesOf(t.base, depth + 1);
|
|
11067
|
+
default:
|
|
11068
|
+
return void 0;
|
|
11069
|
+
}
|
|
11070
|
+
}
|
|
11071
|
+
/** `typeof v == "table"`: a name `typeof` never answers. Returns true if
|
|
11072
|
+
* the comparison needs no further checking. */
|
|
11073
|
+
checkTypeofName(node, typeofSide, nameSide) {
|
|
11074
|
+
const test = unwrapParens(typeofSide);
|
|
11075
|
+
if (test.type !== "UnaryExpression" || test.operator !== "typeof") return false;
|
|
11076
|
+
const name = this.asLiteral(unwrapParens(nameSide));
|
|
11077
|
+
if (typeof name !== "string") return false;
|
|
11078
|
+
if (name === "nil") return true;
|
|
11079
|
+
if (this.typeofTypes().has(name)) return false;
|
|
11080
|
+
if (this.emitDiagnostics) {
|
|
11081
|
+
this.diagnostics.push({
|
|
11082
|
+
node,
|
|
11083
|
+
message: name === "table" ? `'typeof' never answers "table": a table is an "array" or an "object"` : `'typeof' never answers "${name}"`
|
|
11084
|
+
});
|
|
11085
|
+
}
|
|
11086
|
+
return true;
|
|
11087
|
+
}
|
|
11088
|
+
/** `typeof x == "number"`: `x` is what answers that name where the test
|
|
11089
|
+
* holds, and what does not where it fails. Returns true if `testSide` is
|
|
11090
|
+
* a `typeof` compared with a name. */
|
|
11091
|
+
narrowByTypeof(testSide, nameSide, yes, no, env) {
|
|
11092
|
+
const test = unwrapParens(testSide);
|
|
11093
|
+
if (test.type !== "UnaryExpression" || test.operator !== "typeof") return false;
|
|
11094
|
+
const name = this.asLiteral(unwrapParens(nameSide));
|
|
11095
|
+
if (typeof name !== "string") return false;
|
|
11096
|
+
const answered = this.typeofTypes().get(name);
|
|
11097
|
+
if (!answered) return true;
|
|
11098
|
+
const opaque = answered.kind !== "unknown";
|
|
11099
|
+
const subtracts = opaque && name !== "array" && name !== "object";
|
|
11100
|
+
const members = (cur) => {
|
|
11101
|
+
const t = this.expand(cur);
|
|
11102
|
+
return t.kind === "union" ? t.types.flatMap(members) : [t];
|
|
11103
|
+
};
|
|
11104
|
+
if (this.refKeyOf(test.argument) !== void 0) {
|
|
11105
|
+
this.narrowRef(test.argument, env, yes, no, (cur) => {
|
|
11106
|
+
const kept = [];
|
|
11107
|
+
const dropped = [];
|
|
11108
|
+
for (const member of members(cur)) {
|
|
11109
|
+
const names = this.typeofNamesOf(member);
|
|
11110
|
+
if (!names) {
|
|
11111
|
+
kept.push(opaque ? narrowTo(member, answered) : member);
|
|
11112
|
+
dropped.push(subtracts ? narrowExclude(member, answered) : member);
|
|
11113
|
+
continue;
|
|
11114
|
+
}
|
|
11115
|
+
if (names.has(name)) kept.push(member);
|
|
11116
|
+
if (!(names.size === 1 && names.has(name))) dropped.push(member);
|
|
11117
|
+
}
|
|
11118
|
+
return { yes: union(kept), no: union(dropped) };
|
|
11119
|
+
});
|
|
11120
|
+
}
|
|
11121
|
+
this.narrowOptionalLinks(test.argument, env, name === "nil" ? no : yes);
|
|
11122
|
+
return true;
|
|
11123
|
+
}
|
|
11124
|
+
/** Declaration-driven `kind(x) == "number"`.
|
|
10850
11125
|
*
|
|
10851
|
-
*
|
|
10852
|
-
*
|
|
10853
|
-
*
|
|
11126
|
+
* A function declared as an overload set whose members return
|
|
11127
|
+
* *string-literal* types (`(value: number) => "number"`, ...) says by its
|
|
11128
|
+
* answer what it was given. So for `f(x) == "lit"` we keep the parameter
|
|
11129
|
+
* types of the overloads that can return `"lit"`, and that union is what
|
|
11130
|
+
* `x` narrows to. Returns true if it handled the comparison. */
|
|
10854
11131
|
narrowByCallResult(callSide, litSide, yes, no, env) {
|
|
10855
11132
|
if (callSide.type !== "CallExpression" || callSide.arguments.length !== 1) return false;
|
|
10856
11133
|
const name = this.asLiteral(litSide);
|