luaut-parser 2.1.0 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +148 -15
- package/dist/index.cjs +2105 -358
- package/dist/index.d.cts +162 -18
- package/dist/index.d.ts +162 -18
- package/dist/index.js +2100 -358
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -99,11 +99,17 @@ var LexError = class extends Error {
|
|
|
99
99
|
line;
|
|
100
100
|
column;
|
|
101
101
|
};
|
|
102
|
-
function tokenize(source) {
|
|
102
|
+
function tokenize(source, options = {}) {
|
|
103
|
+
const { errors, comments } = options;
|
|
103
104
|
const tokens = [];
|
|
104
105
|
let cursor = 0;
|
|
105
106
|
let line = 1;
|
|
106
107
|
let column = 1;
|
|
108
|
+
function fail(message, atLine, atColumn) {
|
|
109
|
+
const error = new LexError(message, atLine, atColumn);
|
|
110
|
+
if (!errors) throw error;
|
|
111
|
+
errors.push(error);
|
|
112
|
+
}
|
|
107
113
|
function peek(offset = 0) {
|
|
108
114
|
return source[cursor + offset] ?? "";
|
|
109
115
|
}
|
|
@@ -162,7 +168,8 @@ function tokenize(source) {
|
|
|
162
168
|
let content = "";
|
|
163
169
|
while (true) {
|
|
164
170
|
if (isAtEnd()) {
|
|
165
|
-
|
|
171
|
+
fail("Unterminated long bracket", line, column);
|
|
172
|
+
return content;
|
|
166
173
|
}
|
|
167
174
|
if (peek() === "]") {
|
|
168
175
|
const save = cursor;
|
|
@@ -198,16 +205,21 @@ function tokenize(source) {
|
|
|
198
205
|
continue;
|
|
199
206
|
}
|
|
200
207
|
if (ch === "-" && peek(1) === "-") {
|
|
208
|
+
const startLine = line;
|
|
209
|
+
const startColumn = column;
|
|
201
210
|
advance();
|
|
202
211
|
advance();
|
|
203
212
|
if (peek() === "[") {
|
|
204
213
|
const level = tryLongBracketOpen();
|
|
205
214
|
if (level !== null) {
|
|
206
|
-
readLongBracketContent(level);
|
|
215
|
+
const text = readLongBracketContent(level);
|
|
216
|
+
comments?.push({ text, line: startLine, column: startColumn, endLine: line });
|
|
207
217
|
continue;
|
|
208
218
|
}
|
|
209
219
|
}
|
|
220
|
+
const textStart = cursor;
|
|
210
221
|
skipLineComment();
|
|
222
|
+
comments?.push({ text: source.slice(textStart, cursor).replace(/\r$/, ""), line: startLine, column: startColumn, endLine: startLine });
|
|
211
223
|
continue;
|
|
212
224
|
}
|
|
213
225
|
break;
|
|
@@ -318,17 +330,15 @@ function tokenize(source) {
|
|
|
318
330
|
const quote = advance();
|
|
319
331
|
let value = "";
|
|
320
332
|
while (true) {
|
|
321
|
-
if (isAtEnd()) {
|
|
322
|
-
throw new LexError("Unterminated string", line, column);
|
|
323
|
-
}
|
|
324
333
|
const ch = peek();
|
|
334
|
+
if (isAtEnd() || ch === "\n") {
|
|
335
|
+
fail("Unterminated string", line, column);
|
|
336
|
+
break;
|
|
337
|
+
}
|
|
325
338
|
if (ch === quote) {
|
|
326
339
|
advance();
|
|
327
340
|
break;
|
|
328
341
|
}
|
|
329
|
-
if (ch === "\n") {
|
|
330
|
-
throw new LexError("Unterminated string", line, column);
|
|
331
|
-
}
|
|
332
342
|
if (ch === "\\") {
|
|
333
343
|
advance();
|
|
334
344
|
value += readEscapeSequence();
|
|
@@ -377,7 +387,9 @@ function tokenize(source) {
|
|
|
377
387
|
}
|
|
378
388
|
while (true) {
|
|
379
389
|
if (isAtEnd()) {
|
|
380
|
-
|
|
390
|
+
fail("Unterminated interpolated string", line, column);
|
|
391
|
+
flushString();
|
|
392
|
+
break;
|
|
381
393
|
}
|
|
382
394
|
const ch = peek();
|
|
383
395
|
if (ch === "`") {
|
|
@@ -397,10 +409,15 @@ function tokenize(source) {
|
|
|
397
409
|
advance();
|
|
398
410
|
advance();
|
|
399
411
|
const exprStart = cursor;
|
|
412
|
+
const exprLine = line;
|
|
413
|
+
const exprColumn = column;
|
|
400
414
|
let depth = 1;
|
|
415
|
+
let closed = true;
|
|
401
416
|
while (depth > 0) {
|
|
402
417
|
if (isAtEnd()) {
|
|
403
|
-
|
|
418
|
+
fail("Unterminated interpolation expression", line, column);
|
|
419
|
+
closed = false;
|
|
420
|
+
break;
|
|
404
421
|
}
|
|
405
422
|
if (peek() === "{") depth++;
|
|
406
423
|
if (peek() === "}") {
|
|
@@ -410,8 +427,12 @@ function tokenize(source) {
|
|
|
410
427
|
advance();
|
|
411
428
|
}
|
|
412
429
|
const exprRaw = source.slice(exprStart, cursor);
|
|
430
|
+
parts.push({ kind: "expression", raw: exprRaw, line: exprLine, column: exprColumn });
|
|
431
|
+
if (!closed) {
|
|
432
|
+
flushString();
|
|
433
|
+
break;
|
|
434
|
+
}
|
|
413
435
|
advance();
|
|
414
|
-
parts.push({ kind: "expression", raw: exprRaw });
|
|
415
436
|
continue;
|
|
416
437
|
}
|
|
417
438
|
const chStart = cursor;
|
|
@@ -480,7 +501,9 @@ function tokenize(source) {
|
|
|
480
501
|
};
|
|
481
502
|
}
|
|
482
503
|
}
|
|
483
|
-
|
|
504
|
+
fail(`Unexpected character '${peek()}'`, line, column);
|
|
505
|
+
advance();
|
|
506
|
+
return void 0;
|
|
484
507
|
}
|
|
485
508
|
while (true) {
|
|
486
509
|
skipWhitespaceAndComments();
|
|
@@ -513,7 +536,8 @@ function tokenize(source) {
|
|
|
513
536
|
tokens.push(readIdentifierOrKeyword());
|
|
514
537
|
continue;
|
|
515
538
|
}
|
|
516
|
-
|
|
539
|
+
const symbol = readOperatorOrPunctuator();
|
|
540
|
+
if (symbol) tokens.push(symbol);
|
|
517
541
|
}
|
|
518
542
|
tokens.push({
|
|
519
543
|
type: "EOF",
|
|
@@ -523,6 +547,52 @@ function tokenize(source) {
|
|
|
523
547
|
return tokens;
|
|
524
548
|
}
|
|
525
549
|
|
|
550
|
+
// src/ast/directives.ts
|
|
551
|
+
var DIRECTIVE = /^\s*@luaut-(nocheck|ignore|expect-error)(?![\w-])/;
|
|
552
|
+
function readDirectives(comments, tokens) {
|
|
553
|
+
const codeLines = [...new Set(tokens.filter((t) => t.type !== "EOF").map((t) => t.line.start))].sort((a, b) => a - b);
|
|
554
|
+
const firstCode = codeLines[0] ?? Infinity;
|
|
555
|
+
const all = [];
|
|
556
|
+
let nocheck = false;
|
|
557
|
+
for (const comment of comments) {
|
|
558
|
+
const match = DIRECTIVE.exec(comment.text);
|
|
559
|
+
if (!match) continue;
|
|
560
|
+
const kind = match[1];
|
|
561
|
+
if (kind === "nocheck") {
|
|
562
|
+
if (comment.line < firstCode) nocheck = true;
|
|
563
|
+
all.push({ kind, line: comment.line, column: comment.column });
|
|
564
|
+
continue;
|
|
565
|
+
}
|
|
566
|
+
const target = codeLines.find((l) => l > comment.endLine);
|
|
567
|
+
all.push({ kind, line: comment.line, column: comment.column, target });
|
|
568
|
+
}
|
|
569
|
+
return { nocheck, all };
|
|
570
|
+
}
|
|
571
|
+
function directivesOf(source) {
|
|
572
|
+
const comments = [];
|
|
573
|
+
const errors = [];
|
|
574
|
+
const tokens = tokenize(source, { errors, comments });
|
|
575
|
+
return readDirectives(comments, tokens);
|
|
576
|
+
}
|
|
577
|
+
function applyDirectives(directives, diagnostics, lineOf) {
|
|
578
|
+
if (directives.nocheck) return { kept: [], unusedExpectErrors: [] };
|
|
579
|
+
const covering = /* @__PURE__ */ new Map();
|
|
580
|
+
for (const d of directives.all) {
|
|
581
|
+
if (d.target === void 0) continue;
|
|
582
|
+
covering.set(d.target, [...covering.get(d.target) ?? [], d]);
|
|
583
|
+
}
|
|
584
|
+
const used = /* @__PURE__ */ new Set();
|
|
585
|
+
const kept = diagnostics.filter((diagnostic) => {
|
|
586
|
+
const on = covering.get(lineOf(diagnostic));
|
|
587
|
+
if (!on) return true;
|
|
588
|
+
for (const d of on) used.add(d);
|
|
589
|
+
return false;
|
|
590
|
+
});
|
|
591
|
+
const unusedExpectErrors = directives.all.filter((d) => d.kind === "expect-error" && !used.has(d));
|
|
592
|
+
return { kept, unusedExpectErrors };
|
|
593
|
+
}
|
|
594
|
+
var UNUSED_EXPECT_ERROR = "Unused '@luaut-expect-error' directive";
|
|
595
|
+
|
|
526
596
|
// src/ast/builders.ts
|
|
527
597
|
var ParseError = class extends Error {
|
|
528
598
|
constructor(message, line, column) {
|
|
@@ -541,6 +611,25 @@ function spanFrom(start, end) {
|
|
|
541
611
|
column: { start: start.column.start, end: end.column.end }
|
|
542
612
|
};
|
|
543
613
|
}
|
|
614
|
+
function shiftSpans(node, line, column) {
|
|
615
|
+
const visit = (value) => {
|
|
616
|
+
if (!value || typeof value !== "object") return;
|
|
617
|
+
if (Array.isArray(value)) {
|
|
618
|
+
for (const item of value) visit(item);
|
|
619
|
+
return;
|
|
620
|
+
}
|
|
621
|
+
const span = value;
|
|
622
|
+
if (span.line && span.column) {
|
|
623
|
+
if (span.column.start !== void 0 && span.line.start === 1) span.column.start += column - 1;
|
|
624
|
+
if (span.column.end !== void 0 && span.line.end === 1) span.column.end += column - 1;
|
|
625
|
+
span.line.start += line - 1;
|
|
626
|
+
span.line.end += line - 1;
|
|
627
|
+
}
|
|
628
|
+
for (const child of Object.values(value)) visit(child);
|
|
629
|
+
};
|
|
630
|
+
visit(node);
|
|
631
|
+
return node;
|
|
632
|
+
}
|
|
544
633
|
function tokenIdentifier(t) {
|
|
545
634
|
return { type: "Identifier", name: t.value, ...spanFrom(t, t) };
|
|
546
635
|
}
|
|
@@ -573,15 +662,39 @@ var BINARY_PRECEDENCE = {
|
|
|
573
662
|
var RIGHT_ASSOCIATIVE = /* @__PURE__ */ new Set(["..", "^"]);
|
|
574
663
|
var UNARY_PRECEDENCE = 7;
|
|
575
664
|
var COMPOUND_ASSIGN_OPS = /* @__PURE__ */ new Set(["+=", "-=", "*=", "/=", "//=", "%=", "^=", "..="]);
|
|
665
|
+
var STATEMENT_KEYWORDS = /* @__PURE__ */ new Set([
|
|
666
|
+
"const",
|
|
667
|
+
"let",
|
|
668
|
+
"while",
|
|
669
|
+
"for",
|
|
670
|
+
"return",
|
|
671
|
+
"do",
|
|
672
|
+
"repeat",
|
|
673
|
+
"break",
|
|
674
|
+
"continue",
|
|
675
|
+
"import",
|
|
676
|
+
"export",
|
|
677
|
+
"end",
|
|
678
|
+
"else",
|
|
679
|
+
"elseif",
|
|
680
|
+
"until",
|
|
681
|
+
"then"
|
|
682
|
+
]);
|
|
576
683
|
var Parser = class {
|
|
577
684
|
tokens;
|
|
578
685
|
cursor = 0;
|
|
579
686
|
recover;
|
|
687
|
+
indentation;
|
|
580
688
|
/** Populated in recovery mode. */
|
|
581
689
|
errors = [];
|
|
690
|
+
/** Recovery found a block without its `end`. */
|
|
691
|
+
missingEnd = false;
|
|
692
|
+
/** The column of the first token on each line, for `indentation`. */
|
|
693
|
+
lineIndent;
|
|
582
694
|
constructor(tokens, options = {}) {
|
|
583
695
|
this.tokens = tokens;
|
|
584
696
|
this.recover = options.recover ?? false;
|
|
697
|
+
this.indentation = this.recover && (options.indentation ?? false);
|
|
585
698
|
}
|
|
586
699
|
current() {
|
|
587
700
|
return this.tokens[this.cursor];
|
|
@@ -622,6 +735,10 @@ var Parser = class {
|
|
|
622
735
|
const t = this.current();
|
|
623
736
|
return (t.type === "Identifier" || t.type === "Keyword") && t.value === value;
|
|
624
737
|
}
|
|
738
|
+
checkPunctuatorAt(offset, value) {
|
|
739
|
+
const t = this.peek(offset);
|
|
740
|
+
return t.type === "Punctuator" && t.value === value;
|
|
741
|
+
}
|
|
625
742
|
checkIdentifierValue(value) {
|
|
626
743
|
const t = this.current();
|
|
627
744
|
return t.type === "Identifier" && t.value === value;
|
|
@@ -667,46 +784,194 @@ var Parser = class {
|
|
|
667
784
|
const t = this.current();
|
|
668
785
|
const err = new ParseError(`${message}, got '${this.describeToken(t)}'`, t.line.start, t.column.start);
|
|
669
786
|
if (this.recover) {
|
|
670
|
-
this.
|
|
787
|
+
this.record(err);
|
|
671
788
|
throw new ParseRecover(err.message);
|
|
672
789
|
}
|
|
673
790
|
throw err;
|
|
674
791
|
}
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
792
|
+
// ============================================================
|
|
793
|
+
// Recovery
|
|
794
|
+
// ============================================================
|
|
795
|
+
//
|
|
796
|
+
// In recovery mode a syntax error costs as little of the tree as it can.
|
|
797
|
+
// A broken expression becomes an `ErrorExpression` where it stood; a broken
|
|
798
|
+
// field, element or argument is skipped up to the next `,`; a missing `)`,
|
|
799
|
+
// `}`, `then`, `do` or `end` is recorded and parsing goes on as if it were
|
|
800
|
+
// there. Only what none of these cover abandons a whole statement.
|
|
801
|
+
/** An error at the position of the one before it is the same problem seen
|
|
802
|
+
* again, and is not recorded twice. */
|
|
803
|
+
record(error) {
|
|
804
|
+
const last = this.errors[this.errors.length - 1];
|
|
805
|
+
if (last && last.line === error.line && last.column === error.column) return;
|
|
806
|
+
this.errors.push(error);
|
|
807
|
+
}
|
|
808
|
+
/** Record an error without abandoning what is being parsed. */
|
|
809
|
+
softError(message) {
|
|
810
|
+
const t = this.current();
|
|
811
|
+
this.record(new ParseError(`${message}, got '${this.describeToken(t)}'`, t.line.start, t.column.start));
|
|
812
|
+
}
|
|
813
|
+
/** `parse()`; in recovery mode, when it fails, skip to where parsing can go
|
|
814
|
+
* on and return `fallback` instead. */
|
|
815
|
+
attempt(parse2, stop, fallback) {
|
|
816
|
+
if (!this.recover) return parse2();
|
|
817
|
+
const from = this.cursor;
|
|
818
|
+
const start = this.current();
|
|
819
|
+
try {
|
|
820
|
+
return parse2();
|
|
821
|
+
} catch (e) {
|
|
822
|
+
if (e instanceof ParseError) this.record(e);
|
|
823
|
+
else if (!(e instanceof ParseRecover)) throw e;
|
|
824
|
+
this.skip(stop, from, "expression");
|
|
825
|
+
return fallback(start, from);
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
/** An expression, or an `ErrorExpression` over what could not be parsed. */
|
|
829
|
+
expressionOr(stop) {
|
|
830
|
+
return this.attempt(() => this.parseExpression(), stop, (start, from) => this.errorExpression(start, from));
|
|
831
|
+
}
|
|
832
|
+
expressionListOr(stop) {
|
|
833
|
+
const item = () => this.expressionOr(() => stop() || this.checkPunctuator(","));
|
|
834
|
+
const list = [item()];
|
|
835
|
+
while (this.matchPunctuator(",")) list.push(item());
|
|
836
|
+
return list;
|
|
837
|
+
}
|
|
838
|
+
/** A type annotation, or none when it could not be parsed. */
|
|
839
|
+
typeOr(stop) {
|
|
840
|
+
return this.attempt(() => this.parseType(), stop, () => void 0);
|
|
841
|
+
}
|
|
842
|
+
errorExpression(start, from) {
|
|
843
|
+
if (this.cursor > from) return { type: "ErrorExpression", ...spanFrom(start, this.previous()) };
|
|
844
|
+
return {
|
|
845
|
+
type: "ErrorExpression",
|
|
846
|
+
line: { start: start.line.start, end: start.line.start },
|
|
847
|
+
column: { start: start.column.start, end: start.column.start }
|
|
848
|
+
};
|
|
849
|
+
}
|
|
850
|
+
/** A closing bracket; in recovery mode a missing one is recorded and the
|
|
851
|
+
* construct ends where it is. */
|
|
852
|
+
expectCloser(value) {
|
|
853
|
+
if (this.matchPunctuator(value)) return;
|
|
854
|
+
if (!this.recover) this.error(`Expected '${value}'`);
|
|
855
|
+
this.softError(`Expected '${value}'`);
|
|
856
|
+
}
|
|
857
|
+
/** `then` / `do` / `in`; in recovery mode a missing one is recorded and
|
|
858
|
+
* what follows is read as if it were there. */
|
|
859
|
+
expectKeywordSoft(value) {
|
|
860
|
+
if (this.matchKeyword(value)) return;
|
|
861
|
+
if (!this.recover) this.error(`Expected keyword '${value}'`);
|
|
862
|
+
this.softError(`Expected keyword '${value}'`);
|
|
863
|
+
}
|
|
864
|
+
/** The `end` of the block `opener` began. */
|
|
865
|
+
expectEnd(opener) {
|
|
866
|
+
if (this.checkKeyword("end") && !this.endBelongsOutside(opener)) {
|
|
705
867
|
this.advance();
|
|
706
|
-
|
|
707
|
-
|
|
868
|
+
return;
|
|
869
|
+
}
|
|
870
|
+
if (!this.recover) this.error("Expected keyword 'end'");
|
|
871
|
+
this.softError(`Expected 'end' to close '${this.describeToken(opener)}' on line ${opener.line.start}`);
|
|
872
|
+
this.missingEnd = true;
|
|
873
|
+
}
|
|
874
|
+
/** Indentation mode: an `end` indented less than the line that opened the
|
|
875
|
+
* block closes something outside it. */
|
|
876
|
+
endBelongsOutside(opener) {
|
|
877
|
+
if (!this.indentation) return false;
|
|
878
|
+
const t = this.current();
|
|
879
|
+
return t.line.start > opener.line.start && t.column.start < this.indentOf(opener);
|
|
880
|
+
}
|
|
881
|
+
/** Indentation mode: a statement indented no deeper than the line that
|
|
882
|
+
* opened the block is past the block. */
|
|
883
|
+
dedentedPast(opener) {
|
|
884
|
+
if (!this.indentation || !opener) return false;
|
|
885
|
+
const t = this.current();
|
|
886
|
+
return t.line.start > opener.line.start && t.column.start <= this.indentOf(opener);
|
|
887
|
+
}
|
|
888
|
+
indentOf(token) {
|
|
889
|
+
if (!this.lineIndent) {
|
|
890
|
+
this.lineIndent = /* @__PURE__ */ new Map();
|
|
891
|
+
for (const t of this.tokens) {
|
|
892
|
+
if (!this.lineIndent.has(t.line.start)) this.lineIndent.set(t.line.start, t.column.start);
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
return this.lineIndent.get(token.line.start) ?? token.column.start;
|
|
896
|
+
}
|
|
897
|
+
/** Is the current token on a later line than the one before it? */
|
|
898
|
+
onNewLine() {
|
|
899
|
+
const previous = this.previous();
|
|
900
|
+
return previous !== void 0 && this.current().line.start > previous.line.end;
|
|
901
|
+
}
|
|
902
|
+
/** Recovery: move past what could not be parsed.
|
|
903
|
+
*
|
|
904
|
+
* Skipping stops at a token `stop` accepts, at a bracket closing something
|
|
905
|
+
* opened before the skip, or at a keyword that starts a statement. The
|
|
906
|
+
* tokens from `from` on — including those the failed attempt already
|
|
907
|
+
* consumed — count towards nesting, so a bracket or a `function ... end`
|
|
908
|
+
* is skipped whole and an `end` or `}` inside it cannot end what encloses
|
|
909
|
+
* it. A statement keyword inside brackets but outside any function means a
|
|
910
|
+
* bracket was never closed, and it stops the skip as well. */
|
|
911
|
+
skip(stop, from, mode) {
|
|
912
|
+
const closers = [];
|
|
913
|
+
for (let i = from; i < this.cursor; i++) this.nest(this.tokens[i], closers, mode);
|
|
914
|
+
while (!this.isAtEnd()) {
|
|
915
|
+
if (this.stopsSkip(closers, stop, mode)) return;
|
|
916
|
+
const t = this.advance();
|
|
917
|
+
this.nest(t, closers, mode);
|
|
918
|
+
if (mode === "statement" && closers.length === 0 && t.type === "Punctuator" && t.value === ";") return;
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
nest(t, closers, mode) {
|
|
922
|
+
const value = t.value;
|
|
923
|
+
const popTo = (closer) => {
|
|
924
|
+
const at = closers.lastIndexOf(closer);
|
|
925
|
+
if (at >= 0) closers.length = at;
|
|
926
|
+
};
|
|
927
|
+
if (t.type === "Punctuator") {
|
|
928
|
+
if (value === "(") closers.push(")");
|
|
929
|
+
else if (value === "[") closers.push("]");
|
|
930
|
+
else if (value === "{") closers.push("}");
|
|
931
|
+
else if (value === ")" || value === "]" || value === "}") popTo(value);
|
|
932
|
+
return;
|
|
933
|
+
}
|
|
934
|
+
if (t.type !== "Keyword") return;
|
|
935
|
+
const inBody = closers.includes("end") || closers.includes("until") || mode === "statement" && closers.length === 0;
|
|
936
|
+
switch (value) {
|
|
937
|
+
case "function":
|
|
938
|
+
closers.push("end");
|
|
939
|
+
return;
|
|
940
|
+
case "if":
|
|
941
|
+
closers.push(inBody ? "end" : "else");
|
|
942
|
+
return;
|
|
943
|
+
case "do":
|
|
944
|
+
if (inBody) closers.push("end");
|
|
945
|
+
return;
|
|
946
|
+
case "repeat":
|
|
947
|
+
if (inBody) closers.push("until");
|
|
948
|
+
return;
|
|
949
|
+
case "else":
|
|
950
|
+
if (closers[closers.length - 1] === "else") closers.pop();
|
|
951
|
+
return;
|
|
952
|
+
case "end":
|
|
953
|
+
popTo("end");
|
|
954
|
+
return;
|
|
955
|
+
case "until":
|
|
956
|
+
popTo("until");
|
|
957
|
+
return;
|
|
708
958
|
}
|
|
709
959
|
}
|
|
960
|
+
stopsSkip(closers, stop, mode) {
|
|
961
|
+
const t = this.current();
|
|
962
|
+
const value = t.value;
|
|
963
|
+
const inFunction = closers.includes("end") || closers.includes("until");
|
|
964
|
+
if (!inFunction && t.type === "Keyword" && typeof value === "string") {
|
|
965
|
+
const inIfExpression = closers.includes("else") && (value === "then" || value === "elseif" || value === "else");
|
|
966
|
+
if (STATEMENT_KEYWORDS.has(value) && !inIfExpression && !(mode === "statement" && value === "then")) return true;
|
|
967
|
+
if (mode === "statement" && closers.length === 0 && (value === "if" || value === "function" && this.peek(1).type === "Identifier")) return true;
|
|
968
|
+
}
|
|
969
|
+
if (closers.length) return false;
|
|
970
|
+
if (t.type === "Punctuator" && (value === ")" || value === "]" || value === "}")) return true;
|
|
971
|
+
if (mode === "statement" && t.type === "Punctuator" && value === "@") return true;
|
|
972
|
+
if (mode === "expression" && t.type === "Punctuator" && value === ";") return true;
|
|
973
|
+
return stop();
|
|
974
|
+
}
|
|
710
975
|
describeToken(t) {
|
|
711
976
|
if (t.type === "EOF") return "<eof>";
|
|
712
977
|
if ("value" in t) return String(t.value);
|
|
@@ -719,16 +984,13 @@ var Parser = class {
|
|
|
719
984
|
const start = this.current();
|
|
720
985
|
const body = this.parseBlock();
|
|
721
986
|
if (!this.isAtEnd()) {
|
|
722
|
-
if (this.recover)
|
|
723
|
-
|
|
724
|
-
this.
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
t.column.start
|
|
728
|
-
));
|
|
729
|
-
} else {
|
|
730
|
-
this.error("Expected end of file");
|
|
987
|
+
if (!this.recover) this.error("Expected end of file");
|
|
988
|
+
while (!this.isAtEnd()) {
|
|
989
|
+
this.softError("Expected end of file");
|
|
990
|
+
this.advance();
|
|
991
|
+
body.statements.push(...this.parseBlock().statements);
|
|
731
992
|
}
|
|
993
|
+
Object.assign(body, spanFrom(body, this.previous() ?? start));
|
|
732
994
|
}
|
|
733
995
|
return { type: "Program", body, ...spanFrom(start, this.previous() ?? start) };
|
|
734
996
|
}
|
|
@@ -738,11 +1000,14 @@ var Parser = class {
|
|
|
738
1000
|
isBlockEnd() {
|
|
739
1001
|
return this.isAtEnd() || this.checkKeyword("end") || this.checkKeyword("else") || this.checkKeyword("elseif") || this.checkKeyword("until");
|
|
740
1002
|
}
|
|
741
|
-
|
|
1003
|
+
/** `opener` is the token that began the block (`if`, `function`, ...), for
|
|
1004
|
+
* indentation recovery. */
|
|
1005
|
+
parseBlock(opener) {
|
|
742
1006
|
const start = this.current();
|
|
743
1007
|
const statements = [];
|
|
744
1008
|
while (!this.isBlockEnd()) {
|
|
745
1009
|
if (this.matchPunctuator(";")) continue;
|
|
1010
|
+
if (this.dedentedPast(opener)) break;
|
|
746
1011
|
if (this.recover) {
|
|
747
1012
|
const at = this.cursor;
|
|
748
1013
|
const errStart = this.current();
|
|
@@ -756,11 +1021,11 @@ var Parser = class {
|
|
|
756
1021
|
} catch (e) {
|
|
757
1022
|
if (e instanceof ParseRecover) {
|
|
758
1023
|
} else if (e instanceof ParseError) {
|
|
759
|
-
this.
|
|
1024
|
+
this.record(e);
|
|
760
1025
|
} else {
|
|
761
1026
|
throw e;
|
|
762
1027
|
}
|
|
763
|
-
this.
|
|
1028
|
+
this.skip(() => false, at, "statement");
|
|
764
1029
|
if (this.cursor === at) {
|
|
765
1030
|
if (this.isAtEnd()) break;
|
|
766
1031
|
this.advance();
|
|
@@ -796,23 +1061,14 @@ var Parser = class {
|
|
|
796
1061
|
if (t.type === "Punctuator" && t.value === "@") {
|
|
797
1062
|
const { attributes, start } = this.parseAttributes();
|
|
798
1063
|
const next = this.current();
|
|
799
|
-
if (next.type === "Keyword" && (next.value === "const" || next.value === "let")) {
|
|
800
|
-
const stmt = this.parseVariableDeclaration();
|
|
801
|
-
if (stmt.type === "FunctionDeclaration") {
|
|
802
|
-
stmt.attributes = attributes;
|
|
803
|
-
stmt.line.start = start.line.start;
|
|
804
|
-
stmt.column.start = start.column.start;
|
|
805
|
-
}
|
|
806
|
-
return stmt;
|
|
807
|
-
}
|
|
808
1064
|
if (next.type === "Keyword" && next.value === "function") {
|
|
809
|
-
const stmt = this.
|
|
1065
|
+
const stmt = this.parseFunctionStatement();
|
|
810
1066
|
stmt.attributes = attributes;
|
|
811
1067
|
stmt.line.start = start.line.start;
|
|
812
1068
|
stmt.column.start = start.column.start;
|
|
813
1069
|
return stmt;
|
|
814
1070
|
}
|
|
815
|
-
throw new ParseError("Expected 'function'
|
|
1071
|
+
throw new ParseError("Expected 'function' after an attribute", next.line.start, next.column.start);
|
|
816
1072
|
}
|
|
817
1073
|
if (t.type === "Keyword") {
|
|
818
1074
|
switch (t.value) {
|
|
@@ -830,7 +1086,7 @@ var Parser = class {
|
|
|
830
1086
|
case "for":
|
|
831
1087
|
return this.parseForStatement();
|
|
832
1088
|
case "function":
|
|
833
|
-
return this.
|
|
1089
|
+
return this.parseFunctionStatement();
|
|
834
1090
|
case "return":
|
|
835
1091
|
return this.parseReturnStatement();
|
|
836
1092
|
case "import":
|
|
@@ -847,6 +1103,10 @@ var Parser = class {
|
|
|
847
1103
|
}
|
|
848
1104
|
}
|
|
849
1105
|
}
|
|
1106
|
+
if (this.recover && t.type === "Identifier" && t.value === "local" && (this.peek(1).type === "Identifier" || this.checkPunctuatorAt(1, "{") || this.checkPunctuatorAt(1, "["))) {
|
|
1107
|
+
this.softError("luaut has no 'local'; declare with 'const' or 'let'");
|
|
1108
|
+
return this.parseVariableDeclaration("let");
|
|
1109
|
+
}
|
|
850
1110
|
if (t.type === "Identifier" && t.value === "type" && this.peek(1).type === "Identifier") {
|
|
851
1111
|
return this.parseTypeAliasStatement();
|
|
852
1112
|
}
|
|
@@ -932,20 +1192,30 @@ var Parser = class {
|
|
|
932
1192
|
parseImportStatement() {
|
|
933
1193
|
const start = this.current();
|
|
934
1194
|
this.advance();
|
|
1195
|
+
const next = this.peek(1);
|
|
1196
|
+
const isTypeOnly = this.checkIdentifierValue("type") && (next.type === "Punctuator" && next.value === "{" || next.type === "Operator" && next.value === "*" || next.type === "Identifier");
|
|
1197
|
+
if (isTypeOnly) this.advance();
|
|
935
1198
|
let defaultImport;
|
|
936
1199
|
const specifiers = [];
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
this.
|
|
942
|
-
this.
|
|
943
|
-
this.
|
|
1200
|
+
let namespaceImport;
|
|
1201
|
+
const parseBindings = () => {
|
|
1202
|
+
if (this.checkOperator("*")) {
|
|
1203
|
+
this.advance();
|
|
1204
|
+
if (!this.checkKeyword("as")) this.error("Expected 'as' after 'import *'");
|
|
1205
|
+
this.advance();
|
|
1206
|
+
namespaceImport = this.parseIdentifier();
|
|
1207
|
+
return;
|
|
944
1208
|
}
|
|
945
|
-
} else {
|
|
946
1209
|
this.expectPunctuator("{");
|
|
947
1210
|
this.parseImportSpecifierList(specifiers);
|
|
948
1211
|
this.expectPunctuator("}");
|
|
1212
|
+
};
|
|
1213
|
+
if (this.checkType("Identifier")) {
|
|
1214
|
+
const nameTok = this.expectIdentifier();
|
|
1215
|
+
defaultImport = { type: "Identifier", name: nameTok.value, ...spanFrom(nameTok, nameTok) };
|
|
1216
|
+
if (this.matchPunctuator(",")) parseBindings();
|
|
1217
|
+
} else {
|
|
1218
|
+
parseBindings();
|
|
949
1219
|
}
|
|
950
1220
|
if (!this.checkKeyword("from")) {
|
|
951
1221
|
this.error("Expected 'from' in import statement");
|
|
@@ -962,7 +1232,15 @@ var Parser = class {
|
|
|
962
1232
|
raw: sourceTok.raw,
|
|
963
1233
|
...spanFrom(sourceTok, sourceTok)
|
|
964
1234
|
};
|
|
965
|
-
return {
|
|
1235
|
+
return {
|
|
1236
|
+
type: "ImportStatement",
|
|
1237
|
+
defaultImport,
|
|
1238
|
+
namespaceImport,
|
|
1239
|
+
specifiers,
|
|
1240
|
+
source,
|
|
1241
|
+
isTypeOnly: isTypeOnly || void 0,
|
|
1242
|
+
...spanFrom(start, this.previous())
|
|
1243
|
+
};
|
|
966
1244
|
}
|
|
967
1245
|
parseImportSpecifierList(out) {
|
|
968
1246
|
if (this.checkPunctuator("}")) return;
|
|
@@ -998,7 +1276,7 @@ var Parser = class {
|
|
|
998
1276
|
...spanFrom(sourceTok, sourceTok)
|
|
999
1277
|
};
|
|
1000
1278
|
}
|
|
1001
|
-
// `export const ...` / `export let ...` / `export
|
|
1279
|
+
// `export const ...` / `export let ...` / `export function ...` /
|
|
1002
1280
|
// `export type ...` / `export default <expr>`
|
|
1003
1281
|
parseExportStatement() {
|
|
1004
1282
|
const start = this.current();
|
|
@@ -1016,6 +1294,11 @@ var Parser = class {
|
|
|
1016
1294
|
const declaration = this.parseVariableDeclaration();
|
|
1017
1295
|
return { type: "ExportStatement", declaration, ...spanFrom(start, this.previous()) };
|
|
1018
1296
|
}
|
|
1297
|
+
if (this.checkKeyword("function")) {
|
|
1298
|
+
const declaration = this.parseFunctionStatement(true);
|
|
1299
|
+
if (declaration.type !== "FunctionDeclaration") this.error("An exported function needs a plain name: 'export function name()'");
|
|
1300
|
+
return { type: "ExportStatement", declaration, ...spanFrom(start, this.previous()) };
|
|
1301
|
+
}
|
|
1019
1302
|
if (this.checkPunctuator("{")) {
|
|
1020
1303
|
this.advance();
|
|
1021
1304
|
const specifiers = [];
|
|
@@ -1039,15 +1322,17 @@ var Parser = class {
|
|
|
1039
1322
|
const source = this.parseModuleSource();
|
|
1040
1323
|
return { type: "ExportAllStatement", source, ...spanFrom(start, this.previous()) };
|
|
1041
1324
|
}
|
|
1042
|
-
this.error("Expected 'const', 'let', 'type', 'default', '{' or '*' after 'export'");
|
|
1325
|
+
this.error("Expected 'const', 'let', 'function', 'type', 'default', '{' or '*' after 'export'");
|
|
1043
1326
|
}
|
|
1044
|
-
// `const x = ...` / `let x, y =
|
|
1327
|
+
// `const x = ...` / `let x, y = ...`.
|
|
1045
1328
|
// luaut has no `local` — `const` bindings are immutable, `let` mutable.
|
|
1046
|
-
|
|
1329
|
+
/** `kind` reads the leading word as that keyword (recovery's `local`). */
|
|
1330
|
+
parseVariableDeclaration(as) {
|
|
1047
1331
|
const start = this.current();
|
|
1048
|
-
const
|
|
1049
|
-
|
|
1050
|
-
|
|
1332
|
+
const word = this.advance().value;
|
|
1333
|
+
const kind = as ?? word;
|
|
1334
|
+
if (this.checkKeyword("function")) {
|
|
1335
|
+
this.error(`A function is declared as 'function name()'; '${kind}' does not apply to functions`);
|
|
1051
1336
|
}
|
|
1052
1337
|
const names = [this.parseBindingTarget(true)];
|
|
1053
1338
|
while (this.matchPunctuator(",")) {
|
|
@@ -1055,99 +1340,84 @@ var Parser = class {
|
|
|
1055
1340
|
}
|
|
1056
1341
|
let init = [];
|
|
1057
1342
|
if (this.matchOperator("=")) {
|
|
1058
|
-
init = this.
|
|
1343
|
+
init = this.expressionListOr(() => false);
|
|
1059
1344
|
} else if (kind === "const") {
|
|
1060
|
-
this.error("'const' declaration requires an initializer");
|
|
1345
|
+
if (!this.recover) this.error("'const' declaration requires an initializer");
|
|
1346
|
+
this.softError("'const' declaration requires an initializer");
|
|
1061
1347
|
}
|
|
1062
1348
|
return { type: "VariableDeclaration", kind, names, init, ...spanFrom(start, this.previous()) };
|
|
1063
1349
|
}
|
|
1064
|
-
/** `const/let function` — `function` already consumed. Collects TS-style
|
|
1065
|
-
* overload signatures. */
|
|
1066
|
-
parseFunctionDeclarationRest(start, kind) {
|
|
1067
|
-
const name = this.parseIdentifier();
|
|
1068
|
-
const signatures = [];
|
|
1069
|
-
while (true) {
|
|
1070
|
-
const head = this.parseFunctionHead();
|
|
1071
|
-
if (this.isOverloadContinuation(name.name, kind)) {
|
|
1072
|
-
signatures.push(this.headToSignature(head));
|
|
1073
|
-
this.advance();
|
|
1074
|
-
this.expectKeyword("function");
|
|
1075
|
-
this.parseIdentifier();
|
|
1076
|
-
continue;
|
|
1077
|
-
}
|
|
1078
|
-
const func = this.headToBody(head);
|
|
1079
|
-
return {
|
|
1080
|
-
type: "FunctionDeclaration",
|
|
1081
|
-
kind,
|
|
1082
|
-
name,
|
|
1083
|
-
func,
|
|
1084
|
-
signatures: signatures.length ? signatures : void 0,
|
|
1085
|
-
...spanFrom(start, this.previous())
|
|
1086
|
-
};
|
|
1087
|
-
}
|
|
1088
|
-
}
|
|
1089
1350
|
parseIfStatement() {
|
|
1090
1351
|
const start = this.current();
|
|
1091
1352
|
this.expectKeyword("if");
|
|
1092
1353
|
const clauses = [];
|
|
1093
|
-
const
|
|
1094
|
-
this.
|
|
1095
|
-
|
|
1354
|
+
const untilThen = () => this.checkKeyword("then");
|
|
1355
|
+
const cond = this.expressionOr(untilThen);
|
|
1356
|
+
this.expectKeywordSoft("then");
|
|
1357
|
+
const body = this.parseBlock(start);
|
|
1096
1358
|
clauses.push({ type: "IfClause", condition: cond, body, ...spanFrom(cond, this.previous()) });
|
|
1097
1359
|
while (this.checkKeyword("elseif")) {
|
|
1098
1360
|
const clauseStart = this.current();
|
|
1099
1361
|
this.advance();
|
|
1100
|
-
const c = this.
|
|
1101
|
-
this.
|
|
1102
|
-
const b = this.parseBlock();
|
|
1362
|
+
const c = this.expressionOr(untilThen);
|
|
1363
|
+
this.expectKeywordSoft("then");
|
|
1364
|
+
const b = this.parseBlock(start);
|
|
1103
1365
|
clauses.push({ type: "IfClause", condition: c, body: b, ...spanFrom(clauseStart, this.previous()) });
|
|
1104
1366
|
}
|
|
1105
1367
|
let alternate;
|
|
1106
1368
|
if (this.matchKeyword("else")) {
|
|
1107
|
-
alternate = this.parseBlock();
|
|
1369
|
+
alternate = this.parseBlock(start);
|
|
1108
1370
|
}
|
|
1109
|
-
this.
|
|
1371
|
+
this.expectEnd(start);
|
|
1110
1372
|
return { type: "IfStatement", clauses, alternate, ...spanFrom(start, this.previous()) };
|
|
1111
1373
|
}
|
|
1112
1374
|
parseWhileStatement() {
|
|
1113
1375
|
const start = this.current();
|
|
1114
1376
|
this.expectKeyword("while");
|
|
1115
|
-
const condition = this.
|
|
1116
|
-
this.
|
|
1117
|
-
const body = this.parseBlock();
|
|
1118
|
-
this.
|
|
1377
|
+
const condition = this.expressionOr(() => this.checkKeyword("do"));
|
|
1378
|
+
this.expectKeywordSoft("do");
|
|
1379
|
+
const body = this.parseBlock(start);
|
|
1380
|
+
this.expectEnd(start);
|
|
1119
1381
|
return { type: "WhileStatement", condition, body, ...spanFrom(start, this.previous()) };
|
|
1120
1382
|
}
|
|
1121
1383
|
parseRepeatStatement() {
|
|
1122
1384
|
const start = this.current();
|
|
1123
1385
|
this.expectKeyword("repeat");
|
|
1124
|
-
const body = this.parseBlock();
|
|
1125
|
-
|
|
1126
|
-
|
|
1386
|
+
const body = this.parseBlock(start);
|
|
1387
|
+
let condition;
|
|
1388
|
+
if (this.checkKeyword("until") || !this.recover) {
|
|
1389
|
+
this.expectKeyword("until");
|
|
1390
|
+
condition = this.expressionOr(() => false);
|
|
1391
|
+
} else {
|
|
1392
|
+
this.softError(`Expected 'until' to close 'repeat' on line ${start.line.start}`);
|
|
1393
|
+
this.missingEnd = true;
|
|
1394
|
+
condition = this.errorExpression(this.current(), this.cursor);
|
|
1395
|
+
}
|
|
1127
1396
|
return { type: "RepeatStatement", body, condition, ...spanFrom(start, this.previous()) };
|
|
1128
1397
|
}
|
|
1129
1398
|
parseDoStatement() {
|
|
1130
1399
|
const start = this.current();
|
|
1131
1400
|
this.expectKeyword("do");
|
|
1132
|
-
const body = this.parseBlock();
|
|
1133
|
-
this.
|
|
1401
|
+
const body = this.parseBlock(start);
|
|
1402
|
+
this.expectEnd(start);
|
|
1134
1403
|
return { type: "DoStatement", body, ...spanFrom(start, this.previous()) };
|
|
1135
1404
|
}
|
|
1136
1405
|
parseForStatement() {
|
|
1137
1406
|
const start = this.current();
|
|
1138
1407
|
this.expectKeyword("for");
|
|
1139
1408
|
const first = this.parseBindingTarget(true);
|
|
1409
|
+
const untilDo = () => this.checkKeyword("do");
|
|
1140
1410
|
if (first.type === "IdentifierPattern" && this.matchOperator("=")) {
|
|
1141
|
-
const from = this.
|
|
1411
|
+
const from = this.expressionOr(() => untilDo() || this.checkPunctuator(","));
|
|
1142
1412
|
this.expectPunctuator(",");
|
|
1143
|
-
const to = this.
|
|
1413
|
+
const to = this.expressionOr(() => untilDo() || this.checkPunctuator(","));
|
|
1144
1414
|
let step;
|
|
1145
1415
|
if (this.matchPunctuator(",")) {
|
|
1146
|
-
step = this.
|
|
1416
|
+
step = this.expressionOr(untilDo);
|
|
1147
1417
|
}
|
|
1148
|
-
this.
|
|
1149
|
-
const body2 = this.parseBlock();
|
|
1150
|
-
this.
|
|
1418
|
+
this.expectKeywordSoft("do");
|
|
1419
|
+
const body2 = this.parseBlock(start);
|
|
1420
|
+
this.expectEnd(start);
|
|
1151
1421
|
return {
|
|
1152
1422
|
type: "NumericForStatement",
|
|
1153
1423
|
variable: this.identifierPatternToTypedIdentifier(first),
|
|
@@ -1163,10 +1433,10 @@ var Parser = class {
|
|
|
1163
1433
|
variables.push(this.parseBindingTarget(true));
|
|
1164
1434
|
}
|
|
1165
1435
|
this.expectKeyword("in");
|
|
1166
|
-
const iterators = this.
|
|
1167
|
-
this.
|
|
1168
|
-
const body = this.parseBlock();
|
|
1169
|
-
this.
|
|
1436
|
+
const iterators = this.expressionListOr(untilDo);
|
|
1437
|
+
this.expectKeywordSoft("do");
|
|
1438
|
+
const body = this.parseBlock(start);
|
|
1439
|
+
this.expectEnd(start);
|
|
1170
1440
|
return {
|
|
1171
1441
|
type: "GenericForStatement",
|
|
1172
1442
|
variables,
|
|
@@ -1175,22 +1445,41 @@ var Parser = class {
|
|
|
1175
1445
|
...spanFrom(start, this.previous())
|
|
1176
1446
|
};
|
|
1177
1447
|
}
|
|
1178
|
-
|
|
1448
|
+
/** `function name() end` declares `name`; `function a.b() end` and
|
|
1449
|
+
* `function T:m() end` define a member. */
|
|
1450
|
+
/** `exported` — the `export` before this `function` has been consumed, so
|
|
1451
|
+
* each overload signature after it must carry one as well. */
|
|
1452
|
+
parseFunctionStatement(exported = false) {
|
|
1179
1453
|
const start = this.current();
|
|
1180
1454
|
this.expectKeyword("function");
|
|
1181
1455
|
const target = this.parseFunctionName();
|
|
1182
1456
|
const isMethod = target.method !== void 0;
|
|
1183
1457
|
const simpleName = !isMethod && target.path.length === 0 ? target.base.name : void 0;
|
|
1184
1458
|
const signatures = [];
|
|
1459
|
+
let written = target.base;
|
|
1185
1460
|
while (true) {
|
|
1186
1461
|
const head = this.parseFunctionHead();
|
|
1187
1462
|
if (simpleName !== void 0 && this.isOverloadContinuation(simpleName)) {
|
|
1188
|
-
signatures.push(this.headToSignature(head));
|
|
1463
|
+
signatures.push({ ...this.headToSignature(head), name: written });
|
|
1464
|
+
const nextExported = this.matchKeyword("export");
|
|
1465
|
+
if (nextExported !== exported) {
|
|
1466
|
+
this.problem("Overload signatures must all be exported or non-exported");
|
|
1467
|
+
}
|
|
1189
1468
|
this.expectKeyword("function");
|
|
1190
|
-
this.parseFunctionName();
|
|
1469
|
+
written = this.parseFunctionName().base;
|
|
1191
1470
|
continue;
|
|
1192
1471
|
}
|
|
1193
|
-
const func = this.headToBody(head);
|
|
1472
|
+
const func = this.headToBody(head, start);
|
|
1473
|
+
if (simpleName !== void 0) {
|
|
1474
|
+
return {
|
|
1475
|
+
type: "FunctionDeclaration",
|
|
1476
|
+
name: target.base,
|
|
1477
|
+
func,
|
|
1478
|
+
signatures: signatures.length ? signatures : void 0,
|
|
1479
|
+
implementationName: signatures.length ? written : void 0,
|
|
1480
|
+
...spanFrom(start, this.previous())
|
|
1481
|
+
};
|
|
1482
|
+
}
|
|
1194
1483
|
if (isMethod) {
|
|
1195
1484
|
func.params.unshift({ type: "FunctionParameter", name: "self", ...spanFrom(target, target) });
|
|
1196
1485
|
func.isMethod = true;
|
|
@@ -1207,13 +1496,20 @@ var Parser = class {
|
|
|
1207
1496
|
}
|
|
1208
1497
|
/** After a bodyless function head, is the next token the start of another
|
|
1209
1498
|
* declaration for the same simple `name` (making the head an overload
|
|
1210
|
-
* signature rather than an implementation)?
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
if (
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1499
|
+
* signature rather than an implementation)? */
|
|
1500
|
+
isOverloadContinuation(name) {
|
|
1501
|
+
const named = (offset) => this.peek(offset).type === "Identifier" && this.peek(offset).value === name;
|
|
1502
|
+
if (this.checkKeyword("function")) return named(1);
|
|
1503
|
+
return this.checkKeyword("export") && this.peek(1).type === "Keyword" && this.peek(1).value === "function" && named(2);
|
|
1504
|
+
}
|
|
1505
|
+
/** A mistake that does not stop the parse: refused outside recovery, where
|
|
1506
|
+
* the compiler must not accept it, and recorded inside. The message says
|
|
1507
|
+
* what is wrong on its own — no token is appended. */
|
|
1508
|
+
problem(message) {
|
|
1509
|
+
const t = this.current();
|
|
1510
|
+
const error = new ParseError(message, t.line.start, t.column.start);
|
|
1511
|
+
if (!this.recover) throw error;
|
|
1512
|
+
this.record(error);
|
|
1217
1513
|
}
|
|
1218
1514
|
parseFunctionName() {
|
|
1219
1515
|
const start = this.current();
|
|
@@ -1249,7 +1545,7 @@ var Parser = class {
|
|
|
1249
1545
|
this.expectKeyword("return");
|
|
1250
1546
|
let args = [];
|
|
1251
1547
|
if (this.isExpressionStart()) {
|
|
1252
|
-
args = this.
|
|
1548
|
+
args = this.expressionListOr(() => false);
|
|
1253
1549
|
}
|
|
1254
1550
|
return { type: "ReturnStatement", arguments: args, ...spanFrom(start, this.previous()) };
|
|
1255
1551
|
}
|
|
@@ -1281,7 +1577,7 @@ var Parser = class {
|
|
|
1281
1577
|
targets.push(this.parseAssignTarget());
|
|
1282
1578
|
}
|
|
1283
1579
|
this.expectOperator("=");
|
|
1284
|
-
const values = this.
|
|
1580
|
+
const values = this.expressionListOr(() => false);
|
|
1285
1581
|
return { type: "AssignmentStatement", targets, values, ...spanFrom(start, this.previous()) };
|
|
1286
1582
|
}
|
|
1287
1583
|
const first = this.parsePrefixExpression();
|
|
@@ -1290,14 +1586,16 @@ var Parser = class {
|
|
|
1290
1586
|
while (this.matchPunctuator(",")) {
|
|
1291
1587
|
targets.push(this.parseAssignTarget());
|
|
1292
1588
|
}
|
|
1589
|
+
for (const target of targets) this.rejectOptionalTarget(target);
|
|
1293
1590
|
this.expectOperator("=");
|
|
1294
|
-
const values = this.
|
|
1591
|
+
const values = this.expressionListOr(() => false);
|
|
1295
1592
|
return { type: "AssignmentStatement", targets, values, ...spanFrom(start, this.previous()) };
|
|
1296
1593
|
}
|
|
1297
1594
|
const t = this.current();
|
|
1298
1595
|
if (t.type === "Operator" && COMPOUND_ASSIGN_OPS.has(t.value)) {
|
|
1596
|
+
this.rejectOptionalTarget(first);
|
|
1299
1597
|
const op = this.advance().value;
|
|
1300
|
-
const value = this.
|
|
1598
|
+
const value = this.expressionOr(() => false);
|
|
1301
1599
|
return {
|
|
1302
1600
|
type: "CompoundAssignmentStatement",
|
|
1303
1601
|
operator: op,
|
|
@@ -1335,15 +1633,43 @@ var Parser = class {
|
|
|
1335
1633
|
* rather than the `:` of a ternary (`cond ? obj : other`)? Lua requires a
|
|
1336
1634
|
* method call to be called, so the answer is exact rather than heuristic:
|
|
1337
1635
|
* `:` Identifier followed by one of Lua's call forms. */
|
|
1338
|
-
startsMethodCall() {
|
|
1339
|
-
if (this.peek(1).type !== "Identifier") return false;
|
|
1340
|
-
const after = this.peek(2);
|
|
1636
|
+
startsMethodCall(offset = 0) {
|
|
1637
|
+
if (this.peek(offset + 1).type !== "Identifier") return false;
|
|
1638
|
+
const after = this.peek(offset + 2);
|
|
1341
1639
|
if (after.type === "Punctuator") {
|
|
1342
1640
|
const v = String(after.value);
|
|
1343
1641
|
return v === "(" || v === "{";
|
|
1344
1642
|
}
|
|
1345
1643
|
if (after.type === "InterpolatedString") return true;
|
|
1346
|
-
|
|
1644
|
+
if (after.type === "Literal" && after.kind === "string") return true;
|
|
1645
|
+
if (after.type === "Operator" && String(after.value) === "<") {
|
|
1646
|
+
const save = this.cursor;
|
|
1647
|
+
this.cursor += offset + 2;
|
|
1648
|
+
const found = this.tryCallTypeArguments() !== void 0;
|
|
1649
|
+
this.cursor = save;
|
|
1650
|
+
return found;
|
|
1651
|
+
}
|
|
1652
|
+
return false;
|
|
1653
|
+
}
|
|
1654
|
+
/** Does the next token start right where the current one ends? */
|
|
1655
|
+
touchesNext() {
|
|
1656
|
+
const current = this.current();
|
|
1657
|
+
const next = this.peek(1);
|
|
1658
|
+
return current.line.end === next.line.start && current.column.end === next.column.start;
|
|
1659
|
+
}
|
|
1660
|
+
/** `a?.b = 1` cannot be written: there may be nothing to assign to. */
|
|
1661
|
+
rejectOptionalTarget(target) {
|
|
1662
|
+
for (let e = target; e && typeof e === "object"; ) {
|
|
1663
|
+
const node = e;
|
|
1664
|
+
if (node.optional) {
|
|
1665
|
+
const at = target;
|
|
1666
|
+
const err = new ParseError("An optional chain cannot be assigned to", at.line.start, at.column.start);
|
|
1667
|
+
if (!this.recover) throw err;
|
|
1668
|
+
this.record(err);
|
|
1669
|
+
return;
|
|
1670
|
+
}
|
|
1671
|
+
e = node.type === "MemberExpression" || node.type === "IndexExpression" || node.type === "MethodCallExpression" ? node.object : node.type === "CallExpression" ? node.callee : void 0;
|
|
1672
|
+
}
|
|
1347
1673
|
}
|
|
1348
1674
|
isUnaryOperator() {
|
|
1349
1675
|
const t = this.current();
|
|
@@ -1460,7 +1786,7 @@ var Parser = class {
|
|
|
1460
1786
|
}
|
|
1461
1787
|
if (t.type === "Keyword" && t.value === "function") {
|
|
1462
1788
|
this.advance();
|
|
1463
|
-
const func = this.parseFunctionBody();
|
|
1789
|
+
const func = this.parseFunctionBody(t);
|
|
1464
1790
|
return { type: "FunctionExpression", func, ...spanFrom(t, this.previous()) };
|
|
1465
1791
|
}
|
|
1466
1792
|
if (t.type === "Keyword" && t.value === "if") {
|
|
@@ -1483,7 +1809,19 @@ var Parser = class {
|
|
|
1483
1809
|
if (p.kind === "string") {
|
|
1484
1810
|
parts.push({ kind: "string", value: p.value, raw: p.raw });
|
|
1485
1811
|
} else {
|
|
1486
|
-
|
|
1812
|
+
let expression;
|
|
1813
|
+
try {
|
|
1814
|
+
expression = shiftSpans(parseExpressionFromSource(p.raw), p.line, p.column);
|
|
1815
|
+
} catch (e) {
|
|
1816
|
+
if (!this.recover || !(e instanceof ParseError || e instanceof LexError)) throw e;
|
|
1817
|
+
const at = token;
|
|
1818
|
+
this.record(new ParseError(
|
|
1819
|
+
`In '\${${p.raw}}': ${e.message.replace(/ \(\d+:\d+\)$/, "")}`,
|
|
1820
|
+
at.line.start,
|
|
1821
|
+
at.column.start
|
|
1822
|
+
));
|
|
1823
|
+
expression = { type: "ErrorExpression", ...spanFrom(at, at) };
|
|
1824
|
+
}
|
|
1487
1825
|
parts.push({ kind: "expression", expression });
|
|
1488
1826
|
}
|
|
1489
1827
|
}
|
|
@@ -1521,7 +1859,53 @@ var Parser = class {
|
|
|
1521
1859
|
this.error("Expected identifier or '('");
|
|
1522
1860
|
}
|
|
1523
1861
|
while (true) {
|
|
1862
|
+
if (this.checkPunctuator("?") && this.touchesNext()) {
|
|
1863
|
+
const next = this.peek(1);
|
|
1864
|
+
const punct = next.type === "Punctuator" ? String(next.value) : void 0;
|
|
1865
|
+
if (punct === "." && this.peek(2).type === "Identifier") {
|
|
1866
|
+
this.advance();
|
|
1867
|
+
this.advance();
|
|
1868
|
+
const prop = this.parseIdentifier();
|
|
1869
|
+
base = { type: "MemberExpression", object: base, property: prop, optional: true, ...spanFrom(base, prop) };
|
|
1870
|
+
continue;
|
|
1871
|
+
}
|
|
1872
|
+
if (punct === "." && this.punctuatorAt(2, "(")) {
|
|
1873
|
+
this.advance();
|
|
1874
|
+
this.advance();
|
|
1875
|
+
const args = this.parseCallArguments();
|
|
1876
|
+
base = {
|
|
1877
|
+
type: "CallExpression",
|
|
1878
|
+
callee: base,
|
|
1879
|
+
arguments: args,
|
|
1880
|
+
optional: true,
|
|
1881
|
+
...spanFrom(base, this.previous())
|
|
1882
|
+
};
|
|
1883
|
+
continue;
|
|
1884
|
+
}
|
|
1885
|
+
if (punct === ":" && this.startsMethodCall(1)) {
|
|
1886
|
+
this.advance();
|
|
1887
|
+
this.advance();
|
|
1888
|
+
const method = this.parseIdentifier();
|
|
1889
|
+
const typeArguments = this.tryCallTypeArguments();
|
|
1890
|
+
const args = this.parseCallArguments();
|
|
1891
|
+
base = {
|
|
1892
|
+
type: "MethodCallExpression",
|
|
1893
|
+
object: base,
|
|
1894
|
+
method,
|
|
1895
|
+
arguments: args,
|
|
1896
|
+
typeArguments,
|
|
1897
|
+
optional: true,
|
|
1898
|
+
...spanFrom(base, this.previous())
|
|
1899
|
+
};
|
|
1900
|
+
continue;
|
|
1901
|
+
}
|
|
1902
|
+
}
|
|
1524
1903
|
if (this.matchPunctuator(".")) {
|
|
1904
|
+
if (this.recover && !this.checkType("Identifier")) {
|
|
1905
|
+
this.softError("Expected identifier");
|
|
1906
|
+
base = { type: "ErrorExpression", ...spanFrom(base, this.previous()) };
|
|
1907
|
+
break;
|
|
1908
|
+
}
|
|
1525
1909
|
const prop = this.parseIdentifier();
|
|
1526
1910
|
base = { type: "MemberExpression", object: base, property: prop, ...spanFrom(base, prop) };
|
|
1527
1911
|
continue;
|
|
@@ -1535,17 +1919,33 @@ var Parser = class {
|
|
|
1535
1919
|
if (this.checkPunctuator(":") && this.startsMethodCall()) {
|
|
1536
1920
|
this.advance();
|
|
1537
1921
|
const method = this.parseIdentifier();
|
|
1922
|
+
const typeArguments = this.tryCallTypeArguments();
|
|
1538
1923
|
const args = this.parseCallArguments();
|
|
1539
1924
|
base = {
|
|
1540
1925
|
type: "MethodCallExpression",
|
|
1541
1926
|
object: base,
|
|
1542
1927
|
method,
|
|
1543
1928
|
arguments: args,
|
|
1929
|
+
typeArguments,
|
|
1544
1930
|
...spanFrom(base, this.previous())
|
|
1545
1931
|
};
|
|
1546
1932
|
continue;
|
|
1547
1933
|
}
|
|
1548
|
-
if (this.
|
|
1934
|
+
if (this.checkOperator("<")) {
|
|
1935
|
+
const typeArguments = this.tryCallTypeArguments();
|
|
1936
|
+
if (typeArguments) {
|
|
1937
|
+
const args = this.parseCallArguments();
|
|
1938
|
+
base = {
|
|
1939
|
+
type: "CallExpression",
|
|
1940
|
+
callee: base,
|
|
1941
|
+
arguments: args,
|
|
1942
|
+
typeArguments,
|
|
1943
|
+
...spanFrom(base, this.previous())
|
|
1944
|
+
};
|
|
1945
|
+
continue;
|
|
1946
|
+
}
|
|
1947
|
+
}
|
|
1948
|
+
if (this.startsCallArguments()) {
|
|
1549
1949
|
const args = this.parseCallArguments();
|
|
1550
1950
|
base = {
|
|
1551
1951
|
type: "CallExpression",
|
|
@@ -1559,6 +1959,11 @@ var Parser = class {
|
|
|
1559
1959
|
}
|
|
1560
1960
|
return base;
|
|
1561
1961
|
}
|
|
1962
|
+
/** Is the token `ahead` places on the punctuator `value`? */
|
|
1963
|
+
punctuatorAt(ahead, value) {
|
|
1964
|
+
const token = this.peek(ahead);
|
|
1965
|
+
return token.type === "Punctuator" && token.value === value;
|
|
1966
|
+
}
|
|
1562
1967
|
/** An assignment target after the first: a prefix expression (`a.b`,
|
|
1563
1968
|
* `a[i]`, `a`) or a nested destructuring pattern. */
|
|
1564
1969
|
parseAssignTarget() {
|
|
@@ -1566,14 +1971,52 @@ var Parser = class {
|
|
|
1566
1971
|
if (this.checkPunctuator("[")) return this.parseArrayPattern();
|
|
1567
1972
|
return this.parsePrefixExpression();
|
|
1568
1973
|
}
|
|
1974
|
+
/** Does a call's argument list start here? Lua's three forms: `(`, a
|
|
1975
|
+
* string, or a table. */
|
|
1976
|
+
startsCallArguments() {
|
|
1977
|
+
return this.checkPunctuator("(") || this.checkPunctuator("{") || this.checkType("InterpolatedString") || this.checkType("Literal") && this.current().kind === "string";
|
|
1978
|
+
}
|
|
1979
|
+
/** `f<A, B>(x)` — type arguments, when that is what this is. `a < b > (c)`
|
|
1980
|
+
* is three operators, and only what follows the `>` tells them apart, so
|
|
1981
|
+
* this reads ahead and puts the cursor back when the guess was wrong. */
|
|
1982
|
+
tryCallTypeArguments() {
|
|
1983
|
+
if (!this.checkOperator("<")) return void 0;
|
|
1984
|
+
const start = this.cursor;
|
|
1985
|
+
const errors = this.errors.length;
|
|
1986
|
+
try {
|
|
1987
|
+
this.advance();
|
|
1988
|
+
const list = [this.parseTypeArgument()];
|
|
1989
|
+
while (this.matchPunctuator(",") && !this.checkOperator(">")) list.push(this.parseTypeArgument());
|
|
1990
|
+
this.expectOperator(">");
|
|
1991
|
+
if (!this.startsCallArguments()) throw new ParseRecover("not a call");
|
|
1992
|
+
return list;
|
|
1993
|
+
} catch (e) {
|
|
1994
|
+
if (!(e instanceof ParseError || e instanceof ParseRecover)) throw e;
|
|
1995
|
+
this.cursor = start;
|
|
1996
|
+
this.errors.length = errors;
|
|
1997
|
+
return void 0;
|
|
1998
|
+
}
|
|
1999
|
+
}
|
|
1569
2000
|
parseCallArguments() {
|
|
1570
2001
|
if (this.matchPunctuator("(")) {
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
2002
|
+
const list = [];
|
|
2003
|
+
const stop = () => this.checkPunctuator(",");
|
|
2004
|
+
if (!this.checkPunctuator(")")) {
|
|
2005
|
+
while (true) {
|
|
2006
|
+
if (this.recover && this.onNewLine() && this.startsTableField() && !this.startsMethodCall(1)) break;
|
|
2007
|
+
const before = this.cursor;
|
|
2008
|
+
const argument = this.expressionOr(stop);
|
|
2009
|
+
if (argument.type !== "ErrorExpression" || this.cursor > before || list.length) list.push(argument);
|
|
2010
|
+
if (this.matchPunctuator(",") && !this.checkPunctuator(")")) continue;
|
|
2011
|
+
if (!this.recover || this.checkPunctuator(")")) break;
|
|
2012
|
+
if (this.onNewLine() && (this.checkType("Identifier") || this.checkType("Keyword"))) break;
|
|
2013
|
+
this.softError("Expected ',' or ')'");
|
|
2014
|
+
this.skip(stop, this.cursor, "expression");
|
|
2015
|
+
if (this.matchPunctuator(",")) continue;
|
|
2016
|
+
break;
|
|
2017
|
+
}
|
|
1574
2018
|
}
|
|
1575
|
-
|
|
1576
|
-
this.expectPunctuator(")");
|
|
2019
|
+
this.expectCloser(")");
|
|
1577
2020
|
return list;
|
|
1578
2021
|
}
|
|
1579
2022
|
const t = this.current();
|
|
@@ -1600,57 +2043,89 @@ var Parser = class {
|
|
|
1600
2043
|
const start = this.current();
|
|
1601
2044
|
this.expectPunctuator("{");
|
|
1602
2045
|
const fields = [];
|
|
2046
|
+
const stop = () => this.checkPunctuator(",") || this.checkPunctuator(";") || this.onNewLine() && this.startsTableField();
|
|
1603
2047
|
while (!this.checkPunctuator("}")) {
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
this.expectPunctuator(":");
|
|
1612
|
-
const value = this.parseExpression();
|
|
1613
|
-
fields.push({ type: "TableFieldComputed", key, value });
|
|
1614
|
-
} else if (this.checkType("Literal") && this.current().kind === "string") {
|
|
1615
|
-
const t = this.advance();
|
|
1616
|
-
const key = { type: "StringLiteral", value: t.value, raw: t.raw, ...spanFrom(t, t) };
|
|
1617
|
-
this.expectPunctuator(":");
|
|
1618
|
-
const value = this.parseExpression();
|
|
1619
|
-
fields.push({ type: "TableFieldNamed", key, value });
|
|
1620
|
-
} else if (this.checkType("Identifier") && this.peek(1).type === "Punctuator" && this.peek(1).value === ":") {
|
|
1621
|
-
const key = this.parseIdentifier();
|
|
1622
|
-
this.expectPunctuator(":");
|
|
1623
|
-
const value = this.parseExpression();
|
|
1624
|
-
fields.push({ type: "TableFieldNamed", key, value });
|
|
1625
|
-
} else if (this.checkType("Identifier")) {
|
|
1626
|
-
const name = this.parseIdentifier();
|
|
1627
|
-
fields.push({ type: "TableFieldShorthand", name });
|
|
1628
|
-
} else {
|
|
1629
|
-
this.error("Expected object field ('key: value', '[expr]: value', shorthand, or '...spread'); use '[...]' for arrays");
|
|
2048
|
+
const field = this.attempt(() => this.parseTableField(stop), stop, () => void 0);
|
|
2049
|
+
if (field) fields.push(field);
|
|
2050
|
+
if (this.matchPunctuator(",") || this.matchPunctuator(";")) continue;
|
|
2051
|
+
if (!this.recover || this.checkPunctuator("}")) break;
|
|
2052
|
+
if (this.onNewLine() && this.startsTableField()) {
|
|
2053
|
+
this.softError("Expected ','");
|
|
2054
|
+
continue;
|
|
1630
2055
|
}
|
|
2056
|
+
if (this.isAtEnd() || this.onNewLine() && this.checkType("Keyword")) break;
|
|
2057
|
+
this.softError("Expected ',' or '}'");
|
|
2058
|
+
const before = this.cursor;
|
|
2059
|
+
this.skip(stop, this.cursor, "expression");
|
|
1631
2060
|
if (this.matchPunctuator(",") || this.matchPunctuator(";")) continue;
|
|
2061
|
+
if (this.cursor > before && this.onNewLine() && this.startsTableField()) continue;
|
|
1632
2062
|
break;
|
|
1633
2063
|
}
|
|
1634
|
-
this.
|
|
2064
|
+
this.expectCloser("}");
|
|
1635
2065
|
return { type: "TableExpression", fields, ...spanFrom(start, this.previous()) };
|
|
1636
2066
|
}
|
|
2067
|
+
/** Does a `key: value` field, or a spread, start here? */
|
|
2068
|
+
startsTableField() {
|
|
2069
|
+
const next = this.peek(1);
|
|
2070
|
+
const colon = next.type === "Punctuator" && next.value === ":";
|
|
2071
|
+
if (this.checkType("Identifier")) return colon;
|
|
2072
|
+
if (this.checkType("Literal") && this.current().kind === "string") return colon;
|
|
2073
|
+
return this.checkOperator("...");
|
|
2074
|
+
}
|
|
2075
|
+
parseTableField(stop) {
|
|
2076
|
+
if (this.checkOperator("...")) {
|
|
2077
|
+
this.advance();
|
|
2078
|
+
return { type: "TableFieldSpread", argument: this.expressionOr(stop) };
|
|
2079
|
+
}
|
|
2080
|
+
if (this.matchPunctuator("[")) {
|
|
2081
|
+
const key = this.expressionOr(() => this.checkPunctuator("]"));
|
|
2082
|
+
this.expectPunctuator("]");
|
|
2083
|
+
this.expectPunctuator(":");
|
|
2084
|
+
return { type: "TableFieldComputed", key, value: this.expressionOr(stop) };
|
|
2085
|
+
}
|
|
2086
|
+
if (this.checkType("Literal") && this.current().kind === "string") {
|
|
2087
|
+
const t = this.advance();
|
|
2088
|
+
const key = { type: "StringLiteral", value: t.value, raw: t.raw, ...spanFrom(t, t) };
|
|
2089
|
+
this.expectPunctuator(":");
|
|
2090
|
+
return { type: "TableFieldNamed", key, value: this.expressionOr(stop) };
|
|
2091
|
+
}
|
|
2092
|
+
if (this.checkType("Identifier") && this.peek(1).type === "Punctuator" && this.peek(1).value === ":") {
|
|
2093
|
+
const key = this.parseIdentifier();
|
|
2094
|
+
this.expectPunctuator(":");
|
|
2095
|
+
return { type: "TableFieldNamed", key, value: this.expressionOr(stop) };
|
|
2096
|
+
}
|
|
2097
|
+
if (this.checkType("Identifier")) {
|
|
2098
|
+
return { type: "TableFieldShorthand", name: this.parseIdentifier() };
|
|
2099
|
+
}
|
|
2100
|
+
this.error("Expected object field ('key: value', '[expr]: value', shorthand, or '...spread'); use '[...]' for arrays");
|
|
2101
|
+
}
|
|
1637
2102
|
// `[1, 2, 3]` — array literal (trailing comma allowed).
|
|
1638
2103
|
parseArrayExpression() {
|
|
1639
2104
|
const start = this.current();
|
|
1640
2105
|
this.expectPunctuator("[");
|
|
1641
2106
|
const elements = [];
|
|
2107
|
+
const stop = () => this.checkPunctuator(",");
|
|
1642
2108
|
while (!this.checkPunctuator("]")) {
|
|
1643
2109
|
if (this.checkOperator("...")) {
|
|
1644
2110
|
const dots = this.advance();
|
|
1645
|
-
const argument = this.
|
|
2111
|
+
const argument = this.expressionOr(stop);
|
|
1646
2112
|
elements.push({ type: "SpreadElement", argument, ...spanFrom(dots, argument) });
|
|
1647
2113
|
} else {
|
|
1648
|
-
elements.push(this.
|
|
2114
|
+
elements.push(this.expressionOr(stop));
|
|
2115
|
+
}
|
|
2116
|
+
if (this.matchPunctuator(",")) continue;
|
|
2117
|
+
if (!this.recover || this.checkPunctuator("]")) break;
|
|
2118
|
+
if (this.onNewLine() && this.isExpressionStart() && !this.checkType("Keyword")) {
|
|
2119
|
+
this.softError("Expected ','");
|
|
2120
|
+
continue;
|
|
1649
2121
|
}
|
|
2122
|
+
if (this.isAtEnd() || this.onNewLine() && this.checkType("Keyword")) break;
|
|
2123
|
+
this.softError("Expected ',' or ']'");
|
|
2124
|
+
this.skip(stop, this.cursor, "expression");
|
|
1650
2125
|
if (this.matchPunctuator(",")) continue;
|
|
1651
2126
|
break;
|
|
1652
2127
|
}
|
|
1653
|
-
this.
|
|
2128
|
+
this.expectCloser("]");
|
|
1654
2129
|
return { type: "ArrayExpression", elements, ...spanFrom(start, this.previous()) };
|
|
1655
2130
|
}
|
|
1656
2131
|
// ============================================================
|
|
@@ -1683,7 +2158,7 @@ var Parser = class {
|
|
|
1683
2158
|
};
|
|
1684
2159
|
}
|
|
1685
2160
|
if (topLevel && this.matchPunctuator(":")) {
|
|
1686
|
-
target.typeAnnotation = this.
|
|
2161
|
+
target.typeAnnotation = this.typeOr(() => this.checkOperator("=") || this.checkPunctuator(","));
|
|
1687
2162
|
}
|
|
1688
2163
|
return target;
|
|
1689
2164
|
}
|
|
@@ -1842,12 +2317,13 @@ var Parser = class {
|
|
|
1842
2317
|
}
|
|
1843
2318
|
const optional2 = this.matchPunctuator("?");
|
|
1844
2319
|
let typeAnnotation;
|
|
2320
|
+
const paramEnd = () => this.checkPunctuator(",");
|
|
1845
2321
|
if (this.matchPunctuator(":")) {
|
|
1846
|
-
typeAnnotation = this.
|
|
2322
|
+
typeAnnotation = this.typeOr(() => paramEnd() || this.checkOperator("="));
|
|
1847
2323
|
}
|
|
1848
2324
|
let def;
|
|
1849
2325
|
if (this.matchOperator("=")) {
|
|
1850
|
-
def = this.
|
|
2326
|
+
def = this.expressionOr(paramEnd);
|
|
1851
2327
|
}
|
|
1852
2328
|
params.push({
|
|
1853
2329
|
type: "FunctionParameter",
|
|
@@ -1858,7 +2334,7 @@ var Parser = class {
|
|
|
1858
2334
|
optional: optional2 || void 0,
|
|
1859
2335
|
...spanFrom(paramStart, this.previous())
|
|
1860
2336
|
});
|
|
1861
|
-
if (this.matchPunctuator(",")) continue;
|
|
2337
|
+
if (this.matchPunctuator(",") && !this.checkPunctuator(")")) continue;
|
|
1862
2338
|
break;
|
|
1863
2339
|
}
|
|
1864
2340
|
}
|
|
@@ -1867,7 +2343,9 @@ var Parser = class {
|
|
|
1867
2343
|
let predicate;
|
|
1868
2344
|
if (this.matchPunctuator(":")) {
|
|
1869
2345
|
predicate = this.tryParseTypePredicate();
|
|
1870
|
-
if (!predicate)
|
|
2346
|
+
if (!predicate) {
|
|
2347
|
+
returnType = this.attempt(() => this.parseTypeOrTypePackReference(), () => false, () => void 0);
|
|
2348
|
+
}
|
|
1871
2349
|
}
|
|
1872
2350
|
return { start, generics, params, hasVarargs, varargTypeAnnotation, returnType, predicate };
|
|
1873
2351
|
}
|
|
@@ -1913,10 +2391,10 @@ var Parser = class {
|
|
|
1913
2391
|
}
|
|
1914
2392
|
return void 0;
|
|
1915
2393
|
}
|
|
1916
|
-
parseFunctionBody() {
|
|
2394
|
+
parseFunctionBody(opener) {
|
|
1917
2395
|
const head = this.parseFunctionHead();
|
|
1918
|
-
const body = this.parseBlock();
|
|
1919
|
-
this.
|
|
2396
|
+
const body = this.parseBlock(opener);
|
|
2397
|
+
this.expectEnd(opener);
|
|
1920
2398
|
return {
|
|
1921
2399
|
type: "FunctionBody",
|
|
1922
2400
|
generics: head.generics,
|
|
@@ -1941,9 +2419,9 @@ var Parser = class {
|
|
|
1941
2419
|
...spanFrom(head.start, this.previous())
|
|
1942
2420
|
};
|
|
1943
2421
|
}
|
|
1944
|
-
headToBody(head) {
|
|
1945
|
-
const body = this.parseBlock();
|
|
1946
|
-
this.
|
|
2422
|
+
headToBody(head, opener) {
|
|
2423
|
+
const body = this.parseBlock(opener);
|
|
2424
|
+
this.expectEnd(opener);
|
|
1947
2425
|
return {
|
|
1948
2426
|
type: "FunctionBody",
|
|
1949
2427
|
generics: head.generics,
|
|
@@ -2150,7 +2628,7 @@ var Parser = class {
|
|
|
2150
2628
|
this.advance();
|
|
2151
2629
|
if (!this.checkOperator(">")) {
|
|
2152
2630
|
typeArguments.push(this.parseTypeArgument());
|
|
2153
|
-
while (this.matchPunctuator(",")) {
|
|
2631
|
+
while (this.matchPunctuator(",") && !this.checkOperator(">")) {
|
|
2154
2632
|
typeArguments.push(this.parseTypeArgument());
|
|
2155
2633
|
}
|
|
2156
2634
|
}
|
|
@@ -2201,7 +2679,7 @@ var Parser = class {
|
|
|
2201
2679
|
optional: optional2 || void 0,
|
|
2202
2680
|
...spanFrom(paramStart, this.previous())
|
|
2203
2681
|
});
|
|
2204
|
-
if (this.matchPunctuator(",")) continue;
|
|
2682
|
+
if (this.matchPunctuator(",") && !this.checkPunctuator(")")) continue;
|
|
2205
2683
|
break;
|
|
2206
2684
|
}
|
|
2207
2685
|
}
|
|
@@ -2416,7 +2894,7 @@ var Parser = class {
|
|
|
2416
2894
|
default: def,
|
|
2417
2895
|
...spanFrom(nameTok, this.previous())
|
|
2418
2896
|
});
|
|
2419
|
-
if (this.matchPunctuator(",")) continue;
|
|
2897
|
+
if (this.matchPunctuator(",") && !this.checkOperator(">")) continue;
|
|
2420
2898
|
break;
|
|
2421
2899
|
}
|
|
2422
2900
|
this.expectOperator(">");
|
|
@@ -2443,23 +2921,23 @@ function parseExpressionFromSource(raw) {
|
|
|
2443
2921
|
return expr;
|
|
2444
2922
|
}
|
|
2445
2923
|
function parseWithRecovery(source) {
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
const
|
|
2462
|
-
return { program, errors:
|
|
2924
|
+
const lexErrors = [];
|
|
2925
|
+
const comments = [];
|
|
2926
|
+
const tokens = tokenize(source, { errors: lexErrors, comments });
|
|
2927
|
+
const lexed = lexErrors.map((e) => new ParseError(e.message.replace(/ \(\d+:\d+\)$/, ""), e.line, e.column));
|
|
2928
|
+
const first = new Parser(tokens, { recover: true });
|
|
2929
|
+
let program = first.parseProgram();
|
|
2930
|
+
let errors = first.errors;
|
|
2931
|
+
if (first.missingEnd) {
|
|
2932
|
+
const second = new Parser(tokens, { recover: true, indentation: true });
|
|
2933
|
+
const reparsed = second.parseProgram();
|
|
2934
|
+
if (second.errors.length <= errors.length) {
|
|
2935
|
+
program = reparsed;
|
|
2936
|
+
errors = second.errors;
|
|
2937
|
+
}
|
|
2938
|
+
}
|
|
2939
|
+
const all = [...lexed, ...errors].sort((a, b) => a.line - b.line || a.column - b.column);
|
|
2940
|
+
return { program, errors: all, directives: readDirectives(comments, tokens) };
|
|
2463
2941
|
}
|
|
2464
2942
|
|
|
2465
2943
|
// src/ast/nodes.ts
|
|
@@ -2498,19 +2976,24 @@ function childScope(parent) {
|
|
|
2498
2976
|
return { parent, declarations: /* @__PURE__ */ new Map() };
|
|
2499
2977
|
}
|
|
2500
2978
|
var Analyzer = class {
|
|
2501
|
-
nextId = 0;
|
|
2502
|
-
bindingOf = /* @__PURE__ */ new Map();
|
|
2503
|
-
bindings = /* @__PURE__ */ new Map();
|
|
2504
|
-
diagnostics = [];
|
|
2505
|
-
globalScope = { parent: null, declarations: /* @__PURE__ */ new Map() };
|
|
2506
2979
|
constructor(options) {
|
|
2980
|
+
this.options = options;
|
|
2507
2981
|
for (const name of options.builtinGlobals ?? []) {
|
|
2508
2982
|
const id = this.getOrCreateGlobalBinding(name);
|
|
2509
2983
|
this.bindings.get(id).isBuiltin = true;
|
|
2510
2984
|
}
|
|
2511
2985
|
}
|
|
2986
|
+
options;
|
|
2987
|
+
nextId = 0;
|
|
2988
|
+
bindingOf = /* @__PURE__ */ new Map();
|
|
2989
|
+
bindings = /* @__PURE__ */ new Map();
|
|
2990
|
+
diagnostics = [];
|
|
2991
|
+
globalScope = { parent: null, declarations: /* @__PURE__ */ new Map() };
|
|
2512
2992
|
run(program) {
|
|
2513
|
-
this.
|
|
2993
|
+
this.moduleScope = childScope(this.globalScope);
|
|
2994
|
+
this.visitBlock(program.body, this.moduleScope);
|
|
2995
|
+
this.resolveForwardReferences();
|
|
2996
|
+
if (this.options.reportUndeclared) this.reportUndeclared(program);
|
|
2514
2997
|
return {
|
|
2515
2998
|
bindingOf: this.bindingOf,
|
|
2516
2999
|
bindings: this.bindings,
|
|
@@ -2518,8 +3001,101 @@ var Analyzer = class {
|
|
|
2518
3001
|
globalsByName: this.globalScope.declarations
|
|
2519
3002
|
};
|
|
2520
3003
|
}
|
|
3004
|
+
// ---------------- hoisting ----------------
|
|
3005
|
+
//
|
|
3006
|
+
// As in TypeScript, and as the bundle runs a module:
|
|
3007
|
+
//
|
|
3008
|
+
// - a function declaration is visible to its whole block, before it too;
|
|
3009
|
+
// - a name the module declares at its top level is visible to code that
|
|
3010
|
+
// runs later — function bodies, and `typeof` in a type — even where that
|
|
3011
|
+
// code is written above the declaration. A bundle declares every
|
|
3012
|
+
// top-level name before any of the module runs, so this is what happens.
|
|
3013
|
+
//
|
|
3014
|
+
// A read of a later `const` straight in the module's own flow is not
|
|
3015
|
+
// resolved to it: that still reads what was there before.
|
|
3016
|
+
moduleScope = this.globalScope;
|
|
3017
|
+
/** How many function bodies enclose the walk. */
|
|
3018
|
+
functionDepth = 0;
|
|
3019
|
+
/** Function declarations already declared by their block's hoisting, with
|
|
3020
|
+
* the function depth of that block. */
|
|
3021
|
+
hoisted = /* @__PURE__ */ new Map();
|
|
3022
|
+
/** Names that resolved to a global from code that runs later, with the
|
|
3023
|
+
* scope they were read in. */
|
|
3024
|
+
deferredGlobals = [];
|
|
3025
|
+
hoistFunctions(block, scope) {
|
|
3026
|
+
for (const statement of block.statements) {
|
|
3027
|
+
const declaration = statement.type === "ExportStatement" ? statement.declaration : statement;
|
|
3028
|
+
if (declaration.type !== "FunctionDeclaration") continue;
|
|
3029
|
+
this.declare(scope, declaration.name.name, "local", declaration.name, true, "function");
|
|
3030
|
+
this.hoisted.set(declaration.name, scope === this.moduleScope ? -1 : this.functionDepth);
|
|
3031
|
+
}
|
|
3032
|
+
}
|
|
3033
|
+
/** Inside a function, a function declared further down its block is
|
|
3034
|
+
* hoisted only as a name: code that runs later (another function's body)
|
|
3035
|
+
* can call it, but a call straight in the block before the declaration
|
|
3036
|
+
* finds nothing there yet. At a module's top level the whole function is
|
|
3037
|
+
* hoisted, and this does not apply. */
|
|
3038
|
+
checkUseBeforeDefine(identifier, id) {
|
|
3039
|
+
const binding = this.bindings.get(id);
|
|
3040
|
+
if (binding.declaredBy !== "function" || this.typeQueryDepth > 0) return;
|
|
3041
|
+
const declaration = binding.declarationNode;
|
|
3042
|
+
const depth = declaration && this.hoisted.get(declaration);
|
|
3043
|
+
if (depth === void 0 || depth !== this.functionDepth) return;
|
|
3044
|
+
const before = identifier.line.start < declaration.line.start || identifier.line.start === declaration.line.start && identifier.column.start < declaration.column.start;
|
|
3045
|
+
if (!before) return;
|
|
3046
|
+
this.diagnostics.push({
|
|
3047
|
+
node: identifier,
|
|
3048
|
+
message: `'${binding.name}' is used before its definition: inside a function, a function declared further down is only there once its declaration has run`,
|
|
3049
|
+
kind: "use-before-define"
|
|
3050
|
+
});
|
|
3051
|
+
}
|
|
3052
|
+
noteDeferred(identifier, scope, id, assignment) {
|
|
3053
|
+
if (this.functionDepth === 0 && this.typeQueryDepth === 0) return;
|
|
3054
|
+
if (this.bindings.get(id).kind !== "global") return;
|
|
3055
|
+
this.deferredGlobals.push({ node: identifier, scope, assignment });
|
|
3056
|
+
}
|
|
3057
|
+
/** Point each deferred read of a global at the declaration of that name
|
|
3058
|
+
* that turned up later — in the module, or in any block around the code
|
|
3059
|
+
* that reads it. Such code runs after the declaration has: a closure
|
|
3060
|
+
* written inside a value reads the name the value is bound to. */
|
|
3061
|
+
resolveForwardReferences() {
|
|
3062
|
+
for (const { node, scope, assignment } of this.deferredGlobals) {
|
|
3063
|
+
const localId = this.lookup(scope, node.name);
|
|
3064
|
+
const globalId = this.bindingOf.get(node);
|
|
3065
|
+
if (localId === void 0 || globalId === void 0 || localId === globalId) continue;
|
|
3066
|
+
const global = this.bindings.get(globalId);
|
|
3067
|
+
const at = global.references.indexOf(node);
|
|
3068
|
+
if (at >= 0) global.references.splice(at, 1);
|
|
3069
|
+
if (global.declarationNode === node) global.declarationNode = void 0;
|
|
3070
|
+
if (!global.isBuiltin && !global.references.length && global.declarationNode === void 0) {
|
|
3071
|
+
this.bindings.delete(globalId);
|
|
3072
|
+
this.globalScope.declarations.delete(node.name);
|
|
3073
|
+
}
|
|
3074
|
+
this.bindingOf.set(node, localId);
|
|
3075
|
+
this.bindings.get(localId).references.push(node);
|
|
3076
|
+
if (assignment) this.checkConstAssign(localId, node);
|
|
3077
|
+
else if (this.typeQueryDepth === 0) this.checkTypeOnly(localId, node);
|
|
3078
|
+
}
|
|
3079
|
+
}
|
|
3080
|
+
/** Every read of a global nothing declares. A global assigned somewhere
|
|
3081
|
+
* in the file (`x = 1`) is Lua's implicit global, and is left alone. */
|
|
3082
|
+
reportUndeclared(program) {
|
|
3083
|
+
const declared = /* @__PURE__ */ new Set();
|
|
3084
|
+
for (const statement of program.body.statements) {
|
|
3085
|
+
if (statement.type === "DeclareStatement") declared.add(statement.name);
|
|
3086
|
+
}
|
|
3087
|
+
const found = [];
|
|
3088
|
+
for (const binding of this.bindings.values()) {
|
|
3089
|
+
if (!isUnassignedGlobal(binding) || declared.has(binding.name)) continue;
|
|
3090
|
+
for (const reference of binding.references) {
|
|
3091
|
+
found.push({ node: reference, message: `Cannot find name '${binding.name}'`, kind: "undeclared" });
|
|
3092
|
+
}
|
|
3093
|
+
}
|
|
3094
|
+
found.sort((a, b) => a.node.line.start - b.node.line.start || a.node.column.start - b.node.column.start);
|
|
3095
|
+
this.diagnostics.push(...found);
|
|
3096
|
+
}
|
|
2521
3097
|
// ---------------- declaration / resolution primitives ----------------
|
|
2522
|
-
declare(scope, name, kind, node, isConst = false) {
|
|
3098
|
+
declare(scope, name, kind, node, isConst = false, declaredBy) {
|
|
2523
3099
|
if (scope.declarations.has(name) && scope !== this.globalScope) {
|
|
2524
3100
|
this.diagnostics.push({
|
|
2525
3101
|
node,
|
|
@@ -2528,7 +3104,7 @@ var Analyzer = class {
|
|
|
2528
3104
|
});
|
|
2529
3105
|
}
|
|
2530
3106
|
const id = this.nextId++;
|
|
2531
|
-
this.bindings.set(id, { id, name, kind, declarationNode: node, references: [], isConst });
|
|
3107
|
+
this.bindings.set(id, { id, name, kind, declarationNode: node, references: [], isConst, declaredBy });
|
|
2532
3108
|
scope.declarations.set(name, id);
|
|
2533
3109
|
return id;
|
|
2534
3110
|
}
|
|
@@ -2562,6 +3138,21 @@ var Analyzer = class {
|
|
|
2562
3138
|
const id = this.resolve(scope, identifier.name);
|
|
2563
3139
|
this.bindingOf.set(identifier, id);
|
|
2564
3140
|
this.bindings.get(id).references.push(identifier);
|
|
3141
|
+
if (this.typeQueryDepth === 0) this.checkTypeOnly(id, identifier);
|
|
3142
|
+
this.checkUseBeforeDefine(identifier, id);
|
|
3143
|
+
this.noteDeferred(identifier, scope, id, false);
|
|
3144
|
+
}
|
|
3145
|
+
/** Inside `typeof x` in a type, where a type-only import may be named. */
|
|
3146
|
+
typeQueryDepth = 0;
|
|
3147
|
+
/** A name from `import type` used as a value. */
|
|
3148
|
+
checkTypeOnly(id, node) {
|
|
3149
|
+
const b = this.bindings.get(id);
|
|
3150
|
+
if (b.declaredBy !== "type") return;
|
|
3151
|
+
this.diagnostics.push({
|
|
3152
|
+
node,
|
|
3153
|
+
message: `'${b.name}' is imported with 'import type' and can only be used as a type`,
|
|
3154
|
+
kind: "type-only"
|
|
3155
|
+
});
|
|
2565
3156
|
}
|
|
2566
3157
|
/** For assignment-like targets (`x = ...`, `function foo() end`): if
|
|
2567
3158
|
* this resolved to a global with no declaration site yet, treat this
|
|
@@ -2578,14 +3169,35 @@ var Analyzer = class {
|
|
|
2578
3169
|
this.bindingOf.set(identifier, id);
|
|
2579
3170
|
this.bindings.get(id).references.push(identifier);
|
|
2580
3171
|
this.recordPossibleGlobalDefinition(id, identifier);
|
|
3172
|
+
this.checkTypeOnly(id, identifier);
|
|
2581
3173
|
this.checkConstAssign(id, identifier);
|
|
3174
|
+
this.noteDeferred(identifier, scope, id, true);
|
|
3175
|
+
}
|
|
3176
|
+
/** `Module.x = 1` through `import * as Module`: a module's exports belong
|
|
3177
|
+
* to it and are read-only, as in ES modules. Deeper writes (`Module.x.y`)
|
|
3178
|
+
* change the value, not the module, and are fine. */
|
|
3179
|
+
checkModuleWrite(target) {
|
|
3180
|
+
if (target.type !== "MemberExpression" && target.type !== "IndexExpression") return;
|
|
3181
|
+
if (target.object.type !== "Identifier") return;
|
|
3182
|
+
const id = this.bindingOf.get(target.object);
|
|
3183
|
+
if (id !== void 0 && this.bindings.get(id).declaredBy === "namespace") {
|
|
3184
|
+
this.moduleWriteError(target.object.name, target);
|
|
3185
|
+
}
|
|
3186
|
+
}
|
|
3187
|
+
moduleWriteError(name, node) {
|
|
3188
|
+
this.diagnostics.push({
|
|
3189
|
+
node,
|
|
3190
|
+
message: `Cannot assign to a member of '${name}' \u2014 a module's exports are read-only`,
|
|
3191
|
+
kind: "const-assign"
|
|
3192
|
+
});
|
|
2582
3193
|
}
|
|
2583
3194
|
checkConstAssign(id, node) {
|
|
2584
3195
|
const b = this.bindings.get(id);
|
|
3196
|
+
if (b.declaredBy === "type") return;
|
|
2585
3197
|
if (b.isConst) {
|
|
2586
3198
|
this.diagnostics.push({
|
|
2587
3199
|
node,
|
|
2588
|
-
message: `Cannot assign to '${b.name}' \u2014 it is a const`,
|
|
3200
|
+
message: `Cannot assign to '${b.name}' \u2014 it is ${b.declaredBy === "import" || b.declaredBy === "namespace" ? "an import" : b.declaredBy === "function" ? "a function" : "a const"}`,
|
|
2589
3201
|
kind: "const-assign"
|
|
2590
3202
|
});
|
|
2591
3203
|
}
|
|
@@ -2626,6 +3238,7 @@ var Analyzer = class {
|
|
|
2626
3238
|
const id = this.resolve(scope, t.name);
|
|
2627
3239
|
this.bindingOf.set(t, id);
|
|
2628
3240
|
this.recordPossibleGlobalDefinition(id, t);
|
|
3241
|
+
this.checkTypeOnly(id, t);
|
|
2629
3242
|
this.checkConstAssign(id, t);
|
|
2630
3243
|
return;
|
|
2631
3244
|
}
|
|
@@ -2651,6 +3264,7 @@ var Analyzer = class {
|
|
|
2651
3264
|
}
|
|
2652
3265
|
// ---------------- blocks / statements ----------------
|
|
2653
3266
|
visitBlock(block, scope) {
|
|
3267
|
+
this.hoistFunctions(block, scope);
|
|
2654
3268
|
for (const stmt of block.statements) this.visitStatement(stmt, scope);
|
|
2655
3269
|
}
|
|
2656
3270
|
/** Visits a block in a *fresh child scope* of `scope` — the common case
|
|
@@ -2669,7 +3283,11 @@ var Analyzer = class {
|
|
|
2669
3283
|
return;
|
|
2670
3284
|
}
|
|
2671
3285
|
case "FunctionDeclaration": {
|
|
2672
|
-
this.declare(scope, stmt.name.name, "local", stmt.name,
|
|
3286
|
+
if (!this.hoisted.has(stmt.name)) this.declare(scope, stmt.name.name, "local", stmt.name, true, "function");
|
|
3287
|
+
for (const signature of stmt.signatures ?? []) {
|
|
3288
|
+
if (signature.name && signature.name !== stmt.name) this.reference(scope, signature.name);
|
|
3289
|
+
}
|
|
3290
|
+
if (stmt.implementationName) this.reference(scope, stmt.implementationName);
|
|
2673
3291
|
for (const signature of stmt.signatures ?? []) this.visitSignature(signature, scope);
|
|
2674
3292
|
this.visitFunctionBody(stmt.func, scope);
|
|
2675
3293
|
return;
|
|
@@ -2679,6 +3297,11 @@ var Analyzer = class {
|
|
|
2679
3297
|
this.referenceAsAssignmentTarget(scope, stmt.target.base);
|
|
2680
3298
|
} else {
|
|
2681
3299
|
this.reference(scope, stmt.target.base);
|
|
3300
|
+
const id = this.bindingOf.get(stmt.target.base);
|
|
3301
|
+
const depth = stmt.target.path.length + (stmt.target.method ? 1 : 0);
|
|
3302
|
+
if (id !== void 0 && depth === 1 && this.bindings.get(id).declaredBy === "namespace") {
|
|
3303
|
+
this.moduleWriteError(stmt.target.base.name, stmt.target);
|
|
3304
|
+
}
|
|
2682
3305
|
}
|
|
2683
3306
|
for (const signature of stmt.signatures ?? []) this.visitSignature(signature, scope);
|
|
2684
3307
|
this.visitFunctionBody(stmt.func, scope, stmt.isMethod);
|
|
@@ -2693,6 +3316,7 @@ var Analyzer = class {
|
|
|
2693
3316
|
this.assignPattern(scope, target);
|
|
2694
3317
|
} else {
|
|
2695
3318
|
this.visitExpression(target, scope);
|
|
3319
|
+
this.checkModuleWrite(target);
|
|
2696
3320
|
}
|
|
2697
3321
|
}
|
|
2698
3322
|
return;
|
|
@@ -2705,6 +3329,7 @@ var Analyzer = class {
|
|
|
2705
3329
|
if (id !== void 0) this.checkConstAssign(id, stmt.target);
|
|
2706
3330
|
} else {
|
|
2707
3331
|
this.visitExpression(stmt.target, scope);
|
|
3332
|
+
this.checkModuleWrite(stmt.target);
|
|
2708
3333
|
}
|
|
2709
3334
|
return;
|
|
2710
3335
|
}
|
|
@@ -2763,14 +3388,22 @@ var Analyzer = class {
|
|
|
2763
3388
|
return;
|
|
2764
3389
|
case "TypeAliasStatement":
|
|
2765
3390
|
case "ExportTypeAliasStatement":
|
|
2766
|
-
this.
|
|
3391
|
+
this.visitGenerics(
|
|
3392
|
+
(stmt.type === "TypeAliasStatement" ? stmt : stmt.alias).generics,
|
|
3393
|
+
scope
|
|
3394
|
+
);
|
|
3395
|
+
this.visitType(stmt.type === "TypeAliasStatement" ? stmt.definition : stmt.alias.definition, scope);
|
|
2767
3396
|
return;
|
|
2768
3397
|
case "ImportStatement": {
|
|
3398
|
+
const typeOnly = stmt.isTypeOnly ? "type" : void 0;
|
|
2769
3399
|
if (stmt.defaultImport) {
|
|
2770
|
-
this.declare(scope, stmt.defaultImport.name, "local", stmt.defaultImport);
|
|
3400
|
+
this.declare(scope, stmt.defaultImport.name, "local", stmt.defaultImport, true, typeOnly ?? "import");
|
|
3401
|
+
}
|
|
3402
|
+
if (stmt.namespaceImport) {
|
|
3403
|
+
this.declare(scope, stmt.namespaceImport.name, "local", stmt.namespaceImport, true, typeOnly ?? "namespace");
|
|
2771
3404
|
}
|
|
2772
3405
|
for (const spec of stmt.specifiers) {
|
|
2773
|
-
this.declare(scope, spec.local.name, "local", spec.local);
|
|
3406
|
+
this.declare(scope, spec.local.name, "local", spec.local, true, typeOnly ?? "import");
|
|
2774
3407
|
}
|
|
2775
3408
|
return;
|
|
2776
3409
|
}
|
|
@@ -2797,6 +3430,7 @@ var Analyzer = class {
|
|
|
2797
3430
|
// ---------------- functions ----------------
|
|
2798
3431
|
visitFunctionBody(func, outerScope, isMethod = false) {
|
|
2799
3432
|
const fnScope = childScope(outerScope);
|
|
3433
|
+
this.visitGenerics(func.generics, fnScope);
|
|
2800
3434
|
func.params.forEach((param, i) => {
|
|
2801
3435
|
const kind = isMethod && i === 0 ? "self" : "param";
|
|
2802
3436
|
this.visitType(param.typeAnnotation, fnScope);
|
|
@@ -2809,14 +3443,28 @@ var Analyzer = class {
|
|
|
2809
3443
|
});
|
|
2810
3444
|
this.visitType(func.varargTypeAnnotation, fnScope);
|
|
2811
3445
|
this.visitType(func.returnType, fnScope);
|
|
2812
|
-
this.
|
|
3446
|
+
this.functionDepth++;
|
|
3447
|
+
try {
|
|
3448
|
+
this.visitBlock(func.body, fnScope);
|
|
3449
|
+
} finally {
|
|
3450
|
+
this.functionDepth--;
|
|
3451
|
+
}
|
|
2813
3452
|
}
|
|
2814
3453
|
/** An overload signature: no body and no bindings, but its types can hold
|
|
2815
3454
|
* a `typeof x`. */
|
|
2816
3455
|
visitSignature(signature, scope) {
|
|
3456
|
+
this.visitGenerics(signature.generics, scope);
|
|
2817
3457
|
for (const param of signature.params) this.visitType(param.typeAnnotation, scope);
|
|
2818
3458
|
this.visitType(signature.returnType, scope);
|
|
2819
3459
|
}
|
|
3460
|
+
/** `<K extends typeof config>` — a constraint is a type like any other,
|
|
3461
|
+
* and the `typeof` in it reads a value. */
|
|
3462
|
+
visitGenerics(generics, scope) {
|
|
3463
|
+
for (const generic of generics ?? []) {
|
|
3464
|
+
this.visitType(generic.constraint, scope);
|
|
3465
|
+
this.visitType(generic.default, scope);
|
|
3466
|
+
}
|
|
3467
|
+
}
|
|
2820
3468
|
/** Resolve the value references inside a type. Only `typeof x` has any —
|
|
2821
3469
|
* everything else in a type names types, which live in their own
|
|
2822
3470
|
* namespace and are not this pass's business. */
|
|
@@ -2829,7 +3477,12 @@ var Analyzer = class {
|
|
|
2829
3477
|
return;
|
|
2830
3478
|
}
|
|
2831
3479
|
if (value.type === "TypeofTypeNode") {
|
|
2832
|
-
this.
|
|
3480
|
+
this.typeQueryDepth++;
|
|
3481
|
+
try {
|
|
3482
|
+
this.visitExpression(value.expression, scope);
|
|
3483
|
+
} finally {
|
|
3484
|
+
this.typeQueryDepth--;
|
|
3485
|
+
}
|
|
2833
3486
|
return;
|
|
2834
3487
|
}
|
|
2835
3488
|
for (const key of Object.keys(value)) {
|
|
@@ -2849,6 +3502,7 @@ var Analyzer = class {
|
|
|
2849
3502
|
case "NumberLiteral":
|
|
2850
3503
|
case "StringLiteral":
|
|
2851
3504
|
case "VarargExpression":
|
|
3505
|
+
case "ErrorExpression":
|
|
2852
3506
|
return;
|
|
2853
3507
|
case "InterpolatedStringExpression":
|
|
2854
3508
|
for (const part of expr.parts) {
|
|
@@ -2933,6 +3587,37 @@ function analyzeScopes(program, options = {}) {
|
|
|
2933
3587
|
return new Analyzer(options).run(program);
|
|
2934
3588
|
}
|
|
2935
3589
|
|
|
3590
|
+
// src/ast/prelude.ts
|
|
3591
|
+
var PRELUDE_SOURCE = `
|
|
3592
|
+
-- In Luau only \`nil\` and \`false\` are falsy: \`0\` and \`""\` are truthy.
|
|
3593
|
+
-- These are what truthiness narrowing computes, made available to write down.
|
|
3594
|
+
type Falsy = nil | false
|
|
3595
|
+
type Truthy<T> = T - Falsy
|
|
3596
|
+
|
|
3597
|
+
-- \`-\` is set difference. Over a union it drops members; over a concrete type
|
|
3598
|
+
-- it simplifies away; over an opaque type (\`unknown\`, an unresolved parameter)
|
|
3599
|
+
-- it is kept, so \`Exclude<unknown, 1>\` stays \`unknown - 1\`.
|
|
3600
|
+
type Exclude<T, U> = T - U
|
|
3601
|
+
type Extract<T, U> = T extends U ? T : never
|
|
3602
|
+
type NonNullable<T> = T - nil
|
|
3603
|
+
|
|
3604
|
+
type ReturnType<T> = T extends (...unknown) -> infer R ? R : never
|
|
3605
|
+
type Parameters<T> = T extends (...infer P) -> unknown ? P : never
|
|
3606
|
+
|
|
3607
|
+
type Partial<T> = { [K in keyof T]?: T[K] }
|
|
3608
|
+
type Required<T> = { [K in keyof T]-?: T[K] }
|
|
3609
|
+
type Readonly<T> = { readonly [K in keyof T]: T[K] }
|
|
3610
|
+
type Mutable<T> = { -readonly [K in keyof T]: T[K] }
|
|
3611
|
+
|
|
3612
|
+
type Pick<T, K> = { [P in K]: T[P] }
|
|
3613
|
+
type Omit<T, K> = Pick<T, Exclude<keyof T, K>>
|
|
3614
|
+
type Record<K, V> = { [P in K]: V }
|
|
3615
|
+
`;
|
|
3616
|
+
var prelude;
|
|
3617
|
+
function preludeProgram() {
|
|
3618
|
+
return prelude ??= parse(PRELUDE_SOURCE);
|
|
3619
|
+
}
|
|
3620
|
+
|
|
2936
3621
|
// src/ast/typeModel.ts
|
|
2937
3622
|
function isClassType(t) {
|
|
2938
3623
|
return t.kind === "object" && t.class !== void 0;
|
|
@@ -3008,6 +3693,7 @@ function substitute(t, subst) {
|
|
|
3008
3693
|
varargs,
|
|
3009
3694
|
returns: substitute(t.returns, inner),
|
|
3010
3695
|
typeParams: t.typeParams,
|
|
3696
|
+
typeParamDefaults: t.typeParamDefaults,
|
|
3011
3697
|
predicate: t.predicate && {
|
|
3012
3698
|
...t.predicate,
|
|
3013
3699
|
type: t.predicate.type && substitute(t.predicate.type, inner)
|
|
@@ -3264,7 +3950,16 @@ function isAssignableInner(a, b) {
|
|
|
3264
3950
|
if (a.indexer && isAssignable(a.indexer.value, bp.type)) continue;
|
|
3265
3951
|
return false;
|
|
3266
3952
|
}
|
|
3267
|
-
if (!isAssignable(ap.type, bp.type)) return false;
|
|
3953
|
+
if (!isAssignable(ap.type, bp.type)) return false;
|
|
3954
|
+
}
|
|
3955
|
+
if (b.indexer) {
|
|
3956
|
+
for (const [name, ap] of a.properties) {
|
|
3957
|
+
if (b.properties.has(name) || !isAssignable(literal(name), b.indexer.key)) continue;
|
|
3958
|
+
if (!isAssignable(ap.type, b.indexer.value)) return false;
|
|
3959
|
+
}
|
|
3960
|
+
if (a.indexer && isAssignable(a.indexer.key, b.indexer.key) && !isAssignable(a.indexer.value, b.indexer.value)) {
|
|
3961
|
+
return false;
|
|
3962
|
+
}
|
|
3268
3963
|
}
|
|
3269
3964
|
return true;
|
|
3270
3965
|
}
|
|
@@ -3415,10 +4110,14 @@ function containsFreeTypeParam(t, seen, bound) {
|
|
|
3415
4110
|
return containsTypeParam(t.base, seen, bound) || containsTypeParam(t.excluded, seen, bound);
|
|
3416
4111
|
case "indexedAccess":
|
|
3417
4112
|
return containsTypeParam(t.objectType, seen, bound) || containsTypeParam(t.indexType, seen, bound);
|
|
3418
|
-
case "conditional":
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
|
|
4113
|
+
case "conditional": {
|
|
4114
|
+
const inner = t.inferVars.length ? /* @__PURE__ */ new Set([...bound, ...t.inferVars]) : bound;
|
|
4115
|
+
return containsTypeParam(t.checkType, seen, bound) || containsTypeParam(t.extendsType, seen, inner) || containsTypeParam(t.trueType, seen, inner) || containsTypeParam(t.falseType, seen, bound);
|
|
4116
|
+
}
|
|
4117
|
+
case "mapped": {
|
|
4118
|
+
const inner = /* @__PURE__ */ new Set([...bound, t.parameter]);
|
|
4119
|
+
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);
|
|
4120
|
+
}
|
|
3422
4121
|
default:
|
|
3423
4122
|
return false;
|
|
3424
4123
|
}
|
|
@@ -3492,9 +4191,52 @@ function escapeRegExp(s) {
|
|
|
3492
4191
|
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3493
4192
|
}
|
|
3494
4193
|
function overlaps(a, b) {
|
|
4194
|
+
if (a.kind === "union") return a.types.some((m) => overlaps(m, b));
|
|
4195
|
+
if (b.kind === "union") return b.types.some((m) => overlaps(a, m));
|
|
3495
4196
|
return isAssignable(a, b) || isAssignable(b, a);
|
|
3496
4197
|
}
|
|
3497
4198
|
var formatCache = /* @__PURE__ */ new WeakMap();
|
|
4199
|
+
function briefConstraint(t) {
|
|
4200
|
+
if (t.kind === "union" && t.types.length > 8) {
|
|
4201
|
+
return `${t.types.slice(0, 6).map(formatType).join(" | ")} | ... ${t.types.length - 6} more`;
|
|
4202
|
+
}
|
|
4203
|
+
return formatType(t);
|
|
4204
|
+
}
|
|
4205
|
+
function collectTypeParams(t, out, seen = /* @__PURE__ */ new Set()) {
|
|
4206
|
+
if (!t || seen.has(t)) return;
|
|
4207
|
+
seen.add(t);
|
|
4208
|
+
switch (t.kind) {
|
|
4209
|
+
case "typeParam":
|
|
4210
|
+
if (t.constraint && !out.has(t.name)) out.set(t.name, t.constraint);
|
|
4211
|
+
collectTypeParams(t.constraint, out, seen);
|
|
4212
|
+
return;
|
|
4213
|
+
case "array":
|
|
4214
|
+
collectTypeParams(t.element, out, seen);
|
|
4215
|
+
return;
|
|
4216
|
+
case "tuple":
|
|
4217
|
+
for (const e of t.elements) collectTypeParams(e, out, seen);
|
|
4218
|
+
return;
|
|
4219
|
+
case "union":
|
|
4220
|
+
case "intersection":
|
|
4221
|
+
for (const m of t.types) collectTypeParams(m, out, seen);
|
|
4222
|
+
return;
|
|
4223
|
+
case "keyof":
|
|
4224
|
+
collectTypeParams(t.target, out, seen);
|
|
4225
|
+
return;
|
|
4226
|
+
case "indexedAccess":
|
|
4227
|
+
collectTypeParams(t.objectType, out, seen);
|
|
4228
|
+
collectTypeParams(t.indexType, out, seen);
|
|
4229
|
+
return;
|
|
4230
|
+
case "genericRef":
|
|
4231
|
+
for (const a of t.typeArguments) collectTypeParams(a, out, seen);
|
|
4232
|
+
return;
|
|
4233
|
+
case "object":
|
|
4234
|
+
for (const [, p] of t.properties) collectTypeParams(p.type, out, seen);
|
|
4235
|
+
return;
|
|
4236
|
+
default:
|
|
4237
|
+
return;
|
|
4238
|
+
}
|
|
4239
|
+
}
|
|
3498
4240
|
function formatType(t) {
|
|
3499
4241
|
const cached = formatCache.get(t);
|
|
3500
4242
|
if (cached !== void 0) return cached;
|
|
@@ -3531,7 +4273,14 @@ function formatTypeUncached(t) {
|
|
|
3531
4273
|
const consts = new Set(
|
|
3532
4274
|
t.params.filter((p) => p.type.kind === "typeParam" && p.type.isConst).map((p) => p.type.name)
|
|
3533
4275
|
);
|
|
3534
|
-
const
|
|
4276
|
+
const constraints = /* @__PURE__ */ new Map();
|
|
4277
|
+
for (const part of [...t.params.map((p) => p.type), t.varargs, t.returns]) {
|
|
4278
|
+
collectTypeParams(part, constraints);
|
|
4279
|
+
}
|
|
4280
|
+
const gen = t.typeParams?.length ? `<${t.typeParams.map((n) => {
|
|
4281
|
+
const constraint = constraints.get(n);
|
|
4282
|
+
return `${consts.has(n) ? "const " : ""}${n}${constraint ? ` extends ${briefConstraint(constraint)}` : ""}`;
|
|
4283
|
+
}).join(", ")}>` : "";
|
|
3535
4284
|
const ps = t.params.map((p) => `${p.name ? p.name + ": " : ""}${formatType(p.type)}`);
|
|
3536
4285
|
if (t.varargs) ps.push(`...${formatType(t.varargs)}`);
|
|
3537
4286
|
return `${gen}(${ps.join(", ")}) -> ${formatPredicate(t) ?? formatType(t.returns)}`;
|
|
@@ -3747,6 +4496,12 @@ function isFreshLiteralExpr(e) {
|
|
|
3747
4496
|
return isFreshLiteralExpr(e.expression);
|
|
3748
4497
|
case "UnaryExpression":
|
|
3749
4498
|
return isFreshLiteralExpr(e.argument);
|
|
4499
|
+
// `let n = 5 satisfies number` widens like `let n = 5`. An object or
|
|
4500
|
+
// array has already taken its literals from the contract, and keeps them.
|
|
4501
|
+
case "SatisfiesExpression": {
|
|
4502
|
+
const inner = unwrapParens(e.expression);
|
|
4503
|
+
return inner.type !== "TableExpression" && inner.type !== "ArrayExpression" && isFreshLiteralExpr(inner);
|
|
4504
|
+
}
|
|
3750
4505
|
default:
|
|
3751
4506
|
return false;
|
|
3752
4507
|
}
|
|
@@ -3856,6 +4611,48 @@ var AliasMap = class extends Map {
|
|
|
3856
4611
|
return this.entries();
|
|
3857
4612
|
}
|
|
3858
4613
|
};
|
|
4614
|
+
function unwrapParens(e) {
|
|
4615
|
+
while (e.type === "ParenthesizedExpression") e = e.expression;
|
|
4616
|
+
return e;
|
|
4617
|
+
}
|
|
4618
|
+
function expressionLabel(e, depth = 0) {
|
|
4619
|
+
if (depth > 6) return void 0;
|
|
4620
|
+
const args = (list) => {
|
|
4621
|
+
const parts = list.map((a) => a.type === "StringLiteral" ? JSON.stringify(a.value) : a.type === "NumberLiteral" ? a.raw : a.type === "Identifier" ? a.name : void 0);
|
|
4622
|
+
return parts.every((p) => p !== void 0) && parts.join(", ").length <= 40 ? `(${parts.join(", ")})` : "(...)";
|
|
4623
|
+
};
|
|
4624
|
+
switch (e.type) {
|
|
4625
|
+
case "Identifier":
|
|
4626
|
+
return e.name;
|
|
4627
|
+
case "MemberExpression": {
|
|
4628
|
+
const o = expressionLabel(e.object, depth + 1);
|
|
4629
|
+
return o === void 0 ? void 0 : `${o}${e.optional ? "?." : "."}${e.property.name}`;
|
|
4630
|
+
}
|
|
4631
|
+
case "MethodCallExpression": {
|
|
4632
|
+
const o = expressionLabel(e.object, depth + 1);
|
|
4633
|
+
return o === void 0 ? void 0 : `${o}${e.optional ? "?:" : ":"}${e.method.name}${args(e.arguments)}`;
|
|
4634
|
+
}
|
|
4635
|
+
case "CallExpression": {
|
|
4636
|
+
const o = expressionLabel(e.callee, depth + 1);
|
|
4637
|
+
return o === void 0 ? void 0 : `${o}${e.optional ? "?." : ""}${args(e.arguments)}`;
|
|
4638
|
+
}
|
|
4639
|
+
case "IndexExpression": {
|
|
4640
|
+
const o = expressionLabel(e.object, depth + 1);
|
|
4641
|
+
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 : "...";
|
|
4642
|
+
return o === void 0 ? void 0 : `${o}[${i}]`;
|
|
4643
|
+
}
|
|
4644
|
+
case "ParenthesizedExpression": {
|
|
4645
|
+
const inner = expressionLabel(e.expression, depth + 1);
|
|
4646
|
+
return inner === void 0 ? void 0 : `(${inner})`;
|
|
4647
|
+
}
|
|
4648
|
+
default:
|
|
4649
|
+
return void 0;
|
|
4650
|
+
}
|
|
4651
|
+
}
|
|
4652
|
+
function withoutNil(t) {
|
|
4653
|
+
if (t.kind !== "union") return t.kind === "primitive" && t.name === "nil" ? neverType : t;
|
|
4654
|
+
return union(t.types.filter((m) => !(m.kind === "primitive" && m.name === "nil")));
|
|
4655
|
+
}
|
|
3859
4656
|
var METAMETHODS = {
|
|
3860
4657
|
"+": "__add",
|
|
3861
4658
|
"-": "__sub",
|
|
@@ -3929,14 +4726,16 @@ var TypeAnalyzer = class {
|
|
|
3929
4726
|
/** Recursion guard for `preVisitBody`. */
|
|
3930
4727
|
preVisitDepth = 0;
|
|
3931
4728
|
run() {
|
|
4729
|
+
this.registerAliasDefs(preludeProgram().body);
|
|
3932
4730
|
for (const lib of this.options.libs ?? []) this.registerAliasDefs(lib.body);
|
|
3933
4731
|
this.registerAliasDefs(this.program.body);
|
|
3934
4732
|
for (const lib of this.options.libs ?? []) this.harvestDeclares(lib.body);
|
|
3935
|
-
this.harvestDeclares(this.program.body);
|
|
3936
4733
|
this.registerImportedTypes();
|
|
3937
4734
|
this.resolveAllAliases();
|
|
4735
|
+
this.harvestDeclares(this.program.body, true);
|
|
3938
4736
|
this.indexDeclarations();
|
|
3939
4737
|
for (const [name, id] of this.scopes.globalsByName) {
|
|
4738
|
+
if (this.deferredDeclares.has(name) && !this.options.globalTypes?.[name]) continue;
|
|
3940
4739
|
const t = this.options.globalTypes?.[name] ?? this.libGlobalTypes.get(name) ?? anyType;
|
|
3941
4740
|
this.bindingType.set(id, t);
|
|
3942
4741
|
}
|
|
@@ -3944,6 +4743,8 @@ var TypeAnalyzer = class {
|
|
|
3944
4743
|
try {
|
|
3945
4744
|
const env = /* @__PURE__ */ new Map();
|
|
3946
4745
|
this.visitBlock(this.program.body, env);
|
|
4746
|
+
this.resolveDeferredDeclares();
|
|
4747
|
+
if (this.options.reportUnknownTypes) this.reportUnknownTypes();
|
|
3947
4748
|
} finally {
|
|
3948
4749
|
setAliasExpander(void 0);
|
|
3949
4750
|
}
|
|
@@ -3988,6 +4789,13 @@ var TypeAnalyzer = class {
|
|
|
3988
4789
|
if (stmt.type !== "ImportStatement") continue;
|
|
3989
4790
|
const exports = this.moduleFor(stmt.source.value);
|
|
3990
4791
|
if (!exports) continue;
|
|
4792
|
+
if (stmt.namespaceImport) {
|
|
4793
|
+
for (const [name, exported] of exports.types) {
|
|
4794
|
+
const qualified = `${stmt.namespaceImport.name}.${name}`;
|
|
4795
|
+
this.importedTypes.set(qualified, exported);
|
|
4796
|
+
this.aliases.set(qualified, exported.type);
|
|
4797
|
+
}
|
|
4798
|
+
}
|
|
3991
4799
|
for (const s of stmt.specifiers) {
|
|
3992
4800
|
const exported = exports.types.get(s.imported.name);
|
|
3993
4801
|
if (exported) {
|
|
@@ -4099,13 +4907,38 @@ var TypeAnalyzer = class {
|
|
|
4099
4907
|
* string. Any other value is simply redeclared: a sourcemap's
|
|
4100
4908
|
* `declare script: <this file's instance>` replaces the library's
|
|
4101
4909
|
* `declare script: LuaSourceContainer`. */
|
|
4102
|
-
|
|
4910
|
+
/** Program `declare`s whose type depends on a value's, by name. */
|
|
4911
|
+
deferredDeclares = /* @__PURE__ */ new Map();
|
|
4912
|
+
/** A library that declares a name a second time adds to it rather than
|
|
4913
|
+
* replacing it: `declare table: { find: ... }` on top of Lua's `table`
|
|
4914
|
+
* leaves both members there, the way overloads of a function accumulate.
|
|
4915
|
+
* This is what lets one definitions file build on another's — Luau's on
|
|
4916
|
+
* Lua's, Roblox's on Luau's. A property declared twice takes its later
|
|
4917
|
+
* type. Classes stay as they are: they come from one generated file and
|
|
4918
|
+
* merging them would only blur it. */
|
|
4919
|
+
mergeDeclared(prev, next) {
|
|
4920
|
+
if (!prev || prev.kind !== "object" || next.kind !== "object") return next;
|
|
4921
|
+
if (prev.class || next.class) return next;
|
|
4922
|
+
return objectType(
|
|
4923
|
+
[...prev.properties, ...next.properties],
|
|
4924
|
+
next.indexer ?? prev.indexer,
|
|
4925
|
+
next.frozen ?? prev.frozen
|
|
4926
|
+
);
|
|
4927
|
+
}
|
|
4928
|
+
harvestDeclares(block, own = false) {
|
|
4103
4929
|
for (const stmt of block.statements) {
|
|
4104
4930
|
if (stmt.type !== "DeclareStatement") continue;
|
|
4931
|
+
if (own && (containsTypeQuery(stmt.valueType) || referencedTypeNames(stmt.valueType).some((name) => this.dependsOnTypeQuery(name)))) {
|
|
4932
|
+
this.deferredDeclares.set(stmt.name, stmt);
|
|
4933
|
+
continue;
|
|
4934
|
+
}
|
|
4105
4935
|
const t = this.resolveType(stmt.valueType);
|
|
4106
4936
|
const prev = this.libGlobalTypes.get(stmt.name);
|
|
4107
4937
|
const overload = prev && stmt.valueType.type === "FunctionTypeNode" && (prev.kind === "function" || prev.kind === "intersection");
|
|
4108
|
-
this.libGlobalTypes.set(
|
|
4938
|
+
this.libGlobalTypes.set(
|
|
4939
|
+
stmt.name,
|
|
4940
|
+
overload ? intersection([prev, t]) : this.mergeDeclared(prev, t)
|
|
4941
|
+
);
|
|
4109
4942
|
}
|
|
4110
4943
|
}
|
|
4111
4944
|
resolveAllAliases() {
|
|
@@ -4115,14 +4948,154 @@ var TypeAnalyzer = class {
|
|
|
4115
4948
|
this.aliases.defer(name, () => this.classType(cls));
|
|
4116
4949
|
continue;
|
|
4117
4950
|
}
|
|
4118
|
-
if (
|
|
4951
|
+
if (this.dependsOnTypeQuery(name)) continue;
|
|
4119
4952
|
this.withTypeParams(def.params, () => {
|
|
4120
4953
|
this.aliases.set(name, this.resolveDef(def));
|
|
4121
4954
|
});
|
|
4122
4955
|
}
|
|
4123
4956
|
}
|
|
4957
|
+
typeQueryDependents = /* @__PURE__ */ new Map();
|
|
4958
|
+
/** Does alias `name` contain a `typeof`, itself or through an alias it
|
|
4959
|
+
* names? */
|
|
4960
|
+
dependsOnTypeQuery(name, visiting = /* @__PURE__ */ new Set()) {
|
|
4961
|
+
const known = this.typeQueryDependents.get(name);
|
|
4962
|
+
if (known !== void 0) return known;
|
|
4963
|
+
const def = this.aliasDefs.get(name);
|
|
4964
|
+
if (!def || def.class || visiting.has(name)) return false;
|
|
4965
|
+
visiting.add(name);
|
|
4966
|
+
const result = containsTypeQuery(def.node) || referencedTypeNames(def.node).some((other) => other !== name && this.dependsOnTypeQuery(other, visiting));
|
|
4967
|
+
visiting.delete(name);
|
|
4968
|
+
this.typeQueryDependents.set(name, result);
|
|
4969
|
+
return result;
|
|
4970
|
+
}
|
|
4124
4971
|
/** The aliases `resolveAllAliases` left for later, now that every binding
|
|
4125
4972
|
* has its type. */
|
|
4973
|
+
/** Names this file imports. A module that could not be found is reported
|
|
4974
|
+
* as the missing module it is; the names it was to bring are not also
|
|
4975
|
+
* typos. */
|
|
4976
|
+
importedNames() {
|
|
4977
|
+
if (this.imported) return this.imported;
|
|
4978
|
+
this.imported = /* @__PURE__ */ new Set();
|
|
4979
|
+
for (const statement of this.program.body.statements) {
|
|
4980
|
+
if (statement.type !== "ImportStatement") continue;
|
|
4981
|
+
if (statement.defaultImport) this.imported.add(statement.defaultImport.name);
|
|
4982
|
+
if (statement.namespaceImport) this.imported.add(statement.namespaceImport.name);
|
|
4983
|
+
for (const specifier of statement.specifiers) this.imported.add(specifier.local.name);
|
|
4984
|
+
}
|
|
4985
|
+
return this.imported;
|
|
4986
|
+
}
|
|
4987
|
+
imported;
|
|
4988
|
+
/** What a `return` gives, against what the function declared. */
|
|
4989
|
+
checkReturn(stmt, declared, types, sources, env) {
|
|
4990
|
+
if (!declared || !this.emitDiagnostics) return;
|
|
4991
|
+
if (declared.kind === "any" || declared.kind === "unknown" || this.namesNothing(declared)) return;
|
|
4992
|
+
const actual = stmt.arguments.length === 0 ? nilType : types.length === 1 ? types[0] : tuple([...types], true);
|
|
4993
|
+
const source = stmt.arguments.length === 1 ? sources[0] : void 0;
|
|
4994
|
+
const fits = source ? this.fitsAnnotation(source, declared, actual, env) : isAssignable(actual, declared) || isAssignable(widen(actual), declared);
|
|
4995
|
+
if (fits) return;
|
|
4996
|
+
this.diagnostics.push({
|
|
4997
|
+
node: stmt,
|
|
4998
|
+
message: `Type '${formatType(actual)}' is not assignable to '${briefType(declared)}'`
|
|
4999
|
+
});
|
|
5000
|
+
}
|
|
5001
|
+
/** A function that declared what it returns but never does. Only a body
|
|
5002
|
+
* with no `return` at all is reported: anything subtler needs to know
|
|
5003
|
+
* which paths can run off the end, and a wrong guess there is worse than
|
|
5004
|
+
* a missing complaint. */
|
|
5005
|
+
checkReturnsAtAll(func, declared) {
|
|
5006
|
+
if (!declared || !this.emitDiagnostics) return;
|
|
5007
|
+
if (func.predicate) return;
|
|
5008
|
+
if (declared.kind === "any" || declared.kind === "unknown" || declared.kind === "never") return;
|
|
5009
|
+
if (isAssignable(nilType, declared) || this.namesNothing(declared)) return;
|
|
5010
|
+
let found = false;
|
|
5011
|
+
const walk = (statements) => {
|
|
5012
|
+
for (const statement of statements) {
|
|
5013
|
+
if (found) return;
|
|
5014
|
+
if (statement.type === "ReturnStatement") {
|
|
5015
|
+
found = true;
|
|
5016
|
+
return;
|
|
5017
|
+
}
|
|
5018
|
+
for (const value of Object.values(statement)) {
|
|
5019
|
+
if (value && typeof value === "object" && "statements" in value) {
|
|
5020
|
+
walk(value.statements);
|
|
5021
|
+
} else if (Array.isArray(value)) {
|
|
5022
|
+
for (const item of value) {
|
|
5023
|
+
const block = item;
|
|
5024
|
+
if (block?.body?.statements) walk(block.body.statements);
|
|
5025
|
+
}
|
|
5026
|
+
}
|
|
5027
|
+
}
|
|
5028
|
+
}
|
|
5029
|
+
};
|
|
5030
|
+
walk(func.body.statements);
|
|
5031
|
+
if (found) return;
|
|
5032
|
+
this.diagnostics.push({
|
|
5033
|
+
node: func.body,
|
|
5034
|
+
message: `A function that returns '${briefType(declared)}' must return a value`
|
|
5035
|
+
});
|
|
5036
|
+
}
|
|
5037
|
+
/** Does this type rest on a name nothing declares? Such a type says
|
|
5038
|
+
* nothing about what fits it, so checking against it only piles a second
|
|
5039
|
+
* complaint on top of "Cannot find name". */
|
|
5040
|
+
namesNothing(t, seen = /* @__PURE__ */ new Set()) {
|
|
5041
|
+
if (seen.has(t)) return false;
|
|
5042
|
+
seen.add(t);
|
|
5043
|
+
if (t.kind === "genericRef") {
|
|
5044
|
+
return !this.aliasDefs.has(t.name) && !this.importedTypes.has(t.name) && this.options.libTypes?.[t.name] === void 0;
|
|
5045
|
+
}
|
|
5046
|
+
switch (t.kind) {
|
|
5047
|
+
case "union":
|
|
5048
|
+
case "intersection":
|
|
5049
|
+
return t.types.some((m) => this.namesNothing(m, seen));
|
|
5050
|
+
case "array":
|
|
5051
|
+
return this.namesNothing(t.element, seen);
|
|
5052
|
+
case "tuple":
|
|
5053
|
+
return t.elements.some((e) => this.namesNothing(e, seen));
|
|
5054
|
+
case "object":
|
|
5055
|
+
if (t.class) return false;
|
|
5056
|
+
return [...t.properties.values()].some((v) => this.namesNothing(v.type, seen));
|
|
5057
|
+
default:
|
|
5058
|
+
return false;
|
|
5059
|
+
}
|
|
5060
|
+
}
|
|
5061
|
+
/** Every type name in the program that resolved to nothing — a typo, or a
|
|
5062
|
+
* library the config does not load. A name that resolves to a type
|
|
5063
|
+
* parameter, an alias (even one still being resolved), an imported type or
|
|
5064
|
+
* a primitive is fine; what is left is a reference that stayed itself. */
|
|
5065
|
+
reportUnknownTypes() {
|
|
5066
|
+
if (!this.emitDiagnostics) return;
|
|
5067
|
+
const reported = /* @__PURE__ */ new Set();
|
|
5068
|
+
const visit = (node) => {
|
|
5069
|
+
if (!node || typeof node !== "object") return;
|
|
5070
|
+
if (Array.isArray(node)) {
|
|
5071
|
+
for (const item of node) visit(item);
|
|
5072
|
+
return;
|
|
5073
|
+
}
|
|
5074
|
+
const record = node;
|
|
5075
|
+
if (record.type === "TypeReference" && typeof record.base === "string") {
|
|
5076
|
+
const name = typeof record.namespace === "string" ? `${record.namespace}.${record.base}` : record.base;
|
|
5077
|
+
const resolved = this.typeOfTypeNode.get(node);
|
|
5078
|
+
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]);
|
|
5079
|
+
const at = node;
|
|
5080
|
+
const key = `${at.line.start}:${at.column.start}`;
|
|
5081
|
+
if (unresolved && !reported.has(key)) {
|
|
5082
|
+
reported.add(key);
|
|
5083
|
+
this.diagnostics.push({ node, message: `Cannot find name '${name}'` });
|
|
5084
|
+
}
|
|
5085
|
+
}
|
|
5086
|
+
for (const [key, value] of Object.entries(node)) {
|
|
5087
|
+
if (key !== "line" && key !== "column" && value && typeof value === "object") visit(value);
|
|
5088
|
+
}
|
|
5089
|
+
};
|
|
5090
|
+
visit(this.program.body);
|
|
5091
|
+
}
|
|
5092
|
+
/** Deferred `declare`s nothing used, typed now for tools that ask. */
|
|
5093
|
+
resolveDeferredDeclares() {
|
|
5094
|
+
for (const name of this.deferredDeclares.keys()) {
|
|
5095
|
+
const id = this.scopes.globalsByName.get(name);
|
|
5096
|
+
if (id !== void 0 && !this.bindingType.has(id)) this.bindingType.set(id, this.declaredAhead(id) ?? anyType);
|
|
5097
|
+
}
|
|
5098
|
+
}
|
|
4126
5099
|
resolveDeferredAliases() {
|
|
4127
5100
|
for (const [name, def] of this.aliasDefs) {
|
|
4128
5101
|
if (this.aliases.has(name)) continue;
|
|
@@ -4182,6 +5155,16 @@ var TypeAnalyzer = class {
|
|
|
4182
5155
|
});
|
|
4183
5156
|
return subst;
|
|
4184
5157
|
}
|
|
5158
|
+
/** An imported type, with its type arguments applied. */
|
|
5159
|
+
importedType(imported, typeArguments) {
|
|
5160
|
+
if (!imported.params.length) return imported.type;
|
|
5161
|
+
const subst = /* @__PURE__ */ new Map();
|
|
5162
|
+
imported.params.forEach((name, i) => {
|
|
5163
|
+
const arg = typeArguments[i];
|
|
5164
|
+
subst.set(name, arg ? this.resolveType(arg) : unknownType);
|
|
5165
|
+
});
|
|
5166
|
+
return this.reduceType(substitute(imported.type, subst));
|
|
5167
|
+
}
|
|
4185
5168
|
// --------------------------------------------------------
|
|
4186
5169
|
// TypeNode -> Type
|
|
4187
5170
|
// --------------------------------------------------------
|
|
@@ -4232,17 +5215,11 @@ var TypeAnalyzer = class {
|
|
|
4232
5215
|
});
|
|
4233
5216
|
}
|
|
4234
5217
|
const imported = this.importedTypes.get(node.base);
|
|
4235
|
-
if (imported)
|
|
4236
|
-
if (!imported.params.length) return imported.type;
|
|
4237
|
-
const subst = /* @__PURE__ */ new Map();
|
|
4238
|
-
imported.params.forEach((name2, i) => {
|
|
4239
|
-
const arg = node.typeArguments[i];
|
|
4240
|
-
subst.set(name2, arg ? this.resolveType(arg) : unknownType);
|
|
4241
|
-
});
|
|
4242
|
-
return this.reduceType(substitute(imported.type, subst));
|
|
4243
|
-
}
|
|
5218
|
+
if (imported) return this.importedType(imported, node.typeArguments);
|
|
4244
5219
|
const lib = this.options.libTypes?.[node.base];
|
|
4245
5220
|
if (lib) return lib;
|
|
5221
|
+
} else if (this.importedTypes.has(name)) {
|
|
5222
|
+
return this.importedType(this.importedTypes.get(name), node.typeArguments);
|
|
4246
5223
|
} else if (this.aliasDefs.has(name)) {
|
|
4247
5224
|
return this.expand({
|
|
4248
5225
|
kind: "genericRef",
|
|
@@ -4301,13 +5278,13 @@ var TypeAnalyzer = class {
|
|
|
4301
5278
|
type: p.optional ? optional(this.resolveType(p.typeAnnotation)) : this.resolveType(p.typeAnnotation),
|
|
4302
5279
|
optional: p.optional
|
|
4303
5280
|
}));
|
|
4304
|
-
return fn(
|
|
5281
|
+
return this.withTypeParamDefaults(fn(
|
|
4305
5282
|
params,
|
|
4306
5283
|
this.resolveType(node.returnType),
|
|
4307
5284
|
node.hasVarargs ? node.varargType ? this.resolveType(node.varargType) : anyType : void 0,
|
|
4308
5285
|
names,
|
|
4309
5286
|
this.resolvePredicate(node.predicate, params)
|
|
4310
|
-
);
|
|
5287
|
+
), node.generics);
|
|
4311
5288
|
});
|
|
4312
5289
|
}
|
|
4313
5290
|
case "TypeofTypeNode": {
|
|
@@ -4553,7 +5530,11 @@ var TypeAnalyzer = class {
|
|
|
4553
5530
|
}
|
|
4554
5531
|
reduceConditional(t) {
|
|
4555
5532
|
const checkType = this.reduceType(t.checkType);
|
|
4556
|
-
|
|
5533
|
+
const extendsType = this.reduceType(t.extendsType);
|
|
5534
|
+
const free = new Set(t.inferVars);
|
|
5535
|
+
if (containsTypeParam(checkType) || containsTypeParam(extendsType, /* @__PURE__ */ new Set(), free)) {
|
|
5536
|
+
return { ...t, checkType, extendsType };
|
|
5537
|
+
}
|
|
4557
5538
|
if (t.distributeParam && checkType.kind === "union") {
|
|
4558
5539
|
return union(checkType.types.map((m) => this.branchOf(t, m)));
|
|
4559
5540
|
}
|
|
@@ -4658,15 +5639,22 @@ var TypeAnalyzer = class {
|
|
|
4658
5639
|
const source = sources[i];
|
|
4659
5640
|
if (this.emitDiagnostics && target.type === "IdentifierPattern" && target.typeAnnotation && source) {
|
|
4660
5641
|
const declared = this.resolveType(target.typeAnnotation);
|
|
4661
|
-
if (declared.kind !== "any" && !this.fitsAnnotation(source, declared, inferred, env)) {
|
|
5642
|
+
if (declared.kind !== "any" && !this.namesNothing(declared) && !this.fitsAnnotation(source, declared, inferred, env)) {
|
|
4662
5643
|
this.diagnostics.push({
|
|
4663
5644
|
node: stmt,
|
|
4664
5645
|
message: `Type '${formatType(inferred)}' is not assignable to '${formatType(declared)}'`
|
|
4665
5646
|
});
|
|
5647
|
+
} else if (declared.kind !== "any") {
|
|
5648
|
+
this.reportExcessProperties(source, declared);
|
|
4666
5649
|
}
|
|
4667
5650
|
}
|
|
4668
5651
|
const mode = this.initIsAsConst(source) ? "asconst" : !isFreshLiteralExpr(source) ? "keep" : stmt.kind === "const" ? "const" : "widen";
|
|
4669
5652
|
this.bindPattern(target, inferred, env, mode);
|
|
5653
|
+
if (stmt.kind === "const") {
|
|
5654
|
+
this.correlateDestructuring(target, inferred, env);
|
|
5655
|
+
this.correlateIndexed(target, source, env);
|
|
5656
|
+
this.aliasReference(target, source);
|
|
5657
|
+
}
|
|
4670
5658
|
});
|
|
4671
5659
|
return;
|
|
4672
5660
|
}
|
|
@@ -4674,6 +5662,7 @@ var TypeAnalyzer = class {
|
|
|
4674
5662
|
this.checkParamOrder(stmt.func.params, stmt);
|
|
4675
5663
|
for (const sig of stmt.signatures ?? []) this.checkParamOrder(sig.params, stmt);
|
|
4676
5664
|
const id = this.bindingIdByName(stmt.name.name, stmt.name);
|
|
5665
|
+
this.paramsFromSignatures(stmt.func, stmt.signatures);
|
|
4677
5666
|
const fnType = stmt.signatures?.length ? intersection(stmt.signatures.map((s) => this.signatureToFnType(s))) : this.inferFunctionBody(stmt.func, env);
|
|
4678
5667
|
if (id !== void 0) {
|
|
4679
5668
|
this.bindingType.set(id, fnType);
|
|
@@ -4689,6 +5678,7 @@ var TypeAnalyzer = class {
|
|
|
4689
5678
|
const memberName = stmt.target.method?.name ?? (stmt.target.path.length === 1 ? stmt.target.path[0].name : void 0);
|
|
4690
5679
|
if (memberName === void 0 && stmt.target.path.length === 0) {
|
|
4691
5680
|
if (targetId !== void 0) {
|
|
5681
|
+
this.paramsFromSignatures(stmt.func, stmt.signatures);
|
|
4692
5682
|
const fnType = stmt.signatures?.length ? intersection(stmt.signatures.map((s) => this.signatureToFnType(s))) : this.inferFunctionBody(stmt.func, env);
|
|
4693
5683
|
this.bindingType.set(targetId, fnType);
|
|
4694
5684
|
this.setBinding(env, targetId, fnType);
|
|
@@ -4698,6 +5688,7 @@ var TypeAnalyzer = class {
|
|
|
4698
5688
|
}
|
|
4699
5689
|
const recv = targetId === void 0 ? anyType : this.currentType(targetId, env);
|
|
4700
5690
|
this.withSelfType(stmt.isMethod ? recv : void 0, () => {
|
|
5691
|
+
this.paramsFromSignatures(stmt.func, stmt.signatures);
|
|
4701
5692
|
const fnType = stmt.signatures?.length ? intersection(stmt.signatures.map((s) => this.signatureToFnType(s))) : this.inferFunctionBody(stmt.func, env);
|
|
4702
5693
|
if (memberName !== void 0 && targetId !== void 0) {
|
|
4703
5694
|
const grown = intersection([
|
|
@@ -4729,6 +5720,7 @@ var TypeAnalyzer = class {
|
|
|
4729
5720
|
if (target.type === "Identifier") {
|
|
4730
5721
|
const id = this.bindingIdOf(target);
|
|
4731
5722
|
if (id !== void 0) {
|
|
5723
|
+
this.uncorrelate(id);
|
|
4732
5724
|
const next = isFreshLiteralExpr(source) ? widen(vt) : vt;
|
|
4733
5725
|
if (this.annotated.has(id)) {
|
|
4734
5726
|
const declared = this.bindingType.get(id);
|
|
@@ -4802,16 +5794,40 @@ var TypeAnalyzer = class {
|
|
|
4802
5794
|
case "GenericForStatement": {
|
|
4803
5795
|
const iterTypes = stmt.iterators.map((it) => this.infer(it, env));
|
|
4804
5796
|
const bodyEnv = forkEnv(env);
|
|
4805
|
-
const
|
|
4806
|
-
|
|
4807
|
-
|
|
4808
|
-
|
|
5797
|
+
const rows = stmt.variables.length >= 2 ? this.iterationRows(stmt.iterators[0], iterTypes[0]) : void 0;
|
|
5798
|
+
if (rows) {
|
|
5799
|
+
const [key, value] = stmt.variables;
|
|
5800
|
+
this.bindPattern(key, union(rows.map((r) => r[0])), bodyEnv, "keep");
|
|
5801
|
+
this.bindPattern(value, union(rows.map((r) => r[1])), bodyEnv, "keep");
|
|
5802
|
+
stmt.variables.slice(2).forEach((v) => this.bindPattern(v, unknownType, bodyEnv, "widen"));
|
|
5803
|
+
const keyId = key.type === "IdentifierPattern" ? this.bindingIdByName(key.name, key) : void 0;
|
|
5804
|
+
const valueId = value.type === "IdentifierPattern" ? this.bindingIdByName(value.name, value) : void 0;
|
|
5805
|
+
if (keyId !== void 0 && valueId !== void 0) this.correlateBindings(bodyEnv, [keyId, valueId], rows);
|
|
5806
|
+
} else {
|
|
5807
|
+
const [keyT, valT] = this.iterationTypes(stmt.iterators[0], iterTypes[0], stmt.variables.length);
|
|
5808
|
+
stmt.variables.forEach((v, i) => {
|
|
5809
|
+
this.bindPattern(v, i === 0 ? keyT : i === 1 ? valT : unknownType, bodyEnv, "widen");
|
|
5810
|
+
});
|
|
5811
|
+
}
|
|
4809
5812
|
this.visitBlock(stmt.body, bodyEnv);
|
|
4810
5813
|
return;
|
|
4811
5814
|
}
|
|
4812
|
-
case "ReturnStatement":
|
|
4813
|
-
|
|
5815
|
+
case "ReturnStatement": {
|
|
5816
|
+
const declared = this.declaredReturns[this.declaredReturns.length - 1];
|
|
5817
|
+
if (declared) {
|
|
5818
|
+
if (stmt.arguments.length === 1) {
|
|
5819
|
+
this.applyContext(stmt.arguments[0], declared);
|
|
5820
|
+
} else if (declared.kind === "tuple" && declared.isPack) {
|
|
5821
|
+
stmt.arguments.forEach((a, i) => this.applyContext(a, declared.elements[i]));
|
|
5822
|
+
}
|
|
5823
|
+
}
|
|
5824
|
+
const { types, sources } = this.valueList(stmt.arguments, env);
|
|
5825
|
+
this.checkReturn(stmt, declared, types, sources, env);
|
|
5826
|
+
if (this.returnTypes) {
|
|
5827
|
+
this.returnTypes.push(stmt.arguments.length === 0 ? nilType : types.length === 1 ? types[0] : tuple([...types], true));
|
|
5828
|
+
}
|
|
4814
5829
|
return;
|
|
5830
|
+
}
|
|
4815
5831
|
case "ExportStatement":
|
|
4816
5832
|
this.visitStatement(stmt.declaration, env);
|
|
4817
5833
|
return;
|
|
@@ -4846,6 +5862,14 @@ var TypeAnalyzer = class {
|
|
|
4846
5862
|
};
|
|
4847
5863
|
if (resolving && !exports) report(stmt.source, `Cannot find module '${specifier}'`);
|
|
4848
5864
|
const usable = exports && !exports.partial ? exports : void 0;
|
|
5865
|
+
if (stmt.namespaceImport) {
|
|
5866
|
+
const id = this.bindingIdByName(stmt.namespaceImport.name, stmt.namespaceImport);
|
|
5867
|
+
if (id !== void 0) {
|
|
5868
|
+
const members = [...usable?.values ?? []].map(([name, type]) => [name, { type, optional: false, readonly: true }]);
|
|
5869
|
+
if (usable?.default) members.push(["default", { type: usable.default, optional: false, readonly: true }]);
|
|
5870
|
+
this.bindingType.set(id, usable ? objectType(members) : anyType);
|
|
5871
|
+
}
|
|
5872
|
+
}
|
|
4849
5873
|
if (stmt.defaultImport) {
|
|
4850
5874
|
if (usable && usable.default === void 0) {
|
|
4851
5875
|
report(stmt.defaultImport, `Module '${specifier}' has no default export`);
|
|
@@ -4986,6 +6010,7 @@ var TypeAnalyzer = class {
|
|
|
4986
6010
|
}
|
|
4987
6011
|
if (p.typeAnnotation) {
|
|
4988
6012
|
const t = this.resolveType(p.typeAnnotation);
|
|
6013
|
+
if (p.default) this.applyContext(p.default, t);
|
|
4989
6014
|
return p.optional ? optional(t) : t;
|
|
4990
6015
|
}
|
|
4991
6016
|
if (p.pattern) return this.patternToType(p.pattern, env);
|
|
@@ -5003,7 +6028,12 @@ var TypeAnalyzer = class {
|
|
|
5003
6028
|
applyContext(expr, expected) {
|
|
5004
6029
|
let e = expr;
|
|
5005
6030
|
while (e.type === "ParenthesizedExpression") e = e.expression;
|
|
5006
|
-
if (
|
|
6031
|
+
if (!expected) return;
|
|
6032
|
+
this.expectedTypeOf.set(expr, expected);
|
|
6033
|
+
this.expectedTypeOf.set(e, expected);
|
|
6034
|
+
if (e.type === "ArrayExpression") return this.applyArrayContext(e, expected);
|
|
6035
|
+
if (e.type === "TableExpression") return this.applyTableContext(e, expected);
|
|
6036
|
+
if (e.type !== "FunctionExpression") return;
|
|
5007
6037
|
const members = expected.kind === "union" ? expected.types : [expected];
|
|
5008
6038
|
const signatures = members.flatMap((m) => this.overloadsOf(this.expand(m)));
|
|
5009
6039
|
if (!signatures.length) return;
|
|
@@ -5019,6 +6049,46 @@ var TypeAnalyzer = class {
|
|
|
5019
6049
|
this.contextualParams.set(p, containsTypeParam(t) ? anyType : t);
|
|
5020
6050
|
});
|
|
5021
6051
|
}
|
|
6052
|
+
/** What an array literal is expected to be: an empty one takes that type
|
|
6053
|
+
* outright — `let queue: thread[] = []` is a `thread[]`, as in TypeScript
|
|
6054
|
+
* — and the elements of any other get the element type as their own
|
|
6055
|
+
* context. */
|
|
6056
|
+
contextualArrays = /* @__PURE__ */ new WeakMap();
|
|
6057
|
+
applyArrayContext(e, expected) {
|
|
6058
|
+
const target = this.expectedMembers(expected).find((m) => m.kind === "array" || m.kind === "tuple");
|
|
6059
|
+
if (!target) return;
|
|
6060
|
+
if (!e.elements.length) {
|
|
6061
|
+
if (!containsTypeParam(target)) this.contextualArrays.set(e, target);
|
|
6062
|
+
return;
|
|
6063
|
+
}
|
|
6064
|
+
e.elements.forEach((element, i) => {
|
|
6065
|
+
if (element.type === "SpreadElement") return;
|
|
6066
|
+
const elementType = target.kind === "array" ? target.element : target.elements[i];
|
|
6067
|
+
this.applyContext(element, elementType);
|
|
6068
|
+
});
|
|
6069
|
+
}
|
|
6070
|
+
/** `{ list: [] }` where `{ list: thread[] }` is expected: each field's
|
|
6071
|
+
* value gets its property's type as context. */
|
|
6072
|
+
applyTableContext(e, expected) {
|
|
6073
|
+
const objects = this.expectedMembers(expected).filter((m) => m.kind === "object");
|
|
6074
|
+
if (!objects.length) return;
|
|
6075
|
+
for (const field of e.fields) {
|
|
6076
|
+
if (field.type !== "TableFieldNamed") continue;
|
|
6077
|
+
const key = field.key.type === "Identifier" ? field.key.name : field.key.value;
|
|
6078
|
+
const types = objects.flatMap((o) => {
|
|
6079
|
+
const property = o.properties.get(key);
|
|
6080
|
+
return property ? [property.type] : o.indexer ? [o.indexer.value] : [];
|
|
6081
|
+
});
|
|
6082
|
+
if (types.length) this.applyContext(field.value, union(types));
|
|
6083
|
+
}
|
|
6084
|
+
}
|
|
6085
|
+
/** The members of an expected type worth matching a literal against:
|
|
6086
|
+
* aliases seen through, `nil` left out. */
|
|
6087
|
+
expectedMembers(expected) {
|
|
6088
|
+
const t = this.expand(expected);
|
|
6089
|
+
const members = t.kind === "union" ? t.types : [t];
|
|
6090
|
+
return members.map((m) => this.expand(m)).filter((m) => !(m.kind === "primitive" && m.name === "nil"));
|
|
6091
|
+
}
|
|
5022
6092
|
/** The parameter type each written argument lands on, across `fns`. */
|
|
5023
6093
|
expectedArguments(written, fns, selfOf) {
|
|
5024
6094
|
return written.map((_, j) => {
|
|
@@ -5046,11 +6116,28 @@ var TypeAnalyzer = class {
|
|
|
5046
6116
|
}
|
|
5047
6117
|
return tuple(target.elements.map((el) => el ? leaf(el.value, el.default) : anyType));
|
|
5048
6118
|
}
|
|
6119
|
+
/** The type of `...` in each function body being walked. */
|
|
6120
|
+
varargs = [];
|
|
6121
|
+
/** What each function body being walked declared it returns. */
|
|
6122
|
+
declaredReturns = [];
|
|
6123
|
+
/** Run `body` with `...` and `return` as `func` declares them. */
|
|
6124
|
+
withVarargs(func, body) {
|
|
6125
|
+
this.varargs.push(func.hasVarargs ? func.varargTypeAnnotation ? this.resolveType(func.varargTypeAnnotation) : anyType : void 0);
|
|
6126
|
+
this.declaredReturns.push(func.predicate ? booleanType : func.returnType ? this.resolveType(func.returnType) : void 0);
|
|
6127
|
+
try {
|
|
6128
|
+
return body();
|
|
6129
|
+
} finally {
|
|
6130
|
+
this.varargs.pop();
|
|
6131
|
+
this.declaredReturns.pop();
|
|
6132
|
+
}
|
|
6133
|
+
}
|
|
5049
6134
|
visitFunctionBodyInner(func, outerEnv) {
|
|
5050
6135
|
const env = forkEnv(outerEnv);
|
|
5051
6136
|
for (const p of func.params) {
|
|
5052
6137
|
if (p.pattern) {
|
|
5053
|
-
|
|
6138
|
+
const type = this.paramType(p, env);
|
|
6139
|
+
this.bindPattern(p.pattern, type, env, "widen");
|
|
6140
|
+
this.correlateDestructuring(p.pattern, type, env);
|
|
5054
6141
|
continue;
|
|
5055
6142
|
}
|
|
5056
6143
|
const id = this.bindingIdByName(p.name, p);
|
|
@@ -5061,13 +6148,16 @@ var TypeAnalyzer = class {
|
|
|
5061
6148
|
if (p.typeAnnotation) this.annotated.add(id);
|
|
5062
6149
|
}
|
|
5063
6150
|
}
|
|
5064
|
-
this.
|
|
6151
|
+
this.withVarargs(func, () => this.collectReturns(void 0, () => {
|
|
6152
|
+
this.visitBlock(func.body, env);
|
|
6153
|
+
this.checkReturnsAtAll(func, this.declaredReturns[this.declaredReturns.length - 1]);
|
|
6154
|
+
}));
|
|
5065
6155
|
}
|
|
5066
6156
|
/** Return type of calling `f` with `argTypes`. For a generic function,
|
|
5067
6157
|
* infers the type parameters from the arguments and substitutes. */
|
|
5068
|
-
callReturn(f, argTypes) {
|
|
6158
|
+
callReturn(f, argTypes, explicit) {
|
|
5069
6159
|
if (!f.typeParams?.length) return f.returns;
|
|
5070
|
-
return this.reduceType(substitute(f.returns, this.inferTypeArgs(f, argTypes)));
|
|
6160
|
+
return this.reduceType(substitute(f.returns, this.inferTypeArgs(f, argTypes, explicit)));
|
|
5071
6161
|
}
|
|
5072
6162
|
/** Infer a generic call's type arguments from the argument types.
|
|
5073
6163
|
*
|
|
@@ -5075,16 +6165,47 @@ var TypeAnalyzer = class {
|
|
|
5075
6165
|
* `1` — *except* against a parameter whose constraint is made of literal
|
|
5076
6166
|
* types, where the literal is the whole point. That is what lets
|
|
5077
6167
|
* `<K extends keyof T>(name: K) -> T[K]` pick out one property. */
|
|
5078
|
-
|
|
5079
|
-
|
|
6168
|
+
/** The type arguments a call writes out, checked for count. */
|
|
6169
|
+
explicitTypeArguments(expr, fns) {
|
|
6170
|
+
const written = expr.typeArguments;
|
|
6171
|
+
if (!written?.length) return void 0;
|
|
6172
|
+
const resolved = written.map((node) => this.resolveType(node));
|
|
6173
|
+
const most = Math.max(0, ...fns.map((f) => f.typeParams?.length ?? 0));
|
|
6174
|
+
if (this.emitDiagnostics && resolved.length > most) {
|
|
6175
|
+
this.diagnostics.push({
|
|
6176
|
+
node: written[most],
|
|
6177
|
+
message: most === 0 ? "This call takes no type arguments" : `Expected ${most} type argument${most === 1 ? "" : "s"}, got ${resolved.length}`
|
|
6178
|
+
});
|
|
6179
|
+
}
|
|
6180
|
+
return resolved;
|
|
6181
|
+
}
|
|
6182
|
+
/** `<T = Instance>`: what a call falls back to for a parameter it neither
|
|
6183
|
+
* is given nor can infer. */
|
|
6184
|
+
withTypeParamDefaults(type, generics) {
|
|
6185
|
+
if (type.kind !== "function") return type;
|
|
6186
|
+
const defaults = {};
|
|
6187
|
+
for (const generic of generics) {
|
|
6188
|
+
if (generic.default && !generic.isPack) defaults[generic.name] = this.resolveType(generic.default);
|
|
6189
|
+
}
|
|
6190
|
+
return Object.keys(defaults).length ? { ...type, typeParamDefaults: defaults } : type;
|
|
6191
|
+
}
|
|
6192
|
+
inferTypeArgs(f, argTypes, explicit) {
|
|
5080
6193
|
const subst = /* @__PURE__ */ new Map();
|
|
6194
|
+
if (explicit?.length) {
|
|
6195
|
+
(f.typeParams ?? []).forEach((name, i) => {
|
|
6196
|
+
if (explicit[i]) subst.set(name, explicit[i]);
|
|
6197
|
+
});
|
|
6198
|
+
}
|
|
6199
|
+
const vars = new Set((f.typeParams ?? []).filter((name) => !subst.has(name)));
|
|
5081
6200
|
f.params.forEach((p, i) => {
|
|
5082
6201
|
const arg = argTypes[i];
|
|
5083
6202
|
if (arg === void 0) return;
|
|
5084
6203
|
const param = p.type.kind === "typeParam" && p.type.constraint ? { ...p.type, constraint: this.reduceType(p.type.constraint) } : p.type;
|
|
5085
6204
|
unify(p.type, keepsLiterals(param) ? arg : widen(arg), vars, subst);
|
|
5086
6205
|
});
|
|
5087
|
-
for (const name of f.typeParams ?? [])
|
|
6206
|
+
for (const name of f.typeParams ?? []) {
|
|
6207
|
+
if (!subst.has(name)) subst.set(name, f.typeParamDefaults?.[name] ?? unknownType);
|
|
6208
|
+
}
|
|
5088
6209
|
return subst;
|
|
5089
6210
|
}
|
|
5090
6211
|
/** Re-infer the arguments that land on a `<const T>` parameter, keeping
|
|
@@ -5115,6 +6236,32 @@ var TypeAnalyzer = class {
|
|
|
5115
6236
|
}
|
|
5116
6237
|
return void 0;
|
|
5117
6238
|
}
|
|
6239
|
+
/** An overload set called with a union argument, one member at a time.
|
|
6240
|
+
*
|
|
6241
|
+
* One signature for the whole union is often only the catch-all:
|
|
6242
|
+
* `typeof(v)` with `v: Part | nil` accepts nothing more specific than
|
|
6243
|
+
* `typeof<T>(value: T): string`. Each member on its own picks `"Instance"`
|
|
6244
|
+
* and `"nil"`, and that union is what the call returns — whenever every
|
|
6245
|
+
* member picks a signature listed ahead of the whole union's. Otherwise
|
|
6246
|
+
* (a signature taking the union as it is, or a member nothing accepts)
|
|
6247
|
+
* this returns `undefined` and the ordinary pick stands. */
|
|
6248
|
+
distributedReturn(fns, argTypes, picked, argsFor) {
|
|
6249
|
+
if (fns.length < 2) return void 0;
|
|
6250
|
+
const position = argTypes.findIndex((t) => this.expand(t).kind === "union");
|
|
6251
|
+
if (position < 0) return void 0;
|
|
6252
|
+
const members = this.expand(argTypes[position]).types;
|
|
6253
|
+
if (members.length > 32) return void 0;
|
|
6254
|
+
const rank = (f) => ((f.typeParams?.length ?? 0) > 0 ? fns.length : 0) + fns.indexOf(f);
|
|
6255
|
+
const limit = picked ? rank(picked) : Infinity;
|
|
6256
|
+
const results = [];
|
|
6257
|
+
for (const member of members) {
|
|
6258
|
+
const args = argTypes.map((t, i) => i === position ? member : t);
|
|
6259
|
+
const chosen = this.pickOverload(fns, args, (f) => argsFor(f, args));
|
|
6260
|
+
if (!chosen || rank(chosen) >= limit) return void 0;
|
|
6261
|
+
results.push(this.callReturn(chosen, argsFor(chosen, args)));
|
|
6262
|
+
}
|
|
6263
|
+
return union(results);
|
|
6264
|
+
}
|
|
5118
6265
|
/** Can this signature be called with these argument types? The signature's
|
|
5119
6266
|
* own type parameters stand for what the call would infer, so each is
|
|
5120
6267
|
* checked only against its constraint — `<K extends keyof Services>`
|
|
@@ -5150,7 +6297,7 @@ var TypeAnalyzer = class {
|
|
|
5150
6297
|
for (const child of Object.values(value)) walk(child);
|
|
5151
6298
|
};
|
|
5152
6299
|
for (const p of f.params) if (containsTypeParam(p.type)) walk(p.type);
|
|
5153
|
-
return f.params.map((p) => substitute(p.type, bounds));
|
|
6300
|
+
return f.params.map((p) => this.reduceType(substitute(p.type, bounds)));
|
|
5154
6301
|
}
|
|
5155
6302
|
/** Record what each written argument is expected to be — see
|
|
5156
6303
|
* `TypeAnalysis.expectedTypeOf`. */
|
|
@@ -5167,6 +6314,28 @@ var TypeAnalyzer = class {
|
|
|
5167
6314
|
}
|
|
5168
6315
|
/** No signature accepts the call, and the argument count is not the
|
|
5169
6316
|
* problem: say which argument is wrong, the way TypeScript does. */
|
|
6317
|
+
/** Check what was written against the parameters as this call's own type
|
|
6318
|
+
* arguments make them read: `pick("Bones", "C")` is wrong only once `P`
|
|
6319
|
+
* is known to be `"Bones"`. Picking the overload goes by each parameter's
|
|
6320
|
+
* constraint, which is deliberately looser than that. */
|
|
6321
|
+
checkInferredArguments(call, written, f, argTypes, self) {
|
|
6322
|
+
if (!this.emitDiagnostics || !f.typeParams?.length) return;
|
|
6323
|
+
const subst = this.inferTypeArgs(f, [...argTypes]);
|
|
6324
|
+
for (const bound of subst.values()) if (bound.kind === "unknown") return;
|
|
6325
|
+
for (let i = 0; i < f.params.length; i++) {
|
|
6326
|
+
const arg = argTypes[i];
|
|
6327
|
+
const declared = f.params[i].type;
|
|
6328
|
+
if (arg === void 0 || !containsTypeParam(declared)) continue;
|
|
6329
|
+
const expected = this.reduceType(substitute(declared, subst));
|
|
6330
|
+
if (containsTypeParam(expected) || expected.kind === "any" || expected.kind === "unknown") continue;
|
|
6331
|
+
if (isAssignable(arg, expected) || isAssignable(widen(arg), expected)) continue;
|
|
6332
|
+
this.diagnostics.push({
|
|
6333
|
+
node: written[i - self] ?? call,
|
|
6334
|
+
message: `Argument of type '${formatType(arg)}' is not assignable to parameter of type '${briefType(expected)}'`
|
|
6335
|
+
});
|
|
6336
|
+
return;
|
|
6337
|
+
}
|
|
6338
|
+
}
|
|
5170
6339
|
reportArguments(call, written, fns, argsFor, selfOf) {
|
|
5171
6340
|
if (!this.emitDiagnostics) return;
|
|
5172
6341
|
if (fns.length > 1) {
|
|
@@ -5236,9 +6405,33 @@ var TypeAnalyzer = class {
|
|
|
5236
6405
|
});
|
|
5237
6406
|
return false;
|
|
5238
6407
|
}
|
|
6408
|
+
/** An overload set's implementation handles every signature, so a bare
|
|
6409
|
+
* parameter of it holds whatever those signatures allow there:
|
|
6410
|
+
* `function f(Stat, ...)` under 36 `Stat: "..."` signatures is the union
|
|
6411
|
+
* of all 36. TypeScript leaves such a parameter `any`; this says what it
|
|
6412
|
+
* can actually be. An annotation, a pattern or a default still wins. */
|
|
6413
|
+
paramsFromSignatures(func, signatures) {
|
|
6414
|
+
if (!signatures?.length) return;
|
|
6415
|
+
const resolved = signatures.map((sig) => this.signatureToFnType(sig));
|
|
6416
|
+
func.params.forEach((param, i) => {
|
|
6417
|
+
if (param.typeAnnotation || param.pattern || param.default) return;
|
|
6418
|
+
const candidates = [];
|
|
6419
|
+
for (const signature of resolved) {
|
|
6420
|
+
if (signature.kind !== "function") continue;
|
|
6421
|
+
const own = signature.params[i];
|
|
6422
|
+
if (own) candidates.push(own.optional ? optional(own.type) : own.type);
|
|
6423
|
+
else if (signature.varargs) candidates.push(signature.varargs);
|
|
6424
|
+
}
|
|
6425
|
+
if (candidates.length) this.contextualParams.set(param, union(candidates));
|
|
6426
|
+
});
|
|
6427
|
+
}
|
|
5239
6428
|
signatureToFnType(sig) {
|
|
5240
6429
|
const names = sig.generics.map((g) => g.name);
|
|
5241
|
-
|
|
6430
|
+
const record = (type) => {
|
|
6431
|
+
this.typeOfTypeNode.set(sig, type);
|
|
6432
|
+
return type;
|
|
6433
|
+
};
|
|
6434
|
+
return record(this.withTypeParams(sig.generics, () => {
|
|
5242
6435
|
const params = sig.params.map((p) => ({
|
|
5243
6436
|
name: p.pattern ? void 0 : p.name,
|
|
5244
6437
|
type: this.paramType(p, /* @__PURE__ */ new Map()),
|
|
@@ -5251,7 +6444,7 @@ var TypeAnalyzer = class {
|
|
|
5251
6444
|
names,
|
|
5252
6445
|
this.resolvePredicate(sig.predicate, params)
|
|
5253
6446
|
);
|
|
5254
|
-
});
|
|
6447
|
+
}));
|
|
5255
6448
|
}
|
|
5256
6449
|
/** Turn a parsed `v is T` / `asserts v` annotation into a `TypePredicate`,
|
|
5257
6450
|
* resolving the named parameter to its index. A guard naming a parameter
|
|
@@ -5296,8 +6489,11 @@ var TypeAnalyzer = class {
|
|
|
5296
6489
|
} else if (func.predicate) {
|
|
5297
6490
|
returns = booleanType;
|
|
5298
6491
|
} else {
|
|
5299
|
-
|
|
5300
|
-
returns = this.
|
|
6492
|
+
const collected = [];
|
|
6493
|
+
returns = this.withVarargs(func, () => this.silently(() => {
|
|
6494
|
+
this.collectReturns(collected, () => this.preVisitBody(func.body, bodyEnv));
|
|
6495
|
+
return collected.length ? union(collected) : this.inferReturnType(func.body, bodyEnv);
|
|
6496
|
+
}));
|
|
5301
6497
|
}
|
|
5302
6498
|
return fn(
|
|
5303
6499
|
params,
|
|
@@ -5308,6 +6504,29 @@ var TypeAnalyzer = class {
|
|
|
5308
6504
|
);
|
|
5309
6505
|
});
|
|
5310
6506
|
}
|
|
6507
|
+
/** Where the return types of the function being walked are collected, so
|
|
6508
|
+
* each is read where it is written — inside the branch that narrowed it —
|
|
6509
|
+
* rather than in whatever state the body ends in. */
|
|
6510
|
+
returnTypes;
|
|
6511
|
+
collectReturns(into, body) {
|
|
6512
|
+
const previous = this.returnTypes;
|
|
6513
|
+
this.returnTypes = into;
|
|
6514
|
+
try {
|
|
6515
|
+
return body();
|
|
6516
|
+
} finally {
|
|
6517
|
+
this.returnTypes = previous;
|
|
6518
|
+
}
|
|
6519
|
+
}
|
|
6520
|
+
/** Run something without reporting what it finds. */
|
|
6521
|
+
silently(body) {
|
|
6522
|
+
const wasEmitting = this.emitDiagnostics;
|
|
6523
|
+
this.emitDiagnostics = false;
|
|
6524
|
+
try {
|
|
6525
|
+
return body();
|
|
6526
|
+
} finally {
|
|
6527
|
+
this.emitDiagnostics = wasEmitting;
|
|
6528
|
+
}
|
|
6529
|
+
}
|
|
5311
6530
|
/** Populate binding types for a function body without reporting anything,
|
|
5312
6531
|
* purely so an un-annotated return type can see its own locals. Bounded:
|
|
5313
6532
|
* nested functions stop pre-visiting after a couple of levels, since the
|
|
@@ -5324,6 +6543,157 @@ var TypeAnalyzer = class {
|
|
|
5324
6543
|
this.preVisitDepth--;
|
|
5325
6544
|
}
|
|
5326
6545
|
}
|
|
6546
|
+
/** The `[key, value]` pairs iterating a record yields, one per property —
|
|
6547
|
+
* for `pairs(t)`, `next, t` and `for k, v in t` over an object type with
|
|
6548
|
+
* no indexer. `undefined` for anything else (an array, a dictionary, an
|
|
6549
|
+
* iterator function), whose keys have no names to list. */
|
|
6550
|
+
iterationRows(iterNode, iterType) {
|
|
6551
|
+
let source;
|
|
6552
|
+
if (iterNode?.type === "CallExpression" && iterNode.callee.type === "Identifier" && iterNode.arguments[0]) {
|
|
6553
|
+
if (iterNode.callee.name !== "pairs" && iterNode.callee.name !== "next") return void 0;
|
|
6554
|
+
source = this.typeOf.get(iterNode.arguments[0]);
|
|
6555
|
+
} else {
|
|
6556
|
+
source = iterType;
|
|
6557
|
+
}
|
|
6558
|
+
const t = source && this.expand(source);
|
|
6559
|
+
if (!t || t.kind !== "object" || t.class || t.indexer || !t.properties.size) return void 0;
|
|
6560
|
+
return [...t.properties].map(([name, property]) => [
|
|
6561
|
+
literal(name),
|
|
6562
|
+
property.optional ? optional(property.type) : property.type
|
|
6563
|
+
]);
|
|
6564
|
+
}
|
|
6565
|
+
/** Bindings that hold parts of one value: the key and value of a `pairs`
|
|
6566
|
+
* row, or the names destructured from one union member. By flow key.
|
|
6567
|
+
* Which rows are still possible is itself flow state, kept in `env` under
|
|
6568
|
+
* `group` as a union of tuples, so it narrows and merges like any type. */
|
|
6569
|
+
correlations = /* @__PURE__ */ new Map();
|
|
6570
|
+
correlateBindings(env, ids, rows) {
|
|
6571
|
+
const keys = ids.map(bindKey);
|
|
6572
|
+
const group = `rows(${keys.join(",")})`;
|
|
6573
|
+
keys.forEach((key, index) => this.correlations.set(key, { group, index, keys, rows }));
|
|
6574
|
+
env.set(group, union(rows.map((row) => tuple(row))));
|
|
6575
|
+
}
|
|
6576
|
+
/** `key` was just narrowed to `narrowed` in `env`: narrow that column of
|
|
6577
|
+
* every row, drop the rows it rules out, and give the other bindings what
|
|
6578
|
+
* the remaining rows hold. */
|
|
6579
|
+
correlate(env, key, narrowed) {
|
|
6580
|
+
const entry = this.correlations.get(key);
|
|
6581
|
+
if (!entry) return;
|
|
6582
|
+
const state = env.get(entry.group);
|
|
6583
|
+
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;
|
|
6584
|
+
const kept = [];
|
|
6585
|
+
for (const row of current) {
|
|
6586
|
+
const column = narrowTo(row[entry.index], narrowed);
|
|
6587
|
+
if (column.kind !== "never") kept.push(row.map((t, i) => i === entry.index ? column : t));
|
|
6588
|
+
}
|
|
6589
|
+
env.set(entry.group, kept.length ? union(kept.map((row) => tuple(row))) : neverType);
|
|
6590
|
+
entry.keys.forEach((other, j) => {
|
|
6591
|
+
if (j !== entry.index) env.set(other, kept.length ? union(kept.map((row) => row[j])) : neverType);
|
|
6592
|
+
});
|
|
6593
|
+
}
|
|
6594
|
+
/** Stop correlating a binding once it is assigned: its value no longer
|
|
6595
|
+
* comes from the row. */
|
|
6596
|
+
uncorrelate(id) {
|
|
6597
|
+
const entry = this.correlations.get(bindKey(id));
|
|
6598
|
+
if (entry) for (const key of entry.keys) this.correlations.delete(key);
|
|
6599
|
+
}
|
|
6600
|
+
/** `const { kind, payload } = action` over a union of objects: one row per
|
|
6601
|
+
* member, so testing `kind` narrows `payload` (TypeScript's destructured
|
|
6602
|
+
* discriminated unions). Only plain `name` / `key: name` properties take
|
|
6603
|
+
* part. */
|
|
6604
|
+
/** Names that denote one and the same value: `const c = player.Character`
|
|
6605
|
+
* makes `c` and `player.Character` two spellings of one reference. Kept
|
|
6606
|
+
* as an undirected graph of flow keys. */
|
|
6607
|
+
refAliases = /* @__PURE__ */ new Map();
|
|
6608
|
+
/** `const c = a.b` — `c` cannot be re-bound and the path was read once, so
|
|
6609
|
+
* a test of either name is a test of the same value. Only property paths
|
|
6610
|
+
* take part: `const c = other` would tie `c` to a name that may itself be
|
|
6611
|
+
* assigned a different value later. */
|
|
6612
|
+
aliasReference(target, init) {
|
|
6613
|
+
if (target.type !== "IdentifierPattern" || !init) return;
|
|
6614
|
+
const source = unwrapParens(init);
|
|
6615
|
+
if (source.type !== "MemberExpression" && source.type !== "IndexExpression") return;
|
|
6616
|
+
const path = this.refKeyOf(source);
|
|
6617
|
+
const id = this.bindingIdByName(target.name, target);
|
|
6618
|
+
if (path === void 0 || id === void 0) return;
|
|
6619
|
+
const name = bindKey(id);
|
|
6620
|
+
for (const [a, b] of [[name, path], [path, name]]) {
|
|
6621
|
+
const set = this.refAliases.get(a) ?? /* @__PURE__ */ new Set();
|
|
6622
|
+
set.add(b);
|
|
6623
|
+
this.refAliases.set(a, set);
|
|
6624
|
+
}
|
|
6625
|
+
}
|
|
6626
|
+
/** A reference was narrowed: give every other spelling of the same value
|
|
6627
|
+
* the same news. Walks the alias graph, so a path with two names told by
|
|
6628
|
+
* one of them reaches the other. Each alias keeps whatever it already
|
|
6629
|
+
* knew — the narrowing only ever cuts the type further down. */
|
|
6630
|
+
propagateAliases(env, into, key, narrowed) {
|
|
6631
|
+
if (!this.refAliases.size) return;
|
|
6632
|
+
const seen = /* @__PURE__ */ new Set([key]);
|
|
6633
|
+
const queue = [[key, narrowed]];
|
|
6634
|
+
const learn = (at, t) => {
|
|
6635
|
+
seen.add(at);
|
|
6636
|
+
this.setRef(into, at, t);
|
|
6637
|
+
this.correlate(into, at, t);
|
|
6638
|
+
queue.push([at, t]);
|
|
6639
|
+
};
|
|
6640
|
+
for (let at = 0; at < queue.length; at++) {
|
|
6641
|
+
const [from, t] = queue[at];
|
|
6642
|
+
for (const other of this.refAliases.get(from) ?? []) {
|
|
6643
|
+
if (seen.has(other)) continue;
|
|
6644
|
+
const current = into.get(other) ?? env.get(other) ?? this.declaredAtRef(other);
|
|
6645
|
+
const next = narrowTo(current, t);
|
|
6646
|
+
learn(other, next.kind === "never" ? t : next);
|
|
6647
|
+
for (let child = other, value = into.get(other); ; ) {
|
|
6648
|
+
const cut = child.lastIndexOf(".");
|
|
6649
|
+
if (cut <= 0) break;
|
|
6650
|
+
const parent = child.slice(0, cut);
|
|
6651
|
+
if (seen.has(parent)) break;
|
|
6652
|
+
const had = into.get(parent) ?? env.get(parent) ?? this.declaredAtRef(parent);
|
|
6653
|
+
value = this.filterByProperty(had, child.slice(cut + 1), value);
|
|
6654
|
+
learn(parent, value);
|
|
6655
|
+
child = parent;
|
|
6656
|
+
}
|
|
6657
|
+
}
|
|
6658
|
+
}
|
|
6659
|
+
}
|
|
6660
|
+
/** `const path = paths[stat]` where `stat` is one of several keys: which
|
|
6661
|
+
* value came back says which key was asked for. Testing the value then
|
|
6662
|
+
* narrows the key — the `else` of `if path then` leaves exactly the keys
|
|
6663
|
+
* the table does not have. */
|
|
6664
|
+
correlateIndexed(target, init, env) {
|
|
6665
|
+
if (target.type !== "IdentifierPattern" || !init) return;
|
|
6666
|
+
const source = unwrapParens(init);
|
|
6667
|
+
if (source.type !== "IndexExpression" || source.index.type !== "Identifier") return;
|
|
6668
|
+
const valueId = this.bindingIdByName(target.name, target);
|
|
6669
|
+
const keyId = this.bindingIdOf(source.index);
|
|
6670
|
+
if (valueId === void 0 || keyId === void 0) return;
|
|
6671
|
+
const key = this.expand(this.currentType(keyId, env));
|
|
6672
|
+
if (key.kind !== "union" || key.types.length < 2 || key.types.length > 64) return;
|
|
6673
|
+
if (!key.types.every((m) => m.kind === "literal")) return;
|
|
6674
|
+
const object = this.expand(this.typeOf.get(source.object) ?? unknownType);
|
|
6675
|
+
if (object.kind !== "object") return;
|
|
6676
|
+
this.correlateBindings(env, [keyId, valueId], key.types.map((m) => [m, this.indexedType(object, m)]));
|
|
6677
|
+
}
|
|
6678
|
+
correlateDestructuring(pattern, source, env) {
|
|
6679
|
+
if (pattern.type !== "ObjectPattern") return;
|
|
6680
|
+
const members = this.expand(source);
|
|
6681
|
+
if (members.kind !== "union") return;
|
|
6682
|
+
const objects = members.types.map((m) => this.expand(m));
|
|
6683
|
+
if (objects.length < 2 || objects.some((m) => m.kind !== "object")) return;
|
|
6684
|
+
const ids = [];
|
|
6685
|
+
const names = [];
|
|
6686
|
+
for (const property of pattern.properties) {
|
|
6687
|
+
if (property.computed || property.default || property.value.type !== "IdentifierPattern") return;
|
|
6688
|
+
const name = property.key.type === "Identifier" ? property.key.name : property.key.type === "StringLiteral" ? property.key.value : void 0;
|
|
6689
|
+
const id = this.bindingIdByName(property.value.name, property.value);
|
|
6690
|
+
if (name === void 0 || id === void 0) return;
|
|
6691
|
+
ids.push(id);
|
|
6692
|
+
names.push(name);
|
|
6693
|
+
}
|
|
6694
|
+
if (ids.length < 2) return;
|
|
6695
|
+
this.correlateBindings(env, ids, objects.map((member) => names.map((name) => this.propertyType(member, name))));
|
|
6696
|
+
}
|
|
5327
6697
|
/** `(keyType, valueType)` yielded by a generic-for iterator. Handles
|
|
5328
6698
|
* `ipairs`/`pairs`/`next(t)` and Luau generalized iteration (`for … in t`).
|
|
5329
6699
|
* `varCount` is how many loop variables were written. */
|
|
@@ -5388,6 +6758,18 @@ var TypeAnalyzer = class {
|
|
|
5388
6758
|
if (init.type === "ArrayExpression") return isAssignable(this.inferArray(init, env, true), declared);
|
|
5389
6759
|
return false;
|
|
5390
6760
|
}
|
|
6761
|
+
/** `{ a, ...rest }`: what `rest` holds — the value without the properties
|
|
6762
|
+
* the pattern already took. */
|
|
6763
|
+
withoutKeys(raw, properties) {
|
|
6764
|
+
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] : []));
|
|
6765
|
+
if (!taken.size) return raw;
|
|
6766
|
+
const t = this.expand(raw);
|
|
6767
|
+
if (t.kind === "union") return union(t.types.map((m) => this.withoutKeys(m, properties)));
|
|
6768
|
+
if (t.kind !== "object") return raw;
|
|
6769
|
+
const kept = [...t.properties].filter(([name]) => !taken.has(name));
|
|
6770
|
+
if (kept.length === t.properties.size) return raw;
|
|
6771
|
+
return objectType(kept, t.indexer, t.frozen);
|
|
6772
|
+
}
|
|
5391
6773
|
/** Fold a destructuring default (`{ a = 1 }`) into the property's type:
|
|
5392
6774
|
* the default applies when the source value is missing/`nil`. */
|
|
5393
6775
|
withDefault(base, def, env) {
|
|
@@ -5419,7 +6801,7 @@ var TypeAnalyzer = class {
|
|
|
5419
6801
|
const pt = key !== void 0 ? this.propertyType(valueType, key) : unknownType;
|
|
5420
6802
|
this.reassignPattern(p.value, this.withDefault(pt, p.default, env), env);
|
|
5421
6803
|
}
|
|
5422
|
-
if (target.rest) this.reassignPattern(target.rest, valueType, env);
|
|
6804
|
+
if (target.rest) this.reassignPattern(target.rest, this.withoutKeys(valueType, target.properties), env);
|
|
5423
6805
|
return;
|
|
5424
6806
|
}
|
|
5425
6807
|
case "ArrayPattern": {
|
|
@@ -5454,7 +6836,7 @@ var TypeAnalyzer = class {
|
|
|
5454
6836
|
const propType = key !== void 0 ? this.propertyType(valueType, key) : unknownType;
|
|
5455
6837
|
this.bindPattern(p.value, this.withDefault(propType, p.default, env), env, mode);
|
|
5456
6838
|
}
|
|
5457
|
-
if (target.rest) this.bindPattern(target.rest, valueType, env, mode);
|
|
6839
|
+
if (target.rest) this.bindPattern(target.rest, this.withoutKeys(valueType, target.properties), env, mode);
|
|
5458
6840
|
return;
|
|
5459
6841
|
}
|
|
5460
6842
|
case "ArrayPattern": {
|
|
@@ -5497,7 +6879,7 @@ var TypeAnalyzer = class {
|
|
|
5497
6879
|
}
|
|
5498
6880
|
}
|
|
5499
6881
|
propertyType(raw, name) {
|
|
5500
|
-
const t = this.expand(raw);
|
|
6882
|
+
const t = this.deferredAccess(this.expand(raw));
|
|
5501
6883
|
if (t.kind === "object") {
|
|
5502
6884
|
const p = t.properties.get(name);
|
|
5503
6885
|
if (p) return p.optional ? optional(p.type) : p.type;
|
|
@@ -5520,21 +6902,37 @@ var TypeAnalyzer = class {
|
|
|
5520
6902
|
const t = this.expand(raw);
|
|
5521
6903
|
if (t.kind === "any") return anyType;
|
|
5522
6904
|
if (t.kind === "union") return union(t.types.map((m) => this.indexedType(m, idx)));
|
|
5523
|
-
|
|
5524
|
-
if (
|
|
6905
|
+
const index = this.expand(idx);
|
|
6906
|
+
if (index.kind === "union") return union(index.types.map((m) => this.indexedType(t, m)));
|
|
6907
|
+
if (t.kind === "difference") return this.indexedType(t.base, index);
|
|
6908
|
+
if (t.kind === "typeParam" && t.constraint) return this.indexedType(t.constraint, index);
|
|
5525
6909
|
if (t.kind === "array") return t.element;
|
|
5526
6910
|
if (t.kind === "tuple") {
|
|
5527
|
-
if (
|
|
5528
|
-
return t.elements[
|
|
6911
|
+
if (index.kind === "literal" && typeof index.value === "number") {
|
|
6912
|
+
return t.elements[index.value - 1] ?? nilType;
|
|
5529
6913
|
}
|
|
5530
6914
|
return union(t.elements);
|
|
5531
6915
|
}
|
|
5532
6916
|
if (t.kind === "object") {
|
|
5533
|
-
if (
|
|
6917
|
+
if (index.kind === "literal" && typeof index.value === "string") {
|
|
6918
|
+
const property = t.properties.get(index.value);
|
|
6919
|
+
if (property) return property.optional ? optional(property.type) : property.type;
|
|
6920
|
+
if (t.indexer && isAssignable(index, t.indexer.key)) return t.indexer.value;
|
|
6921
|
+
return nilType;
|
|
6922
|
+
}
|
|
6923
|
+
if (containsTypeParam(index)) return this.reduceType({ kind: "indexedAccess", objectType: t, indexType: index });
|
|
5534
6924
|
if (t.indexer) return t.indexer.value;
|
|
5535
6925
|
}
|
|
5536
6926
|
return unknownType;
|
|
5537
6927
|
}
|
|
6928
|
+
/** What a deferred `T[K]` can be: every property its index could name.
|
|
6929
|
+
* Reading a member of one, or calling it, sees that. */
|
|
6930
|
+
deferredAccess(t) {
|
|
6931
|
+
if (t.kind !== "indexedAccess") return t;
|
|
6932
|
+
const index = t.indexType.kind === "typeParam" && t.indexType.constraint ? t.indexType.constraint : t.indexType;
|
|
6933
|
+
if (containsTypeParam(index)) return unknownType;
|
|
6934
|
+
return this.accessType(t.objectType, index);
|
|
6935
|
+
}
|
|
5538
6936
|
elementType(raw, index) {
|
|
5539
6937
|
const t = this.expand(raw);
|
|
5540
6938
|
if (t.kind === "array") return t.element;
|
|
@@ -5567,7 +6965,11 @@ var TypeAnalyzer = class {
|
|
|
5567
6965
|
for (const part of expr.parts) if (part.kind === "expression") this.infer(part.expression, env);
|
|
5568
6966
|
return stringType;
|
|
5569
6967
|
}
|
|
6968
|
+
// `...` holds what the function declared it takes.
|
|
5570
6969
|
case "VarargExpression":
|
|
6970
|
+
return this.varargs[this.varargs.length - 1] ?? anyType;
|
|
6971
|
+
// Broken syntax is reported by the parser; nothing more to say.
|
|
6972
|
+
case "ErrorExpression":
|
|
5571
6973
|
return anyType;
|
|
5572
6974
|
case "Identifier": {
|
|
5573
6975
|
const id = this.bindingIdOf(expr);
|
|
@@ -5593,13 +6995,22 @@ var TypeAnalyzer = class {
|
|
|
5593
6995
|
return this.resolveType(expr.typeAnnotation);
|
|
5594
6996
|
}
|
|
5595
6997
|
case "SatisfiesExpression": {
|
|
5596
|
-
const actual = this.infer(expr.expression, env);
|
|
5597
6998
|
const declared = this.resolveType(expr.typeAnnotation);
|
|
5598
|
-
|
|
6999
|
+
this.applyContext(expr.expression, declared);
|
|
7000
|
+
if (declared.kind === "any") return this.infer(expr.expression, env);
|
|
7001
|
+
const written = unwrapParens(expr.expression);
|
|
7002
|
+
const fresh = written.type === "TableExpression" || written.type === "ArrayExpression";
|
|
7003
|
+
const narrow = fresh ? this.inferAsConst(expr.expression, env) : this.infer(expr.expression, env);
|
|
7004
|
+
const actual = fresh ? this.keepContextualLiterals(narrow, declared) : narrow;
|
|
7005
|
+
this.typeOf.set(expr.expression, actual);
|
|
7006
|
+
if (!this.emitDiagnostics) return actual;
|
|
7007
|
+
if (!isAssignable(narrow, declared) && !isAssignable(actual, declared)) {
|
|
5599
7008
|
this.diagnostics.push({
|
|
5600
7009
|
node: expr,
|
|
5601
|
-
message: `Type '${formatType(actual)}' does not satisfy '${formatType(declared)}'`
|
|
7010
|
+
message: `Type '${formatType(actual)}' does not satisfy the expected type '${formatType(declared)}'`
|
|
5602
7011
|
});
|
|
7012
|
+
} else {
|
|
7013
|
+
this.reportExcessProperties(expr.expression, declared);
|
|
5603
7014
|
}
|
|
5604
7015
|
return actual;
|
|
5605
7016
|
}
|
|
@@ -5633,6 +7044,10 @@ var TypeAnalyzer = class {
|
|
|
5633
7044
|
}
|
|
5634
7045
|
const l = this.infer(expr.left, env);
|
|
5635
7046
|
const r = this.infer(expr.right, env);
|
|
7047
|
+
if (op === "==" || op === "~=") {
|
|
7048
|
+
if (unwrapParens(expr.right).type === "StringLiteral") this.expectedTypeOf.set(unwrapParens(expr.right), l);
|
|
7049
|
+
if (unwrapParens(expr.left).type === "StringLiteral") this.expectedTypeOf.set(unwrapParens(expr.left), r);
|
|
7050
|
+
}
|
|
5636
7051
|
switch (op) {
|
|
5637
7052
|
case "..":
|
|
5638
7053
|
return this.operatorResult(expr, op, l, r) ?? stringType;
|
|
@@ -5655,57 +7070,25 @@ var TypeAnalyzer = class {
|
|
|
5655
7070
|
return union([l, r]);
|
|
5656
7071
|
}
|
|
5657
7072
|
case "MemberExpression": {
|
|
5658
|
-
const obj = this.
|
|
7073
|
+
const { type: obj, shortCircuits } = this.chainObject(expr, expr.object, env);
|
|
5659
7074
|
const key = this.refKeyOf(expr);
|
|
5660
7075
|
const narrowed = key === void 0 ? void 0 : env.get(key);
|
|
5661
|
-
return narrowed ?? this.propertyType(obj, expr.property.name);
|
|
7076
|
+
return this.chainResult(expr, narrowed ?? this.propertyType(obj, expr.property.name), shortCircuits);
|
|
5662
7077
|
}
|
|
5663
7078
|
case "IndexExpression": {
|
|
5664
|
-
const obj = this.
|
|
7079
|
+
const { type: obj, shortCircuits } = this.chainObject(expr, expr.object, env);
|
|
5665
7080
|
const idx = this.infer(expr.index, env);
|
|
5666
7081
|
const key = this.refKeyOf(expr);
|
|
5667
7082
|
const narrowed = key === void 0 ? void 0 : env.get(key);
|
|
5668
|
-
return narrowed ?? this.indexedType(obj, idx);
|
|
7083
|
+
return this.chainResult(expr, narrowed ?? this.indexedType(obj, idx), shortCircuits);
|
|
5669
7084
|
}
|
|
5670
7085
|
case "CallExpression": {
|
|
5671
|
-
const callee = this.
|
|
5672
|
-
|
|
5673
|
-
const expected = this.expectedArguments(expr.arguments, fns, () => 0);
|
|
5674
|
-
expr.arguments.forEach((a, i) => this.applyContext(a, expected[i]));
|
|
5675
|
-
const argTypes = expr.arguments.map((a) => this.infer(a, env));
|
|
5676
|
-
if (fns.length) {
|
|
5677
|
-
this.recordExpected(expr.arguments, fns, () => 0);
|
|
5678
|
-
const arityFits = this.checkArity(expr, fns, argTypes.length, 0);
|
|
5679
|
-
const picked = this.pickOverload(fns, argTypes);
|
|
5680
|
-
if (picked) {
|
|
5681
|
-
return this.callReturn(picked, this.constArgs(picked, expr.arguments, argTypes, env));
|
|
5682
|
-
}
|
|
5683
|
-
if (arityFits) this.reportArguments(expr, expr.arguments, fns, () => argTypes, () => 0);
|
|
5684
|
-
return union(fns.map((f) => this.callReturn(f, argTypes)));
|
|
5685
|
-
}
|
|
5686
|
-
return callee.kind === "any" ? anyType : unknownType;
|
|
7086
|
+
const { type: callee, shortCircuits } = this.chainObject(expr, expr.callee, env);
|
|
7087
|
+
return this.chainResult(expr, this.inferCall(expr, callee, env), shortCircuits);
|
|
5687
7088
|
}
|
|
5688
7089
|
case "MethodCallExpression": {
|
|
5689
|
-
const objType = this.
|
|
5690
|
-
|
|
5691
|
-
const expected = this.expectedArguments(expr.arguments, fns, (f) => this.takesSelf(f) ? 1 : 0);
|
|
5692
|
-
expr.arguments.forEach((a, i) => this.applyContext(a, expected[i]));
|
|
5693
|
-
const argTypes = expr.arguments.map((a) => this.infer(a, env));
|
|
5694
|
-
if (fns.length) {
|
|
5695
|
-
const withSelf = (f) => this.takesSelf(f) ? [objType, ...argTypes] : argTypes;
|
|
5696
|
-
const selfOf = (f) => this.takesSelf(f) ? 1 : 0;
|
|
5697
|
-
this.recordExpected(expr.arguments, fns, selfOf);
|
|
5698
|
-
const arityFits = this.checkArity(expr, fns, argTypes.length, this.takesSelf(fns[0]) ? 1 : 0);
|
|
5699
|
-
const picked = this.pickOverload(fns, argTypes, withSelf);
|
|
5700
|
-
if (picked) {
|
|
5701
|
-
const self = this.takesSelf(picked) ? 1 : 0;
|
|
5702
|
-
const written = this.constArgs(picked, expr.arguments, argTypes, env, self);
|
|
5703
|
-
return this.callReturn(picked, this.takesSelf(picked) ? [objType, ...written] : written);
|
|
5704
|
-
}
|
|
5705
|
-
if (arityFits) this.reportArguments(expr, expr.arguments, fns, withSelf, selfOf);
|
|
5706
|
-
return union(fns.map((f) => this.callReturn(f, withSelf(f))));
|
|
5707
|
-
}
|
|
5708
|
-
return objType.kind === "any" ? anyType : unknownType;
|
|
7090
|
+
const { type: objType, shortCircuits } = this.chainObject(expr, expr.object, env);
|
|
7091
|
+
return this.chainResult(expr, this.inferMethodCall(expr, objType, env), shortCircuits);
|
|
5709
7092
|
}
|
|
5710
7093
|
case "IfElseExpression": {
|
|
5711
7094
|
const branches = [];
|
|
@@ -5721,7 +7104,126 @@ var TypeAnalyzer = class {
|
|
|
5721
7104
|
}
|
|
5722
7105
|
}
|
|
5723
7106
|
}
|
|
7107
|
+
inferCall(expr, callee, env) {
|
|
7108
|
+
const fns = this.overloadsOf(callee);
|
|
7109
|
+
const explicit = this.explicitTypeArguments(expr, fns);
|
|
7110
|
+
const expected = this.expectedArguments(expr.arguments, fns, () => 0);
|
|
7111
|
+
expr.arguments.forEach((a, i) => this.applyContext(a, expected[i]));
|
|
7112
|
+
const argTypes = expr.arguments.map((a) => this.infer(a, env));
|
|
7113
|
+
if (fns.length) {
|
|
7114
|
+
this.recordExpected(expr.arguments, fns, () => 0);
|
|
7115
|
+
const arityFits = this.checkArity(expr, fns, argTypes.length, 0);
|
|
7116
|
+
const picked = this.pickOverload(fns, argTypes);
|
|
7117
|
+
const distributed = this.distributedReturn(fns, argTypes, picked, (_, args) => args);
|
|
7118
|
+
if (distributed) return distributed;
|
|
7119
|
+
if (picked) {
|
|
7120
|
+
this.checkInferredArguments(expr, expr.arguments, picked, argTypes, 0);
|
|
7121
|
+
return this.callReturn(picked, this.constArgs(picked, expr.arguments, argTypes, env), explicit);
|
|
7122
|
+
}
|
|
7123
|
+
if (arityFits) this.reportArguments(expr, expr.arguments, fns, () => argTypes, () => 0);
|
|
7124
|
+
return union(fns.map((f) => this.callReturn(f, argTypes, explicit)));
|
|
7125
|
+
}
|
|
7126
|
+
return callee.kind === "any" ? anyType : unknownType;
|
|
7127
|
+
}
|
|
7128
|
+
inferMethodCall(expr, objType, env) {
|
|
7129
|
+
const fns = this.overloadsOf(this.propertyType(objType, expr.method.name));
|
|
7130
|
+
const explicit = this.explicitTypeArguments(expr, fns);
|
|
7131
|
+
const expected = this.expectedArguments(expr.arguments, fns, (f) => this.takesSelf(f) ? 1 : 0);
|
|
7132
|
+
expr.arguments.forEach((a, i) => this.applyContext(a, expected[i]));
|
|
7133
|
+
const argTypes = expr.arguments.map((a) => this.infer(a, env));
|
|
7134
|
+
if (fns.length) {
|
|
7135
|
+
const withSelf = (f) => this.takesSelf(f) ? [objType, ...argTypes] : argTypes;
|
|
7136
|
+
const selfOf = (f) => this.takesSelf(f) ? 1 : 0;
|
|
7137
|
+
this.recordExpected(expr.arguments, fns, selfOf);
|
|
7138
|
+
const arityFits = this.checkArity(expr, fns, argTypes.length, this.takesSelf(fns[0]) ? 1 : 0);
|
|
7139
|
+
const picked = this.pickOverload(fns, argTypes, withSelf);
|
|
7140
|
+
const distributed = this.distributedReturn(
|
|
7141
|
+
fns,
|
|
7142
|
+
argTypes,
|
|
7143
|
+
picked,
|
|
7144
|
+
(f, args) => this.takesSelf(f) ? [objType, ...args] : args
|
|
7145
|
+
);
|
|
7146
|
+
if (distributed) return distributed;
|
|
7147
|
+
if (picked) {
|
|
7148
|
+
const self = this.takesSelf(picked) ? 1 : 0;
|
|
7149
|
+
this.checkInferredArguments(expr, expr.arguments, picked, withSelf(picked), self);
|
|
7150
|
+
const written = this.constArgs(picked, expr.arguments, argTypes, env, self);
|
|
7151
|
+
return this.callReturn(picked, this.takesSelf(picked) ? [objType, ...written] : written, explicit);
|
|
7152
|
+
}
|
|
7153
|
+
if (arityFits) this.reportArguments(expr, expr.arguments, fns, withSelf, selfOf);
|
|
7154
|
+
return union(fns.map((f) => this.callReturn(f, withSelf(f), explicit)));
|
|
7155
|
+
}
|
|
7156
|
+
return objType.kind === "any" ? anyType : unknownType;
|
|
7157
|
+
}
|
|
7158
|
+
// --------------------------------------------------------
|
|
7159
|
+
// Optional chains
|
|
7160
|
+
// --------------------------------------------------------
|
|
7161
|
+
//
|
|
7162
|
+
// `a?.b.c`: when `a` is nil the whole chain is nil and `.c` never runs.
|
|
7163
|
+
// So a link reads its object without the `nil` a `?.` earlier in the chain
|
|
7164
|
+
// added — that nil has already left the chain — and the chain's outermost
|
|
7165
|
+
// link carries it again. Parentheses end a chain: `(a?.b).c` reads `.c`
|
|
7166
|
+
// from `B | nil`.
|
|
7167
|
+
/** The type of a link's non-nil object, for each link that is past a `?.`:
|
|
7168
|
+
* what the chain holds when it has not short-circuited. */
|
|
7169
|
+
chainValue = /* @__PURE__ */ new WeakMap();
|
|
7170
|
+
/** The object a link reads from, and whether the chain can short-circuit
|
|
7171
|
+
* by this link. */
|
|
7172
|
+
chainObject(link, object, env) {
|
|
7173
|
+
const full = this.infer(object, env);
|
|
7174
|
+
const inChain = this.chainValue.get(object);
|
|
7175
|
+
let type = inChain ?? full;
|
|
7176
|
+
if (link.optional) {
|
|
7177
|
+
type = withoutNil(type);
|
|
7178
|
+
} else if (this.includesNil(type)) {
|
|
7179
|
+
this.reportNilAccess(object, type);
|
|
7180
|
+
type = withoutNil(this.expand(type));
|
|
7181
|
+
}
|
|
7182
|
+
return { type, shortCircuits: inChain !== void 0 || link.optional === true };
|
|
7183
|
+
}
|
|
7184
|
+
/** Objects already reported as possibly nil: a loop body is visited more
|
|
7185
|
+
* than once. */
|
|
7186
|
+
nilAccessReported = /* @__PURE__ */ new WeakSet();
|
|
7187
|
+
includesNil(raw) {
|
|
7188
|
+
const t = this.expand(raw);
|
|
7189
|
+
if (t.kind === "primitive") return t.name === "nil";
|
|
7190
|
+
return t.kind === "union" && t.types.some((m) => m.kind === "primitive" && m.name === "nil");
|
|
7191
|
+
}
|
|
7192
|
+
reportNilAccess(object, type) {
|
|
7193
|
+
if (!this.emitDiagnostics || this.nilAccessReported.has(object)) return;
|
|
7194
|
+
this.nilAccessReported.add(object);
|
|
7195
|
+
const label = expressionLabel(object);
|
|
7196
|
+
const t = this.expand(type);
|
|
7197
|
+
const nilOnly = t.kind === "primitive" && t.name === "nil";
|
|
7198
|
+
const subject = label === void 0 ? "Object" : `'${label}'`;
|
|
7199
|
+
this.diagnostics.push({
|
|
7200
|
+
node: object,
|
|
7201
|
+
message: nilOnly ? `${subject} is nil` : `${subject} is possibly nil. Check it first, or use '?.' / '?:'`
|
|
7202
|
+
});
|
|
7203
|
+
}
|
|
7204
|
+
chainResult(link, value, shortCircuits) {
|
|
7205
|
+
if (!shortCircuits) return value;
|
|
7206
|
+
this.chainValue.set(link, value);
|
|
7207
|
+
return union([value, nilType]);
|
|
7208
|
+
}
|
|
7209
|
+
/** The chain around `cond` did not short-circuit — it produced a truthy
|
|
7210
|
+
* value, or any value but nil — so every object a `?.` in it tested is not
|
|
7211
|
+
* nil in `env`. */
|
|
7212
|
+
narrowOptionalLinks(cond, env, into) {
|
|
7213
|
+
for (let e = cond; ; ) {
|
|
7214
|
+
const link = e;
|
|
7215
|
+
const object = e.type === "CallExpression" ? e.callee : e.type === "MemberExpression" || e.type === "IndexExpression" || e.type === "MethodCallExpression" ? e.object : void 0;
|
|
7216
|
+
if (!object) return;
|
|
7217
|
+
if (link.optional) {
|
|
7218
|
+
const key = this.refKeyOf(object);
|
|
7219
|
+
if (key !== void 0) this.setRef(into, key, withoutNil(this.typeAtRef(object, into)));
|
|
7220
|
+
}
|
|
7221
|
+
e = object;
|
|
7222
|
+
}
|
|
7223
|
+
}
|
|
5724
7224
|
inferArray(expr, env, asConst) {
|
|
7225
|
+
const contextual = this.contextualArrays.get(expr);
|
|
7226
|
+
if (contextual && !asConst) return contextual;
|
|
5725
7227
|
const elems = [];
|
|
5726
7228
|
let hadSpread = false;
|
|
5727
7229
|
for (const el of expr.elements) {
|
|
@@ -5767,6 +7269,123 @@ var TypeAnalyzer = class {
|
|
|
5767
7269
|
}
|
|
5768
7270
|
return objectType(entries, indexer, asConst || void 0);
|
|
5769
7271
|
}
|
|
7272
|
+
/** A value inferred `as const`, widened back wherever `context` does not
|
|
7273
|
+
* ask for a literal: `satisfies`' result type. A property keeps `"circle"`
|
|
7274
|
+
* when the contract's property admits string literals, and becomes
|
|
7275
|
+
* `string` when it is only `string`; a tuple becomes an array unless the
|
|
7276
|
+
* contract is a tuple; nothing stays readonly. */
|
|
7277
|
+
keepContextualLiterals(value, context) {
|
|
7278
|
+
const ctx = context === void 0 ? void 0 : this.expand(context);
|
|
7279
|
+
switch (value.kind) {
|
|
7280
|
+
case "literal":
|
|
7281
|
+
return ctx && this.admitsLiteral(ctx, value.base) ? value : widen(value);
|
|
7282
|
+
case "object": {
|
|
7283
|
+
if (value.class) return value;
|
|
7284
|
+
const entries = [...value.properties].map(([name, property]) => [
|
|
7285
|
+
name,
|
|
7286
|
+
{ ...property, readonly: false, type: this.keepContextualLiterals(property.type, ctx && this.contextProperty(ctx, name)) }
|
|
7287
|
+
]);
|
|
7288
|
+
const indexer = value.indexer && {
|
|
7289
|
+
key: widen(value.indexer.key),
|
|
7290
|
+
value: this.keepContextualLiterals(value.indexer.value, ctx && this.contextIndexValue(ctx))
|
|
7291
|
+
};
|
|
7292
|
+
return objectType(entries, indexer);
|
|
7293
|
+
}
|
|
7294
|
+
case "tuple": {
|
|
7295
|
+
const tupleContext = ctx && this.membersOf(ctx).find((m) => m.kind === "tuple");
|
|
7296
|
+
if (tupleContext?.kind === "tuple") {
|
|
7297
|
+
return tuple(value.elements.map((e, i) => this.keepContextualLiterals(e, tupleContext.elements[i])), value.isPack);
|
|
7298
|
+
}
|
|
7299
|
+
const arrayContext = ctx && this.membersOf(ctx).find((m) => m.kind === "array");
|
|
7300
|
+
const element = arrayContext?.kind === "array" ? arrayContext.element : void 0;
|
|
7301
|
+
if (!value.elements.length) return arrayContext ?? arrayOf(unknownType);
|
|
7302
|
+
return arrayOf(union(value.elements.map((e) => this.keepContextualLiterals(e, element))));
|
|
7303
|
+
}
|
|
7304
|
+
case "array": {
|
|
7305
|
+
const arrayContext = ctx && this.membersOf(ctx).find((m) => m.kind === "array");
|
|
7306
|
+
return arrayOf(this.keepContextualLiterals(value.element, arrayContext?.kind === "array" ? arrayContext.element : void 0));
|
|
7307
|
+
}
|
|
7308
|
+
case "union":
|
|
7309
|
+
return union(value.types.map((t) => this.keepContextualLiterals(t, context)));
|
|
7310
|
+
default:
|
|
7311
|
+
return value;
|
|
7312
|
+
}
|
|
7313
|
+
}
|
|
7314
|
+
membersOf(t) {
|
|
7315
|
+
const x = this.expand(t);
|
|
7316
|
+
return x.kind === "union" ? x.types.map((m) => this.expand(m)) : [x];
|
|
7317
|
+
}
|
|
7318
|
+
/** Does a contract accept literals of `base` as such? */
|
|
7319
|
+
admitsLiteral(ctx, base) {
|
|
7320
|
+
return this.membersOf(ctx).some((m) => m.kind === "literal" && m.base === base || m.kind === "templateLiteral" && base === "string");
|
|
7321
|
+
}
|
|
7322
|
+
/** What a contract expects of property `name`, over every object it allows. */
|
|
7323
|
+
contextProperty(ctx, name) {
|
|
7324
|
+
const found = [];
|
|
7325
|
+
for (const m of this.membersOf(ctx)) {
|
|
7326
|
+
if (m.kind !== "object") continue;
|
|
7327
|
+
const property = m.properties.get(name);
|
|
7328
|
+
if (property) found.push(property.type);
|
|
7329
|
+
else if (m.indexer) found.push(m.indexer.value);
|
|
7330
|
+
}
|
|
7331
|
+
return found.length ? union(found) : void 0;
|
|
7332
|
+
}
|
|
7333
|
+
contextIndexValue(ctx) {
|
|
7334
|
+
const found = this.membersOf(ctx).flatMap((m) => m.kind === "object" && m.indexer ? [m.indexer.value] : []);
|
|
7335
|
+
return found.length ? union(found) : void 0;
|
|
7336
|
+
}
|
|
7337
|
+
/** Fields reported by `reportExcessProperties`, once each: a loop body is
|
|
7338
|
+
* visited more than once. */
|
|
7339
|
+
excessReported = /* @__PURE__ */ new WeakSet();
|
|
7340
|
+
/** TypeScript's excess property check. An object literal written straight
|
|
7341
|
+
* into a typed place — an annotation, `satisfies` — may only name
|
|
7342
|
+
* properties that place knows: anything else is almost always a typo.
|
|
7343
|
+
* A nested literal is checked against the property it is written for.
|
|
7344
|
+
* A target with an indexer, a class, or a member whose shape is not known
|
|
7345
|
+
* accepts anything. */
|
|
7346
|
+
/** The keys an index signature covers, when it covers a countable set of
|
|
7347
|
+
* them: `[("a" | "b")]` yes, `[string]` no. */
|
|
7348
|
+
finiteKeys(key) {
|
|
7349
|
+
const t = this.expand(key);
|
|
7350
|
+
const parts = t.kind === "union" ? t.types : [t];
|
|
7351
|
+
const out = /* @__PURE__ */ new Set();
|
|
7352
|
+
for (const part of parts.map((m) => this.expand(m))) {
|
|
7353
|
+
if (part.kind !== "literal" || typeof part.value === "boolean") return void 0;
|
|
7354
|
+
out.add(String(part.value));
|
|
7355
|
+
}
|
|
7356
|
+
return out.size ? out : void 0;
|
|
7357
|
+
}
|
|
7358
|
+
reportExcessProperties(expression, target) {
|
|
7359
|
+
let literal2 = unwrapParens(expression);
|
|
7360
|
+
while (literal2.type === "AsConstExpression") literal2 = unwrapParens(literal2.expression);
|
|
7361
|
+
if (literal2.type !== "TableExpression" || !this.emitDiagnostics) return;
|
|
7362
|
+
const members = this.membersOf(target);
|
|
7363
|
+
const shapes = members.filter((m) => m.kind === "object");
|
|
7364
|
+
if (!shapes.length || shapes.some((o) => o.class)) return;
|
|
7365
|
+
const keySets = shapes.map((o) => o.indexer && this.finiteKeys(o.indexer.key));
|
|
7366
|
+
if (shapes.some((o, i) => o.indexer && !keySets[i])) return;
|
|
7367
|
+
if (members.some((m) => m.kind === "any" || m.kind === "unknown" || m.kind === "typeParam" || m.kind === "intersection")) return;
|
|
7368
|
+
for (const field of literal2.fields) {
|
|
7369
|
+
if (field.type !== "TableFieldNamed" && field.type !== "TableFieldShorthand") continue;
|
|
7370
|
+
const key = field.type === "TableFieldNamed" ? field.key : field.name;
|
|
7371
|
+
const name = key.type === "Identifier" ? key.name : key.value;
|
|
7372
|
+
const expected = shapes.flatMap((o, i) => {
|
|
7373
|
+
const property = o.properties.get(name);
|
|
7374
|
+
if (property) return [property.type];
|
|
7375
|
+
return o.indexer && keySets[i].has(name) ? [o.indexer.value] : [];
|
|
7376
|
+
});
|
|
7377
|
+
if (!expected.length) {
|
|
7378
|
+
if (this.excessReported.has(key)) continue;
|
|
7379
|
+
this.excessReported.add(key);
|
|
7380
|
+
this.diagnostics.push({
|
|
7381
|
+
node: key,
|
|
7382
|
+
message: `Object literal may only specify known properties, and '${name}' does not exist in type '${formatType(target)}'`
|
|
7383
|
+
});
|
|
7384
|
+
continue;
|
|
7385
|
+
}
|
|
7386
|
+
if (field.type === "TableFieldNamed") this.reportExcessProperties(field.value, union(expected));
|
|
7387
|
+
}
|
|
7388
|
+
}
|
|
5770
7389
|
inferAsConst(expr, env) {
|
|
5771
7390
|
switch (expr.type) {
|
|
5772
7391
|
case "ArrayExpression":
|
|
@@ -5824,12 +7443,13 @@ var TypeAnalyzer = class {
|
|
|
5824
7443
|
}
|
|
5825
7444
|
if (cond.type === "CallExpression" || cond.type === "MethodCallExpression") {
|
|
5826
7445
|
this.narrowByPredicateCall(cond, env, t, f);
|
|
5827
|
-
|
|
7446
|
+
} else {
|
|
7447
|
+
this.narrowRef(cond, env, t, f, (cur) => ({
|
|
7448
|
+
yes: narrowTruthy(cur),
|
|
7449
|
+
no: narrowFalsy(cur)
|
|
7450
|
+
}));
|
|
5828
7451
|
}
|
|
5829
|
-
this.
|
|
5830
|
-
yes: narrowTruthy(cur),
|
|
5831
|
-
no: narrowFalsy(cur)
|
|
5832
|
-
}));
|
|
7452
|
+
this.narrowOptionalLinks(cond, env, t);
|
|
5833
7453
|
}
|
|
5834
7454
|
/** `a == b` / `a ~= b`. Handles, in order: a declaration-driven
|
|
5835
7455
|
* `typeof(x) == "..."` test, a literal/`nil` comparison against a
|
|
@@ -5846,11 +7466,14 @@ var TypeAnalyzer = class {
|
|
|
5846
7466
|
};
|
|
5847
7467
|
for (const [ref, other] of [[left, right], [right, left]]) {
|
|
5848
7468
|
const value = litOf(other);
|
|
5849
|
-
if (value === void 0
|
|
5850
|
-
this.
|
|
5851
|
-
yes
|
|
5852
|
-
|
|
5853
|
-
|
|
7469
|
+
if (value === void 0) continue;
|
|
7470
|
+
if (this.refKeyOf(ref) !== void 0) {
|
|
7471
|
+
this.narrowRef(ref, env, yes, no, (cur) => ({
|
|
7472
|
+
yes: narrowTo(cur, value),
|
|
7473
|
+
no: narrowExclude(cur, value)
|
|
7474
|
+
}));
|
|
7475
|
+
}
|
|
7476
|
+
this.narrowOptionalLinks(ref, env, value.kind === "primitive" && value.name === "nil" ? no : yes);
|
|
5854
7477
|
return;
|
|
5855
7478
|
}
|
|
5856
7479
|
if (this.refKeyOf(left) !== void 0 && this.refKeyOf(right) !== void 0) {
|
|
@@ -5905,11 +7528,16 @@ var TypeAnalyzer = class {
|
|
|
5905
7528
|
predicateCallTarget(cond, env) {
|
|
5906
7529
|
let callee;
|
|
5907
7530
|
let args;
|
|
7531
|
+
let selfType;
|
|
5908
7532
|
if (cond.type === "CallExpression") {
|
|
5909
|
-
callee = this.typeOf.get(cond.callee) ?? this.typeAtRef(cond.callee, env);
|
|
7533
|
+
callee = this.chainValue.get(cond.callee) ?? this.typeOf.get(cond.callee) ?? this.typeAtRef(cond.callee, env);
|
|
5910
7534
|
args = cond.arguments;
|
|
5911
7535
|
} else if (cond.type === "MethodCallExpression") {
|
|
5912
|
-
|
|
7536
|
+
let objType = this.chainValue.get(cond.object) ?? this.typeOf.get(cond.object) ?? this.typeAtRef(cond.object, env);
|
|
7537
|
+
if (cond.optional) {
|
|
7538
|
+
objType = withoutNil(objType);
|
|
7539
|
+
selfType = objType;
|
|
7540
|
+
}
|
|
5913
7541
|
callee = this.propertyType(objType, cond.method.name);
|
|
5914
7542
|
const first = this.overloadsOf(callee)[0];
|
|
5915
7543
|
args = first && this.takesSelf(first) ? [cond.object, ...cond.arguments] : cond.arguments;
|
|
@@ -5917,7 +7545,7 @@ var TypeAnalyzer = class {
|
|
|
5917
7545
|
return void 0;
|
|
5918
7546
|
}
|
|
5919
7547
|
const overloads = this.overloadsOf(callee);
|
|
5920
|
-
const argTypes = args.map((a) => this.typeOf.get(a) ?? this.typeAtRef(a, env));
|
|
7548
|
+
const argTypes = args.map((a) => (selfType && cond.type === "MethodCallExpression" && a === cond.object ? selfType : void 0) ?? this.typeOf.get(a) ?? this.typeAtRef(a, env));
|
|
5921
7549
|
const picked = this.pickOverload(overloads, argTypes);
|
|
5922
7550
|
const candidates = picked ? [picked, ...overloads.filter((f) => f !== picked)] : overloads;
|
|
5923
7551
|
for (const f of candidates) {
|
|
@@ -6024,6 +7652,10 @@ var TypeAnalyzer = class {
|
|
|
6024
7652
|
const { yes, no } = refine(cur);
|
|
6025
7653
|
this.setRef(t, key, yes);
|
|
6026
7654
|
this.setRef(f, key, no);
|
|
7655
|
+
this.correlate(t, key, yes);
|
|
7656
|
+
this.correlate(f, key, no);
|
|
7657
|
+
this.propagateAliases(env, t, key, yes);
|
|
7658
|
+
this.propagateAliases(env, f, key, no);
|
|
6027
7659
|
const inner = expr.type === "ParenthesizedExpression" ? expr.expression : expr;
|
|
6028
7660
|
if (inner.type !== "MemberExpression" && inner.type !== "IndexExpression") return;
|
|
6029
7661
|
const parentKey = this.refKeyOf(inner.object);
|
|
@@ -6031,18 +7663,19 @@ var TypeAnalyzer = class {
|
|
|
6031
7663
|
const step = key.slice(parentKey.length);
|
|
6032
7664
|
if (!step.startsWith(".")) return;
|
|
6033
7665
|
const prop = step.slice(1);
|
|
7666
|
+
const optional2 = inner.type === "MemberExpression" && inner.optional === true;
|
|
6034
7667
|
this.narrowRef(inner.object, env, t, f, (parentType) => ({
|
|
6035
|
-
yes: this.filterByProperty(parentType, prop, yes),
|
|
6036
|
-
no: this.filterByProperty(parentType, prop, no)
|
|
7668
|
+
yes: this.filterByProperty(parentType, prop, yes, optional2),
|
|
7669
|
+
no: this.filterByProperty(parentType, prop, no, optional2)
|
|
6037
7670
|
}));
|
|
6038
7671
|
}
|
|
6039
7672
|
/** Keep the union members of `parent` whose `prop` can still hold `want`.
|
|
6040
7673
|
* Leaves a non-union (or a union nothing matches) alone: over-narrowing a
|
|
6041
7674
|
* plain object to `never` because of a property test would be worse than
|
|
6042
7675
|
* learning nothing. */
|
|
6043
|
-
filterByProperty(parent, prop, want) {
|
|
7676
|
+
filterByProperty(parent, prop, want, optional2 = false) {
|
|
6044
7677
|
if (parent.kind !== "union" || want.kind === "never") return parent;
|
|
6045
|
-
const kept = parent.types.filter((m) => overlaps(this.propertyType(m, prop), want));
|
|
7678
|
+
const kept = parent.types.filter((m) => m.kind === "primitive" && m.name === "nil" ? optional2 && overlaps(nilType, want) : overlaps(this.propertyType(m, prop), want));
|
|
6046
7679
|
return kept.length ? union(kept) : parent;
|
|
6047
7680
|
}
|
|
6048
7681
|
/** Record a narrowing. Deliberately does *not* discard what is known about
|
|
@@ -6054,6 +7687,15 @@ var TypeAnalyzer = class {
|
|
|
6054
7687
|
setRef(env, key, t) {
|
|
6055
7688
|
env.set(key, t);
|
|
6056
7689
|
}
|
|
7690
|
+
/** An assignment to a path (or to anything it hangs off) means the name
|
|
7691
|
+
* that copied it no longer holds that value: forget the alias. */
|
|
7692
|
+
unalias(key) {
|
|
7693
|
+
for (const k of [...this.refAliases.keys()]) {
|
|
7694
|
+
if (k !== key && !k.startsWith(`${key}.`) && !k.startsWith(`${key}#`)) continue;
|
|
7695
|
+
for (const other of this.refAliases.get(k) ?? []) this.refAliases.get(other)?.delete(k);
|
|
7696
|
+
this.refAliases.delete(k);
|
|
7697
|
+
}
|
|
7698
|
+
}
|
|
6057
7699
|
/** Drop every narrowing recorded for a path strictly under `key`. */
|
|
6058
7700
|
invalidateBelow(env, key) {
|
|
6059
7701
|
for (const k of [...env.keys()]) {
|
|
@@ -6066,6 +7708,7 @@ var TypeAnalyzer = class {
|
|
|
6066
7708
|
const key = this.refKeyOf(expr);
|
|
6067
7709
|
if (key === void 0) return;
|
|
6068
7710
|
this.invalidateBelow(env, key);
|
|
7711
|
+
this.unalias(key);
|
|
6069
7712
|
env.set(key, value);
|
|
6070
7713
|
}
|
|
6071
7714
|
// --------------------------------------------------------
|
|
@@ -6146,7 +7789,85 @@ var TypeAnalyzer = class {
|
|
|
6146
7789
|
/** The type a binding has *here*: its flow-narrowed type if the current
|
|
6147
7790
|
* environment has one, else its declared/inferred type. */
|
|
6148
7791
|
currentType(id, env) {
|
|
6149
|
-
return env.get(bindKey(id)) ?? this.bindingType.get(id) ?? anyType;
|
|
7792
|
+
return env.get(bindKey(id)) ?? this.bindingType.get(id) ?? this.declaredAhead(id) ?? anyType;
|
|
7793
|
+
}
|
|
7794
|
+
// --------------------------------------------------------
|
|
7795
|
+
// Hoisting
|
|
7796
|
+
// --------------------------------------------------------
|
|
7797
|
+
//
|
|
7798
|
+
// Scope analysis lets code see a function declared later in its block,
|
|
7799
|
+
// and a module's top-level names from function bodies and `typeof` written
|
|
7800
|
+
// above them. The walk has not reached those declarations yet when such a
|
|
7801
|
+
// reference is met, so their type is worked out from the declaration on
|
|
7802
|
+
// the spot — its annotation, or its body or initializer — as TypeScript
|
|
7803
|
+
// does. The walk reaching the declaration later types it for real.
|
|
7804
|
+
/** Declarations a reference may meet before the walk does. */
|
|
7805
|
+
aheadDeclarations;
|
|
7806
|
+
computingAhead = /* @__PURE__ */ new Set();
|
|
7807
|
+
declaredAhead(id) {
|
|
7808
|
+
this.aheadDeclarations ??= this.indexAheadDeclarations();
|
|
7809
|
+
const found = this.aheadDeclarations.get(id);
|
|
7810
|
+
if (!found || this.computingAhead.has(id)) return void 0;
|
|
7811
|
+
this.computingAhead.add(id);
|
|
7812
|
+
const wasEmitting = this.emitDiagnostics;
|
|
7813
|
+
this.emitDiagnostics = false;
|
|
7814
|
+
try {
|
|
7815
|
+
const { statement, index } = found;
|
|
7816
|
+
let type;
|
|
7817
|
+
if (statement.type === "DeclareStatement") {
|
|
7818
|
+
type = this.resolveType(statement.valueType);
|
|
7819
|
+
} else if (statement.type === "FunctionDeclaration") {
|
|
7820
|
+
type = statement.signatures?.length ? intersection(statement.signatures.map((sig) => this.signatureToFnType(sig))) : this.inferFunctionBody(statement.func, /* @__PURE__ */ new Map());
|
|
7821
|
+
} else if (statement.type === "VariableDeclaration") {
|
|
7822
|
+
const target = statement.names[index];
|
|
7823
|
+
if (target.type === "IdentifierPattern" && target.typeAnnotation) {
|
|
7824
|
+
type = this.resolveType(target.typeAnnotation);
|
|
7825
|
+
} else if (statement.init[index]) {
|
|
7826
|
+
const value = this.infer(statement.init[index], /* @__PURE__ */ new Map());
|
|
7827
|
+
type = statement.kind === "const" ? value : widen(value);
|
|
7828
|
+
}
|
|
7829
|
+
}
|
|
7830
|
+
if (type) this.bindingType.set(id, type);
|
|
7831
|
+
return type;
|
|
7832
|
+
} finally {
|
|
7833
|
+
this.emitDiagnostics = wasEmitting;
|
|
7834
|
+
this.computingAhead.delete(id);
|
|
7835
|
+
}
|
|
7836
|
+
}
|
|
7837
|
+
/** Every function declaration, and every plain name the module declares
|
|
7838
|
+
* at its top level. */
|
|
7839
|
+
indexAheadDeclarations() {
|
|
7840
|
+
const out = /* @__PURE__ */ new Map();
|
|
7841
|
+
for (const statement of this.program.body.statements) {
|
|
7842
|
+
const declaration = statement.type === "ExportStatement" ? statement.declaration : statement;
|
|
7843
|
+
if (declaration.type !== "VariableDeclaration") continue;
|
|
7844
|
+
declaration.names.forEach((target, index) => {
|
|
7845
|
+
if (target.type !== "IdentifierPattern") return;
|
|
7846
|
+
const id = this.bindingIdByName(target.name, target);
|
|
7847
|
+
if (id !== void 0) out.set(id, { statement: declaration, index });
|
|
7848
|
+
});
|
|
7849
|
+
}
|
|
7850
|
+
const visit = (node) => {
|
|
7851
|
+
if (!node || typeof node !== "object") return;
|
|
7852
|
+
if (Array.isArray(node)) {
|
|
7853
|
+
for (const item of node) visit(item);
|
|
7854
|
+
return;
|
|
7855
|
+
}
|
|
7856
|
+
const record = node;
|
|
7857
|
+
if (record.type === "FunctionDeclaration" && record.name) {
|
|
7858
|
+
const id = this.bindingIdByName(record.name.name, record.name);
|
|
7859
|
+
if (id !== void 0) out.set(id, { statement: node, index: 0 });
|
|
7860
|
+
}
|
|
7861
|
+
for (const [key, value] of Object.entries(node)) {
|
|
7862
|
+
if (key !== "line" && key !== "column" && value && typeof value === "object") visit(value);
|
|
7863
|
+
}
|
|
7864
|
+
};
|
|
7865
|
+
visit(this.program.body);
|
|
7866
|
+
for (const [name, statement] of this.deferredDeclares) {
|
|
7867
|
+
const id = this.scopes.globalsByName.get(name);
|
|
7868
|
+
if (id !== void 0) out.set(id, { statement, index: 0 });
|
|
7869
|
+
}
|
|
7870
|
+
return out;
|
|
6150
7871
|
}
|
|
6151
7872
|
/** Bind or rebind a whole variable: any narrowing recorded for a path
|
|
6152
7873
|
* *under* it (`x.a`, `x[1]`) described the old value and must go. */
|
|
@@ -6173,12 +7894,28 @@ var TypeAnalyzer = class {
|
|
|
6173
7894
|
return this.bindingByDecl.get(node) ?? this.bindingByPos.get(posKey(name, node.line.start, node.column.start));
|
|
6174
7895
|
}
|
|
6175
7896
|
};
|
|
7897
|
+
function referencedTypeNames(node, out = []) {
|
|
7898
|
+
if (!node || typeof node !== "object") return out;
|
|
7899
|
+
if (Array.isArray(node)) {
|
|
7900
|
+
for (const item of node) referencedTypeNames(item, out);
|
|
7901
|
+
return out;
|
|
7902
|
+
}
|
|
7903
|
+
const record = node;
|
|
7904
|
+
if (record.type === "TypeReference" && typeof record.base === "string") {
|
|
7905
|
+
out.push(typeof record.namespace === "string" ? `${record.namespace}.${record.base}` : record.base);
|
|
7906
|
+
}
|
|
7907
|
+
for (const [key, value] of Object.entries(node)) {
|
|
7908
|
+
if (key !== "line" && key !== "column" && value && typeof value === "object") referencedTypeNames(value, out);
|
|
7909
|
+
}
|
|
7910
|
+
return out;
|
|
7911
|
+
}
|
|
6176
7912
|
function containsTypeQuery(node) {
|
|
6177
7913
|
if (!node || typeof node !== "object") return false;
|
|
6178
7914
|
if (Array.isArray(node)) return node.some(containsTypeQuery);
|
|
6179
7915
|
if (node.type === "TypeofTypeNode") return true;
|
|
6180
7916
|
return Object.values(node).some(containsTypeQuery);
|
|
6181
7917
|
}
|
|
7918
|
+
var STRING_INTRINSICS = /* @__PURE__ */ new Set(["Uppercase", "Lowercase", "Capitalize", "Uncapitalize"]);
|
|
6182
7919
|
function briefType(t) {
|
|
6183
7920
|
if (t.kind === "union" && t.types.length > 8) {
|
|
6184
7921
|
const shown = t.types.slice(0, 6).map(formatType).join(" | ");
|
|
@@ -6375,13 +8112,13 @@ function resolveTypeLibraries(config, host = nodeHost) {
|
|
|
6375
8112
|
else problems.push({ file: config.path, message: `'${entry}' has no ${ENTRY_FILE} (or 'luaut.types' in its package.json)`, ...entryPosition(config, entry) });
|
|
6376
8113
|
continue;
|
|
6377
8114
|
}
|
|
6378
|
-
const
|
|
6379
|
-
const found =
|
|
8115
|
+
const name = entry.startsWith("@luaut/") ? entry : `@luaut/${entry}`;
|
|
8116
|
+
const found = findPackage(name, config.directory, host);
|
|
6380
8117
|
if (found) addPackage(found.directory, found.file, /* @__PURE__ */ new Set());
|
|
6381
8118
|
else {
|
|
6382
8119
|
problems.push({
|
|
6383
8120
|
file: config.path,
|
|
6384
|
-
message: `Cannot find type library '${
|
|
8121
|
+
message: `Cannot find type library '${name}'. Install it with: npm i -D ${name}`,
|
|
6385
8122
|
...entryPosition(config, entry)
|
|
6386
8123
|
});
|
|
6387
8124
|
}
|
|
@@ -6566,18 +8303,22 @@ export {
|
|
|
6566
8303
|
Keywords,
|
|
6567
8304
|
LexError,
|
|
6568
8305
|
Operators,
|
|
8306
|
+
PRELUDE_SOURCE,
|
|
6569
8307
|
ParseError,
|
|
6570
8308
|
Punctuators,
|
|
8309
|
+
UNUSED_EXPECT_ERROR,
|
|
6571
8310
|
UnaryOperators,
|
|
6572
8311
|
analyzeScopes,
|
|
6573
8312
|
analyzeTypes,
|
|
6574
8313
|
anyType,
|
|
8314
|
+
applyDirectives,
|
|
6575
8315
|
arrayOf,
|
|
6576
8316
|
booleanType,
|
|
6577
8317
|
bufferType,
|
|
6578
8318
|
containsTypeParam,
|
|
6579
8319
|
index_default as default,
|
|
6580
8320
|
difference,
|
|
8321
|
+
directivesOf,
|
|
6581
8322
|
equalTypes,
|
|
6582
8323
|
falsyType,
|
|
6583
8324
|
findConfig,
|
|
@@ -6613,6 +8354,7 @@ export {
|
|
|
6613
8354
|
parseTokens,
|
|
6614
8355
|
parseWithRecovery,
|
|
6615
8356
|
primitive,
|
|
8357
|
+
readDirectives,
|
|
6616
8358
|
resolveModulePath,
|
|
6617
8359
|
resolveTypeLibraries,
|
|
6618
8360
|
setAliasExpander,
|