luaut-parser 3.0.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/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
- throw new LexError("Unterminated long bracket", line, column);
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
- throw new LexError("Unterminated interpolated string", line, column);
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
- throw new LexError("Unterminated interpolation expression", line, column);
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
- throw new LexError(`Unexpected character '${peek()}'`, line, column);
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
- tokens.push(readOperatorOrPunctuator());
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.errors.push(err);
787
+ this.record(err);
671
788
  throw new ParseRecover(err.message);
672
789
  }
673
790
  throw err;
674
791
  }
675
- /** Recovery: skip tokens until the start of a plausible next statement (a
676
- * leading keyword / `@` attribute / just past a `;`) or a block
677
- * terminator. Forward progress past a zero-width failure is guaranteed by
678
- * the caller (`parseBlock`). */
679
- synchronize() {
680
- while (!this.isAtEnd()) {
681
- const t = this.current();
682
- if (t.type === "Punctuator" && t.value === "@") return;
683
- if (t.type === "Keyword") {
684
- switch (t.value) {
685
- case "const":
686
- case "let":
687
- case "function":
688
- case "if":
689
- case "while":
690
- case "for":
691
- case "return":
692
- case "do":
693
- case "repeat":
694
- case "break":
695
- case "continue":
696
- case "import":
697
- case "export":
698
- case "end":
699
- case "else":
700
- case "elseif":
701
- case "until":
702
- return;
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
- const prev = this.previous();
707
- if (prev.type === "Punctuator" && prev.value === ";") return;
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
- const t = this.current();
724
- this.errors.push(new ParseError(
725
- `Expected end of file, got '${this.describeToken(t)}'`,
726
- t.line.start,
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
- parseBlock() {
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.errors.push(e);
1024
+ this.record(e);
760
1025
  } else {
761
1026
  throw e;
762
1027
  }
763
- this.synchronize();
1028
+ this.skip(() => false, at, "statement");
764
1029
  if (this.cursor === at) {
765
1030
  if (this.isAtEnd()) break;
766
1031
  this.advance();
@@ -838,6 +1103,10 @@ var Parser = class {
838
1103
  }
839
1104
  }
840
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
+ }
841
1110
  if (t.type === "Identifier" && t.value === "type" && this.peek(1).type === "Identifier") {
842
1111
  return this.parseTypeAliasStatement();
843
1112
  }
@@ -1026,7 +1295,7 @@ var Parser = class {
1026
1295
  return { type: "ExportStatement", declaration, ...spanFrom(start, this.previous()) };
1027
1296
  }
1028
1297
  if (this.checkKeyword("function")) {
1029
- const declaration = this.parseFunctionStatement();
1298
+ const declaration = this.parseFunctionStatement(true);
1030
1299
  if (declaration.type !== "FunctionDeclaration") this.error("An exported function needs a plain name: 'export function name()'");
1031
1300
  return { type: "ExportStatement", declaration, ...spanFrom(start, this.previous()) };
1032
1301
  }
@@ -1057,9 +1326,11 @@ var Parser = class {
1057
1326
  }
1058
1327
  // `const x = ...` / `let x, y = ...`.
1059
1328
  // luaut has no `local` — `const` bindings are immutable, `let` mutable.
1060
- parseVariableDeclaration() {
1329
+ /** `kind` reads the leading word as that keyword (recovery's `local`). */
1330
+ parseVariableDeclaration(as) {
1061
1331
  const start = this.current();
1062
- const kind = this.advance().value;
1332
+ const word = this.advance().value;
1333
+ const kind = as ?? word;
1063
1334
  if (this.checkKeyword("function")) {
1064
1335
  this.error(`A function is declared as 'function name()'; '${kind}' does not apply to functions`);
1065
1336
  }
@@ -1069,9 +1340,10 @@ var Parser = class {
1069
1340
  }
1070
1341
  let init = [];
1071
1342
  if (this.matchOperator("=")) {
1072
- init = this.parseExpressionList();
1343
+ init = this.expressionListOr(() => false);
1073
1344
  } else if (kind === "const") {
1074
- 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");
1075
1347
  }
1076
1348
  return { type: "VariableDeclaration", kind, names, init, ...spanFrom(start, this.previous()) };
1077
1349
  }
@@ -1079,64 +1351,73 @@ var Parser = class {
1079
1351
  const start = this.current();
1080
1352
  this.expectKeyword("if");
1081
1353
  const clauses = [];
1082
- const cond = this.parseExpression();
1083
- this.expectKeyword("then");
1084
- const body = this.parseBlock();
1354
+ const untilThen = () => this.checkKeyword("then");
1355
+ const cond = this.expressionOr(untilThen);
1356
+ this.expectKeywordSoft("then");
1357
+ const body = this.parseBlock(start);
1085
1358
  clauses.push({ type: "IfClause", condition: cond, body, ...spanFrom(cond, this.previous()) });
1086
1359
  while (this.checkKeyword("elseif")) {
1087
1360
  const clauseStart = this.current();
1088
1361
  this.advance();
1089
- const c = this.parseExpression();
1090
- this.expectKeyword("then");
1091
- const b = this.parseBlock();
1362
+ const c = this.expressionOr(untilThen);
1363
+ this.expectKeywordSoft("then");
1364
+ const b = this.parseBlock(start);
1092
1365
  clauses.push({ type: "IfClause", condition: c, body: b, ...spanFrom(clauseStart, this.previous()) });
1093
1366
  }
1094
1367
  let alternate;
1095
1368
  if (this.matchKeyword("else")) {
1096
- alternate = this.parseBlock();
1369
+ alternate = this.parseBlock(start);
1097
1370
  }
1098
- this.expectKeyword("end");
1371
+ this.expectEnd(start);
1099
1372
  return { type: "IfStatement", clauses, alternate, ...spanFrom(start, this.previous()) };
1100
1373
  }
1101
1374
  parseWhileStatement() {
1102
1375
  const start = this.current();
1103
1376
  this.expectKeyword("while");
1104
- const condition = this.parseExpression();
1105
- this.expectKeyword("do");
1106
- const body = this.parseBlock();
1107
- this.expectKeyword("end");
1377
+ const condition = this.expressionOr(() => this.checkKeyword("do"));
1378
+ this.expectKeywordSoft("do");
1379
+ const body = this.parseBlock(start);
1380
+ this.expectEnd(start);
1108
1381
  return { type: "WhileStatement", condition, body, ...spanFrom(start, this.previous()) };
1109
1382
  }
1110
1383
  parseRepeatStatement() {
1111
1384
  const start = this.current();
1112
1385
  this.expectKeyword("repeat");
1113
- const body = this.parseBlock();
1114
- this.expectKeyword("until");
1115
- const condition = this.parseExpression();
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
+ }
1116
1396
  return { type: "RepeatStatement", body, condition, ...spanFrom(start, this.previous()) };
1117
1397
  }
1118
1398
  parseDoStatement() {
1119
1399
  const start = this.current();
1120
1400
  this.expectKeyword("do");
1121
- const body = this.parseBlock();
1122
- this.expectKeyword("end");
1401
+ const body = this.parseBlock(start);
1402
+ this.expectEnd(start);
1123
1403
  return { type: "DoStatement", body, ...spanFrom(start, this.previous()) };
1124
1404
  }
1125
1405
  parseForStatement() {
1126
1406
  const start = this.current();
1127
1407
  this.expectKeyword("for");
1128
1408
  const first = this.parseBindingTarget(true);
1409
+ const untilDo = () => this.checkKeyword("do");
1129
1410
  if (first.type === "IdentifierPattern" && this.matchOperator("=")) {
1130
- const from = this.parseExpression();
1411
+ const from = this.expressionOr(() => untilDo() || this.checkPunctuator(","));
1131
1412
  this.expectPunctuator(",");
1132
- const to = this.parseExpression();
1413
+ const to = this.expressionOr(() => untilDo() || this.checkPunctuator(","));
1133
1414
  let step;
1134
1415
  if (this.matchPunctuator(",")) {
1135
- step = this.parseExpression();
1416
+ step = this.expressionOr(untilDo);
1136
1417
  }
1137
- this.expectKeyword("do");
1138
- const body2 = this.parseBlock();
1139
- this.expectKeyword("end");
1418
+ this.expectKeywordSoft("do");
1419
+ const body2 = this.parseBlock(start);
1420
+ this.expectEnd(start);
1140
1421
  return {
1141
1422
  type: "NumericForStatement",
1142
1423
  variable: this.identifierPatternToTypedIdentifier(first),
@@ -1152,10 +1433,10 @@ var Parser = class {
1152
1433
  variables.push(this.parseBindingTarget(true));
1153
1434
  }
1154
1435
  this.expectKeyword("in");
1155
- const iterators = this.parseExpressionList();
1156
- this.expectKeyword("do");
1157
- const body = this.parseBlock();
1158
- this.expectKeyword("end");
1436
+ const iterators = this.expressionListOr(untilDo);
1437
+ this.expectKeywordSoft("do");
1438
+ const body = this.parseBlock(start);
1439
+ this.expectEnd(start);
1159
1440
  return {
1160
1441
  type: "GenericForStatement",
1161
1442
  variables,
@@ -1166,28 +1447,36 @@ var Parser = class {
1166
1447
  }
1167
1448
  /** `function name() end` declares `name`; `function a.b() end` and
1168
1449
  * `function T:m() end` define a member. */
1169
- parseFunctionStatement() {
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) {
1170
1453
  const start = this.current();
1171
1454
  this.expectKeyword("function");
1172
1455
  const target = this.parseFunctionName();
1173
1456
  const isMethod = target.method !== void 0;
1174
1457
  const simpleName = !isMethod && target.path.length === 0 ? target.base.name : void 0;
1175
1458
  const signatures = [];
1459
+ let written = target.base;
1176
1460
  while (true) {
1177
1461
  const head = this.parseFunctionHead();
1178
1462
  if (simpleName !== void 0 && this.isOverloadContinuation(simpleName)) {
1179
- 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
+ }
1180
1468
  this.expectKeyword("function");
1181
- this.parseFunctionName();
1469
+ written = this.parseFunctionName().base;
1182
1470
  continue;
1183
1471
  }
1184
- const func = this.headToBody(head);
1472
+ const func = this.headToBody(head, start);
1185
1473
  if (simpleName !== void 0) {
1186
1474
  return {
1187
1475
  type: "FunctionDeclaration",
1188
1476
  name: target.base,
1189
1477
  func,
1190
1478
  signatures: signatures.length ? signatures : void 0,
1479
+ implementationName: signatures.length ? written : void 0,
1191
1480
  ...spanFrom(start, this.previous())
1192
1481
  };
1193
1482
  }
@@ -1209,7 +1498,18 @@ var Parser = class {
1209
1498
  * declaration for the same simple `name` (making the head an overload
1210
1499
  * signature rather than an implementation)? */
1211
1500
  isOverloadContinuation(name) {
1212
- return this.checkKeyword("function") && this.peek(1).type === "Identifier" && this.peek(1).value === 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);
1213
1513
  }
1214
1514
  parseFunctionName() {
1215
1515
  const start = this.current();
@@ -1245,7 +1545,7 @@ var Parser = class {
1245
1545
  this.expectKeyword("return");
1246
1546
  let args = [];
1247
1547
  if (this.isExpressionStart()) {
1248
- args = this.parseExpressionList();
1548
+ args = this.expressionListOr(() => false);
1249
1549
  }
1250
1550
  return { type: "ReturnStatement", arguments: args, ...spanFrom(start, this.previous()) };
1251
1551
  }
@@ -1277,7 +1577,7 @@ var Parser = class {
1277
1577
  targets.push(this.parseAssignTarget());
1278
1578
  }
1279
1579
  this.expectOperator("=");
1280
- const values = this.parseExpressionList();
1580
+ const values = this.expressionListOr(() => false);
1281
1581
  return { type: "AssignmentStatement", targets, values, ...spanFrom(start, this.previous()) };
1282
1582
  }
1283
1583
  const first = this.parsePrefixExpression();
@@ -1286,14 +1586,16 @@ var Parser = class {
1286
1586
  while (this.matchPunctuator(",")) {
1287
1587
  targets.push(this.parseAssignTarget());
1288
1588
  }
1589
+ for (const target of targets) this.rejectOptionalTarget(target);
1289
1590
  this.expectOperator("=");
1290
- const values = this.parseExpressionList();
1591
+ const values = this.expressionListOr(() => false);
1291
1592
  return { type: "AssignmentStatement", targets, values, ...spanFrom(start, this.previous()) };
1292
1593
  }
1293
1594
  const t = this.current();
1294
1595
  if (t.type === "Operator" && COMPOUND_ASSIGN_OPS.has(t.value)) {
1596
+ this.rejectOptionalTarget(first);
1295
1597
  const op = this.advance().value;
1296
- const value = this.parseExpression();
1598
+ const value = this.expressionOr(() => false);
1297
1599
  return {
1298
1600
  type: "CompoundAssignmentStatement",
1299
1601
  operator: op,
@@ -1331,15 +1633,43 @@ var Parser = class {
1331
1633
  * rather than the `:` of a ternary (`cond ? obj : other`)? Lua requires a
1332
1634
  * method call to be called, so the answer is exact rather than heuristic:
1333
1635
  * `:` Identifier followed by one of Lua's call forms. */
1334
- startsMethodCall() {
1335
- if (this.peek(1).type !== "Identifier") return false;
1336
- 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);
1337
1639
  if (after.type === "Punctuator") {
1338
1640
  const v = String(after.value);
1339
1641
  return v === "(" || v === "{";
1340
1642
  }
1341
1643
  if (after.type === "InterpolatedString") return true;
1342
- return after.type === "Literal" && after.kind === "string";
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
+ }
1343
1673
  }
1344
1674
  isUnaryOperator() {
1345
1675
  const t = this.current();
@@ -1456,7 +1786,7 @@ var Parser = class {
1456
1786
  }
1457
1787
  if (t.type === "Keyword" && t.value === "function") {
1458
1788
  this.advance();
1459
- const func = this.parseFunctionBody();
1789
+ const func = this.parseFunctionBody(t);
1460
1790
  return { type: "FunctionExpression", func, ...spanFrom(t, this.previous()) };
1461
1791
  }
1462
1792
  if (t.type === "Keyword" && t.value === "if") {
@@ -1479,7 +1809,19 @@ var Parser = class {
1479
1809
  if (p.kind === "string") {
1480
1810
  parts.push({ kind: "string", value: p.value, raw: p.raw });
1481
1811
  } else {
1482
- const expression = parseExpressionFromSource(p.raw);
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
+ }
1483
1825
  parts.push({ kind: "expression", expression });
1484
1826
  }
1485
1827
  }
@@ -1517,7 +1859,53 @@ var Parser = class {
1517
1859
  this.error("Expected identifier or '('");
1518
1860
  }
1519
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
+ }
1520
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
+ }
1521
1909
  const prop = this.parseIdentifier();
1522
1910
  base = { type: "MemberExpression", object: base, property: prop, ...spanFrom(base, prop) };
1523
1911
  continue;
@@ -1531,17 +1919,33 @@ var Parser = class {
1531
1919
  if (this.checkPunctuator(":") && this.startsMethodCall()) {
1532
1920
  this.advance();
1533
1921
  const method = this.parseIdentifier();
1922
+ const typeArguments = this.tryCallTypeArguments();
1534
1923
  const args = this.parseCallArguments();
1535
1924
  base = {
1536
1925
  type: "MethodCallExpression",
1537
1926
  object: base,
1538
1927
  method,
1539
1928
  arguments: args,
1929
+ typeArguments,
1540
1930
  ...spanFrom(base, this.previous())
1541
1931
  };
1542
1932
  continue;
1543
1933
  }
1544
- if (this.checkPunctuator("(") || this.checkType("Literal") && this.current().kind === "string" || this.checkType("InterpolatedString") || this.checkPunctuator("{")) {
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()) {
1545
1949
  const args = this.parseCallArguments();
1546
1950
  base = {
1547
1951
  type: "CallExpression",
@@ -1555,6 +1959,11 @@ var Parser = class {
1555
1959
  }
1556
1960
  return base;
1557
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
+ }
1558
1967
  /** An assignment target after the first: a prefix expression (`a.b`,
1559
1968
  * `a[i]`, `a`) or a nested destructuring pattern. */
1560
1969
  parseAssignTarget() {
@@ -1562,14 +1971,52 @@ var Parser = class {
1562
1971
  if (this.checkPunctuator("[")) return this.parseArrayPattern();
1563
1972
  return this.parsePrefixExpression();
1564
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
+ }
1565
2000
  parseCallArguments() {
1566
2001
  if (this.matchPunctuator("(")) {
1567
- if (this.checkPunctuator(")")) {
1568
- this.advance();
1569
- return [];
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
+ }
1570
2018
  }
1571
- const list = this.parseExpressionList();
1572
- this.expectPunctuator(")");
2019
+ this.expectCloser(")");
1573
2020
  return list;
1574
2021
  }
1575
2022
  const t = this.current();
@@ -1596,57 +2043,89 @@ var Parser = class {
1596
2043
  const start = this.current();
1597
2044
  this.expectPunctuator("{");
1598
2045
  const fields = [];
2046
+ const stop = () => this.checkPunctuator(",") || this.checkPunctuator(";") || this.onNewLine() && this.startsTableField();
1599
2047
  while (!this.checkPunctuator("}")) {
1600
- if (this.checkOperator("...")) {
1601
- this.advance();
1602
- const argument = this.parseExpression();
1603
- fields.push({ type: "TableFieldSpread", argument });
1604
- } else if (this.matchPunctuator("[")) {
1605
- const key = this.parseExpression();
1606
- this.expectPunctuator("]");
1607
- this.expectPunctuator(":");
1608
- const value = this.parseExpression();
1609
- fields.push({ type: "TableFieldComputed", key, value });
1610
- } else if (this.checkType("Literal") && this.current().kind === "string") {
1611
- const t = this.advance();
1612
- const key = { type: "StringLiteral", value: t.value, raw: t.raw, ...spanFrom(t, t) };
1613
- this.expectPunctuator(":");
1614
- const value = this.parseExpression();
1615
- fields.push({ type: "TableFieldNamed", key, value });
1616
- } else if (this.checkType("Identifier") && this.peek(1).type === "Punctuator" && this.peek(1).value === ":") {
1617
- const key = this.parseIdentifier();
1618
- this.expectPunctuator(":");
1619
- const value = this.parseExpression();
1620
- fields.push({ type: "TableFieldNamed", key, value });
1621
- } else if (this.checkType("Identifier")) {
1622
- const name = this.parseIdentifier();
1623
- fields.push({ type: "TableFieldShorthand", name });
1624
- } else {
1625
- 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;
1626
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");
1627
2060
  if (this.matchPunctuator(",") || this.matchPunctuator(";")) continue;
2061
+ if (this.cursor > before && this.onNewLine() && this.startsTableField()) continue;
1628
2062
  break;
1629
2063
  }
1630
- this.expectPunctuator("}");
2064
+ this.expectCloser("}");
1631
2065
  return { type: "TableExpression", fields, ...spanFrom(start, this.previous()) };
1632
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
+ }
1633
2102
  // `[1, 2, 3]` — array literal (trailing comma allowed).
1634
2103
  parseArrayExpression() {
1635
2104
  const start = this.current();
1636
2105
  this.expectPunctuator("[");
1637
2106
  const elements = [];
2107
+ const stop = () => this.checkPunctuator(",");
1638
2108
  while (!this.checkPunctuator("]")) {
1639
2109
  if (this.checkOperator("...")) {
1640
2110
  const dots = this.advance();
1641
- const argument = this.parseExpression();
2111
+ const argument = this.expressionOr(stop);
1642
2112
  elements.push({ type: "SpreadElement", argument, ...spanFrom(dots, argument) });
1643
2113
  } else {
1644
- elements.push(this.parseExpression());
2114
+ elements.push(this.expressionOr(stop));
1645
2115
  }
1646
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;
2121
+ }
2122
+ if (this.isAtEnd() || this.onNewLine() && this.checkType("Keyword")) break;
2123
+ this.softError("Expected ',' or ']'");
2124
+ this.skip(stop, this.cursor, "expression");
2125
+ if (this.matchPunctuator(",")) continue;
1647
2126
  break;
1648
2127
  }
1649
- this.expectPunctuator("]");
2128
+ this.expectCloser("]");
1650
2129
  return { type: "ArrayExpression", elements, ...spanFrom(start, this.previous()) };
1651
2130
  }
1652
2131
  // ============================================================
@@ -1679,7 +2158,7 @@ var Parser = class {
1679
2158
  };
1680
2159
  }
1681
2160
  if (topLevel && this.matchPunctuator(":")) {
1682
- target.typeAnnotation = this.parseType();
2161
+ target.typeAnnotation = this.typeOr(() => this.checkOperator("=") || this.checkPunctuator(","));
1683
2162
  }
1684
2163
  return target;
1685
2164
  }
@@ -1838,12 +2317,13 @@ var Parser = class {
1838
2317
  }
1839
2318
  const optional2 = this.matchPunctuator("?");
1840
2319
  let typeAnnotation;
2320
+ const paramEnd = () => this.checkPunctuator(",");
1841
2321
  if (this.matchPunctuator(":")) {
1842
- typeAnnotation = this.parseType();
2322
+ typeAnnotation = this.typeOr(() => paramEnd() || this.checkOperator("="));
1843
2323
  }
1844
2324
  let def;
1845
2325
  if (this.matchOperator("=")) {
1846
- def = this.parseExpression();
2326
+ def = this.expressionOr(paramEnd);
1847
2327
  }
1848
2328
  params.push({
1849
2329
  type: "FunctionParameter",
@@ -1854,7 +2334,7 @@ var Parser = class {
1854
2334
  optional: optional2 || void 0,
1855
2335
  ...spanFrom(paramStart, this.previous())
1856
2336
  });
1857
- if (this.matchPunctuator(",")) continue;
2337
+ if (this.matchPunctuator(",") && !this.checkPunctuator(")")) continue;
1858
2338
  break;
1859
2339
  }
1860
2340
  }
@@ -1863,7 +2343,9 @@ var Parser = class {
1863
2343
  let predicate;
1864
2344
  if (this.matchPunctuator(":")) {
1865
2345
  predicate = this.tryParseTypePredicate();
1866
- if (!predicate) returnType = this.parseTypeOrTypePackReference();
2346
+ if (!predicate) {
2347
+ returnType = this.attempt(() => this.parseTypeOrTypePackReference(), () => false, () => void 0);
2348
+ }
1867
2349
  }
1868
2350
  return { start, generics, params, hasVarargs, varargTypeAnnotation, returnType, predicate };
1869
2351
  }
@@ -1909,10 +2391,10 @@ var Parser = class {
1909
2391
  }
1910
2392
  return void 0;
1911
2393
  }
1912
- parseFunctionBody() {
2394
+ parseFunctionBody(opener) {
1913
2395
  const head = this.parseFunctionHead();
1914
- const body = this.parseBlock();
1915
- this.expectKeyword("end");
2396
+ const body = this.parseBlock(opener);
2397
+ this.expectEnd(opener);
1916
2398
  return {
1917
2399
  type: "FunctionBody",
1918
2400
  generics: head.generics,
@@ -1937,9 +2419,9 @@ var Parser = class {
1937
2419
  ...spanFrom(head.start, this.previous())
1938
2420
  };
1939
2421
  }
1940
- headToBody(head) {
1941
- const body = this.parseBlock();
1942
- this.expectKeyword("end");
2422
+ headToBody(head, opener) {
2423
+ const body = this.parseBlock(opener);
2424
+ this.expectEnd(opener);
1943
2425
  return {
1944
2426
  type: "FunctionBody",
1945
2427
  generics: head.generics,
@@ -2146,7 +2628,7 @@ var Parser = class {
2146
2628
  this.advance();
2147
2629
  if (!this.checkOperator(">")) {
2148
2630
  typeArguments.push(this.parseTypeArgument());
2149
- while (this.matchPunctuator(",")) {
2631
+ while (this.matchPunctuator(",") && !this.checkOperator(">")) {
2150
2632
  typeArguments.push(this.parseTypeArgument());
2151
2633
  }
2152
2634
  }
@@ -2197,7 +2679,7 @@ var Parser = class {
2197
2679
  optional: optional2 || void 0,
2198
2680
  ...spanFrom(paramStart, this.previous())
2199
2681
  });
2200
- if (this.matchPunctuator(",")) continue;
2682
+ if (this.matchPunctuator(",") && !this.checkPunctuator(")")) continue;
2201
2683
  break;
2202
2684
  }
2203
2685
  }
@@ -2412,7 +2894,7 @@ var Parser = class {
2412
2894
  default: def,
2413
2895
  ...spanFrom(nameTok, this.previous())
2414
2896
  });
2415
- if (this.matchPunctuator(",")) continue;
2897
+ if (this.matchPunctuator(",") && !this.checkOperator(">")) continue;
2416
2898
  break;
2417
2899
  }
2418
2900
  this.expectOperator(">");
@@ -2439,23 +2921,23 @@ function parseExpressionFromSource(raw) {
2439
2921
  return expr;
2440
2922
  }
2441
2923
  function parseWithRecovery(source) {
2442
- let tokens;
2443
- try {
2444
- tokens = tokenize(source);
2445
- } catch (e) {
2446
- const le = e;
2447
- const err = new ParseError(le.message ?? "Lex error", le.line ?? 1, le.column ?? 1);
2448
- const empty = {
2449
- type: "Program",
2450
- body: { type: "Block", statements: [], line: { start: 1, end: 1 }, column: { start: 1, end: 1 } },
2451
- line: { start: 1, end: 1 },
2452
- column: { start: 1, end: 1 }
2453
- };
2454
- return { program: empty, errors: [err] };
2455
- }
2456
- const parser = new Parser(tokens, { recover: true });
2457
- const program = parser.parseProgram();
2458
- return { program, errors: parser.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) };
2459
2941
  }
2460
2942
 
2461
2943
  // src/ast/nodes.ts
@@ -2494,19 +2976,24 @@ function childScope(parent) {
2494
2976
  return { parent, declarations: /* @__PURE__ */ new Map() };
2495
2977
  }
2496
2978
  var Analyzer = class {
2497
- nextId = 0;
2498
- bindingOf = /* @__PURE__ */ new Map();
2499
- bindings = /* @__PURE__ */ new Map();
2500
- diagnostics = [];
2501
- globalScope = { parent: null, declarations: /* @__PURE__ */ new Map() };
2502
2979
  constructor(options) {
2980
+ this.options = options;
2503
2981
  for (const name of options.builtinGlobals ?? []) {
2504
2982
  const id = this.getOrCreateGlobalBinding(name);
2505
2983
  this.bindings.get(id).isBuiltin = true;
2506
2984
  }
2507
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() };
2508
2992
  run(program) {
2509
- this.visitBlock(program.body, childScope(this.globalScope));
2993
+ this.moduleScope = childScope(this.globalScope);
2994
+ this.visitBlock(program.body, this.moduleScope);
2995
+ this.resolveForwardReferences();
2996
+ if (this.options.reportUndeclared) this.reportUndeclared(program);
2510
2997
  return {
2511
2998
  bindingOf: this.bindingOf,
2512
2999
  bindings: this.bindings,
@@ -2514,6 +3001,99 @@ var Analyzer = class {
2514
3001
  globalsByName: this.globalScope.declarations
2515
3002
  };
2516
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
+ }
2517
3097
  // ---------------- declaration / resolution primitives ----------------
2518
3098
  declare(scope, name, kind, node, isConst = false, declaredBy) {
2519
3099
  if (scope.declarations.has(name) && scope !== this.globalScope) {
@@ -2559,6 +3139,8 @@ var Analyzer = class {
2559
3139
  this.bindingOf.set(identifier, id);
2560
3140
  this.bindings.get(id).references.push(identifier);
2561
3141
  if (this.typeQueryDepth === 0) this.checkTypeOnly(id, identifier);
3142
+ this.checkUseBeforeDefine(identifier, id);
3143
+ this.noteDeferred(identifier, scope, id, false);
2562
3144
  }
2563
3145
  /** Inside `typeof x` in a type, where a type-only import may be named. */
2564
3146
  typeQueryDepth = 0;
@@ -2589,6 +3171,7 @@ var Analyzer = class {
2589
3171
  this.recordPossibleGlobalDefinition(id, identifier);
2590
3172
  this.checkTypeOnly(id, identifier);
2591
3173
  this.checkConstAssign(id, identifier);
3174
+ this.noteDeferred(identifier, scope, id, true);
2592
3175
  }
2593
3176
  /** `Module.x = 1` through `import * as Module`: a module's exports belong
2594
3177
  * to it and are read-only, as in ES modules. Deeper writes (`Module.x.y`)
@@ -2681,6 +3264,7 @@ var Analyzer = class {
2681
3264
  }
2682
3265
  // ---------------- blocks / statements ----------------
2683
3266
  visitBlock(block, scope) {
3267
+ this.hoistFunctions(block, scope);
2684
3268
  for (const stmt of block.statements) this.visitStatement(stmt, scope);
2685
3269
  }
2686
3270
  /** Visits a block in a *fresh child scope* of `scope` — the common case
@@ -2699,7 +3283,11 @@ var Analyzer = class {
2699
3283
  return;
2700
3284
  }
2701
3285
  case "FunctionDeclaration": {
2702
- this.declare(scope, stmt.name.name, "local", stmt.name, true, "function");
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);
2703
3291
  for (const signature of stmt.signatures ?? []) this.visitSignature(signature, scope);
2704
3292
  this.visitFunctionBody(stmt.func, scope);
2705
3293
  return;
@@ -2800,7 +3388,11 @@ var Analyzer = class {
2800
3388
  return;
2801
3389
  case "TypeAliasStatement":
2802
3390
  case "ExportTypeAliasStatement":
2803
- this.visitType(stmt.definition, scope);
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);
2804
3396
  return;
2805
3397
  case "ImportStatement": {
2806
3398
  const typeOnly = stmt.isTypeOnly ? "type" : void 0;
@@ -2838,6 +3430,7 @@ var Analyzer = class {
2838
3430
  // ---------------- functions ----------------
2839
3431
  visitFunctionBody(func, outerScope, isMethod = false) {
2840
3432
  const fnScope = childScope(outerScope);
3433
+ this.visitGenerics(func.generics, fnScope);
2841
3434
  func.params.forEach((param, i) => {
2842
3435
  const kind = isMethod && i === 0 ? "self" : "param";
2843
3436
  this.visitType(param.typeAnnotation, fnScope);
@@ -2850,14 +3443,28 @@ var Analyzer = class {
2850
3443
  });
2851
3444
  this.visitType(func.varargTypeAnnotation, fnScope);
2852
3445
  this.visitType(func.returnType, fnScope);
2853
- this.visitBlock(func.body, fnScope);
3446
+ this.functionDepth++;
3447
+ try {
3448
+ this.visitBlock(func.body, fnScope);
3449
+ } finally {
3450
+ this.functionDepth--;
3451
+ }
2854
3452
  }
2855
3453
  /** An overload signature: no body and no bindings, but its types can hold
2856
3454
  * a `typeof x`. */
2857
3455
  visitSignature(signature, scope) {
3456
+ this.visitGenerics(signature.generics, scope);
2858
3457
  for (const param of signature.params) this.visitType(param.typeAnnotation, scope);
2859
3458
  this.visitType(signature.returnType, scope);
2860
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
+ }
2861
3468
  /** Resolve the value references inside a type. Only `typeof x` has any —
2862
3469
  * everything else in a type names types, which live in their own
2863
3470
  * namespace and are not this pass's business. */
@@ -2895,6 +3502,7 @@ var Analyzer = class {
2895
3502
  case "NumberLiteral":
2896
3503
  case "StringLiteral":
2897
3504
  case "VarargExpression":
3505
+ case "ErrorExpression":
2898
3506
  return;
2899
3507
  case "InterpolatedStringExpression":
2900
3508
  for (const part of expr.parts) {
@@ -2979,6 +3587,37 @@ function analyzeScopes(program, options = {}) {
2979
3587
  return new Analyzer(options).run(program);
2980
3588
  }
2981
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
+
2982
3621
  // src/ast/typeModel.ts
2983
3622
  function isClassType(t) {
2984
3623
  return t.kind === "object" && t.class !== void 0;
@@ -3054,6 +3693,7 @@ function substitute(t, subst) {
3054
3693
  varargs,
3055
3694
  returns: substitute(t.returns, inner),
3056
3695
  typeParams: t.typeParams,
3696
+ typeParamDefaults: t.typeParamDefaults,
3057
3697
  predicate: t.predicate && {
3058
3698
  ...t.predicate,
3059
3699
  type: t.predicate.type && substitute(t.predicate.type, inner)
@@ -3312,6 +3952,15 @@ function isAssignableInner(a, b) {
3312
3952
  }
3313
3953
  if (!isAssignable(ap.type, bp.type)) return false;
3314
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
+ }
3963
+ }
3315
3964
  return true;
3316
3965
  }
3317
3966
  if (a.kind === "function") {
@@ -3461,10 +4110,14 @@ function containsFreeTypeParam(t, seen, bound) {
3461
4110
  return containsTypeParam(t.base, seen, bound) || containsTypeParam(t.excluded, seen, bound);
3462
4111
  case "indexedAccess":
3463
4112
  return containsTypeParam(t.objectType, seen, bound) || containsTypeParam(t.indexType, seen, bound);
3464
- case "conditional":
3465
- return containsTypeParam(t.checkType, seen, bound);
3466
- case "mapped":
3467
- return containsTypeParam(t.constraint, seen, bound);
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
+ }
3468
4121
  default:
3469
4122
  return false;
3470
4123
  }
@@ -3538,9 +4191,52 @@ function escapeRegExp(s) {
3538
4191
  return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3539
4192
  }
3540
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));
3541
4196
  return isAssignable(a, b) || isAssignable(b, a);
3542
4197
  }
3543
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
+ }
3544
4240
  function formatType(t) {
3545
4241
  const cached = formatCache.get(t);
3546
4242
  if (cached !== void 0) return cached;
@@ -3577,7 +4273,14 @@ function formatTypeUncached(t) {
3577
4273
  const consts = new Set(
3578
4274
  t.params.filter((p) => p.type.kind === "typeParam" && p.type.isConst).map((p) => p.type.name)
3579
4275
  );
3580
- const gen = t.typeParams?.length ? `<${t.typeParams.map((n) => consts.has(n) ? `const ${n}` : n).join(", ")}>` : "";
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(", ")}>` : "";
3581
4284
  const ps = t.params.map((p) => `${p.name ? p.name + ": " : ""}${formatType(p.type)}`);
3582
4285
  if (t.varargs) ps.push(`...${formatType(t.varargs)}`);
3583
4286
  return `${gen}(${ps.join(", ")}) -> ${formatPredicate(t) ?? formatType(t.returns)}`;
@@ -3793,6 +4496,12 @@ function isFreshLiteralExpr(e) {
3793
4496
  return isFreshLiteralExpr(e.expression);
3794
4497
  case "UnaryExpression":
3795
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
+ }
3796
4505
  default:
3797
4506
  return false;
3798
4507
  }
@@ -3902,6 +4611,48 @@ var AliasMap = class extends Map {
3902
4611
  return this.entries();
3903
4612
  }
3904
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
+ }
3905
4656
  var METAMETHODS = {
3906
4657
  "+": "__add",
3907
4658
  "-": "__sub",
@@ -3975,14 +4726,16 @@ var TypeAnalyzer = class {
3975
4726
  /** Recursion guard for `preVisitBody`. */
3976
4727
  preVisitDepth = 0;
3977
4728
  run() {
4729
+ this.registerAliasDefs(preludeProgram().body);
3978
4730
  for (const lib of this.options.libs ?? []) this.registerAliasDefs(lib.body);
3979
4731
  this.registerAliasDefs(this.program.body);
3980
4732
  for (const lib of this.options.libs ?? []) this.harvestDeclares(lib.body);
3981
- this.harvestDeclares(this.program.body);
3982
4733
  this.registerImportedTypes();
3983
4734
  this.resolveAllAliases();
4735
+ this.harvestDeclares(this.program.body, true);
3984
4736
  this.indexDeclarations();
3985
4737
  for (const [name, id] of this.scopes.globalsByName) {
4738
+ if (this.deferredDeclares.has(name) && !this.options.globalTypes?.[name]) continue;
3986
4739
  const t = this.options.globalTypes?.[name] ?? this.libGlobalTypes.get(name) ?? anyType;
3987
4740
  this.bindingType.set(id, t);
3988
4741
  }
@@ -3990,6 +4743,8 @@ var TypeAnalyzer = class {
3990
4743
  try {
3991
4744
  const env = /* @__PURE__ */ new Map();
3992
4745
  this.visitBlock(this.program.body, env);
4746
+ this.resolveDeferredDeclares();
4747
+ if (this.options.reportUnknownTypes) this.reportUnknownTypes();
3993
4748
  } finally {
3994
4749
  setAliasExpander(void 0);
3995
4750
  }
@@ -4152,13 +4907,38 @@ var TypeAnalyzer = class {
4152
4907
  * string. Any other value is simply redeclared: a sourcemap's
4153
4908
  * `declare script: <this file's instance>` replaces the library's
4154
4909
  * `declare script: LuaSourceContainer`. */
4155
- harvestDeclares(block) {
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) {
4156
4929
  for (const stmt of block.statements) {
4157
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
+ }
4158
4935
  const t = this.resolveType(stmt.valueType);
4159
4936
  const prev = this.libGlobalTypes.get(stmt.name);
4160
4937
  const overload = prev && stmt.valueType.type === "FunctionTypeNode" && (prev.kind === "function" || prev.kind === "intersection");
4161
- this.libGlobalTypes.set(stmt.name, overload ? intersection([prev, t]) : t);
4938
+ this.libGlobalTypes.set(
4939
+ stmt.name,
4940
+ overload ? intersection([prev, t]) : this.mergeDeclared(prev, t)
4941
+ );
4162
4942
  }
4163
4943
  }
4164
4944
  resolveAllAliases() {
@@ -4168,14 +4948,154 @@ var TypeAnalyzer = class {
4168
4948
  this.aliases.defer(name, () => this.classType(cls));
4169
4949
  continue;
4170
4950
  }
4171
- if (containsTypeQuery(def.node)) continue;
4951
+ if (this.dependsOnTypeQuery(name)) continue;
4172
4952
  this.withTypeParams(def.params, () => {
4173
4953
  this.aliases.set(name, this.resolveDef(def));
4174
4954
  });
4175
4955
  }
4176
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
+ }
4177
4971
  /** The aliases `resolveAllAliases` left for later, now that every binding
4178
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
+ }
4179
5099
  resolveDeferredAliases() {
4180
5100
  for (const [name, def] of this.aliasDefs) {
4181
5101
  if (this.aliases.has(name)) continue;
@@ -4358,13 +5278,13 @@ var TypeAnalyzer = class {
4358
5278
  type: p.optional ? optional(this.resolveType(p.typeAnnotation)) : this.resolveType(p.typeAnnotation),
4359
5279
  optional: p.optional
4360
5280
  }));
4361
- return fn(
5281
+ return this.withTypeParamDefaults(fn(
4362
5282
  params,
4363
5283
  this.resolveType(node.returnType),
4364
5284
  node.hasVarargs ? node.varargType ? this.resolveType(node.varargType) : anyType : void 0,
4365
5285
  names,
4366
5286
  this.resolvePredicate(node.predicate, params)
4367
- );
5287
+ ), node.generics);
4368
5288
  });
4369
5289
  }
4370
5290
  case "TypeofTypeNode": {
@@ -4610,7 +5530,11 @@ var TypeAnalyzer = class {
4610
5530
  }
4611
5531
  reduceConditional(t) {
4612
5532
  const checkType = this.reduceType(t.checkType);
4613
- if (containsTypeParam(checkType)) return { ...t, checkType };
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
+ }
4614
5538
  if (t.distributeParam && checkType.kind === "union") {
4615
5539
  return union(checkType.types.map((m) => this.branchOf(t, m)));
4616
5540
  }
@@ -4715,15 +5639,22 @@ var TypeAnalyzer = class {
4715
5639
  const source = sources[i];
4716
5640
  if (this.emitDiagnostics && target.type === "IdentifierPattern" && target.typeAnnotation && source) {
4717
5641
  const declared = this.resolveType(target.typeAnnotation);
4718
- if (declared.kind !== "any" && !this.fitsAnnotation(source, declared, inferred, env)) {
5642
+ if (declared.kind !== "any" && !this.namesNothing(declared) && !this.fitsAnnotation(source, declared, inferred, env)) {
4719
5643
  this.diagnostics.push({
4720
5644
  node: stmt,
4721
5645
  message: `Type '${formatType(inferred)}' is not assignable to '${formatType(declared)}'`
4722
5646
  });
5647
+ } else if (declared.kind !== "any") {
5648
+ this.reportExcessProperties(source, declared);
4723
5649
  }
4724
5650
  }
4725
5651
  const mode = this.initIsAsConst(source) ? "asconst" : !isFreshLiteralExpr(source) ? "keep" : stmt.kind === "const" ? "const" : "widen";
4726
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
+ }
4727
5658
  });
4728
5659
  return;
4729
5660
  }
@@ -4731,6 +5662,7 @@ var TypeAnalyzer = class {
4731
5662
  this.checkParamOrder(stmt.func.params, stmt);
4732
5663
  for (const sig of stmt.signatures ?? []) this.checkParamOrder(sig.params, stmt);
4733
5664
  const id = this.bindingIdByName(stmt.name.name, stmt.name);
5665
+ this.paramsFromSignatures(stmt.func, stmt.signatures);
4734
5666
  const fnType = stmt.signatures?.length ? intersection(stmt.signatures.map((s) => this.signatureToFnType(s))) : this.inferFunctionBody(stmt.func, env);
4735
5667
  if (id !== void 0) {
4736
5668
  this.bindingType.set(id, fnType);
@@ -4746,6 +5678,7 @@ var TypeAnalyzer = class {
4746
5678
  const memberName = stmt.target.method?.name ?? (stmt.target.path.length === 1 ? stmt.target.path[0].name : void 0);
4747
5679
  if (memberName === void 0 && stmt.target.path.length === 0) {
4748
5680
  if (targetId !== void 0) {
5681
+ this.paramsFromSignatures(stmt.func, stmt.signatures);
4749
5682
  const fnType = stmt.signatures?.length ? intersection(stmt.signatures.map((s) => this.signatureToFnType(s))) : this.inferFunctionBody(stmt.func, env);
4750
5683
  this.bindingType.set(targetId, fnType);
4751
5684
  this.setBinding(env, targetId, fnType);
@@ -4755,6 +5688,7 @@ var TypeAnalyzer = class {
4755
5688
  }
4756
5689
  const recv = targetId === void 0 ? anyType : this.currentType(targetId, env);
4757
5690
  this.withSelfType(stmt.isMethod ? recv : void 0, () => {
5691
+ this.paramsFromSignatures(stmt.func, stmt.signatures);
4758
5692
  const fnType = stmt.signatures?.length ? intersection(stmt.signatures.map((s) => this.signatureToFnType(s))) : this.inferFunctionBody(stmt.func, env);
4759
5693
  if (memberName !== void 0 && targetId !== void 0) {
4760
5694
  const grown = intersection([
@@ -4786,6 +5720,7 @@ var TypeAnalyzer = class {
4786
5720
  if (target.type === "Identifier") {
4787
5721
  const id = this.bindingIdOf(target);
4788
5722
  if (id !== void 0) {
5723
+ this.uncorrelate(id);
4789
5724
  const next = isFreshLiteralExpr(source) ? widen(vt) : vt;
4790
5725
  if (this.annotated.has(id)) {
4791
5726
  const declared = this.bindingType.get(id);
@@ -4859,16 +5794,40 @@ var TypeAnalyzer = class {
4859
5794
  case "GenericForStatement": {
4860
5795
  const iterTypes = stmt.iterators.map((it) => this.infer(it, env));
4861
5796
  const bodyEnv = forkEnv(env);
4862
- const [keyT, valT] = this.iterationTypes(stmt.iterators[0], iterTypes[0], stmt.variables.length);
4863
- stmt.variables.forEach((v, i) => {
4864
- this.bindPattern(v, i === 0 ? keyT : i === 1 ? valT : unknownType, bodyEnv, "widen");
4865
- });
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
+ }
4866
5812
  this.visitBlock(stmt.body, bodyEnv);
4867
5813
  return;
4868
5814
  }
4869
- case "ReturnStatement":
4870
- for (const arg of stmt.arguments) this.infer(arg, env);
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
+ }
4871
5829
  return;
5830
+ }
4872
5831
  case "ExportStatement":
4873
5832
  this.visitStatement(stmt.declaration, env);
4874
5833
  return;
@@ -5051,6 +6010,7 @@ var TypeAnalyzer = class {
5051
6010
  }
5052
6011
  if (p.typeAnnotation) {
5053
6012
  const t = this.resolveType(p.typeAnnotation);
6013
+ if (p.default) this.applyContext(p.default, t);
5054
6014
  return p.optional ? optional(t) : t;
5055
6015
  }
5056
6016
  if (p.pattern) return this.patternToType(p.pattern, env);
@@ -5069,6 +6029,8 @@ var TypeAnalyzer = class {
5069
6029
  let e = expr;
5070
6030
  while (e.type === "ParenthesizedExpression") e = e.expression;
5071
6031
  if (!expected) return;
6032
+ this.expectedTypeOf.set(expr, expected);
6033
+ this.expectedTypeOf.set(e, expected);
5072
6034
  if (e.type === "ArrayExpression") return this.applyArrayContext(e, expected);
5073
6035
  if (e.type === "TableExpression") return this.applyTableContext(e, expected);
5074
6036
  if (e.type !== "FunctionExpression") return;
@@ -5154,11 +6116,28 @@ var TypeAnalyzer = class {
5154
6116
  }
5155
6117
  return tuple(target.elements.map((el) => el ? leaf(el.value, el.default) : anyType));
5156
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
+ }
5157
6134
  visitFunctionBodyInner(func, outerEnv) {
5158
6135
  const env = forkEnv(outerEnv);
5159
6136
  for (const p of func.params) {
5160
6137
  if (p.pattern) {
5161
- this.bindPattern(p.pattern, this.paramType(p, env), env, "widen");
6138
+ const type = this.paramType(p, env);
6139
+ this.bindPattern(p.pattern, type, env, "widen");
6140
+ this.correlateDestructuring(p.pattern, type, env);
5162
6141
  continue;
5163
6142
  }
5164
6143
  const id = this.bindingIdByName(p.name, p);
@@ -5169,13 +6148,16 @@ var TypeAnalyzer = class {
5169
6148
  if (p.typeAnnotation) this.annotated.add(id);
5170
6149
  }
5171
6150
  }
5172
- this.visitBlock(func.body, env);
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
+ }));
5173
6155
  }
5174
6156
  /** Return type of calling `f` with `argTypes`. For a generic function,
5175
6157
  * infers the type parameters from the arguments and substitutes. */
5176
- callReturn(f, argTypes) {
6158
+ callReturn(f, argTypes, explicit) {
5177
6159
  if (!f.typeParams?.length) return f.returns;
5178
- return this.reduceType(substitute(f.returns, this.inferTypeArgs(f, argTypes)));
6160
+ return this.reduceType(substitute(f.returns, this.inferTypeArgs(f, argTypes, explicit)));
5179
6161
  }
5180
6162
  /** Infer a generic call's type arguments from the argument types.
5181
6163
  *
@@ -5183,16 +6165,47 @@ var TypeAnalyzer = class {
5183
6165
  * `1` — *except* against a parameter whose constraint is made of literal
5184
6166
  * types, where the literal is the whole point. That is what lets
5185
6167
  * `<K extends keyof T>(name: K) -> T[K]` pick out one property. */
5186
- inferTypeArgs(f, argTypes) {
5187
- const vars = new Set(f.typeParams ?? []);
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) {
5188
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)));
5189
6200
  f.params.forEach((p, i) => {
5190
6201
  const arg = argTypes[i];
5191
6202
  if (arg === void 0) return;
5192
6203
  const param = p.type.kind === "typeParam" && p.type.constraint ? { ...p.type, constraint: this.reduceType(p.type.constraint) } : p.type;
5193
6204
  unify(p.type, keepsLiterals(param) ? arg : widen(arg), vars, subst);
5194
6205
  });
5195
- for (const name of f.typeParams ?? []) if (!subst.has(name)) subst.set(name, unknownType);
6206
+ for (const name of f.typeParams ?? []) {
6207
+ if (!subst.has(name)) subst.set(name, f.typeParamDefaults?.[name] ?? unknownType);
6208
+ }
5196
6209
  return subst;
5197
6210
  }
5198
6211
  /** Re-infer the arguments that land on a `<const T>` parameter, keeping
@@ -5223,6 +6236,32 @@ var TypeAnalyzer = class {
5223
6236
  }
5224
6237
  return void 0;
5225
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
+ }
5226
6265
  /** Can this signature be called with these argument types? The signature's
5227
6266
  * own type parameters stand for what the call would infer, so each is
5228
6267
  * checked only against its constraint — `<K extends keyof Services>`
@@ -5258,7 +6297,7 @@ var TypeAnalyzer = class {
5258
6297
  for (const child of Object.values(value)) walk(child);
5259
6298
  };
5260
6299
  for (const p of f.params) if (containsTypeParam(p.type)) walk(p.type);
5261
- return f.params.map((p) => substitute(p.type, bounds));
6300
+ return f.params.map((p) => this.reduceType(substitute(p.type, bounds)));
5262
6301
  }
5263
6302
  /** Record what each written argument is expected to be — see
5264
6303
  * `TypeAnalysis.expectedTypeOf`. */
@@ -5275,6 +6314,28 @@ var TypeAnalyzer = class {
5275
6314
  }
5276
6315
  /** No signature accepts the call, and the argument count is not the
5277
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
+ }
5278
6339
  reportArguments(call, written, fns, argsFor, selfOf) {
5279
6340
  if (!this.emitDiagnostics) return;
5280
6341
  if (fns.length > 1) {
@@ -5344,9 +6405,33 @@ var TypeAnalyzer = class {
5344
6405
  });
5345
6406
  return false;
5346
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
+ }
5347
6428
  signatureToFnType(sig) {
5348
6429
  const names = sig.generics.map((g) => g.name);
5349
- return this.withTypeParams(sig.generics, () => {
6430
+ const record = (type) => {
6431
+ this.typeOfTypeNode.set(sig, type);
6432
+ return type;
6433
+ };
6434
+ return record(this.withTypeParams(sig.generics, () => {
5350
6435
  const params = sig.params.map((p) => ({
5351
6436
  name: p.pattern ? void 0 : p.name,
5352
6437
  type: this.paramType(p, /* @__PURE__ */ new Map()),
@@ -5359,7 +6444,7 @@ var TypeAnalyzer = class {
5359
6444
  names,
5360
6445
  this.resolvePredicate(sig.predicate, params)
5361
6446
  );
5362
- });
6447
+ }));
5363
6448
  }
5364
6449
  /** Turn a parsed `v is T` / `asserts v` annotation into a `TypePredicate`,
5365
6450
  * resolving the named parameter to its index. A guard naming a parameter
@@ -5404,8 +6489,11 @@ var TypeAnalyzer = class {
5404
6489
  } else if (func.predicate) {
5405
6490
  returns = booleanType;
5406
6491
  } else {
5407
- this.preVisitBody(func.body, bodyEnv);
5408
- returns = this.inferReturnType(func.body, bodyEnv);
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
+ }));
5409
6497
  }
5410
6498
  return fn(
5411
6499
  params,
@@ -5416,6 +6504,29 @@ var TypeAnalyzer = class {
5416
6504
  );
5417
6505
  });
5418
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
+ }
5419
6530
  /** Populate binding types for a function body without reporting anything,
5420
6531
  * purely so an un-annotated return type can see its own locals. Bounded:
5421
6532
  * nested functions stop pre-visiting after a couple of levels, since the
@@ -5432,6 +6543,157 @@ var TypeAnalyzer = class {
5432
6543
  this.preVisitDepth--;
5433
6544
  }
5434
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
+ }
5435
6697
  /** `(keyType, valueType)` yielded by a generic-for iterator. Handles
5436
6698
  * `ipairs`/`pairs`/`next(t)` and Luau generalized iteration (`for … in t`).
5437
6699
  * `varCount` is how many loop variables were written. */
@@ -5496,6 +6758,18 @@ var TypeAnalyzer = class {
5496
6758
  if (init.type === "ArrayExpression") return isAssignable(this.inferArray(init, env, true), declared);
5497
6759
  return false;
5498
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
+ }
5499
6773
  /** Fold a destructuring default (`{ a = 1 }`) into the property's type:
5500
6774
  * the default applies when the source value is missing/`nil`. */
5501
6775
  withDefault(base, def, env) {
@@ -5527,7 +6801,7 @@ var TypeAnalyzer = class {
5527
6801
  const pt = key !== void 0 ? this.propertyType(valueType, key) : unknownType;
5528
6802
  this.reassignPattern(p.value, this.withDefault(pt, p.default, env), env);
5529
6803
  }
5530
- if (target.rest) this.reassignPattern(target.rest, valueType, env);
6804
+ if (target.rest) this.reassignPattern(target.rest, this.withoutKeys(valueType, target.properties), env);
5531
6805
  return;
5532
6806
  }
5533
6807
  case "ArrayPattern": {
@@ -5562,7 +6836,7 @@ var TypeAnalyzer = class {
5562
6836
  const propType = key !== void 0 ? this.propertyType(valueType, key) : unknownType;
5563
6837
  this.bindPattern(p.value, this.withDefault(propType, p.default, env), env, mode);
5564
6838
  }
5565
- 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);
5566
6840
  return;
5567
6841
  }
5568
6842
  case "ArrayPattern": {
@@ -5605,7 +6879,7 @@ var TypeAnalyzer = class {
5605
6879
  }
5606
6880
  }
5607
6881
  propertyType(raw, name) {
5608
- const t = this.expand(raw);
6882
+ const t = this.deferredAccess(this.expand(raw));
5609
6883
  if (t.kind === "object") {
5610
6884
  const p = t.properties.get(name);
5611
6885
  if (p) return p.optional ? optional(p.type) : p.type;
@@ -5628,21 +6902,37 @@ var TypeAnalyzer = class {
5628
6902
  const t = this.expand(raw);
5629
6903
  if (t.kind === "any") return anyType;
5630
6904
  if (t.kind === "union") return union(t.types.map((m) => this.indexedType(m, idx)));
5631
- if (t.kind === "difference") return this.indexedType(t.base, idx);
5632
- if (t.kind === "typeParam" && t.constraint) return this.indexedType(t.constraint, idx);
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);
5633
6909
  if (t.kind === "array") return t.element;
5634
6910
  if (t.kind === "tuple") {
5635
- if (idx.kind === "literal" && typeof idx.value === "number") {
5636
- return t.elements[idx.value - 1] ?? unknownType;
6911
+ if (index.kind === "literal" && typeof index.value === "number") {
6912
+ return t.elements[index.value - 1] ?? nilType;
5637
6913
  }
5638
6914
  return union(t.elements);
5639
6915
  }
5640
6916
  if (t.kind === "object") {
5641
- if (idx.kind === "literal" && typeof idx.value === "string") return this.propertyType(t, idx.value);
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 });
5642
6924
  if (t.indexer) return t.indexer.value;
5643
6925
  }
5644
6926
  return unknownType;
5645
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
+ }
5646
6936
  elementType(raw, index) {
5647
6937
  const t = this.expand(raw);
5648
6938
  if (t.kind === "array") return t.element;
@@ -5675,7 +6965,11 @@ var TypeAnalyzer = class {
5675
6965
  for (const part of expr.parts) if (part.kind === "expression") this.infer(part.expression, env);
5676
6966
  return stringType;
5677
6967
  }
6968
+ // `...` holds what the function declared it takes.
5678
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":
5679
6973
  return anyType;
5680
6974
  case "Identifier": {
5681
6975
  const id = this.bindingIdOf(expr);
@@ -5703,12 +6997,20 @@ var TypeAnalyzer = class {
5703
6997
  case "SatisfiesExpression": {
5704
6998
  const declared = this.resolveType(expr.typeAnnotation);
5705
6999
  this.applyContext(expr.expression, declared);
5706
- const actual = this.infer(expr.expression, env);
5707
- if (this.emitDiagnostics && declared.kind !== "any" && !this.fitsAnnotation(expr.expression, declared, actual, env)) {
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)) {
5708
7008
  this.diagnostics.push({
5709
7009
  node: expr,
5710
- message: `Type '${formatType(actual)}' does not satisfy '${formatType(declared)}'`
7010
+ message: `Type '${formatType(actual)}' does not satisfy the expected type '${formatType(declared)}'`
5711
7011
  });
7012
+ } else {
7013
+ this.reportExcessProperties(expr.expression, declared);
5712
7014
  }
5713
7015
  return actual;
5714
7016
  }
@@ -5742,6 +7044,10 @@ var TypeAnalyzer = class {
5742
7044
  }
5743
7045
  const l = this.infer(expr.left, env);
5744
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
+ }
5745
7051
  switch (op) {
5746
7052
  case "..":
5747
7053
  return this.operatorResult(expr, op, l, r) ?? stringType;
@@ -5764,57 +7070,25 @@ var TypeAnalyzer = class {
5764
7070
  return union([l, r]);
5765
7071
  }
5766
7072
  case "MemberExpression": {
5767
- const obj = this.infer(expr.object, env);
7073
+ const { type: obj, shortCircuits } = this.chainObject(expr, expr.object, env);
5768
7074
  const key = this.refKeyOf(expr);
5769
7075
  const narrowed = key === void 0 ? void 0 : env.get(key);
5770
- return narrowed ?? this.propertyType(obj, expr.property.name);
7076
+ return this.chainResult(expr, narrowed ?? this.propertyType(obj, expr.property.name), shortCircuits);
5771
7077
  }
5772
7078
  case "IndexExpression": {
5773
- const obj = this.infer(expr.object, env);
7079
+ const { type: obj, shortCircuits } = this.chainObject(expr, expr.object, env);
5774
7080
  const idx = this.infer(expr.index, env);
5775
7081
  const key = this.refKeyOf(expr);
5776
7082
  const narrowed = key === void 0 ? void 0 : env.get(key);
5777
- return narrowed ?? this.indexedType(obj, idx);
7083
+ return this.chainResult(expr, narrowed ?? this.indexedType(obj, idx), shortCircuits);
5778
7084
  }
5779
7085
  case "CallExpression": {
5780
- const callee = this.infer(expr.callee, env);
5781
- const fns = this.overloadsOf(callee);
5782
- const expected = this.expectedArguments(expr.arguments, fns, () => 0);
5783
- expr.arguments.forEach((a, i) => this.applyContext(a, expected[i]));
5784
- const argTypes = expr.arguments.map((a) => this.infer(a, env));
5785
- if (fns.length) {
5786
- this.recordExpected(expr.arguments, fns, () => 0);
5787
- const arityFits = this.checkArity(expr, fns, argTypes.length, 0);
5788
- const picked = this.pickOverload(fns, argTypes);
5789
- if (picked) {
5790
- return this.callReturn(picked, this.constArgs(picked, expr.arguments, argTypes, env));
5791
- }
5792
- if (arityFits) this.reportArguments(expr, expr.arguments, fns, () => argTypes, () => 0);
5793
- return union(fns.map((f) => this.callReturn(f, argTypes)));
5794
- }
5795
- 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);
5796
7088
  }
5797
7089
  case "MethodCallExpression": {
5798
- const objType = this.infer(expr.object, env);
5799
- const fns = this.overloadsOf(this.propertyType(objType, expr.method.name));
5800
- const expected = this.expectedArguments(expr.arguments, fns, (f) => this.takesSelf(f) ? 1 : 0);
5801
- expr.arguments.forEach((a, i) => this.applyContext(a, expected[i]));
5802
- const argTypes = expr.arguments.map((a) => this.infer(a, env));
5803
- if (fns.length) {
5804
- const withSelf = (f) => this.takesSelf(f) ? [objType, ...argTypes] : argTypes;
5805
- const selfOf = (f) => this.takesSelf(f) ? 1 : 0;
5806
- this.recordExpected(expr.arguments, fns, selfOf);
5807
- const arityFits = this.checkArity(expr, fns, argTypes.length, this.takesSelf(fns[0]) ? 1 : 0);
5808
- const picked = this.pickOverload(fns, argTypes, withSelf);
5809
- if (picked) {
5810
- const self = this.takesSelf(picked) ? 1 : 0;
5811
- const written = this.constArgs(picked, expr.arguments, argTypes, env, self);
5812
- return this.callReturn(picked, this.takesSelf(picked) ? [objType, ...written] : written);
5813
- }
5814
- if (arityFits) this.reportArguments(expr, expr.arguments, fns, withSelf, selfOf);
5815
- return union(fns.map((f) => this.callReturn(f, withSelf(f))));
5816
- }
5817
- 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);
5818
7092
  }
5819
7093
  case "IfElseExpression": {
5820
7094
  const branches = [];
@@ -5830,6 +7104,123 @@ var TypeAnalyzer = class {
5830
7104
  }
5831
7105
  }
5832
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
+ }
5833
7224
  inferArray(expr, env, asConst) {
5834
7225
  const contextual = this.contextualArrays.get(expr);
5835
7226
  if (contextual && !asConst) return contextual;
@@ -5878,6 +7269,123 @@ var TypeAnalyzer = class {
5878
7269
  }
5879
7270
  return objectType(entries, indexer, asConst || void 0);
5880
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
+ }
5881
7389
  inferAsConst(expr, env) {
5882
7390
  switch (expr.type) {
5883
7391
  case "ArrayExpression":
@@ -5935,12 +7443,13 @@ var TypeAnalyzer = class {
5935
7443
  }
5936
7444
  if (cond.type === "CallExpression" || cond.type === "MethodCallExpression") {
5937
7445
  this.narrowByPredicateCall(cond, env, t, f);
5938
- return;
7446
+ } else {
7447
+ this.narrowRef(cond, env, t, f, (cur) => ({
7448
+ yes: narrowTruthy(cur),
7449
+ no: narrowFalsy(cur)
7450
+ }));
5939
7451
  }
5940
- this.narrowRef(cond, env, t, f, (cur) => ({
5941
- yes: narrowTruthy(cur),
5942
- no: narrowFalsy(cur)
5943
- }));
7452
+ this.narrowOptionalLinks(cond, env, t);
5944
7453
  }
5945
7454
  /** `a == b` / `a ~= b`. Handles, in order: a declaration-driven
5946
7455
  * `typeof(x) == "..."` test, a literal/`nil` comparison against a
@@ -5957,11 +7466,14 @@ var TypeAnalyzer = class {
5957
7466
  };
5958
7467
  for (const [ref, other] of [[left, right], [right, left]]) {
5959
7468
  const value = litOf(other);
5960
- if (value === void 0 || this.refKeyOf(ref) === void 0) continue;
5961
- this.narrowRef(ref, env, yes, no, (cur) => ({
5962
- yes: narrowTo(cur, value),
5963
- no: narrowExclude(cur, value)
5964
- }));
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);
5965
7477
  return;
5966
7478
  }
5967
7479
  if (this.refKeyOf(left) !== void 0 && this.refKeyOf(right) !== void 0) {
@@ -6016,11 +7528,16 @@ var TypeAnalyzer = class {
6016
7528
  predicateCallTarget(cond, env) {
6017
7529
  let callee;
6018
7530
  let args;
7531
+ let selfType;
6019
7532
  if (cond.type === "CallExpression") {
6020
- 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);
6021
7534
  args = cond.arguments;
6022
7535
  } else if (cond.type === "MethodCallExpression") {
6023
- const objType = this.typeOf.get(cond.object) ?? this.typeAtRef(cond.object, env);
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
+ }
6024
7541
  callee = this.propertyType(objType, cond.method.name);
6025
7542
  const first = this.overloadsOf(callee)[0];
6026
7543
  args = first && this.takesSelf(first) ? [cond.object, ...cond.arguments] : cond.arguments;
@@ -6028,7 +7545,7 @@ var TypeAnalyzer = class {
6028
7545
  return void 0;
6029
7546
  }
6030
7547
  const overloads = this.overloadsOf(callee);
6031
- 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));
6032
7549
  const picked = this.pickOverload(overloads, argTypes);
6033
7550
  const candidates = picked ? [picked, ...overloads.filter((f) => f !== picked)] : overloads;
6034
7551
  for (const f of candidates) {
@@ -6135,6 +7652,10 @@ var TypeAnalyzer = class {
6135
7652
  const { yes, no } = refine(cur);
6136
7653
  this.setRef(t, key, yes);
6137
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);
6138
7659
  const inner = expr.type === "ParenthesizedExpression" ? expr.expression : expr;
6139
7660
  if (inner.type !== "MemberExpression" && inner.type !== "IndexExpression") return;
6140
7661
  const parentKey = this.refKeyOf(inner.object);
@@ -6142,18 +7663,19 @@ var TypeAnalyzer = class {
6142
7663
  const step = key.slice(parentKey.length);
6143
7664
  if (!step.startsWith(".")) return;
6144
7665
  const prop = step.slice(1);
7666
+ const optional2 = inner.type === "MemberExpression" && inner.optional === true;
6145
7667
  this.narrowRef(inner.object, env, t, f, (parentType) => ({
6146
- yes: this.filterByProperty(parentType, prop, yes),
6147
- no: this.filterByProperty(parentType, prop, no)
7668
+ yes: this.filterByProperty(parentType, prop, yes, optional2),
7669
+ no: this.filterByProperty(parentType, prop, no, optional2)
6148
7670
  }));
6149
7671
  }
6150
7672
  /** Keep the union members of `parent` whose `prop` can still hold `want`.
6151
7673
  * Leaves a non-union (or a union nothing matches) alone: over-narrowing a
6152
7674
  * plain object to `never` because of a property test would be worse than
6153
7675
  * learning nothing. */
6154
- filterByProperty(parent, prop, want) {
7676
+ filterByProperty(parent, prop, want, optional2 = false) {
6155
7677
  if (parent.kind !== "union" || want.kind === "never") return parent;
6156
- 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));
6157
7679
  return kept.length ? union(kept) : parent;
6158
7680
  }
6159
7681
  /** Record a narrowing. Deliberately does *not* discard what is known about
@@ -6165,6 +7687,15 @@ var TypeAnalyzer = class {
6165
7687
  setRef(env, key, t) {
6166
7688
  env.set(key, t);
6167
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
+ }
6168
7699
  /** Drop every narrowing recorded for a path strictly under `key`. */
6169
7700
  invalidateBelow(env, key) {
6170
7701
  for (const k of [...env.keys()]) {
@@ -6177,6 +7708,7 @@ var TypeAnalyzer = class {
6177
7708
  const key = this.refKeyOf(expr);
6178
7709
  if (key === void 0) return;
6179
7710
  this.invalidateBelow(env, key);
7711
+ this.unalias(key);
6180
7712
  env.set(key, value);
6181
7713
  }
6182
7714
  // --------------------------------------------------------
@@ -6257,7 +7789,85 @@ var TypeAnalyzer = class {
6257
7789
  /** The type a binding has *here*: its flow-narrowed type if the current
6258
7790
  * environment has one, else its declared/inferred type. */
6259
7791
  currentType(id, env) {
6260
- 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;
6261
7871
  }
6262
7872
  /** Bind or rebind a whole variable: any narrowing recorded for a path
6263
7873
  * *under* it (`x.a`, `x[1]`) described the old value and must go. */
@@ -6284,12 +7894,28 @@ var TypeAnalyzer = class {
6284
7894
  return this.bindingByDecl.get(node) ?? this.bindingByPos.get(posKey(name, node.line.start, node.column.start));
6285
7895
  }
6286
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
+ }
6287
7912
  function containsTypeQuery(node) {
6288
7913
  if (!node || typeof node !== "object") return false;
6289
7914
  if (Array.isArray(node)) return node.some(containsTypeQuery);
6290
7915
  if (node.type === "TypeofTypeNode") return true;
6291
7916
  return Object.values(node).some(containsTypeQuery);
6292
7917
  }
7918
+ var STRING_INTRINSICS = /* @__PURE__ */ new Set(["Uppercase", "Lowercase", "Capitalize", "Uncapitalize"]);
6293
7919
  function briefType(t) {
6294
7920
  if (t.kind === "union" && t.types.length > 8) {
6295
7921
  const shown = t.types.slice(0, 6).map(formatType).join(" | ");
@@ -6677,18 +8303,22 @@ export {
6677
8303
  Keywords,
6678
8304
  LexError,
6679
8305
  Operators,
8306
+ PRELUDE_SOURCE,
6680
8307
  ParseError,
6681
8308
  Punctuators,
8309
+ UNUSED_EXPECT_ERROR,
6682
8310
  UnaryOperators,
6683
8311
  analyzeScopes,
6684
8312
  analyzeTypes,
6685
8313
  anyType,
8314
+ applyDirectives,
6686
8315
  arrayOf,
6687
8316
  booleanType,
6688
8317
  bufferType,
6689
8318
  containsTypeParam,
6690
8319
  index_default as default,
6691
8320
  difference,
8321
+ directivesOf,
6692
8322
  equalTypes,
6693
8323
  falsyType,
6694
8324
  findConfig,
@@ -6724,6 +8354,7 @@ export {
6724
8354
  parseTokens,
6725
8355
  parseWithRecovery,
6726
8356
  primitive,
8357
+ readDirectives,
6727
8358
  resolveModulePath,
6728
8359
  resolveTypeLibraries,
6729
8360
  setAliasExpander,