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.cjs CHANGED
@@ -25,18 +25,22 @@ __export(index_exports, {
25
25
  Keywords: () => Keywords,
26
26
  LexError: () => LexError,
27
27
  Operators: () => Operators,
28
+ PRELUDE_SOURCE: () => PRELUDE_SOURCE,
28
29
  ParseError: () => ParseError,
29
30
  Punctuators: () => Punctuators,
31
+ UNUSED_EXPECT_ERROR: () => UNUSED_EXPECT_ERROR,
30
32
  UnaryOperators: () => UnaryOperators,
31
33
  analyzeScopes: () => analyzeScopes,
32
34
  analyzeTypes: () => analyzeTypes,
33
35
  anyType: () => anyType,
36
+ applyDirectives: () => applyDirectives,
34
37
  arrayOf: () => arrayOf,
35
38
  booleanType: () => booleanType,
36
39
  bufferType: () => bufferType,
37
40
  containsTypeParam: () => containsTypeParam,
38
41
  default: () => index_default,
39
42
  difference: () => difference,
43
+ directivesOf: () => directivesOf,
40
44
  equalTypes: () => equalTypes,
41
45
  falsyType: () => falsyType,
42
46
  findConfig: () => findConfig,
@@ -72,6 +76,7 @@ __export(index_exports, {
72
76
  parseTokens: () => parseTokens,
73
77
  parseWithRecovery: () => parseWithRecovery,
74
78
  primitive: () => primitive,
79
+ readDirectives: () => readDirectives,
75
80
  resolveModulePath: () => resolveModulePath,
76
81
  resolveTypeLibraries: () => resolveTypeLibraries,
77
82
  setAliasExpander: () => setAliasExpander,
@@ -192,11 +197,17 @@ var LexError = class extends Error {
192
197
  line;
193
198
  column;
194
199
  };
195
- function tokenize(source) {
200
+ function tokenize(source, options = {}) {
201
+ const { errors, comments } = options;
196
202
  const tokens = [];
197
203
  let cursor = 0;
198
204
  let line = 1;
199
205
  let column = 1;
206
+ function fail(message, atLine, atColumn) {
207
+ const error = new LexError(message, atLine, atColumn);
208
+ if (!errors) throw error;
209
+ errors.push(error);
210
+ }
200
211
  function peek(offset = 0) {
201
212
  return source[cursor + offset] ?? "";
202
213
  }
@@ -255,7 +266,8 @@ function tokenize(source) {
255
266
  let content = "";
256
267
  while (true) {
257
268
  if (isAtEnd()) {
258
- throw new LexError("Unterminated long bracket", line, column);
269
+ fail("Unterminated long bracket", line, column);
270
+ return content;
259
271
  }
260
272
  if (peek() === "]") {
261
273
  const save = cursor;
@@ -291,16 +303,21 @@ function tokenize(source) {
291
303
  continue;
292
304
  }
293
305
  if (ch === "-" && peek(1) === "-") {
306
+ const startLine = line;
307
+ const startColumn = column;
294
308
  advance();
295
309
  advance();
296
310
  if (peek() === "[") {
297
311
  const level = tryLongBracketOpen();
298
312
  if (level !== null) {
299
- readLongBracketContent(level);
313
+ const text = readLongBracketContent(level);
314
+ comments?.push({ text, line: startLine, column: startColumn, endLine: line });
300
315
  continue;
301
316
  }
302
317
  }
318
+ const textStart = cursor;
303
319
  skipLineComment();
320
+ comments?.push({ text: source.slice(textStart, cursor).replace(/\r$/, ""), line: startLine, column: startColumn, endLine: startLine });
304
321
  continue;
305
322
  }
306
323
  break;
@@ -411,17 +428,15 @@ function tokenize(source) {
411
428
  const quote = advance();
412
429
  let value = "";
413
430
  while (true) {
414
- if (isAtEnd()) {
415
- throw new LexError("Unterminated string", line, column);
416
- }
417
431
  const ch = peek();
432
+ if (isAtEnd() || ch === "\n") {
433
+ fail("Unterminated string", line, column);
434
+ break;
435
+ }
418
436
  if (ch === quote) {
419
437
  advance();
420
438
  break;
421
439
  }
422
- if (ch === "\n") {
423
- throw new LexError("Unterminated string", line, column);
424
- }
425
440
  if (ch === "\\") {
426
441
  advance();
427
442
  value += readEscapeSequence();
@@ -470,7 +485,9 @@ function tokenize(source) {
470
485
  }
471
486
  while (true) {
472
487
  if (isAtEnd()) {
473
- throw new LexError("Unterminated interpolated string", line, column);
488
+ fail("Unterminated interpolated string", line, column);
489
+ flushString();
490
+ break;
474
491
  }
475
492
  const ch = peek();
476
493
  if (ch === "`") {
@@ -490,10 +507,15 @@ function tokenize(source) {
490
507
  advance();
491
508
  advance();
492
509
  const exprStart = cursor;
510
+ const exprLine = line;
511
+ const exprColumn = column;
493
512
  let depth = 1;
513
+ let closed = true;
494
514
  while (depth > 0) {
495
515
  if (isAtEnd()) {
496
- throw new LexError("Unterminated interpolation expression", line, column);
516
+ fail("Unterminated interpolation expression", line, column);
517
+ closed = false;
518
+ break;
497
519
  }
498
520
  if (peek() === "{") depth++;
499
521
  if (peek() === "}") {
@@ -503,8 +525,12 @@ function tokenize(source) {
503
525
  advance();
504
526
  }
505
527
  const exprRaw = source.slice(exprStart, cursor);
528
+ parts.push({ kind: "expression", raw: exprRaw, line: exprLine, column: exprColumn });
529
+ if (!closed) {
530
+ flushString();
531
+ break;
532
+ }
506
533
  advance();
507
- parts.push({ kind: "expression", raw: exprRaw });
508
534
  continue;
509
535
  }
510
536
  const chStart = cursor;
@@ -573,7 +599,9 @@ function tokenize(source) {
573
599
  };
574
600
  }
575
601
  }
576
- throw new LexError(`Unexpected character '${peek()}'`, line, column);
602
+ fail(`Unexpected character '${peek()}'`, line, column);
603
+ advance();
604
+ return void 0;
577
605
  }
578
606
  while (true) {
579
607
  skipWhitespaceAndComments();
@@ -606,7 +634,8 @@ function tokenize(source) {
606
634
  tokens.push(readIdentifierOrKeyword());
607
635
  continue;
608
636
  }
609
- tokens.push(readOperatorOrPunctuator());
637
+ const symbol = readOperatorOrPunctuator();
638
+ if (symbol) tokens.push(symbol);
610
639
  }
611
640
  tokens.push({
612
641
  type: "EOF",
@@ -616,6 +645,52 @@ function tokenize(source) {
616
645
  return tokens;
617
646
  }
618
647
 
648
+ // src/ast/directives.ts
649
+ var DIRECTIVE = /^\s*@luaut-(nocheck|ignore|expect-error)(?![\w-])/;
650
+ function readDirectives(comments, tokens) {
651
+ const codeLines = [...new Set(tokens.filter((t) => t.type !== "EOF").map((t) => t.line.start))].sort((a, b) => a - b);
652
+ const firstCode = codeLines[0] ?? Infinity;
653
+ const all = [];
654
+ let nocheck = false;
655
+ for (const comment of comments) {
656
+ const match = DIRECTIVE.exec(comment.text);
657
+ if (!match) continue;
658
+ const kind = match[1];
659
+ if (kind === "nocheck") {
660
+ if (comment.line < firstCode) nocheck = true;
661
+ all.push({ kind, line: comment.line, column: comment.column });
662
+ continue;
663
+ }
664
+ const target = codeLines.find((l) => l > comment.endLine);
665
+ all.push({ kind, line: comment.line, column: comment.column, target });
666
+ }
667
+ return { nocheck, all };
668
+ }
669
+ function directivesOf(source) {
670
+ const comments = [];
671
+ const errors = [];
672
+ const tokens = tokenize(source, { errors, comments });
673
+ return readDirectives(comments, tokens);
674
+ }
675
+ function applyDirectives(directives, diagnostics, lineOf) {
676
+ if (directives.nocheck) return { kept: [], unusedExpectErrors: [] };
677
+ const covering = /* @__PURE__ */ new Map();
678
+ for (const d of directives.all) {
679
+ if (d.target === void 0) continue;
680
+ covering.set(d.target, [...covering.get(d.target) ?? [], d]);
681
+ }
682
+ const used = /* @__PURE__ */ new Set();
683
+ const kept = diagnostics.filter((diagnostic) => {
684
+ const on = covering.get(lineOf(diagnostic));
685
+ if (!on) return true;
686
+ for (const d of on) used.add(d);
687
+ return false;
688
+ });
689
+ const unusedExpectErrors = directives.all.filter((d) => d.kind === "expect-error" && !used.has(d));
690
+ return { kept, unusedExpectErrors };
691
+ }
692
+ var UNUSED_EXPECT_ERROR = "Unused '@luaut-expect-error' directive";
693
+
619
694
  // src/ast/builders.ts
620
695
  var ParseError = class extends Error {
621
696
  constructor(message, line, column) {
@@ -634,6 +709,25 @@ function spanFrom(start, end) {
634
709
  column: { start: start.column.start, end: end.column.end }
635
710
  };
636
711
  }
712
+ function shiftSpans(node, line, column) {
713
+ const visit = (value) => {
714
+ if (!value || typeof value !== "object") return;
715
+ if (Array.isArray(value)) {
716
+ for (const item of value) visit(item);
717
+ return;
718
+ }
719
+ const span = value;
720
+ if (span.line && span.column) {
721
+ if (span.column.start !== void 0 && span.line.start === 1) span.column.start += column - 1;
722
+ if (span.column.end !== void 0 && span.line.end === 1) span.column.end += column - 1;
723
+ span.line.start += line - 1;
724
+ span.line.end += line - 1;
725
+ }
726
+ for (const child of Object.values(value)) visit(child);
727
+ };
728
+ visit(node);
729
+ return node;
730
+ }
637
731
  function tokenIdentifier(t) {
638
732
  return { type: "Identifier", name: t.value, ...spanFrom(t, t) };
639
733
  }
@@ -666,15 +760,39 @@ var BINARY_PRECEDENCE = {
666
760
  var RIGHT_ASSOCIATIVE = /* @__PURE__ */ new Set(["..", "^"]);
667
761
  var UNARY_PRECEDENCE = 7;
668
762
  var COMPOUND_ASSIGN_OPS = /* @__PURE__ */ new Set(["+=", "-=", "*=", "/=", "//=", "%=", "^=", "..="]);
763
+ var STATEMENT_KEYWORDS = /* @__PURE__ */ new Set([
764
+ "const",
765
+ "let",
766
+ "while",
767
+ "for",
768
+ "return",
769
+ "do",
770
+ "repeat",
771
+ "break",
772
+ "continue",
773
+ "import",
774
+ "export",
775
+ "end",
776
+ "else",
777
+ "elseif",
778
+ "until",
779
+ "then"
780
+ ]);
669
781
  var Parser = class {
670
782
  tokens;
671
783
  cursor = 0;
672
784
  recover;
785
+ indentation;
673
786
  /** Populated in recovery mode. */
674
787
  errors = [];
788
+ /** Recovery found a block without its `end`. */
789
+ missingEnd = false;
790
+ /** The column of the first token on each line, for `indentation`. */
791
+ lineIndent;
675
792
  constructor(tokens, options = {}) {
676
793
  this.tokens = tokens;
677
794
  this.recover = options.recover ?? false;
795
+ this.indentation = this.recover && (options.indentation ?? false);
678
796
  }
679
797
  current() {
680
798
  return this.tokens[this.cursor];
@@ -715,6 +833,10 @@ var Parser = class {
715
833
  const t = this.current();
716
834
  return (t.type === "Identifier" || t.type === "Keyword") && t.value === value;
717
835
  }
836
+ checkPunctuatorAt(offset, value) {
837
+ const t = this.peek(offset);
838
+ return t.type === "Punctuator" && t.value === value;
839
+ }
718
840
  checkIdentifierValue(value) {
719
841
  const t = this.current();
720
842
  return t.type === "Identifier" && t.value === value;
@@ -760,46 +882,194 @@ var Parser = class {
760
882
  const t = this.current();
761
883
  const err = new ParseError(`${message}, got '${this.describeToken(t)}'`, t.line.start, t.column.start);
762
884
  if (this.recover) {
763
- this.errors.push(err);
885
+ this.record(err);
764
886
  throw new ParseRecover(err.message);
765
887
  }
766
888
  throw err;
767
889
  }
768
- /** Recovery: skip tokens until the start of a plausible next statement (a
769
- * leading keyword / `@` attribute / just past a `;`) or a block
770
- * terminator. Forward progress past a zero-width failure is guaranteed by
771
- * the caller (`parseBlock`). */
772
- synchronize() {
773
- while (!this.isAtEnd()) {
774
- const t = this.current();
775
- if (t.type === "Punctuator" && t.value === "@") return;
776
- if (t.type === "Keyword") {
777
- switch (t.value) {
778
- case "const":
779
- case "let":
780
- case "function":
781
- case "if":
782
- case "while":
783
- case "for":
784
- case "return":
785
- case "do":
786
- case "repeat":
787
- case "break":
788
- case "continue":
789
- case "import":
790
- case "export":
791
- case "end":
792
- case "else":
793
- case "elseif":
794
- case "until":
795
- return;
796
- }
797
- }
890
+ // ============================================================
891
+ // Recovery
892
+ // ============================================================
893
+ //
894
+ // In recovery mode a syntax error costs as little of the tree as it can.
895
+ // A broken expression becomes an `ErrorExpression` where it stood; a broken
896
+ // field, element or argument is skipped up to the next `,`; a missing `)`,
897
+ // `}`, `then`, `do` or `end` is recorded and parsing goes on as if it were
898
+ // there. Only what none of these cover abandons a whole statement.
899
+ /** An error at the position of the one before it is the same problem seen
900
+ * again, and is not recorded twice. */
901
+ record(error) {
902
+ const last = this.errors[this.errors.length - 1];
903
+ if (last && last.line === error.line && last.column === error.column) return;
904
+ this.errors.push(error);
905
+ }
906
+ /** Record an error without abandoning what is being parsed. */
907
+ softError(message) {
908
+ const t = this.current();
909
+ this.record(new ParseError(`${message}, got '${this.describeToken(t)}'`, t.line.start, t.column.start));
910
+ }
911
+ /** `parse()`; in recovery mode, when it fails, skip to where parsing can go
912
+ * on and return `fallback` instead. */
913
+ attempt(parse2, stop, fallback) {
914
+ if (!this.recover) return parse2();
915
+ const from = this.cursor;
916
+ const start = this.current();
917
+ try {
918
+ return parse2();
919
+ } catch (e) {
920
+ if (e instanceof ParseError) this.record(e);
921
+ else if (!(e instanceof ParseRecover)) throw e;
922
+ this.skip(stop, from, "expression");
923
+ return fallback(start, from);
924
+ }
925
+ }
926
+ /** An expression, or an `ErrorExpression` over what could not be parsed. */
927
+ expressionOr(stop) {
928
+ return this.attempt(() => this.parseExpression(), stop, (start, from) => this.errorExpression(start, from));
929
+ }
930
+ expressionListOr(stop) {
931
+ const item = () => this.expressionOr(() => stop() || this.checkPunctuator(","));
932
+ const list = [item()];
933
+ while (this.matchPunctuator(",")) list.push(item());
934
+ return list;
935
+ }
936
+ /** A type annotation, or none when it could not be parsed. */
937
+ typeOr(stop) {
938
+ return this.attempt(() => this.parseType(), stop, () => void 0);
939
+ }
940
+ errorExpression(start, from) {
941
+ if (this.cursor > from) return { type: "ErrorExpression", ...spanFrom(start, this.previous()) };
942
+ return {
943
+ type: "ErrorExpression",
944
+ line: { start: start.line.start, end: start.line.start },
945
+ column: { start: start.column.start, end: start.column.start }
946
+ };
947
+ }
948
+ /** A closing bracket; in recovery mode a missing one is recorded and the
949
+ * construct ends where it is. */
950
+ expectCloser(value) {
951
+ if (this.matchPunctuator(value)) return;
952
+ if (!this.recover) this.error(`Expected '${value}'`);
953
+ this.softError(`Expected '${value}'`);
954
+ }
955
+ /** `then` / `do` / `in`; in recovery mode a missing one is recorded and
956
+ * what follows is read as if it were there. */
957
+ expectKeywordSoft(value) {
958
+ if (this.matchKeyword(value)) return;
959
+ if (!this.recover) this.error(`Expected keyword '${value}'`);
960
+ this.softError(`Expected keyword '${value}'`);
961
+ }
962
+ /** The `end` of the block `opener` began. */
963
+ expectEnd(opener) {
964
+ if (this.checkKeyword("end") && !this.endBelongsOutside(opener)) {
798
965
  this.advance();
799
- const prev = this.previous();
800
- if (prev.type === "Punctuator" && prev.value === ";") return;
966
+ return;
967
+ }
968
+ if (!this.recover) this.error("Expected keyword 'end'");
969
+ this.softError(`Expected 'end' to close '${this.describeToken(opener)}' on line ${opener.line.start}`);
970
+ this.missingEnd = true;
971
+ }
972
+ /** Indentation mode: an `end` indented less than the line that opened the
973
+ * block closes something outside it. */
974
+ endBelongsOutside(opener) {
975
+ if (!this.indentation) return false;
976
+ const t = this.current();
977
+ return t.line.start > opener.line.start && t.column.start < this.indentOf(opener);
978
+ }
979
+ /** Indentation mode: a statement indented no deeper than the line that
980
+ * opened the block is past the block. */
981
+ dedentedPast(opener) {
982
+ if (!this.indentation || !opener) return false;
983
+ const t = this.current();
984
+ return t.line.start > opener.line.start && t.column.start <= this.indentOf(opener);
985
+ }
986
+ indentOf(token) {
987
+ if (!this.lineIndent) {
988
+ this.lineIndent = /* @__PURE__ */ new Map();
989
+ for (const t of this.tokens) {
990
+ if (!this.lineIndent.has(t.line.start)) this.lineIndent.set(t.line.start, t.column.start);
991
+ }
992
+ }
993
+ return this.lineIndent.get(token.line.start) ?? token.column.start;
994
+ }
995
+ /** Is the current token on a later line than the one before it? */
996
+ onNewLine() {
997
+ const previous = this.previous();
998
+ return previous !== void 0 && this.current().line.start > previous.line.end;
999
+ }
1000
+ /** Recovery: move past what could not be parsed.
1001
+ *
1002
+ * Skipping stops at a token `stop` accepts, at a bracket closing something
1003
+ * opened before the skip, or at a keyword that starts a statement. The
1004
+ * tokens from `from` on — including those the failed attempt already
1005
+ * consumed — count towards nesting, so a bracket or a `function ... end`
1006
+ * is skipped whole and an `end` or `}` inside it cannot end what encloses
1007
+ * it. A statement keyword inside brackets but outside any function means a
1008
+ * bracket was never closed, and it stops the skip as well. */
1009
+ skip(stop, from, mode) {
1010
+ const closers = [];
1011
+ for (let i = from; i < this.cursor; i++) this.nest(this.tokens[i], closers, mode);
1012
+ while (!this.isAtEnd()) {
1013
+ if (this.stopsSkip(closers, stop, mode)) return;
1014
+ const t = this.advance();
1015
+ this.nest(t, closers, mode);
1016
+ if (mode === "statement" && closers.length === 0 && t.type === "Punctuator" && t.value === ";") return;
1017
+ }
1018
+ }
1019
+ nest(t, closers, mode) {
1020
+ const value = t.value;
1021
+ const popTo = (closer) => {
1022
+ const at = closers.lastIndexOf(closer);
1023
+ if (at >= 0) closers.length = at;
1024
+ };
1025
+ if (t.type === "Punctuator") {
1026
+ if (value === "(") closers.push(")");
1027
+ else if (value === "[") closers.push("]");
1028
+ else if (value === "{") closers.push("}");
1029
+ else if (value === ")" || value === "]" || value === "}") popTo(value);
1030
+ return;
1031
+ }
1032
+ if (t.type !== "Keyword") return;
1033
+ const inBody = closers.includes("end") || closers.includes("until") || mode === "statement" && closers.length === 0;
1034
+ switch (value) {
1035
+ case "function":
1036
+ closers.push("end");
1037
+ return;
1038
+ case "if":
1039
+ closers.push(inBody ? "end" : "else");
1040
+ return;
1041
+ case "do":
1042
+ if (inBody) closers.push("end");
1043
+ return;
1044
+ case "repeat":
1045
+ if (inBody) closers.push("until");
1046
+ return;
1047
+ case "else":
1048
+ if (closers[closers.length - 1] === "else") closers.pop();
1049
+ return;
1050
+ case "end":
1051
+ popTo("end");
1052
+ return;
1053
+ case "until":
1054
+ popTo("until");
1055
+ return;
801
1056
  }
802
1057
  }
1058
+ stopsSkip(closers, stop, mode) {
1059
+ const t = this.current();
1060
+ const value = t.value;
1061
+ const inFunction = closers.includes("end") || closers.includes("until");
1062
+ if (!inFunction && t.type === "Keyword" && typeof value === "string") {
1063
+ const inIfExpression = closers.includes("else") && (value === "then" || value === "elseif" || value === "else");
1064
+ if (STATEMENT_KEYWORDS.has(value) && !inIfExpression && !(mode === "statement" && value === "then")) return true;
1065
+ if (mode === "statement" && closers.length === 0 && (value === "if" || value === "function" && this.peek(1).type === "Identifier")) return true;
1066
+ }
1067
+ if (closers.length) return false;
1068
+ if (t.type === "Punctuator" && (value === ")" || value === "]" || value === "}")) return true;
1069
+ if (mode === "statement" && t.type === "Punctuator" && value === "@") return true;
1070
+ if (mode === "expression" && t.type === "Punctuator" && value === ";") return true;
1071
+ return stop();
1072
+ }
803
1073
  describeToken(t) {
804
1074
  if (t.type === "EOF") return "<eof>";
805
1075
  if ("value" in t) return String(t.value);
@@ -812,16 +1082,13 @@ var Parser = class {
812
1082
  const start = this.current();
813
1083
  const body = this.parseBlock();
814
1084
  if (!this.isAtEnd()) {
815
- if (this.recover) {
816
- const t = this.current();
817
- this.errors.push(new ParseError(
818
- `Expected end of file, got '${this.describeToken(t)}'`,
819
- t.line.start,
820
- t.column.start
821
- ));
822
- } else {
823
- this.error("Expected end of file");
1085
+ if (!this.recover) this.error("Expected end of file");
1086
+ while (!this.isAtEnd()) {
1087
+ this.softError("Expected end of file");
1088
+ this.advance();
1089
+ body.statements.push(...this.parseBlock().statements);
824
1090
  }
1091
+ Object.assign(body, spanFrom(body, this.previous() ?? start));
825
1092
  }
826
1093
  return { type: "Program", body, ...spanFrom(start, this.previous() ?? start) };
827
1094
  }
@@ -831,11 +1098,14 @@ var Parser = class {
831
1098
  isBlockEnd() {
832
1099
  return this.isAtEnd() || this.checkKeyword("end") || this.checkKeyword("else") || this.checkKeyword("elseif") || this.checkKeyword("until");
833
1100
  }
834
- parseBlock() {
1101
+ /** `opener` is the token that began the block (`if`, `function`, ...), for
1102
+ * indentation recovery. */
1103
+ parseBlock(opener) {
835
1104
  const start = this.current();
836
1105
  const statements = [];
837
1106
  while (!this.isBlockEnd()) {
838
1107
  if (this.matchPunctuator(";")) continue;
1108
+ if (this.dedentedPast(opener)) break;
839
1109
  if (this.recover) {
840
1110
  const at = this.cursor;
841
1111
  const errStart = this.current();
@@ -849,11 +1119,11 @@ var Parser = class {
849
1119
  } catch (e) {
850
1120
  if (e instanceof ParseRecover) {
851
1121
  } else if (e instanceof ParseError) {
852
- this.errors.push(e);
1122
+ this.record(e);
853
1123
  } else {
854
1124
  throw e;
855
1125
  }
856
- this.synchronize();
1126
+ this.skip(() => false, at, "statement");
857
1127
  if (this.cursor === at) {
858
1128
  if (this.isAtEnd()) break;
859
1129
  this.advance();
@@ -931,6 +1201,10 @@ var Parser = class {
931
1201
  }
932
1202
  }
933
1203
  }
1204
+ if (this.recover && t.type === "Identifier" && t.value === "local" && (this.peek(1).type === "Identifier" || this.checkPunctuatorAt(1, "{") || this.checkPunctuatorAt(1, "["))) {
1205
+ this.softError("luaut has no 'local'; declare with 'const' or 'let'");
1206
+ return this.parseVariableDeclaration("let");
1207
+ }
934
1208
  if (t.type === "Identifier" && t.value === "type" && this.peek(1).type === "Identifier") {
935
1209
  return this.parseTypeAliasStatement();
936
1210
  }
@@ -1119,7 +1393,7 @@ var Parser = class {
1119
1393
  return { type: "ExportStatement", declaration, ...spanFrom(start, this.previous()) };
1120
1394
  }
1121
1395
  if (this.checkKeyword("function")) {
1122
- const declaration = this.parseFunctionStatement();
1396
+ const declaration = this.parseFunctionStatement(true);
1123
1397
  if (declaration.type !== "FunctionDeclaration") this.error("An exported function needs a plain name: 'export function name()'");
1124
1398
  return { type: "ExportStatement", declaration, ...spanFrom(start, this.previous()) };
1125
1399
  }
@@ -1150,9 +1424,11 @@ var Parser = class {
1150
1424
  }
1151
1425
  // `const x = ...` / `let x, y = ...`.
1152
1426
  // luaut has no `local` — `const` bindings are immutable, `let` mutable.
1153
- parseVariableDeclaration() {
1427
+ /** `kind` reads the leading word as that keyword (recovery's `local`). */
1428
+ parseVariableDeclaration(as) {
1154
1429
  const start = this.current();
1155
- const kind = this.advance().value;
1430
+ const word = this.advance().value;
1431
+ const kind = as ?? word;
1156
1432
  if (this.checkKeyword("function")) {
1157
1433
  this.error(`A function is declared as 'function name()'; '${kind}' does not apply to functions`);
1158
1434
  }
@@ -1162,9 +1438,10 @@ var Parser = class {
1162
1438
  }
1163
1439
  let init = [];
1164
1440
  if (this.matchOperator("=")) {
1165
- init = this.parseExpressionList();
1441
+ init = this.expressionListOr(() => false);
1166
1442
  } else if (kind === "const") {
1167
- this.error("'const' declaration requires an initializer");
1443
+ if (!this.recover) this.error("'const' declaration requires an initializer");
1444
+ this.softError("'const' declaration requires an initializer");
1168
1445
  }
1169
1446
  return { type: "VariableDeclaration", kind, names, init, ...spanFrom(start, this.previous()) };
1170
1447
  }
@@ -1172,64 +1449,73 @@ var Parser = class {
1172
1449
  const start = this.current();
1173
1450
  this.expectKeyword("if");
1174
1451
  const clauses = [];
1175
- const cond = this.parseExpression();
1176
- this.expectKeyword("then");
1177
- const body = this.parseBlock();
1452
+ const untilThen = () => this.checkKeyword("then");
1453
+ const cond = this.expressionOr(untilThen);
1454
+ this.expectKeywordSoft("then");
1455
+ const body = this.parseBlock(start);
1178
1456
  clauses.push({ type: "IfClause", condition: cond, body, ...spanFrom(cond, this.previous()) });
1179
1457
  while (this.checkKeyword("elseif")) {
1180
1458
  const clauseStart = this.current();
1181
1459
  this.advance();
1182
- const c = this.parseExpression();
1183
- this.expectKeyword("then");
1184
- const b = this.parseBlock();
1460
+ const c = this.expressionOr(untilThen);
1461
+ this.expectKeywordSoft("then");
1462
+ const b = this.parseBlock(start);
1185
1463
  clauses.push({ type: "IfClause", condition: c, body: b, ...spanFrom(clauseStart, this.previous()) });
1186
1464
  }
1187
1465
  let alternate;
1188
1466
  if (this.matchKeyword("else")) {
1189
- alternate = this.parseBlock();
1467
+ alternate = this.parseBlock(start);
1190
1468
  }
1191
- this.expectKeyword("end");
1469
+ this.expectEnd(start);
1192
1470
  return { type: "IfStatement", clauses, alternate, ...spanFrom(start, this.previous()) };
1193
1471
  }
1194
1472
  parseWhileStatement() {
1195
1473
  const start = this.current();
1196
1474
  this.expectKeyword("while");
1197
- const condition = this.parseExpression();
1198
- this.expectKeyword("do");
1199
- const body = this.parseBlock();
1200
- this.expectKeyword("end");
1475
+ const condition = this.expressionOr(() => this.checkKeyword("do"));
1476
+ this.expectKeywordSoft("do");
1477
+ const body = this.parseBlock(start);
1478
+ this.expectEnd(start);
1201
1479
  return { type: "WhileStatement", condition, body, ...spanFrom(start, this.previous()) };
1202
1480
  }
1203
1481
  parseRepeatStatement() {
1204
1482
  const start = this.current();
1205
1483
  this.expectKeyword("repeat");
1206
- const body = this.parseBlock();
1207
- this.expectKeyword("until");
1208
- const condition = this.parseExpression();
1484
+ const body = this.parseBlock(start);
1485
+ let condition;
1486
+ if (this.checkKeyword("until") || !this.recover) {
1487
+ this.expectKeyword("until");
1488
+ condition = this.expressionOr(() => false);
1489
+ } else {
1490
+ this.softError(`Expected 'until' to close 'repeat' on line ${start.line.start}`);
1491
+ this.missingEnd = true;
1492
+ condition = this.errorExpression(this.current(), this.cursor);
1493
+ }
1209
1494
  return { type: "RepeatStatement", body, condition, ...spanFrom(start, this.previous()) };
1210
1495
  }
1211
1496
  parseDoStatement() {
1212
1497
  const start = this.current();
1213
1498
  this.expectKeyword("do");
1214
- const body = this.parseBlock();
1215
- this.expectKeyword("end");
1499
+ const body = this.parseBlock(start);
1500
+ this.expectEnd(start);
1216
1501
  return { type: "DoStatement", body, ...spanFrom(start, this.previous()) };
1217
1502
  }
1218
1503
  parseForStatement() {
1219
1504
  const start = this.current();
1220
1505
  this.expectKeyword("for");
1221
1506
  const first = this.parseBindingTarget(true);
1507
+ const untilDo = () => this.checkKeyword("do");
1222
1508
  if (first.type === "IdentifierPattern" && this.matchOperator("=")) {
1223
- const from = this.parseExpression();
1509
+ const from = this.expressionOr(() => untilDo() || this.checkPunctuator(","));
1224
1510
  this.expectPunctuator(",");
1225
- const to = this.parseExpression();
1511
+ const to = this.expressionOr(() => untilDo() || this.checkPunctuator(","));
1226
1512
  let step;
1227
1513
  if (this.matchPunctuator(",")) {
1228
- step = this.parseExpression();
1514
+ step = this.expressionOr(untilDo);
1229
1515
  }
1230
- this.expectKeyword("do");
1231
- const body2 = this.parseBlock();
1232
- this.expectKeyword("end");
1516
+ this.expectKeywordSoft("do");
1517
+ const body2 = this.parseBlock(start);
1518
+ this.expectEnd(start);
1233
1519
  return {
1234
1520
  type: "NumericForStatement",
1235
1521
  variable: this.identifierPatternToTypedIdentifier(first),
@@ -1245,10 +1531,10 @@ var Parser = class {
1245
1531
  variables.push(this.parseBindingTarget(true));
1246
1532
  }
1247
1533
  this.expectKeyword("in");
1248
- const iterators = this.parseExpressionList();
1249
- this.expectKeyword("do");
1250
- const body = this.parseBlock();
1251
- this.expectKeyword("end");
1534
+ const iterators = this.expressionListOr(untilDo);
1535
+ this.expectKeywordSoft("do");
1536
+ const body = this.parseBlock(start);
1537
+ this.expectEnd(start);
1252
1538
  return {
1253
1539
  type: "GenericForStatement",
1254
1540
  variables,
@@ -1259,28 +1545,36 @@ var Parser = class {
1259
1545
  }
1260
1546
  /** `function name() end` declares `name`; `function a.b() end` and
1261
1547
  * `function T:m() end` define a member. */
1262
- parseFunctionStatement() {
1548
+ /** `exported` — the `export` before this `function` has been consumed, so
1549
+ * each overload signature after it must carry one as well. */
1550
+ parseFunctionStatement(exported = false) {
1263
1551
  const start = this.current();
1264
1552
  this.expectKeyword("function");
1265
1553
  const target = this.parseFunctionName();
1266
1554
  const isMethod = target.method !== void 0;
1267
1555
  const simpleName = !isMethod && target.path.length === 0 ? target.base.name : void 0;
1268
1556
  const signatures = [];
1557
+ let written = target.base;
1269
1558
  while (true) {
1270
1559
  const head = this.parseFunctionHead();
1271
1560
  if (simpleName !== void 0 && this.isOverloadContinuation(simpleName)) {
1272
- signatures.push(this.headToSignature(head));
1561
+ signatures.push({ ...this.headToSignature(head), name: written });
1562
+ const nextExported = this.matchKeyword("export");
1563
+ if (nextExported !== exported) {
1564
+ this.problem("Overload signatures must all be exported or non-exported");
1565
+ }
1273
1566
  this.expectKeyword("function");
1274
- this.parseFunctionName();
1567
+ written = this.parseFunctionName().base;
1275
1568
  continue;
1276
1569
  }
1277
- const func = this.headToBody(head);
1570
+ const func = this.headToBody(head, start);
1278
1571
  if (simpleName !== void 0) {
1279
1572
  return {
1280
1573
  type: "FunctionDeclaration",
1281
1574
  name: target.base,
1282
1575
  func,
1283
1576
  signatures: signatures.length ? signatures : void 0,
1577
+ implementationName: signatures.length ? written : void 0,
1284
1578
  ...spanFrom(start, this.previous())
1285
1579
  };
1286
1580
  }
@@ -1302,7 +1596,18 @@ var Parser = class {
1302
1596
  * declaration for the same simple `name` (making the head an overload
1303
1597
  * signature rather than an implementation)? */
1304
1598
  isOverloadContinuation(name) {
1305
- return this.checkKeyword("function") && this.peek(1).type === "Identifier" && this.peek(1).value === name;
1599
+ const named = (offset) => this.peek(offset).type === "Identifier" && this.peek(offset).value === name;
1600
+ if (this.checkKeyword("function")) return named(1);
1601
+ return this.checkKeyword("export") && this.peek(1).type === "Keyword" && this.peek(1).value === "function" && named(2);
1602
+ }
1603
+ /** A mistake that does not stop the parse: refused outside recovery, where
1604
+ * the compiler must not accept it, and recorded inside. The message says
1605
+ * what is wrong on its own — no token is appended. */
1606
+ problem(message) {
1607
+ const t = this.current();
1608
+ const error = new ParseError(message, t.line.start, t.column.start);
1609
+ if (!this.recover) throw error;
1610
+ this.record(error);
1306
1611
  }
1307
1612
  parseFunctionName() {
1308
1613
  const start = this.current();
@@ -1338,7 +1643,7 @@ var Parser = class {
1338
1643
  this.expectKeyword("return");
1339
1644
  let args = [];
1340
1645
  if (this.isExpressionStart()) {
1341
- args = this.parseExpressionList();
1646
+ args = this.expressionListOr(() => false);
1342
1647
  }
1343
1648
  return { type: "ReturnStatement", arguments: args, ...spanFrom(start, this.previous()) };
1344
1649
  }
@@ -1370,7 +1675,7 @@ var Parser = class {
1370
1675
  targets.push(this.parseAssignTarget());
1371
1676
  }
1372
1677
  this.expectOperator("=");
1373
- const values = this.parseExpressionList();
1678
+ const values = this.expressionListOr(() => false);
1374
1679
  return { type: "AssignmentStatement", targets, values, ...spanFrom(start, this.previous()) };
1375
1680
  }
1376
1681
  const first = this.parsePrefixExpression();
@@ -1379,14 +1684,16 @@ var Parser = class {
1379
1684
  while (this.matchPunctuator(",")) {
1380
1685
  targets.push(this.parseAssignTarget());
1381
1686
  }
1687
+ for (const target of targets) this.rejectOptionalTarget(target);
1382
1688
  this.expectOperator("=");
1383
- const values = this.parseExpressionList();
1689
+ const values = this.expressionListOr(() => false);
1384
1690
  return { type: "AssignmentStatement", targets, values, ...spanFrom(start, this.previous()) };
1385
1691
  }
1386
1692
  const t = this.current();
1387
1693
  if (t.type === "Operator" && COMPOUND_ASSIGN_OPS.has(t.value)) {
1694
+ this.rejectOptionalTarget(first);
1388
1695
  const op = this.advance().value;
1389
- const value = this.parseExpression();
1696
+ const value = this.expressionOr(() => false);
1390
1697
  return {
1391
1698
  type: "CompoundAssignmentStatement",
1392
1699
  operator: op,
@@ -1424,15 +1731,43 @@ var Parser = class {
1424
1731
  * rather than the `:` of a ternary (`cond ? obj : other`)? Lua requires a
1425
1732
  * method call to be called, so the answer is exact rather than heuristic:
1426
1733
  * `:` Identifier followed by one of Lua's call forms. */
1427
- startsMethodCall() {
1428
- if (this.peek(1).type !== "Identifier") return false;
1429
- const after = this.peek(2);
1734
+ startsMethodCall(offset = 0) {
1735
+ if (this.peek(offset + 1).type !== "Identifier") return false;
1736
+ const after = this.peek(offset + 2);
1430
1737
  if (after.type === "Punctuator") {
1431
1738
  const v = String(after.value);
1432
1739
  return v === "(" || v === "{";
1433
1740
  }
1434
1741
  if (after.type === "InterpolatedString") return true;
1435
- return after.type === "Literal" && after.kind === "string";
1742
+ if (after.type === "Literal" && after.kind === "string") return true;
1743
+ if (after.type === "Operator" && String(after.value) === "<") {
1744
+ const save = this.cursor;
1745
+ this.cursor += offset + 2;
1746
+ const found = this.tryCallTypeArguments() !== void 0;
1747
+ this.cursor = save;
1748
+ return found;
1749
+ }
1750
+ return false;
1751
+ }
1752
+ /** Does the next token start right where the current one ends? */
1753
+ touchesNext() {
1754
+ const current = this.current();
1755
+ const next = this.peek(1);
1756
+ return current.line.end === next.line.start && current.column.end === next.column.start;
1757
+ }
1758
+ /** `a?.b = 1` cannot be written: there may be nothing to assign to. */
1759
+ rejectOptionalTarget(target) {
1760
+ for (let e = target; e && typeof e === "object"; ) {
1761
+ const node = e;
1762
+ if (node.optional) {
1763
+ const at = target;
1764
+ const err = new ParseError("An optional chain cannot be assigned to", at.line.start, at.column.start);
1765
+ if (!this.recover) throw err;
1766
+ this.record(err);
1767
+ return;
1768
+ }
1769
+ e = node.type === "MemberExpression" || node.type === "IndexExpression" || node.type === "MethodCallExpression" ? node.object : node.type === "CallExpression" ? node.callee : void 0;
1770
+ }
1436
1771
  }
1437
1772
  isUnaryOperator() {
1438
1773
  const t = this.current();
@@ -1549,7 +1884,7 @@ var Parser = class {
1549
1884
  }
1550
1885
  if (t.type === "Keyword" && t.value === "function") {
1551
1886
  this.advance();
1552
- const func = this.parseFunctionBody();
1887
+ const func = this.parseFunctionBody(t);
1553
1888
  return { type: "FunctionExpression", func, ...spanFrom(t, this.previous()) };
1554
1889
  }
1555
1890
  if (t.type === "Keyword" && t.value === "if") {
@@ -1572,7 +1907,19 @@ var Parser = class {
1572
1907
  if (p.kind === "string") {
1573
1908
  parts.push({ kind: "string", value: p.value, raw: p.raw });
1574
1909
  } else {
1575
- const expression = parseExpressionFromSource(p.raw);
1910
+ let expression;
1911
+ try {
1912
+ expression = shiftSpans(parseExpressionFromSource(p.raw), p.line, p.column);
1913
+ } catch (e) {
1914
+ if (!this.recover || !(e instanceof ParseError || e instanceof LexError)) throw e;
1915
+ const at = token;
1916
+ this.record(new ParseError(
1917
+ `In '\${${p.raw}}': ${e.message.replace(/ \(\d+:\d+\)$/, "")}`,
1918
+ at.line.start,
1919
+ at.column.start
1920
+ ));
1921
+ expression = { type: "ErrorExpression", ...spanFrom(at, at) };
1922
+ }
1576
1923
  parts.push({ kind: "expression", expression });
1577
1924
  }
1578
1925
  }
@@ -1610,7 +1957,53 @@ var Parser = class {
1610
1957
  this.error("Expected identifier or '('");
1611
1958
  }
1612
1959
  while (true) {
1960
+ if (this.checkPunctuator("?") && this.touchesNext()) {
1961
+ const next = this.peek(1);
1962
+ const punct = next.type === "Punctuator" ? String(next.value) : void 0;
1963
+ if (punct === "." && this.peek(2).type === "Identifier") {
1964
+ this.advance();
1965
+ this.advance();
1966
+ const prop = this.parseIdentifier();
1967
+ base = { type: "MemberExpression", object: base, property: prop, optional: true, ...spanFrom(base, prop) };
1968
+ continue;
1969
+ }
1970
+ if (punct === "." && this.punctuatorAt(2, "(")) {
1971
+ this.advance();
1972
+ this.advance();
1973
+ const args = this.parseCallArguments();
1974
+ base = {
1975
+ type: "CallExpression",
1976
+ callee: base,
1977
+ arguments: args,
1978
+ optional: true,
1979
+ ...spanFrom(base, this.previous())
1980
+ };
1981
+ continue;
1982
+ }
1983
+ if (punct === ":" && this.startsMethodCall(1)) {
1984
+ this.advance();
1985
+ this.advance();
1986
+ const method = this.parseIdentifier();
1987
+ const typeArguments = this.tryCallTypeArguments();
1988
+ const args = this.parseCallArguments();
1989
+ base = {
1990
+ type: "MethodCallExpression",
1991
+ object: base,
1992
+ method,
1993
+ arguments: args,
1994
+ typeArguments,
1995
+ optional: true,
1996
+ ...spanFrom(base, this.previous())
1997
+ };
1998
+ continue;
1999
+ }
2000
+ }
1613
2001
  if (this.matchPunctuator(".")) {
2002
+ if (this.recover && !this.checkType("Identifier")) {
2003
+ this.softError("Expected identifier");
2004
+ base = { type: "ErrorExpression", ...spanFrom(base, this.previous()) };
2005
+ break;
2006
+ }
1614
2007
  const prop = this.parseIdentifier();
1615
2008
  base = { type: "MemberExpression", object: base, property: prop, ...spanFrom(base, prop) };
1616
2009
  continue;
@@ -1624,17 +2017,33 @@ var Parser = class {
1624
2017
  if (this.checkPunctuator(":") && this.startsMethodCall()) {
1625
2018
  this.advance();
1626
2019
  const method = this.parseIdentifier();
2020
+ const typeArguments = this.tryCallTypeArguments();
1627
2021
  const args = this.parseCallArguments();
1628
2022
  base = {
1629
2023
  type: "MethodCallExpression",
1630
2024
  object: base,
1631
2025
  method,
1632
2026
  arguments: args,
2027
+ typeArguments,
1633
2028
  ...spanFrom(base, this.previous())
1634
2029
  };
1635
2030
  continue;
1636
2031
  }
1637
- if (this.checkPunctuator("(") || this.checkType("Literal") && this.current().kind === "string" || this.checkType("InterpolatedString") || this.checkPunctuator("{")) {
2032
+ if (this.checkOperator("<")) {
2033
+ const typeArguments = this.tryCallTypeArguments();
2034
+ if (typeArguments) {
2035
+ const args = this.parseCallArguments();
2036
+ base = {
2037
+ type: "CallExpression",
2038
+ callee: base,
2039
+ arguments: args,
2040
+ typeArguments,
2041
+ ...spanFrom(base, this.previous())
2042
+ };
2043
+ continue;
2044
+ }
2045
+ }
2046
+ if (this.startsCallArguments()) {
1638
2047
  const args = this.parseCallArguments();
1639
2048
  base = {
1640
2049
  type: "CallExpression",
@@ -1648,6 +2057,11 @@ var Parser = class {
1648
2057
  }
1649
2058
  return base;
1650
2059
  }
2060
+ /** Is the token `ahead` places on the punctuator `value`? */
2061
+ punctuatorAt(ahead, value) {
2062
+ const token = this.peek(ahead);
2063
+ return token.type === "Punctuator" && token.value === value;
2064
+ }
1651
2065
  /** An assignment target after the first: a prefix expression (`a.b`,
1652
2066
  * `a[i]`, `a`) or a nested destructuring pattern. */
1653
2067
  parseAssignTarget() {
@@ -1655,14 +2069,52 @@ var Parser = class {
1655
2069
  if (this.checkPunctuator("[")) return this.parseArrayPattern();
1656
2070
  return this.parsePrefixExpression();
1657
2071
  }
2072
+ /** Does a call's argument list start here? Lua's three forms: `(`, a
2073
+ * string, or a table. */
2074
+ startsCallArguments() {
2075
+ return this.checkPunctuator("(") || this.checkPunctuator("{") || this.checkType("InterpolatedString") || this.checkType("Literal") && this.current().kind === "string";
2076
+ }
2077
+ /** `f<A, B>(x)` — type arguments, when that is what this is. `a < b > (c)`
2078
+ * is three operators, and only what follows the `>` tells them apart, so
2079
+ * this reads ahead and puts the cursor back when the guess was wrong. */
2080
+ tryCallTypeArguments() {
2081
+ if (!this.checkOperator("<")) return void 0;
2082
+ const start = this.cursor;
2083
+ const errors = this.errors.length;
2084
+ try {
2085
+ this.advance();
2086
+ const list = [this.parseTypeArgument()];
2087
+ while (this.matchPunctuator(",") && !this.checkOperator(">")) list.push(this.parseTypeArgument());
2088
+ this.expectOperator(">");
2089
+ if (!this.startsCallArguments()) throw new ParseRecover("not a call");
2090
+ return list;
2091
+ } catch (e) {
2092
+ if (!(e instanceof ParseError || e instanceof ParseRecover)) throw e;
2093
+ this.cursor = start;
2094
+ this.errors.length = errors;
2095
+ return void 0;
2096
+ }
2097
+ }
1658
2098
  parseCallArguments() {
1659
2099
  if (this.matchPunctuator("(")) {
1660
- if (this.checkPunctuator(")")) {
1661
- this.advance();
1662
- return [];
2100
+ const list = [];
2101
+ const stop = () => this.checkPunctuator(",");
2102
+ if (!this.checkPunctuator(")")) {
2103
+ while (true) {
2104
+ if (this.recover && this.onNewLine() && this.startsTableField() && !this.startsMethodCall(1)) break;
2105
+ const before = this.cursor;
2106
+ const argument = this.expressionOr(stop);
2107
+ if (argument.type !== "ErrorExpression" || this.cursor > before || list.length) list.push(argument);
2108
+ if (this.matchPunctuator(",") && !this.checkPunctuator(")")) continue;
2109
+ if (!this.recover || this.checkPunctuator(")")) break;
2110
+ if (this.onNewLine() && (this.checkType("Identifier") || this.checkType("Keyword"))) break;
2111
+ this.softError("Expected ',' or ')'");
2112
+ this.skip(stop, this.cursor, "expression");
2113
+ if (this.matchPunctuator(",")) continue;
2114
+ break;
2115
+ }
1663
2116
  }
1664
- const list = this.parseExpressionList();
1665
- this.expectPunctuator(")");
2117
+ this.expectCloser(")");
1666
2118
  return list;
1667
2119
  }
1668
2120
  const t = this.current();
@@ -1689,57 +2141,89 @@ var Parser = class {
1689
2141
  const start = this.current();
1690
2142
  this.expectPunctuator("{");
1691
2143
  const fields = [];
2144
+ const stop = () => this.checkPunctuator(",") || this.checkPunctuator(";") || this.onNewLine() && this.startsTableField();
1692
2145
  while (!this.checkPunctuator("}")) {
1693
- if (this.checkOperator("...")) {
1694
- this.advance();
1695
- const argument = this.parseExpression();
1696
- fields.push({ type: "TableFieldSpread", argument });
1697
- } else if (this.matchPunctuator("[")) {
1698
- const key = this.parseExpression();
1699
- this.expectPunctuator("]");
1700
- this.expectPunctuator(":");
1701
- const value = this.parseExpression();
1702
- fields.push({ type: "TableFieldComputed", key, value });
1703
- } else if (this.checkType("Literal") && this.current().kind === "string") {
1704
- const t = this.advance();
1705
- const key = { type: "StringLiteral", value: t.value, raw: t.raw, ...spanFrom(t, t) };
1706
- this.expectPunctuator(":");
1707
- const value = this.parseExpression();
1708
- fields.push({ type: "TableFieldNamed", key, value });
1709
- } else if (this.checkType("Identifier") && this.peek(1).type === "Punctuator" && this.peek(1).value === ":") {
1710
- const key = this.parseIdentifier();
1711
- this.expectPunctuator(":");
1712
- const value = this.parseExpression();
1713
- fields.push({ type: "TableFieldNamed", key, value });
1714
- } else if (this.checkType("Identifier")) {
1715
- const name = this.parseIdentifier();
1716
- fields.push({ type: "TableFieldShorthand", name });
1717
- } else {
1718
- this.error("Expected object field ('key: value', '[expr]: value', shorthand, or '...spread'); use '[...]' for arrays");
2146
+ const field = this.attempt(() => this.parseTableField(stop), stop, () => void 0);
2147
+ if (field) fields.push(field);
2148
+ if (this.matchPunctuator(",") || this.matchPunctuator(";")) continue;
2149
+ if (!this.recover || this.checkPunctuator("}")) break;
2150
+ if (this.onNewLine() && this.startsTableField()) {
2151
+ this.softError("Expected ','");
2152
+ continue;
1719
2153
  }
2154
+ if (this.isAtEnd() || this.onNewLine() && this.checkType("Keyword")) break;
2155
+ this.softError("Expected ',' or '}'");
2156
+ const before = this.cursor;
2157
+ this.skip(stop, this.cursor, "expression");
1720
2158
  if (this.matchPunctuator(",") || this.matchPunctuator(";")) continue;
2159
+ if (this.cursor > before && this.onNewLine() && this.startsTableField()) continue;
1721
2160
  break;
1722
2161
  }
1723
- this.expectPunctuator("}");
2162
+ this.expectCloser("}");
1724
2163
  return { type: "TableExpression", fields, ...spanFrom(start, this.previous()) };
1725
2164
  }
2165
+ /** Does a `key: value` field, or a spread, start here? */
2166
+ startsTableField() {
2167
+ const next = this.peek(1);
2168
+ const colon = next.type === "Punctuator" && next.value === ":";
2169
+ if (this.checkType("Identifier")) return colon;
2170
+ if (this.checkType("Literal") && this.current().kind === "string") return colon;
2171
+ return this.checkOperator("...");
2172
+ }
2173
+ parseTableField(stop) {
2174
+ if (this.checkOperator("...")) {
2175
+ this.advance();
2176
+ return { type: "TableFieldSpread", argument: this.expressionOr(stop) };
2177
+ }
2178
+ if (this.matchPunctuator("[")) {
2179
+ const key = this.expressionOr(() => this.checkPunctuator("]"));
2180
+ this.expectPunctuator("]");
2181
+ this.expectPunctuator(":");
2182
+ return { type: "TableFieldComputed", key, value: this.expressionOr(stop) };
2183
+ }
2184
+ if (this.checkType("Literal") && this.current().kind === "string") {
2185
+ const t = this.advance();
2186
+ const key = { type: "StringLiteral", value: t.value, raw: t.raw, ...spanFrom(t, t) };
2187
+ this.expectPunctuator(":");
2188
+ return { type: "TableFieldNamed", key, value: this.expressionOr(stop) };
2189
+ }
2190
+ if (this.checkType("Identifier") && this.peek(1).type === "Punctuator" && this.peek(1).value === ":") {
2191
+ const key = this.parseIdentifier();
2192
+ this.expectPunctuator(":");
2193
+ return { type: "TableFieldNamed", key, value: this.expressionOr(stop) };
2194
+ }
2195
+ if (this.checkType("Identifier")) {
2196
+ return { type: "TableFieldShorthand", name: this.parseIdentifier() };
2197
+ }
2198
+ this.error("Expected object field ('key: value', '[expr]: value', shorthand, or '...spread'); use '[...]' for arrays");
2199
+ }
1726
2200
  // `[1, 2, 3]` — array literal (trailing comma allowed).
1727
2201
  parseArrayExpression() {
1728
2202
  const start = this.current();
1729
2203
  this.expectPunctuator("[");
1730
2204
  const elements = [];
2205
+ const stop = () => this.checkPunctuator(",");
1731
2206
  while (!this.checkPunctuator("]")) {
1732
2207
  if (this.checkOperator("...")) {
1733
2208
  const dots = this.advance();
1734
- const argument = this.parseExpression();
2209
+ const argument = this.expressionOr(stop);
1735
2210
  elements.push({ type: "SpreadElement", argument, ...spanFrom(dots, argument) });
1736
2211
  } else {
1737
- elements.push(this.parseExpression());
2212
+ elements.push(this.expressionOr(stop));
1738
2213
  }
1739
2214
  if (this.matchPunctuator(",")) continue;
2215
+ if (!this.recover || this.checkPunctuator("]")) break;
2216
+ if (this.onNewLine() && this.isExpressionStart() && !this.checkType("Keyword")) {
2217
+ this.softError("Expected ','");
2218
+ continue;
2219
+ }
2220
+ if (this.isAtEnd() || this.onNewLine() && this.checkType("Keyword")) break;
2221
+ this.softError("Expected ',' or ']'");
2222
+ this.skip(stop, this.cursor, "expression");
2223
+ if (this.matchPunctuator(",")) continue;
1740
2224
  break;
1741
2225
  }
1742
- this.expectPunctuator("]");
2226
+ this.expectCloser("]");
1743
2227
  return { type: "ArrayExpression", elements, ...spanFrom(start, this.previous()) };
1744
2228
  }
1745
2229
  // ============================================================
@@ -1772,7 +2256,7 @@ var Parser = class {
1772
2256
  };
1773
2257
  }
1774
2258
  if (topLevel && this.matchPunctuator(":")) {
1775
- target.typeAnnotation = this.parseType();
2259
+ target.typeAnnotation = this.typeOr(() => this.checkOperator("=") || this.checkPunctuator(","));
1776
2260
  }
1777
2261
  return target;
1778
2262
  }
@@ -1931,12 +2415,13 @@ var Parser = class {
1931
2415
  }
1932
2416
  const optional2 = this.matchPunctuator("?");
1933
2417
  let typeAnnotation;
2418
+ const paramEnd = () => this.checkPunctuator(",");
1934
2419
  if (this.matchPunctuator(":")) {
1935
- typeAnnotation = this.parseType();
2420
+ typeAnnotation = this.typeOr(() => paramEnd() || this.checkOperator("="));
1936
2421
  }
1937
2422
  let def;
1938
2423
  if (this.matchOperator("=")) {
1939
- def = this.parseExpression();
2424
+ def = this.expressionOr(paramEnd);
1940
2425
  }
1941
2426
  params.push({
1942
2427
  type: "FunctionParameter",
@@ -1947,7 +2432,7 @@ var Parser = class {
1947
2432
  optional: optional2 || void 0,
1948
2433
  ...spanFrom(paramStart, this.previous())
1949
2434
  });
1950
- if (this.matchPunctuator(",")) continue;
2435
+ if (this.matchPunctuator(",") && !this.checkPunctuator(")")) continue;
1951
2436
  break;
1952
2437
  }
1953
2438
  }
@@ -1956,7 +2441,9 @@ var Parser = class {
1956
2441
  let predicate;
1957
2442
  if (this.matchPunctuator(":")) {
1958
2443
  predicate = this.tryParseTypePredicate();
1959
- if (!predicate) returnType = this.parseTypeOrTypePackReference();
2444
+ if (!predicate) {
2445
+ returnType = this.attempt(() => this.parseTypeOrTypePackReference(), () => false, () => void 0);
2446
+ }
1960
2447
  }
1961
2448
  return { start, generics, params, hasVarargs, varargTypeAnnotation, returnType, predicate };
1962
2449
  }
@@ -2002,10 +2489,10 @@ var Parser = class {
2002
2489
  }
2003
2490
  return void 0;
2004
2491
  }
2005
- parseFunctionBody() {
2492
+ parseFunctionBody(opener) {
2006
2493
  const head = this.parseFunctionHead();
2007
- const body = this.parseBlock();
2008
- this.expectKeyword("end");
2494
+ const body = this.parseBlock(opener);
2495
+ this.expectEnd(opener);
2009
2496
  return {
2010
2497
  type: "FunctionBody",
2011
2498
  generics: head.generics,
@@ -2030,9 +2517,9 @@ var Parser = class {
2030
2517
  ...spanFrom(head.start, this.previous())
2031
2518
  };
2032
2519
  }
2033
- headToBody(head) {
2034
- const body = this.parseBlock();
2035
- this.expectKeyword("end");
2520
+ headToBody(head, opener) {
2521
+ const body = this.parseBlock(opener);
2522
+ this.expectEnd(opener);
2036
2523
  return {
2037
2524
  type: "FunctionBody",
2038
2525
  generics: head.generics,
@@ -2239,7 +2726,7 @@ var Parser = class {
2239
2726
  this.advance();
2240
2727
  if (!this.checkOperator(">")) {
2241
2728
  typeArguments.push(this.parseTypeArgument());
2242
- while (this.matchPunctuator(",")) {
2729
+ while (this.matchPunctuator(",") && !this.checkOperator(">")) {
2243
2730
  typeArguments.push(this.parseTypeArgument());
2244
2731
  }
2245
2732
  }
@@ -2290,7 +2777,7 @@ var Parser = class {
2290
2777
  optional: optional2 || void 0,
2291
2778
  ...spanFrom(paramStart, this.previous())
2292
2779
  });
2293
- if (this.matchPunctuator(",")) continue;
2780
+ if (this.matchPunctuator(",") && !this.checkPunctuator(")")) continue;
2294
2781
  break;
2295
2782
  }
2296
2783
  }
@@ -2505,7 +2992,7 @@ var Parser = class {
2505
2992
  default: def,
2506
2993
  ...spanFrom(nameTok, this.previous())
2507
2994
  });
2508
- if (this.matchPunctuator(",")) continue;
2995
+ if (this.matchPunctuator(",") && !this.checkOperator(">")) continue;
2509
2996
  break;
2510
2997
  }
2511
2998
  this.expectOperator(">");
@@ -2532,23 +3019,23 @@ function parseExpressionFromSource(raw) {
2532
3019
  return expr;
2533
3020
  }
2534
3021
  function parseWithRecovery(source) {
2535
- let tokens;
2536
- try {
2537
- tokens = tokenize(source);
2538
- } catch (e) {
2539
- const le = e;
2540
- const err = new ParseError(le.message ?? "Lex error", le.line ?? 1, le.column ?? 1);
2541
- const empty = {
2542
- type: "Program",
2543
- body: { type: "Block", statements: [], line: { start: 1, end: 1 }, column: { start: 1, end: 1 } },
2544
- line: { start: 1, end: 1 },
2545
- column: { start: 1, end: 1 }
2546
- };
2547
- return { program: empty, errors: [err] };
2548
- }
2549
- const parser = new Parser(tokens, { recover: true });
2550
- const program = parser.parseProgram();
2551
- return { program, errors: parser.errors };
3022
+ const lexErrors = [];
3023
+ const comments = [];
3024
+ const tokens = tokenize(source, { errors: lexErrors, comments });
3025
+ const lexed = lexErrors.map((e) => new ParseError(e.message.replace(/ \(\d+:\d+\)$/, ""), e.line, e.column));
3026
+ const first = new Parser(tokens, { recover: true });
3027
+ let program = first.parseProgram();
3028
+ let errors = first.errors;
3029
+ if (first.missingEnd) {
3030
+ const second = new Parser(tokens, { recover: true, indentation: true });
3031
+ const reparsed = second.parseProgram();
3032
+ if (second.errors.length <= errors.length) {
3033
+ program = reparsed;
3034
+ errors = second.errors;
3035
+ }
3036
+ }
3037
+ const all = [...lexed, ...errors].sort((a, b) => a.line - b.line || a.column - b.column);
3038
+ return { program, errors: all, directives: readDirectives(comments, tokens) };
2552
3039
  }
2553
3040
 
2554
3041
  // src/ast/nodes.ts
@@ -2587,19 +3074,24 @@ function childScope(parent) {
2587
3074
  return { parent, declarations: /* @__PURE__ */ new Map() };
2588
3075
  }
2589
3076
  var Analyzer = class {
2590
- nextId = 0;
2591
- bindingOf = /* @__PURE__ */ new Map();
2592
- bindings = /* @__PURE__ */ new Map();
2593
- diagnostics = [];
2594
- globalScope = { parent: null, declarations: /* @__PURE__ */ new Map() };
2595
3077
  constructor(options) {
3078
+ this.options = options;
2596
3079
  for (const name of options.builtinGlobals ?? []) {
2597
3080
  const id = this.getOrCreateGlobalBinding(name);
2598
3081
  this.bindings.get(id).isBuiltin = true;
2599
3082
  }
2600
3083
  }
3084
+ options;
3085
+ nextId = 0;
3086
+ bindingOf = /* @__PURE__ */ new Map();
3087
+ bindings = /* @__PURE__ */ new Map();
3088
+ diagnostics = [];
3089
+ globalScope = { parent: null, declarations: /* @__PURE__ */ new Map() };
2601
3090
  run(program) {
2602
- this.visitBlock(program.body, childScope(this.globalScope));
3091
+ this.moduleScope = childScope(this.globalScope);
3092
+ this.visitBlock(program.body, this.moduleScope);
3093
+ this.resolveForwardReferences();
3094
+ if (this.options.reportUndeclared) this.reportUndeclared(program);
2603
3095
  return {
2604
3096
  bindingOf: this.bindingOf,
2605
3097
  bindings: this.bindings,
@@ -2607,6 +3099,99 @@ var Analyzer = class {
2607
3099
  globalsByName: this.globalScope.declarations
2608
3100
  };
2609
3101
  }
3102
+ // ---------------- hoisting ----------------
3103
+ //
3104
+ // As in TypeScript, and as the bundle runs a module:
3105
+ //
3106
+ // - a function declaration is visible to its whole block, before it too;
3107
+ // - a name the module declares at its top level is visible to code that
3108
+ // runs later — function bodies, and `typeof` in a type — even where that
3109
+ // code is written above the declaration. A bundle declares every
3110
+ // top-level name before any of the module runs, so this is what happens.
3111
+ //
3112
+ // A read of a later `const` straight in the module's own flow is not
3113
+ // resolved to it: that still reads what was there before.
3114
+ moduleScope = this.globalScope;
3115
+ /** How many function bodies enclose the walk. */
3116
+ functionDepth = 0;
3117
+ /** Function declarations already declared by their block's hoisting, with
3118
+ * the function depth of that block. */
3119
+ hoisted = /* @__PURE__ */ new Map();
3120
+ /** Names that resolved to a global from code that runs later, with the
3121
+ * scope they were read in. */
3122
+ deferredGlobals = [];
3123
+ hoistFunctions(block, scope) {
3124
+ for (const statement of block.statements) {
3125
+ const declaration = statement.type === "ExportStatement" ? statement.declaration : statement;
3126
+ if (declaration.type !== "FunctionDeclaration") continue;
3127
+ this.declare(scope, declaration.name.name, "local", declaration.name, true, "function");
3128
+ this.hoisted.set(declaration.name, scope === this.moduleScope ? -1 : this.functionDepth);
3129
+ }
3130
+ }
3131
+ /** Inside a function, a function declared further down its block is
3132
+ * hoisted only as a name: code that runs later (another function's body)
3133
+ * can call it, but a call straight in the block before the declaration
3134
+ * finds nothing there yet. At a module's top level the whole function is
3135
+ * hoisted, and this does not apply. */
3136
+ checkUseBeforeDefine(identifier, id) {
3137
+ const binding = this.bindings.get(id);
3138
+ if (binding.declaredBy !== "function" || this.typeQueryDepth > 0) return;
3139
+ const declaration = binding.declarationNode;
3140
+ const depth = declaration && this.hoisted.get(declaration);
3141
+ if (depth === void 0 || depth !== this.functionDepth) return;
3142
+ const before = identifier.line.start < declaration.line.start || identifier.line.start === declaration.line.start && identifier.column.start < declaration.column.start;
3143
+ if (!before) return;
3144
+ this.diagnostics.push({
3145
+ node: identifier,
3146
+ message: `'${binding.name}' is used before its definition: inside a function, a function declared further down is only there once its declaration has run`,
3147
+ kind: "use-before-define"
3148
+ });
3149
+ }
3150
+ noteDeferred(identifier, scope, id, assignment) {
3151
+ if (this.functionDepth === 0 && this.typeQueryDepth === 0) return;
3152
+ if (this.bindings.get(id).kind !== "global") return;
3153
+ this.deferredGlobals.push({ node: identifier, scope, assignment });
3154
+ }
3155
+ /** Point each deferred read of a global at the declaration of that name
3156
+ * that turned up later — in the module, or in any block around the code
3157
+ * that reads it. Such code runs after the declaration has: a closure
3158
+ * written inside a value reads the name the value is bound to. */
3159
+ resolveForwardReferences() {
3160
+ for (const { node, scope, assignment } of this.deferredGlobals) {
3161
+ const localId = this.lookup(scope, node.name);
3162
+ const globalId = this.bindingOf.get(node);
3163
+ if (localId === void 0 || globalId === void 0 || localId === globalId) continue;
3164
+ const global = this.bindings.get(globalId);
3165
+ const at = global.references.indexOf(node);
3166
+ if (at >= 0) global.references.splice(at, 1);
3167
+ if (global.declarationNode === node) global.declarationNode = void 0;
3168
+ if (!global.isBuiltin && !global.references.length && global.declarationNode === void 0) {
3169
+ this.bindings.delete(globalId);
3170
+ this.globalScope.declarations.delete(node.name);
3171
+ }
3172
+ this.bindingOf.set(node, localId);
3173
+ this.bindings.get(localId).references.push(node);
3174
+ if (assignment) this.checkConstAssign(localId, node);
3175
+ else if (this.typeQueryDepth === 0) this.checkTypeOnly(localId, node);
3176
+ }
3177
+ }
3178
+ /** Every read of a global nothing declares. A global assigned somewhere
3179
+ * in the file (`x = 1`) is Lua's implicit global, and is left alone. */
3180
+ reportUndeclared(program) {
3181
+ const declared = /* @__PURE__ */ new Set();
3182
+ for (const statement of program.body.statements) {
3183
+ if (statement.type === "DeclareStatement") declared.add(statement.name);
3184
+ }
3185
+ const found = [];
3186
+ for (const binding of this.bindings.values()) {
3187
+ if (!isUnassignedGlobal(binding) || declared.has(binding.name)) continue;
3188
+ for (const reference of binding.references) {
3189
+ found.push({ node: reference, message: `Cannot find name '${binding.name}'`, kind: "undeclared" });
3190
+ }
3191
+ }
3192
+ found.sort((a, b) => a.node.line.start - b.node.line.start || a.node.column.start - b.node.column.start);
3193
+ this.diagnostics.push(...found);
3194
+ }
2610
3195
  // ---------------- declaration / resolution primitives ----------------
2611
3196
  declare(scope, name, kind, node, isConst = false, declaredBy) {
2612
3197
  if (scope.declarations.has(name) && scope !== this.globalScope) {
@@ -2652,6 +3237,8 @@ var Analyzer = class {
2652
3237
  this.bindingOf.set(identifier, id);
2653
3238
  this.bindings.get(id).references.push(identifier);
2654
3239
  if (this.typeQueryDepth === 0) this.checkTypeOnly(id, identifier);
3240
+ this.checkUseBeforeDefine(identifier, id);
3241
+ this.noteDeferred(identifier, scope, id, false);
2655
3242
  }
2656
3243
  /** Inside `typeof x` in a type, where a type-only import may be named. */
2657
3244
  typeQueryDepth = 0;
@@ -2682,6 +3269,7 @@ var Analyzer = class {
2682
3269
  this.recordPossibleGlobalDefinition(id, identifier);
2683
3270
  this.checkTypeOnly(id, identifier);
2684
3271
  this.checkConstAssign(id, identifier);
3272
+ this.noteDeferred(identifier, scope, id, true);
2685
3273
  }
2686
3274
  /** `Module.x = 1` through `import * as Module`: a module's exports belong
2687
3275
  * to it and are read-only, as in ES modules. Deeper writes (`Module.x.y`)
@@ -2774,6 +3362,7 @@ var Analyzer = class {
2774
3362
  }
2775
3363
  // ---------------- blocks / statements ----------------
2776
3364
  visitBlock(block, scope) {
3365
+ this.hoistFunctions(block, scope);
2777
3366
  for (const stmt of block.statements) this.visitStatement(stmt, scope);
2778
3367
  }
2779
3368
  /** Visits a block in a *fresh child scope* of `scope` — the common case
@@ -2792,7 +3381,11 @@ var Analyzer = class {
2792
3381
  return;
2793
3382
  }
2794
3383
  case "FunctionDeclaration": {
2795
- this.declare(scope, stmt.name.name, "local", stmt.name, true, "function");
3384
+ if (!this.hoisted.has(stmt.name)) this.declare(scope, stmt.name.name, "local", stmt.name, true, "function");
3385
+ for (const signature of stmt.signatures ?? []) {
3386
+ if (signature.name && signature.name !== stmt.name) this.reference(scope, signature.name);
3387
+ }
3388
+ if (stmt.implementationName) this.reference(scope, stmt.implementationName);
2796
3389
  for (const signature of stmt.signatures ?? []) this.visitSignature(signature, scope);
2797
3390
  this.visitFunctionBody(stmt.func, scope);
2798
3391
  return;
@@ -2893,7 +3486,11 @@ var Analyzer = class {
2893
3486
  return;
2894
3487
  case "TypeAliasStatement":
2895
3488
  case "ExportTypeAliasStatement":
2896
- this.visitType(stmt.definition, scope);
3489
+ this.visitGenerics(
3490
+ (stmt.type === "TypeAliasStatement" ? stmt : stmt.alias).generics,
3491
+ scope
3492
+ );
3493
+ this.visitType(stmt.type === "TypeAliasStatement" ? stmt.definition : stmt.alias.definition, scope);
2897
3494
  return;
2898
3495
  case "ImportStatement": {
2899
3496
  const typeOnly = stmt.isTypeOnly ? "type" : void 0;
@@ -2931,6 +3528,7 @@ var Analyzer = class {
2931
3528
  // ---------------- functions ----------------
2932
3529
  visitFunctionBody(func, outerScope, isMethod = false) {
2933
3530
  const fnScope = childScope(outerScope);
3531
+ this.visitGenerics(func.generics, fnScope);
2934
3532
  func.params.forEach((param, i) => {
2935
3533
  const kind = isMethod && i === 0 ? "self" : "param";
2936
3534
  this.visitType(param.typeAnnotation, fnScope);
@@ -2943,14 +3541,28 @@ var Analyzer = class {
2943
3541
  });
2944
3542
  this.visitType(func.varargTypeAnnotation, fnScope);
2945
3543
  this.visitType(func.returnType, fnScope);
2946
- this.visitBlock(func.body, fnScope);
3544
+ this.functionDepth++;
3545
+ try {
3546
+ this.visitBlock(func.body, fnScope);
3547
+ } finally {
3548
+ this.functionDepth--;
3549
+ }
2947
3550
  }
2948
3551
  /** An overload signature: no body and no bindings, but its types can hold
2949
3552
  * a `typeof x`. */
2950
3553
  visitSignature(signature, scope) {
3554
+ this.visitGenerics(signature.generics, scope);
2951
3555
  for (const param of signature.params) this.visitType(param.typeAnnotation, scope);
2952
3556
  this.visitType(signature.returnType, scope);
2953
3557
  }
3558
+ /** `<K extends typeof config>` — a constraint is a type like any other,
3559
+ * and the `typeof` in it reads a value. */
3560
+ visitGenerics(generics, scope) {
3561
+ for (const generic of generics ?? []) {
3562
+ this.visitType(generic.constraint, scope);
3563
+ this.visitType(generic.default, scope);
3564
+ }
3565
+ }
2954
3566
  /** Resolve the value references inside a type. Only `typeof x` has any —
2955
3567
  * everything else in a type names types, which live in their own
2956
3568
  * namespace and are not this pass's business. */
@@ -2988,6 +3600,7 @@ var Analyzer = class {
2988
3600
  case "NumberLiteral":
2989
3601
  case "StringLiteral":
2990
3602
  case "VarargExpression":
3603
+ case "ErrorExpression":
2991
3604
  return;
2992
3605
  case "InterpolatedStringExpression":
2993
3606
  for (const part of expr.parts) {
@@ -3072,6 +3685,37 @@ function analyzeScopes(program, options = {}) {
3072
3685
  return new Analyzer(options).run(program);
3073
3686
  }
3074
3687
 
3688
+ // src/ast/prelude.ts
3689
+ var PRELUDE_SOURCE = `
3690
+ -- In Luau only \`nil\` and \`false\` are falsy: \`0\` and \`""\` are truthy.
3691
+ -- These are what truthiness narrowing computes, made available to write down.
3692
+ type Falsy = nil | false
3693
+ type Truthy<T> = T - Falsy
3694
+
3695
+ -- \`-\` is set difference. Over a union it drops members; over a concrete type
3696
+ -- it simplifies away; over an opaque type (\`unknown\`, an unresolved parameter)
3697
+ -- it is kept, so \`Exclude<unknown, 1>\` stays \`unknown - 1\`.
3698
+ type Exclude<T, U> = T - U
3699
+ type Extract<T, U> = T extends U ? T : never
3700
+ type NonNullable<T> = T - nil
3701
+
3702
+ type ReturnType<T> = T extends (...unknown) -> infer R ? R : never
3703
+ type Parameters<T> = T extends (...infer P) -> unknown ? P : never
3704
+
3705
+ type Partial<T> = { [K in keyof T]?: T[K] }
3706
+ type Required<T> = { [K in keyof T]-?: T[K] }
3707
+ type Readonly<T> = { readonly [K in keyof T]: T[K] }
3708
+ type Mutable<T> = { -readonly [K in keyof T]: T[K] }
3709
+
3710
+ type Pick<T, K> = { [P in K]: T[P] }
3711
+ type Omit<T, K> = Pick<T, Exclude<keyof T, K>>
3712
+ type Record<K, V> = { [P in K]: V }
3713
+ `;
3714
+ var prelude;
3715
+ function preludeProgram() {
3716
+ return prelude ??= parse(PRELUDE_SOURCE);
3717
+ }
3718
+
3075
3719
  // src/ast/typeModel.ts
3076
3720
  function isClassType(t) {
3077
3721
  return t.kind === "object" && t.class !== void 0;
@@ -3147,6 +3791,7 @@ function substitute(t, subst) {
3147
3791
  varargs,
3148
3792
  returns: substitute(t.returns, inner),
3149
3793
  typeParams: t.typeParams,
3794
+ typeParamDefaults: t.typeParamDefaults,
3150
3795
  predicate: t.predicate && {
3151
3796
  ...t.predicate,
3152
3797
  type: t.predicate.type && substitute(t.predicate.type, inner)
@@ -3405,6 +4050,15 @@ function isAssignableInner(a, b) {
3405
4050
  }
3406
4051
  if (!isAssignable(ap.type, bp.type)) return false;
3407
4052
  }
4053
+ if (b.indexer) {
4054
+ for (const [name, ap] of a.properties) {
4055
+ if (b.properties.has(name) || !isAssignable(literal(name), b.indexer.key)) continue;
4056
+ if (!isAssignable(ap.type, b.indexer.value)) return false;
4057
+ }
4058
+ if (a.indexer && isAssignable(a.indexer.key, b.indexer.key) && !isAssignable(a.indexer.value, b.indexer.value)) {
4059
+ return false;
4060
+ }
4061
+ }
3408
4062
  return true;
3409
4063
  }
3410
4064
  if (a.kind === "function") {
@@ -3554,10 +4208,14 @@ function containsFreeTypeParam(t, seen, bound) {
3554
4208
  return containsTypeParam(t.base, seen, bound) || containsTypeParam(t.excluded, seen, bound);
3555
4209
  case "indexedAccess":
3556
4210
  return containsTypeParam(t.objectType, seen, bound) || containsTypeParam(t.indexType, seen, bound);
3557
- case "conditional":
3558
- return containsTypeParam(t.checkType, seen, bound);
3559
- case "mapped":
3560
- return containsTypeParam(t.constraint, seen, bound);
4211
+ case "conditional": {
4212
+ const inner = t.inferVars.length ? /* @__PURE__ */ new Set([...bound, ...t.inferVars]) : bound;
4213
+ return containsTypeParam(t.checkType, seen, bound) || containsTypeParam(t.extendsType, seen, inner) || containsTypeParam(t.trueType, seen, inner) || containsTypeParam(t.falseType, seen, bound);
4214
+ }
4215
+ case "mapped": {
4216
+ const inner = /* @__PURE__ */ new Set([...bound, t.parameter]);
4217
+ return containsTypeParam(t.constraint, seen, bound) || !!t.nameType && containsTypeParam(t.nameType, seen, inner) || containsTypeParam(t.template, seen, inner) || !!t.source && containsTypeParam(t.source, seen, bound);
4218
+ }
3561
4219
  default:
3562
4220
  return false;
3563
4221
  }
@@ -3631,9 +4289,52 @@ function escapeRegExp(s) {
3631
4289
  return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3632
4290
  }
3633
4291
  function overlaps(a, b) {
4292
+ if (a.kind === "union") return a.types.some((m) => overlaps(m, b));
4293
+ if (b.kind === "union") return b.types.some((m) => overlaps(a, m));
3634
4294
  return isAssignable(a, b) || isAssignable(b, a);
3635
4295
  }
3636
4296
  var formatCache = /* @__PURE__ */ new WeakMap();
4297
+ function briefConstraint(t) {
4298
+ if (t.kind === "union" && t.types.length > 8) {
4299
+ return `${t.types.slice(0, 6).map(formatType).join(" | ")} | ... ${t.types.length - 6} more`;
4300
+ }
4301
+ return formatType(t);
4302
+ }
4303
+ function collectTypeParams(t, out, seen = /* @__PURE__ */ new Set()) {
4304
+ if (!t || seen.has(t)) return;
4305
+ seen.add(t);
4306
+ switch (t.kind) {
4307
+ case "typeParam":
4308
+ if (t.constraint && !out.has(t.name)) out.set(t.name, t.constraint);
4309
+ collectTypeParams(t.constraint, out, seen);
4310
+ return;
4311
+ case "array":
4312
+ collectTypeParams(t.element, out, seen);
4313
+ return;
4314
+ case "tuple":
4315
+ for (const e of t.elements) collectTypeParams(e, out, seen);
4316
+ return;
4317
+ case "union":
4318
+ case "intersection":
4319
+ for (const m of t.types) collectTypeParams(m, out, seen);
4320
+ return;
4321
+ case "keyof":
4322
+ collectTypeParams(t.target, out, seen);
4323
+ return;
4324
+ case "indexedAccess":
4325
+ collectTypeParams(t.objectType, out, seen);
4326
+ collectTypeParams(t.indexType, out, seen);
4327
+ return;
4328
+ case "genericRef":
4329
+ for (const a of t.typeArguments) collectTypeParams(a, out, seen);
4330
+ return;
4331
+ case "object":
4332
+ for (const [, p] of t.properties) collectTypeParams(p.type, out, seen);
4333
+ return;
4334
+ default:
4335
+ return;
4336
+ }
4337
+ }
3637
4338
  function formatType(t) {
3638
4339
  const cached = formatCache.get(t);
3639
4340
  if (cached !== void 0) return cached;
@@ -3670,7 +4371,14 @@ function formatTypeUncached(t) {
3670
4371
  const consts = new Set(
3671
4372
  t.params.filter((p) => p.type.kind === "typeParam" && p.type.isConst).map((p) => p.type.name)
3672
4373
  );
3673
- const gen = t.typeParams?.length ? `<${t.typeParams.map((n) => consts.has(n) ? `const ${n}` : n).join(", ")}>` : "";
4374
+ const constraints = /* @__PURE__ */ new Map();
4375
+ for (const part of [...t.params.map((p) => p.type), t.varargs, t.returns]) {
4376
+ collectTypeParams(part, constraints);
4377
+ }
4378
+ const gen = t.typeParams?.length ? `<${t.typeParams.map((n) => {
4379
+ const constraint = constraints.get(n);
4380
+ return `${consts.has(n) ? "const " : ""}${n}${constraint ? ` extends ${briefConstraint(constraint)}` : ""}`;
4381
+ }).join(", ")}>` : "";
3674
4382
  const ps = t.params.map((p) => `${p.name ? p.name + ": " : ""}${formatType(p.type)}`);
3675
4383
  if (t.varargs) ps.push(`...${formatType(t.varargs)}`);
3676
4384
  return `${gen}(${ps.join(", ")}) -> ${formatPredicate(t) ?? formatType(t.returns)}`;
@@ -3886,6 +4594,12 @@ function isFreshLiteralExpr(e) {
3886
4594
  return isFreshLiteralExpr(e.expression);
3887
4595
  case "UnaryExpression":
3888
4596
  return isFreshLiteralExpr(e.argument);
4597
+ // `let n = 5 satisfies number` widens like `let n = 5`. An object or
4598
+ // array has already taken its literals from the contract, and keeps them.
4599
+ case "SatisfiesExpression": {
4600
+ const inner = unwrapParens(e.expression);
4601
+ return inner.type !== "TableExpression" && inner.type !== "ArrayExpression" && isFreshLiteralExpr(inner);
4602
+ }
3889
4603
  default:
3890
4604
  return false;
3891
4605
  }
@@ -3995,6 +4709,48 @@ var AliasMap = class extends Map {
3995
4709
  return this.entries();
3996
4710
  }
3997
4711
  };
4712
+ function unwrapParens(e) {
4713
+ while (e.type === "ParenthesizedExpression") e = e.expression;
4714
+ return e;
4715
+ }
4716
+ function expressionLabel(e, depth = 0) {
4717
+ if (depth > 6) return void 0;
4718
+ const args = (list) => {
4719
+ const parts = list.map((a) => a.type === "StringLiteral" ? JSON.stringify(a.value) : a.type === "NumberLiteral" ? a.raw : a.type === "Identifier" ? a.name : void 0);
4720
+ return parts.every((p) => p !== void 0) && parts.join(", ").length <= 40 ? `(${parts.join(", ")})` : "(...)";
4721
+ };
4722
+ switch (e.type) {
4723
+ case "Identifier":
4724
+ return e.name;
4725
+ case "MemberExpression": {
4726
+ const o = expressionLabel(e.object, depth + 1);
4727
+ return o === void 0 ? void 0 : `${o}${e.optional ? "?." : "."}${e.property.name}`;
4728
+ }
4729
+ case "MethodCallExpression": {
4730
+ const o = expressionLabel(e.object, depth + 1);
4731
+ return o === void 0 ? void 0 : `${o}${e.optional ? "?:" : ":"}${e.method.name}${args(e.arguments)}`;
4732
+ }
4733
+ case "CallExpression": {
4734
+ const o = expressionLabel(e.callee, depth + 1);
4735
+ return o === void 0 ? void 0 : `${o}${e.optional ? "?." : ""}${args(e.arguments)}`;
4736
+ }
4737
+ case "IndexExpression": {
4738
+ const o = expressionLabel(e.object, depth + 1);
4739
+ const i = e.index.type === "StringLiteral" ? JSON.stringify(e.index.value) : e.index.type === "NumberLiteral" ? e.index.raw : e.index.type === "Identifier" ? e.index.name : "...";
4740
+ return o === void 0 ? void 0 : `${o}[${i}]`;
4741
+ }
4742
+ case "ParenthesizedExpression": {
4743
+ const inner = expressionLabel(e.expression, depth + 1);
4744
+ return inner === void 0 ? void 0 : `(${inner})`;
4745
+ }
4746
+ default:
4747
+ return void 0;
4748
+ }
4749
+ }
4750
+ function withoutNil(t) {
4751
+ if (t.kind !== "union") return t.kind === "primitive" && t.name === "nil" ? neverType : t;
4752
+ return union(t.types.filter((m) => !(m.kind === "primitive" && m.name === "nil")));
4753
+ }
3998
4754
  var METAMETHODS = {
3999
4755
  "+": "__add",
4000
4756
  "-": "__sub",
@@ -4068,14 +4824,16 @@ var TypeAnalyzer = class {
4068
4824
  /** Recursion guard for `preVisitBody`. */
4069
4825
  preVisitDepth = 0;
4070
4826
  run() {
4827
+ this.registerAliasDefs(preludeProgram().body);
4071
4828
  for (const lib of this.options.libs ?? []) this.registerAliasDefs(lib.body);
4072
4829
  this.registerAliasDefs(this.program.body);
4073
4830
  for (const lib of this.options.libs ?? []) this.harvestDeclares(lib.body);
4074
- this.harvestDeclares(this.program.body);
4075
4831
  this.registerImportedTypes();
4076
4832
  this.resolveAllAliases();
4833
+ this.harvestDeclares(this.program.body, true);
4077
4834
  this.indexDeclarations();
4078
4835
  for (const [name, id] of this.scopes.globalsByName) {
4836
+ if (this.deferredDeclares.has(name) && !this.options.globalTypes?.[name]) continue;
4079
4837
  const t = this.options.globalTypes?.[name] ?? this.libGlobalTypes.get(name) ?? anyType;
4080
4838
  this.bindingType.set(id, t);
4081
4839
  }
@@ -4083,6 +4841,8 @@ var TypeAnalyzer = class {
4083
4841
  try {
4084
4842
  const env = /* @__PURE__ */ new Map();
4085
4843
  this.visitBlock(this.program.body, env);
4844
+ this.resolveDeferredDeclares();
4845
+ if (this.options.reportUnknownTypes) this.reportUnknownTypes();
4086
4846
  } finally {
4087
4847
  setAliasExpander(void 0);
4088
4848
  }
@@ -4245,13 +5005,38 @@ var TypeAnalyzer = class {
4245
5005
  * string. Any other value is simply redeclared: a sourcemap's
4246
5006
  * `declare script: <this file's instance>` replaces the library's
4247
5007
  * `declare script: LuaSourceContainer`. */
4248
- harvestDeclares(block) {
5008
+ /** Program `declare`s whose type depends on a value's, by name. */
5009
+ deferredDeclares = /* @__PURE__ */ new Map();
5010
+ /** A library that declares a name a second time adds to it rather than
5011
+ * replacing it: `declare table: { find: ... }` on top of Lua's `table`
5012
+ * leaves both members there, the way overloads of a function accumulate.
5013
+ * This is what lets one definitions file build on another's — Luau's on
5014
+ * Lua's, Roblox's on Luau's. A property declared twice takes its later
5015
+ * type. Classes stay as they are: they come from one generated file and
5016
+ * merging them would only blur it. */
5017
+ mergeDeclared(prev, next) {
5018
+ if (!prev || prev.kind !== "object" || next.kind !== "object") return next;
5019
+ if (prev.class || next.class) return next;
5020
+ return objectType(
5021
+ [...prev.properties, ...next.properties],
5022
+ next.indexer ?? prev.indexer,
5023
+ next.frozen ?? prev.frozen
5024
+ );
5025
+ }
5026
+ harvestDeclares(block, own = false) {
4249
5027
  for (const stmt of block.statements) {
4250
5028
  if (stmt.type !== "DeclareStatement") continue;
5029
+ if (own && (containsTypeQuery(stmt.valueType) || referencedTypeNames(stmt.valueType).some((name) => this.dependsOnTypeQuery(name)))) {
5030
+ this.deferredDeclares.set(stmt.name, stmt);
5031
+ continue;
5032
+ }
4251
5033
  const t = this.resolveType(stmt.valueType);
4252
5034
  const prev = this.libGlobalTypes.get(stmt.name);
4253
5035
  const overload = prev && stmt.valueType.type === "FunctionTypeNode" && (prev.kind === "function" || prev.kind === "intersection");
4254
- this.libGlobalTypes.set(stmt.name, overload ? intersection([prev, t]) : t);
5036
+ this.libGlobalTypes.set(
5037
+ stmt.name,
5038
+ overload ? intersection([prev, t]) : this.mergeDeclared(prev, t)
5039
+ );
4255
5040
  }
4256
5041
  }
4257
5042
  resolveAllAliases() {
@@ -4261,14 +5046,154 @@ var TypeAnalyzer = class {
4261
5046
  this.aliases.defer(name, () => this.classType(cls));
4262
5047
  continue;
4263
5048
  }
4264
- if (containsTypeQuery(def.node)) continue;
5049
+ if (this.dependsOnTypeQuery(name)) continue;
4265
5050
  this.withTypeParams(def.params, () => {
4266
5051
  this.aliases.set(name, this.resolveDef(def));
4267
5052
  });
4268
5053
  }
4269
5054
  }
5055
+ typeQueryDependents = /* @__PURE__ */ new Map();
5056
+ /** Does alias `name` contain a `typeof`, itself or through an alias it
5057
+ * names? */
5058
+ dependsOnTypeQuery(name, visiting = /* @__PURE__ */ new Set()) {
5059
+ const known = this.typeQueryDependents.get(name);
5060
+ if (known !== void 0) return known;
5061
+ const def = this.aliasDefs.get(name);
5062
+ if (!def || def.class || visiting.has(name)) return false;
5063
+ visiting.add(name);
5064
+ const result = containsTypeQuery(def.node) || referencedTypeNames(def.node).some((other) => other !== name && this.dependsOnTypeQuery(other, visiting));
5065
+ visiting.delete(name);
5066
+ this.typeQueryDependents.set(name, result);
5067
+ return result;
5068
+ }
4270
5069
  /** The aliases `resolveAllAliases` left for later, now that every binding
4271
5070
  * has its type. */
5071
+ /** Names this file imports. A module that could not be found is reported
5072
+ * as the missing module it is; the names it was to bring are not also
5073
+ * typos. */
5074
+ importedNames() {
5075
+ if (this.imported) return this.imported;
5076
+ this.imported = /* @__PURE__ */ new Set();
5077
+ for (const statement of this.program.body.statements) {
5078
+ if (statement.type !== "ImportStatement") continue;
5079
+ if (statement.defaultImport) this.imported.add(statement.defaultImport.name);
5080
+ if (statement.namespaceImport) this.imported.add(statement.namespaceImport.name);
5081
+ for (const specifier of statement.specifiers) this.imported.add(specifier.local.name);
5082
+ }
5083
+ return this.imported;
5084
+ }
5085
+ imported;
5086
+ /** What a `return` gives, against what the function declared. */
5087
+ checkReturn(stmt, declared, types, sources, env) {
5088
+ if (!declared || !this.emitDiagnostics) return;
5089
+ if (declared.kind === "any" || declared.kind === "unknown" || this.namesNothing(declared)) return;
5090
+ const actual = stmt.arguments.length === 0 ? nilType : types.length === 1 ? types[0] : tuple([...types], true);
5091
+ const source = stmt.arguments.length === 1 ? sources[0] : void 0;
5092
+ const fits = source ? this.fitsAnnotation(source, declared, actual, env) : isAssignable(actual, declared) || isAssignable(widen(actual), declared);
5093
+ if (fits) return;
5094
+ this.diagnostics.push({
5095
+ node: stmt,
5096
+ message: `Type '${formatType(actual)}' is not assignable to '${briefType(declared)}'`
5097
+ });
5098
+ }
5099
+ /** A function that declared what it returns but never does. Only a body
5100
+ * with no `return` at all is reported: anything subtler needs to know
5101
+ * which paths can run off the end, and a wrong guess there is worse than
5102
+ * a missing complaint. */
5103
+ checkReturnsAtAll(func, declared) {
5104
+ if (!declared || !this.emitDiagnostics) return;
5105
+ if (func.predicate) return;
5106
+ if (declared.kind === "any" || declared.kind === "unknown" || declared.kind === "never") return;
5107
+ if (isAssignable(nilType, declared) || this.namesNothing(declared)) return;
5108
+ let found = false;
5109
+ const walk = (statements) => {
5110
+ for (const statement of statements) {
5111
+ if (found) return;
5112
+ if (statement.type === "ReturnStatement") {
5113
+ found = true;
5114
+ return;
5115
+ }
5116
+ for (const value of Object.values(statement)) {
5117
+ if (value && typeof value === "object" && "statements" in value) {
5118
+ walk(value.statements);
5119
+ } else if (Array.isArray(value)) {
5120
+ for (const item of value) {
5121
+ const block = item;
5122
+ if (block?.body?.statements) walk(block.body.statements);
5123
+ }
5124
+ }
5125
+ }
5126
+ }
5127
+ };
5128
+ walk(func.body.statements);
5129
+ if (found) return;
5130
+ this.diagnostics.push({
5131
+ node: func.body,
5132
+ message: `A function that returns '${briefType(declared)}' must return a value`
5133
+ });
5134
+ }
5135
+ /** Does this type rest on a name nothing declares? Such a type says
5136
+ * nothing about what fits it, so checking against it only piles a second
5137
+ * complaint on top of "Cannot find name". */
5138
+ namesNothing(t, seen = /* @__PURE__ */ new Set()) {
5139
+ if (seen.has(t)) return false;
5140
+ seen.add(t);
5141
+ if (t.kind === "genericRef") {
5142
+ return !this.aliasDefs.has(t.name) && !this.importedTypes.has(t.name) && this.options.libTypes?.[t.name] === void 0;
5143
+ }
5144
+ switch (t.kind) {
5145
+ case "union":
5146
+ case "intersection":
5147
+ return t.types.some((m) => this.namesNothing(m, seen));
5148
+ case "array":
5149
+ return this.namesNothing(t.element, seen);
5150
+ case "tuple":
5151
+ return t.elements.some((e) => this.namesNothing(e, seen));
5152
+ case "object":
5153
+ if (t.class) return false;
5154
+ return [...t.properties.values()].some((v) => this.namesNothing(v.type, seen));
5155
+ default:
5156
+ return false;
5157
+ }
5158
+ }
5159
+ /** Every type name in the program that resolved to nothing — a typo, or a
5160
+ * library the config does not load. A name that resolves to a type
5161
+ * parameter, an alias (even one still being resolved), an imported type or
5162
+ * a primitive is fine; what is left is a reference that stayed itself. */
5163
+ reportUnknownTypes() {
5164
+ if (!this.emitDiagnostics) return;
5165
+ const reported = /* @__PURE__ */ new Set();
5166
+ const visit = (node) => {
5167
+ if (!node || typeof node !== "object") return;
5168
+ if (Array.isArray(node)) {
5169
+ for (const item of node) visit(item);
5170
+ return;
5171
+ }
5172
+ const record = node;
5173
+ if (record.type === "TypeReference" && typeof record.base === "string") {
5174
+ const name = typeof record.namespace === "string" ? `${record.namespace}.${record.base}` : record.base;
5175
+ const resolved = this.typeOfTypeNode.get(node);
5176
+ const unresolved = resolved?.kind === "genericRef" && resolved.name === name && !this.aliasDefs.has(name) && !this.importedTypes.has(name) && this.options.libTypes?.[name] === void 0 && !STRING_INTRINSICS.has(name) && !this.importedNames().has(name.split(".")[0]);
5177
+ const at = node;
5178
+ const key = `${at.line.start}:${at.column.start}`;
5179
+ if (unresolved && !reported.has(key)) {
5180
+ reported.add(key);
5181
+ this.diagnostics.push({ node, message: `Cannot find name '${name}'` });
5182
+ }
5183
+ }
5184
+ for (const [key, value] of Object.entries(node)) {
5185
+ if (key !== "line" && key !== "column" && value && typeof value === "object") visit(value);
5186
+ }
5187
+ };
5188
+ visit(this.program.body);
5189
+ }
5190
+ /** Deferred `declare`s nothing used, typed now for tools that ask. */
5191
+ resolveDeferredDeclares() {
5192
+ for (const name of this.deferredDeclares.keys()) {
5193
+ const id = this.scopes.globalsByName.get(name);
5194
+ if (id !== void 0 && !this.bindingType.has(id)) this.bindingType.set(id, this.declaredAhead(id) ?? anyType);
5195
+ }
5196
+ }
4272
5197
  resolveDeferredAliases() {
4273
5198
  for (const [name, def] of this.aliasDefs) {
4274
5199
  if (this.aliases.has(name)) continue;
@@ -4451,13 +5376,13 @@ var TypeAnalyzer = class {
4451
5376
  type: p.optional ? optional(this.resolveType(p.typeAnnotation)) : this.resolveType(p.typeAnnotation),
4452
5377
  optional: p.optional
4453
5378
  }));
4454
- return fn(
5379
+ return this.withTypeParamDefaults(fn(
4455
5380
  params,
4456
5381
  this.resolveType(node.returnType),
4457
5382
  node.hasVarargs ? node.varargType ? this.resolveType(node.varargType) : anyType : void 0,
4458
5383
  names,
4459
5384
  this.resolvePredicate(node.predicate, params)
4460
- );
5385
+ ), node.generics);
4461
5386
  });
4462
5387
  }
4463
5388
  case "TypeofTypeNode": {
@@ -4703,7 +5628,11 @@ var TypeAnalyzer = class {
4703
5628
  }
4704
5629
  reduceConditional(t) {
4705
5630
  const checkType = this.reduceType(t.checkType);
4706
- if (containsTypeParam(checkType)) return { ...t, checkType };
5631
+ const extendsType = this.reduceType(t.extendsType);
5632
+ const free = new Set(t.inferVars);
5633
+ if (containsTypeParam(checkType) || containsTypeParam(extendsType, /* @__PURE__ */ new Set(), free)) {
5634
+ return { ...t, checkType, extendsType };
5635
+ }
4707
5636
  if (t.distributeParam && checkType.kind === "union") {
4708
5637
  return union(checkType.types.map((m) => this.branchOf(t, m)));
4709
5638
  }
@@ -4808,15 +5737,22 @@ var TypeAnalyzer = class {
4808
5737
  const source = sources[i];
4809
5738
  if (this.emitDiagnostics && target.type === "IdentifierPattern" && target.typeAnnotation && source) {
4810
5739
  const declared = this.resolveType(target.typeAnnotation);
4811
- if (declared.kind !== "any" && !this.fitsAnnotation(source, declared, inferred, env)) {
5740
+ if (declared.kind !== "any" && !this.namesNothing(declared) && !this.fitsAnnotation(source, declared, inferred, env)) {
4812
5741
  this.diagnostics.push({
4813
5742
  node: stmt,
4814
5743
  message: `Type '${formatType(inferred)}' is not assignable to '${formatType(declared)}'`
4815
5744
  });
5745
+ } else if (declared.kind !== "any") {
5746
+ this.reportExcessProperties(source, declared);
4816
5747
  }
4817
5748
  }
4818
5749
  const mode = this.initIsAsConst(source) ? "asconst" : !isFreshLiteralExpr(source) ? "keep" : stmt.kind === "const" ? "const" : "widen";
4819
5750
  this.bindPattern(target, inferred, env, mode);
5751
+ if (stmt.kind === "const") {
5752
+ this.correlateDestructuring(target, inferred, env);
5753
+ this.correlateIndexed(target, source, env);
5754
+ this.aliasReference(target, source);
5755
+ }
4820
5756
  });
4821
5757
  return;
4822
5758
  }
@@ -4824,6 +5760,7 @@ var TypeAnalyzer = class {
4824
5760
  this.checkParamOrder(stmt.func.params, stmt);
4825
5761
  for (const sig of stmt.signatures ?? []) this.checkParamOrder(sig.params, stmt);
4826
5762
  const id = this.bindingIdByName(stmt.name.name, stmt.name);
5763
+ this.paramsFromSignatures(stmt.func, stmt.signatures);
4827
5764
  const fnType = stmt.signatures?.length ? intersection(stmt.signatures.map((s) => this.signatureToFnType(s))) : this.inferFunctionBody(stmt.func, env);
4828
5765
  if (id !== void 0) {
4829
5766
  this.bindingType.set(id, fnType);
@@ -4839,6 +5776,7 @@ var TypeAnalyzer = class {
4839
5776
  const memberName = stmt.target.method?.name ?? (stmt.target.path.length === 1 ? stmt.target.path[0].name : void 0);
4840
5777
  if (memberName === void 0 && stmt.target.path.length === 0) {
4841
5778
  if (targetId !== void 0) {
5779
+ this.paramsFromSignatures(stmt.func, stmt.signatures);
4842
5780
  const fnType = stmt.signatures?.length ? intersection(stmt.signatures.map((s) => this.signatureToFnType(s))) : this.inferFunctionBody(stmt.func, env);
4843
5781
  this.bindingType.set(targetId, fnType);
4844
5782
  this.setBinding(env, targetId, fnType);
@@ -4848,6 +5786,7 @@ var TypeAnalyzer = class {
4848
5786
  }
4849
5787
  const recv = targetId === void 0 ? anyType : this.currentType(targetId, env);
4850
5788
  this.withSelfType(stmt.isMethod ? recv : void 0, () => {
5789
+ this.paramsFromSignatures(stmt.func, stmt.signatures);
4851
5790
  const fnType = stmt.signatures?.length ? intersection(stmt.signatures.map((s) => this.signatureToFnType(s))) : this.inferFunctionBody(stmt.func, env);
4852
5791
  if (memberName !== void 0 && targetId !== void 0) {
4853
5792
  const grown = intersection([
@@ -4879,6 +5818,7 @@ var TypeAnalyzer = class {
4879
5818
  if (target.type === "Identifier") {
4880
5819
  const id = this.bindingIdOf(target);
4881
5820
  if (id !== void 0) {
5821
+ this.uncorrelate(id);
4882
5822
  const next = isFreshLiteralExpr(source) ? widen(vt) : vt;
4883
5823
  if (this.annotated.has(id)) {
4884
5824
  const declared = this.bindingType.get(id);
@@ -4952,16 +5892,40 @@ var TypeAnalyzer = class {
4952
5892
  case "GenericForStatement": {
4953
5893
  const iterTypes = stmt.iterators.map((it) => this.infer(it, env));
4954
5894
  const bodyEnv = forkEnv(env);
4955
- const [keyT, valT] = this.iterationTypes(stmt.iterators[0], iterTypes[0], stmt.variables.length);
4956
- stmt.variables.forEach((v, i) => {
4957
- this.bindPattern(v, i === 0 ? keyT : i === 1 ? valT : unknownType, bodyEnv, "widen");
4958
- });
5895
+ const rows = stmt.variables.length >= 2 ? this.iterationRows(stmt.iterators[0], iterTypes[0]) : void 0;
5896
+ if (rows) {
5897
+ const [key, value] = stmt.variables;
5898
+ this.bindPattern(key, union(rows.map((r) => r[0])), bodyEnv, "keep");
5899
+ this.bindPattern(value, union(rows.map((r) => r[1])), bodyEnv, "keep");
5900
+ stmt.variables.slice(2).forEach((v) => this.bindPattern(v, unknownType, bodyEnv, "widen"));
5901
+ const keyId = key.type === "IdentifierPattern" ? this.bindingIdByName(key.name, key) : void 0;
5902
+ const valueId = value.type === "IdentifierPattern" ? this.bindingIdByName(value.name, value) : void 0;
5903
+ if (keyId !== void 0 && valueId !== void 0) this.correlateBindings(bodyEnv, [keyId, valueId], rows);
5904
+ } else {
5905
+ const [keyT, valT] = this.iterationTypes(stmt.iterators[0], iterTypes[0], stmt.variables.length);
5906
+ stmt.variables.forEach((v, i) => {
5907
+ this.bindPattern(v, i === 0 ? keyT : i === 1 ? valT : unknownType, bodyEnv, "widen");
5908
+ });
5909
+ }
4959
5910
  this.visitBlock(stmt.body, bodyEnv);
4960
5911
  return;
4961
5912
  }
4962
- case "ReturnStatement":
4963
- for (const arg of stmt.arguments) this.infer(arg, env);
5913
+ case "ReturnStatement": {
5914
+ const declared = this.declaredReturns[this.declaredReturns.length - 1];
5915
+ if (declared) {
5916
+ if (stmt.arguments.length === 1) {
5917
+ this.applyContext(stmt.arguments[0], declared);
5918
+ } else if (declared.kind === "tuple" && declared.isPack) {
5919
+ stmt.arguments.forEach((a, i) => this.applyContext(a, declared.elements[i]));
5920
+ }
5921
+ }
5922
+ const { types, sources } = this.valueList(stmt.arguments, env);
5923
+ this.checkReturn(stmt, declared, types, sources, env);
5924
+ if (this.returnTypes) {
5925
+ this.returnTypes.push(stmt.arguments.length === 0 ? nilType : types.length === 1 ? types[0] : tuple([...types], true));
5926
+ }
4964
5927
  return;
5928
+ }
4965
5929
  case "ExportStatement":
4966
5930
  this.visitStatement(stmt.declaration, env);
4967
5931
  return;
@@ -5144,6 +6108,7 @@ var TypeAnalyzer = class {
5144
6108
  }
5145
6109
  if (p.typeAnnotation) {
5146
6110
  const t = this.resolveType(p.typeAnnotation);
6111
+ if (p.default) this.applyContext(p.default, t);
5147
6112
  return p.optional ? optional(t) : t;
5148
6113
  }
5149
6114
  if (p.pattern) return this.patternToType(p.pattern, env);
@@ -5162,6 +6127,8 @@ var TypeAnalyzer = class {
5162
6127
  let e = expr;
5163
6128
  while (e.type === "ParenthesizedExpression") e = e.expression;
5164
6129
  if (!expected) return;
6130
+ this.expectedTypeOf.set(expr, expected);
6131
+ this.expectedTypeOf.set(e, expected);
5165
6132
  if (e.type === "ArrayExpression") return this.applyArrayContext(e, expected);
5166
6133
  if (e.type === "TableExpression") return this.applyTableContext(e, expected);
5167
6134
  if (e.type !== "FunctionExpression") return;
@@ -5247,11 +6214,28 @@ var TypeAnalyzer = class {
5247
6214
  }
5248
6215
  return tuple(target.elements.map((el) => el ? leaf(el.value, el.default) : anyType));
5249
6216
  }
6217
+ /** The type of `...` in each function body being walked. */
6218
+ varargs = [];
6219
+ /** What each function body being walked declared it returns. */
6220
+ declaredReturns = [];
6221
+ /** Run `body` with `...` and `return` as `func` declares them. */
6222
+ withVarargs(func, body) {
6223
+ this.varargs.push(func.hasVarargs ? func.varargTypeAnnotation ? this.resolveType(func.varargTypeAnnotation) : anyType : void 0);
6224
+ this.declaredReturns.push(func.predicate ? booleanType : func.returnType ? this.resolveType(func.returnType) : void 0);
6225
+ try {
6226
+ return body();
6227
+ } finally {
6228
+ this.varargs.pop();
6229
+ this.declaredReturns.pop();
6230
+ }
6231
+ }
5250
6232
  visitFunctionBodyInner(func, outerEnv) {
5251
6233
  const env = forkEnv(outerEnv);
5252
6234
  for (const p of func.params) {
5253
6235
  if (p.pattern) {
5254
- this.bindPattern(p.pattern, this.paramType(p, env), env, "widen");
6236
+ const type = this.paramType(p, env);
6237
+ this.bindPattern(p.pattern, type, env, "widen");
6238
+ this.correlateDestructuring(p.pattern, type, env);
5255
6239
  continue;
5256
6240
  }
5257
6241
  const id = this.bindingIdByName(p.name, p);
@@ -5262,13 +6246,16 @@ var TypeAnalyzer = class {
5262
6246
  if (p.typeAnnotation) this.annotated.add(id);
5263
6247
  }
5264
6248
  }
5265
- this.visitBlock(func.body, env);
6249
+ this.withVarargs(func, () => this.collectReturns(void 0, () => {
6250
+ this.visitBlock(func.body, env);
6251
+ this.checkReturnsAtAll(func, this.declaredReturns[this.declaredReturns.length - 1]);
6252
+ }));
5266
6253
  }
5267
6254
  /** Return type of calling `f` with `argTypes`. For a generic function,
5268
6255
  * infers the type parameters from the arguments and substitutes. */
5269
- callReturn(f, argTypes) {
6256
+ callReturn(f, argTypes, explicit) {
5270
6257
  if (!f.typeParams?.length) return f.returns;
5271
- return this.reduceType(substitute(f.returns, this.inferTypeArgs(f, argTypes)));
6258
+ return this.reduceType(substitute(f.returns, this.inferTypeArgs(f, argTypes, explicit)));
5272
6259
  }
5273
6260
  /** Infer a generic call's type arguments from the argument types.
5274
6261
  *
@@ -5276,16 +6263,47 @@ var TypeAnalyzer = class {
5276
6263
  * `1` — *except* against a parameter whose constraint is made of literal
5277
6264
  * types, where the literal is the whole point. That is what lets
5278
6265
  * `<K extends keyof T>(name: K) -> T[K]` pick out one property. */
5279
- inferTypeArgs(f, argTypes) {
5280
- const vars = new Set(f.typeParams ?? []);
6266
+ /** The type arguments a call writes out, checked for count. */
6267
+ explicitTypeArguments(expr, fns) {
6268
+ const written = expr.typeArguments;
6269
+ if (!written?.length) return void 0;
6270
+ const resolved = written.map((node) => this.resolveType(node));
6271
+ const most = Math.max(0, ...fns.map((f) => f.typeParams?.length ?? 0));
6272
+ if (this.emitDiagnostics && resolved.length > most) {
6273
+ this.diagnostics.push({
6274
+ node: written[most],
6275
+ message: most === 0 ? "This call takes no type arguments" : `Expected ${most} type argument${most === 1 ? "" : "s"}, got ${resolved.length}`
6276
+ });
6277
+ }
6278
+ return resolved;
6279
+ }
6280
+ /** `<T = Instance>`: what a call falls back to for a parameter it neither
6281
+ * is given nor can infer. */
6282
+ withTypeParamDefaults(type, generics) {
6283
+ if (type.kind !== "function") return type;
6284
+ const defaults = {};
6285
+ for (const generic of generics) {
6286
+ if (generic.default && !generic.isPack) defaults[generic.name] = this.resolveType(generic.default);
6287
+ }
6288
+ return Object.keys(defaults).length ? { ...type, typeParamDefaults: defaults } : type;
6289
+ }
6290
+ inferTypeArgs(f, argTypes, explicit) {
5281
6291
  const subst = /* @__PURE__ */ new Map();
6292
+ if (explicit?.length) {
6293
+ (f.typeParams ?? []).forEach((name, i) => {
6294
+ if (explicit[i]) subst.set(name, explicit[i]);
6295
+ });
6296
+ }
6297
+ const vars = new Set((f.typeParams ?? []).filter((name) => !subst.has(name)));
5282
6298
  f.params.forEach((p, i) => {
5283
6299
  const arg = argTypes[i];
5284
6300
  if (arg === void 0) return;
5285
6301
  const param = p.type.kind === "typeParam" && p.type.constraint ? { ...p.type, constraint: this.reduceType(p.type.constraint) } : p.type;
5286
6302
  unify(p.type, keepsLiterals(param) ? arg : widen(arg), vars, subst);
5287
6303
  });
5288
- for (const name of f.typeParams ?? []) if (!subst.has(name)) subst.set(name, unknownType);
6304
+ for (const name of f.typeParams ?? []) {
6305
+ if (!subst.has(name)) subst.set(name, f.typeParamDefaults?.[name] ?? unknownType);
6306
+ }
5289
6307
  return subst;
5290
6308
  }
5291
6309
  /** Re-infer the arguments that land on a `<const T>` parameter, keeping
@@ -5316,6 +6334,32 @@ var TypeAnalyzer = class {
5316
6334
  }
5317
6335
  return void 0;
5318
6336
  }
6337
+ /** An overload set called with a union argument, one member at a time.
6338
+ *
6339
+ * One signature for the whole union is often only the catch-all:
6340
+ * `typeof(v)` with `v: Part | nil` accepts nothing more specific than
6341
+ * `typeof<T>(value: T): string`. Each member on its own picks `"Instance"`
6342
+ * and `"nil"`, and that union is what the call returns — whenever every
6343
+ * member picks a signature listed ahead of the whole union's. Otherwise
6344
+ * (a signature taking the union as it is, or a member nothing accepts)
6345
+ * this returns `undefined` and the ordinary pick stands. */
6346
+ distributedReturn(fns, argTypes, picked, argsFor) {
6347
+ if (fns.length < 2) return void 0;
6348
+ const position = argTypes.findIndex((t) => this.expand(t).kind === "union");
6349
+ if (position < 0) return void 0;
6350
+ const members = this.expand(argTypes[position]).types;
6351
+ if (members.length > 32) return void 0;
6352
+ const rank = (f) => ((f.typeParams?.length ?? 0) > 0 ? fns.length : 0) + fns.indexOf(f);
6353
+ const limit = picked ? rank(picked) : Infinity;
6354
+ const results = [];
6355
+ for (const member of members) {
6356
+ const args = argTypes.map((t, i) => i === position ? member : t);
6357
+ const chosen = this.pickOverload(fns, args, (f) => argsFor(f, args));
6358
+ if (!chosen || rank(chosen) >= limit) return void 0;
6359
+ results.push(this.callReturn(chosen, argsFor(chosen, args)));
6360
+ }
6361
+ return union(results);
6362
+ }
5319
6363
  /** Can this signature be called with these argument types? The signature's
5320
6364
  * own type parameters stand for what the call would infer, so each is
5321
6365
  * checked only against its constraint — `<K extends keyof Services>`
@@ -5351,7 +6395,7 @@ var TypeAnalyzer = class {
5351
6395
  for (const child of Object.values(value)) walk(child);
5352
6396
  };
5353
6397
  for (const p of f.params) if (containsTypeParam(p.type)) walk(p.type);
5354
- return f.params.map((p) => substitute(p.type, bounds));
6398
+ return f.params.map((p) => this.reduceType(substitute(p.type, bounds)));
5355
6399
  }
5356
6400
  /** Record what each written argument is expected to be — see
5357
6401
  * `TypeAnalysis.expectedTypeOf`. */
@@ -5368,6 +6412,28 @@ var TypeAnalyzer = class {
5368
6412
  }
5369
6413
  /** No signature accepts the call, and the argument count is not the
5370
6414
  * problem: say which argument is wrong, the way TypeScript does. */
6415
+ /** Check what was written against the parameters as this call's own type
6416
+ * arguments make them read: `pick("Bones", "C")` is wrong only once `P`
6417
+ * is known to be `"Bones"`. Picking the overload goes by each parameter's
6418
+ * constraint, which is deliberately looser than that. */
6419
+ checkInferredArguments(call, written, f, argTypes, self) {
6420
+ if (!this.emitDiagnostics || !f.typeParams?.length) return;
6421
+ const subst = this.inferTypeArgs(f, [...argTypes]);
6422
+ for (const bound of subst.values()) if (bound.kind === "unknown") return;
6423
+ for (let i = 0; i < f.params.length; i++) {
6424
+ const arg = argTypes[i];
6425
+ const declared = f.params[i].type;
6426
+ if (arg === void 0 || !containsTypeParam(declared)) continue;
6427
+ const expected = this.reduceType(substitute(declared, subst));
6428
+ if (containsTypeParam(expected) || expected.kind === "any" || expected.kind === "unknown") continue;
6429
+ if (isAssignable(arg, expected) || isAssignable(widen(arg), expected)) continue;
6430
+ this.diagnostics.push({
6431
+ node: written[i - self] ?? call,
6432
+ message: `Argument of type '${formatType(arg)}' is not assignable to parameter of type '${briefType(expected)}'`
6433
+ });
6434
+ return;
6435
+ }
6436
+ }
5371
6437
  reportArguments(call, written, fns, argsFor, selfOf) {
5372
6438
  if (!this.emitDiagnostics) return;
5373
6439
  if (fns.length > 1) {
@@ -5437,9 +6503,33 @@ var TypeAnalyzer = class {
5437
6503
  });
5438
6504
  return false;
5439
6505
  }
6506
+ /** An overload set's implementation handles every signature, so a bare
6507
+ * parameter of it holds whatever those signatures allow there:
6508
+ * `function f(Stat, ...)` under 36 `Stat: "..."` signatures is the union
6509
+ * of all 36. TypeScript leaves such a parameter `any`; this says what it
6510
+ * can actually be. An annotation, a pattern or a default still wins. */
6511
+ paramsFromSignatures(func, signatures) {
6512
+ if (!signatures?.length) return;
6513
+ const resolved = signatures.map((sig) => this.signatureToFnType(sig));
6514
+ func.params.forEach((param, i) => {
6515
+ if (param.typeAnnotation || param.pattern || param.default) return;
6516
+ const candidates = [];
6517
+ for (const signature of resolved) {
6518
+ if (signature.kind !== "function") continue;
6519
+ const own = signature.params[i];
6520
+ if (own) candidates.push(own.optional ? optional(own.type) : own.type);
6521
+ else if (signature.varargs) candidates.push(signature.varargs);
6522
+ }
6523
+ if (candidates.length) this.contextualParams.set(param, union(candidates));
6524
+ });
6525
+ }
5440
6526
  signatureToFnType(sig) {
5441
6527
  const names = sig.generics.map((g) => g.name);
5442
- return this.withTypeParams(sig.generics, () => {
6528
+ const record = (type) => {
6529
+ this.typeOfTypeNode.set(sig, type);
6530
+ return type;
6531
+ };
6532
+ return record(this.withTypeParams(sig.generics, () => {
5443
6533
  const params = sig.params.map((p) => ({
5444
6534
  name: p.pattern ? void 0 : p.name,
5445
6535
  type: this.paramType(p, /* @__PURE__ */ new Map()),
@@ -5452,7 +6542,7 @@ var TypeAnalyzer = class {
5452
6542
  names,
5453
6543
  this.resolvePredicate(sig.predicate, params)
5454
6544
  );
5455
- });
6545
+ }));
5456
6546
  }
5457
6547
  /** Turn a parsed `v is T` / `asserts v` annotation into a `TypePredicate`,
5458
6548
  * resolving the named parameter to its index. A guard naming a parameter
@@ -5497,8 +6587,11 @@ var TypeAnalyzer = class {
5497
6587
  } else if (func.predicate) {
5498
6588
  returns = booleanType;
5499
6589
  } else {
5500
- this.preVisitBody(func.body, bodyEnv);
5501
- returns = this.inferReturnType(func.body, bodyEnv);
6590
+ const collected = [];
6591
+ returns = this.withVarargs(func, () => this.silently(() => {
6592
+ this.collectReturns(collected, () => this.preVisitBody(func.body, bodyEnv));
6593
+ return collected.length ? union(collected) : this.inferReturnType(func.body, bodyEnv);
6594
+ }));
5502
6595
  }
5503
6596
  return fn(
5504
6597
  params,
@@ -5509,6 +6602,29 @@ var TypeAnalyzer = class {
5509
6602
  );
5510
6603
  });
5511
6604
  }
6605
+ /** Where the return types of the function being walked are collected, so
6606
+ * each is read where it is written — inside the branch that narrowed it —
6607
+ * rather than in whatever state the body ends in. */
6608
+ returnTypes;
6609
+ collectReturns(into, body) {
6610
+ const previous = this.returnTypes;
6611
+ this.returnTypes = into;
6612
+ try {
6613
+ return body();
6614
+ } finally {
6615
+ this.returnTypes = previous;
6616
+ }
6617
+ }
6618
+ /** Run something without reporting what it finds. */
6619
+ silently(body) {
6620
+ const wasEmitting = this.emitDiagnostics;
6621
+ this.emitDiagnostics = false;
6622
+ try {
6623
+ return body();
6624
+ } finally {
6625
+ this.emitDiagnostics = wasEmitting;
6626
+ }
6627
+ }
5512
6628
  /** Populate binding types for a function body without reporting anything,
5513
6629
  * purely so an un-annotated return type can see its own locals. Bounded:
5514
6630
  * nested functions stop pre-visiting after a couple of levels, since the
@@ -5525,6 +6641,157 @@ var TypeAnalyzer = class {
5525
6641
  this.preVisitDepth--;
5526
6642
  }
5527
6643
  }
6644
+ /** The `[key, value]` pairs iterating a record yields, one per property —
6645
+ * for `pairs(t)`, `next, t` and `for k, v in t` over an object type with
6646
+ * no indexer. `undefined` for anything else (an array, a dictionary, an
6647
+ * iterator function), whose keys have no names to list. */
6648
+ iterationRows(iterNode, iterType) {
6649
+ let source;
6650
+ if (iterNode?.type === "CallExpression" && iterNode.callee.type === "Identifier" && iterNode.arguments[0]) {
6651
+ if (iterNode.callee.name !== "pairs" && iterNode.callee.name !== "next") return void 0;
6652
+ source = this.typeOf.get(iterNode.arguments[0]);
6653
+ } else {
6654
+ source = iterType;
6655
+ }
6656
+ const t = source && this.expand(source);
6657
+ if (!t || t.kind !== "object" || t.class || t.indexer || !t.properties.size) return void 0;
6658
+ return [...t.properties].map(([name, property]) => [
6659
+ literal(name),
6660
+ property.optional ? optional(property.type) : property.type
6661
+ ]);
6662
+ }
6663
+ /** Bindings that hold parts of one value: the key and value of a `pairs`
6664
+ * row, or the names destructured from one union member. By flow key.
6665
+ * Which rows are still possible is itself flow state, kept in `env` under
6666
+ * `group` as a union of tuples, so it narrows and merges like any type. */
6667
+ correlations = /* @__PURE__ */ new Map();
6668
+ correlateBindings(env, ids, rows) {
6669
+ const keys = ids.map(bindKey);
6670
+ const group = `rows(${keys.join(",")})`;
6671
+ keys.forEach((key, index) => this.correlations.set(key, { group, index, keys, rows }));
6672
+ env.set(group, union(rows.map((row) => tuple(row))));
6673
+ }
6674
+ /** `key` was just narrowed to `narrowed` in `env`: narrow that column of
6675
+ * every row, drop the rows it rules out, and give the other bindings what
6676
+ * the remaining rows hold. */
6677
+ correlate(env, key, narrowed) {
6678
+ const entry = this.correlations.get(key);
6679
+ if (!entry) return;
6680
+ const state = env.get(entry.group);
6681
+ const current = state && (state.kind === "union" ? state.types : [state]).every((t) => t.kind === "tuple") ? (state.kind === "union" ? state.types : [state]).map((t) => t.elements) : entry.rows;
6682
+ const kept = [];
6683
+ for (const row of current) {
6684
+ const column = narrowTo(row[entry.index], narrowed);
6685
+ if (column.kind !== "never") kept.push(row.map((t, i) => i === entry.index ? column : t));
6686
+ }
6687
+ env.set(entry.group, kept.length ? union(kept.map((row) => tuple(row))) : neverType);
6688
+ entry.keys.forEach((other, j) => {
6689
+ if (j !== entry.index) env.set(other, kept.length ? union(kept.map((row) => row[j])) : neverType);
6690
+ });
6691
+ }
6692
+ /** Stop correlating a binding once it is assigned: its value no longer
6693
+ * comes from the row. */
6694
+ uncorrelate(id) {
6695
+ const entry = this.correlations.get(bindKey(id));
6696
+ if (entry) for (const key of entry.keys) this.correlations.delete(key);
6697
+ }
6698
+ /** `const { kind, payload } = action` over a union of objects: one row per
6699
+ * member, so testing `kind` narrows `payload` (TypeScript's destructured
6700
+ * discriminated unions). Only plain `name` / `key: name` properties take
6701
+ * part. */
6702
+ /** Names that denote one and the same value: `const c = player.Character`
6703
+ * makes `c` and `player.Character` two spellings of one reference. Kept
6704
+ * as an undirected graph of flow keys. */
6705
+ refAliases = /* @__PURE__ */ new Map();
6706
+ /** `const c = a.b` — `c` cannot be re-bound and the path was read once, so
6707
+ * a test of either name is a test of the same value. Only property paths
6708
+ * take part: `const c = other` would tie `c` to a name that may itself be
6709
+ * assigned a different value later. */
6710
+ aliasReference(target, init) {
6711
+ if (target.type !== "IdentifierPattern" || !init) return;
6712
+ const source = unwrapParens(init);
6713
+ if (source.type !== "MemberExpression" && source.type !== "IndexExpression") return;
6714
+ const path = this.refKeyOf(source);
6715
+ const id = this.bindingIdByName(target.name, target);
6716
+ if (path === void 0 || id === void 0) return;
6717
+ const name = bindKey(id);
6718
+ for (const [a, b] of [[name, path], [path, name]]) {
6719
+ const set = this.refAliases.get(a) ?? /* @__PURE__ */ new Set();
6720
+ set.add(b);
6721
+ this.refAliases.set(a, set);
6722
+ }
6723
+ }
6724
+ /** A reference was narrowed: give every other spelling of the same value
6725
+ * the same news. Walks the alias graph, so a path with two names told by
6726
+ * one of them reaches the other. Each alias keeps whatever it already
6727
+ * knew — the narrowing only ever cuts the type further down. */
6728
+ propagateAliases(env, into, key, narrowed) {
6729
+ if (!this.refAliases.size) return;
6730
+ const seen = /* @__PURE__ */ new Set([key]);
6731
+ const queue = [[key, narrowed]];
6732
+ const learn = (at, t) => {
6733
+ seen.add(at);
6734
+ this.setRef(into, at, t);
6735
+ this.correlate(into, at, t);
6736
+ queue.push([at, t]);
6737
+ };
6738
+ for (let at = 0; at < queue.length; at++) {
6739
+ const [from, t] = queue[at];
6740
+ for (const other of this.refAliases.get(from) ?? []) {
6741
+ if (seen.has(other)) continue;
6742
+ const current = into.get(other) ?? env.get(other) ?? this.declaredAtRef(other);
6743
+ const next = narrowTo(current, t);
6744
+ learn(other, next.kind === "never" ? t : next);
6745
+ for (let child = other, value = into.get(other); ; ) {
6746
+ const cut = child.lastIndexOf(".");
6747
+ if (cut <= 0) break;
6748
+ const parent = child.slice(0, cut);
6749
+ if (seen.has(parent)) break;
6750
+ const had = into.get(parent) ?? env.get(parent) ?? this.declaredAtRef(parent);
6751
+ value = this.filterByProperty(had, child.slice(cut + 1), value);
6752
+ learn(parent, value);
6753
+ child = parent;
6754
+ }
6755
+ }
6756
+ }
6757
+ }
6758
+ /** `const path = paths[stat]` where `stat` is one of several keys: which
6759
+ * value came back says which key was asked for. Testing the value then
6760
+ * narrows the key — the `else` of `if path then` leaves exactly the keys
6761
+ * the table does not have. */
6762
+ correlateIndexed(target, init, env) {
6763
+ if (target.type !== "IdentifierPattern" || !init) return;
6764
+ const source = unwrapParens(init);
6765
+ if (source.type !== "IndexExpression" || source.index.type !== "Identifier") return;
6766
+ const valueId = this.bindingIdByName(target.name, target);
6767
+ const keyId = this.bindingIdOf(source.index);
6768
+ if (valueId === void 0 || keyId === void 0) return;
6769
+ const key = this.expand(this.currentType(keyId, env));
6770
+ if (key.kind !== "union" || key.types.length < 2 || key.types.length > 64) return;
6771
+ if (!key.types.every((m) => m.kind === "literal")) return;
6772
+ const object = this.expand(this.typeOf.get(source.object) ?? unknownType);
6773
+ if (object.kind !== "object") return;
6774
+ this.correlateBindings(env, [keyId, valueId], key.types.map((m) => [m, this.indexedType(object, m)]));
6775
+ }
6776
+ correlateDestructuring(pattern, source, env) {
6777
+ if (pattern.type !== "ObjectPattern") return;
6778
+ const members = this.expand(source);
6779
+ if (members.kind !== "union") return;
6780
+ const objects = members.types.map((m) => this.expand(m));
6781
+ if (objects.length < 2 || objects.some((m) => m.kind !== "object")) return;
6782
+ const ids = [];
6783
+ const names = [];
6784
+ for (const property of pattern.properties) {
6785
+ if (property.computed || property.default || property.value.type !== "IdentifierPattern") return;
6786
+ const name = property.key.type === "Identifier" ? property.key.name : property.key.type === "StringLiteral" ? property.key.value : void 0;
6787
+ const id = this.bindingIdByName(property.value.name, property.value);
6788
+ if (name === void 0 || id === void 0) return;
6789
+ ids.push(id);
6790
+ names.push(name);
6791
+ }
6792
+ if (ids.length < 2) return;
6793
+ this.correlateBindings(env, ids, objects.map((member) => names.map((name) => this.propertyType(member, name))));
6794
+ }
5528
6795
  /** `(keyType, valueType)` yielded by a generic-for iterator. Handles
5529
6796
  * `ipairs`/`pairs`/`next(t)` and Luau generalized iteration (`for … in t`).
5530
6797
  * `varCount` is how many loop variables were written. */
@@ -5589,6 +6856,18 @@ var TypeAnalyzer = class {
5589
6856
  if (init.type === "ArrayExpression") return isAssignable(this.inferArray(init, env, true), declared);
5590
6857
  return false;
5591
6858
  }
6859
+ /** `{ a, ...rest }`: what `rest` holds — the value without the properties
6860
+ * the pattern already took. */
6861
+ withoutKeys(raw, properties) {
6862
+ const taken = new Set(properties.flatMap((p) => !p.computed && p.key.type === "Identifier" ? [p.key.name] : !p.computed && p.key.type === "StringLiteral" ? [p.key.value] : []));
6863
+ if (!taken.size) return raw;
6864
+ const t = this.expand(raw);
6865
+ if (t.kind === "union") return union(t.types.map((m) => this.withoutKeys(m, properties)));
6866
+ if (t.kind !== "object") return raw;
6867
+ const kept = [...t.properties].filter(([name]) => !taken.has(name));
6868
+ if (kept.length === t.properties.size) return raw;
6869
+ return objectType(kept, t.indexer, t.frozen);
6870
+ }
5592
6871
  /** Fold a destructuring default (`{ a = 1 }`) into the property's type:
5593
6872
  * the default applies when the source value is missing/`nil`. */
5594
6873
  withDefault(base, def, env) {
@@ -5620,7 +6899,7 @@ var TypeAnalyzer = class {
5620
6899
  const pt = key !== void 0 ? this.propertyType(valueType, key) : unknownType;
5621
6900
  this.reassignPattern(p.value, this.withDefault(pt, p.default, env), env);
5622
6901
  }
5623
- if (target.rest) this.reassignPattern(target.rest, valueType, env);
6902
+ if (target.rest) this.reassignPattern(target.rest, this.withoutKeys(valueType, target.properties), env);
5624
6903
  return;
5625
6904
  }
5626
6905
  case "ArrayPattern": {
@@ -5655,7 +6934,7 @@ var TypeAnalyzer = class {
5655
6934
  const propType = key !== void 0 ? this.propertyType(valueType, key) : unknownType;
5656
6935
  this.bindPattern(p.value, this.withDefault(propType, p.default, env), env, mode);
5657
6936
  }
5658
- if (target.rest) this.bindPattern(target.rest, valueType, env, mode);
6937
+ if (target.rest) this.bindPattern(target.rest, this.withoutKeys(valueType, target.properties), env, mode);
5659
6938
  return;
5660
6939
  }
5661
6940
  case "ArrayPattern": {
@@ -5698,7 +6977,7 @@ var TypeAnalyzer = class {
5698
6977
  }
5699
6978
  }
5700
6979
  propertyType(raw, name) {
5701
- const t = this.expand(raw);
6980
+ const t = this.deferredAccess(this.expand(raw));
5702
6981
  if (t.kind === "object") {
5703
6982
  const p = t.properties.get(name);
5704
6983
  if (p) return p.optional ? optional(p.type) : p.type;
@@ -5721,21 +7000,37 @@ var TypeAnalyzer = class {
5721
7000
  const t = this.expand(raw);
5722
7001
  if (t.kind === "any") return anyType;
5723
7002
  if (t.kind === "union") return union(t.types.map((m) => this.indexedType(m, idx)));
5724
- if (t.kind === "difference") return this.indexedType(t.base, idx);
5725
- if (t.kind === "typeParam" && t.constraint) return this.indexedType(t.constraint, idx);
7003
+ const index = this.expand(idx);
7004
+ if (index.kind === "union") return union(index.types.map((m) => this.indexedType(t, m)));
7005
+ if (t.kind === "difference") return this.indexedType(t.base, index);
7006
+ if (t.kind === "typeParam" && t.constraint) return this.indexedType(t.constraint, index);
5726
7007
  if (t.kind === "array") return t.element;
5727
7008
  if (t.kind === "tuple") {
5728
- if (idx.kind === "literal" && typeof idx.value === "number") {
5729
- return t.elements[idx.value - 1] ?? unknownType;
7009
+ if (index.kind === "literal" && typeof index.value === "number") {
7010
+ return t.elements[index.value - 1] ?? nilType;
5730
7011
  }
5731
7012
  return union(t.elements);
5732
7013
  }
5733
7014
  if (t.kind === "object") {
5734
- if (idx.kind === "literal" && typeof idx.value === "string") return this.propertyType(t, idx.value);
7015
+ if (index.kind === "literal" && typeof index.value === "string") {
7016
+ const property = t.properties.get(index.value);
7017
+ if (property) return property.optional ? optional(property.type) : property.type;
7018
+ if (t.indexer && isAssignable(index, t.indexer.key)) return t.indexer.value;
7019
+ return nilType;
7020
+ }
7021
+ if (containsTypeParam(index)) return this.reduceType({ kind: "indexedAccess", objectType: t, indexType: index });
5735
7022
  if (t.indexer) return t.indexer.value;
5736
7023
  }
5737
7024
  return unknownType;
5738
7025
  }
7026
+ /** What a deferred `T[K]` can be: every property its index could name.
7027
+ * Reading a member of one, or calling it, sees that. */
7028
+ deferredAccess(t) {
7029
+ if (t.kind !== "indexedAccess") return t;
7030
+ const index = t.indexType.kind === "typeParam" && t.indexType.constraint ? t.indexType.constraint : t.indexType;
7031
+ if (containsTypeParam(index)) return unknownType;
7032
+ return this.accessType(t.objectType, index);
7033
+ }
5739
7034
  elementType(raw, index) {
5740
7035
  const t = this.expand(raw);
5741
7036
  if (t.kind === "array") return t.element;
@@ -5768,7 +7063,11 @@ var TypeAnalyzer = class {
5768
7063
  for (const part of expr.parts) if (part.kind === "expression") this.infer(part.expression, env);
5769
7064
  return stringType;
5770
7065
  }
7066
+ // `...` holds what the function declared it takes.
5771
7067
  case "VarargExpression":
7068
+ return this.varargs[this.varargs.length - 1] ?? anyType;
7069
+ // Broken syntax is reported by the parser; nothing more to say.
7070
+ case "ErrorExpression":
5772
7071
  return anyType;
5773
7072
  case "Identifier": {
5774
7073
  const id = this.bindingIdOf(expr);
@@ -5796,12 +7095,20 @@ var TypeAnalyzer = class {
5796
7095
  case "SatisfiesExpression": {
5797
7096
  const declared = this.resolveType(expr.typeAnnotation);
5798
7097
  this.applyContext(expr.expression, declared);
5799
- const actual = this.infer(expr.expression, env);
5800
- if (this.emitDiagnostics && declared.kind !== "any" && !this.fitsAnnotation(expr.expression, declared, actual, env)) {
7098
+ if (declared.kind === "any") return this.infer(expr.expression, env);
7099
+ const written = unwrapParens(expr.expression);
7100
+ const fresh = written.type === "TableExpression" || written.type === "ArrayExpression";
7101
+ const narrow = fresh ? this.inferAsConst(expr.expression, env) : this.infer(expr.expression, env);
7102
+ const actual = fresh ? this.keepContextualLiterals(narrow, declared) : narrow;
7103
+ this.typeOf.set(expr.expression, actual);
7104
+ if (!this.emitDiagnostics) return actual;
7105
+ if (!isAssignable(narrow, declared) && !isAssignable(actual, declared)) {
5801
7106
  this.diagnostics.push({
5802
7107
  node: expr,
5803
- message: `Type '${formatType(actual)}' does not satisfy '${formatType(declared)}'`
7108
+ message: `Type '${formatType(actual)}' does not satisfy the expected type '${formatType(declared)}'`
5804
7109
  });
7110
+ } else {
7111
+ this.reportExcessProperties(expr.expression, declared);
5805
7112
  }
5806
7113
  return actual;
5807
7114
  }
@@ -5835,6 +7142,10 @@ var TypeAnalyzer = class {
5835
7142
  }
5836
7143
  const l = this.infer(expr.left, env);
5837
7144
  const r = this.infer(expr.right, env);
7145
+ if (op === "==" || op === "~=") {
7146
+ if (unwrapParens(expr.right).type === "StringLiteral") this.expectedTypeOf.set(unwrapParens(expr.right), l);
7147
+ if (unwrapParens(expr.left).type === "StringLiteral") this.expectedTypeOf.set(unwrapParens(expr.left), r);
7148
+ }
5838
7149
  switch (op) {
5839
7150
  case "..":
5840
7151
  return this.operatorResult(expr, op, l, r) ?? stringType;
@@ -5857,57 +7168,25 @@ var TypeAnalyzer = class {
5857
7168
  return union([l, r]);
5858
7169
  }
5859
7170
  case "MemberExpression": {
5860
- const obj = this.infer(expr.object, env);
7171
+ const { type: obj, shortCircuits } = this.chainObject(expr, expr.object, env);
5861
7172
  const key = this.refKeyOf(expr);
5862
7173
  const narrowed = key === void 0 ? void 0 : env.get(key);
5863
- return narrowed ?? this.propertyType(obj, expr.property.name);
7174
+ return this.chainResult(expr, narrowed ?? this.propertyType(obj, expr.property.name), shortCircuits);
5864
7175
  }
5865
7176
  case "IndexExpression": {
5866
- const obj = this.infer(expr.object, env);
7177
+ const { type: obj, shortCircuits } = this.chainObject(expr, expr.object, env);
5867
7178
  const idx = this.infer(expr.index, env);
5868
7179
  const key = this.refKeyOf(expr);
5869
7180
  const narrowed = key === void 0 ? void 0 : env.get(key);
5870
- return narrowed ?? this.indexedType(obj, idx);
7181
+ return this.chainResult(expr, narrowed ?? this.indexedType(obj, idx), shortCircuits);
5871
7182
  }
5872
7183
  case "CallExpression": {
5873
- const callee = this.infer(expr.callee, env);
5874
- const fns = this.overloadsOf(callee);
5875
- const expected = this.expectedArguments(expr.arguments, fns, () => 0);
5876
- expr.arguments.forEach((a, i) => this.applyContext(a, expected[i]));
5877
- const argTypes = expr.arguments.map((a) => this.infer(a, env));
5878
- if (fns.length) {
5879
- this.recordExpected(expr.arguments, fns, () => 0);
5880
- const arityFits = this.checkArity(expr, fns, argTypes.length, 0);
5881
- const picked = this.pickOverload(fns, argTypes);
5882
- if (picked) {
5883
- return this.callReturn(picked, this.constArgs(picked, expr.arguments, argTypes, env));
5884
- }
5885
- if (arityFits) this.reportArguments(expr, expr.arguments, fns, () => argTypes, () => 0);
5886
- return union(fns.map((f) => this.callReturn(f, argTypes)));
5887
- }
5888
- return callee.kind === "any" ? anyType : unknownType;
7184
+ const { type: callee, shortCircuits } = this.chainObject(expr, expr.callee, env);
7185
+ return this.chainResult(expr, this.inferCall(expr, callee, env), shortCircuits);
5889
7186
  }
5890
7187
  case "MethodCallExpression": {
5891
- const objType = this.infer(expr.object, env);
5892
- const fns = this.overloadsOf(this.propertyType(objType, expr.method.name));
5893
- const expected = this.expectedArguments(expr.arguments, fns, (f) => this.takesSelf(f) ? 1 : 0);
5894
- expr.arguments.forEach((a, i) => this.applyContext(a, expected[i]));
5895
- const argTypes = expr.arguments.map((a) => this.infer(a, env));
5896
- if (fns.length) {
5897
- const withSelf = (f) => this.takesSelf(f) ? [objType, ...argTypes] : argTypes;
5898
- const selfOf = (f) => this.takesSelf(f) ? 1 : 0;
5899
- this.recordExpected(expr.arguments, fns, selfOf);
5900
- const arityFits = this.checkArity(expr, fns, argTypes.length, this.takesSelf(fns[0]) ? 1 : 0);
5901
- const picked = this.pickOverload(fns, argTypes, withSelf);
5902
- if (picked) {
5903
- const self = this.takesSelf(picked) ? 1 : 0;
5904
- const written = this.constArgs(picked, expr.arguments, argTypes, env, self);
5905
- return this.callReturn(picked, this.takesSelf(picked) ? [objType, ...written] : written);
5906
- }
5907
- if (arityFits) this.reportArguments(expr, expr.arguments, fns, withSelf, selfOf);
5908
- return union(fns.map((f) => this.callReturn(f, withSelf(f))));
5909
- }
5910
- return objType.kind === "any" ? anyType : unknownType;
7188
+ const { type: objType, shortCircuits } = this.chainObject(expr, expr.object, env);
7189
+ return this.chainResult(expr, this.inferMethodCall(expr, objType, env), shortCircuits);
5911
7190
  }
5912
7191
  case "IfElseExpression": {
5913
7192
  const branches = [];
@@ -5923,6 +7202,123 @@ var TypeAnalyzer = class {
5923
7202
  }
5924
7203
  }
5925
7204
  }
7205
+ inferCall(expr, callee, env) {
7206
+ const fns = this.overloadsOf(callee);
7207
+ const explicit = this.explicitTypeArguments(expr, fns);
7208
+ const expected = this.expectedArguments(expr.arguments, fns, () => 0);
7209
+ expr.arguments.forEach((a, i) => this.applyContext(a, expected[i]));
7210
+ const argTypes = expr.arguments.map((a) => this.infer(a, env));
7211
+ if (fns.length) {
7212
+ this.recordExpected(expr.arguments, fns, () => 0);
7213
+ const arityFits = this.checkArity(expr, fns, argTypes.length, 0);
7214
+ const picked = this.pickOverload(fns, argTypes);
7215
+ const distributed = this.distributedReturn(fns, argTypes, picked, (_, args) => args);
7216
+ if (distributed) return distributed;
7217
+ if (picked) {
7218
+ this.checkInferredArguments(expr, expr.arguments, picked, argTypes, 0);
7219
+ return this.callReturn(picked, this.constArgs(picked, expr.arguments, argTypes, env), explicit);
7220
+ }
7221
+ if (arityFits) this.reportArguments(expr, expr.arguments, fns, () => argTypes, () => 0);
7222
+ return union(fns.map((f) => this.callReturn(f, argTypes, explicit)));
7223
+ }
7224
+ return callee.kind === "any" ? anyType : unknownType;
7225
+ }
7226
+ inferMethodCall(expr, objType, env) {
7227
+ const fns = this.overloadsOf(this.propertyType(objType, expr.method.name));
7228
+ const explicit = this.explicitTypeArguments(expr, fns);
7229
+ const expected = this.expectedArguments(expr.arguments, fns, (f) => this.takesSelf(f) ? 1 : 0);
7230
+ expr.arguments.forEach((a, i) => this.applyContext(a, expected[i]));
7231
+ const argTypes = expr.arguments.map((a) => this.infer(a, env));
7232
+ if (fns.length) {
7233
+ const withSelf = (f) => this.takesSelf(f) ? [objType, ...argTypes] : argTypes;
7234
+ const selfOf = (f) => this.takesSelf(f) ? 1 : 0;
7235
+ this.recordExpected(expr.arguments, fns, selfOf);
7236
+ const arityFits = this.checkArity(expr, fns, argTypes.length, this.takesSelf(fns[0]) ? 1 : 0);
7237
+ const picked = this.pickOverload(fns, argTypes, withSelf);
7238
+ const distributed = this.distributedReturn(
7239
+ fns,
7240
+ argTypes,
7241
+ picked,
7242
+ (f, args) => this.takesSelf(f) ? [objType, ...args] : args
7243
+ );
7244
+ if (distributed) return distributed;
7245
+ if (picked) {
7246
+ const self = this.takesSelf(picked) ? 1 : 0;
7247
+ this.checkInferredArguments(expr, expr.arguments, picked, withSelf(picked), self);
7248
+ const written = this.constArgs(picked, expr.arguments, argTypes, env, self);
7249
+ return this.callReturn(picked, this.takesSelf(picked) ? [objType, ...written] : written, explicit);
7250
+ }
7251
+ if (arityFits) this.reportArguments(expr, expr.arguments, fns, withSelf, selfOf);
7252
+ return union(fns.map((f) => this.callReturn(f, withSelf(f), explicit)));
7253
+ }
7254
+ return objType.kind === "any" ? anyType : unknownType;
7255
+ }
7256
+ // --------------------------------------------------------
7257
+ // Optional chains
7258
+ // --------------------------------------------------------
7259
+ //
7260
+ // `a?.b.c`: when `a` is nil the whole chain is nil and `.c` never runs.
7261
+ // So a link reads its object without the `nil` a `?.` earlier in the chain
7262
+ // added — that nil has already left the chain — and the chain's outermost
7263
+ // link carries it again. Parentheses end a chain: `(a?.b).c` reads `.c`
7264
+ // from `B | nil`.
7265
+ /** The type of a link's non-nil object, for each link that is past a `?.`:
7266
+ * what the chain holds when it has not short-circuited. */
7267
+ chainValue = /* @__PURE__ */ new WeakMap();
7268
+ /** The object a link reads from, and whether the chain can short-circuit
7269
+ * by this link. */
7270
+ chainObject(link, object, env) {
7271
+ const full = this.infer(object, env);
7272
+ const inChain = this.chainValue.get(object);
7273
+ let type = inChain ?? full;
7274
+ if (link.optional) {
7275
+ type = withoutNil(type);
7276
+ } else if (this.includesNil(type)) {
7277
+ this.reportNilAccess(object, type);
7278
+ type = withoutNil(this.expand(type));
7279
+ }
7280
+ return { type, shortCircuits: inChain !== void 0 || link.optional === true };
7281
+ }
7282
+ /** Objects already reported as possibly nil: a loop body is visited more
7283
+ * than once. */
7284
+ nilAccessReported = /* @__PURE__ */ new WeakSet();
7285
+ includesNil(raw) {
7286
+ const t = this.expand(raw);
7287
+ if (t.kind === "primitive") return t.name === "nil";
7288
+ return t.kind === "union" && t.types.some((m) => m.kind === "primitive" && m.name === "nil");
7289
+ }
7290
+ reportNilAccess(object, type) {
7291
+ if (!this.emitDiagnostics || this.nilAccessReported.has(object)) return;
7292
+ this.nilAccessReported.add(object);
7293
+ const label = expressionLabel(object);
7294
+ const t = this.expand(type);
7295
+ const nilOnly = t.kind === "primitive" && t.name === "nil";
7296
+ const subject = label === void 0 ? "Object" : `'${label}'`;
7297
+ this.diagnostics.push({
7298
+ node: object,
7299
+ message: nilOnly ? `${subject} is nil` : `${subject} is possibly nil. Check it first, or use '?.' / '?:'`
7300
+ });
7301
+ }
7302
+ chainResult(link, value, shortCircuits) {
7303
+ if (!shortCircuits) return value;
7304
+ this.chainValue.set(link, value);
7305
+ return union([value, nilType]);
7306
+ }
7307
+ /** The chain around `cond` did not short-circuit — it produced a truthy
7308
+ * value, or any value but nil — so every object a `?.` in it tested is not
7309
+ * nil in `env`. */
7310
+ narrowOptionalLinks(cond, env, into) {
7311
+ for (let e = cond; ; ) {
7312
+ const link = e;
7313
+ const object = e.type === "CallExpression" ? e.callee : e.type === "MemberExpression" || e.type === "IndexExpression" || e.type === "MethodCallExpression" ? e.object : void 0;
7314
+ if (!object) return;
7315
+ if (link.optional) {
7316
+ const key = this.refKeyOf(object);
7317
+ if (key !== void 0) this.setRef(into, key, withoutNil(this.typeAtRef(object, into)));
7318
+ }
7319
+ e = object;
7320
+ }
7321
+ }
5926
7322
  inferArray(expr, env, asConst) {
5927
7323
  const contextual = this.contextualArrays.get(expr);
5928
7324
  if (contextual && !asConst) return contextual;
@@ -5971,6 +7367,123 @@ var TypeAnalyzer = class {
5971
7367
  }
5972
7368
  return objectType(entries, indexer, asConst || void 0);
5973
7369
  }
7370
+ /** A value inferred `as const`, widened back wherever `context` does not
7371
+ * ask for a literal: `satisfies`' result type. A property keeps `"circle"`
7372
+ * when the contract's property admits string literals, and becomes
7373
+ * `string` when it is only `string`; a tuple becomes an array unless the
7374
+ * contract is a tuple; nothing stays readonly. */
7375
+ keepContextualLiterals(value, context) {
7376
+ const ctx = context === void 0 ? void 0 : this.expand(context);
7377
+ switch (value.kind) {
7378
+ case "literal":
7379
+ return ctx && this.admitsLiteral(ctx, value.base) ? value : widen(value);
7380
+ case "object": {
7381
+ if (value.class) return value;
7382
+ const entries = [...value.properties].map(([name, property]) => [
7383
+ name,
7384
+ { ...property, readonly: false, type: this.keepContextualLiterals(property.type, ctx && this.contextProperty(ctx, name)) }
7385
+ ]);
7386
+ const indexer = value.indexer && {
7387
+ key: widen(value.indexer.key),
7388
+ value: this.keepContextualLiterals(value.indexer.value, ctx && this.contextIndexValue(ctx))
7389
+ };
7390
+ return objectType(entries, indexer);
7391
+ }
7392
+ case "tuple": {
7393
+ const tupleContext = ctx && this.membersOf(ctx).find((m) => m.kind === "tuple");
7394
+ if (tupleContext?.kind === "tuple") {
7395
+ return tuple(value.elements.map((e, i) => this.keepContextualLiterals(e, tupleContext.elements[i])), value.isPack);
7396
+ }
7397
+ const arrayContext = ctx && this.membersOf(ctx).find((m) => m.kind === "array");
7398
+ const element = arrayContext?.kind === "array" ? arrayContext.element : void 0;
7399
+ if (!value.elements.length) return arrayContext ?? arrayOf(unknownType);
7400
+ return arrayOf(union(value.elements.map((e) => this.keepContextualLiterals(e, element))));
7401
+ }
7402
+ case "array": {
7403
+ const arrayContext = ctx && this.membersOf(ctx).find((m) => m.kind === "array");
7404
+ return arrayOf(this.keepContextualLiterals(value.element, arrayContext?.kind === "array" ? arrayContext.element : void 0));
7405
+ }
7406
+ case "union":
7407
+ return union(value.types.map((t) => this.keepContextualLiterals(t, context)));
7408
+ default:
7409
+ return value;
7410
+ }
7411
+ }
7412
+ membersOf(t) {
7413
+ const x = this.expand(t);
7414
+ return x.kind === "union" ? x.types.map((m) => this.expand(m)) : [x];
7415
+ }
7416
+ /** Does a contract accept literals of `base` as such? */
7417
+ admitsLiteral(ctx, base) {
7418
+ return this.membersOf(ctx).some((m) => m.kind === "literal" && m.base === base || m.kind === "templateLiteral" && base === "string");
7419
+ }
7420
+ /** What a contract expects of property `name`, over every object it allows. */
7421
+ contextProperty(ctx, name) {
7422
+ const found = [];
7423
+ for (const m of this.membersOf(ctx)) {
7424
+ if (m.kind !== "object") continue;
7425
+ const property = m.properties.get(name);
7426
+ if (property) found.push(property.type);
7427
+ else if (m.indexer) found.push(m.indexer.value);
7428
+ }
7429
+ return found.length ? union(found) : void 0;
7430
+ }
7431
+ contextIndexValue(ctx) {
7432
+ const found = this.membersOf(ctx).flatMap((m) => m.kind === "object" && m.indexer ? [m.indexer.value] : []);
7433
+ return found.length ? union(found) : void 0;
7434
+ }
7435
+ /** Fields reported by `reportExcessProperties`, once each: a loop body is
7436
+ * visited more than once. */
7437
+ excessReported = /* @__PURE__ */ new WeakSet();
7438
+ /** TypeScript's excess property check. An object literal written straight
7439
+ * into a typed place — an annotation, `satisfies` — may only name
7440
+ * properties that place knows: anything else is almost always a typo.
7441
+ * A nested literal is checked against the property it is written for.
7442
+ * A target with an indexer, a class, or a member whose shape is not known
7443
+ * accepts anything. */
7444
+ /** The keys an index signature covers, when it covers a countable set of
7445
+ * them: `[("a" | "b")]` yes, `[string]` no. */
7446
+ finiteKeys(key) {
7447
+ const t = this.expand(key);
7448
+ const parts = t.kind === "union" ? t.types : [t];
7449
+ const out = /* @__PURE__ */ new Set();
7450
+ for (const part of parts.map((m) => this.expand(m))) {
7451
+ if (part.kind !== "literal" || typeof part.value === "boolean") return void 0;
7452
+ out.add(String(part.value));
7453
+ }
7454
+ return out.size ? out : void 0;
7455
+ }
7456
+ reportExcessProperties(expression, target) {
7457
+ let literal2 = unwrapParens(expression);
7458
+ while (literal2.type === "AsConstExpression") literal2 = unwrapParens(literal2.expression);
7459
+ if (literal2.type !== "TableExpression" || !this.emitDiagnostics) return;
7460
+ const members = this.membersOf(target);
7461
+ const shapes = members.filter((m) => m.kind === "object");
7462
+ if (!shapes.length || shapes.some((o) => o.class)) return;
7463
+ const keySets = shapes.map((o) => o.indexer && this.finiteKeys(o.indexer.key));
7464
+ if (shapes.some((o, i) => o.indexer && !keySets[i])) return;
7465
+ if (members.some((m) => m.kind === "any" || m.kind === "unknown" || m.kind === "typeParam" || m.kind === "intersection")) return;
7466
+ for (const field of literal2.fields) {
7467
+ if (field.type !== "TableFieldNamed" && field.type !== "TableFieldShorthand") continue;
7468
+ const key = field.type === "TableFieldNamed" ? field.key : field.name;
7469
+ const name = key.type === "Identifier" ? key.name : key.value;
7470
+ const expected = shapes.flatMap((o, i) => {
7471
+ const property = o.properties.get(name);
7472
+ if (property) return [property.type];
7473
+ return o.indexer && keySets[i].has(name) ? [o.indexer.value] : [];
7474
+ });
7475
+ if (!expected.length) {
7476
+ if (this.excessReported.has(key)) continue;
7477
+ this.excessReported.add(key);
7478
+ this.diagnostics.push({
7479
+ node: key,
7480
+ message: `Object literal may only specify known properties, and '${name}' does not exist in type '${formatType(target)}'`
7481
+ });
7482
+ continue;
7483
+ }
7484
+ if (field.type === "TableFieldNamed") this.reportExcessProperties(field.value, union(expected));
7485
+ }
7486
+ }
5974
7487
  inferAsConst(expr, env) {
5975
7488
  switch (expr.type) {
5976
7489
  case "ArrayExpression":
@@ -6028,12 +7541,13 @@ var TypeAnalyzer = class {
6028
7541
  }
6029
7542
  if (cond.type === "CallExpression" || cond.type === "MethodCallExpression") {
6030
7543
  this.narrowByPredicateCall(cond, env, t, f);
6031
- return;
7544
+ } else {
7545
+ this.narrowRef(cond, env, t, f, (cur) => ({
7546
+ yes: narrowTruthy(cur),
7547
+ no: narrowFalsy(cur)
7548
+ }));
6032
7549
  }
6033
- this.narrowRef(cond, env, t, f, (cur) => ({
6034
- yes: narrowTruthy(cur),
6035
- no: narrowFalsy(cur)
6036
- }));
7550
+ this.narrowOptionalLinks(cond, env, t);
6037
7551
  }
6038
7552
  /** `a == b` / `a ~= b`. Handles, in order: a declaration-driven
6039
7553
  * `typeof(x) == "..."` test, a literal/`nil` comparison against a
@@ -6050,11 +7564,14 @@ var TypeAnalyzer = class {
6050
7564
  };
6051
7565
  for (const [ref, other] of [[left, right], [right, left]]) {
6052
7566
  const value = litOf(other);
6053
- if (value === void 0 || this.refKeyOf(ref) === void 0) continue;
6054
- this.narrowRef(ref, env, yes, no, (cur) => ({
6055
- yes: narrowTo(cur, value),
6056
- no: narrowExclude(cur, value)
6057
- }));
7567
+ if (value === void 0) continue;
7568
+ if (this.refKeyOf(ref) !== void 0) {
7569
+ this.narrowRef(ref, env, yes, no, (cur) => ({
7570
+ yes: narrowTo(cur, value),
7571
+ no: narrowExclude(cur, value)
7572
+ }));
7573
+ }
7574
+ this.narrowOptionalLinks(ref, env, value.kind === "primitive" && value.name === "nil" ? no : yes);
6058
7575
  return;
6059
7576
  }
6060
7577
  if (this.refKeyOf(left) !== void 0 && this.refKeyOf(right) !== void 0) {
@@ -6109,11 +7626,16 @@ var TypeAnalyzer = class {
6109
7626
  predicateCallTarget(cond, env) {
6110
7627
  let callee;
6111
7628
  let args;
7629
+ let selfType;
6112
7630
  if (cond.type === "CallExpression") {
6113
- callee = this.typeOf.get(cond.callee) ?? this.typeAtRef(cond.callee, env);
7631
+ callee = this.chainValue.get(cond.callee) ?? this.typeOf.get(cond.callee) ?? this.typeAtRef(cond.callee, env);
6114
7632
  args = cond.arguments;
6115
7633
  } else if (cond.type === "MethodCallExpression") {
6116
- const objType = this.typeOf.get(cond.object) ?? this.typeAtRef(cond.object, env);
7634
+ let objType = this.chainValue.get(cond.object) ?? this.typeOf.get(cond.object) ?? this.typeAtRef(cond.object, env);
7635
+ if (cond.optional) {
7636
+ objType = withoutNil(objType);
7637
+ selfType = objType;
7638
+ }
6117
7639
  callee = this.propertyType(objType, cond.method.name);
6118
7640
  const first = this.overloadsOf(callee)[0];
6119
7641
  args = first && this.takesSelf(first) ? [cond.object, ...cond.arguments] : cond.arguments;
@@ -6121,7 +7643,7 @@ var TypeAnalyzer = class {
6121
7643
  return void 0;
6122
7644
  }
6123
7645
  const overloads = this.overloadsOf(callee);
6124
- const argTypes = args.map((a) => this.typeOf.get(a) ?? this.typeAtRef(a, env));
7646
+ const argTypes = args.map((a) => (selfType && cond.type === "MethodCallExpression" && a === cond.object ? selfType : void 0) ?? this.typeOf.get(a) ?? this.typeAtRef(a, env));
6125
7647
  const picked = this.pickOverload(overloads, argTypes);
6126
7648
  const candidates = picked ? [picked, ...overloads.filter((f) => f !== picked)] : overloads;
6127
7649
  for (const f of candidates) {
@@ -6228,6 +7750,10 @@ var TypeAnalyzer = class {
6228
7750
  const { yes, no } = refine(cur);
6229
7751
  this.setRef(t, key, yes);
6230
7752
  this.setRef(f, key, no);
7753
+ this.correlate(t, key, yes);
7754
+ this.correlate(f, key, no);
7755
+ this.propagateAliases(env, t, key, yes);
7756
+ this.propagateAliases(env, f, key, no);
6231
7757
  const inner = expr.type === "ParenthesizedExpression" ? expr.expression : expr;
6232
7758
  if (inner.type !== "MemberExpression" && inner.type !== "IndexExpression") return;
6233
7759
  const parentKey = this.refKeyOf(inner.object);
@@ -6235,18 +7761,19 @@ var TypeAnalyzer = class {
6235
7761
  const step = key.slice(parentKey.length);
6236
7762
  if (!step.startsWith(".")) return;
6237
7763
  const prop = step.slice(1);
7764
+ const optional2 = inner.type === "MemberExpression" && inner.optional === true;
6238
7765
  this.narrowRef(inner.object, env, t, f, (parentType) => ({
6239
- yes: this.filterByProperty(parentType, prop, yes),
6240
- no: this.filterByProperty(parentType, prop, no)
7766
+ yes: this.filterByProperty(parentType, prop, yes, optional2),
7767
+ no: this.filterByProperty(parentType, prop, no, optional2)
6241
7768
  }));
6242
7769
  }
6243
7770
  /** Keep the union members of `parent` whose `prop` can still hold `want`.
6244
7771
  * Leaves a non-union (or a union nothing matches) alone: over-narrowing a
6245
7772
  * plain object to `never` because of a property test would be worse than
6246
7773
  * learning nothing. */
6247
- filterByProperty(parent, prop, want) {
7774
+ filterByProperty(parent, prop, want, optional2 = false) {
6248
7775
  if (parent.kind !== "union" || want.kind === "never") return parent;
6249
- const kept = parent.types.filter((m) => overlaps(this.propertyType(m, prop), want));
7776
+ const kept = parent.types.filter((m) => m.kind === "primitive" && m.name === "nil" ? optional2 && overlaps(nilType, want) : overlaps(this.propertyType(m, prop), want));
6250
7777
  return kept.length ? union(kept) : parent;
6251
7778
  }
6252
7779
  /** Record a narrowing. Deliberately does *not* discard what is known about
@@ -6258,6 +7785,15 @@ var TypeAnalyzer = class {
6258
7785
  setRef(env, key, t) {
6259
7786
  env.set(key, t);
6260
7787
  }
7788
+ /** An assignment to a path (or to anything it hangs off) means the name
7789
+ * that copied it no longer holds that value: forget the alias. */
7790
+ unalias(key) {
7791
+ for (const k of [...this.refAliases.keys()]) {
7792
+ if (k !== key && !k.startsWith(`${key}.`) && !k.startsWith(`${key}#`)) continue;
7793
+ for (const other of this.refAliases.get(k) ?? []) this.refAliases.get(other)?.delete(k);
7794
+ this.refAliases.delete(k);
7795
+ }
7796
+ }
6261
7797
  /** Drop every narrowing recorded for a path strictly under `key`. */
6262
7798
  invalidateBelow(env, key) {
6263
7799
  for (const k of [...env.keys()]) {
@@ -6270,6 +7806,7 @@ var TypeAnalyzer = class {
6270
7806
  const key = this.refKeyOf(expr);
6271
7807
  if (key === void 0) return;
6272
7808
  this.invalidateBelow(env, key);
7809
+ this.unalias(key);
6273
7810
  env.set(key, value);
6274
7811
  }
6275
7812
  // --------------------------------------------------------
@@ -6350,7 +7887,85 @@ var TypeAnalyzer = class {
6350
7887
  /** The type a binding has *here*: its flow-narrowed type if the current
6351
7888
  * environment has one, else its declared/inferred type. */
6352
7889
  currentType(id, env) {
6353
- return env.get(bindKey(id)) ?? this.bindingType.get(id) ?? anyType;
7890
+ return env.get(bindKey(id)) ?? this.bindingType.get(id) ?? this.declaredAhead(id) ?? anyType;
7891
+ }
7892
+ // --------------------------------------------------------
7893
+ // Hoisting
7894
+ // --------------------------------------------------------
7895
+ //
7896
+ // Scope analysis lets code see a function declared later in its block,
7897
+ // and a module's top-level names from function bodies and `typeof` written
7898
+ // above them. The walk has not reached those declarations yet when such a
7899
+ // reference is met, so their type is worked out from the declaration on
7900
+ // the spot — its annotation, or its body or initializer — as TypeScript
7901
+ // does. The walk reaching the declaration later types it for real.
7902
+ /** Declarations a reference may meet before the walk does. */
7903
+ aheadDeclarations;
7904
+ computingAhead = /* @__PURE__ */ new Set();
7905
+ declaredAhead(id) {
7906
+ this.aheadDeclarations ??= this.indexAheadDeclarations();
7907
+ const found = this.aheadDeclarations.get(id);
7908
+ if (!found || this.computingAhead.has(id)) return void 0;
7909
+ this.computingAhead.add(id);
7910
+ const wasEmitting = this.emitDiagnostics;
7911
+ this.emitDiagnostics = false;
7912
+ try {
7913
+ const { statement, index } = found;
7914
+ let type;
7915
+ if (statement.type === "DeclareStatement") {
7916
+ type = this.resolveType(statement.valueType);
7917
+ } else if (statement.type === "FunctionDeclaration") {
7918
+ type = statement.signatures?.length ? intersection(statement.signatures.map((sig) => this.signatureToFnType(sig))) : this.inferFunctionBody(statement.func, /* @__PURE__ */ new Map());
7919
+ } else if (statement.type === "VariableDeclaration") {
7920
+ const target = statement.names[index];
7921
+ if (target.type === "IdentifierPattern" && target.typeAnnotation) {
7922
+ type = this.resolveType(target.typeAnnotation);
7923
+ } else if (statement.init[index]) {
7924
+ const value = this.infer(statement.init[index], /* @__PURE__ */ new Map());
7925
+ type = statement.kind === "const" ? value : widen(value);
7926
+ }
7927
+ }
7928
+ if (type) this.bindingType.set(id, type);
7929
+ return type;
7930
+ } finally {
7931
+ this.emitDiagnostics = wasEmitting;
7932
+ this.computingAhead.delete(id);
7933
+ }
7934
+ }
7935
+ /** Every function declaration, and every plain name the module declares
7936
+ * at its top level. */
7937
+ indexAheadDeclarations() {
7938
+ const out = /* @__PURE__ */ new Map();
7939
+ for (const statement of this.program.body.statements) {
7940
+ const declaration = statement.type === "ExportStatement" ? statement.declaration : statement;
7941
+ if (declaration.type !== "VariableDeclaration") continue;
7942
+ declaration.names.forEach((target, index) => {
7943
+ if (target.type !== "IdentifierPattern") return;
7944
+ const id = this.bindingIdByName(target.name, target);
7945
+ if (id !== void 0) out.set(id, { statement: declaration, index });
7946
+ });
7947
+ }
7948
+ const visit = (node) => {
7949
+ if (!node || typeof node !== "object") return;
7950
+ if (Array.isArray(node)) {
7951
+ for (const item of node) visit(item);
7952
+ return;
7953
+ }
7954
+ const record = node;
7955
+ if (record.type === "FunctionDeclaration" && record.name) {
7956
+ const id = this.bindingIdByName(record.name.name, record.name);
7957
+ if (id !== void 0) out.set(id, { statement: node, index: 0 });
7958
+ }
7959
+ for (const [key, value] of Object.entries(node)) {
7960
+ if (key !== "line" && key !== "column" && value && typeof value === "object") visit(value);
7961
+ }
7962
+ };
7963
+ visit(this.program.body);
7964
+ for (const [name, statement] of this.deferredDeclares) {
7965
+ const id = this.scopes.globalsByName.get(name);
7966
+ if (id !== void 0) out.set(id, { statement, index: 0 });
7967
+ }
7968
+ return out;
6354
7969
  }
6355
7970
  /** Bind or rebind a whole variable: any narrowing recorded for a path
6356
7971
  * *under* it (`x.a`, `x[1]`) described the old value and must go. */
@@ -6377,12 +7992,28 @@ var TypeAnalyzer = class {
6377
7992
  return this.bindingByDecl.get(node) ?? this.bindingByPos.get(posKey(name, node.line.start, node.column.start));
6378
7993
  }
6379
7994
  };
7995
+ function referencedTypeNames(node, out = []) {
7996
+ if (!node || typeof node !== "object") return out;
7997
+ if (Array.isArray(node)) {
7998
+ for (const item of node) referencedTypeNames(item, out);
7999
+ return out;
8000
+ }
8001
+ const record = node;
8002
+ if (record.type === "TypeReference" && typeof record.base === "string") {
8003
+ out.push(typeof record.namespace === "string" ? `${record.namespace}.${record.base}` : record.base);
8004
+ }
8005
+ for (const [key, value] of Object.entries(node)) {
8006
+ if (key !== "line" && key !== "column" && value && typeof value === "object") referencedTypeNames(value, out);
8007
+ }
8008
+ return out;
8009
+ }
6380
8010
  function containsTypeQuery(node) {
6381
8011
  if (!node || typeof node !== "object") return false;
6382
8012
  if (Array.isArray(node)) return node.some(containsTypeQuery);
6383
8013
  if (node.type === "TypeofTypeNode") return true;
6384
8014
  return Object.values(node).some(containsTypeQuery);
6385
8015
  }
8016
+ var STRING_INTRINSICS = /* @__PURE__ */ new Set(["Uppercase", "Lowercase", "Capitalize", "Uncapitalize"]);
6386
8017
  function briefType(t) {
6387
8018
  if (t.kind === "union" && t.types.length > 8) {
6388
8019
  const shown = t.types.slice(0, 6).map(formatType).join(" | ");
@@ -6771,17 +8402,21 @@ var index_default = luautparser;
6771
8402
  Keywords,
6772
8403
  LexError,
6773
8404
  Operators,
8405
+ PRELUDE_SOURCE,
6774
8406
  ParseError,
6775
8407
  Punctuators,
8408
+ UNUSED_EXPECT_ERROR,
6776
8409
  UnaryOperators,
6777
8410
  analyzeScopes,
6778
8411
  analyzeTypes,
6779
8412
  anyType,
8413
+ applyDirectives,
6780
8414
  arrayOf,
6781
8415
  booleanType,
6782
8416
  bufferType,
6783
8417
  containsTypeParam,
6784
8418
  difference,
8419
+ directivesOf,
6785
8420
  equalTypes,
6786
8421
  falsyType,
6787
8422
  findConfig,
@@ -6817,6 +8452,7 @@ var index_default = luautparser;
6817
8452
  parseTokens,
6818
8453
  parseWithRecovery,
6819
8454
  primitive,
8455
+ readDirectives,
6820
8456
  resolveModulePath,
6821
8457
  resolveTypeLibraries,
6822
8458
  setAliasExpander,