@tilua/parser 2.4.0 → 3.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 +45 -23
- package/dist/index.cjs +312 -44
- package/dist/index.d.cts +70 -22
- package/dist/index.d.ts +70 -22
- package/dist/index.js +312 -44
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -260,22 +260,51 @@ brings in names that are types and nothing else: unlike TypeScript, using one
|
|
|
260
260
|
as a value is an error, and only type positions — `typeof A` included — may
|
|
261
261
|
name it. Compiled code keeps no trace of it.
|
|
262
262
|
|
|
263
|
-
**
|
|
264
|
-
|
|
263
|
+
**Methods of strings, arrays and tables** — a value answers to the methods
|
|
264
|
+
its metatable's `__index` has, written with `:`:
|
|
265
265
|
|
|
266
266
|
```luau
|
|
267
267
|
const long = names:filter(n => #n > 3):map(string.upper)
|
|
268
|
-
|
|
269
|
-
|
|
268
|
+
print(names:join(", "), text:trim(), text:upper())
|
|
269
|
+
const point = { x: 1, y: "up" }
|
|
270
|
+
const [x, y] = point:values() -- [number, string]
|
|
270
271
|
```
|
|
271
272
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
273
|
+
Strings, arrays and tables are the language's own, so their metatables are
|
|
274
|
+
too: the prelude declares them, in tilua, and they are there with no type
|
|
275
|
+
library at all. A string's is Lua's (`upper`, `sub`, with one value where Lua
|
|
276
|
+
answers several: `find` is an array) plus the JavaScript-shaped ones (`trim`,
|
|
277
|
+
`startsWith`, ...); an array's is JavaScript's (`filter`, `map`, `join`, ...),
|
|
278
|
+
with Lua's indices — the first element is 1, `indexOf` answers `nil` rather
|
|
279
|
+
than -1 — and `push`, `pop`, `shift`, `unshift`, `sort` and `reverse` change
|
|
280
|
+
the array. A table's are `keys`, `values` and `entries`, which answer a tuple
|
|
281
|
+
in the order the table's type wrote its members, not `string[]` as
|
|
282
|
+
JavaScript's `Object.keys` does; the analyzer supplies that order as
|
|
283
|
+
`ObjectKeys<T>`, `ObjectValues<T>` and `ObjectEntries<T>`. A member of a
|
|
284
|
+
value's own with the same name is what a call reaches, and a class instance,
|
|
285
|
+
which has a metatable of its own, has none of them.
|
|
286
|
+
|
|
287
|
+
No Lua array or table really has these methods, so the compiler lowers each
|
|
288
|
+
call into its own runtime (`names:filter(f)` becomes `array.filter(names, f)`);
|
|
289
|
+
a string's Lua methods stay plain method calls.
|
|
290
|
+
|
|
291
|
+
A library adds to a metatable by declaring the same target again, and a
|
|
292
|
+
metatable can be declared for any type:
|
|
276
293
|
|
|
277
|
-
|
|
278
|
-
|
|
294
|
+
```luau
|
|
295
|
+
declare metatable string: { __index: { split: (self: string, separator?: string) => string[] } }
|
|
296
|
+
declare metatable<T> T[]: { __index: { first: (self: T[]) => T | nil } }
|
|
297
|
+
declare metatable<T extends {}> T: { __index: { size: (self: T) => number } }
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
`<T> T[]` is every array, `T` what it holds; `<T extends C> T` every value
|
|
301
|
+
that is a `C`, `T` the value itself; a target with no parameters, every value
|
|
302
|
+
of that type. The most specific target a value matches is its metatable.
|
|
303
|
+
|
|
304
|
+
A method a library's metatable gives may be one the value really has, as
|
|
305
|
+
Luau's `split` is. When it is not, the library says how it runs. It points at
|
|
306
|
+
a JavaScript module in its package.json, and the compiler asks it — and only
|
|
307
|
+
it — what a call through a metatable it declared becomes:
|
|
279
308
|
|
|
280
309
|
```json
|
|
281
310
|
"tilua": { "types": "index.d.tilua", "lowering": "lowering.mjs" }
|
|
@@ -285,11 +314,9 @@ module in its package.json, and the compiler asks it what a call becomes:
|
|
|
285
314
|
// @ts-check
|
|
286
315
|
/** @type {import("@tilua/parser").LoweringPlugin} */ // the contract, declared here
|
|
287
316
|
const plugin = {
|
|
288
|
-
runtime: { array: "local __NAME__ = {}\nfunction __NAME__.
|
|
289
|
-
methodCall({ method,
|
|
290
|
-
if (
|
|
291
|
-
return { callee: `${use("array")}.filter` }
|
|
292
|
-
}
|
|
317
|
+
runtime: { array: "local __NAME__ = {}\nfunction __NAME__.first(t) return t[1] end" },
|
|
318
|
+
methodCall({ method, use }) {
|
|
319
|
+
if (method === "first") return { callee: `${use("array")}.first` }
|
|
293
320
|
return undefined
|
|
294
321
|
},
|
|
295
322
|
}
|
|
@@ -300,7 +327,9 @@ export default plugin
|
|
|
300
327
|
local name that table got — emitted once, at the top of the output, only if a
|
|
301
328
|
call needed it — and the receiver is passed as the call's first argument. An
|
|
302
329
|
answer of `undefined` leaves an ordinary Luau method call, which is what
|
|
303
|
-
`text:
|
|
330
|
+
`text:split(",")` wants, since a Luau string already answers to it. A method
|
|
331
|
+
of a value's own (`console:log`, a member of a declared table) is asked of
|
|
332
|
+
every library, the last loaded first.
|
|
304
333
|
|
|
305
334
|
A call can also carry more than was written. Every hook is told `at`, where
|
|
306
335
|
the call was written (`{ file, line, column }`), and `arguments`: each one's
|
|
@@ -314,13 +343,6 @@ hook is asked about it. A runtime can read `__LINES__`, the bundle's line map
|
|
|
314
343
|
project. `console:log`, `print` and `error` in `@tilua-types/lua` are built on
|
|
315
344
|
these hooks.
|
|
316
345
|
|
|
317
|
-
The compiler lowers the language and nothing else: `filter` appears nowhere in
|
|
318
|
-
it.
|
|
319
|
-
|
|
320
|
-
`@tilua-types/lua` ships the JavaScript-shaped set; there, indices are Luau's (the
|
|
321
|
-
first element is 1, `indexOf` answers `nil` rather than -1) and `push`, `pop`,
|
|
322
|
-
`shift`, `unshift`, `sort` and `reverse` change the array they are called on.
|
|
323
|
-
|
|
324
346
|
**A `(` that starts a line** continues the statement above it, as in Lua and
|
|
325
347
|
in JavaScript — `const v = map[key]` followed by `("A"):upper()` is one
|
|
326
348
|
statement, a call of `map[key]`. tilua says so rather than letting it pass:
|
package/dist/index.cjs
CHANGED
|
@@ -734,7 +734,8 @@ var DECLARATION_STATEMENTS = /* @__PURE__ */ new Set([
|
|
|
734
734
|
"ExportNamedStatement",
|
|
735
735
|
"ExportAllStatement",
|
|
736
736
|
"DeclareStatement",
|
|
737
|
-
"DeclareClassStatement"
|
|
737
|
+
"DeclareClassStatement",
|
|
738
|
+
"DeclareMetatableStatement"
|
|
738
739
|
]);
|
|
739
740
|
function spanFrom(start, end) {
|
|
740
741
|
return {
|
|
@@ -1352,6 +1353,9 @@ var Parser = class {
|
|
|
1352
1353
|
if (p1.type === "Identifier" && p1.value === "class" && this.peek(2).type === "Identifier") {
|
|
1353
1354
|
return this.parseDeclareClassStatement();
|
|
1354
1355
|
}
|
|
1356
|
+
if (p1.type === "Identifier" && p1.value === "metatable" && !this.punctuatorAt(2, ":")) {
|
|
1357
|
+
return this.parseDeclareMetatableStatement();
|
|
1358
|
+
}
|
|
1355
1359
|
if (p1.type === "Identifier" || p1.type === "Keyword" && p1.value === "function") {
|
|
1356
1360
|
return this.parseDeclareStatement();
|
|
1357
1361
|
}
|
|
@@ -1404,6 +1408,17 @@ var Parser = class {
|
|
|
1404
1408
|
}
|
|
1405
1409
|
return tokenIdentifier(first);
|
|
1406
1410
|
}
|
|
1411
|
+
// `declare metatable<T> T[]: { __index: { ... } }`
|
|
1412
|
+
parseDeclareMetatableStatement() {
|
|
1413
|
+
const start = this.current();
|
|
1414
|
+
this.advance();
|
|
1415
|
+
this.advance();
|
|
1416
|
+
const generics = this.checkOperator("<") ? this.parseGenericTypeParameterList() : [];
|
|
1417
|
+
const target = this.parseType();
|
|
1418
|
+
this.expectPunctuator(":");
|
|
1419
|
+
const metatable = this.parseType();
|
|
1420
|
+
return { type: "DeclareMetatableStatement", generics, target, metatable, ...spanFrom(start, this.previous()) };
|
|
1421
|
+
}
|
|
1407
1422
|
// `declare class Name extends Base { member: T, ... }`
|
|
1408
1423
|
parseDeclareClassStatement() {
|
|
1409
1424
|
const start = this.current();
|
|
@@ -2112,6 +2127,14 @@ var Parser = class {
|
|
|
2112
2127
|
parseExpressionStatement() {
|
|
2113
2128
|
const start = this.current();
|
|
2114
2129
|
if (this.checkPunctuator("{") || this.checkPunctuator("[")) {
|
|
2130
|
+
const call = this.tryParse(() => {
|
|
2131
|
+
const errors = this.errors.length;
|
|
2132
|
+
const expression2 = this.parseAtom();
|
|
2133
|
+
if (expression2.type !== "MethodCallExpression" && expression2.type !== "CallExpression") throw new ParseRecover("not a call");
|
|
2134
|
+
if (this.errors.length > errors) throw new ParseRecover("not a call");
|
|
2135
|
+
return expression2;
|
|
2136
|
+
});
|
|
2137
|
+
if (call) return { type: "CallStatement", expression: call, ...spanFrom(start, this.previous()) };
|
|
2115
2138
|
const target = this.checkPunctuator("{") ? this.parseObjectPattern(true) : this.parseArrayPattern(true);
|
|
2116
2139
|
return this.parseAssignmentRest(start, target);
|
|
2117
2140
|
}
|
|
@@ -2433,11 +2456,9 @@ var Parser = class {
|
|
|
2433
2456
|
if (t.type === "Identifier" && t.value === "class") {
|
|
2434
2457
|
return this.parseClassExpression();
|
|
2435
2458
|
}
|
|
2436
|
-
if (t.type === "Punctuator" && t.value === "{") {
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
if (t.type === "Punctuator" && t.value === "[") {
|
|
2440
|
-
return this.parseArrayExpression();
|
|
2459
|
+
if (t.type === "Punctuator" && (t.value === "{" || t.value === "[")) {
|
|
2460
|
+
const literal2 = t.value === "{" ? this.parseTableExpression() : this.parseArrayExpression();
|
|
2461
|
+
return this.checkPunctuator(":") && this.startsMethodCall() ? this.parseSuffixes(literal2) : literal2;
|
|
2441
2462
|
}
|
|
2442
2463
|
if (this.checkType("Identifier") && this.punctuatorAt(1, "=>")) return this.parseArrow();
|
|
2443
2464
|
if (this.checkPunctuator("(") || this.checkOperator("<")) {
|
|
@@ -2514,6 +2535,11 @@ var Parser = class {
|
|
|
2514
2535
|
} else {
|
|
2515
2536
|
this.error("Expected identifier or '('");
|
|
2516
2537
|
}
|
|
2538
|
+
return this.parseSuffixes(base);
|
|
2539
|
+
}
|
|
2540
|
+
/** What follows a prefix expression's base: `.name`, `[key]`, `:m(...)`,
|
|
2541
|
+
* a call, and their optional forms. */
|
|
2542
|
+
parseSuffixes(base) {
|
|
2517
2543
|
while (true) {
|
|
2518
2544
|
if (this.checkPunctuator("?") && this.touchesNext()) {
|
|
2519
2545
|
const next = this.peek(1);
|
|
@@ -4264,6 +4290,11 @@ var Analyzer = class {
|
|
|
4264
4290
|
case "DeclareClassStatement":
|
|
4265
4291
|
this.visitType(stmt.body, scope);
|
|
4266
4292
|
return;
|
|
4293
|
+
case "DeclareMetatableStatement":
|
|
4294
|
+
this.visitGenerics(stmt.generics, scope);
|
|
4295
|
+
this.visitType(stmt.target, scope);
|
|
4296
|
+
this.visitType(stmt.metatable, scope);
|
|
4297
|
+
return;
|
|
4267
4298
|
case "TypeAliasStatement":
|
|
4268
4299
|
case "ExportTypeAliasStatement":
|
|
4269
4300
|
this.visitGenerics(
|
|
@@ -4529,6 +4560,101 @@ type Mutable<T> = { -readonly [K in keyof T]: T[K] }
|
|
|
4529
4560
|
type Pick<T, K> = { [P in K]: T[P] }
|
|
4530
4561
|
type Omit<T, K> = Pick<T, Exclude<keyof T, K>>
|
|
4531
4562
|
type Record<K, V> = { [P in K]: V }
|
|
4563
|
+
|
|
4564
|
+
-- ============================================================
|
|
4565
|
+
-- The metatables of the language's own values
|
|
4566
|
+
-- ============================================================
|
|
4567
|
+
|
|
4568
|
+
-- A string's methods. The first group is Lua's string library, which a
|
|
4569
|
+
-- string really answers to; where Lua answers several values, tilua answers
|
|
4570
|
+
-- one, as the global functions do (\`find\` is an array, \`gsub\` the new
|
|
4571
|
+
-- string). The second group is written the way JavaScript writes it.
|
|
4572
|
+
--
|
|
4573
|
+
-- Positions are Lua's: \`sub\` and \`slice\` count from 1, and \`indexOf\` is
|
|
4574
|
+
-- \`nil\` when the text is not there.
|
|
4575
|
+
declare metatable string: {
|
|
4576
|
+
__index: {
|
|
4577
|
+
upper: (self: string) => string,
|
|
4578
|
+
lower: (self: string) => string,
|
|
4579
|
+
len: (self: string) => number,
|
|
4580
|
+
sub: (self: string, i: number, j?: number) => string,
|
|
4581
|
+
rep: (self: string, n: number, separator?: string) => string,
|
|
4582
|
+
reverse: (self: string) => string,
|
|
4583
|
+
format: (self: string, ...args: unknown[]) => string,
|
|
4584
|
+
byte: ((self: string, i?: number) => number) & ((self: string, i: number, j: number) => number[]),
|
|
4585
|
+
find: (self: string, pattern: string, init?: number, plain?: boolean) => [number, number, ...string[]] | nil,
|
|
4586
|
+
match: (self: string, pattern: string, init?: number) => [...string[]] | nil,
|
|
4587
|
+
gmatch: (self: string, pattern: string) => () => [string, ...string[]] | nil,
|
|
4588
|
+
gsub: (self: string, pattern: string, replacement: unknown, n?: number) => string,
|
|
4589
|
+
|
|
4590
|
+
trim: (self: string) => string,
|
|
4591
|
+
trimStart: (self: string) => string,
|
|
4592
|
+
trimEnd: (self: string) => string,
|
|
4593
|
+
startsWith: (self: string, text: string) => boolean,
|
|
4594
|
+
endsWith: (self: string, text: string) => boolean,
|
|
4595
|
+
includes: (self: string, text: string) => boolean,
|
|
4596
|
+
indexOf: (self: string, text: string, start?: number) => number | nil,
|
|
4597
|
+
slice: (self: string, start?: number, stop?: number) => string,
|
|
4598
|
+
replace: (self: string, text: string, replacement: string) => string,
|
|
4599
|
+
replaceAll: (self: string, text: string, replacement: string) => string,
|
|
4600
|
+
padStart: (self: string, length: number, padding?: string) => string,
|
|
4601
|
+
padEnd: (self: string, length: number, padding?: string) => string,
|
|
4602
|
+
},
|
|
4603
|
+
}
|
|
4604
|
+
|
|
4605
|
+
-- An array's methods. Lua gives an array no metatable: the compiler lowers
|
|
4606
|
+
-- each call to a plain function, so they work on any array, one a Lua
|
|
4607
|
+
-- library made included.
|
|
4608
|
+
--
|
|
4609
|
+
-- Indices are Lua's: the first element is 1, and \`indexOf\` / \`findIndex\`
|
|
4610
|
+
-- answer \`nil\` rather than JavaScript's -1. \`sort\` takes Lua's comparator
|
|
4611
|
+
-- (true when \`a\` comes first). \`push\`, \`pop\`, \`shift\`, \`unshift\`,
|
|
4612
|
+
-- \`sort\` and \`reverse\` change the array; everything else returns a new one.
|
|
4613
|
+
declare metatable<T> T[]: {
|
|
4614
|
+
__index: {
|
|
4615
|
+
find: (self: T[], test: (value: T, index: number) => boolean) => T | nil,
|
|
4616
|
+
findIndex: (self: T[], test: (value: T, index: number) => boolean) => number | nil,
|
|
4617
|
+
filter: (self: T[], test: (value: T, index: number) => boolean) => T[],
|
|
4618
|
+
map: <U>(self: T[], transform: (value: T, index: number) => U) => U[],
|
|
4619
|
+
forEach: (self: T[], visit: (value: T, index: number) => nil) => nil,
|
|
4620
|
+
some: (self: T[], test: (value: T, index: number) => boolean) => boolean,
|
|
4621
|
+
every: (self: T[], test: (value: T, index: number) => boolean) => boolean,
|
|
4622
|
+
reduce: <U>(self: T[], step: (total: U, value: T, index: number) => U, initial: U) => U,
|
|
4623
|
+
includes: (self: T[], value: T) => boolean,
|
|
4624
|
+
indexOf: (self: T[], value: T, start?: number) => number | nil,
|
|
4625
|
+
join: (self: T[], separator?: string) => string,
|
|
4626
|
+
concat: (self: T[], ...others: T[][]) => T[],
|
|
4627
|
+
slice: (self: T[], start?: number, stop?: number) => T[],
|
|
4628
|
+
flat: (self: T[]) => T[],
|
|
4629
|
+
reverse: (self: T[]) => T[],
|
|
4630
|
+
sort: (self: T[], compare?: (a: T, b: T) => boolean) => T[],
|
|
4631
|
+
push: (self: T[], ...args: T[]) => number,
|
|
4632
|
+
pop: (self: T[]) => T | nil,
|
|
4633
|
+
shift: (self: T[]) => T | nil,
|
|
4634
|
+
unshift: (self: T[], ...args: T[]) => number,
|
|
4635
|
+
},
|
|
4636
|
+
}
|
|
4637
|
+
|
|
4638
|
+
-- A table's methods. Unlike JavaScript's \`Object.keys\`, they answer a
|
|
4639
|
+
-- tuple, in the order the table's type wrote its members:
|
|
4640
|
+
--
|
|
4641
|
+
-- const point = { x: 1, y: "up" }
|
|
4642
|
+
-- point:keys() -- ["x", "y"]
|
|
4643
|
+
-- point:values() -- [number, string]
|
|
4644
|
+
-- point:entries() -- [["x", number], ["y", string]]
|
|
4645
|
+
--
|
|
4646
|
+
-- A Lua table has no order of its own, so the compiler hands its runtime the
|
|
4647
|
+
-- keys as the type lists them. An optional member keeps its place, holding
|
|
4648
|
+
-- \`nil\` when it is missing; an indexer's keys follow the named ones. A
|
|
4649
|
+
-- member of the table's own with one of these names is what a call reaches,
|
|
4650
|
+
-- and a class instance, which has a metatable of its own, has none of them.
|
|
4651
|
+
declare metatable<T extends {}> T: {
|
|
4652
|
+
__index: {
|
|
4653
|
+
keys: (self: T) => ObjectKeys<T>,
|
|
4654
|
+
values: (self: T) => ObjectValues<T>,
|
|
4655
|
+
entries: (self: T) => ObjectEntries<T>,
|
|
4656
|
+
},
|
|
4657
|
+
}
|
|
4532
4658
|
`;
|
|
4533
4659
|
var prelude;
|
|
4534
4660
|
function preludeProgram() {
|
|
@@ -5894,6 +6020,10 @@ var TypeAnalyzer = class {
|
|
|
5894
6020
|
typeOfTypeNode = /* @__PURE__ */ new Map();
|
|
5895
6021
|
expectedTypeOf = /* @__PURE__ */ new Map();
|
|
5896
6022
|
loops = /* @__PURE__ */ new Map();
|
|
6023
|
+
/** Every `declare metatable`, libraries first, in declaration order. */
|
|
6024
|
+
metatables = [];
|
|
6025
|
+
metatableCache = /* @__PURE__ */ new WeakMap();
|
|
6026
|
+
methodSources = /* @__PURE__ */ new Map();
|
|
5897
6027
|
/** Public: each alias resolved once (generic aliases keep their params as
|
|
5898
6028
|
* `typeParam` nodes in the body). */
|
|
5899
6029
|
aliases = new LazyMap();
|
|
@@ -5960,6 +6090,9 @@ var TypeAnalyzer = class {
|
|
|
5960
6090
|
this.registerNestedClasses();
|
|
5961
6091
|
this.registerNestedAliases();
|
|
5962
6092
|
for (const lib of this.options.libs ?? []) this.harvestDeclares(lib.body);
|
|
6093
|
+
this.harvestMetatables(preludeProgram().body, { origin: "language" });
|
|
6094
|
+
(this.options.libs ?? []).forEach((lib, i) => this.harvestMetatables(lib.body, { origin: "library", library: i }));
|
|
6095
|
+
this.harvestMetatables(this.program.body, { origin: "program" });
|
|
5963
6096
|
this.registerImportedTypes();
|
|
5964
6097
|
this.resolveAllAliases();
|
|
5965
6098
|
this.harvestDeclares(this.program.body, true);
|
|
@@ -5991,6 +6124,8 @@ var TypeAnalyzer = class {
|
|
|
5991
6124
|
expectedTypeOf: this.expectedTypeOf,
|
|
5992
6125
|
aliases: this.resolveDeferredAliases(),
|
|
5993
6126
|
loops: this.loops,
|
|
6127
|
+
methodSources: this.methodSources,
|
|
6128
|
+
metatableMembers: (type) => this.metatableMembers(type),
|
|
5994
6129
|
diagnostics: this.diagnostics
|
|
5995
6130
|
};
|
|
5996
6131
|
}
|
|
@@ -6048,8 +6183,7 @@ var TypeAnalyzer = class {
|
|
|
6048
6183
|
}
|
|
6049
6184
|
/** `layering` is on for the prelude and for definitions files: a second
|
|
6050
6185
|
* library that declares an alias already declared *adds* to it, the way a
|
|
6051
|
-
* second `declare` of a table's name does
|
|
6052
|
-
* `StringMethods` Luau's `split` without restating Lua's. The file being
|
|
6186
|
+
* second `declare` of a table's name does. The file being
|
|
6053
6187
|
* analysed is not a layer: its own alias replaces what the libraries
|
|
6054
6188
|
* gave, which is how a project opts out of a set. */
|
|
6055
6189
|
registerAliasDefs(block, layering = false) {
|
|
@@ -6967,7 +7101,7 @@ var TypeAnalyzer = class {
|
|
|
6967
7101
|
if (record.type === "TypeReference" && typeof record.base === "string") {
|
|
6968
7102
|
const name = typeof record.namespace === "string" ? `${record.namespace}.${record.base}` : record.base;
|
|
6969
7103
|
const resolved = this.typeOfTypeNode.get(node);
|
|
6970
|
-
const unresolved = resolved?.kind === "genericRef" && resolved.name === name && !this.aliasDefs.has(name) && !this.importedTypes.has(name) && this.options.libTypes?.[name] === void 0 && !
|
|
7104
|
+
const unresolved = resolved?.kind === "genericRef" && resolved.name === name && !this.aliasDefs.has(name) && !this.importedTypes.has(name) && this.options.libTypes?.[name] === void 0 && !INTRINSICS.has(name) && !this.importedNames().has(name.split(".")[0]);
|
|
6971
7105
|
const at = node;
|
|
6972
7106
|
const key = `${at.line.start}:${at.column.start}`;
|
|
6973
7107
|
if (unresolved && !reported.has(key)) {
|
|
@@ -7085,7 +7219,7 @@ var TypeAnalyzer = class {
|
|
|
7085
7219
|
const tp = this.lookupTypeParam(node.base);
|
|
7086
7220
|
if (tp) return typeParam(tp.name, tp.constraint, tp.isConst);
|
|
7087
7221
|
if (node.typeArguments.length === 1 && !this.aliasDefs.has(node.base)) {
|
|
7088
|
-
const intrinsic = this.
|
|
7222
|
+
const intrinsic = this.applyIntrinsic(
|
|
7089
7223
|
node.base,
|
|
7090
7224
|
this.resolveType(node.typeArguments[0])
|
|
7091
7225
|
);
|
|
@@ -7292,7 +7426,7 @@ var TypeAnalyzer = class {
|
|
|
7292
7426
|
return difference(this.reduceType(t.base), this.reduceType(t.excluded));
|
|
7293
7427
|
case "genericRef": {
|
|
7294
7428
|
if (t.typeArguments.length !== 1 || this.aliasDefs.has(t.name)) return t;
|
|
7295
|
-
return this.
|
|
7429
|
+
return this.applyIntrinsic(t.name, t.typeArguments[0]) ?? t;
|
|
7296
7430
|
}
|
|
7297
7431
|
case "conditional":
|
|
7298
7432
|
return this.reduceConditional(t);
|
|
@@ -7405,6 +7539,41 @@ var TypeAnalyzer = class {
|
|
|
7405
7539
|
}
|
|
7406
7540
|
return union(combos.map((c) => literal(c)));
|
|
7407
7541
|
}
|
|
7542
|
+
/** A type function the analyzer supplies, since tilua cannot write it. */
|
|
7543
|
+
applyIntrinsic(name, arg) {
|
|
7544
|
+
return this.applyStringIntrinsic(name, arg) ?? this.applyObjectIntrinsic(name, arg);
|
|
7545
|
+
}
|
|
7546
|
+
/** `ObjectKeys<T>`, `ObjectValues<T>`, `ObjectEntries<T>`: an object's
|
|
7547
|
+
* members as a tuple, in the order they were written — `{ a: 1, b: "x" }`
|
|
7548
|
+
* has keys `["a", "b"]`, values `[number, string]` and entries
|
|
7549
|
+
* `[["a", number], ["b", string]]`. TypeScript answers `string[]` for
|
|
7550
|
+
* `Object.keys`, since its objects have no order; tilua's lowering hands
|
|
7551
|
+
* the runtime the written order, so the tuple is what runs. An indexer's
|
|
7552
|
+
* keys follow the named ones, as the tuple's rest. */
|
|
7553
|
+
applyObjectIntrinsic(name, arg) {
|
|
7554
|
+
if (!OBJECT_INTRINSICS.has(name)) return void 0;
|
|
7555
|
+
const t = this.expand(this.reduceType(arg));
|
|
7556
|
+
if (containsTypeParam(t)) return void 0;
|
|
7557
|
+
if (t.kind === "union") return union(t.types.map((m) => this.applyObjectIntrinsic(name, m) ?? unknownType));
|
|
7558
|
+
const parts = t.kind === "intersection" ? t.types.map((m) => this.expand(m)) : [t];
|
|
7559
|
+
if (!parts.every((m) => m.kind === "object" && !m.class)) return unknownType;
|
|
7560
|
+
const properties = /* @__PURE__ */ new Map();
|
|
7561
|
+
let indexer;
|
|
7562
|
+
for (const part of parts) {
|
|
7563
|
+
for (const [key, property] of part.properties) properties.set(key, property);
|
|
7564
|
+
indexer ??= part.indexer;
|
|
7565
|
+
}
|
|
7566
|
+
const entries = [...properties].map(([key, p]) => [literal(key), p.optional ? optional(p.type) : p.type]);
|
|
7567
|
+
const listed = (elements, rest) => !elements.length && rest ? arrayOf(rest) : tuple(elements, rest);
|
|
7568
|
+
switch (name) {
|
|
7569
|
+
case "ObjectKeys":
|
|
7570
|
+
return listed(entries.map(([key]) => key), indexer?.key);
|
|
7571
|
+
case "ObjectValues":
|
|
7572
|
+
return listed(entries.map(([, value]) => value), indexer?.value);
|
|
7573
|
+
default:
|
|
7574
|
+
return listed(entries.map((entry) => tuple([...entry])), indexer && tuple([indexer.key, indexer.value]));
|
|
7575
|
+
}
|
|
7576
|
+
}
|
|
7408
7577
|
/** TypeScript's four string intrinsics. They cannot be written in tilua —
|
|
7409
7578
|
* there is no character-level type arithmetic — so the analyzer supplies
|
|
7410
7579
|
* them, and only them, as named type functions. */
|
|
@@ -9086,28 +9255,122 @@ var TypeAnalyzer = class {
|
|
|
9086
9255
|
this.resolvingAliases.delete(t.name);
|
|
9087
9256
|
}
|
|
9088
9257
|
}
|
|
9089
|
-
|
|
9090
|
-
|
|
9091
|
-
|
|
9092
|
-
|
|
9093
|
-
|
|
9094
|
-
|
|
9095
|
-
|
|
9096
|
-
|
|
9097
|
-
|
|
9098
|
-
|
|
9099
|
-
|
|
9100
|
-
|
|
9101
|
-
|
|
9102
|
-
|
|
9103
|
-
|
|
9104
|
-
|
|
9105
|
-
|
|
9106
|
-
|
|
9107
|
-
|
|
9258
|
+
// --------------------------------------------------------
|
|
9259
|
+
// Metatables
|
|
9260
|
+
// --------------------------------------------------------
|
|
9261
|
+
harvestMetatables(block, source) {
|
|
9262
|
+
for (const stmt of block.statements) {
|
|
9263
|
+
if (stmt.type === "DeclareMetatableStatement") this.metatables.push({ node: stmt, source });
|
|
9264
|
+
}
|
|
9265
|
+
}
|
|
9266
|
+
resolvedMetatable(entry) {
|
|
9267
|
+
entry.resolved ??= this.withTypeParams(entry.node.generics, () => ({
|
|
9268
|
+
target: this.resolveType(entry.node.target),
|
|
9269
|
+
metatable: this.resolveType(entry.node.metatable)
|
|
9270
|
+
}));
|
|
9271
|
+
return entry.resolved;
|
|
9272
|
+
}
|
|
9273
|
+
/** Does a value (`whole`, whose union members are `parts`) have the
|
|
9274
|
+
* metatable `entry` declares — and if so how specifically, and what its
|
|
9275
|
+
* type parameters stand for there. Three kinds of target:
|
|
9276
|
+
*
|
|
9277
|
+
* - `<T> T[]`: every array and tuple, `T` what they hold;
|
|
9278
|
+
* - `<T extends C> T`: every value that is a `C` other than a class
|
|
9279
|
+
* instance, which has a metatable of its own; `T` the value itself;
|
|
9280
|
+
* - a type with no parameters (`string`): every value that is one.
|
|
9281
|
+
*
|
|
9282
|
+
* A bare parameter matches the most, so it is the least specific: an
|
|
9283
|
+
* array that is also a `{}` takes an array's metatable. */
|
|
9284
|
+
matchMetatable(entry, whole, parts) {
|
|
9285
|
+
if (!parts.length || parts.some((m) => m.kind === "any" || m.kind === "unknown" || m.kind === "never")) return void 0;
|
|
9286
|
+
const { target } = this.resolvedMetatable(entry);
|
|
9287
|
+
if (target.kind === "array" && target.element.kind === "typeParam") {
|
|
9288
|
+
const elements = parts.map((m) => m.kind === "array" ? m.element : m.kind === "tuple" ? union(tupleMembers(m)) : void 0);
|
|
9289
|
+
if (!elements.every((e) => e !== void 0)) return void 0;
|
|
9290
|
+
return { specificity: 2, bindings: /* @__PURE__ */ new Map([[target.element.name, union(elements)]]) };
|
|
9291
|
+
}
|
|
9292
|
+
if (target.kind === "typeParam") {
|
|
9293
|
+
const constraint = target.constraint;
|
|
9294
|
+
if (parts.some((m) => m.kind === "object" && m.class)) return void 0;
|
|
9295
|
+
if (constraint && !parts.every((m) => isAssignable(m, constraint))) return void 0;
|
|
9296
|
+
return { specificity: 1, bindings: /* @__PURE__ */ new Map([[target.name, whole]]) };
|
|
9297
|
+
}
|
|
9298
|
+
if (containsTypeParam(target)) return void 0;
|
|
9299
|
+
return parts.every((m) => isAssignable(m, target)) ? { specificity: 2, bindings: /* @__PURE__ */ new Map() } : void 0;
|
|
9300
|
+
}
|
|
9301
|
+
/** The metatables `t` has: every declaration of the most specific target
|
|
9302
|
+
* it matches, instantiated for it, in declaration order — a later one
|
|
9303
|
+
* adds to an earlier, the way a library layers on another. A type
|
|
9304
|
+
* parameter is read at its constraint. */
|
|
9305
|
+
metatablesOf(t) {
|
|
9306
|
+
if (!this.metatables.length) return [];
|
|
9307
|
+
const cached = this.metatableCache.get(t);
|
|
9308
|
+
if (cached) return cached;
|
|
9309
|
+
const expanded = this.expand(t);
|
|
9310
|
+
const parts = (expanded.kind === "union" ? expanded.types : [expanded]).map((m) => this.expand(m)).map((m) => m.kind === "typeParam" && m.constraint ? this.expand(m.constraint) : m);
|
|
9311
|
+
const whole = union(parts);
|
|
9312
|
+
const matches = this.metatables.flatMap((entry) => {
|
|
9313
|
+
const match = this.matchMetatable(entry, whole, parts);
|
|
9314
|
+
return match ? [{ entry, ...match }] : [];
|
|
9315
|
+
});
|
|
9316
|
+
const best = Math.max(0, ...matches.map((m) => m.specificity));
|
|
9317
|
+
const found = matches.filter((m) => m.specificity === best).map(({ entry, bindings }) => ({
|
|
9318
|
+
type: this.expand(this.reduceType(substitute(this.resolvedMetatable(entry).metatable, bindings))),
|
|
9319
|
+
source: entry.source
|
|
9320
|
+
}));
|
|
9321
|
+
this.metatableCache.set(t, found);
|
|
9322
|
+
return found;
|
|
9323
|
+
}
|
|
9324
|
+
/** The `__index` tables of `t`'s metatables, each with the library that
|
|
9325
|
+
* declared it, in declaration order. */
|
|
9326
|
+
metatableIndexes(t) {
|
|
9327
|
+
return this.metatablesOf(t).flatMap(({ type, source }) => {
|
|
9328
|
+
const parts = type.kind === "intersection" ? type.types.map((m) => this.expand(m)) : [type];
|
|
9329
|
+
return parts.flatMap((part) => {
|
|
9330
|
+
const index = part.kind === "object" ? part.properties.get("__index") : void 0;
|
|
9331
|
+
if (!index) return [];
|
|
9332
|
+
const table = this.expand(index.type);
|
|
9333
|
+
return (table.kind === "intersection" ? table.types.map((m) => this.expand(m)) : [table]).map((m) => ({ table: m, source }));
|
|
9334
|
+
});
|
|
9335
|
+
});
|
|
9336
|
+
}
|
|
9337
|
+
/** `name` read through `t`'s metatable: `text:upper()` is `upper` in the
|
|
9338
|
+
* string metatable's `__index`. The last declaration to give the name
|
|
9339
|
+
* wins. The language's own metatables are declared in the prelude, in
|
|
9340
|
+
* tilua, like any library's; a library adds to them the same way. */
|
|
9341
|
+
metatableMember(t, name) {
|
|
9342
|
+
const indexes = this.metatableIndexes(t);
|
|
9343
|
+
for (let i = indexes.length - 1; i >= 0; i--) {
|
|
9344
|
+
const { table, source } = indexes[i];
|
|
9345
|
+
const property = table.kind === "object" ? table.properties.get(name) : void 0;
|
|
9346
|
+
if (property) return { type: property.type, source };
|
|
9108
9347
|
}
|
|
9109
9348
|
return void 0;
|
|
9110
9349
|
}
|
|
9350
|
+
metatableMethod(t, name) {
|
|
9351
|
+
return this.metatableMember(t, name)?.type;
|
|
9352
|
+
}
|
|
9353
|
+
hasMetatable(t) {
|
|
9354
|
+
return this.metatablesOf(t).length > 0;
|
|
9355
|
+
}
|
|
9356
|
+
metatableMembers(t) {
|
|
9357
|
+
const members = /* @__PURE__ */ new Map();
|
|
9358
|
+
for (const { table } of this.metatableIndexes(t)) {
|
|
9359
|
+
if (table.kind !== "object") continue;
|
|
9360
|
+
for (const [name, property] of table.properties) members.set(name, property.type);
|
|
9361
|
+
}
|
|
9362
|
+
return members;
|
|
9363
|
+
}
|
|
9364
|
+
/** `text:upper()`, `scores:keys()`: the method a value's metatable gives
|
|
9365
|
+
* it, when the value has no member of that name of its own. Called with
|
|
9366
|
+
* `:`, it answers before an object's indexer, since a metatable's
|
|
9367
|
+
* `__index` is only read for a key the table does not hold. */
|
|
9368
|
+
receiverMetatableMember(objType, name) {
|
|
9369
|
+
const t = this.expand(objType);
|
|
9370
|
+
const parts = (t.kind === "union" ? t.types : [t]).map((m) => this.expand(m));
|
|
9371
|
+
if (parts.some((m) => m.kind === "object" && m.properties.has(name))) return void 0;
|
|
9372
|
+
return this.metatableMember(t, name);
|
|
9373
|
+
}
|
|
9111
9374
|
/** The most a deferred type could turn out to be. A conditional is one of
|
|
9112
9375
|
* its branches, and the true branch stands for a member of what was
|
|
9113
9376
|
* tested (`T` in `T extends U ? T : never`); an indexed access reads
|
|
@@ -9173,7 +9436,7 @@ var TypeAnalyzer = class {
|
|
|
9173
9436
|
if (index.kind !== "literal" || typeof index.value !== "string") return void 0;
|
|
9174
9437
|
const parts = this.stringParts(object);
|
|
9175
9438
|
if (!parts) return void 0;
|
|
9176
|
-
const found = parts.map((part) => this.
|
|
9439
|
+
const found = parts.map((part) => this.metatableMethod(part, index.value));
|
|
9177
9440
|
return found.every((t) => t !== void 0) ? union(found) : void 0;
|
|
9178
9441
|
}
|
|
9179
9442
|
/** The members of `t` when every one of them is a string — `"a" | "b"` is
|
|
@@ -9194,13 +9457,13 @@ var TypeAnalyzer = class {
|
|
|
9194
9457
|
* not one of them is a mistake worth reporting, rather than the nil Lua
|
|
9195
9458
|
* would hand back. */
|
|
9196
9459
|
checkStringMember(node, object, key) {
|
|
9197
|
-
if (!this.emitDiagnostics || !this.
|
|
9460
|
+
if (!this.emitDiagnostics || !this.hasMetatable(stringType)) return;
|
|
9198
9461
|
const parts = this.stringParts(object);
|
|
9199
9462
|
if (!parts) return;
|
|
9200
9463
|
const keys = (key.kind === "union" ? key.types : [key]).map((m) => this.expand(m));
|
|
9201
9464
|
if (!keys.length || !keys.every((m) => m.kind === "literal" && typeof m.value === "string")) return;
|
|
9202
9465
|
const names = keys.map((m) => String(m.value));
|
|
9203
|
-
if (names.some((name) => parts.some((part) => this.
|
|
9466
|
+
if (names.some((name) => parts.some((part) => this.metatableMethod(part, name)))) return;
|
|
9204
9467
|
this.diagnostics.push({
|
|
9205
9468
|
node,
|
|
9206
9469
|
message: names.length === 1 ? `'${names[0]}' does not exist on a string` : `'${briefType(key)}' does not name a member of a string`
|
|
@@ -9232,24 +9495,24 @@ var TypeAnalyzer = class {
|
|
|
9232
9495
|
switch (part.kind) {
|
|
9233
9496
|
case "primitive":
|
|
9234
9497
|
if (part.name === "nil") continue;
|
|
9235
|
-
if (part.name === "string" && !this.
|
|
9236
|
-
if (part.name === "string" && this.
|
|
9498
|
+
if (part.name === "string" && !this.hasMetatable(part)) return false;
|
|
9499
|
+
if (part.name === "string" && this.metatableMethod(part, name)) continue;
|
|
9237
9500
|
break;
|
|
9238
9501
|
case "literal":
|
|
9239
9502
|
case "templateLiteral":
|
|
9240
9503
|
if (part.kind === "templateLiteral" || part.base === "string") {
|
|
9241
|
-
if (!this.
|
|
9504
|
+
if (!this.hasMetatable(part)) return false;
|
|
9242
9505
|
}
|
|
9243
|
-
if (this.
|
|
9506
|
+
if (this.metatableMethod(part, name)) continue;
|
|
9244
9507
|
break;
|
|
9245
9508
|
case "array":
|
|
9246
9509
|
case "tuple":
|
|
9247
|
-
if (!this.
|
|
9248
|
-
if (this.
|
|
9510
|
+
if (!this.hasMetatable(part)) return false;
|
|
9511
|
+
if (this.metatableMethod(part, name)) continue;
|
|
9249
9512
|
break;
|
|
9250
9513
|
case "object":
|
|
9251
9514
|
if (part.properties.has(name)) continue;
|
|
9252
|
-
if (part.indexer || part.properties.size === 0 && !part.class || this.
|
|
9515
|
+
if (part.indexer || part.properties.size === 0 && !part.class || this.metatableMethod(part, name)) return false;
|
|
9253
9516
|
break;
|
|
9254
9517
|
default:
|
|
9255
9518
|
return false;
|
|
@@ -9384,10 +9647,10 @@ var TypeAnalyzer = class {
|
|
|
9384
9647
|
if (p) return p.optional ? optional(p.type) : p.type;
|
|
9385
9648
|
if (t.indexer) return t.indexer.value;
|
|
9386
9649
|
}
|
|
9387
|
-
const built = this.
|
|
9650
|
+
const built = this.metatableMethod(t, name);
|
|
9388
9651
|
if (built) return built;
|
|
9389
9652
|
if (t.kind === "union") {
|
|
9390
|
-
const built2 = this.
|
|
9653
|
+
const built2 = this.metatableMethod(t, name);
|
|
9391
9654
|
if (built2) return built2;
|
|
9392
9655
|
return union(t.types.map((m) => this.propertyType(m, name)));
|
|
9393
9656
|
}
|
|
@@ -9788,7 +10051,9 @@ var TypeAnalyzer = class {
|
|
|
9788
10051
|
this.checkPrivateMember(expr.method, objType, expr.method.name);
|
|
9789
10052
|
this.checkStringMember(expr.method, objType, literal(expr.method.name));
|
|
9790
10053
|
this.checkMissingMember(expr.method, objType, expr.method.name);
|
|
9791
|
-
const
|
|
10054
|
+
const fromMetatable = this.receiverMetatableMember(objType, expr.method.name);
|
|
10055
|
+
if (fromMetatable) this.methodSources.set(expr, fromMetatable.source);
|
|
10056
|
+
const method = fromMetatable?.type ?? this.propertyType(objType, expr.method.name);
|
|
9792
10057
|
const united = this.unionSignatures(method);
|
|
9793
10058
|
const fns = united ?? this.overloadsOf(method);
|
|
9794
10059
|
const explicit = this.explicitTypeArguments(expr, fns);
|
|
@@ -11030,6 +11295,8 @@ function containsTypeQuery(node) {
|
|
|
11030
11295
|
return found;
|
|
11031
11296
|
}
|
|
11032
11297
|
var STRING_INTRINSICS = /* @__PURE__ */ new Set(["Uppercase", "Lowercase", "Capitalize", "Uncapitalize"]);
|
|
11298
|
+
var OBJECT_INTRINSICS = /* @__PURE__ */ new Set(["ObjectKeys", "ObjectValues", "ObjectEntries"]);
|
|
11299
|
+
var INTRINSICS = /* @__PURE__ */ new Set([...STRING_INTRINSICS, ...OBJECT_INTRINSICS]);
|
|
11033
11300
|
function briefType(t) {
|
|
11034
11301
|
if (t.kind === "union" && t.types.length > 8) {
|
|
11035
11302
|
const shown = t.types.slice(0, 6).map(formatType).join(" | ");
|
|
@@ -11237,7 +11504,8 @@ function resolveTypeLibraries(config, host = nodeHost) {
|
|
|
11237
11504
|
}
|
|
11238
11505
|
addFile(entryFile);
|
|
11239
11506
|
const lowering = loweringModule(directory, host, problems, config);
|
|
11240
|
-
|
|
11507
|
+
const library = files.findIndex((file) => pathKey(file) === pathKey(entryFile));
|
|
11508
|
+
if (lowering) lowerings.push({ ...lowering, library });
|
|
11241
11509
|
};
|
|
11242
11510
|
for (const entry of config.types) {
|
|
11243
11511
|
const relative = entry.startsWith("./") || entry.startsWith("../") || entry.startsWith("/") || /^[A-Za-z]:[\\/]/.test(entry);
|