luaut-parser 2.1.0 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +148 -15
- package/dist/index.cjs +2105 -358
- package/dist/index.d.cts +162 -18
- package/dist/index.d.ts +162 -18
- package/dist/index.js +2100 -358
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -25,18 +25,22 @@ __export(index_exports, {
|
|
|
25
25
|
Keywords: () => Keywords,
|
|
26
26
|
LexError: () => LexError,
|
|
27
27
|
Operators: () => Operators,
|
|
28
|
+
PRELUDE_SOURCE: () => PRELUDE_SOURCE,
|
|
28
29
|
ParseError: () => ParseError,
|
|
29
30
|
Punctuators: () => Punctuators,
|
|
31
|
+
UNUSED_EXPECT_ERROR: () => UNUSED_EXPECT_ERROR,
|
|
30
32
|
UnaryOperators: () => UnaryOperators,
|
|
31
33
|
analyzeScopes: () => analyzeScopes,
|
|
32
34
|
analyzeTypes: () => analyzeTypes,
|
|
33
35
|
anyType: () => anyType,
|
|
36
|
+
applyDirectives: () => applyDirectives,
|
|
34
37
|
arrayOf: () => arrayOf,
|
|
35
38
|
booleanType: () => booleanType,
|
|
36
39
|
bufferType: () => bufferType,
|
|
37
40
|
containsTypeParam: () => containsTypeParam,
|
|
38
41
|
default: () => index_default,
|
|
39
42
|
difference: () => difference,
|
|
43
|
+
directivesOf: () => directivesOf,
|
|
40
44
|
equalTypes: () => equalTypes,
|
|
41
45
|
falsyType: () => falsyType,
|
|
42
46
|
findConfig: () => findConfig,
|
|
@@ -72,6 +76,7 @@ __export(index_exports, {
|
|
|
72
76
|
parseTokens: () => parseTokens,
|
|
73
77
|
parseWithRecovery: () => parseWithRecovery,
|
|
74
78
|
primitive: () => primitive,
|
|
79
|
+
readDirectives: () => readDirectives,
|
|
75
80
|
resolveModulePath: () => resolveModulePath,
|
|
76
81
|
resolveTypeLibraries: () => resolveTypeLibraries,
|
|
77
82
|
setAliasExpander: () => setAliasExpander,
|
|
@@ -192,11 +197,17 @@ var LexError = class extends Error {
|
|
|
192
197
|
line;
|
|
193
198
|
column;
|
|
194
199
|
};
|
|
195
|
-
function tokenize(source) {
|
|
200
|
+
function tokenize(source, options = {}) {
|
|
201
|
+
const { errors, comments } = options;
|
|
196
202
|
const tokens = [];
|
|
197
203
|
let cursor = 0;
|
|
198
204
|
let line = 1;
|
|
199
205
|
let column = 1;
|
|
206
|
+
function fail(message, atLine, atColumn) {
|
|
207
|
+
const error = new LexError(message, atLine, atColumn);
|
|
208
|
+
if (!errors) throw error;
|
|
209
|
+
errors.push(error);
|
|
210
|
+
}
|
|
200
211
|
function peek(offset = 0) {
|
|
201
212
|
return source[cursor + offset] ?? "";
|
|
202
213
|
}
|
|
@@ -255,7 +266,8 @@ function tokenize(source) {
|
|
|
255
266
|
let content = "";
|
|
256
267
|
while (true) {
|
|
257
268
|
if (isAtEnd()) {
|
|
258
|
-
|
|
269
|
+
fail("Unterminated long bracket", line, column);
|
|
270
|
+
return content;
|
|
259
271
|
}
|
|
260
272
|
if (peek() === "]") {
|
|
261
273
|
const save = cursor;
|
|
@@ -291,16 +303,21 @@ function tokenize(source) {
|
|
|
291
303
|
continue;
|
|
292
304
|
}
|
|
293
305
|
if (ch === "-" && peek(1) === "-") {
|
|
306
|
+
const startLine = line;
|
|
307
|
+
const startColumn = column;
|
|
294
308
|
advance();
|
|
295
309
|
advance();
|
|
296
310
|
if (peek() === "[") {
|
|
297
311
|
const level = tryLongBracketOpen();
|
|
298
312
|
if (level !== null) {
|
|
299
|
-
readLongBracketContent(level);
|
|
313
|
+
const text = readLongBracketContent(level);
|
|
314
|
+
comments?.push({ text, line: startLine, column: startColumn, endLine: line });
|
|
300
315
|
continue;
|
|
301
316
|
}
|
|
302
317
|
}
|
|
318
|
+
const textStart = cursor;
|
|
303
319
|
skipLineComment();
|
|
320
|
+
comments?.push({ text: source.slice(textStart, cursor).replace(/\r$/, ""), line: startLine, column: startColumn, endLine: startLine });
|
|
304
321
|
continue;
|
|
305
322
|
}
|
|
306
323
|
break;
|
|
@@ -411,17 +428,15 @@ function tokenize(source) {
|
|
|
411
428
|
const quote = advance();
|
|
412
429
|
let value = "";
|
|
413
430
|
while (true) {
|
|
414
|
-
if (isAtEnd()) {
|
|
415
|
-
throw new LexError("Unterminated string", line, column);
|
|
416
|
-
}
|
|
417
431
|
const ch = peek();
|
|
432
|
+
if (isAtEnd() || ch === "\n") {
|
|
433
|
+
fail("Unterminated string", line, column);
|
|
434
|
+
break;
|
|
435
|
+
}
|
|
418
436
|
if (ch === quote) {
|
|
419
437
|
advance();
|
|
420
438
|
break;
|
|
421
439
|
}
|
|
422
|
-
if (ch === "\n") {
|
|
423
|
-
throw new LexError("Unterminated string", line, column);
|
|
424
|
-
}
|
|
425
440
|
if (ch === "\\") {
|
|
426
441
|
advance();
|
|
427
442
|
value += readEscapeSequence();
|
|
@@ -470,7 +485,9 @@ function tokenize(source) {
|
|
|
470
485
|
}
|
|
471
486
|
while (true) {
|
|
472
487
|
if (isAtEnd()) {
|
|
473
|
-
|
|
488
|
+
fail("Unterminated interpolated string", line, column);
|
|
489
|
+
flushString();
|
|
490
|
+
break;
|
|
474
491
|
}
|
|
475
492
|
const ch = peek();
|
|
476
493
|
if (ch === "`") {
|
|
@@ -490,10 +507,15 @@ function tokenize(source) {
|
|
|
490
507
|
advance();
|
|
491
508
|
advance();
|
|
492
509
|
const exprStart = cursor;
|
|
510
|
+
const exprLine = line;
|
|
511
|
+
const exprColumn = column;
|
|
493
512
|
let depth = 1;
|
|
513
|
+
let closed = true;
|
|
494
514
|
while (depth > 0) {
|
|
495
515
|
if (isAtEnd()) {
|
|
496
|
-
|
|
516
|
+
fail("Unterminated interpolation expression", line, column);
|
|
517
|
+
closed = false;
|
|
518
|
+
break;
|
|
497
519
|
}
|
|
498
520
|
if (peek() === "{") depth++;
|
|
499
521
|
if (peek() === "}") {
|
|
@@ -503,8 +525,12 @@ function tokenize(source) {
|
|
|
503
525
|
advance();
|
|
504
526
|
}
|
|
505
527
|
const exprRaw = source.slice(exprStart, cursor);
|
|
528
|
+
parts.push({ kind: "expression", raw: exprRaw, line: exprLine, column: exprColumn });
|
|
529
|
+
if (!closed) {
|
|
530
|
+
flushString();
|
|
531
|
+
break;
|
|
532
|
+
}
|
|
506
533
|
advance();
|
|
507
|
-
parts.push({ kind: "expression", raw: exprRaw });
|
|
508
534
|
continue;
|
|
509
535
|
}
|
|
510
536
|
const chStart = cursor;
|
|
@@ -573,7 +599,9 @@ function tokenize(source) {
|
|
|
573
599
|
};
|
|
574
600
|
}
|
|
575
601
|
}
|
|
576
|
-
|
|
602
|
+
fail(`Unexpected character '${peek()}'`, line, column);
|
|
603
|
+
advance();
|
|
604
|
+
return void 0;
|
|
577
605
|
}
|
|
578
606
|
while (true) {
|
|
579
607
|
skipWhitespaceAndComments();
|
|
@@ -606,7 +634,8 @@ function tokenize(source) {
|
|
|
606
634
|
tokens.push(readIdentifierOrKeyword());
|
|
607
635
|
continue;
|
|
608
636
|
}
|
|
609
|
-
|
|
637
|
+
const symbol = readOperatorOrPunctuator();
|
|
638
|
+
if (symbol) tokens.push(symbol);
|
|
610
639
|
}
|
|
611
640
|
tokens.push({
|
|
612
641
|
type: "EOF",
|
|
@@ -616,6 +645,52 @@ function tokenize(source) {
|
|
|
616
645
|
return tokens;
|
|
617
646
|
}
|
|
618
647
|
|
|
648
|
+
// src/ast/directives.ts
|
|
649
|
+
var DIRECTIVE = /^\s*@luaut-(nocheck|ignore|expect-error)(?![\w-])/;
|
|
650
|
+
function readDirectives(comments, tokens) {
|
|
651
|
+
const codeLines = [...new Set(tokens.filter((t) => t.type !== "EOF").map((t) => t.line.start))].sort((a, b) => a - b);
|
|
652
|
+
const firstCode = codeLines[0] ?? Infinity;
|
|
653
|
+
const all = [];
|
|
654
|
+
let nocheck = false;
|
|
655
|
+
for (const comment of comments) {
|
|
656
|
+
const match = DIRECTIVE.exec(comment.text);
|
|
657
|
+
if (!match) continue;
|
|
658
|
+
const kind = match[1];
|
|
659
|
+
if (kind === "nocheck") {
|
|
660
|
+
if (comment.line < firstCode) nocheck = true;
|
|
661
|
+
all.push({ kind, line: comment.line, column: comment.column });
|
|
662
|
+
continue;
|
|
663
|
+
}
|
|
664
|
+
const target = codeLines.find((l) => l > comment.endLine);
|
|
665
|
+
all.push({ kind, line: comment.line, column: comment.column, target });
|
|
666
|
+
}
|
|
667
|
+
return { nocheck, all };
|
|
668
|
+
}
|
|
669
|
+
function directivesOf(source) {
|
|
670
|
+
const comments = [];
|
|
671
|
+
const errors = [];
|
|
672
|
+
const tokens = tokenize(source, { errors, comments });
|
|
673
|
+
return readDirectives(comments, tokens);
|
|
674
|
+
}
|
|
675
|
+
function applyDirectives(directives, diagnostics, lineOf) {
|
|
676
|
+
if (directives.nocheck) return { kept: [], unusedExpectErrors: [] };
|
|
677
|
+
const covering = /* @__PURE__ */ new Map();
|
|
678
|
+
for (const d of directives.all) {
|
|
679
|
+
if (d.target === void 0) continue;
|
|
680
|
+
covering.set(d.target, [...covering.get(d.target) ?? [], d]);
|
|
681
|
+
}
|
|
682
|
+
const used = /* @__PURE__ */ new Set();
|
|
683
|
+
const kept = diagnostics.filter((diagnostic) => {
|
|
684
|
+
const on = covering.get(lineOf(diagnostic));
|
|
685
|
+
if (!on) return true;
|
|
686
|
+
for (const d of on) used.add(d);
|
|
687
|
+
return false;
|
|
688
|
+
});
|
|
689
|
+
const unusedExpectErrors = directives.all.filter((d) => d.kind === "expect-error" && !used.has(d));
|
|
690
|
+
return { kept, unusedExpectErrors };
|
|
691
|
+
}
|
|
692
|
+
var UNUSED_EXPECT_ERROR = "Unused '@luaut-expect-error' directive";
|
|
693
|
+
|
|
619
694
|
// src/ast/builders.ts
|
|
620
695
|
var ParseError = class extends Error {
|
|
621
696
|
constructor(message, line, column) {
|
|
@@ -634,6 +709,25 @@ function spanFrom(start, end) {
|
|
|
634
709
|
column: { start: start.column.start, end: end.column.end }
|
|
635
710
|
};
|
|
636
711
|
}
|
|
712
|
+
function shiftSpans(node, line, column) {
|
|
713
|
+
const visit = (value) => {
|
|
714
|
+
if (!value || typeof value !== "object") return;
|
|
715
|
+
if (Array.isArray(value)) {
|
|
716
|
+
for (const item of value) visit(item);
|
|
717
|
+
return;
|
|
718
|
+
}
|
|
719
|
+
const span = value;
|
|
720
|
+
if (span.line && span.column) {
|
|
721
|
+
if (span.column.start !== void 0 && span.line.start === 1) span.column.start += column - 1;
|
|
722
|
+
if (span.column.end !== void 0 && span.line.end === 1) span.column.end += column - 1;
|
|
723
|
+
span.line.start += line - 1;
|
|
724
|
+
span.line.end += line - 1;
|
|
725
|
+
}
|
|
726
|
+
for (const child of Object.values(value)) visit(child);
|
|
727
|
+
};
|
|
728
|
+
visit(node);
|
|
729
|
+
return node;
|
|
730
|
+
}
|
|
637
731
|
function tokenIdentifier(t) {
|
|
638
732
|
return { type: "Identifier", name: t.value, ...spanFrom(t, t) };
|
|
639
733
|
}
|
|
@@ -666,15 +760,39 @@ var BINARY_PRECEDENCE = {
|
|
|
666
760
|
var RIGHT_ASSOCIATIVE = /* @__PURE__ */ new Set(["..", "^"]);
|
|
667
761
|
var UNARY_PRECEDENCE = 7;
|
|
668
762
|
var COMPOUND_ASSIGN_OPS = /* @__PURE__ */ new Set(["+=", "-=", "*=", "/=", "//=", "%=", "^=", "..="]);
|
|
763
|
+
var STATEMENT_KEYWORDS = /* @__PURE__ */ new Set([
|
|
764
|
+
"const",
|
|
765
|
+
"let",
|
|
766
|
+
"while",
|
|
767
|
+
"for",
|
|
768
|
+
"return",
|
|
769
|
+
"do",
|
|
770
|
+
"repeat",
|
|
771
|
+
"break",
|
|
772
|
+
"continue",
|
|
773
|
+
"import",
|
|
774
|
+
"export",
|
|
775
|
+
"end",
|
|
776
|
+
"else",
|
|
777
|
+
"elseif",
|
|
778
|
+
"until",
|
|
779
|
+
"then"
|
|
780
|
+
]);
|
|
669
781
|
var Parser = class {
|
|
670
782
|
tokens;
|
|
671
783
|
cursor = 0;
|
|
672
784
|
recover;
|
|
785
|
+
indentation;
|
|
673
786
|
/** Populated in recovery mode. */
|
|
674
787
|
errors = [];
|
|
788
|
+
/** Recovery found a block without its `end`. */
|
|
789
|
+
missingEnd = false;
|
|
790
|
+
/** The column of the first token on each line, for `indentation`. */
|
|
791
|
+
lineIndent;
|
|
675
792
|
constructor(tokens, options = {}) {
|
|
676
793
|
this.tokens = tokens;
|
|
677
794
|
this.recover = options.recover ?? false;
|
|
795
|
+
this.indentation = this.recover && (options.indentation ?? false);
|
|
678
796
|
}
|
|
679
797
|
current() {
|
|
680
798
|
return this.tokens[this.cursor];
|
|
@@ -715,6 +833,10 @@ var Parser = class {
|
|
|
715
833
|
const t = this.current();
|
|
716
834
|
return (t.type === "Identifier" || t.type === "Keyword") && t.value === value;
|
|
717
835
|
}
|
|
836
|
+
checkPunctuatorAt(offset, value) {
|
|
837
|
+
const t = this.peek(offset);
|
|
838
|
+
return t.type === "Punctuator" && t.value === value;
|
|
839
|
+
}
|
|
718
840
|
checkIdentifierValue(value) {
|
|
719
841
|
const t = this.current();
|
|
720
842
|
return t.type === "Identifier" && t.value === value;
|
|
@@ -760,46 +882,194 @@ var Parser = class {
|
|
|
760
882
|
const t = this.current();
|
|
761
883
|
const err = new ParseError(`${message}, got '${this.describeToken(t)}'`, t.line.start, t.column.start);
|
|
762
884
|
if (this.recover) {
|
|
763
|
-
this.
|
|
885
|
+
this.record(err);
|
|
764
886
|
throw new ParseRecover(err.message);
|
|
765
887
|
}
|
|
766
888
|
throw err;
|
|
767
889
|
}
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
890
|
+
// ============================================================
|
|
891
|
+
// Recovery
|
|
892
|
+
// ============================================================
|
|
893
|
+
//
|
|
894
|
+
// In recovery mode a syntax error costs as little of the tree as it can.
|
|
895
|
+
// A broken expression becomes an `ErrorExpression` where it stood; a broken
|
|
896
|
+
// field, element or argument is skipped up to the next `,`; a missing `)`,
|
|
897
|
+
// `}`, `then`, `do` or `end` is recorded and parsing goes on as if it were
|
|
898
|
+
// there. Only what none of these cover abandons a whole statement.
|
|
899
|
+
/** An error at the position of the one before it is the same problem seen
|
|
900
|
+
* again, and is not recorded twice. */
|
|
901
|
+
record(error) {
|
|
902
|
+
const last = this.errors[this.errors.length - 1];
|
|
903
|
+
if (last && last.line === error.line && last.column === error.column) return;
|
|
904
|
+
this.errors.push(error);
|
|
905
|
+
}
|
|
906
|
+
/** Record an error without abandoning what is being parsed. */
|
|
907
|
+
softError(message) {
|
|
908
|
+
const t = this.current();
|
|
909
|
+
this.record(new ParseError(`${message}, got '${this.describeToken(t)}'`, t.line.start, t.column.start));
|
|
910
|
+
}
|
|
911
|
+
/** `parse()`; in recovery mode, when it fails, skip to where parsing can go
|
|
912
|
+
* on and return `fallback` instead. */
|
|
913
|
+
attempt(parse2, stop, fallback) {
|
|
914
|
+
if (!this.recover) return parse2();
|
|
915
|
+
const from = this.cursor;
|
|
916
|
+
const start = this.current();
|
|
917
|
+
try {
|
|
918
|
+
return parse2();
|
|
919
|
+
} catch (e) {
|
|
920
|
+
if (e instanceof ParseError) this.record(e);
|
|
921
|
+
else if (!(e instanceof ParseRecover)) throw e;
|
|
922
|
+
this.skip(stop, from, "expression");
|
|
923
|
+
return fallback(start, from);
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
/** An expression, or an `ErrorExpression` over what could not be parsed. */
|
|
927
|
+
expressionOr(stop) {
|
|
928
|
+
return this.attempt(() => this.parseExpression(), stop, (start, from) => this.errorExpression(start, from));
|
|
929
|
+
}
|
|
930
|
+
expressionListOr(stop) {
|
|
931
|
+
const item = () => this.expressionOr(() => stop() || this.checkPunctuator(","));
|
|
932
|
+
const list = [item()];
|
|
933
|
+
while (this.matchPunctuator(",")) list.push(item());
|
|
934
|
+
return list;
|
|
935
|
+
}
|
|
936
|
+
/** A type annotation, or none when it could not be parsed. */
|
|
937
|
+
typeOr(stop) {
|
|
938
|
+
return this.attempt(() => this.parseType(), stop, () => void 0);
|
|
939
|
+
}
|
|
940
|
+
errorExpression(start, from) {
|
|
941
|
+
if (this.cursor > from) return { type: "ErrorExpression", ...spanFrom(start, this.previous()) };
|
|
942
|
+
return {
|
|
943
|
+
type: "ErrorExpression",
|
|
944
|
+
line: { start: start.line.start, end: start.line.start },
|
|
945
|
+
column: { start: start.column.start, end: start.column.start }
|
|
946
|
+
};
|
|
947
|
+
}
|
|
948
|
+
/** A closing bracket; in recovery mode a missing one is recorded and the
|
|
949
|
+
* construct ends where it is. */
|
|
950
|
+
expectCloser(value) {
|
|
951
|
+
if (this.matchPunctuator(value)) return;
|
|
952
|
+
if (!this.recover) this.error(`Expected '${value}'`);
|
|
953
|
+
this.softError(`Expected '${value}'`);
|
|
954
|
+
}
|
|
955
|
+
/** `then` / `do` / `in`; in recovery mode a missing one is recorded and
|
|
956
|
+
* what follows is read as if it were there. */
|
|
957
|
+
expectKeywordSoft(value) {
|
|
958
|
+
if (this.matchKeyword(value)) return;
|
|
959
|
+
if (!this.recover) this.error(`Expected keyword '${value}'`);
|
|
960
|
+
this.softError(`Expected keyword '${value}'`);
|
|
961
|
+
}
|
|
962
|
+
/** The `end` of the block `opener` began. */
|
|
963
|
+
expectEnd(opener) {
|
|
964
|
+
if (this.checkKeyword("end") && !this.endBelongsOutside(opener)) {
|
|
798
965
|
this.advance();
|
|
799
|
-
|
|
800
|
-
|
|
966
|
+
return;
|
|
967
|
+
}
|
|
968
|
+
if (!this.recover) this.error("Expected keyword 'end'");
|
|
969
|
+
this.softError(`Expected 'end' to close '${this.describeToken(opener)}' on line ${opener.line.start}`);
|
|
970
|
+
this.missingEnd = true;
|
|
971
|
+
}
|
|
972
|
+
/** Indentation mode: an `end` indented less than the line that opened the
|
|
973
|
+
* block closes something outside it. */
|
|
974
|
+
endBelongsOutside(opener) {
|
|
975
|
+
if (!this.indentation) return false;
|
|
976
|
+
const t = this.current();
|
|
977
|
+
return t.line.start > opener.line.start && t.column.start < this.indentOf(opener);
|
|
978
|
+
}
|
|
979
|
+
/** Indentation mode: a statement indented no deeper than the line that
|
|
980
|
+
* opened the block is past the block. */
|
|
981
|
+
dedentedPast(opener) {
|
|
982
|
+
if (!this.indentation || !opener) return false;
|
|
983
|
+
const t = this.current();
|
|
984
|
+
return t.line.start > opener.line.start && t.column.start <= this.indentOf(opener);
|
|
985
|
+
}
|
|
986
|
+
indentOf(token) {
|
|
987
|
+
if (!this.lineIndent) {
|
|
988
|
+
this.lineIndent = /* @__PURE__ */ new Map();
|
|
989
|
+
for (const t of this.tokens) {
|
|
990
|
+
if (!this.lineIndent.has(t.line.start)) this.lineIndent.set(t.line.start, t.column.start);
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
return this.lineIndent.get(token.line.start) ?? token.column.start;
|
|
994
|
+
}
|
|
995
|
+
/** Is the current token on a later line than the one before it? */
|
|
996
|
+
onNewLine() {
|
|
997
|
+
const previous = this.previous();
|
|
998
|
+
return previous !== void 0 && this.current().line.start > previous.line.end;
|
|
999
|
+
}
|
|
1000
|
+
/** Recovery: move past what could not be parsed.
|
|
1001
|
+
*
|
|
1002
|
+
* Skipping stops at a token `stop` accepts, at a bracket closing something
|
|
1003
|
+
* opened before the skip, or at a keyword that starts a statement. The
|
|
1004
|
+
* tokens from `from` on — including those the failed attempt already
|
|
1005
|
+
* consumed — count towards nesting, so a bracket or a `function ... end`
|
|
1006
|
+
* is skipped whole and an `end` or `}` inside it cannot end what encloses
|
|
1007
|
+
* it. A statement keyword inside brackets but outside any function means a
|
|
1008
|
+
* bracket was never closed, and it stops the skip as well. */
|
|
1009
|
+
skip(stop, from, mode) {
|
|
1010
|
+
const closers = [];
|
|
1011
|
+
for (let i = from; i < this.cursor; i++) this.nest(this.tokens[i], closers, mode);
|
|
1012
|
+
while (!this.isAtEnd()) {
|
|
1013
|
+
if (this.stopsSkip(closers, stop, mode)) return;
|
|
1014
|
+
const t = this.advance();
|
|
1015
|
+
this.nest(t, closers, mode);
|
|
1016
|
+
if (mode === "statement" && closers.length === 0 && t.type === "Punctuator" && t.value === ";") return;
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
nest(t, closers, mode) {
|
|
1020
|
+
const value = t.value;
|
|
1021
|
+
const popTo = (closer) => {
|
|
1022
|
+
const at = closers.lastIndexOf(closer);
|
|
1023
|
+
if (at >= 0) closers.length = at;
|
|
1024
|
+
};
|
|
1025
|
+
if (t.type === "Punctuator") {
|
|
1026
|
+
if (value === "(") closers.push(")");
|
|
1027
|
+
else if (value === "[") closers.push("]");
|
|
1028
|
+
else if (value === "{") closers.push("}");
|
|
1029
|
+
else if (value === ")" || value === "]" || value === "}") popTo(value);
|
|
1030
|
+
return;
|
|
1031
|
+
}
|
|
1032
|
+
if (t.type !== "Keyword") return;
|
|
1033
|
+
const inBody = closers.includes("end") || closers.includes("until") || mode === "statement" && closers.length === 0;
|
|
1034
|
+
switch (value) {
|
|
1035
|
+
case "function":
|
|
1036
|
+
closers.push("end");
|
|
1037
|
+
return;
|
|
1038
|
+
case "if":
|
|
1039
|
+
closers.push(inBody ? "end" : "else");
|
|
1040
|
+
return;
|
|
1041
|
+
case "do":
|
|
1042
|
+
if (inBody) closers.push("end");
|
|
1043
|
+
return;
|
|
1044
|
+
case "repeat":
|
|
1045
|
+
if (inBody) closers.push("until");
|
|
1046
|
+
return;
|
|
1047
|
+
case "else":
|
|
1048
|
+
if (closers[closers.length - 1] === "else") closers.pop();
|
|
1049
|
+
return;
|
|
1050
|
+
case "end":
|
|
1051
|
+
popTo("end");
|
|
1052
|
+
return;
|
|
1053
|
+
case "until":
|
|
1054
|
+
popTo("until");
|
|
1055
|
+
return;
|
|
801
1056
|
}
|
|
802
1057
|
}
|
|
1058
|
+
stopsSkip(closers, stop, mode) {
|
|
1059
|
+
const t = this.current();
|
|
1060
|
+
const value = t.value;
|
|
1061
|
+
const inFunction = closers.includes("end") || closers.includes("until");
|
|
1062
|
+
if (!inFunction && t.type === "Keyword" && typeof value === "string") {
|
|
1063
|
+
const inIfExpression = closers.includes("else") && (value === "then" || value === "elseif" || value === "else");
|
|
1064
|
+
if (STATEMENT_KEYWORDS.has(value) && !inIfExpression && !(mode === "statement" && value === "then")) return true;
|
|
1065
|
+
if (mode === "statement" && closers.length === 0 && (value === "if" || value === "function" && this.peek(1).type === "Identifier")) return true;
|
|
1066
|
+
}
|
|
1067
|
+
if (closers.length) return false;
|
|
1068
|
+
if (t.type === "Punctuator" && (value === ")" || value === "]" || value === "}")) return true;
|
|
1069
|
+
if (mode === "statement" && t.type === "Punctuator" && value === "@") return true;
|
|
1070
|
+
if (mode === "expression" && t.type === "Punctuator" && value === ";") return true;
|
|
1071
|
+
return stop();
|
|
1072
|
+
}
|
|
803
1073
|
describeToken(t) {
|
|
804
1074
|
if (t.type === "EOF") return "<eof>";
|
|
805
1075
|
if ("value" in t) return String(t.value);
|
|
@@ -812,16 +1082,13 @@ var Parser = class {
|
|
|
812
1082
|
const start = this.current();
|
|
813
1083
|
const body = this.parseBlock();
|
|
814
1084
|
if (!this.isAtEnd()) {
|
|
815
|
-
if (this.recover)
|
|
816
|
-
|
|
817
|
-
this.
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
t.column.start
|
|
821
|
-
));
|
|
822
|
-
} else {
|
|
823
|
-
this.error("Expected end of file");
|
|
1085
|
+
if (!this.recover) this.error("Expected end of file");
|
|
1086
|
+
while (!this.isAtEnd()) {
|
|
1087
|
+
this.softError("Expected end of file");
|
|
1088
|
+
this.advance();
|
|
1089
|
+
body.statements.push(...this.parseBlock().statements);
|
|
824
1090
|
}
|
|
1091
|
+
Object.assign(body, spanFrom(body, this.previous() ?? start));
|
|
825
1092
|
}
|
|
826
1093
|
return { type: "Program", body, ...spanFrom(start, this.previous() ?? start) };
|
|
827
1094
|
}
|
|
@@ -831,11 +1098,14 @@ var Parser = class {
|
|
|
831
1098
|
isBlockEnd() {
|
|
832
1099
|
return this.isAtEnd() || this.checkKeyword("end") || this.checkKeyword("else") || this.checkKeyword("elseif") || this.checkKeyword("until");
|
|
833
1100
|
}
|
|
834
|
-
|
|
1101
|
+
/** `opener` is the token that began the block (`if`, `function`, ...), for
|
|
1102
|
+
* indentation recovery. */
|
|
1103
|
+
parseBlock(opener) {
|
|
835
1104
|
const start = this.current();
|
|
836
1105
|
const statements = [];
|
|
837
1106
|
while (!this.isBlockEnd()) {
|
|
838
1107
|
if (this.matchPunctuator(";")) continue;
|
|
1108
|
+
if (this.dedentedPast(opener)) break;
|
|
839
1109
|
if (this.recover) {
|
|
840
1110
|
const at = this.cursor;
|
|
841
1111
|
const errStart = this.current();
|
|
@@ -849,11 +1119,11 @@ var Parser = class {
|
|
|
849
1119
|
} catch (e) {
|
|
850
1120
|
if (e instanceof ParseRecover) {
|
|
851
1121
|
} else if (e instanceof ParseError) {
|
|
852
|
-
this.
|
|
1122
|
+
this.record(e);
|
|
853
1123
|
} else {
|
|
854
1124
|
throw e;
|
|
855
1125
|
}
|
|
856
|
-
this.
|
|
1126
|
+
this.skip(() => false, at, "statement");
|
|
857
1127
|
if (this.cursor === at) {
|
|
858
1128
|
if (this.isAtEnd()) break;
|
|
859
1129
|
this.advance();
|
|
@@ -889,23 +1159,14 @@ var Parser = class {
|
|
|
889
1159
|
if (t.type === "Punctuator" && t.value === "@") {
|
|
890
1160
|
const { attributes, start } = this.parseAttributes();
|
|
891
1161
|
const next = this.current();
|
|
892
|
-
if (next.type === "Keyword" && (next.value === "const" || next.value === "let")) {
|
|
893
|
-
const stmt = this.parseVariableDeclaration();
|
|
894
|
-
if (stmt.type === "FunctionDeclaration") {
|
|
895
|
-
stmt.attributes = attributes;
|
|
896
|
-
stmt.line.start = start.line.start;
|
|
897
|
-
stmt.column.start = start.column.start;
|
|
898
|
-
}
|
|
899
|
-
return stmt;
|
|
900
|
-
}
|
|
901
1162
|
if (next.type === "Keyword" && next.value === "function") {
|
|
902
|
-
const stmt = this.
|
|
1163
|
+
const stmt = this.parseFunctionStatement();
|
|
903
1164
|
stmt.attributes = attributes;
|
|
904
1165
|
stmt.line.start = start.line.start;
|
|
905
1166
|
stmt.column.start = start.column.start;
|
|
906
1167
|
return stmt;
|
|
907
1168
|
}
|
|
908
|
-
throw new ParseError("Expected 'function'
|
|
1169
|
+
throw new ParseError("Expected 'function' after an attribute", next.line.start, next.column.start);
|
|
909
1170
|
}
|
|
910
1171
|
if (t.type === "Keyword") {
|
|
911
1172
|
switch (t.value) {
|
|
@@ -923,7 +1184,7 @@ var Parser = class {
|
|
|
923
1184
|
case "for":
|
|
924
1185
|
return this.parseForStatement();
|
|
925
1186
|
case "function":
|
|
926
|
-
return this.
|
|
1187
|
+
return this.parseFunctionStatement();
|
|
927
1188
|
case "return":
|
|
928
1189
|
return this.parseReturnStatement();
|
|
929
1190
|
case "import":
|
|
@@ -940,6 +1201,10 @@ var Parser = class {
|
|
|
940
1201
|
}
|
|
941
1202
|
}
|
|
942
1203
|
}
|
|
1204
|
+
if (this.recover && t.type === "Identifier" && t.value === "local" && (this.peek(1).type === "Identifier" || this.checkPunctuatorAt(1, "{") || this.checkPunctuatorAt(1, "["))) {
|
|
1205
|
+
this.softError("luaut has no 'local'; declare with 'const' or 'let'");
|
|
1206
|
+
return this.parseVariableDeclaration("let");
|
|
1207
|
+
}
|
|
943
1208
|
if (t.type === "Identifier" && t.value === "type" && this.peek(1).type === "Identifier") {
|
|
944
1209
|
return this.parseTypeAliasStatement();
|
|
945
1210
|
}
|
|
@@ -1025,20 +1290,30 @@ var Parser = class {
|
|
|
1025
1290
|
parseImportStatement() {
|
|
1026
1291
|
const start = this.current();
|
|
1027
1292
|
this.advance();
|
|
1293
|
+
const next = this.peek(1);
|
|
1294
|
+
const isTypeOnly = this.checkIdentifierValue("type") && (next.type === "Punctuator" && next.value === "{" || next.type === "Operator" && next.value === "*" || next.type === "Identifier");
|
|
1295
|
+
if (isTypeOnly) this.advance();
|
|
1028
1296
|
let defaultImport;
|
|
1029
1297
|
const specifiers = [];
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
this.
|
|
1035
|
-
this.
|
|
1036
|
-
this.
|
|
1298
|
+
let namespaceImport;
|
|
1299
|
+
const parseBindings = () => {
|
|
1300
|
+
if (this.checkOperator("*")) {
|
|
1301
|
+
this.advance();
|
|
1302
|
+
if (!this.checkKeyword("as")) this.error("Expected 'as' after 'import *'");
|
|
1303
|
+
this.advance();
|
|
1304
|
+
namespaceImport = this.parseIdentifier();
|
|
1305
|
+
return;
|
|
1037
1306
|
}
|
|
1038
|
-
} else {
|
|
1039
1307
|
this.expectPunctuator("{");
|
|
1040
1308
|
this.parseImportSpecifierList(specifiers);
|
|
1041
1309
|
this.expectPunctuator("}");
|
|
1310
|
+
};
|
|
1311
|
+
if (this.checkType("Identifier")) {
|
|
1312
|
+
const nameTok = this.expectIdentifier();
|
|
1313
|
+
defaultImport = { type: "Identifier", name: nameTok.value, ...spanFrom(nameTok, nameTok) };
|
|
1314
|
+
if (this.matchPunctuator(",")) parseBindings();
|
|
1315
|
+
} else {
|
|
1316
|
+
parseBindings();
|
|
1042
1317
|
}
|
|
1043
1318
|
if (!this.checkKeyword("from")) {
|
|
1044
1319
|
this.error("Expected 'from' in import statement");
|
|
@@ -1055,7 +1330,15 @@ var Parser = class {
|
|
|
1055
1330
|
raw: sourceTok.raw,
|
|
1056
1331
|
...spanFrom(sourceTok, sourceTok)
|
|
1057
1332
|
};
|
|
1058
|
-
return {
|
|
1333
|
+
return {
|
|
1334
|
+
type: "ImportStatement",
|
|
1335
|
+
defaultImport,
|
|
1336
|
+
namespaceImport,
|
|
1337
|
+
specifiers,
|
|
1338
|
+
source,
|
|
1339
|
+
isTypeOnly: isTypeOnly || void 0,
|
|
1340
|
+
...spanFrom(start, this.previous())
|
|
1341
|
+
};
|
|
1059
1342
|
}
|
|
1060
1343
|
parseImportSpecifierList(out) {
|
|
1061
1344
|
if (this.checkPunctuator("}")) return;
|
|
@@ -1091,7 +1374,7 @@ var Parser = class {
|
|
|
1091
1374
|
...spanFrom(sourceTok, sourceTok)
|
|
1092
1375
|
};
|
|
1093
1376
|
}
|
|
1094
|
-
// `export const ...` / `export let ...` / `export
|
|
1377
|
+
// `export const ...` / `export let ...` / `export function ...` /
|
|
1095
1378
|
// `export type ...` / `export default <expr>`
|
|
1096
1379
|
parseExportStatement() {
|
|
1097
1380
|
const start = this.current();
|
|
@@ -1109,6 +1392,11 @@ var Parser = class {
|
|
|
1109
1392
|
const declaration = this.parseVariableDeclaration();
|
|
1110
1393
|
return { type: "ExportStatement", declaration, ...spanFrom(start, this.previous()) };
|
|
1111
1394
|
}
|
|
1395
|
+
if (this.checkKeyword("function")) {
|
|
1396
|
+
const declaration = this.parseFunctionStatement(true);
|
|
1397
|
+
if (declaration.type !== "FunctionDeclaration") this.error("An exported function needs a plain name: 'export function name()'");
|
|
1398
|
+
return { type: "ExportStatement", declaration, ...spanFrom(start, this.previous()) };
|
|
1399
|
+
}
|
|
1112
1400
|
if (this.checkPunctuator("{")) {
|
|
1113
1401
|
this.advance();
|
|
1114
1402
|
const specifiers = [];
|
|
@@ -1132,15 +1420,17 @@ var Parser = class {
|
|
|
1132
1420
|
const source = this.parseModuleSource();
|
|
1133
1421
|
return { type: "ExportAllStatement", source, ...spanFrom(start, this.previous()) };
|
|
1134
1422
|
}
|
|
1135
|
-
this.error("Expected 'const', 'let', 'type', 'default', '{' or '*' after 'export'");
|
|
1423
|
+
this.error("Expected 'const', 'let', 'function', 'type', 'default', '{' or '*' after 'export'");
|
|
1136
1424
|
}
|
|
1137
|
-
// `const x = ...` / `let x, y =
|
|
1425
|
+
// `const x = ...` / `let x, y = ...`.
|
|
1138
1426
|
// luaut has no `local` — `const` bindings are immutable, `let` mutable.
|
|
1139
|
-
|
|
1427
|
+
/** `kind` reads the leading word as that keyword (recovery's `local`). */
|
|
1428
|
+
parseVariableDeclaration(as) {
|
|
1140
1429
|
const start = this.current();
|
|
1141
|
-
const
|
|
1142
|
-
|
|
1143
|
-
|
|
1430
|
+
const word = this.advance().value;
|
|
1431
|
+
const kind = as ?? word;
|
|
1432
|
+
if (this.checkKeyword("function")) {
|
|
1433
|
+
this.error(`A function is declared as 'function name()'; '${kind}' does not apply to functions`);
|
|
1144
1434
|
}
|
|
1145
1435
|
const names = [this.parseBindingTarget(true)];
|
|
1146
1436
|
while (this.matchPunctuator(",")) {
|
|
@@ -1148,99 +1438,84 @@ var Parser = class {
|
|
|
1148
1438
|
}
|
|
1149
1439
|
let init = [];
|
|
1150
1440
|
if (this.matchOperator("=")) {
|
|
1151
|
-
init = this.
|
|
1441
|
+
init = this.expressionListOr(() => false);
|
|
1152
1442
|
} else if (kind === "const") {
|
|
1153
|
-
this.error("'const' declaration requires an initializer");
|
|
1443
|
+
if (!this.recover) this.error("'const' declaration requires an initializer");
|
|
1444
|
+
this.softError("'const' declaration requires an initializer");
|
|
1154
1445
|
}
|
|
1155
1446
|
return { type: "VariableDeclaration", kind, names, init, ...spanFrom(start, this.previous()) };
|
|
1156
1447
|
}
|
|
1157
|
-
/** `const/let function` — `function` already consumed. Collects TS-style
|
|
1158
|
-
* overload signatures. */
|
|
1159
|
-
parseFunctionDeclarationRest(start, kind) {
|
|
1160
|
-
const name = this.parseIdentifier();
|
|
1161
|
-
const signatures = [];
|
|
1162
|
-
while (true) {
|
|
1163
|
-
const head = this.parseFunctionHead();
|
|
1164
|
-
if (this.isOverloadContinuation(name.name, kind)) {
|
|
1165
|
-
signatures.push(this.headToSignature(head));
|
|
1166
|
-
this.advance();
|
|
1167
|
-
this.expectKeyword("function");
|
|
1168
|
-
this.parseIdentifier();
|
|
1169
|
-
continue;
|
|
1170
|
-
}
|
|
1171
|
-
const func = this.headToBody(head);
|
|
1172
|
-
return {
|
|
1173
|
-
type: "FunctionDeclaration",
|
|
1174
|
-
kind,
|
|
1175
|
-
name,
|
|
1176
|
-
func,
|
|
1177
|
-
signatures: signatures.length ? signatures : void 0,
|
|
1178
|
-
...spanFrom(start, this.previous())
|
|
1179
|
-
};
|
|
1180
|
-
}
|
|
1181
|
-
}
|
|
1182
1448
|
parseIfStatement() {
|
|
1183
1449
|
const start = this.current();
|
|
1184
1450
|
this.expectKeyword("if");
|
|
1185
1451
|
const clauses = [];
|
|
1186
|
-
const
|
|
1187
|
-
this.
|
|
1188
|
-
|
|
1452
|
+
const untilThen = () => this.checkKeyword("then");
|
|
1453
|
+
const cond = this.expressionOr(untilThen);
|
|
1454
|
+
this.expectKeywordSoft("then");
|
|
1455
|
+
const body = this.parseBlock(start);
|
|
1189
1456
|
clauses.push({ type: "IfClause", condition: cond, body, ...spanFrom(cond, this.previous()) });
|
|
1190
1457
|
while (this.checkKeyword("elseif")) {
|
|
1191
1458
|
const clauseStart = this.current();
|
|
1192
1459
|
this.advance();
|
|
1193
|
-
const c = this.
|
|
1194
|
-
this.
|
|
1195
|
-
const b = this.parseBlock();
|
|
1460
|
+
const c = this.expressionOr(untilThen);
|
|
1461
|
+
this.expectKeywordSoft("then");
|
|
1462
|
+
const b = this.parseBlock(start);
|
|
1196
1463
|
clauses.push({ type: "IfClause", condition: c, body: b, ...spanFrom(clauseStart, this.previous()) });
|
|
1197
1464
|
}
|
|
1198
1465
|
let alternate;
|
|
1199
1466
|
if (this.matchKeyword("else")) {
|
|
1200
|
-
alternate = this.parseBlock();
|
|
1467
|
+
alternate = this.parseBlock(start);
|
|
1201
1468
|
}
|
|
1202
|
-
this.
|
|
1469
|
+
this.expectEnd(start);
|
|
1203
1470
|
return { type: "IfStatement", clauses, alternate, ...spanFrom(start, this.previous()) };
|
|
1204
1471
|
}
|
|
1205
1472
|
parseWhileStatement() {
|
|
1206
1473
|
const start = this.current();
|
|
1207
1474
|
this.expectKeyword("while");
|
|
1208
|
-
const condition = this.
|
|
1209
|
-
this.
|
|
1210
|
-
const body = this.parseBlock();
|
|
1211
|
-
this.
|
|
1475
|
+
const condition = this.expressionOr(() => this.checkKeyword("do"));
|
|
1476
|
+
this.expectKeywordSoft("do");
|
|
1477
|
+
const body = this.parseBlock(start);
|
|
1478
|
+
this.expectEnd(start);
|
|
1212
1479
|
return { type: "WhileStatement", condition, body, ...spanFrom(start, this.previous()) };
|
|
1213
1480
|
}
|
|
1214
1481
|
parseRepeatStatement() {
|
|
1215
1482
|
const start = this.current();
|
|
1216
1483
|
this.expectKeyword("repeat");
|
|
1217
|
-
const body = this.parseBlock();
|
|
1218
|
-
|
|
1219
|
-
|
|
1484
|
+
const body = this.parseBlock(start);
|
|
1485
|
+
let condition;
|
|
1486
|
+
if (this.checkKeyword("until") || !this.recover) {
|
|
1487
|
+
this.expectKeyword("until");
|
|
1488
|
+
condition = this.expressionOr(() => false);
|
|
1489
|
+
} else {
|
|
1490
|
+
this.softError(`Expected 'until' to close 'repeat' on line ${start.line.start}`);
|
|
1491
|
+
this.missingEnd = true;
|
|
1492
|
+
condition = this.errorExpression(this.current(), this.cursor);
|
|
1493
|
+
}
|
|
1220
1494
|
return { type: "RepeatStatement", body, condition, ...spanFrom(start, this.previous()) };
|
|
1221
1495
|
}
|
|
1222
1496
|
parseDoStatement() {
|
|
1223
1497
|
const start = this.current();
|
|
1224
1498
|
this.expectKeyword("do");
|
|
1225
|
-
const body = this.parseBlock();
|
|
1226
|
-
this.
|
|
1499
|
+
const body = this.parseBlock(start);
|
|
1500
|
+
this.expectEnd(start);
|
|
1227
1501
|
return { type: "DoStatement", body, ...spanFrom(start, this.previous()) };
|
|
1228
1502
|
}
|
|
1229
1503
|
parseForStatement() {
|
|
1230
1504
|
const start = this.current();
|
|
1231
1505
|
this.expectKeyword("for");
|
|
1232
1506
|
const first = this.parseBindingTarget(true);
|
|
1507
|
+
const untilDo = () => this.checkKeyword("do");
|
|
1233
1508
|
if (first.type === "IdentifierPattern" && this.matchOperator("=")) {
|
|
1234
|
-
const from = this.
|
|
1509
|
+
const from = this.expressionOr(() => untilDo() || this.checkPunctuator(","));
|
|
1235
1510
|
this.expectPunctuator(",");
|
|
1236
|
-
const to = this.
|
|
1511
|
+
const to = this.expressionOr(() => untilDo() || this.checkPunctuator(","));
|
|
1237
1512
|
let step;
|
|
1238
1513
|
if (this.matchPunctuator(",")) {
|
|
1239
|
-
step = this.
|
|
1514
|
+
step = this.expressionOr(untilDo);
|
|
1240
1515
|
}
|
|
1241
|
-
this.
|
|
1242
|
-
const body2 = this.parseBlock();
|
|
1243
|
-
this.
|
|
1516
|
+
this.expectKeywordSoft("do");
|
|
1517
|
+
const body2 = this.parseBlock(start);
|
|
1518
|
+
this.expectEnd(start);
|
|
1244
1519
|
return {
|
|
1245
1520
|
type: "NumericForStatement",
|
|
1246
1521
|
variable: this.identifierPatternToTypedIdentifier(first),
|
|
@@ -1256,10 +1531,10 @@ var Parser = class {
|
|
|
1256
1531
|
variables.push(this.parseBindingTarget(true));
|
|
1257
1532
|
}
|
|
1258
1533
|
this.expectKeyword("in");
|
|
1259
|
-
const iterators = this.
|
|
1260
|
-
this.
|
|
1261
|
-
const body = this.parseBlock();
|
|
1262
|
-
this.
|
|
1534
|
+
const iterators = this.expressionListOr(untilDo);
|
|
1535
|
+
this.expectKeywordSoft("do");
|
|
1536
|
+
const body = this.parseBlock(start);
|
|
1537
|
+
this.expectEnd(start);
|
|
1263
1538
|
return {
|
|
1264
1539
|
type: "GenericForStatement",
|
|
1265
1540
|
variables,
|
|
@@ -1268,22 +1543,41 @@ var Parser = class {
|
|
|
1268
1543
|
...spanFrom(start, this.previous())
|
|
1269
1544
|
};
|
|
1270
1545
|
}
|
|
1271
|
-
|
|
1546
|
+
/** `function name() end` declares `name`; `function a.b() end` and
|
|
1547
|
+
* `function T:m() end` define a member. */
|
|
1548
|
+
/** `exported` — the `export` before this `function` has been consumed, so
|
|
1549
|
+
* each overload signature after it must carry one as well. */
|
|
1550
|
+
parseFunctionStatement(exported = false) {
|
|
1272
1551
|
const start = this.current();
|
|
1273
1552
|
this.expectKeyword("function");
|
|
1274
1553
|
const target = this.parseFunctionName();
|
|
1275
1554
|
const isMethod = target.method !== void 0;
|
|
1276
1555
|
const simpleName = !isMethod && target.path.length === 0 ? target.base.name : void 0;
|
|
1277
1556
|
const signatures = [];
|
|
1557
|
+
let written = target.base;
|
|
1278
1558
|
while (true) {
|
|
1279
1559
|
const head = this.parseFunctionHead();
|
|
1280
1560
|
if (simpleName !== void 0 && this.isOverloadContinuation(simpleName)) {
|
|
1281
|
-
signatures.push(this.headToSignature(head));
|
|
1561
|
+
signatures.push({ ...this.headToSignature(head), name: written });
|
|
1562
|
+
const nextExported = this.matchKeyword("export");
|
|
1563
|
+
if (nextExported !== exported) {
|
|
1564
|
+
this.problem("Overload signatures must all be exported or non-exported");
|
|
1565
|
+
}
|
|
1282
1566
|
this.expectKeyword("function");
|
|
1283
|
-
this.parseFunctionName();
|
|
1567
|
+
written = this.parseFunctionName().base;
|
|
1284
1568
|
continue;
|
|
1285
1569
|
}
|
|
1286
|
-
const func = this.headToBody(head);
|
|
1570
|
+
const func = this.headToBody(head, start);
|
|
1571
|
+
if (simpleName !== void 0) {
|
|
1572
|
+
return {
|
|
1573
|
+
type: "FunctionDeclaration",
|
|
1574
|
+
name: target.base,
|
|
1575
|
+
func,
|
|
1576
|
+
signatures: signatures.length ? signatures : void 0,
|
|
1577
|
+
implementationName: signatures.length ? written : void 0,
|
|
1578
|
+
...spanFrom(start, this.previous())
|
|
1579
|
+
};
|
|
1580
|
+
}
|
|
1287
1581
|
if (isMethod) {
|
|
1288
1582
|
func.params.unshift({ type: "FunctionParameter", name: "self", ...spanFrom(target, target) });
|
|
1289
1583
|
func.isMethod = true;
|
|
@@ -1300,13 +1594,20 @@ var Parser = class {
|
|
|
1300
1594
|
}
|
|
1301
1595
|
/** After a bodyless function head, is the next token the start of another
|
|
1302
1596
|
* declaration for the same simple `name` (making the head an overload
|
|
1303
|
-
* signature rather than an implementation)?
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
if (
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1597
|
+
* signature rather than an implementation)? */
|
|
1598
|
+
isOverloadContinuation(name) {
|
|
1599
|
+
const named = (offset) => this.peek(offset).type === "Identifier" && this.peek(offset).value === name;
|
|
1600
|
+
if (this.checkKeyword("function")) return named(1);
|
|
1601
|
+
return this.checkKeyword("export") && this.peek(1).type === "Keyword" && this.peek(1).value === "function" && named(2);
|
|
1602
|
+
}
|
|
1603
|
+
/** A mistake that does not stop the parse: refused outside recovery, where
|
|
1604
|
+
* the compiler must not accept it, and recorded inside. The message says
|
|
1605
|
+
* what is wrong on its own — no token is appended. */
|
|
1606
|
+
problem(message) {
|
|
1607
|
+
const t = this.current();
|
|
1608
|
+
const error = new ParseError(message, t.line.start, t.column.start);
|
|
1609
|
+
if (!this.recover) throw error;
|
|
1610
|
+
this.record(error);
|
|
1310
1611
|
}
|
|
1311
1612
|
parseFunctionName() {
|
|
1312
1613
|
const start = this.current();
|
|
@@ -1342,7 +1643,7 @@ var Parser = class {
|
|
|
1342
1643
|
this.expectKeyword("return");
|
|
1343
1644
|
let args = [];
|
|
1344
1645
|
if (this.isExpressionStart()) {
|
|
1345
|
-
args = this.
|
|
1646
|
+
args = this.expressionListOr(() => false);
|
|
1346
1647
|
}
|
|
1347
1648
|
return { type: "ReturnStatement", arguments: args, ...spanFrom(start, this.previous()) };
|
|
1348
1649
|
}
|
|
@@ -1374,7 +1675,7 @@ var Parser = class {
|
|
|
1374
1675
|
targets.push(this.parseAssignTarget());
|
|
1375
1676
|
}
|
|
1376
1677
|
this.expectOperator("=");
|
|
1377
|
-
const values = this.
|
|
1678
|
+
const values = this.expressionListOr(() => false);
|
|
1378
1679
|
return { type: "AssignmentStatement", targets, values, ...spanFrom(start, this.previous()) };
|
|
1379
1680
|
}
|
|
1380
1681
|
const first = this.parsePrefixExpression();
|
|
@@ -1383,14 +1684,16 @@ var Parser = class {
|
|
|
1383
1684
|
while (this.matchPunctuator(",")) {
|
|
1384
1685
|
targets.push(this.parseAssignTarget());
|
|
1385
1686
|
}
|
|
1687
|
+
for (const target of targets) this.rejectOptionalTarget(target);
|
|
1386
1688
|
this.expectOperator("=");
|
|
1387
|
-
const values = this.
|
|
1689
|
+
const values = this.expressionListOr(() => false);
|
|
1388
1690
|
return { type: "AssignmentStatement", targets, values, ...spanFrom(start, this.previous()) };
|
|
1389
1691
|
}
|
|
1390
1692
|
const t = this.current();
|
|
1391
1693
|
if (t.type === "Operator" && COMPOUND_ASSIGN_OPS.has(t.value)) {
|
|
1694
|
+
this.rejectOptionalTarget(first);
|
|
1392
1695
|
const op = this.advance().value;
|
|
1393
|
-
const value = this.
|
|
1696
|
+
const value = this.expressionOr(() => false);
|
|
1394
1697
|
return {
|
|
1395
1698
|
type: "CompoundAssignmentStatement",
|
|
1396
1699
|
operator: op,
|
|
@@ -1428,15 +1731,43 @@ var Parser = class {
|
|
|
1428
1731
|
* rather than the `:` of a ternary (`cond ? obj : other`)? Lua requires a
|
|
1429
1732
|
* method call to be called, so the answer is exact rather than heuristic:
|
|
1430
1733
|
* `:` Identifier followed by one of Lua's call forms. */
|
|
1431
|
-
startsMethodCall() {
|
|
1432
|
-
if (this.peek(1).type !== "Identifier") return false;
|
|
1433
|
-
const after = this.peek(2);
|
|
1734
|
+
startsMethodCall(offset = 0) {
|
|
1735
|
+
if (this.peek(offset + 1).type !== "Identifier") return false;
|
|
1736
|
+
const after = this.peek(offset + 2);
|
|
1434
1737
|
if (after.type === "Punctuator") {
|
|
1435
1738
|
const v = String(after.value);
|
|
1436
1739
|
return v === "(" || v === "{";
|
|
1437
1740
|
}
|
|
1438
1741
|
if (after.type === "InterpolatedString") return true;
|
|
1439
|
-
|
|
1742
|
+
if (after.type === "Literal" && after.kind === "string") return true;
|
|
1743
|
+
if (after.type === "Operator" && String(after.value) === "<") {
|
|
1744
|
+
const save = this.cursor;
|
|
1745
|
+
this.cursor += offset + 2;
|
|
1746
|
+
const found = this.tryCallTypeArguments() !== void 0;
|
|
1747
|
+
this.cursor = save;
|
|
1748
|
+
return found;
|
|
1749
|
+
}
|
|
1750
|
+
return false;
|
|
1751
|
+
}
|
|
1752
|
+
/** Does the next token start right where the current one ends? */
|
|
1753
|
+
touchesNext() {
|
|
1754
|
+
const current = this.current();
|
|
1755
|
+
const next = this.peek(1);
|
|
1756
|
+
return current.line.end === next.line.start && current.column.end === next.column.start;
|
|
1757
|
+
}
|
|
1758
|
+
/** `a?.b = 1` cannot be written: there may be nothing to assign to. */
|
|
1759
|
+
rejectOptionalTarget(target) {
|
|
1760
|
+
for (let e = target; e && typeof e === "object"; ) {
|
|
1761
|
+
const node = e;
|
|
1762
|
+
if (node.optional) {
|
|
1763
|
+
const at = target;
|
|
1764
|
+
const err = new ParseError("An optional chain cannot be assigned to", at.line.start, at.column.start);
|
|
1765
|
+
if (!this.recover) throw err;
|
|
1766
|
+
this.record(err);
|
|
1767
|
+
return;
|
|
1768
|
+
}
|
|
1769
|
+
e = node.type === "MemberExpression" || node.type === "IndexExpression" || node.type === "MethodCallExpression" ? node.object : node.type === "CallExpression" ? node.callee : void 0;
|
|
1770
|
+
}
|
|
1440
1771
|
}
|
|
1441
1772
|
isUnaryOperator() {
|
|
1442
1773
|
const t = this.current();
|
|
@@ -1553,7 +1884,7 @@ var Parser = class {
|
|
|
1553
1884
|
}
|
|
1554
1885
|
if (t.type === "Keyword" && t.value === "function") {
|
|
1555
1886
|
this.advance();
|
|
1556
|
-
const func = this.parseFunctionBody();
|
|
1887
|
+
const func = this.parseFunctionBody(t);
|
|
1557
1888
|
return { type: "FunctionExpression", func, ...spanFrom(t, this.previous()) };
|
|
1558
1889
|
}
|
|
1559
1890
|
if (t.type === "Keyword" && t.value === "if") {
|
|
@@ -1576,7 +1907,19 @@ var Parser = class {
|
|
|
1576
1907
|
if (p.kind === "string") {
|
|
1577
1908
|
parts.push({ kind: "string", value: p.value, raw: p.raw });
|
|
1578
1909
|
} else {
|
|
1579
|
-
|
|
1910
|
+
let expression;
|
|
1911
|
+
try {
|
|
1912
|
+
expression = shiftSpans(parseExpressionFromSource(p.raw), p.line, p.column);
|
|
1913
|
+
} catch (e) {
|
|
1914
|
+
if (!this.recover || !(e instanceof ParseError || e instanceof LexError)) throw e;
|
|
1915
|
+
const at = token;
|
|
1916
|
+
this.record(new ParseError(
|
|
1917
|
+
`In '\${${p.raw}}': ${e.message.replace(/ \(\d+:\d+\)$/, "")}`,
|
|
1918
|
+
at.line.start,
|
|
1919
|
+
at.column.start
|
|
1920
|
+
));
|
|
1921
|
+
expression = { type: "ErrorExpression", ...spanFrom(at, at) };
|
|
1922
|
+
}
|
|
1580
1923
|
parts.push({ kind: "expression", expression });
|
|
1581
1924
|
}
|
|
1582
1925
|
}
|
|
@@ -1614,7 +1957,53 @@ var Parser = class {
|
|
|
1614
1957
|
this.error("Expected identifier or '('");
|
|
1615
1958
|
}
|
|
1616
1959
|
while (true) {
|
|
1960
|
+
if (this.checkPunctuator("?") && this.touchesNext()) {
|
|
1961
|
+
const next = this.peek(1);
|
|
1962
|
+
const punct = next.type === "Punctuator" ? String(next.value) : void 0;
|
|
1963
|
+
if (punct === "." && this.peek(2).type === "Identifier") {
|
|
1964
|
+
this.advance();
|
|
1965
|
+
this.advance();
|
|
1966
|
+
const prop = this.parseIdentifier();
|
|
1967
|
+
base = { type: "MemberExpression", object: base, property: prop, optional: true, ...spanFrom(base, prop) };
|
|
1968
|
+
continue;
|
|
1969
|
+
}
|
|
1970
|
+
if (punct === "." && this.punctuatorAt(2, "(")) {
|
|
1971
|
+
this.advance();
|
|
1972
|
+
this.advance();
|
|
1973
|
+
const args = this.parseCallArguments();
|
|
1974
|
+
base = {
|
|
1975
|
+
type: "CallExpression",
|
|
1976
|
+
callee: base,
|
|
1977
|
+
arguments: args,
|
|
1978
|
+
optional: true,
|
|
1979
|
+
...spanFrom(base, this.previous())
|
|
1980
|
+
};
|
|
1981
|
+
continue;
|
|
1982
|
+
}
|
|
1983
|
+
if (punct === ":" && this.startsMethodCall(1)) {
|
|
1984
|
+
this.advance();
|
|
1985
|
+
this.advance();
|
|
1986
|
+
const method = this.parseIdentifier();
|
|
1987
|
+
const typeArguments = this.tryCallTypeArguments();
|
|
1988
|
+
const args = this.parseCallArguments();
|
|
1989
|
+
base = {
|
|
1990
|
+
type: "MethodCallExpression",
|
|
1991
|
+
object: base,
|
|
1992
|
+
method,
|
|
1993
|
+
arguments: args,
|
|
1994
|
+
typeArguments,
|
|
1995
|
+
optional: true,
|
|
1996
|
+
...spanFrom(base, this.previous())
|
|
1997
|
+
};
|
|
1998
|
+
continue;
|
|
1999
|
+
}
|
|
2000
|
+
}
|
|
1617
2001
|
if (this.matchPunctuator(".")) {
|
|
2002
|
+
if (this.recover && !this.checkType("Identifier")) {
|
|
2003
|
+
this.softError("Expected identifier");
|
|
2004
|
+
base = { type: "ErrorExpression", ...spanFrom(base, this.previous()) };
|
|
2005
|
+
break;
|
|
2006
|
+
}
|
|
1618
2007
|
const prop = this.parseIdentifier();
|
|
1619
2008
|
base = { type: "MemberExpression", object: base, property: prop, ...spanFrom(base, prop) };
|
|
1620
2009
|
continue;
|
|
@@ -1628,17 +2017,33 @@ var Parser = class {
|
|
|
1628
2017
|
if (this.checkPunctuator(":") && this.startsMethodCall()) {
|
|
1629
2018
|
this.advance();
|
|
1630
2019
|
const method = this.parseIdentifier();
|
|
2020
|
+
const typeArguments = this.tryCallTypeArguments();
|
|
1631
2021
|
const args = this.parseCallArguments();
|
|
1632
2022
|
base = {
|
|
1633
2023
|
type: "MethodCallExpression",
|
|
1634
2024
|
object: base,
|
|
1635
2025
|
method,
|
|
1636
2026
|
arguments: args,
|
|
2027
|
+
typeArguments,
|
|
1637
2028
|
...spanFrom(base, this.previous())
|
|
1638
2029
|
};
|
|
1639
2030
|
continue;
|
|
1640
2031
|
}
|
|
1641
|
-
if (this.
|
|
2032
|
+
if (this.checkOperator("<")) {
|
|
2033
|
+
const typeArguments = this.tryCallTypeArguments();
|
|
2034
|
+
if (typeArguments) {
|
|
2035
|
+
const args = this.parseCallArguments();
|
|
2036
|
+
base = {
|
|
2037
|
+
type: "CallExpression",
|
|
2038
|
+
callee: base,
|
|
2039
|
+
arguments: args,
|
|
2040
|
+
typeArguments,
|
|
2041
|
+
...spanFrom(base, this.previous())
|
|
2042
|
+
};
|
|
2043
|
+
continue;
|
|
2044
|
+
}
|
|
2045
|
+
}
|
|
2046
|
+
if (this.startsCallArguments()) {
|
|
1642
2047
|
const args = this.parseCallArguments();
|
|
1643
2048
|
base = {
|
|
1644
2049
|
type: "CallExpression",
|
|
@@ -1652,6 +2057,11 @@ var Parser = class {
|
|
|
1652
2057
|
}
|
|
1653
2058
|
return base;
|
|
1654
2059
|
}
|
|
2060
|
+
/** Is the token `ahead` places on the punctuator `value`? */
|
|
2061
|
+
punctuatorAt(ahead, value) {
|
|
2062
|
+
const token = this.peek(ahead);
|
|
2063
|
+
return token.type === "Punctuator" && token.value === value;
|
|
2064
|
+
}
|
|
1655
2065
|
/** An assignment target after the first: a prefix expression (`a.b`,
|
|
1656
2066
|
* `a[i]`, `a`) or a nested destructuring pattern. */
|
|
1657
2067
|
parseAssignTarget() {
|
|
@@ -1659,14 +2069,52 @@ var Parser = class {
|
|
|
1659
2069
|
if (this.checkPunctuator("[")) return this.parseArrayPattern();
|
|
1660
2070
|
return this.parsePrefixExpression();
|
|
1661
2071
|
}
|
|
2072
|
+
/** Does a call's argument list start here? Lua's three forms: `(`, a
|
|
2073
|
+
* string, or a table. */
|
|
2074
|
+
startsCallArguments() {
|
|
2075
|
+
return this.checkPunctuator("(") || this.checkPunctuator("{") || this.checkType("InterpolatedString") || this.checkType("Literal") && this.current().kind === "string";
|
|
2076
|
+
}
|
|
2077
|
+
/** `f<A, B>(x)` — type arguments, when that is what this is. `a < b > (c)`
|
|
2078
|
+
* is three operators, and only what follows the `>` tells them apart, so
|
|
2079
|
+
* this reads ahead and puts the cursor back when the guess was wrong. */
|
|
2080
|
+
tryCallTypeArguments() {
|
|
2081
|
+
if (!this.checkOperator("<")) return void 0;
|
|
2082
|
+
const start = this.cursor;
|
|
2083
|
+
const errors = this.errors.length;
|
|
2084
|
+
try {
|
|
2085
|
+
this.advance();
|
|
2086
|
+
const list = [this.parseTypeArgument()];
|
|
2087
|
+
while (this.matchPunctuator(",") && !this.checkOperator(">")) list.push(this.parseTypeArgument());
|
|
2088
|
+
this.expectOperator(">");
|
|
2089
|
+
if (!this.startsCallArguments()) throw new ParseRecover("not a call");
|
|
2090
|
+
return list;
|
|
2091
|
+
} catch (e) {
|
|
2092
|
+
if (!(e instanceof ParseError || e instanceof ParseRecover)) throw e;
|
|
2093
|
+
this.cursor = start;
|
|
2094
|
+
this.errors.length = errors;
|
|
2095
|
+
return void 0;
|
|
2096
|
+
}
|
|
2097
|
+
}
|
|
1662
2098
|
parseCallArguments() {
|
|
1663
2099
|
if (this.matchPunctuator("(")) {
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
2100
|
+
const list = [];
|
|
2101
|
+
const stop = () => this.checkPunctuator(",");
|
|
2102
|
+
if (!this.checkPunctuator(")")) {
|
|
2103
|
+
while (true) {
|
|
2104
|
+
if (this.recover && this.onNewLine() && this.startsTableField() && !this.startsMethodCall(1)) break;
|
|
2105
|
+
const before = this.cursor;
|
|
2106
|
+
const argument = this.expressionOr(stop);
|
|
2107
|
+
if (argument.type !== "ErrorExpression" || this.cursor > before || list.length) list.push(argument);
|
|
2108
|
+
if (this.matchPunctuator(",") && !this.checkPunctuator(")")) continue;
|
|
2109
|
+
if (!this.recover || this.checkPunctuator(")")) break;
|
|
2110
|
+
if (this.onNewLine() && (this.checkType("Identifier") || this.checkType("Keyword"))) break;
|
|
2111
|
+
this.softError("Expected ',' or ')'");
|
|
2112
|
+
this.skip(stop, this.cursor, "expression");
|
|
2113
|
+
if (this.matchPunctuator(",")) continue;
|
|
2114
|
+
break;
|
|
2115
|
+
}
|
|
1667
2116
|
}
|
|
1668
|
-
|
|
1669
|
-
this.expectPunctuator(")");
|
|
2117
|
+
this.expectCloser(")");
|
|
1670
2118
|
return list;
|
|
1671
2119
|
}
|
|
1672
2120
|
const t = this.current();
|
|
@@ -1693,57 +2141,89 @@ var Parser = class {
|
|
|
1693
2141
|
const start = this.current();
|
|
1694
2142
|
this.expectPunctuator("{");
|
|
1695
2143
|
const fields = [];
|
|
2144
|
+
const stop = () => this.checkPunctuator(",") || this.checkPunctuator(";") || this.onNewLine() && this.startsTableField();
|
|
1696
2145
|
while (!this.checkPunctuator("}")) {
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
this.expectPunctuator(":");
|
|
1705
|
-
const value = this.parseExpression();
|
|
1706
|
-
fields.push({ type: "TableFieldComputed", key, value });
|
|
1707
|
-
} else if (this.checkType("Literal") && this.current().kind === "string") {
|
|
1708
|
-
const t = this.advance();
|
|
1709
|
-
const key = { type: "StringLiteral", value: t.value, raw: t.raw, ...spanFrom(t, t) };
|
|
1710
|
-
this.expectPunctuator(":");
|
|
1711
|
-
const value = this.parseExpression();
|
|
1712
|
-
fields.push({ type: "TableFieldNamed", key, value });
|
|
1713
|
-
} else if (this.checkType("Identifier") && this.peek(1).type === "Punctuator" && this.peek(1).value === ":") {
|
|
1714
|
-
const key = this.parseIdentifier();
|
|
1715
|
-
this.expectPunctuator(":");
|
|
1716
|
-
const value = this.parseExpression();
|
|
1717
|
-
fields.push({ type: "TableFieldNamed", key, value });
|
|
1718
|
-
} else if (this.checkType("Identifier")) {
|
|
1719
|
-
const name = this.parseIdentifier();
|
|
1720
|
-
fields.push({ type: "TableFieldShorthand", name });
|
|
1721
|
-
} else {
|
|
1722
|
-
this.error("Expected object field ('key: value', '[expr]: value', shorthand, or '...spread'); use '[...]' for arrays");
|
|
2146
|
+
const field = this.attempt(() => this.parseTableField(stop), stop, () => void 0);
|
|
2147
|
+
if (field) fields.push(field);
|
|
2148
|
+
if (this.matchPunctuator(",") || this.matchPunctuator(";")) continue;
|
|
2149
|
+
if (!this.recover || this.checkPunctuator("}")) break;
|
|
2150
|
+
if (this.onNewLine() && this.startsTableField()) {
|
|
2151
|
+
this.softError("Expected ','");
|
|
2152
|
+
continue;
|
|
1723
2153
|
}
|
|
2154
|
+
if (this.isAtEnd() || this.onNewLine() && this.checkType("Keyword")) break;
|
|
2155
|
+
this.softError("Expected ',' or '}'");
|
|
2156
|
+
const before = this.cursor;
|
|
2157
|
+
this.skip(stop, this.cursor, "expression");
|
|
1724
2158
|
if (this.matchPunctuator(",") || this.matchPunctuator(";")) continue;
|
|
2159
|
+
if (this.cursor > before && this.onNewLine() && this.startsTableField()) continue;
|
|
1725
2160
|
break;
|
|
1726
2161
|
}
|
|
1727
|
-
this.
|
|
2162
|
+
this.expectCloser("}");
|
|
1728
2163
|
return { type: "TableExpression", fields, ...spanFrom(start, this.previous()) };
|
|
1729
2164
|
}
|
|
2165
|
+
/** Does a `key: value` field, or a spread, start here? */
|
|
2166
|
+
startsTableField() {
|
|
2167
|
+
const next = this.peek(1);
|
|
2168
|
+
const colon = next.type === "Punctuator" && next.value === ":";
|
|
2169
|
+
if (this.checkType("Identifier")) return colon;
|
|
2170
|
+
if (this.checkType("Literal") && this.current().kind === "string") return colon;
|
|
2171
|
+
return this.checkOperator("...");
|
|
2172
|
+
}
|
|
2173
|
+
parseTableField(stop) {
|
|
2174
|
+
if (this.checkOperator("...")) {
|
|
2175
|
+
this.advance();
|
|
2176
|
+
return { type: "TableFieldSpread", argument: this.expressionOr(stop) };
|
|
2177
|
+
}
|
|
2178
|
+
if (this.matchPunctuator("[")) {
|
|
2179
|
+
const key = this.expressionOr(() => this.checkPunctuator("]"));
|
|
2180
|
+
this.expectPunctuator("]");
|
|
2181
|
+
this.expectPunctuator(":");
|
|
2182
|
+
return { type: "TableFieldComputed", key, value: this.expressionOr(stop) };
|
|
2183
|
+
}
|
|
2184
|
+
if (this.checkType("Literal") && this.current().kind === "string") {
|
|
2185
|
+
const t = this.advance();
|
|
2186
|
+
const key = { type: "StringLiteral", value: t.value, raw: t.raw, ...spanFrom(t, t) };
|
|
2187
|
+
this.expectPunctuator(":");
|
|
2188
|
+
return { type: "TableFieldNamed", key, value: this.expressionOr(stop) };
|
|
2189
|
+
}
|
|
2190
|
+
if (this.checkType("Identifier") && this.peek(1).type === "Punctuator" && this.peek(1).value === ":") {
|
|
2191
|
+
const key = this.parseIdentifier();
|
|
2192
|
+
this.expectPunctuator(":");
|
|
2193
|
+
return { type: "TableFieldNamed", key, value: this.expressionOr(stop) };
|
|
2194
|
+
}
|
|
2195
|
+
if (this.checkType("Identifier")) {
|
|
2196
|
+
return { type: "TableFieldShorthand", name: this.parseIdentifier() };
|
|
2197
|
+
}
|
|
2198
|
+
this.error("Expected object field ('key: value', '[expr]: value', shorthand, or '...spread'); use '[...]' for arrays");
|
|
2199
|
+
}
|
|
1730
2200
|
// `[1, 2, 3]` — array literal (trailing comma allowed).
|
|
1731
2201
|
parseArrayExpression() {
|
|
1732
2202
|
const start = this.current();
|
|
1733
2203
|
this.expectPunctuator("[");
|
|
1734
2204
|
const elements = [];
|
|
2205
|
+
const stop = () => this.checkPunctuator(",");
|
|
1735
2206
|
while (!this.checkPunctuator("]")) {
|
|
1736
2207
|
if (this.checkOperator("...")) {
|
|
1737
2208
|
const dots = this.advance();
|
|
1738
|
-
const argument = this.
|
|
2209
|
+
const argument = this.expressionOr(stop);
|
|
1739
2210
|
elements.push({ type: "SpreadElement", argument, ...spanFrom(dots, argument) });
|
|
1740
2211
|
} else {
|
|
1741
|
-
elements.push(this.
|
|
2212
|
+
elements.push(this.expressionOr(stop));
|
|
2213
|
+
}
|
|
2214
|
+
if (this.matchPunctuator(",")) continue;
|
|
2215
|
+
if (!this.recover || this.checkPunctuator("]")) break;
|
|
2216
|
+
if (this.onNewLine() && this.isExpressionStart() && !this.checkType("Keyword")) {
|
|
2217
|
+
this.softError("Expected ','");
|
|
2218
|
+
continue;
|
|
1742
2219
|
}
|
|
2220
|
+
if (this.isAtEnd() || this.onNewLine() && this.checkType("Keyword")) break;
|
|
2221
|
+
this.softError("Expected ',' or ']'");
|
|
2222
|
+
this.skip(stop, this.cursor, "expression");
|
|
1743
2223
|
if (this.matchPunctuator(",")) continue;
|
|
1744
2224
|
break;
|
|
1745
2225
|
}
|
|
1746
|
-
this.
|
|
2226
|
+
this.expectCloser("]");
|
|
1747
2227
|
return { type: "ArrayExpression", elements, ...spanFrom(start, this.previous()) };
|
|
1748
2228
|
}
|
|
1749
2229
|
// ============================================================
|
|
@@ -1776,7 +2256,7 @@ var Parser = class {
|
|
|
1776
2256
|
};
|
|
1777
2257
|
}
|
|
1778
2258
|
if (topLevel && this.matchPunctuator(":")) {
|
|
1779
|
-
target.typeAnnotation = this.
|
|
2259
|
+
target.typeAnnotation = this.typeOr(() => this.checkOperator("=") || this.checkPunctuator(","));
|
|
1780
2260
|
}
|
|
1781
2261
|
return target;
|
|
1782
2262
|
}
|
|
@@ -1935,12 +2415,13 @@ var Parser = class {
|
|
|
1935
2415
|
}
|
|
1936
2416
|
const optional2 = this.matchPunctuator("?");
|
|
1937
2417
|
let typeAnnotation;
|
|
2418
|
+
const paramEnd = () => this.checkPunctuator(",");
|
|
1938
2419
|
if (this.matchPunctuator(":")) {
|
|
1939
|
-
typeAnnotation = this.
|
|
2420
|
+
typeAnnotation = this.typeOr(() => paramEnd() || this.checkOperator("="));
|
|
1940
2421
|
}
|
|
1941
2422
|
let def;
|
|
1942
2423
|
if (this.matchOperator("=")) {
|
|
1943
|
-
def = this.
|
|
2424
|
+
def = this.expressionOr(paramEnd);
|
|
1944
2425
|
}
|
|
1945
2426
|
params.push({
|
|
1946
2427
|
type: "FunctionParameter",
|
|
@@ -1951,7 +2432,7 @@ var Parser = class {
|
|
|
1951
2432
|
optional: optional2 || void 0,
|
|
1952
2433
|
...spanFrom(paramStart, this.previous())
|
|
1953
2434
|
});
|
|
1954
|
-
if (this.matchPunctuator(",")) continue;
|
|
2435
|
+
if (this.matchPunctuator(",") && !this.checkPunctuator(")")) continue;
|
|
1955
2436
|
break;
|
|
1956
2437
|
}
|
|
1957
2438
|
}
|
|
@@ -1960,7 +2441,9 @@ var Parser = class {
|
|
|
1960
2441
|
let predicate;
|
|
1961
2442
|
if (this.matchPunctuator(":")) {
|
|
1962
2443
|
predicate = this.tryParseTypePredicate();
|
|
1963
|
-
if (!predicate)
|
|
2444
|
+
if (!predicate) {
|
|
2445
|
+
returnType = this.attempt(() => this.parseTypeOrTypePackReference(), () => false, () => void 0);
|
|
2446
|
+
}
|
|
1964
2447
|
}
|
|
1965
2448
|
return { start, generics, params, hasVarargs, varargTypeAnnotation, returnType, predicate };
|
|
1966
2449
|
}
|
|
@@ -2006,10 +2489,10 @@ var Parser = class {
|
|
|
2006
2489
|
}
|
|
2007
2490
|
return void 0;
|
|
2008
2491
|
}
|
|
2009
|
-
parseFunctionBody() {
|
|
2492
|
+
parseFunctionBody(opener) {
|
|
2010
2493
|
const head = this.parseFunctionHead();
|
|
2011
|
-
const body = this.parseBlock();
|
|
2012
|
-
this.
|
|
2494
|
+
const body = this.parseBlock(opener);
|
|
2495
|
+
this.expectEnd(opener);
|
|
2013
2496
|
return {
|
|
2014
2497
|
type: "FunctionBody",
|
|
2015
2498
|
generics: head.generics,
|
|
@@ -2034,9 +2517,9 @@ var Parser = class {
|
|
|
2034
2517
|
...spanFrom(head.start, this.previous())
|
|
2035
2518
|
};
|
|
2036
2519
|
}
|
|
2037
|
-
headToBody(head) {
|
|
2038
|
-
const body = this.parseBlock();
|
|
2039
|
-
this.
|
|
2520
|
+
headToBody(head, opener) {
|
|
2521
|
+
const body = this.parseBlock(opener);
|
|
2522
|
+
this.expectEnd(opener);
|
|
2040
2523
|
return {
|
|
2041
2524
|
type: "FunctionBody",
|
|
2042
2525
|
generics: head.generics,
|
|
@@ -2243,7 +2726,7 @@ var Parser = class {
|
|
|
2243
2726
|
this.advance();
|
|
2244
2727
|
if (!this.checkOperator(">")) {
|
|
2245
2728
|
typeArguments.push(this.parseTypeArgument());
|
|
2246
|
-
while (this.matchPunctuator(",")) {
|
|
2729
|
+
while (this.matchPunctuator(",") && !this.checkOperator(">")) {
|
|
2247
2730
|
typeArguments.push(this.parseTypeArgument());
|
|
2248
2731
|
}
|
|
2249
2732
|
}
|
|
@@ -2294,7 +2777,7 @@ var Parser = class {
|
|
|
2294
2777
|
optional: optional2 || void 0,
|
|
2295
2778
|
...spanFrom(paramStart, this.previous())
|
|
2296
2779
|
});
|
|
2297
|
-
if (this.matchPunctuator(",")) continue;
|
|
2780
|
+
if (this.matchPunctuator(",") && !this.checkPunctuator(")")) continue;
|
|
2298
2781
|
break;
|
|
2299
2782
|
}
|
|
2300
2783
|
}
|
|
@@ -2509,7 +2992,7 @@ var Parser = class {
|
|
|
2509
2992
|
default: def,
|
|
2510
2993
|
...spanFrom(nameTok, this.previous())
|
|
2511
2994
|
});
|
|
2512
|
-
if (this.matchPunctuator(",")) continue;
|
|
2995
|
+
if (this.matchPunctuator(",") && !this.checkOperator(">")) continue;
|
|
2513
2996
|
break;
|
|
2514
2997
|
}
|
|
2515
2998
|
this.expectOperator(">");
|
|
@@ -2536,23 +3019,23 @@ function parseExpressionFromSource(raw) {
|
|
|
2536
3019
|
return expr;
|
|
2537
3020
|
}
|
|
2538
3021
|
function parseWithRecovery(source) {
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
const
|
|
2555
|
-
return { program, errors:
|
|
3022
|
+
const lexErrors = [];
|
|
3023
|
+
const comments = [];
|
|
3024
|
+
const tokens = tokenize(source, { errors: lexErrors, comments });
|
|
3025
|
+
const lexed = lexErrors.map((e) => new ParseError(e.message.replace(/ \(\d+:\d+\)$/, ""), e.line, e.column));
|
|
3026
|
+
const first = new Parser(tokens, { recover: true });
|
|
3027
|
+
let program = first.parseProgram();
|
|
3028
|
+
let errors = first.errors;
|
|
3029
|
+
if (first.missingEnd) {
|
|
3030
|
+
const second = new Parser(tokens, { recover: true, indentation: true });
|
|
3031
|
+
const reparsed = second.parseProgram();
|
|
3032
|
+
if (second.errors.length <= errors.length) {
|
|
3033
|
+
program = reparsed;
|
|
3034
|
+
errors = second.errors;
|
|
3035
|
+
}
|
|
3036
|
+
}
|
|
3037
|
+
const all = [...lexed, ...errors].sort((a, b) => a.line - b.line || a.column - b.column);
|
|
3038
|
+
return { program, errors: all, directives: readDirectives(comments, tokens) };
|
|
2556
3039
|
}
|
|
2557
3040
|
|
|
2558
3041
|
// src/ast/nodes.ts
|
|
@@ -2591,19 +3074,24 @@ function childScope(parent) {
|
|
|
2591
3074
|
return { parent, declarations: /* @__PURE__ */ new Map() };
|
|
2592
3075
|
}
|
|
2593
3076
|
var Analyzer = class {
|
|
2594
|
-
nextId = 0;
|
|
2595
|
-
bindingOf = /* @__PURE__ */ new Map();
|
|
2596
|
-
bindings = /* @__PURE__ */ new Map();
|
|
2597
|
-
diagnostics = [];
|
|
2598
|
-
globalScope = { parent: null, declarations: /* @__PURE__ */ new Map() };
|
|
2599
3077
|
constructor(options) {
|
|
3078
|
+
this.options = options;
|
|
2600
3079
|
for (const name of options.builtinGlobals ?? []) {
|
|
2601
3080
|
const id = this.getOrCreateGlobalBinding(name);
|
|
2602
3081
|
this.bindings.get(id).isBuiltin = true;
|
|
2603
3082
|
}
|
|
2604
3083
|
}
|
|
3084
|
+
options;
|
|
3085
|
+
nextId = 0;
|
|
3086
|
+
bindingOf = /* @__PURE__ */ new Map();
|
|
3087
|
+
bindings = /* @__PURE__ */ new Map();
|
|
3088
|
+
diagnostics = [];
|
|
3089
|
+
globalScope = { parent: null, declarations: /* @__PURE__ */ new Map() };
|
|
2605
3090
|
run(program) {
|
|
2606
|
-
this.
|
|
3091
|
+
this.moduleScope = childScope(this.globalScope);
|
|
3092
|
+
this.visitBlock(program.body, this.moduleScope);
|
|
3093
|
+
this.resolveForwardReferences();
|
|
3094
|
+
if (this.options.reportUndeclared) this.reportUndeclared(program);
|
|
2607
3095
|
return {
|
|
2608
3096
|
bindingOf: this.bindingOf,
|
|
2609
3097
|
bindings: this.bindings,
|
|
@@ -2611,8 +3099,101 @@ var Analyzer = class {
|
|
|
2611
3099
|
globalsByName: this.globalScope.declarations
|
|
2612
3100
|
};
|
|
2613
3101
|
}
|
|
3102
|
+
// ---------------- hoisting ----------------
|
|
3103
|
+
//
|
|
3104
|
+
// As in TypeScript, and as the bundle runs a module:
|
|
3105
|
+
//
|
|
3106
|
+
// - a function declaration is visible to its whole block, before it too;
|
|
3107
|
+
// - a name the module declares at its top level is visible to code that
|
|
3108
|
+
// runs later — function bodies, and `typeof` in a type — even where that
|
|
3109
|
+
// code is written above the declaration. A bundle declares every
|
|
3110
|
+
// top-level name before any of the module runs, so this is what happens.
|
|
3111
|
+
//
|
|
3112
|
+
// A read of a later `const` straight in the module's own flow is not
|
|
3113
|
+
// resolved to it: that still reads what was there before.
|
|
3114
|
+
moduleScope = this.globalScope;
|
|
3115
|
+
/** How many function bodies enclose the walk. */
|
|
3116
|
+
functionDepth = 0;
|
|
3117
|
+
/** Function declarations already declared by their block's hoisting, with
|
|
3118
|
+
* the function depth of that block. */
|
|
3119
|
+
hoisted = /* @__PURE__ */ new Map();
|
|
3120
|
+
/** Names that resolved to a global from code that runs later, with the
|
|
3121
|
+
* scope they were read in. */
|
|
3122
|
+
deferredGlobals = [];
|
|
3123
|
+
hoistFunctions(block, scope) {
|
|
3124
|
+
for (const statement of block.statements) {
|
|
3125
|
+
const declaration = statement.type === "ExportStatement" ? statement.declaration : statement;
|
|
3126
|
+
if (declaration.type !== "FunctionDeclaration") continue;
|
|
3127
|
+
this.declare(scope, declaration.name.name, "local", declaration.name, true, "function");
|
|
3128
|
+
this.hoisted.set(declaration.name, scope === this.moduleScope ? -1 : this.functionDepth);
|
|
3129
|
+
}
|
|
3130
|
+
}
|
|
3131
|
+
/** Inside a function, a function declared further down its block is
|
|
3132
|
+
* hoisted only as a name: code that runs later (another function's body)
|
|
3133
|
+
* can call it, but a call straight in the block before the declaration
|
|
3134
|
+
* finds nothing there yet. At a module's top level the whole function is
|
|
3135
|
+
* hoisted, and this does not apply. */
|
|
3136
|
+
checkUseBeforeDefine(identifier, id) {
|
|
3137
|
+
const binding = this.bindings.get(id);
|
|
3138
|
+
if (binding.declaredBy !== "function" || this.typeQueryDepth > 0) return;
|
|
3139
|
+
const declaration = binding.declarationNode;
|
|
3140
|
+
const depth = declaration && this.hoisted.get(declaration);
|
|
3141
|
+
if (depth === void 0 || depth !== this.functionDepth) return;
|
|
3142
|
+
const before = identifier.line.start < declaration.line.start || identifier.line.start === declaration.line.start && identifier.column.start < declaration.column.start;
|
|
3143
|
+
if (!before) return;
|
|
3144
|
+
this.diagnostics.push({
|
|
3145
|
+
node: identifier,
|
|
3146
|
+
message: `'${binding.name}' is used before its definition: inside a function, a function declared further down is only there once its declaration has run`,
|
|
3147
|
+
kind: "use-before-define"
|
|
3148
|
+
});
|
|
3149
|
+
}
|
|
3150
|
+
noteDeferred(identifier, scope, id, assignment) {
|
|
3151
|
+
if (this.functionDepth === 0 && this.typeQueryDepth === 0) return;
|
|
3152
|
+
if (this.bindings.get(id).kind !== "global") return;
|
|
3153
|
+
this.deferredGlobals.push({ node: identifier, scope, assignment });
|
|
3154
|
+
}
|
|
3155
|
+
/** Point each deferred read of a global at the declaration of that name
|
|
3156
|
+
* that turned up later — in the module, or in any block around the code
|
|
3157
|
+
* that reads it. Such code runs after the declaration has: a closure
|
|
3158
|
+
* written inside a value reads the name the value is bound to. */
|
|
3159
|
+
resolveForwardReferences() {
|
|
3160
|
+
for (const { node, scope, assignment } of this.deferredGlobals) {
|
|
3161
|
+
const localId = this.lookup(scope, node.name);
|
|
3162
|
+
const globalId = this.bindingOf.get(node);
|
|
3163
|
+
if (localId === void 0 || globalId === void 0 || localId === globalId) continue;
|
|
3164
|
+
const global = this.bindings.get(globalId);
|
|
3165
|
+
const at = global.references.indexOf(node);
|
|
3166
|
+
if (at >= 0) global.references.splice(at, 1);
|
|
3167
|
+
if (global.declarationNode === node) global.declarationNode = void 0;
|
|
3168
|
+
if (!global.isBuiltin && !global.references.length && global.declarationNode === void 0) {
|
|
3169
|
+
this.bindings.delete(globalId);
|
|
3170
|
+
this.globalScope.declarations.delete(node.name);
|
|
3171
|
+
}
|
|
3172
|
+
this.bindingOf.set(node, localId);
|
|
3173
|
+
this.bindings.get(localId).references.push(node);
|
|
3174
|
+
if (assignment) this.checkConstAssign(localId, node);
|
|
3175
|
+
else if (this.typeQueryDepth === 0) this.checkTypeOnly(localId, node);
|
|
3176
|
+
}
|
|
3177
|
+
}
|
|
3178
|
+
/** Every read of a global nothing declares. A global assigned somewhere
|
|
3179
|
+
* in the file (`x = 1`) is Lua's implicit global, and is left alone. */
|
|
3180
|
+
reportUndeclared(program) {
|
|
3181
|
+
const declared = /* @__PURE__ */ new Set();
|
|
3182
|
+
for (const statement of program.body.statements) {
|
|
3183
|
+
if (statement.type === "DeclareStatement") declared.add(statement.name);
|
|
3184
|
+
}
|
|
3185
|
+
const found = [];
|
|
3186
|
+
for (const binding of this.bindings.values()) {
|
|
3187
|
+
if (!isUnassignedGlobal(binding) || declared.has(binding.name)) continue;
|
|
3188
|
+
for (const reference of binding.references) {
|
|
3189
|
+
found.push({ node: reference, message: `Cannot find name '${binding.name}'`, kind: "undeclared" });
|
|
3190
|
+
}
|
|
3191
|
+
}
|
|
3192
|
+
found.sort((a, b) => a.node.line.start - b.node.line.start || a.node.column.start - b.node.column.start);
|
|
3193
|
+
this.diagnostics.push(...found);
|
|
3194
|
+
}
|
|
2614
3195
|
// ---------------- declaration / resolution primitives ----------------
|
|
2615
|
-
declare(scope, name, kind, node, isConst = false) {
|
|
3196
|
+
declare(scope, name, kind, node, isConst = false, declaredBy) {
|
|
2616
3197
|
if (scope.declarations.has(name) && scope !== this.globalScope) {
|
|
2617
3198
|
this.diagnostics.push({
|
|
2618
3199
|
node,
|
|
@@ -2621,7 +3202,7 @@ var Analyzer = class {
|
|
|
2621
3202
|
});
|
|
2622
3203
|
}
|
|
2623
3204
|
const id = this.nextId++;
|
|
2624
|
-
this.bindings.set(id, { id, name, kind, declarationNode: node, references: [], isConst });
|
|
3205
|
+
this.bindings.set(id, { id, name, kind, declarationNode: node, references: [], isConst, declaredBy });
|
|
2625
3206
|
scope.declarations.set(name, id);
|
|
2626
3207
|
return id;
|
|
2627
3208
|
}
|
|
@@ -2655,6 +3236,21 @@ var Analyzer = class {
|
|
|
2655
3236
|
const id = this.resolve(scope, identifier.name);
|
|
2656
3237
|
this.bindingOf.set(identifier, id);
|
|
2657
3238
|
this.bindings.get(id).references.push(identifier);
|
|
3239
|
+
if (this.typeQueryDepth === 0) this.checkTypeOnly(id, identifier);
|
|
3240
|
+
this.checkUseBeforeDefine(identifier, id);
|
|
3241
|
+
this.noteDeferred(identifier, scope, id, false);
|
|
3242
|
+
}
|
|
3243
|
+
/** Inside `typeof x` in a type, where a type-only import may be named. */
|
|
3244
|
+
typeQueryDepth = 0;
|
|
3245
|
+
/** A name from `import type` used as a value. */
|
|
3246
|
+
checkTypeOnly(id, node) {
|
|
3247
|
+
const b = this.bindings.get(id);
|
|
3248
|
+
if (b.declaredBy !== "type") return;
|
|
3249
|
+
this.diagnostics.push({
|
|
3250
|
+
node,
|
|
3251
|
+
message: `'${b.name}' is imported with 'import type' and can only be used as a type`,
|
|
3252
|
+
kind: "type-only"
|
|
3253
|
+
});
|
|
2658
3254
|
}
|
|
2659
3255
|
/** For assignment-like targets (`x = ...`, `function foo() end`): if
|
|
2660
3256
|
* this resolved to a global with no declaration site yet, treat this
|
|
@@ -2671,14 +3267,35 @@ var Analyzer = class {
|
|
|
2671
3267
|
this.bindingOf.set(identifier, id);
|
|
2672
3268
|
this.bindings.get(id).references.push(identifier);
|
|
2673
3269
|
this.recordPossibleGlobalDefinition(id, identifier);
|
|
3270
|
+
this.checkTypeOnly(id, identifier);
|
|
2674
3271
|
this.checkConstAssign(id, identifier);
|
|
3272
|
+
this.noteDeferred(identifier, scope, id, true);
|
|
3273
|
+
}
|
|
3274
|
+
/** `Module.x = 1` through `import * as Module`: a module's exports belong
|
|
3275
|
+
* to it and are read-only, as in ES modules. Deeper writes (`Module.x.y`)
|
|
3276
|
+
* change the value, not the module, and are fine. */
|
|
3277
|
+
checkModuleWrite(target) {
|
|
3278
|
+
if (target.type !== "MemberExpression" && target.type !== "IndexExpression") return;
|
|
3279
|
+
if (target.object.type !== "Identifier") return;
|
|
3280
|
+
const id = this.bindingOf.get(target.object);
|
|
3281
|
+
if (id !== void 0 && this.bindings.get(id).declaredBy === "namespace") {
|
|
3282
|
+
this.moduleWriteError(target.object.name, target);
|
|
3283
|
+
}
|
|
3284
|
+
}
|
|
3285
|
+
moduleWriteError(name, node) {
|
|
3286
|
+
this.diagnostics.push({
|
|
3287
|
+
node,
|
|
3288
|
+
message: `Cannot assign to a member of '${name}' \u2014 a module's exports are read-only`,
|
|
3289
|
+
kind: "const-assign"
|
|
3290
|
+
});
|
|
2675
3291
|
}
|
|
2676
3292
|
checkConstAssign(id, node) {
|
|
2677
3293
|
const b = this.bindings.get(id);
|
|
3294
|
+
if (b.declaredBy === "type") return;
|
|
2678
3295
|
if (b.isConst) {
|
|
2679
3296
|
this.diagnostics.push({
|
|
2680
3297
|
node,
|
|
2681
|
-
message: `Cannot assign to '${b.name}' \u2014 it is a const`,
|
|
3298
|
+
message: `Cannot assign to '${b.name}' \u2014 it is ${b.declaredBy === "import" || b.declaredBy === "namespace" ? "an import" : b.declaredBy === "function" ? "a function" : "a const"}`,
|
|
2682
3299
|
kind: "const-assign"
|
|
2683
3300
|
});
|
|
2684
3301
|
}
|
|
@@ -2719,6 +3336,7 @@ var Analyzer = class {
|
|
|
2719
3336
|
const id = this.resolve(scope, t.name);
|
|
2720
3337
|
this.bindingOf.set(t, id);
|
|
2721
3338
|
this.recordPossibleGlobalDefinition(id, t);
|
|
3339
|
+
this.checkTypeOnly(id, t);
|
|
2722
3340
|
this.checkConstAssign(id, t);
|
|
2723
3341
|
return;
|
|
2724
3342
|
}
|
|
@@ -2744,6 +3362,7 @@ var Analyzer = class {
|
|
|
2744
3362
|
}
|
|
2745
3363
|
// ---------------- blocks / statements ----------------
|
|
2746
3364
|
visitBlock(block, scope) {
|
|
3365
|
+
this.hoistFunctions(block, scope);
|
|
2747
3366
|
for (const stmt of block.statements) this.visitStatement(stmt, scope);
|
|
2748
3367
|
}
|
|
2749
3368
|
/** Visits a block in a *fresh child scope* of `scope` — the common case
|
|
@@ -2762,7 +3381,11 @@ var Analyzer = class {
|
|
|
2762
3381
|
return;
|
|
2763
3382
|
}
|
|
2764
3383
|
case "FunctionDeclaration": {
|
|
2765
|
-
this.declare(scope, stmt.name.name, "local", stmt.name,
|
|
3384
|
+
if (!this.hoisted.has(stmt.name)) this.declare(scope, stmt.name.name, "local", stmt.name, true, "function");
|
|
3385
|
+
for (const signature of stmt.signatures ?? []) {
|
|
3386
|
+
if (signature.name && signature.name !== stmt.name) this.reference(scope, signature.name);
|
|
3387
|
+
}
|
|
3388
|
+
if (stmt.implementationName) this.reference(scope, stmt.implementationName);
|
|
2766
3389
|
for (const signature of stmt.signatures ?? []) this.visitSignature(signature, scope);
|
|
2767
3390
|
this.visitFunctionBody(stmt.func, scope);
|
|
2768
3391
|
return;
|
|
@@ -2772,6 +3395,11 @@ var Analyzer = class {
|
|
|
2772
3395
|
this.referenceAsAssignmentTarget(scope, stmt.target.base);
|
|
2773
3396
|
} else {
|
|
2774
3397
|
this.reference(scope, stmt.target.base);
|
|
3398
|
+
const id = this.bindingOf.get(stmt.target.base);
|
|
3399
|
+
const depth = stmt.target.path.length + (stmt.target.method ? 1 : 0);
|
|
3400
|
+
if (id !== void 0 && depth === 1 && this.bindings.get(id).declaredBy === "namespace") {
|
|
3401
|
+
this.moduleWriteError(stmt.target.base.name, stmt.target);
|
|
3402
|
+
}
|
|
2775
3403
|
}
|
|
2776
3404
|
for (const signature of stmt.signatures ?? []) this.visitSignature(signature, scope);
|
|
2777
3405
|
this.visitFunctionBody(stmt.func, scope, stmt.isMethod);
|
|
@@ -2786,6 +3414,7 @@ var Analyzer = class {
|
|
|
2786
3414
|
this.assignPattern(scope, target);
|
|
2787
3415
|
} else {
|
|
2788
3416
|
this.visitExpression(target, scope);
|
|
3417
|
+
this.checkModuleWrite(target);
|
|
2789
3418
|
}
|
|
2790
3419
|
}
|
|
2791
3420
|
return;
|
|
@@ -2798,6 +3427,7 @@ var Analyzer = class {
|
|
|
2798
3427
|
if (id !== void 0) this.checkConstAssign(id, stmt.target);
|
|
2799
3428
|
} else {
|
|
2800
3429
|
this.visitExpression(stmt.target, scope);
|
|
3430
|
+
this.checkModuleWrite(stmt.target);
|
|
2801
3431
|
}
|
|
2802
3432
|
return;
|
|
2803
3433
|
}
|
|
@@ -2856,14 +3486,22 @@ var Analyzer = class {
|
|
|
2856
3486
|
return;
|
|
2857
3487
|
case "TypeAliasStatement":
|
|
2858
3488
|
case "ExportTypeAliasStatement":
|
|
2859
|
-
this.
|
|
3489
|
+
this.visitGenerics(
|
|
3490
|
+
(stmt.type === "TypeAliasStatement" ? stmt : stmt.alias).generics,
|
|
3491
|
+
scope
|
|
3492
|
+
);
|
|
3493
|
+
this.visitType(stmt.type === "TypeAliasStatement" ? stmt.definition : stmt.alias.definition, scope);
|
|
2860
3494
|
return;
|
|
2861
3495
|
case "ImportStatement": {
|
|
3496
|
+
const typeOnly = stmt.isTypeOnly ? "type" : void 0;
|
|
2862
3497
|
if (stmt.defaultImport) {
|
|
2863
|
-
this.declare(scope, stmt.defaultImport.name, "local", stmt.defaultImport);
|
|
3498
|
+
this.declare(scope, stmt.defaultImport.name, "local", stmt.defaultImport, true, typeOnly ?? "import");
|
|
3499
|
+
}
|
|
3500
|
+
if (stmt.namespaceImport) {
|
|
3501
|
+
this.declare(scope, stmt.namespaceImport.name, "local", stmt.namespaceImport, true, typeOnly ?? "namespace");
|
|
2864
3502
|
}
|
|
2865
3503
|
for (const spec of stmt.specifiers) {
|
|
2866
|
-
this.declare(scope, spec.local.name, "local", spec.local);
|
|
3504
|
+
this.declare(scope, spec.local.name, "local", spec.local, true, typeOnly ?? "import");
|
|
2867
3505
|
}
|
|
2868
3506
|
return;
|
|
2869
3507
|
}
|
|
@@ -2890,6 +3528,7 @@ var Analyzer = class {
|
|
|
2890
3528
|
// ---------------- functions ----------------
|
|
2891
3529
|
visitFunctionBody(func, outerScope, isMethod = false) {
|
|
2892
3530
|
const fnScope = childScope(outerScope);
|
|
3531
|
+
this.visitGenerics(func.generics, fnScope);
|
|
2893
3532
|
func.params.forEach((param, i) => {
|
|
2894
3533
|
const kind = isMethod && i === 0 ? "self" : "param";
|
|
2895
3534
|
this.visitType(param.typeAnnotation, fnScope);
|
|
@@ -2902,14 +3541,28 @@ var Analyzer = class {
|
|
|
2902
3541
|
});
|
|
2903
3542
|
this.visitType(func.varargTypeAnnotation, fnScope);
|
|
2904
3543
|
this.visitType(func.returnType, fnScope);
|
|
2905
|
-
this.
|
|
3544
|
+
this.functionDepth++;
|
|
3545
|
+
try {
|
|
3546
|
+
this.visitBlock(func.body, fnScope);
|
|
3547
|
+
} finally {
|
|
3548
|
+
this.functionDepth--;
|
|
3549
|
+
}
|
|
2906
3550
|
}
|
|
2907
3551
|
/** An overload signature: no body and no bindings, but its types can hold
|
|
2908
3552
|
* a `typeof x`. */
|
|
2909
3553
|
visitSignature(signature, scope) {
|
|
3554
|
+
this.visitGenerics(signature.generics, scope);
|
|
2910
3555
|
for (const param of signature.params) this.visitType(param.typeAnnotation, scope);
|
|
2911
3556
|
this.visitType(signature.returnType, scope);
|
|
2912
3557
|
}
|
|
3558
|
+
/** `<K extends typeof config>` — a constraint is a type like any other,
|
|
3559
|
+
* and the `typeof` in it reads a value. */
|
|
3560
|
+
visitGenerics(generics, scope) {
|
|
3561
|
+
for (const generic of generics ?? []) {
|
|
3562
|
+
this.visitType(generic.constraint, scope);
|
|
3563
|
+
this.visitType(generic.default, scope);
|
|
3564
|
+
}
|
|
3565
|
+
}
|
|
2913
3566
|
/** Resolve the value references inside a type. Only `typeof x` has any —
|
|
2914
3567
|
* everything else in a type names types, which live in their own
|
|
2915
3568
|
* namespace and are not this pass's business. */
|
|
@@ -2922,7 +3575,12 @@ var Analyzer = class {
|
|
|
2922
3575
|
return;
|
|
2923
3576
|
}
|
|
2924
3577
|
if (value.type === "TypeofTypeNode") {
|
|
2925
|
-
this.
|
|
3578
|
+
this.typeQueryDepth++;
|
|
3579
|
+
try {
|
|
3580
|
+
this.visitExpression(value.expression, scope);
|
|
3581
|
+
} finally {
|
|
3582
|
+
this.typeQueryDepth--;
|
|
3583
|
+
}
|
|
2926
3584
|
return;
|
|
2927
3585
|
}
|
|
2928
3586
|
for (const key of Object.keys(value)) {
|
|
@@ -2942,6 +3600,7 @@ var Analyzer = class {
|
|
|
2942
3600
|
case "NumberLiteral":
|
|
2943
3601
|
case "StringLiteral":
|
|
2944
3602
|
case "VarargExpression":
|
|
3603
|
+
case "ErrorExpression":
|
|
2945
3604
|
return;
|
|
2946
3605
|
case "InterpolatedStringExpression":
|
|
2947
3606
|
for (const part of expr.parts) {
|
|
@@ -3026,6 +3685,37 @@ function analyzeScopes(program, options = {}) {
|
|
|
3026
3685
|
return new Analyzer(options).run(program);
|
|
3027
3686
|
}
|
|
3028
3687
|
|
|
3688
|
+
// src/ast/prelude.ts
|
|
3689
|
+
var PRELUDE_SOURCE = `
|
|
3690
|
+
-- In Luau only \`nil\` and \`false\` are falsy: \`0\` and \`""\` are truthy.
|
|
3691
|
+
-- These are what truthiness narrowing computes, made available to write down.
|
|
3692
|
+
type Falsy = nil | false
|
|
3693
|
+
type Truthy<T> = T - Falsy
|
|
3694
|
+
|
|
3695
|
+
-- \`-\` is set difference. Over a union it drops members; over a concrete type
|
|
3696
|
+
-- it simplifies away; over an opaque type (\`unknown\`, an unresolved parameter)
|
|
3697
|
+
-- it is kept, so \`Exclude<unknown, 1>\` stays \`unknown - 1\`.
|
|
3698
|
+
type Exclude<T, U> = T - U
|
|
3699
|
+
type Extract<T, U> = T extends U ? T : never
|
|
3700
|
+
type NonNullable<T> = T - nil
|
|
3701
|
+
|
|
3702
|
+
type ReturnType<T> = T extends (...unknown) -> infer R ? R : never
|
|
3703
|
+
type Parameters<T> = T extends (...infer P) -> unknown ? P : never
|
|
3704
|
+
|
|
3705
|
+
type Partial<T> = { [K in keyof T]?: T[K] }
|
|
3706
|
+
type Required<T> = { [K in keyof T]-?: T[K] }
|
|
3707
|
+
type Readonly<T> = { readonly [K in keyof T]: T[K] }
|
|
3708
|
+
type Mutable<T> = { -readonly [K in keyof T]: T[K] }
|
|
3709
|
+
|
|
3710
|
+
type Pick<T, K> = { [P in K]: T[P] }
|
|
3711
|
+
type Omit<T, K> = Pick<T, Exclude<keyof T, K>>
|
|
3712
|
+
type Record<K, V> = { [P in K]: V }
|
|
3713
|
+
`;
|
|
3714
|
+
var prelude;
|
|
3715
|
+
function preludeProgram() {
|
|
3716
|
+
return prelude ??= parse(PRELUDE_SOURCE);
|
|
3717
|
+
}
|
|
3718
|
+
|
|
3029
3719
|
// src/ast/typeModel.ts
|
|
3030
3720
|
function isClassType(t) {
|
|
3031
3721
|
return t.kind === "object" && t.class !== void 0;
|
|
@@ -3101,6 +3791,7 @@ function substitute(t, subst) {
|
|
|
3101
3791
|
varargs,
|
|
3102
3792
|
returns: substitute(t.returns, inner),
|
|
3103
3793
|
typeParams: t.typeParams,
|
|
3794
|
+
typeParamDefaults: t.typeParamDefaults,
|
|
3104
3795
|
predicate: t.predicate && {
|
|
3105
3796
|
...t.predicate,
|
|
3106
3797
|
type: t.predicate.type && substitute(t.predicate.type, inner)
|
|
@@ -3357,7 +4048,16 @@ function isAssignableInner(a, b) {
|
|
|
3357
4048
|
if (a.indexer && isAssignable(a.indexer.value, bp.type)) continue;
|
|
3358
4049
|
return false;
|
|
3359
4050
|
}
|
|
3360
|
-
if (!isAssignable(ap.type, bp.type)) return false;
|
|
4051
|
+
if (!isAssignable(ap.type, bp.type)) return false;
|
|
4052
|
+
}
|
|
4053
|
+
if (b.indexer) {
|
|
4054
|
+
for (const [name, ap] of a.properties) {
|
|
4055
|
+
if (b.properties.has(name) || !isAssignable(literal(name), b.indexer.key)) continue;
|
|
4056
|
+
if (!isAssignable(ap.type, b.indexer.value)) return false;
|
|
4057
|
+
}
|
|
4058
|
+
if (a.indexer && isAssignable(a.indexer.key, b.indexer.key) && !isAssignable(a.indexer.value, b.indexer.value)) {
|
|
4059
|
+
return false;
|
|
4060
|
+
}
|
|
3361
4061
|
}
|
|
3362
4062
|
return true;
|
|
3363
4063
|
}
|
|
@@ -3508,10 +4208,14 @@ function containsFreeTypeParam(t, seen, bound) {
|
|
|
3508
4208
|
return containsTypeParam(t.base, seen, bound) || containsTypeParam(t.excluded, seen, bound);
|
|
3509
4209
|
case "indexedAccess":
|
|
3510
4210
|
return containsTypeParam(t.objectType, seen, bound) || containsTypeParam(t.indexType, seen, bound);
|
|
3511
|
-
case "conditional":
|
|
3512
|
-
|
|
3513
|
-
|
|
3514
|
-
|
|
4211
|
+
case "conditional": {
|
|
4212
|
+
const inner = t.inferVars.length ? /* @__PURE__ */ new Set([...bound, ...t.inferVars]) : bound;
|
|
4213
|
+
return containsTypeParam(t.checkType, seen, bound) || containsTypeParam(t.extendsType, seen, inner) || containsTypeParam(t.trueType, seen, inner) || containsTypeParam(t.falseType, seen, bound);
|
|
4214
|
+
}
|
|
4215
|
+
case "mapped": {
|
|
4216
|
+
const inner = /* @__PURE__ */ new Set([...bound, t.parameter]);
|
|
4217
|
+
return containsTypeParam(t.constraint, seen, bound) || !!t.nameType && containsTypeParam(t.nameType, seen, inner) || containsTypeParam(t.template, seen, inner) || !!t.source && containsTypeParam(t.source, seen, bound);
|
|
4218
|
+
}
|
|
3515
4219
|
default:
|
|
3516
4220
|
return false;
|
|
3517
4221
|
}
|
|
@@ -3585,9 +4289,52 @@ function escapeRegExp(s) {
|
|
|
3585
4289
|
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3586
4290
|
}
|
|
3587
4291
|
function overlaps(a, b) {
|
|
4292
|
+
if (a.kind === "union") return a.types.some((m) => overlaps(m, b));
|
|
4293
|
+
if (b.kind === "union") return b.types.some((m) => overlaps(a, m));
|
|
3588
4294
|
return isAssignable(a, b) || isAssignable(b, a);
|
|
3589
4295
|
}
|
|
3590
4296
|
var formatCache = /* @__PURE__ */ new WeakMap();
|
|
4297
|
+
function briefConstraint(t) {
|
|
4298
|
+
if (t.kind === "union" && t.types.length > 8) {
|
|
4299
|
+
return `${t.types.slice(0, 6).map(formatType).join(" | ")} | ... ${t.types.length - 6} more`;
|
|
4300
|
+
}
|
|
4301
|
+
return formatType(t);
|
|
4302
|
+
}
|
|
4303
|
+
function collectTypeParams(t, out, seen = /* @__PURE__ */ new Set()) {
|
|
4304
|
+
if (!t || seen.has(t)) return;
|
|
4305
|
+
seen.add(t);
|
|
4306
|
+
switch (t.kind) {
|
|
4307
|
+
case "typeParam":
|
|
4308
|
+
if (t.constraint && !out.has(t.name)) out.set(t.name, t.constraint);
|
|
4309
|
+
collectTypeParams(t.constraint, out, seen);
|
|
4310
|
+
return;
|
|
4311
|
+
case "array":
|
|
4312
|
+
collectTypeParams(t.element, out, seen);
|
|
4313
|
+
return;
|
|
4314
|
+
case "tuple":
|
|
4315
|
+
for (const e of t.elements) collectTypeParams(e, out, seen);
|
|
4316
|
+
return;
|
|
4317
|
+
case "union":
|
|
4318
|
+
case "intersection":
|
|
4319
|
+
for (const m of t.types) collectTypeParams(m, out, seen);
|
|
4320
|
+
return;
|
|
4321
|
+
case "keyof":
|
|
4322
|
+
collectTypeParams(t.target, out, seen);
|
|
4323
|
+
return;
|
|
4324
|
+
case "indexedAccess":
|
|
4325
|
+
collectTypeParams(t.objectType, out, seen);
|
|
4326
|
+
collectTypeParams(t.indexType, out, seen);
|
|
4327
|
+
return;
|
|
4328
|
+
case "genericRef":
|
|
4329
|
+
for (const a of t.typeArguments) collectTypeParams(a, out, seen);
|
|
4330
|
+
return;
|
|
4331
|
+
case "object":
|
|
4332
|
+
for (const [, p] of t.properties) collectTypeParams(p.type, out, seen);
|
|
4333
|
+
return;
|
|
4334
|
+
default:
|
|
4335
|
+
return;
|
|
4336
|
+
}
|
|
4337
|
+
}
|
|
3591
4338
|
function formatType(t) {
|
|
3592
4339
|
const cached = formatCache.get(t);
|
|
3593
4340
|
if (cached !== void 0) return cached;
|
|
@@ -3624,7 +4371,14 @@ function formatTypeUncached(t) {
|
|
|
3624
4371
|
const consts = new Set(
|
|
3625
4372
|
t.params.filter((p) => p.type.kind === "typeParam" && p.type.isConst).map((p) => p.type.name)
|
|
3626
4373
|
);
|
|
3627
|
-
const
|
|
4374
|
+
const constraints = /* @__PURE__ */ new Map();
|
|
4375
|
+
for (const part of [...t.params.map((p) => p.type), t.varargs, t.returns]) {
|
|
4376
|
+
collectTypeParams(part, constraints);
|
|
4377
|
+
}
|
|
4378
|
+
const gen = t.typeParams?.length ? `<${t.typeParams.map((n) => {
|
|
4379
|
+
const constraint = constraints.get(n);
|
|
4380
|
+
return `${consts.has(n) ? "const " : ""}${n}${constraint ? ` extends ${briefConstraint(constraint)}` : ""}`;
|
|
4381
|
+
}).join(", ")}>` : "";
|
|
3628
4382
|
const ps = t.params.map((p) => `${p.name ? p.name + ": " : ""}${formatType(p.type)}`);
|
|
3629
4383
|
if (t.varargs) ps.push(`...${formatType(t.varargs)}`);
|
|
3630
4384
|
return `${gen}(${ps.join(", ")}) -> ${formatPredicate(t) ?? formatType(t.returns)}`;
|
|
@@ -3840,6 +4594,12 @@ function isFreshLiteralExpr(e) {
|
|
|
3840
4594
|
return isFreshLiteralExpr(e.expression);
|
|
3841
4595
|
case "UnaryExpression":
|
|
3842
4596
|
return isFreshLiteralExpr(e.argument);
|
|
4597
|
+
// `let n = 5 satisfies number` widens like `let n = 5`. An object or
|
|
4598
|
+
// array has already taken its literals from the contract, and keeps them.
|
|
4599
|
+
case "SatisfiesExpression": {
|
|
4600
|
+
const inner = unwrapParens(e.expression);
|
|
4601
|
+
return inner.type !== "TableExpression" && inner.type !== "ArrayExpression" && isFreshLiteralExpr(inner);
|
|
4602
|
+
}
|
|
3843
4603
|
default:
|
|
3844
4604
|
return false;
|
|
3845
4605
|
}
|
|
@@ -3949,6 +4709,48 @@ var AliasMap = class extends Map {
|
|
|
3949
4709
|
return this.entries();
|
|
3950
4710
|
}
|
|
3951
4711
|
};
|
|
4712
|
+
function unwrapParens(e) {
|
|
4713
|
+
while (e.type === "ParenthesizedExpression") e = e.expression;
|
|
4714
|
+
return e;
|
|
4715
|
+
}
|
|
4716
|
+
function expressionLabel(e, depth = 0) {
|
|
4717
|
+
if (depth > 6) return void 0;
|
|
4718
|
+
const args = (list) => {
|
|
4719
|
+
const parts = list.map((a) => a.type === "StringLiteral" ? JSON.stringify(a.value) : a.type === "NumberLiteral" ? a.raw : a.type === "Identifier" ? a.name : void 0);
|
|
4720
|
+
return parts.every((p) => p !== void 0) && parts.join(", ").length <= 40 ? `(${parts.join(", ")})` : "(...)";
|
|
4721
|
+
};
|
|
4722
|
+
switch (e.type) {
|
|
4723
|
+
case "Identifier":
|
|
4724
|
+
return e.name;
|
|
4725
|
+
case "MemberExpression": {
|
|
4726
|
+
const o = expressionLabel(e.object, depth + 1);
|
|
4727
|
+
return o === void 0 ? void 0 : `${o}${e.optional ? "?." : "."}${e.property.name}`;
|
|
4728
|
+
}
|
|
4729
|
+
case "MethodCallExpression": {
|
|
4730
|
+
const o = expressionLabel(e.object, depth + 1);
|
|
4731
|
+
return o === void 0 ? void 0 : `${o}${e.optional ? "?:" : ":"}${e.method.name}${args(e.arguments)}`;
|
|
4732
|
+
}
|
|
4733
|
+
case "CallExpression": {
|
|
4734
|
+
const o = expressionLabel(e.callee, depth + 1);
|
|
4735
|
+
return o === void 0 ? void 0 : `${o}${e.optional ? "?." : ""}${args(e.arguments)}`;
|
|
4736
|
+
}
|
|
4737
|
+
case "IndexExpression": {
|
|
4738
|
+
const o = expressionLabel(e.object, depth + 1);
|
|
4739
|
+
const i = e.index.type === "StringLiteral" ? JSON.stringify(e.index.value) : e.index.type === "NumberLiteral" ? e.index.raw : e.index.type === "Identifier" ? e.index.name : "...";
|
|
4740
|
+
return o === void 0 ? void 0 : `${o}[${i}]`;
|
|
4741
|
+
}
|
|
4742
|
+
case "ParenthesizedExpression": {
|
|
4743
|
+
const inner = expressionLabel(e.expression, depth + 1);
|
|
4744
|
+
return inner === void 0 ? void 0 : `(${inner})`;
|
|
4745
|
+
}
|
|
4746
|
+
default:
|
|
4747
|
+
return void 0;
|
|
4748
|
+
}
|
|
4749
|
+
}
|
|
4750
|
+
function withoutNil(t) {
|
|
4751
|
+
if (t.kind !== "union") return t.kind === "primitive" && t.name === "nil" ? neverType : t;
|
|
4752
|
+
return union(t.types.filter((m) => !(m.kind === "primitive" && m.name === "nil")));
|
|
4753
|
+
}
|
|
3952
4754
|
var METAMETHODS = {
|
|
3953
4755
|
"+": "__add",
|
|
3954
4756
|
"-": "__sub",
|
|
@@ -4022,14 +4824,16 @@ var TypeAnalyzer = class {
|
|
|
4022
4824
|
/** Recursion guard for `preVisitBody`. */
|
|
4023
4825
|
preVisitDepth = 0;
|
|
4024
4826
|
run() {
|
|
4827
|
+
this.registerAliasDefs(preludeProgram().body);
|
|
4025
4828
|
for (const lib of this.options.libs ?? []) this.registerAliasDefs(lib.body);
|
|
4026
4829
|
this.registerAliasDefs(this.program.body);
|
|
4027
4830
|
for (const lib of this.options.libs ?? []) this.harvestDeclares(lib.body);
|
|
4028
|
-
this.harvestDeclares(this.program.body);
|
|
4029
4831
|
this.registerImportedTypes();
|
|
4030
4832
|
this.resolveAllAliases();
|
|
4833
|
+
this.harvestDeclares(this.program.body, true);
|
|
4031
4834
|
this.indexDeclarations();
|
|
4032
4835
|
for (const [name, id] of this.scopes.globalsByName) {
|
|
4836
|
+
if (this.deferredDeclares.has(name) && !this.options.globalTypes?.[name]) continue;
|
|
4033
4837
|
const t = this.options.globalTypes?.[name] ?? this.libGlobalTypes.get(name) ?? anyType;
|
|
4034
4838
|
this.bindingType.set(id, t);
|
|
4035
4839
|
}
|
|
@@ -4037,6 +4841,8 @@ var TypeAnalyzer = class {
|
|
|
4037
4841
|
try {
|
|
4038
4842
|
const env = /* @__PURE__ */ new Map();
|
|
4039
4843
|
this.visitBlock(this.program.body, env);
|
|
4844
|
+
this.resolveDeferredDeclares();
|
|
4845
|
+
if (this.options.reportUnknownTypes) this.reportUnknownTypes();
|
|
4040
4846
|
} finally {
|
|
4041
4847
|
setAliasExpander(void 0);
|
|
4042
4848
|
}
|
|
@@ -4081,6 +4887,13 @@ var TypeAnalyzer = class {
|
|
|
4081
4887
|
if (stmt.type !== "ImportStatement") continue;
|
|
4082
4888
|
const exports2 = this.moduleFor(stmt.source.value);
|
|
4083
4889
|
if (!exports2) continue;
|
|
4890
|
+
if (stmt.namespaceImport) {
|
|
4891
|
+
for (const [name, exported] of exports2.types) {
|
|
4892
|
+
const qualified = `${stmt.namespaceImport.name}.${name}`;
|
|
4893
|
+
this.importedTypes.set(qualified, exported);
|
|
4894
|
+
this.aliases.set(qualified, exported.type);
|
|
4895
|
+
}
|
|
4896
|
+
}
|
|
4084
4897
|
for (const s of stmt.specifiers) {
|
|
4085
4898
|
const exported = exports2.types.get(s.imported.name);
|
|
4086
4899
|
if (exported) {
|
|
@@ -4192,13 +5005,38 @@ var TypeAnalyzer = class {
|
|
|
4192
5005
|
* string. Any other value is simply redeclared: a sourcemap's
|
|
4193
5006
|
* `declare script: <this file's instance>` replaces the library's
|
|
4194
5007
|
* `declare script: LuaSourceContainer`. */
|
|
4195
|
-
|
|
5008
|
+
/** Program `declare`s whose type depends on a value's, by name. */
|
|
5009
|
+
deferredDeclares = /* @__PURE__ */ new Map();
|
|
5010
|
+
/** A library that declares a name a second time adds to it rather than
|
|
5011
|
+
* replacing it: `declare table: { find: ... }` on top of Lua's `table`
|
|
5012
|
+
* leaves both members there, the way overloads of a function accumulate.
|
|
5013
|
+
* This is what lets one definitions file build on another's — Luau's on
|
|
5014
|
+
* Lua's, Roblox's on Luau's. A property declared twice takes its later
|
|
5015
|
+
* type. Classes stay as they are: they come from one generated file and
|
|
5016
|
+
* merging them would only blur it. */
|
|
5017
|
+
mergeDeclared(prev, next) {
|
|
5018
|
+
if (!prev || prev.kind !== "object" || next.kind !== "object") return next;
|
|
5019
|
+
if (prev.class || next.class) return next;
|
|
5020
|
+
return objectType(
|
|
5021
|
+
[...prev.properties, ...next.properties],
|
|
5022
|
+
next.indexer ?? prev.indexer,
|
|
5023
|
+
next.frozen ?? prev.frozen
|
|
5024
|
+
);
|
|
5025
|
+
}
|
|
5026
|
+
harvestDeclares(block, own = false) {
|
|
4196
5027
|
for (const stmt of block.statements) {
|
|
4197
5028
|
if (stmt.type !== "DeclareStatement") continue;
|
|
5029
|
+
if (own && (containsTypeQuery(stmt.valueType) || referencedTypeNames(stmt.valueType).some((name) => this.dependsOnTypeQuery(name)))) {
|
|
5030
|
+
this.deferredDeclares.set(stmt.name, stmt);
|
|
5031
|
+
continue;
|
|
5032
|
+
}
|
|
4198
5033
|
const t = this.resolveType(stmt.valueType);
|
|
4199
5034
|
const prev = this.libGlobalTypes.get(stmt.name);
|
|
4200
5035
|
const overload = prev && stmt.valueType.type === "FunctionTypeNode" && (prev.kind === "function" || prev.kind === "intersection");
|
|
4201
|
-
this.libGlobalTypes.set(
|
|
5036
|
+
this.libGlobalTypes.set(
|
|
5037
|
+
stmt.name,
|
|
5038
|
+
overload ? intersection([prev, t]) : this.mergeDeclared(prev, t)
|
|
5039
|
+
);
|
|
4202
5040
|
}
|
|
4203
5041
|
}
|
|
4204
5042
|
resolveAllAliases() {
|
|
@@ -4208,14 +5046,154 @@ var TypeAnalyzer = class {
|
|
|
4208
5046
|
this.aliases.defer(name, () => this.classType(cls));
|
|
4209
5047
|
continue;
|
|
4210
5048
|
}
|
|
4211
|
-
if (
|
|
5049
|
+
if (this.dependsOnTypeQuery(name)) continue;
|
|
4212
5050
|
this.withTypeParams(def.params, () => {
|
|
4213
5051
|
this.aliases.set(name, this.resolveDef(def));
|
|
4214
5052
|
});
|
|
4215
5053
|
}
|
|
4216
5054
|
}
|
|
5055
|
+
typeQueryDependents = /* @__PURE__ */ new Map();
|
|
5056
|
+
/** Does alias `name` contain a `typeof`, itself or through an alias it
|
|
5057
|
+
* names? */
|
|
5058
|
+
dependsOnTypeQuery(name, visiting = /* @__PURE__ */ new Set()) {
|
|
5059
|
+
const known = this.typeQueryDependents.get(name);
|
|
5060
|
+
if (known !== void 0) return known;
|
|
5061
|
+
const def = this.aliasDefs.get(name);
|
|
5062
|
+
if (!def || def.class || visiting.has(name)) return false;
|
|
5063
|
+
visiting.add(name);
|
|
5064
|
+
const result = containsTypeQuery(def.node) || referencedTypeNames(def.node).some((other) => other !== name && this.dependsOnTypeQuery(other, visiting));
|
|
5065
|
+
visiting.delete(name);
|
|
5066
|
+
this.typeQueryDependents.set(name, result);
|
|
5067
|
+
return result;
|
|
5068
|
+
}
|
|
4217
5069
|
/** The aliases `resolveAllAliases` left for later, now that every binding
|
|
4218
5070
|
* has its type. */
|
|
5071
|
+
/** Names this file imports. A module that could not be found is reported
|
|
5072
|
+
* as the missing module it is; the names it was to bring are not also
|
|
5073
|
+
* typos. */
|
|
5074
|
+
importedNames() {
|
|
5075
|
+
if (this.imported) return this.imported;
|
|
5076
|
+
this.imported = /* @__PURE__ */ new Set();
|
|
5077
|
+
for (const statement of this.program.body.statements) {
|
|
5078
|
+
if (statement.type !== "ImportStatement") continue;
|
|
5079
|
+
if (statement.defaultImport) this.imported.add(statement.defaultImport.name);
|
|
5080
|
+
if (statement.namespaceImport) this.imported.add(statement.namespaceImport.name);
|
|
5081
|
+
for (const specifier of statement.specifiers) this.imported.add(specifier.local.name);
|
|
5082
|
+
}
|
|
5083
|
+
return this.imported;
|
|
5084
|
+
}
|
|
5085
|
+
imported;
|
|
5086
|
+
/** What a `return` gives, against what the function declared. */
|
|
5087
|
+
checkReturn(stmt, declared, types, sources, env) {
|
|
5088
|
+
if (!declared || !this.emitDiagnostics) return;
|
|
5089
|
+
if (declared.kind === "any" || declared.kind === "unknown" || this.namesNothing(declared)) return;
|
|
5090
|
+
const actual = stmt.arguments.length === 0 ? nilType : types.length === 1 ? types[0] : tuple([...types], true);
|
|
5091
|
+
const source = stmt.arguments.length === 1 ? sources[0] : void 0;
|
|
5092
|
+
const fits = source ? this.fitsAnnotation(source, declared, actual, env) : isAssignable(actual, declared) || isAssignable(widen(actual), declared);
|
|
5093
|
+
if (fits) return;
|
|
5094
|
+
this.diagnostics.push({
|
|
5095
|
+
node: stmt,
|
|
5096
|
+
message: `Type '${formatType(actual)}' is not assignable to '${briefType(declared)}'`
|
|
5097
|
+
});
|
|
5098
|
+
}
|
|
5099
|
+
/** A function that declared what it returns but never does. Only a body
|
|
5100
|
+
* with no `return` at all is reported: anything subtler needs to know
|
|
5101
|
+
* which paths can run off the end, and a wrong guess there is worse than
|
|
5102
|
+
* a missing complaint. */
|
|
5103
|
+
checkReturnsAtAll(func, declared) {
|
|
5104
|
+
if (!declared || !this.emitDiagnostics) return;
|
|
5105
|
+
if (func.predicate) return;
|
|
5106
|
+
if (declared.kind === "any" || declared.kind === "unknown" || declared.kind === "never") return;
|
|
5107
|
+
if (isAssignable(nilType, declared) || this.namesNothing(declared)) return;
|
|
5108
|
+
let found = false;
|
|
5109
|
+
const walk = (statements) => {
|
|
5110
|
+
for (const statement of statements) {
|
|
5111
|
+
if (found) return;
|
|
5112
|
+
if (statement.type === "ReturnStatement") {
|
|
5113
|
+
found = true;
|
|
5114
|
+
return;
|
|
5115
|
+
}
|
|
5116
|
+
for (const value of Object.values(statement)) {
|
|
5117
|
+
if (value && typeof value === "object" && "statements" in value) {
|
|
5118
|
+
walk(value.statements);
|
|
5119
|
+
} else if (Array.isArray(value)) {
|
|
5120
|
+
for (const item of value) {
|
|
5121
|
+
const block = item;
|
|
5122
|
+
if (block?.body?.statements) walk(block.body.statements);
|
|
5123
|
+
}
|
|
5124
|
+
}
|
|
5125
|
+
}
|
|
5126
|
+
}
|
|
5127
|
+
};
|
|
5128
|
+
walk(func.body.statements);
|
|
5129
|
+
if (found) return;
|
|
5130
|
+
this.diagnostics.push({
|
|
5131
|
+
node: func.body,
|
|
5132
|
+
message: `A function that returns '${briefType(declared)}' must return a value`
|
|
5133
|
+
});
|
|
5134
|
+
}
|
|
5135
|
+
/** Does this type rest on a name nothing declares? Such a type says
|
|
5136
|
+
* nothing about what fits it, so checking against it only piles a second
|
|
5137
|
+
* complaint on top of "Cannot find name". */
|
|
5138
|
+
namesNothing(t, seen = /* @__PURE__ */ new Set()) {
|
|
5139
|
+
if (seen.has(t)) return false;
|
|
5140
|
+
seen.add(t);
|
|
5141
|
+
if (t.kind === "genericRef") {
|
|
5142
|
+
return !this.aliasDefs.has(t.name) && !this.importedTypes.has(t.name) && this.options.libTypes?.[t.name] === void 0;
|
|
5143
|
+
}
|
|
5144
|
+
switch (t.kind) {
|
|
5145
|
+
case "union":
|
|
5146
|
+
case "intersection":
|
|
5147
|
+
return t.types.some((m) => this.namesNothing(m, seen));
|
|
5148
|
+
case "array":
|
|
5149
|
+
return this.namesNothing(t.element, seen);
|
|
5150
|
+
case "tuple":
|
|
5151
|
+
return t.elements.some((e) => this.namesNothing(e, seen));
|
|
5152
|
+
case "object":
|
|
5153
|
+
if (t.class) return false;
|
|
5154
|
+
return [...t.properties.values()].some((v) => this.namesNothing(v.type, seen));
|
|
5155
|
+
default:
|
|
5156
|
+
return false;
|
|
5157
|
+
}
|
|
5158
|
+
}
|
|
5159
|
+
/** Every type name in the program that resolved to nothing — a typo, or a
|
|
5160
|
+
* library the config does not load. A name that resolves to a type
|
|
5161
|
+
* parameter, an alias (even one still being resolved), an imported type or
|
|
5162
|
+
* a primitive is fine; what is left is a reference that stayed itself. */
|
|
5163
|
+
reportUnknownTypes() {
|
|
5164
|
+
if (!this.emitDiagnostics) return;
|
|
5165
|
+
const reported = /* @__PURE__ */ new Set();
|
|
5166
|
+
const visit = (node) => {
|
|
5167
|
+
if (!node || typeof node !== "object") return;
|
|
5168
|
+
if (Array.isArray(node)) {
|
|
5169
|
+
for (const item of node) visit(item);
|
|
5170
|
+
return;
|
|
5171
|
+
}
|
|
5172
|
+
const record = node;
|
|
5173
|
+
if (record.type === "TypeReference" && typeof record.base === "string") {
|
|
5174
|
+
const name = typeof record.namespace === "string" ? `${record.namespace}.${record.base}` : record.base;
|
|
5175
|
+
const resolved = this.typeOfTypeNode.get(node);
|
|
5176
|
+
const unresolved = resolved?.kind === "genericRef" && resolved.name === name && !this.aliasDefs.has(name) && !this.importedTypes.has(name) && this.options.libTypes?.[name] === void 0 && !STRING_INTRINSICS.has(name) && !this.importedNames().has(name.split(".")[0]);
|
|
5177
|
+
const at = node;
|
|
5178
|
+
const key = `${at.line.start}:${at.column.start}`;
|
|
5179
|
+
if (unresolved && !reported.has(key)) {
|
|
5180
|
+
reported.add(key);
|
|
5181
|
+
this.diagnostics.push({ node, message: `Cannot find name '${name}'` });
|
|
5182
|
+
}
|
|
5183
|
+
}
|
|
5184
|
+
for (const [key, value] of Object.entries(node)) {
|
|
5185
|
+
if (key !== "line" && key !== "column" && value && typeof value === "object") visit(value);
|
|
5186
|
+
}
|
|
5187
|
+
};
|
|
5188
|
+
visit(this.program.body);
|
|
5189
|
+
}
|
|
5190
|
+
/** Deferred `declare`s nothing used, typed now for tools that ask. */
|
|
5191
|
+
resolveDeferredDeclares() {
|
|
5192
|
+
for (const name of this.deferredDeclares.keys()) {
|
|
5193
|
+
const id = this.scopes.globalsByName.get(name);
|
|
5194
|
+
if (id !== void 0 && !this.bindingType.has(id)) this.bindingType.set(id, this.declaredAhead(id) ?? anyType);
|
|
5195
|
+
}
|
|
5196
|
+
}
|
|
4219
5197
|
resolveDeferredAliases() {
|
|
4220
5198
|
for (const [name, def] of this.aliasDefs) {
|
|
4221
5199
|
if (this.aliases.has(name)) continue;
|
|
@@ -4275,6 +5253,16 @@ var TypeAnalyzer = class {
|
|
|
4275
5253
|
});
|
|
4276
5254
|
return subst;
|
|
4277
5255
|
}
|
|
5256
|
+
/** An imported type, with its type arguments applied. */
|
|
5257
|
+
importedType(imported, typeArguments) {
|
|
5258
|
+
if (!imported.params.length) return imported.type;
|
|
5259
|
+
const subst = /* @__PURE__ */ new Map();
|
|
5260
|
+
imported.params.forEach((name, i) => {
|
|
5261
|
+
const arg = typeArguments[i];
|
|
5262
|
+
subst.set(name, arg ? this.resolveType(arg) : unknownType);
|
|
5263
|
+
});
|
|
5264
|
+
return this.reduceType(substitute(imported.type, subst));
|
|
5265
|
+
}
|
|
4278
5266
|
// --------------------------------------------------------
|
|
4279
5267
|
// TypeNode -> Type
|
|
4280
5268
|
// --------------------------------------------------------
|
|
@@ -4325,17 +5313,11 @@ var TypeAnalyzer = class {
|
|
|
4325
5313
|
});
|
|
4326
5314
|
}
|
|
4327
5315
|
const imported = this.importedTypes.get(node.base);
|
|
4328
|
-
if (imported)
|
|
4329
|
-
if (!imported.params.length) return imported.type;
|
|
4330
|
-
const subst = /* @__PURE__ */ new Map();
|
|
4331
|
-
imported.params.forEach((name2, i) => {
|
|
4332
|
-
const arg = node.typeArguments[i];
|
|
4333
|
-
subst.set(name2, arg ? this.resolveType(arg) : unknownType);
|
|
4334
|
-
});
|
|
4335
|
-
return this.reduceType(substitute(imported.type, subst));
|
|
4336
|
-
}
|
|
5316
|
+
if (imported) return this.importedType(imported, node.typeArguments);
|
|
4337
5317
|
const lib = this.options.libTypes?.[node.base];
|
|
4338
5318
|
if (lib) return lib;
|
|
5319
|
+
} else if (this.importedTypes.has(name)) {
|
|
5320
|
+
return this.importedType(this.importedTypes.get(name), node.typeArguments);
|
|
4339
5321
|
} else if (this.aliasDefs.has(name)) {
|
|
4340
5322
|
return this.expand({
|
|
4341
5323
|
kind: "genericRef",
|
|
@@ -4394,13 +5376,13 @@ var TypeAnalyzer = class {
|
|
|
4394
5376
|
type: p.optional ? optional(this.resolveType(p.typeAnnotation)) : this.resolveType(p.typeAnnotation),
|
|
4395
5377
|
optional: p.optional
|
|
4396
5378
|
}));
|
|
4397
|
-
return fn(
|
|
5379
|
+
return this.withTypeParamDefaults(fn(
|
|
4398
5380
|
params,
|
|
4399
5381
|
this.resolveType(node.returnType),
|
|
4400
5382
|
node.hasVarargs ? node.varargType ? this.resolveType(node.varargType) : anyType : void 0,
|
|
4401
5383
|
names,
|
|
4402
5384
|
this.resolvePredicate(node.predicate, params)
|
|
4403
|
-
);
|
|
5385
|
+
), node.generics);
|
|
4404
5386
|
});
|
|
4405
5387
|
}
|
|
4406
5388
|
case "TypeofTypeNode": {
|
|
@@ -4646,7 +5628,11 @@ var TypeAnalyzer = class {
|
|
|
4646
5628
|
}
|
|
4647
5629
|
reduceConditional(t) {
|
|
4648
5630
|
const checkType = this.reduceType(t.checkType);
|
|
4649
|
-
|
|
5631
|
+
const extendsType = this.reduceType(t.extendsType);
|
|
5632
|
+
const free = new Set(t.inferVars);
|
|
5633
|
+
if (containsTypeParam(checkType) || containsTypeParam(extendsType, /* @__PURE__ */ new Set(), free)) {
|
|
5634
|
+
return { ...t, checkType, extendsType };
|
|
5635
|
+
}
|
|
4650
5636
|
if (t.distributeParam && checkType.kind === "union") {
|
|
4651
5637
|
return union(checkType.types.map((m) => this.branchOf(t, m)));
|
|
4652
5638
|
}
|
|
@@ -4751,15 +5737,22 @@ var TypeAnalyzer = class {
|
|
|
4751
5737
|
const source = sources[i];
|
|
4752
5738
|
if (this.emitDiagnostics && target.type === "IdentifierPattern" && target.typeAnnotation && source) {
|
|
4753
5739
|
const declared = this.resolveType(target.typeAnnotation);
|
|
4754
|
-
if (declared.kind !== "any" && !this.fitsAnnotation(source, declared, inferred, env)) {
|
|
5740
|
+
if (declared.kind !== "any" && !this.namesNothing(declared) && !this.fitsAnnotation(source, declared, inferred, env)) {
|
|
4755
5741
|
this.diagnostics.push({
|
|
4756
5742
|
node: stmt,
|
|
4757
5743
|
message: `Type '${formatType(inferred)}' is not assignable to '${formatType(declared)}'`
|
|
4758
5744
|
});
|
|
5745
|
+
} else if (declared.kind !== "any") {
|
|
5746
|
+
this.reportExcessProperties(source, declared);
|
|
4759
5747
|
}
|
|
4760
5748
|
}
|
|
4761
5749
|
const mode = this.initIsAsConst(source) ? "asconst" : !isFreshLiteralExpr(source) ? "keep" : stmt.kind === "const" ? "const" : "widen";
|
|
4762
5750
|
this.bindPattern(target, inferred, env, mode);
|
|
5751
|
+
if (stmt.kind === "const") {
|
|
5752
|
+
this.correlateDestructuring(target, inferred, env);
|
|
5753
|
+
this.correlateIndexed(target, source, env);
|
|
5754
|
+
this.aliasReference(target, source);
|
|
5755
|
+
}
|
|
4763
5756
|
});
|
|
4764
5757
|
return;
|
|
4765
5758
|
}
|
|
@@ -4767,6 +5760,7 @@ var TypeAnalyzer = class {
|
|
|
4767
5760
|
this.checkParamOrder(stmt.func.params, stmt);
|
|
4768
5761
|
for (const sig of stmt.signatures ?? []) this.checkParamOrder(sig.params, stmt);
|
|
4769
5762
|
const id = this.bindingIdByName(stmt.name.name, stmt.name);
|
|
5763
|
+
this.paramsFromSignatures(stmt.func, stmt.signatures);
|
|
4770
5764
|
const fnType = stmt.signatures?.length ? intersection(stmt.signatures.map((s) => this.signatureToFnType(s))) : this.inferFunctionBody(stmt.func, env);
|
|
4771
5765
|
if (id !== void 0) {
|
|
4772
5766
|
this.bindingType.set(id, fnType);
|
|
@@ -4782,6 +5776,7 @@ var TypeAnalyzer = class {
|
|
|
4782
5776
|
const memberName = stmt.target.method?.name ?? (stmt.target.path.length === 1 ? stmt.target.path[0].name : void 0);
|
|
4783
5777
|
if (memberName === void 0 && stmt.target.path.length === 0) {
|
|
4784
5778
|
if (targetId !== void 0) {
|
|
5779
|
+
this.paramsFromSignatures(stmt.func, stmt.signatures);
|
|
4785
5780
|
const fnType = stmt.signatures?.length ? intersection(stmt.signatures.map((s) => this.signatureToFnType(s))) : this.inferFunctionBody(stmt.func, env);
|
|
4786
5781
|
this.bindingType.set(targetId, fnType);
|
|
4787
5782
|
this.setBinding(env, targetId, fnType);
|
|
@@ -4791,6 +5786,7 @@ var TypeAnalyzer = class {
|
|
|
4791
5786
|
}
|
|
4792
5787
|
const recv = targetId === void 0 ? anyType : this.currentType(targetId, env);
|
|
4793
5788
|
this.withSelfType(stmt.isMethod ? recv : void 0, () => {
|
|
5789
|
+
this.paramsFromSignatures(stmt.func, stmt.signatures);
|
|
4794
5790
|
const fnType = stmt.signatures?.length ? intersection(stmt.signatures.map((s) => this.signatureToFnType(s))) : this.inferFunctionBody(stmt.func, env);
|
|
4795
5791
|
if (memberName !== void 0 && targetId !== void 0) {
|
|
4796
5792
|
const grown = intersection([
|
|
@@ -4822,6 +5818,7 @@ var TypeAnalyzer = class {
|
|
|
4822
5818
|
if (target.type === "Identifier") {
|
|
4823
5819
|
const id = this.bindingIdOf(target);
|
|
4824
5820
|
if (id !== void 0) {
|
|
5821
|
+
this.uncorrelate(id);
|
|
4825
5822
|
const next = isFreshLiteralExpr(source) ? widen(vt) : vt;
|
|
4826
5823
|
if (this.annotated.has(id)) {
|
|
4827
5824
|
const declared = this.bindingType.get(id);
|
|
@@ -4895,16 +5892,40 @@ var TypeAnalyzer = class {
|
|
|
4895
5892
|
case "GenericForStatement": {
|
|
4896
5893
|
const iterTypes = stmt.iterators.map((it) => this.infer(it, env));
|
|
4897
5894
|
const bodyEnv = forkEnv(env);
|
|
4898
|
-
const
|
|
4899
|
-
|
|
4900
|
-
|
|
4901
|
-
|
|
5895
|
+
const rows = stmt.variables.length >= 2 ? this.iterationRows(stmt.iterators[0], iterTypes[0]) : void 0;
|
|
5896
|
+
if (rows) {
|
|
5897
|
+
const [key, value] = stmt.variables;
|
|
5898
|
+
this.bindPattern(key, union(rows.map((r) => r[0])), bodyEnv, "keep");
|
|
5899
|
+
this.bindPattern(value, union(rows.map((r) => r[1])), bodyEnv, "keep");
|
|
5900
|
+
stmt.variables.slice(2).forEach((v) => this.bindPattern(v, unknownType, bodyEnv, "widen"));
|
|
5901
|
+
const keyId = key.type === "IdentifierPattern" ? this.bindingIdByName(key.name, key) : void 0;
|
|
5902
|
+
const valueId = value.type === "IdentifierPattern" ? this.bindingIdByName(value.name, value) : void 0;
|
|
5903
|
+
if (keyId !== void 0 && valueId !== void 0) this.correlateBindings(bodyEnv, [keyId, valueId], rows);
|
|
5904
|
+
} else {
|
|
5905
|
+
const [keyT, valT] = this.iterationTypes(stmt.iterators[0], iterTypes[0], stmt.variables.length);
|
|
5906
|
+
stmt.variables.forEach((v, i) => {
|
|
5907
|
+
this.bindPattern(v, i === 0 ? keyT : i === 1 ? valT : unknownType, bodyEnv, "widen");
|
|
5908
|
+
});
|
|
5909
|
+
}
|
|
4902
5910
|
this.visitBlock(stmt.body, bodyEnv);
|
|
4903
5911
|
return;
|
|
4904
5912
|
}
|
|
4905
|
-
case "ReturnStatement":
|
|
4906
|
-
|
|
5913
|
+
case "ReturnStatement": {
|
|
5914
|
+
const declared = this.declaredReturns[this.declaredReturns.length - 1];
|
|
5915
|
+
if (declared) {
|
|
5916
|
+
if (stmt.arguments.length === 1) {
|
|
5917
|
+
this.applyContext(stmt.arguments[0], declared);
|
|
5918
|
+
} else if (declared.kind === "tuple" && declared.isPack) {
|
|
5919
|
+
stmt.arguments.forEach((a, i) => this.applyContext(a, declared.elements[i]));
|
|
5920
|
+
}
|
|
5921
|
+
}
|
|
5922
|
+
const { types, sources } = this.valueList(stmt.arguments, env);
|
|
5923
|
+
this.checkReturn(stmt, declared, types, sources, env);
|
|
5924
|
+
if (this.returnTypes) {
|
|
5925
|
+
this.returnTypes.push(stmt.arguments.length === 0 ? nilType : types.length === 1 ? types[0] : tuple([...types], true));
|
|
5926
|
+
}
|
|
4907
5927
|
return;
|
|
5928
|
+
}
|
|
4908
5929
|
case "ExportStatement":
|
|
4909
5930
|
this.visitStatement(stmt.declaration, env);
|
|
4910
5931
|
return;
|
|
@@ -4939,6 +5960,14 @@ var TypeAnalyzer = class {
|
|
|
4939
5960
|
};
|
|
4940
5961
|
if (resolving && !exports2) report(stmt.source, `Cannot find module '${specifier}'`);
|
|
4941
5962
|
const usable = exports2 && !exports2.partial ? exports2 : void 0;
|
|
5963
|
+
if (stmt.namespaceImport) {
|
|
5964
|
+
const id = this.bindingIdByName(stmt.namespaceImport.name, stmt.namespaceImport);
|
|
5965
|
+
if (id !== void 0) {
|
|
5966
|
+
const members = [...usable?.values ?? []].map(([name, type]) => [name, { type, optional: false, readonly: true }]);
|
|
5967
|
+
if (usable?.default) members.push(["default", { type: usable.default, optional: false, readonly: true }]);
|
|
5968
|
+
this.bindingType.set(id, usable ? objectType(members) : anyType);
|
|
5969
|
+
}
|
|
5970
|
+
}
|
|
4942
5971
|
if (stmt.defaultImport) {
|
|
4943
5972
|
if (usable && usable.default === void 0) {
|
|
4944
5973
|
report(stmt.defaultImport, `Module '${specifier}' has no default export`);
|
|
@@ -5079,6 +6108,7 @@ var TypeAnalyzer = class {
|
|
|
5079
6108
|
}
|
|
5080
6109
|
if (p.typeAnnotation) {
|
|
5081
6110
|
const t = this.resolveType(p.typeAnnotation);
|
|
6111
|
+
if (p.default) this.applyContext(p.default, t);
|
|
5082
6112
|
return p.optional ? optional(t) : t;
|
|
5083
6113
|
}
|
|
5084
6114
|
if (p.pattern) return this.patternToType(p.pattern, env);
|
|
@@ -5096,7 +6126,12 @@ var TypeAnalyzer = class {
|
|
|
5096
6126
|
applyContext(expr, expected) {
|
|
5097
6127
|
let e = expr;
|
|
5098
6128
|
while (e.type === "ParenthesizedExpression") e = e.expression;
|
|
5099
|
-
if (
|
|
6129
|
+
if (!expected) return;
|
|
6130
|
+
this.expectedTypeOf.set(expr, expected);
|
|
6131
|
+
this.expectedTypeOf.set(e, expected);
|
|
6132
|
+
if (e.type === "ArrayExpression") return this.applyArrayContext(e, expected);
|
|
6133
|
+
if (e.type === "TableExpression") return this.applyTableContext(e, expected);
|
|
6134
|
+
if (e.type !== "FunctionExpression") return;
|
|
5100
6135
|
const members = expected.kind === "union" ? expected.types : [expected];
|
|
5101
6136
|
const signatures = members.flatMap((m) => this.overloadsOf(this.expand(m)));
|
|
5102
6137
|
if (!signatures.length) return;
|
|
@@ -5112,6 +6147,46 @@ var TypeAnalyzer = class {
|
|
|
5112
6147
|
this.contextualParams.set(p, containsTypeParam(t) ? anyType : t);
|
|
5113
6148
|
});
|
|
5114
6149
|
}
|
|
6150
|
+
/** What an array literal is expected to be: an empty one takes that type
|
|
6151
|
+
* outright — `let queue: thread[] = []` is a `thread[]`, as in TypeScript
|
|
6152
|
+
* — and the elements of any other get the element type as their own
|
|
6153
|
+
* context. */
|
|
6154
|
+
contextualArrays = /* @__PURE__ */ new WeakMap();
|
|
6155
|
+
applyArrayContext(e, expected) {
|
|
6156
|
+
const target = this.expectedMembers(expected).find((m) => m.kind === "array" || m.kind === "tuple");
|
|
6157
|
+
if (!target) return;
|
|
6158
|
+
if (!e.elements.length) {
|
|
6159
|
+
if (!containsTypeParam(target)) this.contextualArrays.set(e, target);
|
|
6160
|
+
return;
|
|
6161
|
+
}
|
|
6162
|
+
e.elements.forEach((element, i) => {
|
|
6163
|
+
if (element.type === "SpreadElement") return;
|
|
6164
|
+
const elementType = target.kind === "array" ? target.element : target.elements[i];
|
|
6165
|
+
this.applyContext(element, elementType);
|
|
6166
|
+
});
|
|
6167
|
+
}
|
|
6168
|
+
/** `{ list: [] }` where `{ list: thread[] }` is expected: each field's
|
|
6169
|
+
* value gets its property's type as context. */
|
|
6170
|
+
applyTableContext(e, expected) {
|
|
6171
|
+
const objects = this.expectedMembers(expected).filter((m) => m.kind === "object");
|
|
6172
|
+
if (!objects.length) return;
|
|
6173
|
+
for (const field of e.fields) {
|
|
6174
|
+
if (field.type !== "TableFieldNamed") continue;
|
|
6175
|
+
const key = field.key.type === "Identifier" ? field.key.name : field.key.value;
|
|
6176
|
+
const types = objects.flatMap((o) => {
|
|
6177
|
+
const property = o.properties.get(key);
|
|
6178
|
+
return property ? [property.type] : o.indexer ? [o.indexer.value] : [];
|
|
6179
|
+
});
|
|
6180
|
+
if (types.length) this.applyContext(field.value, union(types));
|
|
6181
|
+
}
|
|
6182
|
+
}
|
|
6183
|
+
/** The members of an expected type worth matching a literal against:
|
|
6184
|
+
* aliases seen through, `nil` left out. */
|
|
6185
|
+
expectedMembers(expected) {
|
|
6186
|
+
const t = this.expand(expected);
|
|
6187
|
+
const members = t.kind === "union" ? t.types : [t];
|
|
6188
|
+
return members.map((m) => this.expand(m)).filter((m) => !(m.kind === "primitive" && m.name === "nil"));
|
|
6189
|
+
}
|
|
5115
6190
|
/** The parameter type each written argument lands on, across `fns`. */
|
|
5116
6191
|
expectedArguments(written, fns, selfOf) {
|
|
5117
6192
|
return written.map((_, j) => {
|
|
@@ -5139,11 +6214,28 @@ var TypeAnalyzer = class {
|
|
|
5139
6214
|
}
|
|
5140
6215
|
return tuple(target.elements.map((el) => el ? leaf(el.value, el.default) : anyType));
|
|
5141
6216
|
}
|
|
6217
|
+
/** The type of `...` in each function body being walked. */
|
|
6218
|
+
varargs = [];
|
|
6219
|
+
/** What each function body being walked declared it returns. */
|
|
6220
|
+
declaredReturns = [];
|
|
6221
|
+
/** Run `body` with `...` and `return` as `func` declares them. */
|
|
6222
|
+
withVarargs(func, body) {
|
|
6223
|
+
this.varargs.push(func.hasVarargs ? func.varargTypeAnnotation ? this.resolveType(func.varargTypeAnnotation) : anyType : void 0);
|
|
6224
|
+
this.declaredReturns.push(func.predicate ? booleanType : func.returnType ? this.resolveType(func.returnType) : void 0);
|
|
6225
|
+
try {
|
|
6226
|
+
return body();
|
|
6227
|
+
} finally {
|
|
6228
|
+
this.varargs.pop();
|
|
6229
|
+
this.declaredReturns.pop();
|
|
6230
|
+
}
|
|
6231
|
+
}
|
|
5142
6232
|
visitFunctionBodyInner(func, outerEnv) {
|
|
5143
6233
|
const env = forkEnv(outerEnv);
|
|
5144
6234
|
for (const p of func.params) {
|
|
5145
6235
|
if (p.pattern) {
|
|
5146
|
-
|
|
6236
|
+
const type = this.paramType(p, env);
|
|
6237
|
+
this.bindPattern(p.pattern, type, env, "widen");
|
|
6238
|
+
this.correlateDestructuring(p.pattern, type, env);
|
|
5147
6239
|
continue;
|
|
5148
6240
|
}
|
|
5149
6241
|
const id = this.bindingIdByName(p.name, p);
|
|
@@ -5154,13 +6246,16 @@ var TypeAnalyzer = class {
|
|
|
5154
6246
|
if (p.typeAnnotation) this.annotated.add(id);
|
|
5155
6247
|
}
|
|
5156
6248
|
}
|
|
5157
|
-
this.
|
|
6249
|
+
this.withVarargs(func, () => this.collectReturns(void 0, () => {
|
|
6250
|
+
this.visitBlock(func.body, env);
|
|
6251
|
+
this.checkReturnsAtAll(func, this.declaredReturns[this.declaredReturns.length - 1]);
|
|
6252
|
+
}));
|
|
5158
6253
|
}
|
|
5159
6254
|
/** Return type of calling `f` with `argTypes`. For a generic function,
|
|
5160
6255
|
* infers the type parameters from the arguments and substitutes. */
|
|
5161
|
-
callReturn(f, argTypes) {
|
|
6256
|
+
callReturn(f, argTypes, explicit) {
|
|
5162
6257
|
if (!f.typeParams?.length) return f.returns;
|
|
5163
|
-
return this.reduceType(substitute(f.returns, this.inferTypeArgs(f, argTypes)));
|
|
6258
|
+
return this.reduceType(substitute(f.returns, this.inferTypeArgs(f, argTypes, explicit)));
|
|
5164
6259
|
}
|
|
5165
6260
|
/** Infer a generic call's type arguments from the argument types.
|
|
5166
6261
|
*
|
|
@@ -5168,16 +6263,47 @@ var TypeAnalyzer = class {
|
|
|
5168
6263
|
* `1` — *except* against a parameter whose constraint is made of literal
|
|
5169
6264
|
* types, where the literal is the whole point. That is what lets
|
|
5170
6265
|
* `<K extends keyof T>(name: K) -> T[K]` pick out one property. */
|
|
5171
|
-
|
|
5172
|
-
|
|
6266
|
+
/** The type arguments a call writes out, checked for count. */
|
|
6267
|
+
explicitTypeArguments(expr, fns) {
|
|
6268
|
+
const written = expr.typeArguments;
|
|
6269
|
+
if (!written?.length) return void 0;
|
|
6270
|
+
const resolved = written.map((node) => this.resolveType(node));
|
|
6271
|
+
const most = Math.max(0, ...fns.map((f) => f.typeParams?.length ?? 0));
|
|
6272
|
+
if (this.emitDiagnostics && resolved.length > most) {
|
|
6273
|
+
this.diagnostics.push({
|
|
6274
|
+
node: written[most],
|
|
6275
|
+
message: most === 0 ? "This call takes no type arguments" : `Expected ${most} type argument${most === 1 ? "" : "s"}, got ${resolved.length}`
|
|
6276
|
+
});
|
|
6277
|
+
}
|
|
6278
|
+
return resolved;
|
|
6279
|
+
}
|
|
6280
|
+
/** `<T = Instance>`: what a call falls back to for a parameter it neither
|
|
6281
|
+
* is given nor can infer. */
|
|
6282
|
+
withTypeParamDefaults(type, generics) {
|
|
6283
|
+
if (type.kind !== "function") return type;
|
|
6284
|
+
const defaults = {};
|
|
6285
|
+
for (const generic of generics) {
|
|
6286
|
+
if (generic.default && !generic.isPack) defaults[generic.name] = this.resolveType(generic.default);
|
|
6287
|
+
}
|
|
6288
|
+
return Object.keys(defaults).length ? { ...type, typeParamDefaults: defaults } : type;
|
|
6289
|
+
}
|
|
6290
|
+
inferTypeArgs(f, argTypes, explicit) {
|
|
5173
6291
|
const subst = /* @__PURE__ */ new Map();
|
|
6292
|
+
if (explicit?.length) {
|
|
6293
|
+
(f.typeParams ?? []).forEach((name, i) => {
|
|
6294
|
+
if (explicit[i]) subst.set(name, explicit[i]);
|
|
6295
|
+
});
|
|
6296
|
+
}
|
|
6297
|
+
const vars = new Set((f.typeParams ?? []).filter((name) => !subst.has(name)));
|
|
5174
6298
|
f.params.forEach((p, i) => {
|
|
5175
6299
|
const arg = argTypes[i];
|
|
5176
6300
|
if (arg === void 0) return;
|
|
5177
6301
|
const param = p.type.kind === "typeParam" && p.type.constraint ? { ...p.type, constraint: this.reduceType(p.type.constraint) } : p.type;
|
|
5178
6302
|
unify(p.type, keepsLiterals(param) ? arg : widen(arg), vars, subst);
|
|
5179
6303
|
});
|
|
5180
|
-
for (const name of f.typeParams ?? [])
|
|
6304
|
+
for (const name of f.typeParams ?? []) {
|
|
6305
|
+
if (!subst.has(name)) subst.set(name, f.typeParamDefaults?.[name] ?? unknownType);
|
|
6306
|
+
}
|
|
5181
6307
|
return subst;
|
|
5182
6308
|
}
|
|
5183
6309
|
/** Re-infer the arguments that land on a `<const T>` parameter, keeping
|
|
@@ -5208,6 +6334,32 @@ var TypeAnalyzer = class {
|
|
|
5208
6334
|
}
|
|
5209
6335
|
return void 0;
|
|
5210
6336
|
}
|
|
6337
|
+
/** An overload set called with a union argument, one member at a time.
|
|
6338
|
+
*
|
|
6339
|
+
* One signature for the whole union is often only the catch-all:
|
|
6340
|
+
* `typeof(v)` with `v: Part | nil` accepts nothing more specific than
|
|
6341
|
+
* `typeof<T>(value: T): string`. Each member on its own picks `"Instance"`
|
|
6342
|
+
* and `"nil"`, and that union is what the call returns — whenever every
|
|
6343
|
+
* member picks a signature listed ahead of the whole union's. Otherwise
|
|
6344
|
+
* (a signature taking the union as it is, or a member nothing accepts)
|
|
6345
|
+
* this returns `undefined` and the ordinary pick stands. */
|
|
6346
|
+
distributedReturn(fns, argTypes, picked, argsFor) {
|
|
6347
|
+
if (fns.length < 2) return void 0;
|
|
6348
|
+
const position = argTypes.findIndex((t) => this.expand(t).kind === "union");
|
|
6349
|
+
if (position < 0) return void 0;
|
|
6350
|
+
const members = this.expand(argTypes[position]).types;
|
|
6351
|
+
if (members.length > 32) return void 0;
|
|
6352
|
+
const rank = (f) => ((f.typeParams?.length ?? 0) > 0 ? fns.length : 0) + fns.indexOf(f);
|
|
6353
|
+
const limit = picked ? rank(picked) : Infinity;
|
|
6354
|
+
const results = [];
|
|
6355
|
+
for (const member of members) {
|
|
6356
|
+
const args = argTypes.map((t, i) => i === position ? member : t);
|
|
6357
|
+
const chosen = this.pickOverload(fns, args, (f) => argsFor(f, args));
|
|
6358
|
+
if (!chosen || rank(chosen) >= limit) return void 0;
|
|
6359
|
+
results.push(this.callReturn(chosen, argsFor(chosen, args)));
|
|
6360
|
+
}
|
|
6361
|
+
return union(results);
|
|
6362
|
+
}
|
|
5211
6363
|
/** Can this signature be called with these argument types? The signature's
|
|
5212
6364
|
* own type parameters stand for what the call would infer, so each is
|
|
5213
6365
|
* checked only against its constraint — `<K extends keyof Services>`
|
|
@@ -5243,7 +6395,7 @@ var TypeAnalyzer = class {
|
|
|
5243
6395
|
for (const child of Object.values(value)) walk(child);
|
|
5244
6396
|
};
|
|
5245
6397
|
for (const p of f.params) if (containsTypeParam(p.type)) walk(p.type);
|
|
5246
|
-
return f.params.map((p) => substitute(p.type, bounds));
|
|
6398
|
+
return f.params.map((p) => this.reduceType(substitute(p.type, bounds)));
|
|
5247
6399
|
}
|
|
5248
6400
|
/** Record what each written argument is expected to be — see
|
|
5249
6401
|
* `TypeAnalysis.expectedTypeOf`. */
|
|
@@ -5260,6 +6412,28 @@ var TypeAnalyzer = class {
|
|
|
5260
6412
|
}
|
|
5261
6413
|
/** No signature accepts the call, and the argument count is not the
|
|
5262
6414
|
* problem: say which argument is wrong, the way TypeScript does. */
|
|
6415
|
+
/** Check what was written against the parameters as this call's own type
|
|
6416
|
+
* arguments make them read: `pick("Bones", "C")` is wrong only once `P`
|
|
6417
|
+
* is known to be `"Bones"`. Picking the overload goes by each parameter's
|
|
6418
|
+
* constraint, which is deliberately looser than that. */
|
|
6419
|
+
checkInferredArguments(call, written, f, argTypes, self) {
|
|
6420
|
+
if (!this.emitDiagnostics || !f.typeParams?.length) return;
|
|
6421
|
+
const subst = this.inferTypeArgs(f, [...argTypes]);
|
|
6422
|
+
for (const bound of subst.values()) if (bound.kind === "unknown") return;
|
|
6423
|
+
for (let i = 0; i < f.params.length; i++) {
|
|
6424
|
+
const arg = argTypes[i];
|
|
6425
|
+
const declared = f.params[i].type;
|
|
6426
|
+
if (arg === void 0 || !containsTypeParam(declared)) continue;
|
|
6427
|
+
const expected = this.reduceType(substitute(declared, subst));
|
|
6428
|
+
if (containsTypeParam(expected) || expected.kind === "any" || expected.kind === "unknown") continue;
|
|
6429
|
+
if (isAssignable(arg, expected) || isAssignable(widen(arg), expected)) continue;
|
|
6430
|
+
this.diagnostics.push({
|
|
6431
|
+
node: written[i - self] ?? call,
|
|
6432
|
+
message: `Argument of type '${formatType(arg)}' is not assignable to parameter of type '${briefType(expected)}'`
|
|
6433
|
+
});
|
|
6434
|
+
return;
|
|
6435
|
+
}
|
|
6436
|
+
}
|
|
5263
6437
|
reportArguments(call, written, fns, argsFor, selfOf) {
|
|
5264
6438
|
if (!this.emitDiagnostics) return;
|
|
5265
6439
|
if (fns.length > 1) {
|
|
@@ -5329,9 +6503,33 @@ var TypeAnalyzer = class {
|
|
|
5329
6503
|
});
|
|
5330
6504
|
return false;
|
|
5331
6505
|
}
|
|
6506
|
+
/** An overload set's implementation handles every signature, so a bare
|
|
6507
|
+
* parameter of it holds whatever those signatures allow there:
|
|
6508
|
+
* `function f(Stat, ...)` under 36 `Stat: "..."` signatures is the union
|
|
6509
|
+
* of all 36. TypeScript leaves such a parameter `any`; this says what it
|
|
6510
|
+
* can actually be. An annotation, a pattern or a default still wins. */
|
|
6511
|
+
paramsFromSignatures(func, signatures) {
|
|
6512
|
+
if (!signatures?.length) return;
|
|
6513
|
+
const resolved = signatures.map((sig) => this.signatureToFnType(sig));
|
|
6514
|
+
func.params.forEach((param, i) => {
|
|
6515
|
+
if (param.typeAnnotation || param.pattern || param.default) return;
|
|
6516
|
+
const candidates = [];
|
|
6517
|
+
for (const signature of resolved) {
|
|
6518
|
+
if (signature.kind !== "function") continue;
|
|
6519
|
+
const own = signature.params[i];
|
|
6520
|
+
if (own) candidates.push(own.optional ? optional(own.type) : own.type);
|
|
6521
|
+
else if (signature.varargs) candidates.push(signature.varargs);
|
|
6522
|
+
}
|
|
6523
|
+
if (candidates.length) this.contextualParams.set(param, union(candidates));
|
|
6524
|
+
});
|
|
6525
|
+
}
|
|
5332
6526
|
signatureToFnType(sig) {
|
|
5333
6527
|
const names = sig.generics.map((g) => g.name);
|
|
5334
|
-
|
|
6528
|
+
const record = (type) => {
|
|
6529
|
+
this.typeOfTypeNode.set(sig, type);
|
|
6530
|
+
return type;
|
|
6531
|
+
};
|
|
6532
|
+
return record(this.withTypeParams(sig.generics, () => {
|
|
5335
6533
|
const params = sig.params.map((p) => ({
|
|
5336
6534
|
name: p.pattern ? void 0 : p.name,
|
|
5337
6535
|
type: this.paramType(p, /* @__PURE__ */ new Map()),
|
|
@@ -5344,7 +6542,7 @@ var TypeAnalyzer = class {
|
|
|
5344
6542
|
names,
|
|
5345
6543
|
this.resolvePredicate(sig.predicate, params)
|
|
5346
6544
|
);
|
|
5347
|
-
});
|
|
6545
|
+
}));
|
|
5348
6546
|
}
|
|
5349
6547
|
/** Turn a parsed `v is T` / `asserts v` annotation into a `TypePredicate`,
|
|
5350
6548
|
* resolving the named parameter to its index. A guard naming a parameter
|
|
@@ -5389,8 +6587,11 @@ var TypeAnalyzer = class {
|
|
|
5389
6587
|
} else if (func.predicate) {
|
|
5390
6588
|
returns = booleanType;
|
|
5391
6589
|
} else {
|
|
5392
|
-
|
|
5393
|
-
returns = this.
|
|
6590
|
+
const collected = [];
|
|
6591
|
+
returns = this.withVarargs(func, () => this.silently(() => {
|
|
6592
|
+
this.collectReturns(collected, () => this.preVisitBody(func.body, bodyEnv));
|
|
6593
|
+
return collected.length ? union(collected) : this.inferReturnType(func.body, bodyEnv);
|
|
6594
|
+
}));
|
|
5394
6595
|
}
|
|
5395
6596
|
return fn(
|
|
5396
6597
|
params,
|
|
@@ -5401,6 +6602,29 @@ var TypeAnalyzer = class {
|
|
|
5401
6602
|
);
|
|
5402
6603
|
});
|
|
5403
6604
|
}
|
|
6605
|
+
/** Where the return types of the function being walked are collected, so
|
|
6606
|
+
* each is read where it is written — inside the branch that narrowed it —
|
|
6607
|
+
* rather than in whatever state the body ends in. */
|
|
6608
|
+
returnTypes;
|
|
6609
|
+
collectReturns(into, body) {
|
|
6610
|
+
const previous = this.returnTypes;
|
|
6611
|
+
this.returnTypes = into;
|
|
6612
|
+
try {
|
|
6613
|
+
return body();
|
|
6614
|
+
} finally {
|
|
6615
|
+
this.returnTypes = previous;
|
|
6616
|
+
}
|
|
6617
|
+
}
|
|
6618
|
+
/** Run something without reporting what it finds. */
|
|
6619
|
+
silently(body) {
|
|
6620
|
+
const wasEmitting = this.emitDiagnostics;
|
|
6621
|
+
this.emitDiagnostics = false;
|
|
6622
|
+
try {
|
|
6623
|
+
return body();
|
|
6624
|
+
} finally {
|
|
6625
|
+
this.emitDiagnostics = wasEmitting;
|
|
6626
|
+
}
|
|
6627
|
+
}
|
|
5404
6628
|
/** Populate binding types for a function body without reporting anything,
|
|
5405
6629
|
* purely so an un-annotated return type can see its own locals. Bounded:
|
|
5406
6630
|
* nested functions stop pre-visiting after a couple of levels, since the
|
|
@@ -5417,6 +6641,157 @@ var TypeAnalyzer = class {
|
|
|
5417
6641
|
this.preVisitDepth--;
|
|
5418
6642
|
}
|
|
5419
6643
|
}
|
|
6644
|
+
/** The `[key, value]` pairs iterating a record yields, one per property —
|
|
6645
|
+
* for `pairs(t)`, `next, t` and `for k, v in t` over an object type with
|
|
6646
|
+
* no indexer. `undefined` for anything else (an array, a dictionary, an
|
|
6647
|
+
* iterator function), whose keys have no names to list. */
|
|
6648
|
+
iterationRows(iterNode, iterType) {
|
|
6649
|
+
let source;
|
|
6650
|
+
if (iterNode?.type === "CallExpression" && iterNode.callee.type === "Identifier" && iterNode.arguments[0]) {
|
|
6651
|
+
if (iterNode.callee.name !== "pairs" && iterNode.callee.name !== "next") return void 0;
|
|
6652
|
+
source = this.typeOf.get(iterNode.arguments[0]);
|
|
6653
|
+
} else {
|
|
6654
|
+
source = iterType;
|
|
6655
|
+
}
|
|
6656
|
+
const t = source && this.expand(source);
|
|
6657
|
+
if (!t || t.kind !== "object" || t.class || t.indexer || !t.properties.size) return void 0;
|
|
6658
|
+
return [...t.properties].map(([name, property]) => [
|
|
6659
|
+
literal(name),
|
|
6660
|
+
property.optional ? optional(property.type) : property.type
|
|
6661
|
+
]);
|
|
6662
|
+
}
|
|
6663
|
+
/** Bindings that hold parts of one value: the key and value of a `pairs`
|
|
6664
|
+
* row, or the names destructured from one union member. By flow key.
|
|
6665
|
+
* Which rows are still possible is itself flow state, kept in `env` under
|
|
6666
|
+
* `group` as a union of tuples, so it narrows and merges like any type. */
|
|
6667
|
+
correlations = /* @__PURE__ */ new Map();
|
|
6668
|
+
correlateBindings(env, ids, rows) {
|
|
6669
|
+
const keys = ids.map(bindKey);
|
|
6670
|
+
const group = `rows(${keys.join(",")})`;
|
|
6671
|
+
keys.forEach((key, index) => this.correlations.set(key, { group, index, keys, rows }));
|
|
6672
|
+
env.set(group, union(rows.map((row) => tuple(row))));
|
|
6673
|
+
}
|
|
6674
|
+
/** `key` was just narrowed to `narrowed` in `env`: narrow that column of
|
|
6675
|
+
* every row, drop the rows it rules out, and give the other bindings what
|
|
6676
|
+
* the remaining rows hold. */
|
|
6677
|
+
correlate(env, key, narrowed) {
|
|
6678
|
+
const entry = this.correlations.get(key);
|
|
6679
|
+
if (!entry) return;
|
|
6680
|
+
const state = env.get(entry.group);
|
|
6681
|
+
const current = state && (state.kind === "union" ? state.types : [state]).every((t) => t.kind === "tuple") ? (state.kind === "union" ? state.types : [state]).map((t) => t.elements) : entry.rows;
|
|
6682
|
+
const kept = [];
|
|
6683
|
+
for (const row of current) {
|
|
6684
|
+
const column = narrowTo(row[entry.index], narrowed);
|
|
6685
|
+
if (column.kind !== "never") kept.push(row.map((t, i) => i === entry.index ? column : t));
|
|
6686
|
+
}
|
|
6687
|
+
env.set(entry.group, kept.length ? union(kept.map((row) => tuple(row))) : neverType);
|
|
6688
|
+
entry.keys.forEach((other, j) => {
|
|
6689
|
+
if (j !== entry.index) env.set(other, kept.length ? union(kept.map((row) => row[j])) : neverType);
|
|
6690
|
+
});
|
|
6691
|
+
}
|
|
6692
|
+
/** Stop correlating a binding once it is assigned: its value no longer
|
|
6693
|
+
* comes from the row. */
|
|
6694
|
+
uncorrelate(id) {
|
|
6695
|
+
const entry = this.correlations.get(bindKey(id));
|
|
6696
|
+
if (entry) for (const key of entry.keys) this.correlations.delete(key);
|
|
6697
|
+
}
|
|
6698
|
+
/** `const { kind, payload } = action` over a union of objects: one row per
|
|
6699
|
+
* member, so testing `kind` narrows `payload` (TypeScript's destructured
|
|
6700
|
+
* discriminated unions). Only plain `name` / `key: name` properties take
|
|
6701
|
+
* part. */
|
|
6702
|
+
/** Names that denote one and the same value: `const c = player.Character`
|
|
6703
|
+
* makes `c` and `player.Character` two spellings of one reference. Kept
|
|
6704
|
+
* as an undirected graph of flow keys. */
|
|
6705
|
+
refAliases = /* @__PURE__ */ new Map();
|
|
6706
|
+
/** `const c = a.b` — `c` cannot be re-bound and the path was read once, so
|
|
6707
|
+
* a test of either name is a test of the same value. Only property paths
|
|
6708
|
+
* take part: `const c = other` would tie `c` to a name that may itself be
|
|
6709
|
+
* assigned a different value later. */
|
|
6710
|
+
aliasReference(target, init) {
|
|
6711
|
+
if (target.type !== "IdentifierPattern" || !init) return;
|
|
6712
|
+
const source = unwrapParens(init);
|
|
6713
|
+
if (source.type !== "MemberExpression" && source.type !== "IndexExpression") return;
|
|
6714
|
+
const path = this.refKeyOf(source);
|
|
6715
|
+
const id = this.bindingIdByName(target.name, target);
|
|
6716
|
+
if (path === void 0 || id === void 0) return;
|
|
6717
|
+
const name = bindKey(id);
|
|
6718
|
+
for (const [a, b] of [[name, path], [path, name]]) {
|
|
6719
|
+
const set = this.refAliases.get(a) ?? /* @__PURE__ */ new Set();
|
|
6720
|
+
set.add(b);
|
|
6721
|
+
this.refAliases.set(a, set);
|
|
6722
|
+
}
|
|
6723
|
+
}
|
|
6724
|
+
/** A reference was narrowed: give every other spelling of the same value
|
|
6725
|
+
* the same news. Walks the alias graph, so a path with two names told by
|
|
6726
|
+
* one of them reaches the other. Each alias keeps whatever it already
|
|
6727
|
+
* knew — the narrowing only ever cuts the type further down. */
|
|
6728
|
+
propagateAliases(env, into, key, narrowed) {
|
|
6729
|
+
if (!this.refAliases.size) return;
|
|
6730
|
+
const seen = /* @__PURE__ */ new Set([key]);
|
|
6731
|
+
const queue = [[key, narrowed]];
|
|
6732
|
+
const learn = (at, t) => {
|
|
6733
|
+
seen.add(at);
|
|
6734
|
+
this.setRef(into, at, t);
|
|
6735
|
+
this.correlate(into, at, t);
|
|
6736
|
+
queue.push([at, t]);
|
|
6737
|
+
};
|
|
6738
|
+
for (let at = 0; at < queue.length; at++) {
|
|
6739
|
+
const [from, t] = queue[at];
|
|
6740
|
+
for (const other of this.refAliases.get(from) ?? []) {
|
|
6741
|
+
if (seen.has(other)) continue;
|
|
6742
|
+
const current = into.get(other) ?? env.get(other) ?? this.declaredAtRef(other);
|
|
6743
|
+
const next = narrowTo(current, t);
|
|
6744
|
+
learn(other, next.kind === "never" ? t : next);
|
|
6745
|
+
for (let child = other, value = into.get(other); ; ) {
|
|
6746
|
+
const cut = child.lastIndexOf(".");
|
|
6747
|
+
if (cut <= 0) break;
|
|
6748
|
+
const parent = child.slice(0, cut);
|
|
6749
|
+
if (seen.has(parent)) break;
|
|
6750
|
+
const had = into.get(parent) ?? env.get(parent) ?? this.declaredAtRef(parent);
|
|
6751
|
+
value = this.filterByProperty(had, child.slice(cut + 1), value);
|
|
6752
|
+
learn(parent, value);
|
|
6753
|
+
child = parent;
|
|
6754
|
+
}
|
|
6755
|
+
}
|
|
6756
|
+
}
|
|
6757
|
+
}
|
|
6758
|
+
/** `const path = paths[stat]` where `stat` is one of several keys: which
|
|
6759
|
+
* value came back says which key was asked for. Testing the value then
|
|
6760
|
+
* narrows the key — the `else` of `if path then` leaves exactly the keys
|
|
6761
|
+
* the table does not have. */
|
|
6762
|
+
correlateIndexed(target, init, env) {
|
|
6763
|
+
if (target.type !== "IdentifierPattern" || !init) return;
|
|
6764
|
+
const source = unwrapParens(init);
|
|
6765
|
+
if (source.type !== "IndexExpression" || source.index.type !== "Identifier") return;
|
|
6766
|
+
const valueId = this.bindingIdByName(target.name, target);
|
|
6767
|
+
const keyId = this.bindingIdOf(source.index);
|
|
6768
|
+
if (valueId === void 0 || keyId === void 0) return;
|
|
6769
|
+
const key = this.expand(this.currentType(keyId, env));
|
|
6770
|
+
if (key.kind !== "union" || key.types.length < 2 || key.types.length > 64) return;
|
|
6771
|
+
if (!key.types.every((m) => m.kind === "literal")) return;
|
|
6772
|
+
const object = this.expand(this.typeOf.get(source.object) ?? unknownType);
|
|
6773
|
+
if (object.kind !== "object") return;
|
|
6774
|
+
this.correlateBindings(env, [keyId, valueId], key.types.map((m) => [m, this.indexedType(object, m)]));
|
|
6775
|
+
}
|
|
6776
|
+
correlateDestructuring(pattern, source, env) {
|
|
6777
|
+
if (pattern.type !== "ObjectPattern") return;
|
|
6778
|
+
const members = this.expand(source);
|
|
6779
|
+
if (members.kind !== "union") return;
|
|
6780
|
+
const objects = members.types.map((m) => this.expand(m));
|
|
6781
|
+
if (objects.length < 2 || objects.some((m) => m.kind !== "object")) return;
|
|
6782
|
+
const ids = [];
|
|
6783
|
+
const names = [];
|
|
6784
|
+
for (const property of pattern.properties) {
|
|
6785
|
+
if (property.computed || property.default || property.value.type !== "IdentifierPattern") return;
|
|
6786
|
+
const name = property.key.type === "Identifier" ? property.key.name : property.key.type === "StringLiteral" ? property.key.value : void 0;
|
|
6787
|
+
const id = this.bindingIdByName(property.value.name, property.value);
|
|
6788
|
+
if (name === void 0 || id === void 0) return;
|
|
6789
|
+
ids.push(id);
|
|
6790
|
+
names.push(name);
|
|
6791
|
+
}
|
|
6792
|
+
if (ids.length < 2) return;
|
|
6793
|
+
this.correlateBindings(env, ids, objects.map((member) => names.map((name) => this.propertyType(member, name))));
|
|
6794
|
+
}
|
|
5420
6795
|
/** `(keyType, valueType)` yielded by a generic-for iterator. Handles
|
|
5421
6796
|
* `ipairs`/`pairs`/`next(t)` and Luau generalized iteration (`for … in t`).
|
|
5422
6797
|
* `varCount` is how many loop variables were written. */
|
|
@@ -5481,6 +6856,18 @@ var TypeAnalyzer = class {
|
|
|
5481
6856
|
if (init.type === "ArrayExpression") return isAssignable(this.inferArray(init, env, true), declared);
|
|
5482
6857
|
return false;
|
|
5483
6858
|
}
|
|
6859
|
+
/** `{ a, ...rest }`: what `rest` holds — the value without the properties
|
|
6860
|
+
* the pattern already took. */
|
|
6861
|
+
withoutKeys(raw, properties) {
|
|
6862
|
+
const taken = new Set(properties.flatMap((p) => !p.computed && p.key.type === "Identifier" ? [p.key.name] : !p.computed && p.key.type === "StringLiteral" ? [p.key.value] : []));
|
|
6863
|
+
if (!taken.size) return raw;
|
|
6864
|
+
const t = this.expand(raw);
|
|
6865
|
+
if (t.kind === "union") return union(t.types.map((m) => this.withoutKeys(m, properties)));
|
|
6866
|
+
if (t.kind !== "object") return raw;
|
|
6867
|
+
const kept = [...t.properties].filter(([name]) => !taken.has(name));
|
|
6868
|
+
if (kept.length === t.properties.size) return raw;
|
|
6869
|
+
return objectType(kept, t.indexer, t.frozen);
|
|
6870
|
+
}
|
|
5484
6871
|
/** Fold a destructuring default (`{ a = 1 }`) into the property's type:
|
|
5485
6872
|
* the default applies when the source value is missing/`nil`. */
|
|
5486
6873
|
withDefault(base, def, env) {
|
|
@@ -5512,7 +6899,7 @@ var TypeAnalyzer = class {
|
|
|
5512
6899
|
const pt = key !== void 0 ? this.propertyType(valueType, key) : unknownType;
|
|
5513
6900
|
this.reassignPattern(p.value, this.withDefault(pt, p.default, env), env);
|
|
5514
6901
|
}
|
|
5515
|
-
if (target.rest) this.reassignPattern(target.rest, valueType, env);
|
|
6902
|
+
if (target.rest) this.reassignPattern(target.rest, this.withoutKeys(valueType, target.properties), env);
|
|
5516
6903
|
return;
|
|
5517
6904
|
}
|
|
5518
6905
|
case "ArrayPattern": {
|
|
@@ -5547,7 +6934,7 @@ var TypeAnalyzer = class {
|
|
|
5547
6934
|
const propType = key !== void 0 ? this.propertyType(valueType, key) : unknownType;
|
|
5548
6935
|
this.bindPattern(p.value, this.withDefault(propType, p.default, env), env, mode);
|
|
5549
6936
|
}
|
|
5550
|
-
if (target.rest) this.bindPattern(target.rest, valueType, env, mode);
|
|
6937
|
+
if (target.rest) this.bindPattern(target.rest, this.withoutKeys(valueType, target.properties), env, mode);
|
|
5551
6938
|
return;
|
|
5552
6939
|
}
|
|
5553
6940
|
case "ArrayPattern": {
|
|
@@ -5590,7 +6977,7 @@ var TypeAnalyzer = class {
|
|
|
5590
6977
|
}
|
|
5591
6978
|
}
|
|
5592
6979
|
propertyType(raw, name) {
|
|
5593
|
-
const t = this.expand(raw);
|
|
6980
|
+
const t = this.deferredAccess(this.expand(raw));
|
|
5594
6981
|
if (t.kind === "object") {
|
|
5595
6982
|
const p = t.properties.get(name);
|
|
5596
6983
|
if (p) return p.optional ? optional(p.type) : p.type;
|
|
@@ -5613,21 +7000,37 @@ var TypeAnalyzer = class {
|
|
|
5613
7000
|
const t = this.expand(raw);
|
|
5614
7001
|
if (t.kind === "any") return anyType;
|
|
5615
7002
|
if (t.kind === "union") return union(t.types.map((m) => this.indexedType(m, idx)));
|
|
5616
|
-
|
|
5617
|
-
if (
|
|
7003
|
+
const index = this.expand(idx);
|
|
7004
|
+
if (index.kind === "union") return union(index.types.map((m) => this.indexedType(t, m)));
|
|
7005
|
+
if (t.kind === "difference") return this.indexedType(t.base, index);
|
|
7006
|
+
if (t.kind === "typeParam" && t.constraint) return this.indexedType(t.constraint, index);
|
|
5618
7007
|
if (t.kind === "array") return t.element;
|
|
5619
7008
|
if (t.kind === "tuple") {
|
|
5620
|
-
if (
|
|
5621
|
-
return t.elements[
|
|
7009
|
+
if (index.kind === "literal" && typeof index.value === "number") {
|
|
7010
|
+
return t.elements[index.value - 1] ?? nilType;
|
|
5622
7011
|
}
|
|
5623
7012
|
return union(t.elements);
|
|
5624
7013
|
}
|
|
5625
7014
|
if (t.kind === "object") {
|
|
5626
|
-
if (
|
|
7015
|
+
if (index.kind === "literal" && typeof index.value === "string") {
|
|
7016
|
+
const property = t.properties.get(index.value);
|
|
7017
|
+
if (property) return property.optional ? optional(property.type) : property.type;
|
|
7018
|
+
if (t.indexer && isAssignable(index, t.indexer.key)) return t.indexer.value;
|
|
7019
|
+
return nilType;
|
|
7020
|
+
}
|
|
7021
|
+
if (containsTypeParam(index)) return this.reduceType({ kind: "indexedAccess", objectType: t, indexType: index });
|
|
5627
7022
|
if (t.indexer) return t.indexer.value;
|
|
5628
7023
|
}
|
|
5629
7024
|
return unknownType;
|
|
5630
7025
|
}
|
|
7026
|
+
/** What a deferred `T[K]` can be: every property its index could name.
|
|
7027
|
+
* Reading a member of one, or calling it, sees that. */
|
|
7028
|
+
deferredAccess(t) {
|
|
7029
|
+
if (t.kind !== "indexedAccess") return t;
|
|
7030
|
+
const index = t.indexType.kind === "typeParam" && t.indexType.constraint ? t.indexType.constraint : t.indexType;
|
|
7031
|
+
if (containsTypeParam(index)) return unknownType;
|
|
7032
|
+
return this.accessType(t.objectType, index);
|
|
7033
|
+
}
|
|
5631
7034
|
elementType(raw, index) {
|
|
5632
7035
|
const t = this.expand(raw);
|
|
5633
7036
|
if (t.kind === "array") return t.element;
|
|
@@ -5660,7 +7063,11 @@ var TypeAnalyzer = class {
|
|
|
5660
7063
|
for (const part of expr.parts) if (part.kind === "expression") this.infer(part.expression, env);
|
|
5661
7064
|
return stringType;
|
|
5662
7065
|
}
|
|
7066
|
+
// `...` holds what the function declared it takes.
|
|
5663
7067
|
case "VarargExpression":
|
|
7068
|
+
return this.varargs[this.varargs.length - 1] ?? anyType;
|
|
7069
|
+
// Broken syntax is reported by the parser; nothing more to say.
|
|
7070
|
+
case "ErrorExpression":
|
|
5664
7071
|
return anyType;
|
|
5665
7072
|
case "Identifier": {
|
|
5666
7073
|
const id = this.bindingIdOf(expr);
|
|
@@ -5686,13 +7093,22 @@ var TypeAnalyzer = class {
|
|
|
5686
7093
|
return this.resolveType(expr.typeAnnotation);
|
|
5687
7094
|
}
|
|
5688
7095
|
case "SatisfiesExpression": {
|
|
5689
|
-
const actual = this.infer(expr.expression, env);
|
|
5690
7096
|
const declared = this.resolveType(expr.typeAnnotation);
|
|
5691
|
-
|
|
7097
|
+
this.applyContext(expr.expression, declared);
|
|
7098
|
+
if (declared.kind === "any") return this.infer(expr.expression, env);
|
|
7099
|
+
const written = unwrapParens(expr.expression);
|
|
7100
|
+
const fresh = written.type === "TableExpression" || written.type === "ArrayExpression";
|
|
7101
|
+
const narrow = fresh ? this.inferAsConst(expr.expression, env) : this.infer(expr.expression, env);
|
|
7102
|
+
const actual = fresh ? this.keepContextualLiterals(narrow, declared) : narrow;
|
|
7103
|
+
this.typeOf.set(expr.expression, actual);
|
|
7104
|
+
if (!this.emitDiagnostics) return actual;
|
|
7105
|
+
if (!isAssignable(narrow, declared) && !isAssignable(actual, declared)) {
|
|
5692
7106
|
this.diagnostics.push({
|
|
5693
7107
|
node: expr,
|
|
5694
|
-
message: `Type '${formatType(actual)}' does not satisfy '${formatType(declared)}'`
|
|
7108
|
+
message: `Type '${formatType(actual)}' does not satisfy the expected type '${formatType(declared)}'`
|
|
5695
7109
|
});
|
|
7110
|
+
} else {
|
|
7111
|
+
this.reportExcessProperties(expr.expression, declared);
|
|
5696
7112
|
}
|
|
5697
7113
|
return actual;
|
|
5698
7114
|
}
|
|
@@ -5726,6 +7142,10 @@ var TypeAnalyzer = class {
|
|
|
5726
7142
|
}
|
|
5727
7143
|
const l = this.infer(expr.left, env);
|
|
5728
7144
|
const r = this.infer(expr.right, env);
|
|
7145
|
+
if (op === "==" || op === "~=") {
|
|
7146
|
+
if (unwrapParens(expr.right).type === "StringLiteral") this.expectedTypeOf.set(unwrapParens(expr.right), l);
|
|
7147
|
+
if (unwrapParens(expr.left).type === "StringLiteral") this.expectedTypeOf.set(unwrapParens(expr.left), r);
|
|
7148
|
+
}
|
|
5729
7149
|
switch (op) {
|
|
5730
7150
|
case "..":
|
|
5731
7151
|
return this.operatorResult(expr, op, l, r) ?? stringType;
|
|
@@ -5748,57 +7168,25 @@ var TypeAnalyzer = class {
|
|
|
5748
7168
|
return union([l, r]);
|
|
5749
7169
|
}
|
|
5750
7170
|
case "MemberExpression": {
|
|
5751
|
-
const obj = this.
|
|
7171
|
+
const { type: obj, shortCircuits } = this.chainObject(expr, expr.object, env);
|
|
5752
7172
|
const key = this.refKeyOf(expr);
|
|
5753
7173
|
const narrowed = key === void 0 ? void 0 : env.get(key);
|
|
5754
|
-
return narrowed ?? this.propertyType(obj, expr.property.name);
|
|
7174
|
+
return this.chainResult(expr, narrowed ?? this.propertyType(obj, expr.property.name), shortCircuits);
|
|
5755
7175
|
}
|
|
5756
7176
|
case "IndexExpression": {
|
|
5757
|
-
const obj = this.
|
|
7177
|
+
const { type: obj, shortCircuits } = this.chainObject(expr, expr.object, env);
|
|
5758
7178
|
const idx = this.infer(expr.index, env);
|
|
5759
7179
|
const key = this.refKeyOf(expr);
|
|
5760
7180
|
const narrowed = key === void 0 ? void 0 : env.get(key);
|
|
5761
|
-
return narrowed ?? this.indexedType(obj, idx);
|
|
7181
|
+
return this.chainResult(expr, narrowed ?? this.indexedType(obj, idx), shortCircuits);
|
|
5762
7182
|
}
|
|
5763
7183
|
case "CallExpression": {
|
|
5764
|
-
const callee = this.
|
|
5765
|
-
|
|
5766
|
-
const expected = this.expectedArguments(expr.arguments, fns, () => 0);
|
|
5767
|
-
expr.arguments.forEach((a, i) => this.applyContext(a, expected[i]));
|
|
5768
|
-
const argTypes = expr.arguments.map((a) => this.infer(a, env));
|
|
5769
|
-
if (fns.length) {
|
|
5770
|
-
this.recordExpected(expr.arguments, fns, () => 0);
|
|
5771
|
-
const arityFits = this.checkArity(expr, fns, argTypes.length, 0);
|
|
5772
|
-
const picked = this.pickOverload(fns, argTypes);
|
|
5773
|
-
if (picked) {
|
|
5774
|
-
return this.callReturn(picked, this.constArgs(picked, expr.arguments, argTypes, env));
|
|
5775
|
-
}
|
|
5776
|
-
if (arityFits) this.reportArguments(expr, expr.arguments, fns, () => argTypes, () => 0);
|
|
5777
|
-
return union(fns.map((f) => this.callReturn(f, argTypes)));
|
|
5778
|
-
}
|
|
5779
|
-
return callee.kind === "any" ? anyType : unknownType;
|
|
7184
|
+
const { type: callee, shortCircuits } = this.chainObject(expr, expr.callee, env);
|
|
7185
|
+
return this.chainResult(expr, this.inferCall(expr, callee, env), shortCircuits);
|
|
5780
7186
|
}
|
|
5781
7187
|
case "MethodCallExpression": {
|
|
5782
|
-
const objType = this.
|
|
5783
|
-
|
|
5784
|
-
const expected = this.expectedArguments(expr.arguments, fns, (f) => this.takesSelf(f) ? 1 : 0);
|
|
5785
|
-
expr.arguments.forEach((a, i) => this.applyContext(a, expected[i]));
|
|
5786
|
-
const argTypes = expr.arguments.map((a) => this.infer(a, env));
|
|
5787
|
-
if (fns.length) {
|
|
5788
|
-
const withSelf = (f) => this.takesSelf(f) ? [objType, ...argTypes] : argTypes;
|
|
5789
|
-
const selfOf = (f) => this.takesSelf(f) ? 1 : 0;
|
|
5790
|
-
this.recordExpected(expr.arguments, fns, selfOf);
|
|
5791
|
-
const arityFits = this.checkArity(expr, fns, argTypes.length, this.takesSelf(fns[0]) ? 1 : 0);
|
|
5792
|
-
const picked = this.pickOverload(fns, argTypes, withSelf);
|
|
5793
|
-
if (picked) {
|
|
5794
|
-
const self = this.takesSelf(picked) ? 1 : 0;
|
|
5795
|
-
const written = this.constArgs(picked, expr.arguments, argTypes, env, self);
|
|
5796
|
-
return this.callReturn(picked, this.takesSelf(picked) ? [objType, ...written] : written);
|
|
5797
|
-
}
|
|
5798
|
-
if (arityFits) this.reportArguments(expr, expr.arguments, fns, withSelf, selfOf);
|
|
5799
|
-
return union(fns.map((f) => this.callReturn(f, withSelf(f))));
|
|
5800
|
-
}
|
|
5801
|
-
return objType.kind === "any" ? anyType : unknownType;
|
|
7188
|
+
const { type: objType, shortCircuits } = this.chainObject(expr, expr.object, env);
|
|
7189
|
+
return this.chainResult(expr, this.inferMethodCall(expr, objType, env), shortCircuits);
|
|
5802
7190
|
}
|
|
5803
7191
|
case "IfElseExpression": {
|
|
5804
7192
|
const branches = [];
|
|
@@ -5814,7 +7202,126 @@ var TypeAnalyzer = class {
|
|
|
5814
7202
|
}
|
|
5815
7203
|
}
|
|
5816
7204
|
}
|
|
7205
|
+
inferCall(expr, callee, env) {
|
|
7206
|
+
const fns = this.overloadsOf(callee);
|
|
7207
|
+
const explicit = this.explicitTypeArguments(expr, fns);
|
|
7208
|
+
const expected = this.expectedArguments(expr.arguments, fns, () => 0);
|
|
7209
|
+
expr.arguments.forEach((a, i) => this.applyContext(a, expected[i]));
|
|
7210
|
+
const argTypes = expr.arguments.map((a) => this.infer(a, env));
|
|
7211
|
+
if (fns.length) {
|
|
7212
|
+
this.recordExpected(expr.arguments, fns, () => 0);
|
|
7213
|
+
const arityFits = this.checkArity(expr, fns, argTypes.length, 0);
|
|
7214
|
+
const picked = this.pickOverload(fns, argTypes);
|
|
7215
|
+
const distributed = this.distributedReturn(fns, argTypes, picked, (_, args) => args);
|
|
7216
|
+
if (distributed) return distributed;
|
|
7217
|
+
if (picked) {
|
|
7218
|
+
this.checkInferredArguments(expr, expr.arguments, picked, argTypes, 0);
|
|
7219
|
+
return this.callReturn(picked, this.constArgs(picked, expr.arguments, argTypes, env), explicit);
|
|
7220
|
+
}
|
|
7221
|
+
if (arityFits) this.reportArguments(expr, expr.arguments, fns, () => argTypes, () => 0);
|
|
7222
|
+
return union(fns.map((f) => this.callReturn(f, argTypes, explicit)));
|
|
7223
|
+
}
|
|
7224
|
+
return callee.kind === "any" ? anyType : unknownType;
|
|
7225
|
+
}
|
|
7226
|
+
inferMethodCall(expr, objType, env) {
|
|
7227
|
+
const fns = this.overloadsOf(this.propertyType(objType, expr.method.name));
|
|
7228
|
+
const explicit = this.explicitTypeArguments(expr, fns);
|
|
7229
|
+
const expected = this.expectedArguments(expr.arguments, fns, (f) => this.takesSelf(f) ? 1 : 0);
|
|
7230
|
+
expr.arguments.forEach((a, i) => this.applyContext(a, expected[i]));
|
|
7231
|
+
const argTypes = expr.arguments.map((a) => this.infer(a, env));
|
|
7232
|
+
if (fns.length) {
|
|
7233
|
+
const withSelf = (f) => this.takesSelf(f) ? [objType, ...argTypes] : argTypes;
|
|
7234
|
+
const selfOf = (f) => this.takesSelf(f) ? 1 : 0;
|
|
7235
|
+
this.recordExpected(expr.arguments, fns, selfOf);
|
|
7236
|
+
const arityFits = this.checkArity(expr, fns, argTypes.length, this.takesSelf(fns[0]) ? 1 : 0);
|
|
7237
|
+
const picked = this.pickOverload(fns, argTypes, withSelf);
|
|
7238
|
+
const distributed = this.distributedReturn(
|
|
7239
|
+
fns,
|
|
7240
|
+
argTypes,
|
|
7241
|
+
picked,
|
|
7242
|
+
(f, args) => this.takesSelf(f) ? [objType, ...args] : args
|
|
7243
|
+
);
|
|
7244
|
+
if (distributed) return distributed;
|
|
7245
|
+
if (picked) {
|
|
7246
|
+
const self = this.takesSelf(picked) ? 1 : 0;
|
|
7247
|
+
this.checkInferredArguments(expr, expr.arguments, picked, withSelf(picked), self);
|
|
7248
|
+
const written = this.constArgs(picked, expr.arguments, argTypes, env, self);
|
|
7249
|
+
return this.callReturn(picked, this.takesSelf(picked) ? [objType, ...written] : written, explicit);
|
|
7250
|
+
}
|
|
7251
|
+
if (arityFits) this.reportArguments(expr, expr.arguments, fns, withSelf, selfOf);
|
|
7252
|
+
return union(fns.map((f) => this.callReturn(f, withSelf(f), explicit)));
|
|
7253
|
+
}
|
|
7254
|
+
return objType.kind === "any" ? anyType : unknownType;
|
|
7255
|
+
}
|
|
7256
|
+
// --------------------------------------------------------
|
|
7257
|
+
// Optional chains
|
|
7258
|
+
// --------------------------------------------------------
|
|
7259
|
+
//
|
|
7260
|
+
// `a?.b.c`: when `a` is nil the whole chain is nil and `.c` never runs.
|
|
7261
|
+
// So a link reads its object without the `nil` a `?.` earlier in the chain
|
|
7262
|
+
// added — that nil has already left the chain — and the chain's outermost
|
|
7263
|
+
// link carries it again. Parentheses end a chain: `(a?.b).c` reads `.c`
|
|
7264
|
+
// from `B | nil`.
|
|
7265
|
+
/** The type of a link's non-nil object, for each link that is past a `?.`:
|
|
7266
|
+
* what the chain holds when it has not short-circuited. */
|
|
7267
|
+
chainValue = /* @__PURE__ */ new WeakMap();
|
|
7268
|
+
/** The object a link reads from, and whether the chain can short-circuit
|
|
7269
|
+
* by this link. */
|
|
7270
|
+
chainObject(link, object, env) {
|
|
7271
|
+
const full = this.infer(object, env);
|
|
7272
|
+
const inChain = this.chainValue.get(object);
|
|
7273
|
+
let type = inChain ?? full;
|
|
7274
|
+
if (link.optional) {
|
|
7275
|
+
type = withoutNil(type);
|
|
7276
|
+
} else if (this.includesNil(type)) {
|
|
7277
|
+
this.reportNilAccess(object, type);
|
|
7278
|
+
type = withoutNil(this.expand(type));
|
|
7279
|
+
}
|
|
7280
|
+
return { type, shortCircuits: inChain !== void 0 || link.optional === true };
|
|
7281
|
+
}
|
|
7282
|
+
/** Objects already reported as possibly nil: a loop body is visited more
|
|
7283
|
+
* than once. */
|
|
7284
|
+
nilAccessReported = /* @__PURE__ */ new WeakSet();
|
|
7285
|
+
includesNil(raw) {
|
|
7286
|
+
const t = this.expand(raw);
|
|
7287
|
+
if (t.kind === "primitive") return t.name === "nil";
|
|
7288
|
+
return t.kind === "union" && t.types.some((m) => m.kind === "primitive" && m.name === "nil");
|
|
7289
|
+
}
|
|
7290
|
+
reportNilAccess(object, type) {
|
|
7291
|
+
if (!this.emitDiagnostics || this.nilAccessReported.has(object)) return;
|
|
7292
|
+
this.nilAccessReported.add(object);
|
|
7293
|
+
const label = expressionLabel(object);
|
|
7294
|
+
const t = this.expand(type);
|
|
7295
|
+
const nilOnly = t.kind === "primitive" && t.name === "nil";
|
|
7296
|
+
const subject = label === void 0 ? "Object" : `'${label}'`;
|
|
7297
|
+
this.diagnostics.push({
|
|
7298
|
+
node: object,
|
|
7299
|
+
message: nilOnly ? `${subject} is nil` : `${subject} is possibly nil. Check it first, or use '?.' / '?:'`
|
|
7300
|
+
});
|
|
7301
|
+
}
|
|
7302
|
+
chainResult(link, value, shortCircuits) {
|
|
7303
|
+
if (!shortCircuits) return value;
|
|
7304
|
+
this.chainValue.set(link, value);
|
|
7305
|
+
return union([value, nilType]);
|
|
7306
|
+
}
|
|
7307
|
+
/** The chain around `cond` did not short-circuit — it produced a truthy
|
|
7308
|
+
* value, or any value but nil — so every object a `?.` in it tested is not
|
|
7309
|
+
* nil in `env`. */
|
|
7310
|
+
narrowOptionalLinks(cond, env, into) {
|
|
7311
|
+
for (let e = cond; ; ) {
|
|
7312
|
+
const link = e;
|
|
7313
|
+
const object = e.type === "CallExpression" ? e.callee : e.type === "MemberExpression" || e.type === "IndexExpression" || e.type === "MethodCallExpression" ? e.object : void 0;
|
|
7314
|
+
if (!object) return;
|
|
7315
|
+
if (link.optional) {
|
|
7316
|
+
const key = this.refKeyOf(object);
|
|
7317
|
+
if (key !== void 0) this.setRef(into, key, withoutNil(this.typeAtRef(object, into)));
|
|
7318
|
+
}
|
|
7319
|
+
e = object;
|
|
7320
|
+
}
|
|
7321
|
+
}
|
|
5817
7322
|
inferArray(expr, env, asConst) {
|
|
7323
|
+
const contextual = this.contextualArrays.get(expr);
|
|
7324
|
+
if (contextual && !asConst) return contextual;
|
|
5818
7325
|
const elems = [];
|
|
5819
7326
|
let hadSpread = false;
|
|
5820
7327
|
for (const el of expr.elements) {
|
|
@@ -5860,6 +7367,123 @@ var TypeAnalyzer = class {
|
|
|
5860
7367
|
}
|
|
5861
7368
|
return objectType(entries, indexer, asConst || void 0);
|
|
5862
7369
|
}
|
|
7370
|
+
/** A value inferred `as const`, widened back wherever `context` does not
|
|
7371
|
+
* ask for a literal: `satisfies`' result type. A property keeps `"circle"`
|
|
7372
|
+
* when the contract's property admits string literals, and becomes
|
|
7373
|
+
* `string` when it is only `string`; a tuple becomes an array unless the
|
|
7374
|
+
* contract is a tuple; nothing stays readonly. */
|
|
7375
|
+
keepContextualLiterals(value, context) {
|
|
7376
|
+
const ctx = context === void 0 ? void 0 : this.expand(context);
|
|
7377
|
+
switch (value.kind) {
|
|
7378
|
+
case "literal":
|
|
7379
|
+
return ctx && this.admitsLiteral(ctx, value.base) ? value : widen(value);
|
|
7380
|
+
case "object": {
|
|
7381
|
+
if (value.class) return value;
|
|
7382
|
+
const entries = [...value.properties].map(([name, property]) => [
|
|
7383
|
+
name,
|
|
7384
|
+
{ ...property, readonly: false, type: this.keepContextualLiterals(property.type, ctx && this.contextProperty(ctx, name)) }
|
|
7385
|
+
]);
|
|
7386
|
+
const indexer = value.indexer && {
|
|
7387
|
+
key: widen(value.indexer.key),
|
|
7388
|
+
value: this.keepContextualLiterals(value.indexer.value, ctx && this.contextIndexValue(ctx))
|
|
7389
|
+
};
|
|
7390
|
+
return objectType(entries, indexer);
|
|
7391
|
+
}
|
|
7392
|
+
case "tuple": {
|
|
7393
|
+
const tupleContext = ctx && this.membersOf(ctx).find((m) => m.kind === "tuple");
|
|
7394
|
+
if (tupleContext?.kind === "tuple") {
|
|
7395
|
+
return tuple(value.elements.map((e, i) => this.keepContextualLiterals(e, tupleContext.elements[i])), value.isPack);
|
|
7396
|
+
}
|
|
7397
|
+
const arrayContext = ctx && this.membersOf(ctx).find((m) => m.kind === "array");
|
|
7398
|
+
const element = arrayContext?.kind === "array" ? arrayContext.element : void 0;
|
|
7399
|
+
if (!value.elements.length) return arrayContext ?? arrayOf(unknownType);
|
|
7400
|
+
return arrayOf(union(value.elements.map((e) => this.keepContextualLiterals(e, element))));
|
|
7401
|
+
}
|
|
7402
|
+
case "array": {
|
|
7403
|
+
const arrayContext = ctx && this.membersOf(ctx).find((m) => m.kind === "array");
|
|
7404
|
+
return arrayOf(this.keepContextualLiterals(value.element, arrayContext?.kind === "array" ? arrayContext.element : void 0));
|
|
7405
|
+
}
|
|
7406
|
+
case "union":
|
|
7407
|
+
return union(value.types.map((t) => this.keepContextualLiterals(t, context)));
|
|
7408
|
+
default:
|
|
7409
|
+
return value;
|
|
7410
|
+
}
|
|
7411
|
+
}
|
|
7412
|
+
membersOf(t) {
|
|
7413
|
+
const x = this.expand(t);
|
|
7414
|
+
return x.kind === "union" ? x.types.map((m) => this.expand(m)) : [x];
|
|
7415
|
+
}
|
|
7416
|
+
/** Does a contract accept literals of `base` as such? */
|
|
7417
|
+
admitsLiteral(ctx, base) {
|
|
7418
|
+
return this.membersOf(ctx).some((m) => m.kind === "literal" && m.base === base || m.kind === "templateLiteral" && base === "string");
|
|
7419
|
+
}
|
|
7420
|
+
/** What a contract expects of property `name`, over every object it allows. */
|
|
7421
|
+
contextProperty(ctx, name) {
|
|
7422
|
+
const found = [];
|
|
7423
|
+
for (const m of this.membersOf(ctx)) {
|
|
7424
|
+
if (m.kind !== "object") continue;
|
|
7425
|
+
const property = m.properties.get(name);
|
|
7426
|
+
if (property) found.push(property.type);
|
|
7427
|
+
else if (m.indexer) found.push(m.indexer.value);
|
|
7428
|
+
}
|
|
7429
|
+
return found.length ? union(found) : void 0;
|
|
7430
|
+
}
|
|
7431
|
+
contextIndexValue(ctx) {
|
|
7432
|
+
const found = this.membersOf(ctx).flatMap((m) => m.kind === "object" && m.indexer ? [m.indexer.value] : []);
|
|
7433
|
+
return found.length ? union(found) : void 0;
|
|
7434
|
+
}
|
|
7435
|
+
/** Fields reported by `reportExcessProperties`, once each: a loop body is
|
|
7436
|
+
* visited more than once. */
|
|
7437
|
+
excessReported = /* @__PURE__ */ new WeakSet();
|
|
7438
|
+
/** TypeScript's excess property check. An object literal written straight
|
|
7439
|
+
* into a typed place — an annotation, `satisfies` — may only name
|
|
7440
|
+
* properties that place knows: anything else is almost always a typo.
|
|
7441
|
+
* A nested literal is checked against the property it is written for.
|
|
7442
|
+
* A target with an indexer, a class, or a member whose shape is not known
|
|
7443
|
+
* accepts anything. */
|
|
7444
|
+
/** The keys an index signature covers, when it covers a countable set of
|
|
7445
|
+
* them: `[("a" | "b")]` yes, `[string]` no. */
|
|
7446
|
+
finiteKeys(key) {
|
|
7447
|
+
const t = this.expand(key);
|
|
7448
|
+
const parts = t.kind === "union" ? t.types : [t];
|
|
7449
|
+
const out = /* @__PURE__ */ new Set();
|
|
7450
|
+
for (const part of parts.map((m) => this.expand(m))) {
|
|
7451
|
+
if (part.kind !== "literal" || typeof part.value === "boolean") return void 0;
|
|
7452
|
+
out.add(String(part.value));
|
|
7453
|
+
}
|
|
7454
|
+
return out.size ? out : void 0;
|
|
7455
|
+
}
|
|
7456
|
+
reportExcessProperties(expression, target) {
|
|
7457
|
+
let literal2 = unwrapParens(expression);
|
|
7458
|
+
while (literal2.type === "AsConstExpression") literal2 = unwrapParens(literal2.expression);
|
|
7459
|
+
if (literal2.type !== "TableExpression" || !this.emitDiagnostics) return;
|
|
7460
|
+
const members = this.membersOf(target);
|
|
7461
|
+
const shapes = members.filter((m) => m.kind === "object");
|
|
7462
|
+
if (!shapes.length || shapes.some((o) => o.class)) return;
|
|
7463
|
+
const keySets = shapes.map((o) => o.indexer && this.finiteKeys(o.indexer.key));
|
|
7464
|
+
if (shapes.some((o, i) => o.indexer && !keySets[i])) return;
|
|
7465
|
+
if (members.some((m) => m.kind === "any" || m.kind === "unknown" || m.kind === "typeParam" || m.kind === "intersection")) return;
|
|
7466
|
+
for (const field of literal2.fields) {
|
|
7467
|
+
if (field.type !== "TableFieldNamed" && field.type !== "TableFieldShorthand") continue;
|
|
7468
|
+
const key = field.type === "TableFieldNamed" ? field.key : field.name;
|
|
7469
|
+
const name = key.type === "Identifier" ? key.name : key.value;
|
|
7470
|
+
const expected = shapes.flatMap((o, i) => {
|
|
7471
|
+
const property = o.properties.get(name);
|
|
7472
|
+
if (property) return [property.type];
|
|
7473
|
+
return o.indexer && keySets[i].has(name) ? [o.indexer.value] : [];
|
|
7474
|
+
});
|
|
7475
|
+
if (!expected.length) {
|
|
7476
|
+
if (this.excessReported.has(key)) continue;
|
|
7477
|
+
this.excessReported.add(key);
|
|
7478
|
+
this.diagnostics.push({
|
|
7479
|
+
node: key,
|
|
7480
|
+
message: `Object literal may only specify known properties, and '${name}' does not exist in type '${formatType(target)}'`
|
|
7481
|
+
});
|
|
7482
|
+
continue;
|
|
7483
|
+
}
|
|
7484
|
+
if (field.type === "TableFieldNamed") this.reportExcessProperties(field.value, union(expected));
|
|
7485
|
+
}
|
|
7486
|
+
}
|
|
5863
7487
|
inferAsConst(expr, env) {
|
|
5864
7488
|
switch (expr.type) {
|
|
5865
7489
|
case "ArrayExpression":
|
|
@@ -5917,12 +7541,13 @@ var TypeAnalyzer = class {
|
|
|
5917
7541
|
}
|
|
5918
7542
|
if (cond.type === "CallExpression" || cond.type === "MethodCallExpression") {
|
|
5919
7543
|
this.narrowByPredicateCall(cond, env, t, f);
|
|
5920
|
-
|
|
7544
|
+
} else {
|
|
7545
|
+
this.narrowRef(cond, env, t, f, (cur) => ({
|
|
7546
|
+
yes: narrowTruthy(cur),
|
|
7547
|
+
no: narrowFalsy(cur)
|
|
7548
|
+
}));
|
|
5921
7549
|
}
|
|
5922
|
-
this.
|
|
5923
|
-
yes: narrowTruthy(cur),
|
|
5924
|
-
no: narrowFalsy(cur)
|
|
5925
|
-
}));
|
|
7550
|
+
this.narrowOptionalLinks(cond, env, t);
|
|
5926
7551
|
}
|
|
5927
7552
|
/** `a == b` / `a ~= b`. Handles, in order: a declaration-driven
|
|
5928
7553
|
* `typeof(x) == "..."` test, a literal/`nil` comparison against a
|
|
@@ -5939,11 +7564,14 @@ var TypeAnalyzer = class {
|
|
|
5939
7564
|
};
|
|
5940
7565
|
for (const [ref, other] of [[left, right], [right, left]]) {
|
|
5941
7566
|
const value = litOf(other);
|
|
5942
|
-
if (value === void 0
|
|
5943
|
-
this.
|
|
5944
|
-
yes
|
|
5945
|
-
|
|
5946
|
-
|
|
7567
|
+
if (value === void 0) continue;
|
|
7568
|
+
if (this.refKeyOf(ref) !== void 0) {
|
|
7569
|
+
this.narrowRef(ref, env, yes, no, (cur) => ({
|
|
7570
|
+
yes: narrowTo(cur, value),
|
|
7571
|
+
no: narrowExclude(cur, value)
|
|
7572
|
+
}));
|
|
7573
|
+
}
|
|
7574
|
+
this.narrowOptionalLinks(ref, env, value.kind === "primitive" && value.name === "nil" ? no : yes);
|
|
5947
7575
|
return;
|
|
5948
7576
|
}
|
|
5949
7577
|
if (this.refKeyOf(left) !== void 0 && this.refKeyOf(right) !== void 0) {
|
|
@@ -5998,11 +7626,16 @@ var TypeAnalyzer = class {
|
|
|
5998
7626
|
predicateCallTarget(cond, env) {
|
|
5999
7627
|
let callee;
|
|
6000
7628
|
let args;
|
|
7629
|
+
let selfType;
|
|
6001
7630
|
if (cond.type === "CallExpression") {
|
|
6002
|
-
callee = this.typeOf.get(cond.callee) ?? this.typeAtRef(cond.callee, env);
|
|
7631
|
+
callee = this.chainValue.get(cond.callee) ?? this.typeOf.get(cond.callee) ?? this.typeAtRef(cond.callee, env);
|
|
6003
7632
|
args = cond.arguments;
|
|
6004
7633
|
} else if (cond.type === "MethodCallExpression") {
|
|
6005
|
-
|
|
7634
|
+
let objType = this.chainValue.get(cond.object) ?? this.typeOf.get(cond.object) ?? this.typeAtRef(cond.object, env);
|
|
7635
|
+
if (cond.optional) {
|
|
7636
|
+
objType = withoutNil(objType);
|
|
7637
|
+
selfType = objType;
|
|
7638
|
+
}
|
|
6006
7639
|
callee = this.propertyType(objType, cond.method.name);
|
|
6007
7640
|
const first = this.overloadsOf(callee)[0];
|
|
6008
7641
|
args = first && this.takesSelf(first) ? [cond.object, ...cond.arguments] : cond.arguments;
|
|
@@ -6010,7 +7643,7 @@ var TypeAnalyzer = class {
|
|
|
6010
7643
|
return void 0;
|
|
6011
7644
|
}
|
|
6012
7645
|
const overloads = this.overloadsOf(callee);
|
|
6013
|
-
const argTypes = args.map((a) => this.typeOf.get(a) ?? this.typeAtRef(a, env));
|
|
7646
|
+
const argTypes = args.map((a) => (selfType && cond.type === "MethodCallExpression" && a === cond.object ? selfType : void 0) ?? this.typeOf.get(a) ?? this.typeAtRef(a, env));
|
|
6014
7647
|
const picked = this.pickOverload(overloads, argTypes);
|
|
6015
7648
|
const candidates = picked ? [picked, ...overloads.filter((f) => f !== picked)] : overloads;
|
|
6016
7649
|
for (const f of candidates) {
|
|
@@ -6117,6 +7750,10 @@ var TypeAnalyzer = class {
|
|
|
6117
7750
|
const { yes, no } = refine(cur);
|
|
6118
7751
|
this.setRef(t, key, yes);
|
|
6119
7752
|
this.setRef(f, key, no);
|
|
7753
|
+
this.correlate(t, key, yes);
|
|
7754
|
+
this.correlate(f, key, no);
|
|
7755
|
+
this.propagateAliases(env, t, key, yes);
|
|
7756
|
+
this.propagateAliases(env, f, key, no);
|
|
6120
7757
|
const inner = expr.type === "ParenthesizedExpression" ? expr.expression : expr;
|
|
6121
7758
|
if (inner.type !== "MemberExpression" && inner.type !== "IndexExpression") return;
|
|
6122
7759
|
const parentKey = this.refKeyOf(inner.object);
|
|
@@ -6124,18 +7761,19 @@ var TypeAnalyzer = class {
|
|
|
6124
7761
|
const step = key.slice(parentKey.length);
|
|
6125
7762
|
if (!step.startsWith(".")) return;
|
|
6126
7763
|
const prop = step.slice(1);
|
|
7764
|
+
const optional2 = inner.type === "MemberExpression" && inner.optional === true;
|
|
6127
7765
|
this.narrowRef(inner.object, env, t, f, (parentType) => ({
|
|
6128
|
-
yes: this.filterByProperty(parentType, prop, yes),
|
|
6129
|
-
no: this.filterByProperty(parentType, prop, no)
|
|
7766
|
+
yes: this.filterByProperty(parentType, prop, yes, optional2),
|
|
7767
|
+
no: this.filterByProperty(parentType, prop, no, optional2)
|
|
6130
7768
|
}));
|
|
6131
7769
|
}
|
|
6132
7770
|
/** Keep the union members of `parent` whose `prop` can still hold `want`.
|
|
6133
7771
|
* Leaves a non-union (or a union nothing matches) alone: over-narrowing a
|
|
6134
7772
|
* plain object to `never` because of a property test would be worse than
|
|
6135
7773
|
* learning nothing. */
|
|
6136
|
-
filterByProperty(parent, prop, want) {
|
|
7774
|
+
filterByProperty(parent, prop, want, optional2 = false) {
|
|
6137
7775
|
if (parent.kind !== "union" || want.kind === "never") return parent;
|
|
6138
|
-
const kept = parent.types.filter((m) => overlaps(this.propertyType(m, prop), want));
|
|
7776
|
+
const kept = parent.types.filter((m) => m.kind === "primitive" && m.name === "nil" ? optional2 && overlaps(nilType, want) : overlaps(this.propertyType(m, prop), want));
|
|
6139
7777
|
return kept.length ? union(kept) : parent;
|
|
6140
7778
|
}
|
|
6141
7779
|
/** Record a narrowing. Deliberately does *not* discard what is known about
|
|
@@ -6147,6 +7785,15 @@ var TypeAnalyzer = class {
|
|
|
6147
7785
|
setRef(env, key, t) {
|
|
6148
7786
|
env.set(key, t);
|
|
6149
7787
|
}
|
|
7788
|
+
/** An assignment to a path (or to anything it hangs off) means the name
|
|
7789
|
+
* that copied it no longer holds that value: forget the alias. */
|
|
7790
|
+
unalias(key) {
|
|
7791
|
+
for (const k of [...this.refAliases.keys()]) {
|
|
7792
|
+
if (k !== key && !k.startsWith(`${key}.`) && !k.startsWith(`${key}#`)) continue;
|
|
7793
|
+
for (const other of this.refAliases.get(k) ?? []) this.refAliases.get(other)?.delete(k);
|
|
7794
|
+
this.refAliases.delete(k);
|
|
7795
|
+
}
|
|
7796
|
+
}
|
|
6150
7797
|
/** Drop every narrowing recorded for a path strictly under `key`. */
|
|
6151
7798
|
invalidateBelow(env, key) {
|
|
6152
7799
|
for (const k of [...env.keys()]) {
|
|
@@ -6159,6 +7806,7 @@ var TypeAnalyzer = class {
|
|
|
6159
7806
|
const key = this.refKeyOf(expr);
|
|
6160
7807
|
if (key === void 0) return;
|
|
6161
7808
|
this.invalidateBelow(env, key);
|
|
7809
|
+
this.unalias(key);
|
|
6162
7810
|
env.set(key, value);
|
|
6163
7811
|
}
|
|
6164
7812
|
// --------------------------------------------------------
|
|
@@ -6239,7 +7887,85 @@ var TypeAnalyzer = class {
|
|
|
6239
7887
|
/** The type a binding has *here*: its flow-narrowed type if the current
|
|
6240
7888
|
* environment has one, else its declared/inferred type. */
|
|
6241
7889
|
currentType(id, env) {
|
|
6242
|
-
return env.get(bindKey(id)) ?? this.bindingType.get(id) ?? anyType;
|
|
7890
|
+
return env.get(bindKey(id)) ?? this.bindingType.get(id) ?? this.declaredAhead(id) ?? anyType;
|
|
7891
|
+
}
|
|
7892
|
+
// --------------------------------------------------------
|
|
7893
|
+
// Hoisting
|
|
7894
|
+
// --------------------------------------------------------
|
|
7895
|
+
//
|
|
7896
|
+
// Scope analysis lets code see a function declared later in its block,
|
|
7897
|
+
// and a module's top-level names from function bodies and `typeof` written
|
|
7898
|
+
// above them. The walk has not reached those declarations yet when such a
|
|
7899
|
+
// reference is met, so their type is worked out from the declaration on
|
|
7900
|
+
// the spot — its annotation, or its body or initializer — as TypeScript
|
|
7901
|
+
// does. The walk reaching the declaration later types it for real.
|
|
7902
|
+
/** Declarations a reference may meet before the walk does. */
|
|
7903
|
+
aheadDeclarations;
|
|
7904
|
+
computingAhead = /* @__PURE__ */ new Set();
|
|
7905
|
+
declaredAhead(id) {
|
|
7906
|
+
this.aheadDeclarations ??= this.indexAheadDeclarations();
|
|
7907
|
+
const found = this.aheadDeclarations.get(id);
|
|
7908
|
+
if (!found || this.computingAhead.has(id)) return void 0;
|
|
7909
|
+
this.computingAhead.add(id);
|
|
7910
|
+
const wasEmitting = this.emitDiagnostics;
|
|
7911
|
+
this.emitDiagnostics = false;
|
|
7912
|
+
try {
|
|
7913
|
+
const { statement, index } = found;
|
|
7914
|
+
let type;
|
|
7915
|
+
if (statement.type === "DeclareStatement") {
|
|
7916
|
+
type = this.resolveType(statement.valueType);
|
|
7917
|
+
} else if (statement.type === "FunctionDeclaration") {
|
|
7918
|
+
type = statement.signatures?.length ? intersection(statement.signatures.map((sig) => this.signatureToFnType(sig))) : this.inferFunctionBody(statement.func, /* @__PURE__ */ new Map());
|
|
7919
|
+
} else if (statement.type === "VariableDeclaration") {
|
|
7920
|
+
const target = statement.names[index];
|
|
7921
|
+
if (target.type === "IdentifierPattern" && target.typeAnnotation) {
|
|
7922
|
+
type = this.resolveType(target.typeAnnotation);
|
|
7923
|
+
} else if (statement.init[index]) {
|
|
7924
|
+
const value = this.infer(statement.init[index], /* @__PURE__ */ new Map());
|
|
7925
|
+
type = statement.kind === "const" ? value : widen(value);
|
|
7926
|
+
}
|
|
7927
|
+
}
|
|
7928
|
+
if (type) this.bindingType.set(id, type);
|
|
7929
|
+
return type;
|
|
7930
|
+
} finally {
|
|
7931
|
+
this.emitDiagnostics = wasEmitting;
|
|
7932
|
+
this.computingAhead.delete(id);
|
|
7933
|
+
}
|
|
7934
|
+
}
|
|
7935
|
+
/** Every function declaration, and every plain name the module declares
|
|
7936
|
+
* at its top level. */
|
|
7937
|
+
indexAheadDeclarations() {
|
|
7938
|
+
const out = /* @__PURE__ */ new Map();
|
|
7939
|
+
for (const statement of this.program.body.statements) {
|
|
7940
|
+
const declaration = statement.type === "ExportStatement" ? statement.declaration : statement;
|
|
7941
|
+
if (declaration.type !== "VariableDeclaration") continue;
|
|
7942
|
+
declaration.names.forEach((target, index) => {
|
|
7943
|
+
if (target.type !== "IdentifierPattern") return;
|
|
7944
|
+
const id = this.bindingIdByName(target.name, target);
|
|
7945
|
+
if (id !== void 0) out.set(id, { statement: declaration, index });
|
|
7946
|
+
});
|
|
7947
|
+
}
|
|
7948
|
+
const visit = (node) => {
|
|
7949
|
+
if (!node || typeof node !== "object") return;
|
|
7950
|
+
if (Array.isArray(node)) {
|
|
7951
|
+
for (const item of node) visit(item);
|
|
7952
|
+
return;
|
|
7953
|
+
}
|
|
7954
|
+
const record = node;
|
|
7955
|
+
if (record.type === "FunctionDeclaration" && record.name) {
|
|
7956
|
+
const id = this.bindingIdByName(record.name.name, record.name);
|
|
7957
|
+
if (id !== void 0) out.set(id, { statement: node, index: 0 });
|
|
7958
|
+
}
|
|
7959
|
+
for (const [key, value] of Object.entries(node)) {
|
|
7960
|
+
if (key !== "line" && key !== "column" && value && typeof value === "object") visit(value);
|
|
7961
|
+
}
|
|
7962
|
+
};
|
|
7963
|
+
visit(this.program.body);
|
|
7964
|
+
for (const [name, statement] of this.deferredDeclares) {
|
|
7965
|
+
const id = this.scopes.globalsByName.get(name);
|
|
7966
|
+
if (id !== void 0) out.set(id, { statement, index: 0 });
|
|
7967
|
+
}
|
|
7968
|
+
return out;
|
|
6243
7969
|
}
|
|
6244
7970
|
/** Bind or rebind a whole variable: any narrowing recorded for a path
|
|
6245
7971
|
* *under* it (`x.a`, `x[1]`) described the old value and must go. */
|
|
@@ -6266,12 +7992,28 @@ var TypeAnalyzer = class {
|
|
|
6266
7992
|
return this.bindingByDecl.get(node) ?? this.bindingByPos.get(posKey(name, node.line.start, node.column.start));
|
|
6267
7993
|
}
|
|
6268
7994
|
};
|
|
7995
|
+
function referencedTypeNames(node, out = []) {
|
|
7996
|
+
if (!node || typeof node !== "object") return out;
|
|
7997
|
+
if (Array.isArray(node)) {
|
|
7998
|
+
for (const item of node) referencedTypeNames(item, out);
|
|
7999
|
+
return out;
|
|
8000
|
+
}
|
|
8001
|
+
const record = node;
|
|
8002
|
+
if (record.type === "TypeReference" && typeof record.base === "string") {
|
|
8003
|
+
out.push(typeof record.namespace === "string" ? `${record.namespace}.${record.base}` : record.base);
|
|
8004
|
+
}
|
|
8005
|
+
for (const [key, value] of Object.entries(node)) {
|
|
8006
|
+
if (key !== "line" && key !== "column" && value && typeof value === "object") referencedTypeNames(value, out);
|
|
8007
|
+
}
|
|
8008
|
+
return out;
|
|
8009
|
+
}
|
|
6269
8010
|
function containsTypeQuery(node) {
|
|
6270
8011
|
if (!node || typeof node !== "object") return false;
|
|
6271
8012
|
if (Array.isArray(node)) return node.some(containsTypeQuery);
|
|
6272
8013
|
if (node.type === "TypeofTypeNode") return true;
|
|
6273
8014
|
return Object.values(node).some(containsTypeQuery);
|
|
6274
8015
|
}
|
|
8016
|
+
var STRING_INTRINSICS = /* @__PURE__ */ new Set(["Uppercase", "Lowercase", "Capitalize", "Uncapitalize"]);
|
|
6275
8017
|
function briefType(t) {
|
|
6276
8018
|
if (t.kind === "union" && t.types.length > 8) {
|
|
6277
8019
|
const shown = t.types.slice(0, 6).map(formatType).join(" | ");
|
|
@@ -6468,13 +8210,13 @@ function resolveTypeLibraries(config, host = nodeHost) {
|
|
|
6468
8210
|
else problems.push({ file: config.path, message: `'${entry}' has no ${ENTRY_FILE} (or 'luaut.types' in its package.json)`, ...entryPosition(config, entry) });
|
|
6469
8211
|
continue;
|
|
6470
8212
|
}
|
|
6471
|
-
const
|
|
6472
|
-
const found =
|
|
8213
|
+
const name = entry.startsWith("@luaut/") ? entry : `@luaut/${entry}`;
|
|
8214
|
+
const found = findPackage(name, config.directory, host);
|
|
6473
8215
|
if (found) addPackage(found.directory, found.file, /* @__PURE__ */ new Set());
|
|
6474
8216
|
else {
|
|
6475
8217
|
problems.push({
|
|
6476
8218
|
file: config.path,
|
|
6477
|
-
message: `Cannot find type library '${
|
|
8219
|
+
message: `Cannot find type library '${name}'. Install it with: npm i -D ${name}`,
|
|
6478
8220
|
...entryPosition(config, entry)
|
|
6479
8221
|
});
|
|
6480
8222
|
}
|
|
@@ -6660,17 +8402,21 @@ var index_default = luautparser;
|
|
|
6660
8402
|
Keywords,
|
|
6661
8403
|
LexError,
|
|
6662
8404
|
Operators,
|
|
8405
|
+
PRELUDE_SOURCE,
|
|
6663
8406
|
ParseError,
|
|
6664
8407
|
Punctuators,
|
|
8408
|
+
UNUSED_EXPECT_ERROR,
|
|
6665
8409
|
UnaryOperators,
|
|
6666
8410
|
analyzeScopes,
|
|
6667
8411
|
analyzeTypes,
|
|
6668
8412
|
anyType,
|
|
8413
|
+
applyDirectives,
|
|
6669
8414
|
arrayOf,
|
|
6670
8415
|
booleanType,
|
|
6671
8416
|
bufferType,
|
|
6672
8417
|
containsTypeParam,
|
|
6673
8418
|
difference,
|
|
8419
|
+
directivesOf,
|
|
6674
8420
|
equalTypes,
|
|
6675
8421
|
falsyType,
|
|
6676
8422
|
findConfig,
|
|
@@ -6706,6 +8452,7 @@ var index_default = luautparser;
|
|
|
6706
8452
|
parseTokens,
|
|
6707
8453
|
parseWithRecovery,
|
|
6708
8454
|
primitive,
|
|
8455
|
+
readDirectives,
|
|
6709
8456
|
resolveModulePath,
|
|
6710
8457
|
resolveTypeLibraries,
|
|
6711
8458
|
setAliasExpander,
|