luaut-parser 3.0.0 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +173 -11
- package/dist/index.cjs +2007 -293
- package/dist/index.d.cts +234 -12
- package/dist/index.d.ts +234 -12
- package/dist/index.js +2002 -293
- 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;
|
|
801
1017
|
}
|
|
802
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;
|
|
1056
|
+
}
|
|
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();
|
|
@@ -931,6 +1201,10 @@ var Parser = class {
|
|
|
931
1201
|
}
|
|
932
1202
|
}
|
|
933
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
|
+
}
|
|
934
1208
|
if (t.type === "Identifier" && t.value === "type" && this.peek(1).type === "Identifier") {
|
|
935
1209
|
return this.parseTypeAliasStatement();
|
|
936
1210
|
}
|
|
@@ -1119,7 +1393,7 @@ var Parser = class {
|
|
|
1119
1393
|
return { type: "ExportStatement", declaration, ...spanFrom(start, this.previous()) };
|
|
1120
1394
|
}
|
|
1121
1395
|
if (this.checkKeyword("function")) {
|
|
1122
|
-
const declaration = this.parseFunctionStatement();
|
|
1396
|
+
const declaration = this.parseFunctionStatement(true);
|
|
1123
1397
|
if (declaration.type !== "FunctionDeclaration") this.error("An exported function needs a plain name: 'export function name()'");
|
|
1124
1398
|
return { type: "ExportStatement", declaration, ...spanFrom(start, this.previous()) };
|
|
1125
1399
|
}
|
|
@@ -1150,9 +1424,11 @@ var Parser = class {
|
|
|
1150
1424
|
}
|
|
1151
1425
|
// `const x = ...` / `let x, y = ...`.
|
|
1152
1426
|
// luaut has no `local` — `const` bindings are immutable, `let` mutable.
|
|
1153
|
-
|
|
1427
|
+
/** `kind` reads the leading word as that keyword (recovery's `local`). */
|
|
1428
|
+
parseVariableDeclaration(as) {
|
|
1154
1429
|
const start = this.current();
|
|
1155
|
-
const
|
|
1430
|
+
const word = this.advance().value;
|
|
1431
|
+
const kind = as ?? word;
|
|
1156
1432
|
if (this.checkKeyword("function")) {
|
|
1157
1433
|
this.error(`A function is declared as 'function name()'; '${kind}' does not apply to functions`);
|
|
1158
1434
|
}
|
|
@@ -1162,9 +1438,10 @@ var Parser = class {
|
|
|
1162
1438
|
}
|
|
1163
1439
|
let init = [];
|
|
1164
1440
|
if (this.matchOperator("=")) {
|
|
1165
|
-
init = this.
|
|
1441
|
+
init = this.expressionListOr(() => false);
|
|
1166
1442
|
} else if (kind === "const") {
|
|
1167
|
-
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");
|
|
1168
1445
|
}
|
|
1169
1446
|
return { type: "VariableDeclaration", kind, names, init, ...spanFrom(start, this.previous()) };
|
|
1170
1447
|
}
|
|
@@ -1172,64 +1449,73 @@ var Parser = class {
|
|
|
1172
1449
|
const start = this.current();
|
|
1173
1450
|
this.expectKeyword("if");
|
|
1174
1451
|
const clauses = [];
|
|
1175
|
-
const
|
|
1176
|
-
this.
|
|
1177
|
-
|
|
1452
|
+
const untilThen = () => this.checkKeyword("then");
|
|
1453
|
+
const cond = this.expressionOr(untilThen);
|
|
1454
|
+
this.expectKeywordSoft("then");
|
|
1455
|
+
const body = this.parseBlock(start);
|
|
1178
1456
|
clauses.push({ type: "IfClause", condition: cond, body, ...spanFrom(cond, this.previous()) });
|
|
1179
1457
|
while (this.checkKeyword("elseif")) {
|
|
1180
1458
|
const clauseStart = this.current();
|
|
1181
1459
|
this.advance();
|
|
1182
|
-
const c = this.
|
|
1183
|
-
this.
|
|
1184
|
-
const b = this.parseBlock();
|
|
1460
|
+
const c = this.expressionOr(untilThen);
|
|
1461
|
+
this.expectKeywordSoft("then");
|
|
1462
|
+
const b = this.parseBlock(start);
|
|
1185
1463
|
clauses.push({ type: "IfClause", condition: c, body: b, ...spanFrom(clauseStart, this.previous()) });
|
|
1186
1464
|
}
|
|
1187
1465
|
let alternate;
|
|
1188
1466
|
if (this.matchKeyword("else")) {
|
|
1189
|
-
alternate = this.parseBlock();
|
|
1467
|
+
alternate = this.parseBlock(start);
|
|
1190
1468
|
}
|
|
1191
|
-
this.
|
|
1469
|
+
this.expectEnd(start);
|
|
1192
1470
|
return { type: "IfStatement", clauses, alternate, ...spanFrom(start, this.previous()) };
|
|
1193
1471
|
}
|
|
1194
1472
|
parseWhileStatement() {
|
|
1195
1473
|
const start = this.current();
|
|
1196
1474
|
this.expectKeyword("while");
|
|
1197
|
-
const condition = this.
|
|
1198
|
-
this.
|
|
1199
|
-
const body = this.parseBlock();
|
|
1200
|
-
this.
|
|
1475
|
+
const condition = this.expressionOr(() => this.checkKeyword("do"));
|
|
1476
|
+
this.expectKeywordSoft("do");
|
|
1477
|
+
const body = this.parseBlock(start);
|
|
1478
|
+
this.expectEnd(start);
|
|
1201
1479
|
return { type: "WhileStatement", condition, body, ...spanFrom(start, this.previous()) };
|
|
1202
1480
|
}
|
|
1203
1481
|
parseRepeatStatement() {
|
|
1204
1482
|
const start = this.current();
|
|
1205
1483
|
this.expectKeyword("repeat");
|
|
1206
|
-
const body = this.parseBlock();
|
|
1207
|
-
|
|
1208
|
-
|
|
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
|
+
}
|
|
1209
1494
|
return { type: "RepeatStatement", body, condition, ...spanFrom(start, this.previous()) };
|
|
1210
1495
|
}
|
|
1211
1496
|
parseDoStatement() {
|
|
1212
1497
|
const start = this.current();
|
|
1213
1498
|
this.expectKeyword("do");
|
|
1214
|
-
const body = this.parseBlock();
|
|
1215
|
-
this.
|
|
1499
|
+
const body = this.parseBlock(start);
|
|
1500
|
+
this.expectEnd(start);
|
|
1216
1501
|
return { type: "DoStatement", body, ...spanFrom(start, this.previous()) };
|
|
1217
1502
|
}
|
|
1218
1503
|
parseForStatement() {
|
|
1219
1504
|
const start = this.current();
|
|
1220
1505
|
this.expectKeyword("for");
|
|
1221
1506
|
const first = this.parseBindingTarget(true);
|
|
1507
|
+
const untilDo = () => this.checkKeyword("do");
|
|
1222
1508
|
if (first.type === "IdentifierPattern" && this.matchOperator("=")) {
|
|
1223
|
-
const from = this.
|
|
1509
|
+
const from = this.expressionOr(() => untilDo() || this.checkPunctuator(","));
|
|
1224
1510
|
this.expectPunctuator(",");
|
|
1225
|
-
const to = this.
|
|
1511
|
+
const to = this.expressionOr(() => untilDo() || this.checkPunctuator(","));
|
|
1226
1512
|
let step;
|
|
1227
1513
|
if (this.matchPunctuator(",")) {
|
|
1228
|
-
step = this.
|
|
1514
|
+
step = this.expressionOr(untilDo);
|
|
1229
1515
|
}
|
|
1230
|
-
this.
|
|
1231
|
-
const body2 = this.parseBlock();
|
|
1232
|
-
this.
|
|
1516
|
+
this.expectKeywordSoft("do");
|
|
1517
|
+
const body2 = this.parseBlock(start);
|
|
1518
|
+
this.expectEnd(start);
|
|
1233
1519
|
return {
|
|
1234
1520
|
type: "NumericForStatement",
|
|
1235
1521
|
variable: this.identifierPatternToTypedIdentifier(first),
|
|
@@ -1245,10 +1531,10 @@ var Parser = class {
|
|
|
1245
1531
|
variables.push(this.parseBindingTarget(true));
|
|
1246
1532
|
}
|
|
1247
1533
|
this.expectKeyword("in");
|
|
1248
|
-
const iterators = this.
|
|
1249
|
-
this.
|
|
1250
|
-
const body = this.parseBlock();
|
|
1251
|
-
this.
|
|
1534
|
+
const iterators = this.expressionListOr(untilDo);
|
|
1535
|
+
this.expectKeywordSoft("do");
|
|
1536
|
+
const body = this.parseBlock(start);
|
|
1537
|
+
this.expectEnd(start);
|
|
1252
1538
|
return {
|
|
1253
1539
|
type: "GenericForStatement",
|
|
1254
1540
|
variables,
|
|
@@ -1259,28 +1545,36 @@ var Parser = class {
|
|
|
1259
1545
|
}
|
|
1260
1546
|
/** `function name() end` declares `name`; `function a.b() end` and
|
|
1261
1547
|
* `function T:m() end` define a member. */
|
|
1262
|
-
|
|
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) {
|
|
1263
1551
|
const start = this.current();
|
|
1264
1552
|
this.expectKeyword("function");
|
|
1265
1553
|
const target = this.parseFunctionName();
|
|
1266
1554
|
const isMethod = target.method !== void 0;
|
|
1267
1555
|
const simpleName = !isMethod && target.path.length === 0 ? target.base.name : void 0;
|
|
1268
1556
|
const signatures = [];
|
|
1557
|
+
let written = target.base;
|
|
1269
1558
|
while (true) {
|
|
1270
1559
|
const head = this.parseFunctionHead();
|
|
1271
1560
|
if (simpleName !== void 0 && this.isOverloadContinuation(simpleName)) {
|
|
1272
|
-
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
|
+
}
|
|
1273
1566
|
this.expectKeyword("function");
|
|
1274
|
-
this.parseFunctionName();
|
|
1567
|
+
written = this.parseFunctionName().base;
|
|
1275
1568
|
continue;
|
|
1276
1569
|
}
|
|
1277
|
-
const func = this.headToBody(head);
|
|
1570
|
+
const func = this.headToBody(head, start);
|
|
1278
1571
|
if (simpleName !== void 0) {
|
|
1279
1572
|
return {
|
|
1280
1573
|
type: "FunctionDeclaration",
|
|
1281
1574
|
name: target.base,
|
|
1282
1575
|
func,
|
|
1283
1576
|
signatures: signatures.length ? signatures : void 0,
|
|
1577
|
+
implementationName: signatures.length ? written : void 0,
|
|
1284
1578
|
...spanFrom(start, this.previous())
|
|
1285
1579
|
};
|
|
1286
1580
|
}
|
|
@@ -1302,7 +1596,18 @@ var Parser = class {
|
|
|
1302
1596
|
* declaration for the same simple `name` (making the head an overload
|
|
1303
1597
|
* signature rather than an implementation)? */
|
|
1304
1598
|
isOverloadContinuation(name) {
|
|
1305
|
-
|
|
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);
|
|
1306
1611
|
}
|
|
1307
1612
|
parseFunctionName() {
|
|
1308
1613
|
const start = this.current();
|
|
@@ -1338,7 +1643,7 @@ var Parser = class {
|
|
|
1338
1643
|
this.expectKeyword("return");
|
|
1339
1644
|
let args = [];
|
|
1340
1645
|
if (this.isExpressionStart()) {
|
|
1341
|
-
args = this.
|
|
1646
|
+
args = this.expressionListOr(() => false);
|
|
1342
1647
|
}
|
|
1343
1648
|
return { type: "ReturnStatement", arguments: args, ...spanFrom(start, this.previous()) };
|
|
1344
1649
|
}
|
|
@@ -1370,7 +1675,7 @@ var Parser = class {
|
|
|
1370
1675
|
targets.push(this.parseAssignTarget());
|
|
1371
1676
|
}
|
|
1372
1677
|
this.expectOperator("=");
|
|
1373
|
-
const values = this.
|
|
1678
|
+
const values = this.expressionListOr(() => false);
|
|
1374
1679
|
return { type: "AssignmentStatement", targets, values, ...spanFrom(start, this.previous()) };
|
|
1375
1680
|
}
|
|
1376
1681
|
const first = this.parsePrefixExpression();
|
|
@@ -1379,14 +1684,16 @@ var Parser = class {
|
|
|
1379
1684
|
while (this.matchPunctuator(",")) {
|
|
1380
1685
|
targets.push(this.parseAssignTarget());
|
|
1381
1686
|
}
|
|
1687
|
+
for (const target of targets) this.rejectOptionalTarget(target);
|
|
1382
1688
|
this.expectOperator("=");
|
|
1383
|
-
const values = this.
|
|
1689
|
+
const values = this.expressionListOr(() => false);
|
|
1384
1690
|
return { type: "AssignmentStatement", targets, values, ...spanFrom(start, this.previous()) };
|
|
1385
1691
|
}
|
|
1386
1692
|
const t = this.current();
|
|
1387
1693
|
if (t.type === "Operator" && COMPOUND_ASSIGN_OPS.has(t.value)) {
|
|
1694
|
+
this.rejectOptionalTarget(first);
|
|
1388
1695
|
const op = this.advance().value;
|
|
1389
|
-
const value = this.
|
|
1696
|
+
const value = this.expressionOr(() => false);
|
|
1390
1697
|
return {
|
|
1391
1698
|
type: "CompoundAssignmentStatement",
|
|
1392
1699
|
operator: op,
|
|
@@ -1424,15 +1731,43 @@ var Parser = class {
|
|
|
1424
1731
|
* rather than the `:` of a ternary (`cond ? obj : other`)? Lua requires a
|
|
1425
1732
|
* method call to be called, so the answer is exact rather than heuristic:
|
|
1426
1733
|
* `:` Identifier followed by one of Lua's call forms. */
|
|
1427
|
-
startsMethodCall() {
|
|
1428
|
-
if (this.peek(1).type !== "Identifier") return false;
|
|
1429
|
-
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);
|
|
1430
1737
|
if (after.type === "Punctuator") {
|
|
1431
1738
|
const v = String(after.value);
|
|
1432
1739
|
return v === "(" || v === "{";
|
|
1433
1740
|
}
|
|
1434
1741
|
if (after.type === "InterpolatedString") return true;
|
|
1435
|
-
|
|
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
|
+
}
|
|
1436
1771
|
}
|
|
1437
1772
|
isUnaryOperator() {
|
|
1438
1773
|
const t = this.current();
|
|
@@ -1549,7 +1884,7 @@ var Parser = class {
|
|
|
1549
1884
|
}
|
|
1550
1885
|
if (t.type === "Keyword" && t.value === "function") {
|
|
1551
1886
|
this.advance();
|
|
1552
|
-
const func = this.parseFunctionBody();
|
|
1887
|
+
const func = this.parseFunctionBody(t);
|
|
1553
1888
|
return { type: "FunctionExpression", func, ...spanFrom(t, this.previous()) };
|
|
1554
1889
|
}
|
|
1555
1890
|
if (t.type === "Keyword" && t.value === "if") {
|
|
@@ -1572,7 +1907,19 @@ var Parser = class {
|
|
|
1572
1907
|
if (p.kind === "string") {
|
|
1573
1908
|
parts.push({ kind: "string", value: p.value, raw: p.raw });
|
|
1574
1909
|
} else {
|
|
1575
|
-
|
|
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
|
+
}
|
|
1576
1923
|
parts.push({ kind: "expression", expression });
|
|
1577
1924
|
}
|
|
1578
1925
|
}
|
|
@@ -1610,7 +1957,53 @@ var Parser = class {
|
|
|
1610
1957
|
this.error("Expected identifier or '('");
|
|
1611
1958
|
}
|
|
1612
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
|
+
}
|
|
1613
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
|
+
}
|
|
1614
2007
|
const prop = this.parseIdentifier();
|
|
1615
2008
|
base = { type: "MemberExpression", object: base, property: prop, ...spanFrom(base, prop) };
|
|
1616
2009
|
continue;
|
|
@@ -1624,17 +2017,33 @@ var Parser = class {
|
|
|
1624
2017
|
if (this.checkPunctuator(":") && this.startsMethodCall()) {
|
|
1625
2018
|
this.advance();
|
|
1626
2019
|
const method = this.parseIdentifier();
|
|
2020
|
+
const typeArguments = this.tryCallTypeArguments();
|
|
1627
2021
|
const args = this.parseCallArguments();
|
|
1628
2022
|
base = {
|
|
1629
2023
|
type: "MethodCallExpression",
|
|
1630
2024
|
object: base,
|
|
1631
2025
|
method,
|
|
1632
2026
|
arguments: args,
|
|
2027
|
+
typeArguments,
|
|
1633
2028
|
...spanFrom(base, this.previous())
|
|
1634
2029
|
};
|
|
1635
2030
|
continue;
|
|
1636
2031
|
}
|
|
1637
|
-
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()) {
|
|
1638
2047
|
const args = this.parseCallArguments();
|
|
1639
2048
|
base = {
|
|
1640
2049
|
type: "CallExpression",
|
|
@@ -1648,6 +2057,11 @@ var Parser = class {
|
|
|
1648
2057
|
}
|
|
1649
2058
|
return base;
|
|
1650
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
|
+
}
|
|
1651
2065
|
/** An assignment target after the first: a prefix expression (`a.b`,
|
|
1652
2066
|
* `a[i]`, `a`) or a nested destructuring pattern. */
|
|
1653
2067
|
parseAssignTarget() {
|
|
@@ -1655,14 +2069,52 @@ var Parser = class {
|
|
|
1655
2069
|
if (this.checkPunctuator("[")) return this.parseArrayPattern();
|
|
1656
2070
|
return this.parsePrefixExpression();
|
|
1657
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
|
+
}
|
|
1658
2098
|
parseCallArguments() {
|
|
1659
2099
|
if (this.matchPunctuator("(")) {
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
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
|
+
}
|
|
1663
2116
|
}
|
|
1664
|
-
|
|
1665
|
-
this.expectPunctuator(")");
|
|
2117
|
+
this.expectCloser(")");
|
|
1666
2118
|
return list;
|
|
1667
2119
|
}
|
|
1668
2120
|
const t = this.current();
|
|
@@ -1689,57 +2141,89 @@ var Parser = class {
|
|
|
1689
2141
|
const start = this.current();
|
|
1690
2142
|
this.expectPunctuator("{");
|
|
1691
2143
|
const fields = [];
|
|
2144
|
+
const stop = () => this.checkPunctuator(",") || this.checkPunctuator(";") || this.onNewLine() && this.startsTableField();
|
|
1692
2145
|
while (!this.checkPunctuator("}")) {
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
this.expectPunctuator(":");
|
|
1701
|
-
const value = this.parseExpression();
|
|
1702
|
-
fields.push({ type: "TableFieldComputed", key, value });
|
|
1703
|
-
} else if (this.checkType("Literal") && this.current().kind === "string") {
|
|
1704
|
-
const t = this.advance();
|
|
1705
|
-
const key = { type: "StringLiteral", value: t.value, raw: t.raw, ...spanFrom(t, t) };
|
|
1706
|
-
this.expectPunctuator(":");
|
|
1707
|
-
const value = this.parseExpression();
|
|
1708
|
-
fields.push({ type: "TableFieldNamed", key, value });
|
|
1709
|
-
} else if (this.checkType("Identifier") && this.peek(1).type === "Punctuator" && this.peek(1).value === ":") {
|
|
1710
|
-
const key = this.parseIdentifier();
|
|
1711
|
-
this.expectPunctuator(":");
|
|
1712
|
-
const value = this.parseExpression();
|
|
1713
|
-
fields.push({ type: "TableFieldNamed", key, value });
|
|
1714
|
-
} else if (this.checkType("Identifier")) {
|
|
1715
|
-
const name = this.parseIdentifier();
|
|
1716
|
-
fields.push({ type: "TableFieldShorthand", name });
|
|
1717
|
-
} else {
|
|
1718
|
-
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;
|
|
1719
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");
|
|
1720
2158
|
if (this.matchPunctuator(",") || this.matchPunctuator(";")) continue;
|
|
2159
|
+
if (this.cursor > before && this.onNewLine() && this.startsTableField()) continue;
|
|
1721
2160
|
break;
|
|
1722
2161
|
}
|
|
1723
|
-
this.
|
|
2162
|
+
this.expectCloser("}");
|
|
1724
2163
|
return { type: "TableExpression", fields, ...spanFrom(start, this.previous()) };
|
|
1725
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
|
+
}
|
|
1726
2200
|
// `[1, 2, 3]` — array literal (trailing comma allowed).
|
|
1727
2201
|
parseArrayExpression() {
|
|
1728
2202
|
const start = this.current();
|
|
1729
2203
|
this.expectPunctuator("[");
|
|
1730
2204
|
const elements = [];
|
|
2205
|
+
const stop = () => this.checkPunctuator(",");
|
|
1731
2206
|
while (!this.checkPunctuator("]")) {
|
|
1732
2207
|
if (this.checkOperator("...")) {
|
|
1733
2208
|
const dots = this.advance();
|
|
1734
|
-
const argument = this.
|
|
2209
|
+
const argument = this.expressionOr(stop);
|
|
1735
2210
|
elements.push({ type: "SpreadElement", argument, ...spanFrom(dots, argument) });
|
|
1736
2211
|
} else {
|
|
1737
|
-
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;
|
|
1738
2219
|
}
|
|
2220
|
+
if (this.isAtEnd() || this.onNewLine() && this.checkType("Keyword")) break;
|
|
2221
|
+
this.softError("Expected ',' or ']'");
|
|
2222
|
+
this.skip(stop, this.cursor, "expression");
|
|
1739
2223
|
if (this.matchPunctuator(",")) continue;
|
|
1740
2224
|
break;
|
|
1741
2225
|
}
|
|
1742
|
-
this.
|
|
2226
|
+
this.expectCloser("]");
|
|
1743
2227
|
return { type: "ArrayExpression", elements, ...spanFrom(start, this.previous()) };
|
|
1744
2228
|
}
|
|
1745
2229
|
// ============================================================
|
|
@@ -1772,7 +2256,7 @@ var Parser = class {
|
|
|
1772
2256
|
};
|
|
1773
2257
|
}
|
|
1774
2258
|
if (topLevel && this.matchPunctuator(":")) {
|
|
1775
|
-
target.typeAnnotation = this.
|
|
2259
|
+
target.typeAnnotation = this.typeOr(() => this.checkOperator("=") || this.checkPunctuator(","));
|
|
1776
2260
|
}
|
|
1777
2261
|
return target;
|
|
1778
2262
|
}
|
|
@@ -1931,12 +2415,13 @@ var Parser = class {
|
|
|
1931
2415
|
}
|
|
1932
2416
|
const optional2 = this.matchPunctuator("?");
|
|
1933
2417
|
let typeAnnotation;
|
|
2418
|
+
const paramEnd = () => this.checkPunctuator(",");
|
|
1934
2419
|
if (this.matchPunctuator(":")) {
|
|
1935
|
-
typeAnnotation = this.
|
|
2420
|
+
typeAnnotation = this.typeOr(() => paramEnd() || this.checkOperator("="));
|
|
1936
2421
|
}
|
|
1937
2422
|
let def;
|
|
1938
2423
|
if (this.matchOperator("=")) {
|
|
1939
|
-
def = this.
|
|
2424
|
+
def = this.expressionOr(paramEnd);
|
|
1940
2425
|
}
|
|
1941
2426
|
params.push({
|
|
1942
2427
|
type: "FunctionParameter",
|
|
@@ -1947,7 +2432,7 @@ var Parser = class {
|
|
|
1947
2432
|
optional: optional2 || void 0,
|
|
1948
2433
|
...spanFrom(paramStart, this.previous())
|
|
1949
2434
|
});
|
|
1950
|
-
if (this.matchPunctuator(",")) continue;
|
|
2435
|
+
if (this.matchPunctuator(",") && !this.checkPunctuator(")")) continue;
|
|
1951
2436
|
break;
|
|
1952
2437
|
}
|
|
1953
2438
|
}
|
|
@@ -1956,7 +2441,9 @@ var Parser = class {
|
|
|
1956
2441
|
let predicate;
|
|
1957
2442
|
if (this.matchPunctuator(":")) {
|
|
1958
2443
|
predicate = this.tryParseTypePredicate();
|
|
1959
|
-
if (!predicate)
|
|
2444
|
+
if (!predicate) {
|
|
2445
|
+
returnType = this.attempt(() => this.parseTypeOrTypePackReference(), () => false, () => void 0);
|
|
2446
|
+
}
|
|
1960
2447
|
}
|
|
1961
2448
|
return { start, generics, params, hasVarargs, varargTypeAnnotation, returnType, predicate };
|
|
1962
2449
|
}
|
|
@@ -2002,10 +2489,10 @@ var Parser = class {
|
|
|
2002
2489
|
}
|
|
2003
2490
|
return void 0;
|
|
2004
2491
|
}
|
|
2005
|
-
parseFunctionBody() {
|
|
2492
|
+
parseFunctionBody(opener) {
|
|
2006
2493
|
const head = this.parseFunctionHead();
|
|
2007
|
-
const body = this.parseBlock();
|
|
2008
|
-
this.
|
|
2494
|
+
const body = this.parseBlock(opener);
|
|
2495
|
+
this.expectEnd(opener);
|
|
2009
2496
|
return {
|
|
2010
2497
|
type: "FunctionBody",
|
|
2011
2498
|
generics: head.generics,
|
|
@@ -2030,9 +2517,9 @@ var Parser = class {
|
|
|
2030
2517
|
...spanFrom(head.start, this.previous())
|
|
2031
2518
|
};
|
|
2032
2519
|
}
|
|
2033
|
-
headToBody(head) {
|
|
2034
|
-
const body = this.parseBlock();
|
|
2035
|
-
this.
|
|
2520
|
+
headToBody(head, opener) {
|
|
2521
|
+
const body = this.parseBlock(opener);
|
|
2522
|
+
this.expectEnd(opener);
|
|
2036
2523
|
return {
|
|
2037
2524
|
type: "FunctionBody",
|
|
2038
2525
|
generics: head.generics,
|
|
@@ -2239,7 +2726,7 @@ var Parser = class {
|
|
|
2239
2726
|
this.advance();
|
|
2240
2727
|
if (!this.checkOperator(">")) {
|
|
2241
2728
|
typeArguments.push(this.parseTypeArgument());
|
|
2242
|
-
while (this.matchPunctuator(",")) {
|
|
2729
|
+
while (this.matchPunctuator(",") && !this.checkOperator(">")) {
|
|
2243
2730
|
typeArguments.push(this.parseTypeArgument());
|
|
2244
2731
|
}
|
|
2245
2732
|
}
|
|
@@ -2290,7 +2777,7 @@ var Parser = class {
|
|
|
2290
2777
|
optional: optional2 || void 0,
|
|
2291
2778
|
...spanFrom(paramStart, this.previous())
|
|
2292
2779
|
});
|
|
2293
|
-
if (this.matchPunctuator(",")) continue;
|
|
2780
|
+
if (this.matchPunctuator(",") && !this.checkPunctuator(")")) continue;
|
|
2294
2781
|
break;
|
|
2295
2782
|
}
|
|
2296
2783
|
}
|
|
@@ -2505,7 +2992,7 @@ var Parser = class {
|
|
|
2505
2992
|
default: def,
|
|
2506
2993
|
...spanFrom(nameTok, this.previous())
|
|
2507
2994
|
});
|
|
2508
|
-
if (this.matchPunctuator(",")) continue;
|
|
2995
|
+
if (this.matchPunctuator(",") && !this.checkOperator(">")) continue;
|
|
2509
2996
|
break;
|
|
2510
2997
|
}
|
|
2511
2998
|
this.expectOperator(">");
|
|
@@ -2532,23 +3019,23 @@ function parseExpressionFromSource(raw) {
|
|
|
2532
3019
|
return expr;
|
|
2533
3020
|
}
|
|
2534
3021
|
function parseWithRecovery(source) {
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
const
|
|
2551
|
-
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) };
|
|
2552
3039
|
}
|
|
2553
3040
|
|
|
2554
3041
|
// src/ast/nodes.ts
|
|
@@ -2587,19 +3074,24 @@ function childScope(parent) {
|
|
|
2587
3074
|
return { parent, declarations: /* @__PURE__ */ new Map() };
|
|
2588
3075
|
}
|
|
2589
3076
|
var Analyzer = class {
|
|
2590
|
-
nextId = 0;
|
|
2591
|
-
bindingOf = /* @__PURE__ */ new Map();
|
|
2592
|
-
bindings = /* @__PURE__ */ new Map();
|
|
2593
|
-
diagnostics = [];
|
|
2594
|
-
globalScope = { parent: null, declarations: /* @__PURE__ */ new Map() };
|
|
2595
3077
|
constructor(options) {
|
|
3078
|
+
this.options = options;
|
|
2596
3079
|
for (const name of options.builtinGlobals ?? []) {
|
|
2597
3080
|
const id = this.getOrCreateGlobalBinding(name);
|
|
2598
3081
|
this.bindings.get(id).isBuiltin = true;
|
|
2599
3082
|
}
|
|
2600
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() };
|
|
2601
3090
|
run(program) {
|
|
2602
|
-
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);
|
|
2603
3095
|
return {
|
|
2604
3096
|
bindingOf: this.bindingOf,
|
|
2605
3097
|
bindings: this.bindings,
|
|
@@ -2607,6 +3099,99 @@ var Analyzer = class {
|
|
|
2607
3099
|
globalsByName: this.globalScope.declarations
|
|
2608
3100
|
};
|
|
2609
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
|
+
}
|
|
2610
3195
|
// ---------------- declaration / resolution primitives ----------------
|
|
2611
3196
|
declare(scope, name, kind, node, isConst = false, declaredBy) {
|
|
2612
3197
|
if (scope.declarations.has(name) && scope !== this.globalScope) {
|
|
@@ -2652,6 +3237,8 @@ var Analyzer = class {
|
|
|
2652
3237
|
this.bindingOf.set(identifier, id);
|
|
2653
3238
|
this.bindings.get(id).references.push(identifier);
|
|
2654
3239
|
if (this.typeQueryDepth === 0) this.checkTypeOnly(id, identifier);
|
|
3240
|
+
this.checkUseBeforeDefine(identifier, id);
|
|
3241
|
+
this.noteDeferred(identifier, scope, id, false);
|
|
2655
3242
|
}
|
|
2656
3243
|
/** Inside `typeof x` in a type, where a type-only import may be named. */
|
|
2657
3244
|
typeQueryDepth = 0;
|
|
@@ -2682,6 +3269,7 @@ var Analyzer = class {
|
|
|
2682
3269
|
this.recordPossibleGlobalDefinition(id, identifier);
|
|
2683
3270
|
this.checkTypeOnly(id, identifier);
|
|
2684
3271
|
this.checkConstAssign(id, identifier);
|
|
3272
|
+
this.noteDeferred(identifier, scope, id, true);
|
|
2685
3273
|
}
|
|
2686
3274
|
/** `Module.x = 1` through `import * as Module`: a module's exports belong
|
|
2687
3275
|
* to it and are read-only, as in ES modules. Deeper writes (`Module.x.y`)
|
|
@@ -2774,6 +3362,7 @@ var Analyzer = class {
|
|
|
2774
3362
|
}
|
|
2775
3363
|
// ---------------- blocks / statements ----------------
|
|
2776
3364
|
visitBlock(block, scope) {
|
|
3365
|
+
this.hoistFunctions(block, scope);
|
|
2777
3366
|
for (const stmt of block.statements) this.visitStatement(stmt, scope);
|
|
2778
3367
|
}
|
|
2779
3368
|
/** Visits a block in a *fresh child scope* of `scope` — the common case
|
|
@@ -2792,7 +3381,11 @@ var Analyzer = class {
|
|
|
2792
3381
|
return;
|
|
2793
3382
|
}
|
|
2794
3383
|
case "FunctionDeclaration": {
|
|
2795
|
-
this.declare(scope, stmt.name.name, "local", stmt.name, true, "function");
|
|
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);
|
|
2796
3389
|
for (const signature of stmt.signatures ?? []) this.visitSignature(signature, scope);
|
|
2797
3390
|
this.visitFunctionBody(stmt.func, scope);
|
|
2798
3391
|
return;
|
|
@@ -2893,7 +3486,11 @@ var Analyzer = class {
|
|
|
2893
3486
|
return;
|
|
2894
3487
|
case "TypeAliasStatement":
|
|
2895
3488
|
case "ExportTypeAliasStatement":
|
|
2896
|
-
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);
|
|
2897
3494
|
return;
|
|
2898
3495
|
case "ImportStatement": {
|
|
2899
3496
|
const typeOnly = stmt.isTypeOnly ? "type" : void 0;
|
|
@@ -2931,6 +3528,7 @@ var Analyzer = class {
|
|
|
2931
3528
|
// ---------------- functions ----------------
|
|
2932
3529
|
visitFunctionBody(func, outerScope, isMethod = false) {
|
|
2933
3530
|
const fnScope = childScope(outerScope);
|
|
3531
|
+
this.visitGenerics(func.generics, fnScope);
|
|
2934
3532
|
func.params.forEach((param, i) => {
|
|
2935
3533
|
const kind = isMethod && i === 0 ? "self" : "param";
|
|
2936
3534
|
this.visitType(param.typeAnnotation, fnScope);
|
|
@@ -2943,14 +3541,28 @@ var Analyzer = class {
|
|
|
2943
3541
|
});
|
|
2944
3542
|
this.visitType(func.varargTypeAnnotation, fnScope);
|
|
2945
3543
|
this.visitType(func.returnType, fnScope);
|
|
2946
|
-
this.
|
|
3544
|
+
this.functionDepth++;
|
|
3545
|
+
try {
|
|
3546
|
+
this.visitBlock(func.body, fnScope);
|
|
3547
|
+
} finally {
|
|
3548
|
+
this.functionDepth--;
|
|
3549
|
+
}
|
|
2947
3550
|
}
|
|
2948
3551
|
/** An overload signature: no body and no bindings, but its types can hold
|
|
2949
3552
|
* a `typeof x`. */
|
|
2950
3553
|
visitSignature(signature, scope) {
|
|
3554
|
+
this.visitGenerics(signature.generics, scope);
|
|
2951
3555
|
for (const param of signature.params) this.visitType(param.typeAnnotation, scope);
|
|
2952
3556
|
this.visitType(signature.returnType, scope);
|
|
2953
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
|
+
}
|
|
2954
3566
|
/** Resolve the value references inside a type. Only `typeof x` has any —
|
|
2955
3567
|
* everything else in a type names types, which live in their own
|
|
2956
3568
|
* namespace and are not this pass's business. */
|
|
@@ -2988,6 +3600,7 @@ var Analyzer = class {
|
|
|
2988
3600
|
case "NumberLiteral":
|
|
2989
3601
|
case "StringLiteral":
|
|
2990
3602
|
case "VarargExpression":
|
|
3603
|
+
case "ErrorExpression":
|
|
2991
3604
|
return;
|
|
2992
3605
|
case "InterpolatedStringExpression":
|
|
2993
3606
|
for (const part of expr.parts) {
|
|
@@ -3072,6 +3685,37 @@ function analyzeScopes(program, options = {}) {
|
|
|
3072
3685
|
return new Analyzer(options).run(program);
|
|
3073
3686
|
}
|
|
3074
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
|
+
|
|
3075
3719
|
// src/ast/typeModel.ts
|
|
3076
3720
|
function isClassType(t) {
|
|
3077
3721
|
return t.kind === "object" && t.class !== void 0;
|
|
@@ -3147,6 +3791,7 @@ function substitute(t, subst) {
|
|
|
3147
3791
|
varargs,
|
|
3148
3792
|
returns: substitute(t.returns, inner),
|
|
3149
3793
|
typeParams: t.typeParams,
|
|
3794
|
+
typeParamDefaults: t.typeParamDefaults,
|
|
3150
3795
|
predicate: t.predicate && {
|
|
3151
3796
|
...t.predicate,
|
|
3152
3797
|
type: t.predicate.type && substitute(t.predicate.type, inner)
|
|
@@ -3405,6 +4050,15 @@ function isAssignableInner(a, b) {
|
|
|
3405
4050
|
}
|
|
3406
4051
|
if (!isAssignable(ap.type, bp.type)) return false;
|
|
3407
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
|
+
}
|
|
4061
|
+
}
|
|
3408
4062
|
return true;
|
|
3409
4063
|
}
|
|
3410
4064
|
if (a.kind === "function") {
|
|
@@ -3554,10 +4208,14 @@ function containsFreeTypeParam(t, seen, bound) {
|
|
|
3554
4208
|
return containsTypeParam(t.base, seen, bound) || containsTypeParam(t.excluded, seen, bound);
|
|
3555
4209
|
case "indexedAccess":
|
|
3556
4210
|
return containsTypeParam(t.objectType, seen, bound) || containsTypeParam(t.indexType, seen, bound);
|
|
3557
|
-
case "conditional":
|
|
3558
|
-
|
|
3559
|
-
|
|
3560
|
-
|
|
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
|
+
}
|
|
3561
4219
|
default:
|
|
3562
4220
|
return false;
|
|
3563
4221
|
}
|
|
@@ -3631,9 +4289,52 @@ function escapeRegExp(s) {
|
|
|
3631
4289
|
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3632
4290
|
}
|
|
3633
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));
|
|
3634
4294
|
return isAssignable(a, b) || isAssignable(b, a);
|
|
3635
4295
|
}
|
|
3636
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
|
+
}
|
|
3637
4338
|
function formatType(t) {
|
|
3638
4339
|
const cached = formatCache.get(t);
|
|
3639
4340
|
if (cached !== void 0) return cached;
|
|
@@ -3670,7 +4371,14 @@ function formatTypeUncached(t) {
|
|
|
3670
4371
|
const consts = new Set(
|
|
3671
4372
|
t.params.filter((p) => p.type.kind === "typeParam" && p.type.isConst).map((p) => p.type.name)
|
|
3672
4373
|
);
|
|
3673
|
-
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(", ")}>` : "";
|
|
3674
4382
|
const ps = t.params.map((p) => `${p.name ? p.name + ": " : ""}${formatType(p.type)}`);
|
|
3675
4383
|
if (t.varargs) ps.push(`...${formatType(t.varargs)}`);
|
|
3676
4384
|
return `${gen}(${ps.join(", ")}) -> ${formatPredicate(t) ?? formatType(t.returns)}`;
|
|
@@ -3886,6 +4594,12 @@ function isFreshLiteralExpr(e) {
|
|
|
3886
4594
|
return isFreshLiteralExpr(e.expression);
|
|
3887
4595
|
case "UnaryExpression":
|
|
3888
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
|
+
}
|
|
3889
4603
|
default:
|
|
3890
4604
|
return false;
|
|
3891
4605
|
}
|
|
@@ -3995,6 +4709,48 @@ var AliasMap = class extends Map {
|
|
|
3995
4709
|
return this.entries();
|
|
3996
4710
|
}
|
|
3997
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
|
+
}
|
|
3998
4754
|
var METAMETHODS = {
|
|
3999
4755
|
"+": "__add",
|
|
4000
4756
|
"-": "__sub",
|
|
@@ -4068,14 +4824,16 @@ var TypeAnalyzer = class {
|
|
|
4068
4824
|
/** Recursion guard for `preVisitBody`. */
|
|
4069
4825
|
preVisitDepth = 0;
|
|
4070
4826
|
run() {
|
|
4071
|
-
|
|
4827
|
+
this.registerAliasDefs(preludeProgram().body, true);
|
|
4828
|
+
for (const lib of this.options.libs ?? []) this.registerAliasDefs(lib.body, true);
|
|
4072
4829
|
this.registerAliasDefs(this.program.body);
|
|
4073
4830
|
for (const lib of this.options.libs ?? []) this.harvestDeclares(lib.body);
|
|
4074
|
-
this.harvestDeclares(this.program.body);
|
|
4075
4831
|
this.registerImportedTypes();
|
|
4076
4832
|
this.resolveAllAliases();
|
|
4833
|
+
this.harvestDeclares(this.program.body, true);
|
|
4077
4834
|
this.indexDeclarations();
|
|
4078
4835
|
for (const [name, id] of this.scopes.globalsByName) {
|
|
4836
|
+
if (this.deferredDeclares.has(name) && !this.options.globalTypes?.[name]) continue;
|
|
4079
4837
|
const t = this.options.globalTypes?.[name] ?? this.libGlobalTypes.get(name) ?? anyType;
|
|
4080
4838
|
this.bindingType.set(id, t);
|
|
4081
4839
|
}
|
|
@@ -4083,6 +4841,8 @@ var TypeAnalyzer = class {
|
|
|
4083
4841
|
try {
|
|
4084
4842
|
const env = /* @__PURE__ */ new Map();
|
|
4085
4843
|
this.visitBlock(this.program.body, env);
|
|
4844
|
+
this.resolveDeferredDeclares();
|
|
4845
|
+
if (this.options.reportUnknownTypes) this.reportUnknownTypes();
|
|
4086
4846
|
} finally {
|
|
4087
4847
|
setAliasExpander(void 0);
|
|
4088
4848
|
}
|
|
@@ -4143,10 +4903,28 @@ var TypeAnalyzer = class {
|
|
|
4143
4903
|
}
|
|
4144
4904
|
}
|
|
4145
4905
|
}
|
|
4146
|
-
|
|
4906
|
+
/** `layering` is on for the prelude and for definitions files: a second
|
|
4907
|
+
* library that declares an alias already declared *adds* to it, the way a
|
|
4908
|
+
* second `declare` of a table's name does, so `@luaut/roblox` can give
|
|
4909
|
+
* `StringMethods` Luau's `split` without restating Lua's. The file being
|
|
4910
|
+
* analysed is not a layer: its own alias replaces what the libraries
|
|
4911
|
+
* gave, which is how a project opts out of a set. */
|
|
4912
|
+
registerAliasDefs(block, layering = false) {
|
|
4147
4913
|
for (const stmt of block.statements) {
|
|
4148
4914
|
const alias = stmt.type === "TypeAliasStatement" ? stmt : stmt.type === "ExportTypeAliasStatement" ? stmt.alias : void 0;
|
|
4149
|
-
if (alias)
|
|
4915
|
+
if (alias) {
|
|
4916
|
+
const previous = layering ? this.aliasDefs.get(alias.name.name) : void 0;
|
|
4917
|
+
const node = previous && !previous.class ? {
|
|
4918
|
+
type: "IntersectionTypeNode",
|
|
4919
|
+
types: [previous.node, alias.definition],
|
|
4920
|
+
line: alias.definition.line,
|
|
4921
|
+
column: alias.definition.column
|
|
4922
|
+
} : alias.definition;
|
|
4923
|
+
this.aliasDefs.set(alias.name.name, {
|
|
4924
|
+
params: previous && !previous.class && previous.params.length ? previous.params : alias.generics,
|
|
4925
|
+
node
|
|
4926
|
+
});
|
|
4927
|
+
}
|
|
4150
4928
|
if (stmt.type === "DeclareClassStatement") {
|
|
4151
4929
|
this.aliasDefs.set(stmt.name.name, { params: [], node: stmt.body, class: stmt });
|
|
4152
4930
|
}
|
|
@@ -4245,13 +5023,38 @@ var TypeAnalyzer = class {
|
|
|
4245
5023
|
* string. Any other value is simply redeclared: a sourcemap's
|
|
4246
5024
|
* `declare script: <this file's instance>` replaces the library's
|
|
4247
5025
|
* `declare script: LuaSourceContainer`. */
|
|
4248
|
-
|
|
5026
|
+
/** Program `declare`s whose type depends on a value's, by name. */
|
|
5027
|
+
deferredDeclares = /* @__PURE__ */ new Map();
|
|
5028
|
+
/** A library that declares a name a second time adds to it rather than
|
|
5029
|
+
* replacing it: `declare table: { find: ... }` on top of Lua's `table`
|
|
5030
|
+
* leaves both members there, the way overloads of a function accumulate.
|
|
5031
|
+
* This is what lets one definitions file build on another's — Luau's on
|
|
5032
|
+
* Lua's, Roblox's on Luau's. A property declared twice takes its later
|
|
5033
|
+
* type. Classes stay as they are: they come from one generated file and
|
|
5034
|
+
* merging them would only blur it. */
|
|
5035
|
+
mergeDeclared(prev, next) {
|
|
5036
|
+
if (!prev || prev.kind !== "object" || next.kind !== "object") return next;
|
|
5037
|
+
if (prev.class || next.class) return next;
|
|
5038
|
+
return objectType(
|
|
5039
|
+
[...prev.properties, ...next.properties],
|
|
5040
|
+
next.indexer ?? prev.indexer,
|
|
5041
|
+
next.frozen ?? prev.frozen
|
|
5042
|
+
);
|
|
5043
|
+
}
|
|
5044
|
+
harvestDeclares(block, own = false) {
|
|
4249
5045
|
for (const stmt of block.statements) {
|
|
4250
5046
|
if (stmt.type !== "DeclareStatement") continue;
|
|
5047
|
+
if (own && (containsTypeQuery(stmt.valueType) || referencedTypeNames(stmt.valueType).some((name) => this.dependsOnTypeQuery(name)))) {
|
|
5048
|
+
this.deferredDeclares.set(stmt.name, stmt);
|
|
5049
|
+
continue;
|
|
5050
|
+
}
|
|
4251
5051
|
const t = this.resolveType(stmt.valueType);
|
|
4252
5052
|
const prev = this.libGlobalTypes.get(stmt.name);
|
|
4253
5053
|
const overload = prev && stmt.valueType.type === "FunctionTypeNode" && (prev.kind === "function" || prev.kind === "intersection");
|
|
4254
|
-
this.libGlobalTypes.set(
|
|
5054
|
+
this.libGlobalTypes.set(
|
|
5055
|
+
stmt.name,
|
|
5056
|
+
overload ? intersection([prev, t]) : this.mergeDeclared(prev, t)
|
|
5057
|
+
);
|
|
4255
5058
|
}
|
|
4256
5059
|
}
|
|
4257
5060
|
resolveAllAliases() {
|
|
@@ -4261,14 +5064,154 @@ var TypeAnalyzer = class {
|
|
|
4261
5064
|
this.aliases.defer(name, () => this.classType(cls));
|
|
4262
5065
|
continue;
|
|
4263
5066
|
}
|
|
4264
|
-
if (
|
|
5067
|
+
if (this.dependsOnTypeQuery(name)) continue;
|
|
4265
5068
|
this.withTypeParams(def.params, () => {
|
|
4266
5069
|
this.aliases.set(name, this.resolveDef(def));
|
|
4267
5070
|
});
|
|
4268
5071
|
}
|
|
4269
5072
|
}
|
|
5073
|
+
typeQueryDependents = /* @__PURE__ */ new Map();
|
|
5074
|
+
/** Does alias `name` contain a `typeof`, itself or through an alias it
|
|
5075
|
+
* names? */
|
|
5076
|
+
dependsOnTypeQuery(name, visiting = /* @__PURE__ */ new Set()) {
|
|
5077
|
+
const known = this.typeQueryDependents.get(name);
|
|
5078
|
+
if (known !== void 0) return known;
|
|
5079
|
+
const def = this.aliasDefs.get(name);
|
|
5080
|
+
if (!def || def.class || visiting.has(name)) return false;
|
|
5081
|
+
visiting.add(name);
|
|
5082
|
+
const result = containsTypeQuery(def.node) || referencedTypeNames(def.node).some((other) => other !== name && this.dependsOnTypeQuery(other, visiting));
|
|
5083
|
+
visiting.delete(name);
|
|
5084
|
+
this.typeQueryDependents.set(name, result);
|
|
5085
|
+
return result;
|
|
5086
|
+
}
|
|
4270
5087
|
/** The aliases `resolveAllAliases` left for later, now that every binding
|
|
4271
5088
|
* has its type. */
|
|
5089
|
+
/** Names this file imports. A module that could not be found is reported
|
|
5090
|
+
* as the missing module it is; the names it was to bring are not also
|
|
5091
|
+
* typos. */
|
|
5092
|
+
importedNames() {
|
|
5093
|
+
if (this.imported) return this.imported;
|
|
5094
|
+
this.imported = /* @__PURE__ */ new Set();
|
|
5095
|
+
for (const statement of this.program.body.statements) {
|
|
5096
|
+
if (statement.type !== "ImportStatement") continue;
|
|
5097
|
+
if (statement.defaultImport) this.imported.add(statement.defaultImport.name);
|
|
5098
|
+
if (statement.namespaceImport) this.imported.add(statement.namespaceImport.name);
|
|
5099
|
+
for (const specifier of statement.specifiers) this.imported.add(specifier.local.name);
|
|
5100
|
+
}
|
|
5101
|
+
return this.imported;
|
|
5102
|
+
}
|
|
5103
|
+
imported;
|
|
5104
|
+
/** What a `return` gives, against what the function declared. */
|
|
5105
|
+
checkReturn(stmt, declared, types, sources, env) {
|
|
5106
|
+
if (!declared || !this.emitDiagnostics) return;
|
|
5107
|
+
if (declared.kind === "any" || declared.kind === "unknown" || this.namesNothing(declared)) return;
|
|
5108
|
+
const actual = stmt.arguments.length === 0 ? nilType : types.length === 1 ? types[0] : tuple([...types], true);
|
|
5109
|
+
const source = stmt.arguments.length === 1 ? sources[0] : void 0;
|
|
5110
|
+
const fits = source ? this.fitsAnnotation(source, declared, actual, env) : isAssignable(actual, declared) || isAssignable(widen(actual), declared);
|
|
5111
|
+
if (fits) return;
|
|
5112
|
+
this.diagnostics.push({
|
|
5113
|
+
node: stmt,
|
|
5114
|
+
message: `Type '${formatType(actual)}' is not assignable to '${briefType(declared)}'`
|
|
5115
|
+
});
|
|
5116
|
+
}
|
|
5117
|
+
/** A function that declared what it returns but never does. Only a body
|
|
5118
|
+
* with no `return` at all is reported: anything subtler needs to know
|
|
5119
|
+
* which paths can run off the end, and a wrong guess there is worse than
|
|
5120
|
+
* a missing complaint. */
|
|
5121
|
+
checkReturnsAtAll(func, declared) {
|
|
5122
|
+
if (!declared || !this.emitDiagnostics) return;
|
|
5123
|
+
if (func.predicate) return;
|
|
5124
|
+
if (declared.kind === "any" || declared.kind === "unknown" || declared.kind === "never") return;
|
|
5125
|
+
if (isAssignable(nilType, declared) || this.namesNothing(declared)) return;
|
|
5126
|
+
let found = false;
|
|
5127
|
+
const walk = (statements) => {
|
|
5128
|
+
for (const statement of statements) {
|
|
5129
|
+
if (found) return;
|
|
5130
|
+
if (statement.type === "ReturnStatement") {
|
|
5131
|
+
found = true;
|
|
5132
|
+
return;
|
|
5133
|
+
}
|
|
5134
|
+
for (const value of Object.values(statement)) {
|
|
5135
|
+
if (value && typeof value === "object" && "statements" in value) {
|
|
5136
|
+
walk(value.statements);
|
|
5137
|
+
} else if (Array.isArray(value)) {
|
|
5138
|
+
for (const item of value) {
|
|
5139
|
+
const block = item;
|
|
5140
|
+
if (block?.body?.statements) walk(block.body.statements);
|
|
5141
|
+
}
|
|
5142
|
+
}
|
|
5143
|
+
}
|
|
5144
|
+
}
|
|
5145
|
+
};
|
|
5146
|
+
walk(func.body.statements);
|
|
5147
|
+
if (found) return;
|
|
5148
|
+
this.diagnostics.push({
|
|
5149
|
+
node: func.body,
|
|
5150
|
+
message: `A function that returns '${briefType(declared)}' must return a value`
|
|
5151
|
+
});
|
|
5152
|
+
}
|
|
5153
|
+
/** Does this type rest on a name nothing declares? Such a type says
|
|
5154
|
+
* nothing about what fits it, so checking against it only piles a second
|
|
5155
|
+
* complaint on top of "Cannot find name". */
|
|
5156
|
+
namesNothing(t, seen = /* @__PURE__ */ new Set()) {
|
|
5157
|
+
if (seen.has(t)) return false;
|
|
5158
|
+
seen.add(t);
|
|
5159
|
+
if (t.kind === "genericRef") {
|
|
5160
|
+
return !this.aliasDefs.has(t.name) && !this.importedTypes.has(t.name) && this.options.libTypes?.[t.name] === void 0;
|
|
5161
|
+
}
|
|
5162
|
+
switch (t.kind) {
|
|
5163
|
+
case "union":
|
|
5164
|
+
case "intersection":
|
|
5165
|
+
return t.types.some((m) => this.namesNothing(m, seen));
|
|
5166
|
+
case "array":
|
|
5167
|
+
return this.namesNothing(t.element, seen);
|
|
5168
|
+
case "tuple":
|
|
5169
|
+
return t.elements.some((e) => this.namesNothing(e, seen));
|
|
5170
|
+
case "object":
|
|
5171
|
+
if (t.class) return false;
|
|
5172
|
+
return [...t.properties.values()].some((v) => this.namesNothing(v.type, seen));
|
|
5173
|
+
default:
|
|
5174
|
+
return false;
|
|
5175
|
+
}
|
|
5176
|
+
}
|
|
5177
|
+
/** Every type name in the program that resolved to nothing — a typo, or a
|
|
5178
|
+
* library the config does not load. A name that resolves to a type
|
|
5179
|
+
* parameter, an alias (even one still being resolved), an imported type or
|
|
5180
|
+
* a primitive is fine; what is left is a reference that stayed itself. */
|
|
5181
|
+
reportUnknownTypes() {
|
|
5182
|
+
if (!this.emitDiagnostics) return;
|
|
5183
|
+
const reported = /* @__PURE__ */ new Set();
|
|
5184
|
+
const visit = (node) => {
|
|
5185
|
+
if (!node || typeof node !== "object") return;
|
|
5186
|
+
if (Array.isArray(node)) {
|
|
5187
|
+
for (const item of node) visit(item);
|
|
5188
|
+
return;
|
|
5189
|
+
}
|
|
5190
|
+
const record = node;
|
|
5191
|
+
if (record.type === "TypeReference" && typeof record.base === "string") {
|
|
5192
|
+
const name = typeof record.namespace === "string" ? `${record.namespace}.${record.base}` : record.base;
|
|
5193
|
+
const resolved = this.typeOfTypeNode.get(node);
|
|
5194
|
+
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]);
|
|
5195
|
+
const at = node;
|
|
5196
|
+
const key = `${at.line.start}:${at.column.start}`;
|
|
5197
|
+
if (unresolved && !reported.has(key)) {
|
|
5198
|
+
reported.add(key);
|
|
5199
|
+
this.diagnostics.push({ node, message: `Cannot find name '${name}'` });
|
|
5200
|
+
}
|
|
5201
|
+
}
|
|
5202
|
+
for (const [key, value] of Object.entries(node)) {
|
|
5203
|
+
if (key !== "line" && key !== "column" && value && typeof value === "object") visit(value);
|
|
5204
|
+
}
|
|
5205
|
+
};
|
|
5206
|
+
visit(this.program.body);
|
|
5207
|
+
}
|
|
5208
|
+
/** Deferred `declare`s nothing used, typed now for tools that ask. */
|
|
5209
|
+
resolveDeferredDeclares() {
|
|
5210
|
+
for (const name of this.deferredDeclares.keys()) {
|
|
5211
|
+
const id = this.scopes.globalsByName.get(name);
|
|
5212
|
+
if (id !== void 0 && !this.bindingType.has(id)) this.bindingType.set(id, this.declaredAhead(id) ?? anyType);
|
|
5213
|
+
}
|
|
5214
|
+
}
|
|
4272
5215
|
resolveDeferredAliases() {
|
|
4273
5216
|
for (const [name, def] of this.aliasDefs) {
|
|
4274
5217
|
if (this.aliases.has(name)) continue;
|
|
@@ -4451,13 +5394,13 @@ var TypeAnalyzer = class {
|
|
|
4451
5394
|
type: p.optional ? optional(this.resolveType(p.typeAnnotation)) : this.resolveType(p.typeAnnotation),
|
|
4452
5395
|
optional: p.optional
|
|
4453
5396
|
}));
|
|
4454
|
-
return fn(
|
|
5397
|
+
return this.withTypeParamDefaults(fn(
|
|
4455
5398
|
params,
|
|
4456
5399
|
this.resolveType(node.returnType),
|
|
4457
5400
|
node.hasVarargs ? node.varargType ? this.resolveType(node.varargType) : anyType : void 0,
|
|
4458
5401
|
names,
|
|
4459
5402
|
this.resolvePredicate(node.predicate, params)
|
|
4460
|
-
);
|
|
5403
|
+
), node.generics);
|
|
4461
5404
|
});
|
|
4462
5405
|
}
|
|
4463
5406
|
case "TypeofTypeNode": {
|
|
@@ -4703,7 +5646,11 @@ var TypeAnalyzer = class {
|
|
|
4703
5646
|
}
|
|
4704
5647
|
reduceConditional(t) {
|
|
4705
5648
|
const checkType = this.reduceType(t.checkType);
|
|
4706
|
-
|
|
5649
|
+
const extendsType = this.reduceType(t.extendsType);
|
|
5650
|
+
const free = new Set(t.inferVars);
|
|
5651
|
+
if (containsTypeParam(checkType) || containsTypeParam(extendsType, /* @__PURE__ */ new Set(), free)) {
|
|
5652
|
+
return { ...t, checkType, extendsType };
|
|
5653
|
+
}
|
|
4707
5654
|
if (t.distributeParam && checkType.kind === "union") {
|
|
4708
5655
|
return union(checkType.types.map((m) => this.branchOf(t, m)));
|
|
4709
5656
|
}
|
|
@@ -4808,15 +5755,22 @@ var TypeAnalyzer = class {
|
|
|
4808
5755
|
const source = sources[i];
|
|
4809
5756
|
if (this.emitDiagnostics && target.type === "IdentifierPattern" && target.typeAnnotation && source) {
|
|
4810
5757
|
const declared = this.resolveType(target.typeAnnotation);
|
|
4811
|
-
if (declared.kind !== "any" && !this.fitsAnnotation(source, declared, inferred, env)) {
|
|
5758
|
+
if (declared.kind !== "any" && !this.namesNothing(declared) && !this.fitsAnnotation(source, declared, inferred, env)) {
|
|
4812
5759
|
this.diagnostics.push({
|
|
4813
5760
|
node: stmt,
|
|
4814
5761
|
message: `Type '${formatType(inferred)}' is not assignable to '${formatType(declared)}'`
|
|
4815
5762
|
});
|
|
5763
|
+
} else if (declared.kind !== "any") {
|
|
5764
|
+
this.reportExcessProperties(source, declared);
|
|
4816
5765
|
}
|
|
4817
5766
|
}
|
|
4818
5767
|
const mode = this.initIsAsConst(source) ? "asconst" : !isFreshLiteralExpr(source) ? "keep" : stmt.kind === "const" ? "const" : "widen";
|
|
4819
5768
|
this.bindPattern(target, inferred, env, mode);
|
|
5769
|
+
if (stmt.kind === "const") {
|
|
5770
|
+
this.correlateDestructuring(target, inferred, env);
|
|
5771
|
+
this.correlateIndexed(target, source, env);
|
|
5772
|
+
this.aliasReference(target, source);
|
|
5773
|
+
}
|
|
4820
5774
|
});
|
|
4821
5775
|
return;
|
|
4822
5776
|
}
|
|
@@ -4824,6 +5778,7 @@ var TypeAnalyzer = class {
|
|
|
4824
5778
|
this.checkParamOrder(stmt.func.params, stmt);
|
|
4825
5779
|
for (const sig of stmt.signatures ?? []) this.checkParamOrder(sig.params, stmt);
|
|
4826
5780
|
const id = this.bindingIdByName(stmt.name.name, stmt.name);
|
|
5781
|
+
this.paramsFromSignatures(stmt.func, stmt.signatures);
|
|
4827
5782
|
const fnType = stmt.signatures?.length ? intersection(stmt.signatures.map((s) => this.signatureToFnType(s))) : this.inferFunctionBody(stmt.func, env);
|
|
4828
5783
|
if (id !== void 0) {
|
|
4829
5784
|
this.bindingType.set(id, fnType);
|
|
@@ -4839,6 +5794,7 @@ var TypeAnalyzer = class {
|
|
|
4839
5794
|
const memberName = stmt.target.method?.name ?? (stmt.target.path.length === 1 ? stmt.target.path[0].name : void 0);
|
|
4840
5795
|
if (memberName === void 0 && stmt.target.path.length === 0) {
|
|
4841
5796
|
if (targetId !== void 0) {
|
|
5797
|
+
this.paramsFromSignatures(stmt.func, stmt.signatures);
|
|
4842
5798
|
const fnType = stmt.signatures?.length ? intersection(stmt.signatures.map((s) => this.signatureToFnType(s))) : this.inferFunctionBody(stmt.func, env);
|
|
4843
5799
|
this.bindingType.set(targetId, fnType);
|
|
4844
5800
|
this.setBinding(env, targetId, fnType);
|
|
@@ -4848,6 +5804,7 @@ var TypeAnalyzer = class {
|
|
|
4848
5804
|
}
|
|
4849
5805
|
const recv = targetId === void 0 ? anyType : this.currentType(targetId, env);
|
|
4850
5806
|
this.withSelfType(stmt.isMethod ? recv : void 0, () => {
|
|
5807
|
+
this.paramsFromSignatures(stmt.func, stmt.signatures);
|
|
4851
5808
|
const fnType = stmt.signatures?.length ? intersection(stmt.signatures.map((s) => this.signatureToFnType(s))) : this.inferFunctionBody(stmt.func, env);
|
|
4852
5809
|
if (memberName !== void 0 && targetId !== void 0) {
|
|
4853
5810
|
const grown = intersection([
|
|
@@ -4879,6 +5836,7 @@ var TypeAnalyzer = class {
|
|
|
4879
5836
|
if (target.type === "Identifier") {
|
|
4880
5837
|
const id = this.bindingIdOf(target);
|
|
4881
5838
|
if (id !== void 0) {
|
|
5839
|
+
this.uncorrelate(id);
|
|
4882
5840
|
const next = isFreshLiteralExpr(source) ? widen(vt) : vt;
|
|
4883
5841
|
if (this.annotated.has(id)) {
|
|
4884
5842
|
const declared = this.bindingType.get(id);
|
|
@@ -4952,16 +5910,40 @@ var TypeAnalyzer = class {
|
|
|
4952
5910
|
case "GenericForStatement": {
|
|
4953
5911
|
const iterTypes = stmt.iterators.map((it) => this.infer(it, env));
|
|
4954
5912
|
const bodyEnv = forkEnv(env);
|
|
4955
|
-
const
|
|
4956
|
-
|
|
4957
|
-
|
|
4958
|
-
|
|
5913
|
+
const rows = stmt.variables.length >= 2 ? this.iterationRows(stmt.iterators[0], iterTypes[0]) : void 0;
|
|
5914
|
+
if (rows) {
|
|
5915
|
+
const [key, value] = stmt.variables;
|
|
5916
|
+
this.bindPattern(key, union(rows.map((r) => r[0])), bodyEnv, "keep");
|
|
5917
|
+
this.bindPattern(value, union(rows.map((r) => r[1])), bodyEnv, "keep");
|
|
5918
|
+
stmt.variables.slice(2).forEach((v) => this.bindPattern(v, unknownType, bodyEnv, "widen"));
|
|
5919
|
+
const keyId = key.type === "IdentifierPattern" ? this.bindingIdByName(key.name, key) : void 0;
|
|
5920
|
+
const valueId = value.type === "IdentifierPattern" ? this.bindingIdByName(value.name, value) : void 0;
|
|
5921
|
+
if (keyId !== void 0 && valueId !== void 0) this.correlateBindings(bodyEnv, [keyId, valueId], rows);
|
|
5922
|
+
} else {
|
|
5923
|
+
const [keyT, valT] = this.iterationTypes(stmt.iterators[0], iterTypes[0], stmt.variables.length);
|
|
5924
|
+
stmt.variables.forEach((v, i) => {
|
|
5925
|
+
this.bindPattern(v, i === 0 ? keyT : i === 1 ? valT : unknownType, bodyEnv, "widen");
|
|
5926
|
+
});
|
|
5927
|
+
}
|
|
4959
5928
|
this.visitBlock(stmt.body, bodyEnv);
|
|
4960
5929
|
return;
|
|
4961
5930
|
}
|
|
4962
|
-
case "ReturnStatement":
|
|
4963
|
-
|
|
5931
|
+
case "ReturnStatement": {
|
|
5932
|
+
const declared = this.declaredReturns[this.declaredReturns.length - 1];
|
|
5933
|
+
if (declared) {
|
|
5934
|
+
if (stmt.arguments.length === 1) {
|
|
5935
|
+
this.applyContext(stmt.arguments[0], declared);
|
|
5936
|
+
} else if (declared.kind === "tuple" && declared.isPack) {
|
|
5937
|
+
stmt.arguments.forEach((a, i) => this.applyContext(a, declared.elements[i]));
|
|
5938
|
+
}
|
|
5939
|
+
}
|
|
5940
|
+
const { types, sources } = this.valueList(stmt.arguments, env);
|
|
5941
|
+
this.checkReturn(stmt, declared, types, sources, env);
|
|
5942
|
+
if (this.returnTypes) {
|
|
5943
|
+
this.returnTypes.push(stmt.arguments.length === 0 ? nilType : types.length === 1 ? types[0] : tuple([...types], true));
|
|
5944
|
+
}
|
|
4964
5945
|
return;
|
|
5946
|
+
}
|
|
4965
5947
|
case "ExportStatement":
|
|
4966
5948
|
this.visitStatement(stmt.declaration, env);
|
|
4967
5949
|
return;
|
|
@@ -5144,6 +6126,7 @@ var TypeAnalyzer = class {
|
|
|
5144
6126
|
}
|
|
5145
6127
|
if (p.typeAnnotation) {
|
|
5146
6128
|
const t = this.resolveType(p.typeAnnotation);
|
|
6129
|
+
if (p.default) this.applyContext(p.default, t);
|
|
5147
6130
|
return p.optional ? optional(t) : t;
|
|
5148
6131
|
}
|
|
5149
6132
|
if (p.pattern) return this.patternToType(p.pattern, env);
|
|
@@ -5162,6 +6145,8 @@ var TypeAnalyzer = class {
|
|
|
5162
6145
|
let e = expr;
|
|
5163
6146
|
while (e.type === "ParenthesizedExpression") e = e.expression;
|
|
5164
6147
|
if (!expected) return;
|
|
6148
|
+
this.expectedTypeOf.set(expr, expected);
|
|
6149
|
+
this.expectedTypeOf.set(e, expected);
|
|
5165
6150
|
if (e.type === "ArrayExpression") return this.applyArrayContext(e, expected);
|
|
5166
6151
|
if (e.type === "TableExpression") return this.applyTableContext(e, expected);
|
|
5167
6152
|
if (e.type !== "FunctionExpression") return;
|
|
@@ -5204,13 +6189,15 @@ var TypeAnalyzer = class {
|
|
|
5204
6189
|
const objects = this.expectedMembers(expected).filter((m) => m.kind === "object");
|
|
5205
6190
|
if (!objects.length) return;
|
|
5206
6191
|
for (const field of e.fields) {
|
|
5207
|
-
if (field.type !== "TableFieldNamed") continue;
|
|
5208
|
-
const key = field.key.type === "Identifier" ? field.key.name : field.key.value;
|
|
6192
|
+
if (field.type !== "TableFieldNamed" && field.type !== "TableFieldShorthand") continue;
|
|
6193
|
+
const key = field.type === "TableFieldShorthand" ? field.name.name : field.key.type === "Identifier" ? field.key.name : field.key.value;
|
|
5209
6194
|
const types = objects.flatMap((o) => {
|
|
5210
6195
|
const property = o.properties.get(key);
|
|
5211
6196
|
return property ? [property.type] : o.indexer ? [o.indexer.value] : [];
|
|
5212
6197
|
});
|
|
5213
|
-
if (types.length)
|
|
6198
|
+
if (types.length) {
|
|
6199
|
+
this.applyContext(field.type === "TableFieldShorthand" ? field.name : field.value, union(types));
|
|
6200
|
+
}
|
|
5214
6201
|
}
|
|
5215
6202
|
}
|
|
5216
6203
|
/** The members of an expected type worth matching a literal against:
|
|
@@ -5247,11 +6234,28 @@ var TypeAnalyzer = class {
|
|
|
5247
6234
|
}
|
|
5248
6235
|
return tuple(target.elements.map((el) => el ? leaf(el.value, el.default) : anyType));
|
|
5249
6236
|
}
|
|
6237
|
+
/** The type of `...` in each function body being walked. */
|
|
6238
|
+
varargs = [];
|
|
6239
|
+
/** What each function body being walked declared it returns. */
|
|
6240
|
+
declaredReturns = [];
|
|
6241
|
+
/** Run `body` with `...` and `return` as `func` declares them. */
|
|
6242
|
+
withVarargs(func, body) {
|
|
6243
|
+
this.varargs.push(func.hasVarargs ? func.varargTypeAnnotation ? this.resolveType(func.varargTypeAnnotation) : anyType : void 0);
|
|
6244
|
+
this.declaredReturns.push(func.predicate ? booleanType : func.returnType ? this.resolveType(func.returnType) : void 0);
|
|
6245
|
+
try {
|
|
6246
|
+
return body();
|
|
6247
|
+
} finally {
|
|
6248
|
+
this.varargs.pop();
|
|
6249
|
+
this.declaredReturns.pop();
|
|
6250
|
+
}
|
|
6251
|
+
}
|
|
5250
6252
|
visitFunctionBodyInner(func, outerEnv) {
|
|
5251
6253
|
const env = forkEnv(outerEnv);
|
|
5252
6254
|
for (const p of func.params) {
|
|
5253
6255
|
if (p.pattern) {
|
|
5254
|
-
|
|
6256
|
+
const type = this.paramType(p, env);
|
|
6257
|
+
this.bindPattern(p.pattern, type, env, "widen");
|
|
6258
|
+
this.correlateDestructuring(p.pattern, type, env);
|
|
5255
6259
|
continue;
|
|
5256
6260
|
}
|
|
5257
6261
|
const id = this.bindingIdByName(p.name, p);
|
|
@@ -5262,13 +6266,16 @@ var TypeAnalyzer = class {
|
|
|
5262
6266
|
if (p.typeAnnotation) this.annotated.add(id);
|
|
5263
6267
|
}
|
|
5264
6268
|
}
|
|
5265
|
-
this.
|
|
6269
|
+
this.withVarargs(func, () => this.collectReturns(void 0, () => {
|
|
6270
|
+
this.visitBlock(func.body, env);
|
|
6271
|
+
this.checkReturnsAtAll(func, this.declaredReturns[this.declaredReturns.length - 1]);
|
|
6272
|
+
}));
|
|
5266
6273
|
}
|
|
5267
6274
|
/** Return type of calling `f` with `argTypes`. For a generic function,
|
|
5268
6275
|
* infers the type parameters from the arguments and substitutes. */
|
|
5269
|
-
callReturn(f, argTypes) {
|
|
6276
|
+
callReturn(f, argTypes, explicit) {
|
|
5270
6277
|
if (!f.typeParams?.length) return f.returns;
|
|
5271
|
-
return this.reduceType(substitute(f.returns, this.inferTypeArgs(f, argTypes)));
|
|
6278
|
+
return this.reduceType(substitute(f.returns, this.inferTypeArgs(f, argTypes, explicit)));
|
|
5272
6279
|
}
|
|
5273
6280
|
/** Infer a generic call's type arguments from the argument types.
|
|
5274
6281
|
*
|
|
@@ -5276,16 +6283,47 @@ var TypeAnalyzer = class {
|
|
|
5276
6283
|
* `1` — *except* against a parameter whose constraint is made of literal
|
|
5277
6284
|
* types, where the literal is the whole point. That is what lets
|
|
5278
6285
|
* `<K extends keyof T>(name: K) -> T[K]` pick out one property. */
|
|
5279
|
-
|
|
5280
|
-
|
|
6286
|
+
/** The type arguments a call writes out, checked for count. */
|
|
6287
|
+
explicitTypeArguments(expr, fns) {
|
|
6288
|
+
const written = expr.typeArguments;
|
|
6289
|
+
if (!written?.length) return void 0;
|
|
6290
|
+
const resolved = written.map((node) => this.resolveType(node));
|
|
6291
|
+
const most = Math.max(0, ...fns.map((f) => f.typeParams?.length ?? 0));
|
|
6292
|
+
if (this.emitDiagnostics && resolved.length > most) {
|
|
6293
|
+
this.diagnostics.push({
|
|
6294
|
+
node: written[most],
|
|
6295
|
+
message: most === 0 ? "This call takes no type arguments" : `Expected ${most} type argument${most === 1 ? "" : "s"}, got ${resolved.length}`
|
|
6296
|
+
});
|
|
6297
|
+
}
|
|
6298
|
+
return resolved;
|
|
6299
|
+
}
|
|
6300
|
+
/** `<T = Instance>`: what a call falls back to for a parameter it neither
|
|
6301
|
+
* is given nor can infer. */
|
|
6302
|
+
withTypeParamDefaults(type, generics) {
|
|
6303
|
+
if (type.kind !== "function") return type;
|
|
6304
|
+
const defaults = {};
|
|
6305
|
+
for (const generic of generics) {
|
|
6306
|
+
if (generic.default && !generic.isPack) defaults[generic.name] = this.resolveType(generic.default);
|
|
6307
|
+
}
|
|
6308
|
+
return Object.keys(defaults).length ? { ...type, typeParamDefaults: defaults } : type;
|
|
6309
|
+
}
|
|
6310
|
+
inferTypeArgs(f, argTypes, explicit) {
|
|
5281
6311
|
const subst = /* @__PURE__ */ new Map();
|
|
6312
|
+
if (explicit?.length) {
|
|
6313
|
+
(f.typeParams ?? []).forEach((name, i) => {
|
|
6314
|
+
if (explicit[i]) subst.set(name, explicit[i]);
|
|
6315
|
+
});
|
|
6316
|
+
}
|
|
6317
|
+
const vars = new Set((f.typeParams ?? []).filter((name) => !subst.has(name)));
|
|
5282
6318
|
f.params.forEach((p, i) => {
|
|
5283
6319
|
const arg = argTypes[i];
|
|
5284
6320
|
if (arg === void 0) return;
|
|
5285
6321
|
const param = p.type.kind === "typeParam" && p.type.constraint ? { ...p.type, constraint: this.reduceType(p.type.constraint) } : p.type;
|
|
5286
6322
|
unify(p.type, keepsLiterals(param) ? arg : widen(arg), vars, subst);
|
|
5287
6323
|
});
|
|
5288
|
-
for (const name of f.typeParams ?? [])
|
|
6324
|
+
for (const name of f.typeParams ?? []) {
|
|
6325
|
+
if (!subst.has(name)) subst.set(name, f.typeParamDefaults?.[name] ?? unknownType);
|
|
6326
|
+
}
|
|
5289
6327
|
return subst;
|
|
5290
6328
|
}
|
|
5291
6329
|
/** Re-infer the arguments that land on a `<const T>` parameter, keeping
|
|
@@ -5316,6 +6354,32 @@ var TypeAnalyzer = class {
|
|
|
5316
6354
|
}
|
|
5317
6355
|
return void 0;
|
|
5318
6356
|
}
|
|
6357
|
+
/** An overload set called with a union argument, one member at a time.
|
|
6358
|
+
*
|
|
6359
|
+
* One signature for the whole union is often only the catch-all:
|
|
6360
|
+
* `typeof(v)` with `v: Part | nil` accepts nothing more specific than
|
|
6361
|
+
* `typeof<T>(value: T): string`. Each member on its own picks `"Instance"`
|
|
6362
|
+
* and `"nil"`, and that union is what the call returns — whenever every
|
|
6363
|
+
* member picks a signature listed ahead of the whole union's. Otherwise
|
|
6364
|
+
* (a signature taking the union as it is, or a member nothing accepts)
|
|
6365
|
+
* this returns `undefined` and the ordinary pick stands. */
|
|
6366
|
+
distributedReturn(fns, argTypes, picked, argsFor) {
|
|
6367
|
+
if (fns.length < 2) return void 0;
|
|
6368
|
+
const position = argTypes.findIndex((t) => this.expand(t).kind === "union");
|
|
6369
|
+
if (position < 0) return void 0;
|
|
6370
|
+
const members = this.expand(argTypes[position]).types;
|
|
6371
|
+
if (members.length > 32) return void 0;
|
|
6372
|
+
const rank = (f) => ((f.typeParams?.length ?? 0) > 0 ? fns.length : 0) + fns.indexOf(f);
|
|
6373
|
+
const limit = picked ? rank(picked) : Infinity;
|
|
6374
|
+
const results = [];
|
|
6375
|
+
for (const member of members) {
|
|
6376
|
+
const args = argTypes.map((t, i) => i === position ? member : t);
|
|
6377
|
+
const chosen = this.pickOverload(fns, args, (f) => argsFor(f, args));
|
|
6378
|
+
if (!chosen || rank(chosen) >= limit) return void 0;
|
|
6379
|
+
results.push(this.callReturn(chosen, argsFor(chosen, args)));
|
|
6380
|
+
}
|
|
6381
|
+
return union(results);
|
|
6382
|
+
}
|
|
5319
6383
|
/** Can this signature be called with these argument types? The signature's
|
|
5320
6384
|
* own type parameters stand for what the call would infer, so each is
|
|
5321
6385
|
* checked only against its constraint — `<K extends keyof Services>`
|
|
@@ -5351,7 +6415,7 @@ var TypeAnalyzer = class {
|
|
|
5351
6415
|
for (const child of Object.values(value)) walk(child);
|
|
5352
6416
|
};
|
|
5353
6417
|
for (const p of f.params) if (containsTypeParam(p.type)) walk(p.type);
|
|
5354
|
-
return f.params.map((p) => substitute(p.type, bounds));
|
|
6418
|
+
return f.params.map((p) => this.reduceType(substitute(p.type, bounds)));
|
|
5355
6419
|
}
|
|
5356
6420
|
/** Record what each written argument is expected to be — see
|
|
5357
6421
|
* `TypeAnalysis.expectedTypeOf`. */
|
|
@@ -5368,6 +6432,28 @@ var TypeAnalyzer = class {
|
|
|
5368
6432
|
}
|
|
5369
6433
|
/** No signature accepts the call, and the argument count is not the
|
|
5370
6434
|
* problem: say which argument is wrong, the way TypeScript does. */
|
|
6435
|
+
/** Check what was written against the parameters as this call's own type
|
|
6436
|
+
* arguments make them read: `pick("Bones", "C")` is wrong only once `P`
|
|
6437
|
+
* is known to be `"Bones"`. Picking the overload goes by each parameter's
|
|
6438
|
+
* constraint, which is deliberately looser than that. */
|
|
6439
|
+
checkInferredArguments(call, written, f, argTypes, self) {
|
|
6440
|
+
if (!this.emitDiagnostics || !f.typeParams?.length) return;
|
|
6441
|
+
const subst = this.inferTypeArgs(f, [...argTypes]);
|
|
6442
|
+
for (const bound of subst.values()) if (bound.kind === "unknown") return;
|
|
6443
|
+
for (let i = 0; i < f.params.length; i++) {
|
|
6444
|
+
const arg = argTypes[i];
|
|
6445
|
+
const declared = f.params[i].type;
|
|
6446
|
+
if (arg === void 0 || !containsTypeParam(declared)) continue;
|
|
6447
|
+
const expected = this.reduceType(substitute(declared, subst));
|
|
6448
|
+
if (containsTypeParam(expected) || expected.kind === "any" || expected.kind === "unknown") continue;
|
|
6449
|
+
if (isAssignable(arg, expected) || isAssignable(widen(arg), expected)) continue;
|
|
6450
|
+
this.diagnostics.push({
|
|
6451
|
+
node: written[i - self] ?? call,
|
|
6452
|
+
message: `Argument of type '${formatType(arg)}' is not assignable to parameter of type '${briefType(expected)}'`
|
|
6453
|
+
});
|
|
6454
|
+
return;
|
|
6455
|
+
}
|
|
6456
|
+
}
|
|
5371
6457
|
reportArguments(call, written, fns, argsFor, selfOf) {
|
|
5372
6458
|
if (!this.emitDiagnostics) return;
|
|
5373
6459
|
if (fns.length > 1) {
|
|
@@ -5437,9 +6523,33 @@ var TypeAnalyzer = class {
|
|
|
5437
6523
|
});
|
|
5438
6524
|
return false;
|
|
5439
6525
|
}
|
|
6526
|
+
/** An overload set's implementation handles every signature, so a bare
|
|
6527
|
+
* parameter of it holds whatever those signatures allow there:
|
|
6528
|
+
* `function f(Stat, ...)` under 36 `Stat: "..."` signatures is the union
|
|
6529
|
+
* of all 36. TypeScript leaves such a parameter `any`; this says what it
|
|
6530
|
+
* can actually be. An annotation, a pattern or a default still wins. */
|
|
6531
|
+
paramsFromSignatures(func, signatures) {
|
|
6532
|
+
if (!signatures?.length) return;
|
|
6533
|
+
const resolved = signatures.map((sig) => this.signatureToFnType(sig));
|
|
6534
|
+
func.params.forEach((param, i) => {
|
|
6535
|
+
if (param.typeAnnotation || param.pattern || param.default) return;
|
|
6536
|
+
const candidates = [];
|
|
6537
|
+
for (const signature of resolved) {
|
|
6538
|
+
if (signature.kind !== "function") continue;
|
|
6539
|
+
const own = signature.params[i];
|
|
6540
|
+
if (own) candidates.push(own.optional ? optional(own.type) : own.type);
|
|
6541
|
+
else if (signature.varargs) candidates.push(signature.varargs);
|
|
6542
|
+
}
|
|
6543
|
+
if (candidates.length) this.contextualParams.set(param, union(candidates));
|
|
6544
|
+
});
|
|
6545
|
+
}
|
|
5440
6546
|
signatureToFnType(sig) {
|
|
5441
6547
|
const names = sig.generics.map((g) => g.name);
|
|
5442
|
-
|
|
6548
|
+
const record = (type) => {
|
|
6549
|
+
this.typeOfTypeNode.set(sig, type);
|
|
6550
|
+
return type;
|
|
6551
|
+
};
|
|
6552
|
+
return record(this.withTypeParams(sig.generics, () => {
|
|
5443
6553
|
const params = sig.params.map((p) => ({
|
|
5444
6554
|
name: p.pattern ? void 0 : p.name,
|
|
5445
6555
|
type: this.paramType(p, /* @__PURE__ */ new Map()),
|
|
@@ -5452,7 +6562,7 @@ var TypeAnalyzer = class {
|
|
|
5452
6562
|
names,
|
|
5453
6563
|
this.resolvePredicate(sig.predicate, params)
|
|
5454
6564
|
);
|
|
5455
|
-
});
|
|
6565
|
+
}));
|
|
5456
6566
|
}
|
|
5457
6567
|
/** Turn a parsed `v is T` / `asserts v` annotation into a `TypePredicate`,
|
|
5458
6568
|
* resolving the named parameter to its index. A guard naming a parameter
|
|
@@ -5497,8 +6607,11 @@ var TypeAnalyzer = class {
|
|
|
5497
6607
|
} else if (func.predicate) {
|
|
5498
6608
|
returns = booleanType;
|
|
5499
6609
|
} else {
|
|
5500
|
-
|
|
5501
|
-
returns = this.
|
|
6610
|
+
const collected = [];
|
|
6611
|
+
returns = this.withVarargs(func, () => this.silently(() => {
|
|
6612
|
+
this.collectReturns(collected, () => this.preVisitBody(func.body, bodyEnv));
|
|
6613
|
+
return collected.length ? union(collected) : this.inferReturnType(func.body, bodyEnv);
|
|
6614
|
+
}));
|
|
5502
6615
|
}
|
|
5503
6616
|
return fn(
|
|
5504
6617
|
params,
|
|
@@ -5509,6 +6622,29 @@ var TypeAnalyzer = class {
|
|
|
5509
6622
|
);
|
|
5510
6623
|
});
|
|
5511
6624
|
}
|
|
6625
|
+
/** Where the return types of the function being walked are collected, so
|
|
6626
|
+
* each is read where it is written — inside the branch that narrowed it —
|
|
6627
|
+
* rather than in whatever state the body ends in. */
|
|
6628
|
+
returnTypes;
|
|
6629
|
+
collectReturns(into, body) {
|
|
6630
|
+
const previous = this.returnTypes;
|
|
6631
|
+
this.returnTypes = into;
|
|
6632
|
+
try {
|
|
6633
|
+
return body();
|
|
6634
|
+
} finally {
|
|
6635
|
+
this.returnTypes = previous;
|
|
6636
|
+
}
|
|
6637
|
+
}
|
|
6638
|
+
/** Run something without reporting what it finds. */
|
|
6639
|
+
silently(body) {
|
|
6640
|
+
const wasEmitting = this.emitDiagnostics;
|
|
6641
|
+
this.emitDiagnostics = false;
|
|
6642
|
+
try {
|
|
6643
|
+
return body();
|
|
6644
|
+
} finally {
|
|
6645
|
+
this.emitDiagnostics = wasEmitting;
|
|
6646
|
+
}
|
|
6647
|
+
}
|
|
5512
6648
|
/** Populate binding types for a function body without reporting anything,
|
|
5513
6649
|
* purely so an un-annotated return type can see its own locals. Bounded:
|
|
5514
6650
|
* nested functions stop pre-visiting after a couple of levels, since the
|
|
@@ -5525,6 +6661,157 @@ var TypeAnalyzer = class {
|
|
|
5525
6661
|
this.preVisitDepth--;
|
|
5526
6662
|
}
|
|
5527
6663
|
}
|
|
6664
|
+
/** The `[key, value]` pairs iterating a record yields, one per property —
|
|
6665
|
+
* for `pairs(t)`, `next, t` and `for k, v in t` over an object type with
|
|
6666
|
+
* no indexer. `undefined` for anything else (an array, a dictionary, an
|
|
6667
|
+
* iterator function), whose keys have no names to list. */
|
|
6668
|
+
iterationRows(iterNode, iterType) {
|
|
6669
|
+
let source;
|
|
6670
|
+
if (iterNode?.type === "CallExpression" && iterNode.callee.type === "Identifier" && iterNode.arguments[0]) {
|
|
6671
|
+
if (iterNode.callee.name !== "pairs" && iterNode.callee.name !== "next") return void 0;
|
|
6672
|
+
source = this.typeOf.get(iterNode.arguments[0]);
|
|
6673
|
+
} else {
|
|
6674
|
+
source = iterType;
|
|
6675
|
+
}
|
|
6676
|
+
const t = source && this.expand(source);
|
|
6677
|
+
if (!t || t.kind !== "object" || t.class || t.indexer || !t.properties.size) return void 0;
|
|
6678
|
+
return [...t.properties].map(([name, property]) => [
|
|
6679
|
+
literal(name),
|
|
6680
|
+
property.optional ? optional(property.type) : property.type
|
|
6681
|
+
]);
|
|
6682
|
+
}
|
|
6683
|
+
/** Bindings that hold parts of one value: the key and value of a `pairs`
|
|
6684
|
+
* row, or the names destructured from one union member. By flow key.
|
|
6685
|
+
* Which rows are still possible is itself flow state, kept in `env` under
|
|
6686
|
+
* `group` as a union of tuples, so it narrows and merges like any type. */
|
|
6687
|
+
correlations = /* @__PURE__ */ new Map();
|
|
6688
|
+
correlateBindings(env, ids, rows) {
|
|
6689
|
+
const keys = ids.map(bindKey);
|
|
6690
|
+
const group = `rows(${keys.join(",")})`;
|
|
6691
|
+
keys.forEach((key, index) => this.correlations.set(key, { group, index, keys, rows }));
|
|
6692
|
+
env.set(group, union(rows.map((row) => tuple(row))));
|
|
6693
|
+
}
|
|
6694
|
+
/** `key` was just narrowed to `narrowed` in `env`: narrow that column of
|
|
6695
|
+
* every row, drop the rows it rules out, and give the other bindings what
|
|
6696
|
+
* the remaining rows hold. */
|
|
6697
|
+
correlate(env, key, narrowed) {
|
|
6698
|
+
const entry = this.correlations.get(key);
|
|
6699
|
+
if (!entry) return;
|
|
6700
|
+
const state = env.get(entry.group);
|
|
6701
|
+
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;
|
|
6702
|
+
const kept = [];
|
|
6703
|
+
for (const row of current) {
|
|
6704
|
+
const column = narrowTo(row[entry.index], narrowed);
|
|
6705
|
+
if (column.kind !== "never") kept.push(row.map((t, i) => i === entry.index ? column : t));
|
|
6706
|
+
}
|
|
6707
|
+
env.set(entry.group, kept.length ? union(kept.map((row) => tuple(row))) : neverType);
|
|
6708
|
+
entry.keys.forEach((other, j) => {
|
|
6709
|
+
if (j !== entry.index) env.set(other, kept.length ? union(kept.map((row) => row[j])) : neverType);
|
|
6710
|
+
});
|
|
6711
|
+
}
|
|
6712
|
+
/** Stop correlating a binding once it is assigned: its value no longer
|
|
6713
|
+
* comes from the row. */
|
|
6714
|
+
uncorrelate(id) {
|
|
6715
|
+
const entry = this.correlations.get(bindKey(id));
|
|
6716
|
+
if (entry) for (const key of entry.keys) this.correlations.delete(key);
|
|
6717
|
+
}
|
|
6718
|
+
/** `const { kind, payload } = action` over a union of objects: one row per
|
|
6719
|
+
* member, so testing `kind` narrows `payload` (TypeScript's destructured
|
|
6720
|
+
* discriminated unions). Only plain `name` / `key: name` properties take
|
|
6721
|
+
* part. */
|
|
6722
|
+
/** Names that denote one and the same value: `const c = player.Character`
|
|
6723
|
+
* makes `c` and `player.Character` two spellings of one reference. Kept
|
|
6724
|
+
* as an undirected graph of flow keys. */
|
|
6725
|
+
refAliases = /* @__PURE__ */ new Map();
|
|
6726
|
+
/** `const c = a.b` — `c` cannot be re-bound and the path was read once, so
|
|
6727
|
+
* a test of either name is a test of the same value. Only property paths
|
|
6728
|
+
* take part: `const c = other` would tie `c` to a name that may itself be
|
|
6729
|
+
* assigned a different value later. */
|
|
6730
|
+
aliasReference(target, init) {
|
|
6731
|
+
if (target.type !== "IdentifierPattern" || !init) return;
|
|
6732
|
+
const source = unwrapParens(init);
|
|
6733
|
+
if (source.type !== "MemberExpression" && source.type !== "IndexExpression") return;
|
|
6734
|
+
const path = this.refKeyOf(source);
|
|
6735
|
+
const id = this.bindingIdByName(target.name, target);
|
|
6736
|
+
if (path === void 0 || id === void 0) return;
|
|
6737
|
+
const name = bindKey(id);
|
|
6738
|
+
for (const [a, b] of [[name, path], [path, name]]) {
|
|
6739
|
+
const set = this.refAliases.get(a) ?? /* @__PURE__ */ new Set();
|
|
6740
|
+
set.add(b);
|
|
6741
|
+
this.refAliases.set(a, set);
|
|
6742
|
+
}
|
|
6743
|
+
}
|
|
6744
|
+
/** A reference was narrowed: give every other spelling of the same value
|
|
6745
|
+
* the same news. Walks the alias graph, so a path with two names told by
|
|
6746
|
+
* one of them reaches the other. Each alias keeps whatever it already
|
|
6747
|
+
* knew — the narrowing only ever cuts the type further down. */
|
|
6748
|
+
propagateAliases(env, into, key, narrowed) {
|
|
6749
|
+
if (!this.refAliases.size) return;
|
|
6750
|
+
const seen = /* @__PURE__ */ new Set([key]);
|
|
6751
|
+
const queue = [[key, narrowed]];
|
|
6752
|
+
const learn = (at, t) => {
|
|
6753
|
+
seen.add(at);
|
|
6754
|
+
this.setRef(into, at, t);
|
|
6755
|
+
this.correlate(into, at, t);
|
|
6756
|
+
queue.push([at, t]);
|
|
6757
|
+
};
|
|
6758
|
+
for (let at = 0; at < queue.length; at++) {
|
|
6759
|
+
const [from, t] = queue[at];
|
|
6760
|
+
for (const other of this.refAliases.get(from) ?? []) {
|
|
6761
|
+
if (seen.has(other)) continue;
|
|
6762
|
+
const current = into.get(other) ?? env.get(other) ?? this.declaredAtRef(other);
|
|
6763
|
+
const next = narrowTo(current, t);
|
|
6764
|
+
learn(other, next.kind === "never" ? t : next);
|
|
6765
|
+
for (let child = other, value = into.get(other); ; ) {
|
|
6766
|
+
const cut = child.lastIndexOf(".");
|
|
6767
|
+
if (cut <= 0) break;
|
|
6768
|
+
const parent = child.slice(0, cut);
|
|
6769
|
+
if (seen.has(parent)) break;
|
|
6770
|
+
const had = into.get(parent) ?? env.get(parent) ?? this.declaredAtRef(parent);
|
|
6771
|
+
value = this.filterByProperty(had, child.slice(cut + 1), value);
|
|
6772
|
+
learn(parent, value);
|
|
6773
|
+
child = parent;
|
|
6774
|
+
}
|
|
6775
|
+
}
|
|
6776
|
+
}
|
|
6777
|
+
}
|
|
6778
|
+
/** `const path = paths[stat]` where `stat` is one of several keys: which
|
|
6779
|
+
* value came back says which key was asked for. Testing the value then
|
|
6780
|
+
* narrows the key — the `else` of `if path then` leaves exactly the keys
|
|
6781
|
+
* the table does not have. */
|
|
6782
|
+
correlateIndexed(target, init, env) {
|
|
6783
|
+
if (target.type !== "IdentifierPattern" || !init) return;
|
|
6784
|
+
const source = unwrapParens(init);
|
|
6785
|
+
if (source.type !== "IndexExpression" || source.index.type !== "Identifier") return;
|
|
6786
|
+
const valueId = this.bindingIdByName(target.name, target);
|
|
6787
|
+
const keyId = this.bindingIdOf(source.index);
|
|
6788
|
+
if (valueId === void 0 || keyId === void 0) return;
|
|
6789
|
+
const key = this.expand(this.currentType(keyId, env));
|
|
6790
|
+
if (key.kind !== "union" || key.types.length < 2 || key.types.length > 64) return;
|
|
6791
|
+
if (!key.types.every((m) => m.kind === "literal")) return;
|
|
6792
|
+
const object = this.expand(this.typeOf.get(source.object) ?? unknownType);
|
|
6793
|
+
if (object.kind !== "object") return;
|
|
6794
|
+
this.correlateBindings(env, [keyId, valueId], key.types.map((m) => [m, this.indexedType(object, m)]));
|
|
6795
|
+
}
|
|
6796
|
+
correlateDestructuring(pattern, source, env) {
|
|
6797
|
+
if (pattern.type !== "ObjectPattern") return;
|
|
6798
|
+
const members = this.expand(source);
|
|
6799
|
+
if (members.kind !== "union") return;
|
|
6800
|
+
const objects = members.types.map((m) => this.expand(m));
|
|
6801
|
+
if (objects.length < 2 || objects.some((m) => m.kind !== "object")) return;
|
|
6802
|
+
const ids = [];
|
|
6803
|
+
const names = [];
|
|
6804
|
+
for (const property of pattern.properties) {
|
|
6805
|
+
if (property.computed || property.default || property.value.type !== "IdentifierPattern") return;
|
|
6806
|
+
const name = property.key.type === "Identifier" ? property.key.name : property.key.type === "StringLiteral" ? property.key.value : void 0;
|
|
6807
|
+
const id = this.bindingIdByName(property.value.name, property.value);
|
|
6808
|
+
if (name === void 0 || id === void 0) return;
|
|
6809
|
+
ids.push(id);
|
|
6810
|
+
names.push(name);
|
|
6811
|
+
}
|
|
6812
|
+
if (ids.length < 2) return;
|
|
6813
|
+
this.correlateBindings(env, ids, objects.map((member) => names.map((name) => this.propertyType(member, name))));
|
|
6814
|
+
}
|
|
5528
6815
|
/** `(keyType, valueType)` yielded by a generic-for iterator. Handles
|
|
5529
6816
|
* `ipairs`/`pairs`/`next(t)` and Luau generalized iteration (`for … in t`).
|
|
5530
6817
|
* `varCount` is how many loop variables were written. */
|
|
@@ -5589,6 +6876,18 @@ var TypeAnalyzer = class {
|
|
|
5589
6876
|
if (init.type === "ArrayExpression") return isAssignable(this.inferArray(init, env, true), declared);
|
|
5590
6877
|
return false;
|
|
5591
6878
|
}
|
|
6879
|
+
/** `{ a, ...rest }`: what `rest` holds — the value without the properties
|
|
6880
|
+
* the pattern already took. */
|
|
6881
|
+
withoutKeys(raw, properties) {
|
|
6882
|
+
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] : []));
|
|
6883
|
+
if (!taken.size) return raw;
|
|
6884
|
+
const t = this.expand(raw);
|
|
6885
|
+
if (t.kind === "union") return union(t.types.map((m) => this.withoutKeys(m, properties)));
|
|
6886
|
+
if (t.kind !== "object") return raw;
|
|
6887
|
+
const kept = [...t.properties].filter(([name]) => !taken.has(name));
|
|
6888
|
+
if (kept.length === t.properties.size) return raw;
|
|
6889
|
+
return objectType(kept, t.indexer, t.frozen);
|
|
6890
|
+
}
|
|
5592
6891
|
/** Fold a destructuring default (`{ a = 1 }`) into the property's type:
|
|
5593
6892
|
* the default applies when the source value is missing/`nil`. */
|
|
5594
6893
|
withDefault(base, def, env) {
|
|
@@ -5620,7 +6919,7 @@ var TypeAnalyzer = class {
|
|
|
5620
6919
|
const pt = key !== void 0 ? this.propertyType(valueType, key) : unknownType;
|
|
5621
6920
|
this.reassignPattern(p.value, this.withDefault(pt, p.default, env), env);
|
|
5622
6921
|
}
|
|
5623
|
-
if (target.rest) this.reassignPattern(target.rest, valueType, env);
|
|
6922
|
+
if (target.rest) this.reassignPattern(target.rest, this.withoutKeys(valueType, target.properties), env);
|
|
5624
6923
|
return;
|
|
5625
6924
|
}
|
|
5626
6925
|
case "ArrayPattern": {
|
|
@@ -5655,7 +6954,7 @@ var TypeAnalyzer = class {
|
|
|
5655
6954
|
const propType = key !== void 0 ? this.propertyType(valueType, key) : unknownType;
|
|
5656
6955
|
this.bindPattern(p.value, this.withDefault(propType, p.default, env), env, mode);
|
|
5657
6956
|
}
|
|
5658
|
-
if (target.rest) this.bindPattern(target.rest, valueType, env, mode);
|
|
6957
|
+
if (target.rest) this.bindPattern(target.rest, this.withoutKeys(valueType, target.properties), env, mode);
|
|
5659
6958
|
return;
|
|
5660
6959
|
}
|
|
5661
6960
|
case "ArrayPattern": {
|
|
@@ -5697,13 +6996,34 @@ var TypeAnalyzer = class {
|
|
|
5697
6996
|
this.resolvingAliases.delete(t.name);
|
|
5698
6997
|
}
|
|
5699
6998
|
}
|
|
6999
|
+
/** `names:filter(f)`, `text:trim()` — the methods arrays and strings have.
|
|
7000
|
+
* They are written in the prelude as `ArrayMethods<T>` and
|
|
7001
|
+
* `StringMethods`, so a file (or a type library) that declares one of
|
|
7002
|
+
* those names again replaces the whole set, and nothing here is a special
|
|
7003
|
+
* case in the analyzer. The build lowers each call to a plain function. */
|
|
7004
|
+
builtInMethod(t, name) {
|
|
7005
|
+
const element = t.kind === "array" ? t.element : t.kind === "tuple" ? union(t.elements) : void 0;
|
|
7006
|
+
const methodTable = element !== void 0 ? "ArrayMethods" : t.kind === "primitive" && t.name === "string" || t.kind === "literal" && t.base === "string" ? "StringMethods" : void 0;
|
|
7007
|
+
const def = methodTable === void 0 ? void 0 : this.aliasDefs.get(methodTable);
|
|
7008
|
+
if (!def || def.class) return void 0;
|
|
7009
|
+
const table = this.expand(this.instantiateAlias(def, element !== void 0 ? [element] : []));
|
|
7010
|
+
const parts = table.kind === "intersection" ? table.types.map((m) => this.expand(m)) : [table];
|
|
7011
|
+
for (let i = parts.length - 1; i >= 0; i--) {
|
|
7012
|
+
const part = parts[i];
|
|
7013
|
+
const property = part.kind === "object" ? part.properties.get(name) : void 0;
|
|
7014
|
+
if (property) return property.type;
|
|
7015
|
+
}
|
|
7016
|
+
return void 0;
|
|
7017
|
+
}
|
|
5700
7018
|
propertyType(raw, name) {
|
|
5701
|
-
const t = this.expand(raw);
|
|
7019
|
+
const t = this.deferredAccess(this.expand(raw));
|
|
5702
7020
|
if (t.kind === "object") {
|
|
5703
7021
|
const p = t.properties.get(name);
|
|
5704
7022
|
if (p) return p.optional ? optional(p.type) : p.type;
|
|
5705
7023
|
if (t.indexer) return t.indexer.value;
|
|
5706
7024
|
}
|
|
7025
|
+
const built = this.builtInMethod(t, name);
|
|
7026
|
+
if (built) return built;
|
|
5707
7027
|
if (t.kind === "union") return union(t.types.map((m) => this.propertyType(m, name)));
|
|
5708
7028
|
if (t.kind === "intersection") {
|
|
5709
7029
|
const parts = t.types.map((m) => this.propertyType(m, name)).filter((p) => p.kind !== "unknown");
|
|
@@ -5721,21 +7041,37 @@ var TypeAnalyzer = class {
|
|
|
5721
7041
|
const t = this.expand(raw);
|
|
5722
7042
|
if (t.kind === "any") return anyType;
|
|
5723
7043
|
if (t.kind === "union") return union(t.types.map((m) => this.indexedType(m, idx)));
|
|
5724
|
-
|
|
5725
|
-
if (
|
|
7044
|
+
const index = this.expand(idx);
|
|
7045
|
+
if (index.kind === "union") return union(index.types.map((m) => this.indexedType(t, m)));
|
|
7046
|
+
if (t.kind === "difference") return this.indexedType(t.base, index);
|
|
7047
|
+
if (t.kind === "typeParam" && t.constraint) return this.indexedType(t.constraint, index);
|
|
5726
7048
|
if (t.kind === "array") return t.element;
|
|
5727
7049
|
if (t.kind === "tuple") {
|
|
5728
|
-
if (
|
|
5729
|
-
return t.elements[
|
|
7050
|
+
if (index.kind === "literal" && typeof index.value === "number") {
|
|
7051
|
+
return t.elements[index.value - 1] ?? nilType;
|
|
5730
7052
|
}
|
|
5731
7053
|
return union(t.elements);
|
|
5732
7054
|
}
|
|
5733
7055
|
if (t.kind === "object") {
|
|
5734
|
-
if (
|
|
7056
|
+
if (index.kind === "literal" && typeof index.value === "string") {
|
|
7057
|
+
const property = t.properties.get(index.value);
|
|
7058
|
+
if (property) return property.optional ? optional(property.type) : property.type;
|
|
7059
|
+
if (t.indexer && isAssignable(index, t.indexer.key)) return t.indexer.value;
|
|
7060
|
+
return nilType;
|
|
7061
|
+
}
|
|
7062
|
+
if (containsTypeParam(index)) return this.reduceType({ kind: "indexedAccess", objectType: t, indexType: index });
|
|
5735
7063
|
if (t.indexer) return t.indexer.value;
|
|
5736
7064
|
}
|
|
5737
7065
|
return unknownType;
|
|
5738
7066
|
}
|
|
7067
|
+
/** What a deferred `T[K]` can be: every property its index could name.
|
|
7068
|
+
* Reading a member of one, or calling it, sees that. */
|
|
7069
|
+
deferredAccess(t) {
|
|
7070
|
+
if (t.kind !== "indexedAccess") return t;
|
|
7071
|
+
const index = t.indexType.kind === "typeParam" && t.indexType.constraint ? t.indexType.constraint : t.indexType;
|
|
7072
|
+
if (containsTypeParam(index)) return unknownType;
|
|
7073
|
+
return this.accessType(t.objectType, index);
|
|
7074
|
+
}
|
|
5739
7075
|
elementType(raw, index) {
|
|
5740
7076
|
const t = this.expand(raw);
|
|
5741
7077
|
if (t.kind === "array") return t.element;
|
|
@@ -5768,7 +7104,11 @@ var TypeAnalyzer = class {
|
|
|
5768
7104
|
for (const part of expr.parts) if (part.kind === "expression") this.infer(part.expression, env);
|
|
5769
7105
|
return stringType;
|
|
5770
7106
|
}
|
|
7107
|
+
// `...` holds what the function declared it takes.
|
|
5771
7108
|
case "VarargExpression":
|
|
7109
|
+
return this.varargs[this.varargs.length - 1] ?? anyType;
|
|
7110
|
+
// Broken syntax is reported by the parser; nothing more to say.
|
|
7111
|
+
case "ErrorExpression":
|
|
5772
7112
|
return anyType;
|
|
5773
7113
|
case "Identifier": {
|
|
5774
7114
|
const id = this.bindingIdOf(expr);
|
|
@@ -5796,12 +7136,20 @@ var TypeAnalyzer = class {
|
|
|
5796
7136
|
case "SatisfiesExpression": {
|
|
5797
7137
|
const declared = this.resolveType(expr.typeAnnotation);
|
|
5798
7138
|
this.applyContext(expr.expression, declared);
|
|
5799
|
-
|
|
5800
|
-
|
|
7139
|
+
if (declared.kind === "any") return this.infer(expr.expression, env);
|
|
7140
|
+
const written = unwrapParens(expr.expression);
|
|
7141
|
+
const fresh = written.type === "TableExpression" || written.type === "ArrayExpression";
|
|
7142
|
+
const narrow = fresh ? this.inferAsConst(expr.expression, env) : this.infer(expr.expression, env);
|
|
7143
|
+
const actual = fresh ? this.keepContextualLiterals(narrow, declared) : narrow;
|
|
7144
|
+
this.typeOf.set(expr.expression, actual);
|
|
7145
|
+
if (!this.emitDiagnostics) return actual;
|
|
7146
|
+
if (!isAssignable(narrow, declared) && !isAssignable(actual, declared)) {
|
|
5801
7147
|
this.diagnostics.push({
|
|
5802
7148
|
node: expr,
|
|
5803
|
-
message: `Type '${formatType(actual)}' does not satisfy '${formatType(declared)}'`
|
|
7149
|
+
message: `Type '${formatType(actual)}' does not satisfy the expected type '${formatType(declared)}'`
|
|
5804
7150
|
});
|
|
7151
|
+
} else {
|
|
7152
|
+
this.reportExcessProperties(expr.expression, declared);
|
|
5805
7153
|
}
|
|
5806
7154
|
return actual;
|
|
5807
7155
|
}
|
|
@@ -5835,6 +7183,10 @@ var TypeAnalyzer = class {
|
|
|
5835
7183
|
}
|
|
5836
7184
|
const l = this.infer(expr.left, env);
|
|
5837
7185
|
const r = this.infer(expr.right, env);
|
|
7186
|
+
if (op === "==" || op === "~=") {
|
|
7187
|
+
if (unwrapParens(expr.right).type === "StringLiteral") this.expectedTypeOf.set(unwrapParens(expr.right), l);
|
|
7188
|
+
if (unwrapParens(expr.left).type === "StringLiteral") this.expectedTypeOf.set(unwrapParens(expr.left), r);
|
|
7189
|
+
}
|
|
5838
7190
|
switch (op) {
|
|
5839
7191
|
case "..":
|
|
5840
7192
|
return this.operatorResult(expr, op, l, r) ?? stringType;
|
|
@@ -5857,57 +7209,25 @@ var TypeAnalyzer = class {
|
|
|
5857
7209
|
return union([l, r]);
|
|
5858
7210
|
}
|
|
5859
7211
|
case "MemberExpression": {
|
|
5860
|
-
const obj = this.
|
|
7212
|
+
const { type: obj, shortCircuits } = this.chainObject(expr, expr.object, env);
|
|
5861
7213
|
const key = this.refKeyOf(expr);
|
|
5862
7214
|
const narrowed = key === void 0 ? void 0 : env.get(key);
|
|
5863
|
-
return narrowed ?? this.propertyType(obj, expr.property.name);
|
|
7215
|
+
return this.chainResult(expr, narrowed ?? this.propertyType(obj, expr.property.name), shortCircuits);
|
|
5864
7216
|
}
|
|
5865
7217
|
case "IndexExpression": {
|
|
5866
|
-
const obj = this.
|
|
7218
|
+
const { type: obj, shortCircuits } = this.chainObject(expr, expr.object, env);
|
|
5867
7219
|
const idx = this.infer(expr.index, env);
|
|
5868
7220
|
const key = this.refKeyOf(expr);
|
|
5869
7221
|
const narrowed = key === void 0 ? void 0 : env.get(key);
|
|
5870
|
-
return narrowed ?? this.indexedType(obj, idx);
|
|
7222
|
+
return this.chainResult(expr, narrowed ?? this.indexedType(obj, idx), shortCircuits);
|
|
5871
7223
|
}
|
|
5872
7224
|
case "CallExpression": {
|
|
5873
|
-
const callee = this.
|
|
5874
|
-
|
|
5875
|
-
const expected = this.expectedArguments(expr.arguments, fns, () => 0);
|
|
5876
|
-
expr.arguments.forEach((a, i) => this.applyContext(a, expected[i]));
|
|
5877
|
-
const argTypes = expr.arguments.map((a) => this.infer(a, env));
|
|
5878
|
-
if (fns.length) {
|
|
5879
|
-
this.recordExpected(expr.arguments, fns, () => 0);
|
|
5880
|
-
const arityFits = this.checkArity(expr, fns, argTypes.length, 0);
|
|
5881
|
-
const picked = this.pickOverload(fns, argTypes);
|
|
5882
|
-
if (picked) {
|
|
5883
|
-
return this.callReturn(picked, this.constArgs(picked, expr.arguments, argTypes, env));
|
|
5884
|
-
}
|
|
5885
|
-
if (arityFits) this.reportArguments(expr, expr.arguments, fns, () => argTypes, () => 0);
|
|
5886
|
-
return union(fns.map((f) => this.callReturn(f, argTypes)));
|
|
5887
|
-
}
|
|
5888
|
-
return callee.kind === "any" ? anyType : unknownType;
|
|
7225
|
+
const { type: callee, shortCircuits } = this.chainObject(expr, expr.callee, env);
|
|
7226
|
+
return this.chainResult(expr, this.inferCall(expr, callee, env), shortCircuits);
|
|
5889
7227
|
}
|
|
5890
7228
|
case "MethodCallExpression": {
|
|
5891
|
-
const objType = this.
|
|
5892
|
-
|
|
5893
|
-
const expected = this.expectedArguments(expr.arguments, fns, (f) => this.takesSelf(f) ? 1 : 0);
|
|
5894
|
-
expr.arguments.forEach((a, i) => this.applyContext(a, expected[i]));
|
|
5895
|
-
const argTypes = expr.arguments.map((a) => this.infer(a, env));
|
|
5896
|
-
if (fns.length) {
|
|
5897
|
-
const withSelf = (f) => this.takesSelf(f) ? [objType, ...argTypes] : argTypes;
|
|
5898
|
-
const selfOf = (f) => this.takesSelf(f) ? 1 : 0;
|
|
5899
|
-
this.recordExpected(expr.arguments, fns, selfOf);
|
|
5900
|
-
const arityFits = this.checkArity(expr, fns, argTypes.length, this.takesSelf(fns[0]) ? 1 : 0);
|
|
5901
|
-
const picked = this.pickOverload(fns, argTypes, withSelf);
|
|
5902
|
-
if (picked) {
|
|
5903
|
-
const self = this.takesSelf(picked) ? 1 : 0;
|
|
5904
|
-
const written = this.constArgs(picked, expr.arguments, argTypes, env, self);
|
|
5905
|
-
return this.callReturn(picked, this.takesSelf(picked) ? [objType, ...written] : written);
|
|
5906
|
-
}
|
|
5907
|
-
if (arityFits) this.reportArguments(expr, expr.arguments, fns, withSelf, selfOf);
|
|
5908
|
-
return union(fns.map((f) => this.callReturn(f, withSelf(f))));
|
|
5909
|
-
}
|
|
5910
|
-
return objType.kind === "any" ? anyType : unknownType;
|
|
7229
|
+
const { type: objType, shortCircuits } = this.chainObject(expr, expr.object, env);
|
|
7230
|
+
return this.chainResult(expr, this.inferMethodCall(expr, objType, env), shortCircuits);
|
|
5911
7231
|
}
|
|
5912
7232
|
case "IfElseExpression": {
|
|
5913
7233
|
const branches = [];
|
|
@@ -5923,6 +7243,123 @@ var TypeAnalyzer = class {
|
|
|
5923
7243
|
}
|
|
5924
7244
|
}
|
|
5925
7245
|
}
|
|
7246
|
+
inferCall(expr, callee, env) {
|
|
7247
|
+
const fns = this.overloadsOf(callee);
|
|
7248
|
+
const explicit = this.explicitTypeArguments(expr, fns);
|
|
7249
|
+
const expected = this.expectedArguments(expr.arguments, fns, () => 0);
|
|
7250
|
+
expr.arguments.forEach((a, i) => this.applyContext(a, expected[i]));
|
|
7251
|
+
const argTypes = expr.arguments.map((a) => this.infer(a, env));
|
|
7252
|
+
if (fns.length) {
|
|
7253
|
+
this.recordExpected(expr.arguments, fns, () => 0);
|
|
7254
|
+
const arityFits = this.checkArity(expr, fns, argTypes.length, 0);
|
|
7255
|
+
const picked = this.pickOverload(fns, argTypes);
|
|
7256
|
+
const distributed = this.distributedReturn(fns, argTypes, picked, (_, args) => args);
|
|
7257
|
+
if (distributed) return distributed;
|
|
7258
|
+
if (picked) {
|
|
7259
|
+
this.checkInferredArguments(expr, expr.arguments, picked, argTypes, 0);
|
|
7260
|
+
return this.callReturn(picked, this.constArgs(picked, expr.arguments, argTypes, env), explicit);
|
|
7261
|
+
}
|
|
7262
|
+
if (arityFits) this.reportArguments(expr, expr.arguments, fns, () => argTypes, () => 0);
|
|
7263
|
+
return union(fns.map((f) => this.callReturn(f, argTypes, explicit)));
|
|
7264
|
+
}
|
|
7265
|
+
return callee.kind === "any" ? anyType : unknownType;
|
|
7266
|
+
}
|
|
7267
|
+
inferMethodCall(expr, objType, env) {
|
|
7268
|
+
const fns = this.overloadsOf(this.propertyType(objType, expr.method.name));
|
|
7269
|
+
const explicit = this.explicitTypeArguments(expr, fns);
|
|
7270
|
+
const expected = this.expectedArguments(expr.arguments, fns, (f) => this.takesSelf(f) ? 1 : 0);
|
|
7271
|
+
expr.arguments.forEach((a, i) => this.applyContext(a, expected[i]));
|
|
7272
|
+
const argTypes = expr.arguments.map((a) => this.infer(a, env));
|
|
7273
|
+
if (fns.length) {
|
|
7274
|
+
const withSelf = (f) => this.takesSelf(f) ? [objType, ...argTypes] : argTypes;
|
|
7275
|
+
const selfOf = (f) => this.takesSelf(f) ? 1 : 0;
|
|
7276
|
+
this.recordExpected(expr.arguments, fns, selfOf);
|
|
7277
|
+
const arityFits = this.checkArity(expr, fns, argTypes.length, this.takesSelf(fns[0]) ? 1 : 0);
|
|
7278
|
+
const picked = this.pickOverload(fns, argTypes, withSelf);
|
|
7279
|
+
const distributed = this.distributedReturn(
|
|
7280
|
+
fns,
|
|
7281
|
+
argTypes,
|
|
7282
|
+
picked,
|
|
7283
|
+
(f, args) => this.takesSelf(f) ? [objType, ...args] : args
|
|
7284
|
+
);
|
|
7285
|
+
if (distributed) return distributed;
|
|
7286
|
+
if (picked) {
|
|
7287
|
+
const self = this.takesSelf(picked) ? 1 : 0;
|
|
7288
|
+
this.checkInferredArguments(expr, expr.arguments, picked, withSelf(picked), self);
|
|
7289
|
+
const written = this.constArgs(picked, expr.arguments, argTypes, env, self);
|
|
7290
|
+
return this.callReturn(picked, this.takesSelf(picked) ? [objType, ...written] : written, explicit);
|
|
7291
|
+
}
|
|
7292
|
+
if (arityFits) this.reportArguments(expr, expr.arguments, fns, withSelf, selfOf);
|
|
7293
|
+
return union(fns.map((f) => this.callReturn(f, withSelf(f), explicit)));
|
|
7294
|
+
}
|
|
7295
|
+
return objType.kind === "any" ? anyType : unknownType;
|
|
7296
|
+
}
|
|
7297
|
+
// --------------------------------------------------------
|
|
7298
|
+
// Optional chains
|
|
7299
|
+
// --------------------------------------------------------
|
|
7300
|
+
//
|
|
7301
|
+
// `a?.b.c`: when `a` is nil the whole chain is nil and `.c` never runs.
|
|
7302
|
+
// So a link reads its object without the `nil` a `?.` earlier in the chain
|
|
7303
|
+
// added — that nil has already left the chain — and the chain's outermost
|
|
7304
|
+
// link carries it again. Parentheses end a chain: `(a?.b).c` reads `.c`
|
|
7305
|
+
// from `B | nil`.
|
|
7306
|
+
/** The type of a link's non-nil object, for each link that is past a `?.`:
|
|
7307
|
+
* what the chain holds when it has not short-circuited. */
|
|
7308
|
+
chainValue = /* @__PURE__ */ new WeakMap();
|
|
7309
|
+
/** The object a link reads from, and whether the chain can short-circuit
|
|
7310
|
+
* by this link. */
|
|
7311
|
+
chainObject(link, object, env) {
|
|
7312
|
+
const full = this.infer(object, env);
|
|
7313
|
+
const inChain = this.chainValue.get(object);
|
|
7314
|
+
let type = inChain ?? full;
|
|
7315
|
+
if (link.optional) {
|
|
7316
|
+
type = withoutNil(type);
|
|
7317
|
+
} else if (this.includesNil(type)) {
|
|
7318
|
+
this.reportNilAccess(object, type);
|
|
7319
|
+
type = withoutNil(this.expand(type));
|
|
7320
|
+
}
|
|
7321
|
+
return { type, shortCircuits: inChain !== void 0 || link.optional === true };
|
|
7322
|
+
}
|
|
7323
|
+
/** Objects already reported as possibly nil: a loop body is visited more
|
|
7324
|
+
* than once. */
|
|
7325
|
+
nilAccessReported = /* @__PURE__ */ new WeakSet();
|
|
7326
|
+
includesNil(raw) {
|
|
7327
|
+
const t = this.expand(raw);
|
|
7328
|
+
if (t.kind === "primitive") return t.name === "nil";
|
|
7329
|
+
return t.kind === "union" && t.types.some((m) => m.kind === "primitive" && m.name === "nil");
|
|
7330
|
+
}
|
|
7331
|
+
reportNilAccess(object, type) {
|
|
7332
|
+
if (!this.emitDiagnostics || this.nilAccessReported.has(object)) return;
|
|
7333
|
+
this.nilAccessReported.add(object);
|
|
7334
|
+
const label = expressionLabel(object);
|
|
7335
|
+
const t = this.expand(type);
|
|
7336
|
+
const nilOnly = t.kind === "primitive" && t.name === "nil";
|
|
7337
|
+
const subject = label === void 0 ? "Object" : `'${label}'`;
|
|
7338
|
+
this.diagnostics.push({
|
|
7339
|
+
node: object,
|
|
7340
|
+
message: nilOnly ? `${subject} is nil` : `${subject} is possibly nil. Check it first, or use '?.' / '?:'`
|
|
7341
|
+
});
|
|
7342
|
+
}
|
|
7343
|
+
chainResult(link, value, shortCircuits) {
|
|
7344
|
+
if (!shortCircuits) return value;
|
|
7345
|
+
this.chainValue.set(link, value);
|
|
7346
|
+
return union([value, nilType]);
|
|
7347
|
+
}
|
|
7348
|
+
/** The chain around `cond` did not short-circuit — it produced a truthy
|
|
7349
|
+
* value, or any value but nil — so every object a `?.` in it tested is not
|
|
7350
|
+
* nil in `env`. */
|
|
7351
|
+
narrowOptionalLinks(cond, env, into) {
|
|
7352
|
+
for (let e = cond; ; ) {
|
|
7353
|
+
const link = e;
|
|
7354
|
+
const object = e.type === "CallExpression" ? e.callee : e.type === "MemberExpression" || e.type === "IndexExpression" || e.type === "MethodCallExpression" ? e.object : void 0;
|
|
7355
|
+
if (!object) return;
|
|
7356
|
+
if (link.optional) {
|
|
7357
|
+
const key = this.refKeyOf(object);
|
|
7358
|
+
if (key !== void 0) this.setRef(into, key, withoutNil(this.typeAtRef(object, into)));
|
|
7359
|
+
}
|
|
7360
|
+
e = object;
|
|
7361
|
+
}
|
|
7362
|
+
}
|
|
5926
7363
|
inferArray(expr, env, asConst) {
|
|
5927
7364
|
const contextual = this.contextualArrays.get(expr);
|
|
5928
7365
|
if (contextual && !asConst) return contextual;
|
|
@@ -5940,7 +7377,21 @@ var TypeAnalyzer = class {
|
|
|
5940
7377
|
}
|
|
5941
7378
|
}
|
|
5942
7379
|
if (asConst && !hadSpread) return tuple(elems);
|
|
5943
|
-
return arrayOf(elems.length ? union(elems.map((t) =>
|
|
7380
|
+
return arrayOf(elems.length ? union(elems.map((t, i) => {
|
|
7381
|
+
const element = expr.elements[i];
|
|
7382
|
+
return asConst || !element || element.type === "SpreadElement" ? t : this.widenUnlessAsked(t, element);
|
|
7383
|
+
})) : unknownType);
|
|
7384
|
+
}
|
|
7385
|
+
/** A literal written inside a fresh table or array widens — `{ n = 1 }` is
|
|
7386
|
+
* `{ n: number }` — unless the surroundings said a literal belongs there.
|
|
7387
|
+
* `request({ Method: "GET" })` keeps `"GET"` when `Method` is a union of
|
|
7388
|
+
* string literals, exactly as TypeScript's contextual typing does, and
|
|
7389
|
+
* goes on widening to `string` when the parameter only says `string`.
|
|
7390
|
+
* The context was recorded by `applyContext` before the value was
|
|
7391
|
+
* inferred, so this is a lookup rather than a second pass. */
|
|
7392
|
+
widenUnlessAsked(value, at) {
|
|
7393
|
+
const wanted = this.expectedTypeOf.get(at);
|
|
7394
|
+
return wanted === void 0 ? widen(value) : this.keepContextualLiterals(value, wanted);
|
|
5944
7395
|
}
|
|
5945
7396
|
inferObject(expr, env, asConst) {
|
|
5946
7397
|
const entries = [];
|
|
@@ -5948,16 +7399,24 @@ var TypeAnalyzer = class {
|
|
|
5948
7399
|
for (const field of expr.fields) {
|
|
5949
7400
|
if (field.type === "TableFieldNamed") {
|
|
5950
7401
|
const key = field.key.type === "Identifier" ? field.key.name : field.key.value;
|
|
5951
|
-
const v = asConst ? this.inferAsConst(field.value, env) :
|
|
7402
|
+
const v = asConst ? this.inferAsConst(field.value, env) : this.widenUnlessAsked(this.infer(field.value, env), field.value);
|
|
5952
7403
|
entries.push([key, { type: v, optional: false, readonly: asConst }]);
|
|
5953
7404
|
} else if (field.type === "TableFieldShorthand") {
|
|
5954
7405
|
const v = this.infer(field.name, env);
|
|
5955
|
-
entries.push([field.name.name, {
|
|
7406
|
+
entries.push([field.name.name, {
|
|
7407
|
+
type: asConst ? v : this.widenUnlessAsked(v, field.name),
|
|
7408
|
+
optional: false,
|
|
7409
|
+
readonly: asConst
|
|
7410
|
+
}]);
|
|
5956
7411
|
} else if (field.type === "TableFieldComputed") {
|
|
5957
7412
|
const k = this.infer(field.key, env);
|
|
5958
7413
|
const v = this.infer(field.value, env);
|
|
5959
7414
|
if (k.kind === "literal" && typeof k.value === "string") {
|
|
5960
|
-
entries.push([k.value, {
|
|
7415
|
+
entries.push([k.value, {
|
|
7416
|
+
type: asConst ? v : this.widenUnlessAsked(v, field.value),
|
|
7417
|
+
optional: false,
|
|
7418
|
+
readonly: asConst
|
|
7419
|
+
}]);
|
|
5961
7420
|
} else {
|
|
5962
7421
|
indexer = mergeIndexer(indexer, { key: widen(k), value: asConst ? v : widen(v) });
|
|
5963
7422
|
}
|
|
@@ -5971,6 +7430,123 @@ var TypeAnalyzer = class {
|
|
|
5971
7430
|
}
|
|
5972
7431
|
return objectType(entries, indexer, asConst || void 0);
|
|
5973
7432
|
}
|
|
7433
|
+
/** A value inferred `as const`, widened back wherever `context` does not
|
|
7434
|
+
* ask for a literal: `satisfies`' result type. A property keeps `"circle"`
|
|
7435
|
+
* when the contract's property admits string literals, and becomes
|
|
7436
|
+
* `string` when it is only `string`; a tuple becomes an array unless the
|
|
7437
|
+
* contract is a tuple; nothing stays readonly. */
|
|
7438
|
+
keepContextualLiterals(value, context) {
|
|
7439
|
+
const ctx = context === void 0 ? void 0 : this.expand(context);
|
|
7440
|
+
switch (value.kind) {
|
|
7441
|
+
case "literal":
|
|
7442
|
+
return ctx && this.admitsLiteral(ctx, value.base) ? value : widen(value);
|
|
7443
|
+
case "object": {
|
|
7444
|
+
if (value.class) return value;
|
|
7445
|
+
const entries = [...value.properties].map(([name, property]) => [
|
|
7446
|
+
name,
|
|
7447
|
+
{ ...property, readonly: false, type: this.keepContextualLiterals(property.type, ctx && this.contextProperty(ctx, name)) }
|
|
7448
|
+
]);
|
|
7449
|
+
const indexer = value.indexer && {
|
|
7450
|
+
key: widen(value.indexer.key),
|
|
7451
|
+
value: this.keepContextualLiterals(value.indexer.value, ctx && this.contextIndexValue(ctx))
|
|
7452
|
+
};
|
|
7453
|
+
return objectType(entries, indexer);
|
|
7454
|
+
}
|
|
7455
|
+
case "tuple": {
|
|
7456
|
+
const tupleContext = ctx && this.membersOf(ctx).find((m) => m.kind === "tuple");
|
|
7457
|
+
if (tupleContext?.kind === "tuple") {
|
|
7458
|
+
return tuple(value.elements.map((e, i) => this.keepContextualLiterals(e, tupleContext.elements[i])), value.isPack);
|
|
7459
|
+
}
|
|
7460
|
+
const arrayContext = ctx && this.membersOf(ctx).find((m) => m.kind === "array");
|
|
7461
|
+
const element = arrayContext?.kind === "array" ? arrayContext.element : void 0;
|
|
7462
|
+
if (!value.elements.length) return arrayContext ?? arrayOf(unknownType);
|
|
7463
|
+
return arrayOf(union(value.elements.map((e) => this.keepContextualLiterals(e, element))));
|
|
7464
|
+
}
|
|
7465
|
+
case "array": {
|
|
7466
|
+
const arrayContext = ctx && this.membersOf(ctx).find((m) => m.kind === "array");
|
|
7467
|
+
return arrayOf(this.keepContextualLiterals(value.element, arrayContext?.kind === "array" ? arrayContext.element : void 0));
|
|
7468
|
+
}
|
|
7469
|
+
case "union":
|
|
7470
|
+
return union(value.types.map((t) => this.keepContextualLiterals(t, context)));
|
|
7471
|
+
default:
|
|
7472
|
+
return value;
|
|
7473
|
+
}
|
|
7474
|
+
}
|
|
7475
|
+
membersOf(t) {
|
|
7476
|
+
const x = this.expand(t);
|
|
7477
|
+
return x.kind === "union" ? x.types.map((m) => this.expand(m)) : [x];
|
|
7478
|
+
}
|
|
7479
|
+
/** Does a contract accept literals of `base` as such? */
|
|
7480
|
+
admitsLiteral(ctx, base) {
|
|
7481
|
+
return this.membersOf(ctx).some((m) => m.kind === "literal" && m.base === base || m.kind === "templateLiteral" && base === "string");
|
|
7482
|
+
}
|
|
7483
|
+
/** What a contract expects of property `name`, over every object it allows. */
|
|
7484
|
+
contextProperty(ctx, name) {
|
|
7485
|
+
const found = [];
|
|
7486
|
+
for (const m of this.membersOf(ctx)) {
|
|
7487
|
+
if (m.kind !== "object") continue;
|
|
7488
|
+
const property = m.properties.get(name);
|
|
7489
|
+
if (property) found.push(property.type);
|
|
7490
|
+
else if (m.indexer) found.push(m.indexer.value);
|
|
7491
|
+
}
|
|
7492
|
+
return found.length ? union(found) : void 0;
|
|
7493
|
+
}
|
|
7494
|
+
contextIndexValue(ctx) {
|
|
7495
|
+
const found = this.membersOf(ctx).flatMap((m) => m.kind === "object" && m.indexer ? [m.indexer.value] : []);
|
|
7496
|
+
return found.length ? union(found) : void 0;
|
|
7497
|
+
}
|
|
7498
|
+
/** Fields reported by `reportExcessProperties`, once each: a loop body is
|
|
7499
|
+
* visited more than once. */
|
|
7500
|
+
excessReported = /* @__PURE__ */ new WeakSet();
|
|
7501
|
+
/** TypeScript's excess property check. An object literal written straight
|
|
7502
|
+
* into a typed place — an annotation, `satisfies` — may only name
|
|
7503
|
+
* properties that place knows: anything else is almost always a typo.
|
|
7504
|
+
* A nested literal is checked against the property it is written for.
|
|
7505
|
+
* A target with an indexer, a class, or a member whose shape is not known
|
|
7506
|
+
* accepts anything. */
|
|
7507
|
+
/** The keys an index signature covers, when it covers a countable set of
|
|
7508
|
+
* them: `[("a" | "b")]` yes, `[string]` no. */
|
|
7509
|
+
finiteKeys(key) {
|
|
7510
|
+
const t = this.expand(key);
|
|
7511
|
+
const parts = t.kind === "union" ? t.types : [t];
|
|
7512
|
+
const out = /* @__PURE__ */ new Set();
|
|
7513
|
+
for (const part of parts.map((m) => this.expand(m))) {
|
|
7514
|
+
if (part.kind !== "literal" || typeof part.value === "boolean") return void 0;
|
|
7515
|
+
out.add(String(part.value));
|
|
7516
|
+
}
|
|
7517
|
+
return out.size ? out : void 0;
|
|
7518
|
+
}
|
|
7519
|
+
reportExcessProperties(expression, target) {
|
|
7520
|
+
let literal2 = unwrapParens(expression);
|
|
7521
|
+
while (literal2.type === "AsConstExpression") literal2 = unwrapParens(literal2.expression);
|
|
7522
|
+
if (literal2.type !== "TableExpression" || !this.emitDiagnostics) return;
|
|
7523
|
+
const members = this.membersOf(target);
|
|
7524
|
+
const shapes = members.filter((m) => m.kind === "object");
|
|
7525
|
+
if (!shapes.length || shapes.some((o) => o.class)) return;
|
|
7526
|
+
const keySets = shapes.map((o) => o.indexer && this.finiteKeys(o.indexer.key));
|
|
7527
|
+
if (shapes.some((o, i) => o.indexer && !keySets[i])) return;
|
|
7528
|
+
if (members.some((m) => m.kind === "any" || m.kind === "unknown" || m.kind === "typeParam" || m.kind === "intersection")) return;
|
|
7529
|
+
for (const field of literal2.fields) {
|
|
7530
|
+
if (field.type !== "TableFieldNamed" && field.type !== "TableFieldShorthand") continue;
|
|
7531
|
+
const key = field.type === "TableFieldNamed" ? field.key : field.name;
|
|
7532
|
+
const name = key.type === "Identifier" ? key.name : key.value;
|
|
7533
|
+
const expected = shapes.flatMap((o, i) => {
|
|
7534
|
+
const property = o.properties.get(name);
|
|
7535
|
+
if (property) return [property.type];
|
|
7536
|
+
return o.indexer && keySets[i].has(name) ? [o.indexer.value] : [];
|
|
7537
|
+
});
|
|
7538
|
+
if (!expected.length) {
|
|
7539
|
+
if (this.excessReported.has(key)) continue;
|
|
7540
|
+
this.excessReported.add(key);
|
|
7541
|
+
this.diagnostics.push({
|
|
7542
|
+
node: key,
|
|
7543
|
+
message: `Object literal may only specify known properties, and '${name}' does not exist in type '${formatType(target)}'`
|
|
7544
|
+
});
|
|
7545
|
+
continue;
|
|
7546
|
+
}
|
|
7547
|
+
if (field.type === "TableFieldNamed") this.reportExcessProperties(field.value, union(expected));
|
|
7548
|
+
}
|
|
7549
|
+
}
|
|
5974
7550
|
inferAsConst(expr, env) {
|
|
5975
7551
|
switch (expr.type) {
|
|
5976
7552
|
case "ArrayExpression":
|
|
@@ -6028,12 +7604,13 @@ var TypeAnalyzer = class {
|
|
|
6028
7604
|
}
|
|
6029
7605
|
if (cond.type === "CallExpression" || cond.type === "MethodCallExpression") {
|
|
6030
7606
|
this.narrowByPredicateCall(cond, env, t, f);
|
|
6031
|
-
|
|
7607
|
+
} else {
|
|
7608
|
+
this.narrowRef(cond, env, t, f, (cur) => ({
|
|
7609
|
+
yes: narrowTruthy(cur),
|
|
7610
|
+
no: narrowFalsy(cur)
|
|
7611
|
+
}));
|
|
6032
7612
|
}
|
|
6033
|
-
this.
|
|
6034
|
-
yes: narrowTruthy(cur),
|
|
6035
|
-
no: narrowFalsy(cur)
|
|
6036
|
-
}));
|
|
7613
|
+
this.narrowOptionalLinks(cond, env, t);
|
|
6037
7614
|
}
|
|
6038
7615
|
/** `a == b` / `a ~= b`. Handles, in order: a declaration-driven
|
|
6039
7616
|
* `typeof(x) == "..."` test, a literal/`nil` comparison against a
|
|
@@ -6050,11 +7627,14 @@ var TypeAnalyzer = class {
|
|
|
6050
7627
|
};
|
|
6051
7628
|
for (const [ref, other] of [[left, right], [right, left]]) {
|
|
6052
7629
|
const value = litOf(other);
|
|
6053
|
-
if (value === void 0
|
|
6054
|
-
this.
|
|
6055
|
-
yes
|
|
6056
|
-
|
|
6057
|
-
|
|
7630
|
+
if (value === void 0) continue;
|
|
7631
|
+
if (this.refKeyOf(ref) !== void 0) {
|
|
7632
|
+
this.narrowRef(ref, env, yes, no, (cur) => ({
|
|
7633
|
+
yes: narrowTo(cur, value),
|
|
7634
|
+
no: narrowExclude(cur, value)
|
|
7635
|
+
}));
|
|
7636
|
+
}
|
|
7637
|
+
this.narrowOptionalLinks(ref, env, value.kind === "primitive" && value.name === "nil" ? no : yes);
|
|
6058
7638
|
return;
|
|
6059
7639
|
}
|
|
6060
7640
|
if (this.refKeyOf(left) !== void 0 && this.refKeyOf(right) !== void 0) {
|
|
@@ -6109,11 +7689,16 @@ var TypeAnalyzer = class {
|
|
|
6109
7689
|
predicateCallTarget(cond, env) {
|
|
6110
7690
|
let callee;
|
|
6111
7691
|
let args;
|
|
7692
|
+
let selfType;
|
|
6112
7693
|
if (cond.type === "CallExpression") {
|
|
6113
|
-
callee = this.typeOf.get(cond.callee) ?? this.typeAtRef(cond.callee, env);
|
|
7694
|
+
callee = this.chainValue.get(cond.callee) ?? this.typeOf.get(cond.callee) ?? this.typeAtRef(cond.callee, env);
|
|
6114
7695
|
args = cond.arguments;
|
|
6115
7696
|
} else if (cond.type === "MethodCallExpression") {
|
|
6116
|
-
|
|
7697
|
+
let objType = this.chainValue.get(cond.object) ?? this.typeOf.get(cond.object) ?? this.typeAtRef(cond.object, env);
|
|
7698
|
+
if (cond.optional) {
|
|
7699
|
+
objType = withoutNil(objType);
|
|
7700
|
+
selfType = objType;
|
|
7701
|
+
}
|
|
6117
7702
|
callee = this.propertyType(objType, cond.method.name);
|
|
6118
7703
|
const first = this.overloadsOf(callee)[0];
|
|
6119
7704
|
args = first && this.takesSelf(first) ? [cond.object, ...cond.arguments] : cond.arguments;
|
|
@@ -6121,7 +7706,7 @@ var TypeAnalyzer = class {
|
|
|
6121
7706
|
return void 0;
|
|
6122
7707
|
}
|
|
6123
7708
|
const overloads = this.overloadsOf(callee);
|
|
6124
|
-
const argTypes = args.map((a) => this.typeOf.get(a) ?? this.typeAtRef(a, env));
|
|
7709
|
+
const argTypes = args.map((a) => (selfType && cond.type === "MethodCallExpression" && a === cond.object ? selfType : void 0) ?? this.typeOf.get(a) ?? this.typeAtRef(a, env));
|
|
6125
7710
|
const picked = this.pickOverload(overloads, argTypes);
|
|
6126
7711
|
const candidates = picked ? [picked, ...overloads.filter((f) => f !== picked)] : overloads;
|
|
6127
7712
|
for (const f of candidates) {
|
|
@@ -6228,6 +7813,10 @@ var TypeAnalyzer = class {
|
|
|
6228
7813
|
const { yes, no } = refine(cur);
|
|
6229
7814
|
this.setRef(t, key, yes);
|
|
6230
7815
|
this.setRef(f, key, no);
|
|
7816
|
+
this.correlate(t, key, yes);
|
|
7817
|
+
this.correlate(f, key, no);
|
|
7818
|
+
this.propagateAliases(env, t, key, yes);
|
|
7819
|
+
this.propagateAliases(env, f, key, no);
|
|
6231
7820
|
const inner = expr.type === "ParenthesizedExpression" ? expr.expression : expr;
|
|
6232
7821
|
if (inner.type !== "MemberExpression" && inner.type !== "IndexExpression") return;
|
|
6233
7822
|
const parentKey = this.refKeyOf(inner.object);
|
|
@@ -6235,18 +7824,19 @@ var TypeAnalyzer = class {
|
|
|
6235
7824
|
const step = key.slice(parentKey.length);
|
|
6236
7825
|
if (!step.startsWith(".")) return;
|
|
6237
7826
|
const prop = step.slice(1);
|
|
7827
|
+
const optional2 = inner.type === "MemberExpression" && inner.optional === true;
|
|
6238
7828
|
this.narrowRef(inner.object, env, t, f, (parentType) => ({
|
|
6239
|
-
yes: this.filterByProperty(parentType, prop, yes),
|
|
6240
|
-
no: this.filterByProperty(parentType, prop, no)
|
|
7829
|
+
yes: this.filterByProperty(parentType, prop, yes, optional2),
|
|
7830
|
+
no: this.filterByProperty(parentType, prop, no, optional2)
|
|
6241
7831
|
}));
|
|
6242
7832
|
}
|
|
6243
7833
|
/** Keep the union members of `parent` whose `prop` can still hold `want`.
|
|
6244
7834
|
* Leaves a non-union (or a union nothing matches) alone: over-narrowing a
|
|
6245
7835
|
* plain object to `never` because of a property test would be worse than
|
|
6246
7836
|
* learning nothing. */
|
|
6247
|
-
filterByProperty(parent, prop, want) {
|
|
7837
|
+
filterByProperty(parent, prop, want, optional2 = false) {
|
|
6248
7838
|
if (parent.kind !== "union" || want.kind === "never") return parent;
|
|
6249
|
-
const kept = parent.types.filter((m) => overlaps(this.propertyType(m, prop), want));
|
|
7839
|
+
const kept = parent.types.filter((m) => m.kind === "primitive" && m.name === "nil" ? optional2 && overlaps(nilType, want) : overlaps(this.propertyType(m, prop), want));
|
|
6250
7840
|
return kept.length ? union(kept) : parent;
|
|
6251
7841
|
}
|
|
6252
7842
|
/** Record a narrowing. Deliberately does *not* discard what is known about
|
|
@@ -6258,6 +7848,15 @@ var TypeAnalyzer = class {
|
|
|
6258
7848
|
setRef(env, key, t) {
|
|
6259
7849
|
env.set(key, t);
|
|
6260
7850
|
}
|
|
7851
|
+
/** An assignment to a path (or to anything it hangs off) means the name
|
|
7852
|
+
* that copied it no longer holds that value: forget the alias. */
|
|
7853
|
+
unalias(key) {
|
|
7854
|
+
for (const k of [...this.refAliases.keys()]) {
|
|
7855
|
+
if (k !== key && !k.startsWith(`${key}.`) && !k.startsWith(`${key}#`)) continue;
|
|
7856
|
+
for (const other of this.refAliases.get(k) ?? []) this.refAliases.get(other)?.delete(k);
|
|
7857
|
+
this.refAliases.delete(k);
|
|
7858
|
+
}
|
|
7859
|
+
}
|
|
6261
7860
|
/** Drop every narrowing recorded for a path strictly under `key`. */
|
|
6262
7861
|
invalidateBelow(env, key) {
|
|
6263
7862
|
for (const k of [...env.keys()]) {
|
|
@@ -6270,6 +7869,7 @@ var TypeAnalyzer = class {
|
|
|
6270
7869
|
const key = this.refKeyOf(expr);
|
|
6271
7870
|
if (key === void 0) return;
|
|
6272
7871
|
this.invalidateBelow(env, key);
|
|
7872
|
+
this.unalias(key);
|
|
6273
7873
|
env.set(key, value);
|
|
6274
7874
|
}
|
|
6275
7875
|
// --------------------------------------------------------
|
|
@@ -6350,7 +7950,85 @@ var TypeAnalyzer = class {
|
|
|
6350
7950
|
/** The type a binding has *here*: its flow-narrowed type if the current
|
|
6351
7951
|
* environment has one, else its declared/inferred type. */
|
|
6352
7952
|
currentType(id, env) {
|
|
6353
|
-
return env.get(bindKey(id)) ?? this.bindingType.get(id) ?? anyType;
|
|
7953
|
+
return env.get(bindKey(id)) ?? this.bindingType.get(id) ?? this.declaredAhead(id) ?? anyType;
|
|
7954
|
+
}
|
|
7955
|
+
// --------------------------------------------------------
|
|
7956
|
+
// Hoisting
|
|
7957
|
+
// --------------------------------------------------------
|
|
7958
|
+
//
|
|
7959
|
+
// Scope analysis lets code see a function declared later in its block,
|
|
7960
|
+
// and a module's top-level names from function bodies and `typeof` written
|
|
7961
|
+
// above them. The walk has not reached those declarations yet when such a
|
|
7962
|
+
// reference is met, so their type is worked out from the declaration on
|
|
7963
|
+
// the spot — its annotation, or its body or initializer — as TypeScript
|
|
7964
|
+
// does. The walk reaching the declaration later types it for real.
|
|
7965
|
+
/** Declarations a reference may meet before the walk does. */
|
|
7966
|
+
aheadDeclarations;
|
|
7967
|
+
computingAhead = /* @__PURE__ */ new Set();
|
|
7968
|
+
declaredAhead(id) {
|
|
7969
|
+
this.aheadDeclarations ??= this.indexAheadDeclarations();
|
|
7970
|
+
const found = this.aheadDeclarations.get(id);
|
|
7971
|
+
if (!found || this.computingAhead.has(id)) return void 0;
|
|
7972
|
+
this.computingAhead.add(id);
|
|
7973
|
+
const wasEmitting = this.emitDiagnostics;
|
|
7974
|
+
this.emitDiagnostics = false;
|
|
7975
|
+
try {
|
|
7976
|
+
const { statement, index } = found;
|
|
7977
|
+
let type;
|
|
7978
|
+
if (statement.type === "DeclareStatement") {
|
|
7979
|
+
type = this.resolveType(statement.valueType);
|
|
7980
|
+
} else if (statement.type === "FunctionDeclaration") {
|
|
7981
|
+
type = statement.signatures?.length ? intersection(statement.signatures.map((sig) => this.signatureToFnType(sig))) : this.inferFunctionBody(statement.func, /* @__PURE__ */ new Map());
|
|
7982
|
+
} else if (statement.type === "VariableDeclaration") {
|
|
7983
|
+
const target = statement.names[index];
|
|
7984
|
+
if (target.type === "IdentifierPattern" && target.typeAnnotation) {
|
|
7985
|
+
type = this.resolveType(target.typeAnnotation);
|
|
7986
|
+
} else if (statement.init[index]) {
|
|
7987
|
+
const value = this.infer(statement.init[index], /* @__PURE__ */ new Map());
|
|
7988
|
+
type = statement.kind === "const" ? value : widen(value);
|
|
7989
|
+
}
|
|
7990
|
+
}
|
|
7991
|
+
if (type) this.bindingType.set(id, type);
|
|
7992
|
+
return type;
|
|
7993
|
+
} finally {
|
|
7994
|
+
this.emitDiagnostics = wasEmitting;
|
|
7995
|
+
this.computingAhead.delete(id);
|
|
7996
|
+
}
|
|
7997
|
+
}
|
|
7998
|
+
/** Every function declaration, and every plain name the module declares
|
|
7999
|
+
* at its top level. */
|
|
8000
|
+
indexAheadDeclarations() {
|
|
8001
|
+
const out = /* @__PURE__ */ new Map();
|
|
8002
|
+
for (const statement of this.program.body.statements) {
|
|
8003
|
+
const declaration = statement.type === "ExportStatement" ? statement.declaration : statement;
|
|
8004
|
+
if (declaration.type !== "VariableDeclaration") continue;
|
|
8005
|
+
declaration.names.forEach((target, index) => {
|
|
8006
|
+
if (target.type !== "IdentifierPattern") return;
|
|
8007
|
+
const id = this.bindingIdByName(target.name, target);
|
|
8008
|
+
if (id !== void 0) out.set(id, { statement: declaration, index });
|
|
8009
|
+
});
|
|
8010
|
+
}
|
|
8011
|
+
const visit = (node) => {
|
|
8012
|
+
if (!node || typeof node !== "object") return;
|
|
8013
|
+
if (Array.isArray(node)) {
|
|
8014
|
+
for (const item of node) visit(item);
|
|
8015
|
+
return;
|
|
8016
|
+
}
|
|
8017
|
+
const record = node;
|
|
8018
|
+
if (record.type === "FunctionDeclaration" && record.name) {
|
|
8019
|
+
const id = this.bindingIdByName(record.name.name, record.name);
|
|
8020
|
+
if (id !== void 0) out.set(id, { statement: node, index: 0 });
|
|
8021
|
+
}
|
|
8022
|
+
for (const [key, value] of Object.entries(node)) {
|
|
8023
|
+
if (key !== "line" && key !== "column" && value && typeof value === "object") visit(value);
|
|
8024
|
+
}
|
|
8025
|
+
};
|
|
8026
|
+
visit(this.program.body);
|
|
8027
|
+
for (const [name, statement] of this.deferredDeclares) {
|
|
8028
|
+
const id = this.scopes.globalsByName.get(name);
|
|
8029
|
+
if (id !== void 0) out.set(id, { statement, index: 0 });
|
|
8030
|
+
}
|
|
8031
|
+
return out;
|
|
6354
8032
|
}
|
|
6355
8033
|
/** Bind or rebind a whole variable: any narrowing recorded for a path
|
|
6356
8034
|
* *under* it (`x.a`, `x[1]`) described the old value and must go. */
|
|
@@ -6377,12 +8055,28 @@ var TypeAnalyzer = class {
|
|
|
6377
8055
|
return this.bindingByDecl.get(node) ?? this.bindingByPos.get(posKey(name, node.line.start, node.column.start));
|
|
6378
8056
|
}
|
|
6379
8057
|
};
|
|
8058
|
+
function referencedTypeNames(node, out = []) {
|
|
8059
|
+
if (!node || typeof node !== "object") return out;
|
|
8060
|
+
if (Array.isArray(node)) {
|
|
8061
|
+
for (const item of node) referencedTypeNames(item, out);
|
|
8062
|
+
return out;
|
|
8063
|
+
}
|
|
8064
|
+
const record = node;
|
|
8065
|
+
if (record.type === "TypeReference" && typeof record.base === "string") {
|
|
8066
|
+
out.push(typeof record.namespace === "string" ? `${record.namespace}.${record.base}` : record.base);
|
|
8067
|
+
}
|
|
8068
|
+
for (const [key, value] of Object.entries(node)) {
|
|
8069
|
+
if (key !== "line" && key !== "column" && value && typeof value === "object") referencedTypeNames(value, out);
|
|
8070
|
+
}
|
|
8071
|
+
return out;
|
|
8072
|
+
}
|
|
6380
8073
|
function containsTypeQuery(node) {
|
|
6381
8074
|
if (!node || typeof node !== "object") return false;
|
|
6382
8075
|
if (Array.isArray(node)) return node.some(containsTypeQuery);
|
|
6383
8076
|
if (node.type === "TypeofTypeNode") return true;
|
|
6384
8077
|
return Object.values(node).some(containsTypeQuery);
|
|
6385
8078
|
}
|
|
8079
|
+
var STRING_INTRINSICS = /* @__PURE__ */ new Set(["Uppercase", "Lowercase", "Capitalize", "Uncapitalize"]);
|
|
6386
8080
|
function briefType(t) {
|
|
6387
8081
|
if (t.kind === "union" && t.types.length > 8) {
|
|
6388
8082
|
const shown = t.types.slice(0, 6).map(formatType).join(" | ");
|
|
@@ -6547,6 +8241,7 @@ function offsetPosition(source, offset) {
|
|
|
6547
8241
|
var import_node_path2 = require("path");
|
|
6548
8242
|
function resolveTypeLibraries(config, host = nodeHost) {
|
|
6549
8243
|
const files = [];
|
|
8244
|
+
const lowerings = [];
|
|
6550
8245
|
const problems = [];
|
|
6551
8246
|
const loaded = /* @__PURE__ */ new Set();
|
|
6552
8247
|
const addFile = (file) => {
|
|
@@ -6564,6 +8259,8 @@ function resolveTypeLibraries(config, host = nodeHost) {
|
|
|
6564
8259
|
if (found) addPackage(found.directory, found.file, visiting);
|
|
6565
8260
|
}
|
|
6566
8261
|
addFile(entryFile);
|
|
8262
|
+
const lowering = loweringModule(directory, host, problems, config);
|
|
8263
|
+
if (lowering) lowerings.push(lowering);
|
|
6567
8264
|
};
|
|
6568
8265
|
for (const entry of config.types) {
|
|
6569
8266
|
const relative = entry.startsWith("./") || entry.startsWith("../") || entry.startsWith("/") || /^[A-Za-z]:[\\/]/.test(entry);
|
|
@@ -6590,7 +8287,19 @@ function resolveTypeLibraries(config, host = nodeHost) {
|
|
|
6590
8287
|
});
|
|
6591
8288
|
}
|
|
6592
8289
|
}
|
|
6593
|
-
return { files, problems };
|
|
8290
|
+
return { files, lowerings, problems };
|
|
8291
|
+
}
|
|
8292
|
+
function loweringModule(directory, host, problems, config) {
|
|
8293
|
+
const manifest = readJson((0, import_node_path2.join)(directory, "package.json"), host);
|
|
8294
|
+
const declared = manifest?.luaut?.lowering;
|
|
8295
|
+
if (typeof declared !== "string") return void 0;
|
|
8296
|
+
const from = typeof manifest?.name === "string" ? manifest.name : directory;
|
|
8297
|
+
const file = (0, import_node_path2.resolve)(directory, declared);
|
|
8298
|
+
if (host.readFile(file) === void 0) {
|
|
8299
|
+
problems.push({ file: config.path, message: `'${from}' names a lowering module '${declared}', which is not there` });
|
|
8300
|
+
return void 0;
|
|
8301
|
+
}
|
|
8302
|
+
return { file, from };
|
|
6594
8303
|
}
|
|
6595
8304
|
var ENTRY_FILE = "index.d.luaut";
|
|
6596
8305
|
function packageEntry(directory, host) {
|
|
@@ -6771,17 +8480,21 @@ var index_default = luautparser;
|
|
|
6771
8480
|
Keywords,
|
|
6772
8481
|
LexError,
|
|
6773
8482
|
Operators,
|
|
8483
|
+
PRELUDE_SOURCE,
|
|
6774
8484
|
ParseError,
|
|
6775
8485
|
Punctuators,
|
|
8486
|
+
UNUSED_EXPECT_ERROR,
|
|
6776
8487
|
UnaryOperators,
|
|
6777
8488
|
analyzeScopes,
|
|
6778
8489
|
analyzeTypes,
|
|
6779
8490
|
anyType,
|
|
8491
|
+
applyDirectives,
|
|
6780
8492
|
arrayOf,
|
|
6781
8493
|
booleanType,
|
|
6782
8494
|
bufferType,
|
|
6783
8495
|
containsTypeParam,
|
|
6784
8496
|
difference,
|
|
8497
|
+
directivesOf,
|
|
6785
8498
|
equalTypes,
|
|
6786
8499
|
falsyType,
|
|
6787
8500
|
findConfig,
|
|
@@ -6817,6 +8530,7 @@ var index_default = luautparser;
|
|
|
6817
8530
|
parseTokens,
|
|
6818
8531
|
parseWithRecovery,
|
|
6819
8532
|
primitive,
|
|
8533
|
+
readDirectives,
|
|
6820
8534
|
resolveModulePath,
|
|
6821
8535
|
resolveTypeLibraries,
|
|
6822
8536
|
setAliasExpander,
|