luaut-parser 2.0.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -796,23 +796,14 @@ var Parser = class {
796
796
  if (t.type === "Punctuator" && t.value === "@") {
797
797
  const { attributes, start } = this.parseAttributes();
798
798
  const next = this.current();
799
- if (next.type === "Keyword" && (next.value === "const" || next.value === "let")) {
800
- const stmt = this.parseVariableDeclaration();
801
- if (stmt.type === "FunctionDeclaration") {
802
- stmt.attributes = attributes;
803
- stmt.line.start = start.line.start;
804
- stmt.column.start = start.column.start;
805
- }
806
- return stmt;
807
- }
808
799
  if (next.type === "Keyword" && next.value === "function") {
809
- const stmt = this.parseFunctionDeclarationStatement();
800
+ const stmt = this.parseFunctionStatement();
810
801
  stmt.attributes = attributes;
811
802
  stmt.line.start = start.line.start;
812
803
  stmt.column.start = start.column.start;
813
804
  return stmt;
814
805
  }
815
- throw new ParseError("Expected 'function', 'const', or 'let' after attribute", next.line.start, next.column.start);
806
+ throw new ParseError("Expected 'function' after an attribute", next.line.start, next.column.start);
816
807
  }
817
808
  if (t.type === "Keyword") {
818
809
  switch (t.value) {
@@ -830,7 +821,7 @@ var Parser = class {
830
821
  case "for":
831
822
  return this.parseForStatement();
832
823
  case "function":
833
- return this.parseFunctionDeclarationStatement();
824
+ return this.parseFunctionStatement();
834
825
  case "return":
835
826
  return this.parseReturnStatement();
836
827
  case "import":
@@ -852,6 +843,9 @@ var Parser = class {
852
843
  }
853
844
  if (t.type === "Identifier" && t.value === "declare") {
854
845
  const p1 = this.peek(1);
846
+ if (p1.type === "Identifier" && p1.value === "class" && this.peek(2).type === "Identifier") {
847
+ return this.parseDeclareClassStatement();
848
+ }
855
849
  if (p1.type === "Identifier" || p1.type === "Keyword" && p1.value === "function") {
856
850
  return this.parseDeclareStatement();
857
851
  }
@@ -893,26 +887,66 @@ var Parser = class {
893
887
  const valueType = this.parseType();
894
888
  return { type: "DeclareStatement", name: nameTok.value, id: tokenIdentifier(nameTok), valueType, ...spanFrom(start, this.previous()) };
895
889
  }
890
+ /** A declared type's name. It may be qualified once — `Enum.Material` —
891
+ * which is how a definitions file names types under a namespace, and how
892
+ * they are then written (`const m: Enum.Material`). */
893
+ parseTypeName() {
894
+ const first = this.expectIdentifier();
895
+ if (this.checkPunctuator(".") && this.peek(1).type === "Identifier") {
896
+ this.advance();
897
+ const second = this.expectIdentifier();
898
+ return { type: "Identifier", name: `${first.value}.${second.value}`, ...spanFrom(first, second) };
899
+ }
900
+ return tokenIdentifier(first);
901
+ }
902
+ // `declare class Name extends Base { member: T, ... }`
903
+ parseDeclareClassStatement() {
904
+ const start = this.current();
905
+ this.advance();
906
+ this.advance();
907
+ const name = this.parseTypeName();
908
+ let superclass;
909
+ if (this.checkIdentifierValue("extends")) {
910
+ this.advance();
911
+ const base = this.parseType();
912
+ if (base.type !== "TypeReference") this.error("A class can only extend another class, written by name");
913
+ superclass = base;
914
+ }
915
+ if (!this.checkPunctuator("{")) this.error("Expected '{' to start the class body");
916
+ const body = this.parseTableType();
917
+ if (body.type !== "TableTypeNode") this.error("A class body lists members ('name: T'), not a mapped type");
918
+ return { type: "DeclareClassStatement", name, superclass, body, ...spanFrom(start, this.previous()) };
919
+ }
896
920
  // `import { a, b as c } from '...'` / `import Default from '...'` /
897
921
  // `import Default, { a } from '...'`. Compiled away entirely by the
898
922
  // bundler — never survives into emitted Luau.
899
923
  parseImportStatement() {
900
924
  const start = this.current();
901
925
  this.advance();
926
+ const next = this.peek(1);
927
+ const isTypeOnly = this.checkIdentifierValue("type") && (next.type === "Punctuator" && next.value === "{" || next.type === "Operator" && next.value === "*" || next.type === "Identifier");
928
+ if (isTypeOnly) this.advance();
902
929
  let defaultImport;
903
930
  const specifiers = [];
904
- if (this.checkType("Identifier")) {
905
- const nameTok = this.expectIdentifier();
906
- defaultImport = { type: "Identifier", name: nameTok.value, ...spanFrom(nameTok, nameTok) };
907
- if (this.matchPunctuator(",")) {
908
- this.expectPunctuator("{");
909
- this.parseImportSpecifierList(specifiers);
910
- this.expectPunctuator("}");
931
+ let namespaceImport;
932
+ const parseBindings = () => {
933
+ if (this.checkOperator("*")) {
934
+ this.advance();
935
+ if (!this.checkKeyword("as")) this.error("Expected 'as' after 'import *'");
936
+ this.advance();
937
+ namespaceImport = this.parseIdentifier();
938
+ return;
911
939
  }
912
- } else {
913
940
  this.expectPunctuator("{");
914
941
  this.parseImportSpecifierList(specifiers);
915
942
  this.expectPunctuator("}");
943
+ };
944
+ if (this.checkType("Identifier")) {
945
+ const nameTok = this.expectIdentifier();
946
+ defaultImport = { type: "Identifier", name: nameTok.value, ...spanFrom(nameTok, nameTok) };
947
+ if (this.matchPunctuator(",")) parseBindings();
948
+ } else {
949
+ parseBindings();
916
950
  }
917
951
  if (!this.checkKeyword("from")) {
918
952
  this.error("Expected 'from' in import statement");
@@ -929,7 +963,15 @@ var Parser = class {
929
963
  raw: sourceTok.raw,
930
964
  ...spanFrom(sourceTok, sourceTok)
931
965
  };
932
- return { type: "ImportStatement", defaultImport, specifiers, source, ...spanFrom(start, this.previous()) };
966
+ return {
967
+ type: "ImportStatement",
968
+ defaultImport,
969
+ namespaceImport,
970
+ specifiers,
971
+ source,
972
+ isTypeOnly: isTypeOnly || void 0,
973
+ ...spanFrom(start, this.previous())
974
+ };
933
975
  }
934
976
  parseImportSpecifierList(out) {
935
977
  if (this.checkPunctuator("}")) return;
@@ -965,7 +1007,7 @@ var Parser = class {
965
1007
  ...spanFrom(sourceTok, sourceTok)
966
1008
  };
967
1009
  }
968
- // `export const ...` / `export let ...` / `export const function ...` /
1010
+ // `export const ...` / `export let ...` / `export function ...` /
969
1011
  // `export type ...` / `export default <expr>`
970
1012
  parseExportStatement() {
971
1013
  const start = this.current();
@@ -983,6 +1025,11 @@ var Parser = class {
983
1025
  const declaration = this.parseVariableDeclaration();
984
1026
  return { type: "ExportStatement", declaration, ...spanFrom(start, this.previous()) };
985
1027
  }
1028
+ if (this.checkKeyword("function")) {
1029
+ const declaration = this.parseFunctionStatement();
1030
+ if (declaration.type !== "FunctionDeclaration") this.error("An exported function needs a plain name: 'export function name()'");
1031
+ return { type: "ExportStatement", declaration, ...spanFrom(start, this.previous()) };
1032
+ }
986
1033
  if (this.checkPunctuator("{")) {
987
1034
  this.advance();
988
1035
  const specifiers = [];
@@ -1006,15 +1053,15 @@ var Parser = class {
1006
1053
  const source = this.parseModuleSource();
1007
1054
  return { type: "ExportAllStatement", source, ...spanFrom(start, this.previous()) };
1008
1055
  }
1009
- this.error("Expected 'const', 'let', 'type', 'default', '{' or '*' after 'export'");
1056
+ this.error("Expected 'const', 'let', 'function', 'type', 'default', '{' or '*' after 'export'");
1010
1057
  }
1011
- // `const x = ...` / `let x, y = ...` / `const function f() ... end`.
1058
+ // `const x = ...` / `let x, y = ...`.
1012
1059
  // luaut has no `local` — `const` bindings are immutable, `let` mutable.
1013
1060
  parseVariableDeclaration() {
1014
1061
  const start = this.current();
1015
1062
  const kind = this.advance().value;
1016
- if (this.matchKeyword("function")) {
1017
- return this.parseFunctionDeclarationRest(start, kind);
1063
+ if (this.checkKeyword("function")) {
1064
+ this.error(`A function is declared as 'function name()'; '${kind}' does not apply to functions`);
1018
1065
  }
1019
1066
  const names = [this.parseBindingTarget(true)];
1020
1067
  while (this.matchPunctuator(",")) {
@@ -1028,31 +1075,6 @@ var Parser = class {
1028
1075
  }
1029
1076
  return { type: "VariableDeclaration", kind, names, init, ...spanFrom(start, this.previous()) };
1030
1077
  }
1031
- /** `const/let function` — `function` already consumed. Collects TS-style
1032
- * overload signatures. */
1033
- parseFunctionDeclarationRest(start, kind) {
1034
- const name = this.parseIdentifier();
1035
- const signatures = [];
1036
- while (true) {
1037
- const head = this.parseFunctionHead();
1038
- if (this.isOverloadContinuation(name.name, kind)) {
1039
- signatures.push(this.headToSignature(head));
1040
- this.advance();
1041
- this.expectKeyword("function");
1042
- this.parseIdentifier();
1043
- continue;
1044
- }
1045
- const func = this.headToBody(head);
1046
- return {
1047
- type: "FunctionDeclaration",
1048
- kind,
1049
- name,
1050
- func,
1051
- signatures: signatures.length ? signatures : void 0,
1052
- ...spanFrom(start, this.previous())
1053
- };
1054
- }
1055
- }
1056
1078
  parseIfStatement() {
1057
1079
  const start = this.current();
1058
1080
  this.expectKeyword("if");
@@ -1142,7 +1164,9 @@ var Parser = class {
1142
1164
  ...spanFrom(start, this.previous())
1143
1165
  };
1144
1166
  }
1145
- parseFunctionDeclarationStatement() {
1167
+ /** `function name() end` declares `name`; `function a.b() end` and
1168
+ * `function T:m() end` define a member. */
1169
+ parseFunctionStatement() {
1146
1170
  const start = this.current();
1147
1171
  this.expectKeyword("function");
1148
1172
  const target = this.parseFunctionName();
@@ -1158,6 +1182,15 @@ var Parser = class {
1158
1182
  continue;
1159
1183
  }
1160
1184
  const func = this.headToBody(head);
1185
+ if (simpleName !== void 0) {
1186
+ return {
1187
+ type: "FunctionDeclaration",
1188
+ name: target.base,
1189
+ func,
1190
+ signatures: signatures.length ? signatures : void 0,
1191
+ ...spanFrom(start, this.previous())
1192
+ };
1193
+ }
1161
1194
  if (isMethod) {
1162
1195
  func.params.unshift({ type: "FunctionParameter", name: "self", ...spanFrom(target, target) });
1163
1196
  func.isMethod = true;
@@ -1174,12 +1207,8 @@ var Parser = class {
1174
1207
  }
1175
1208
  /** After a bodyless function head, is the next token the start of another
1176
1209
  * declaration for the same simple `name` (making the head an overload
1177
- * signature rather than an implementation)? `kind` is set for a
1178
- * `const/let function` group, undefined for a bare `function` group. */
1179
- isOverloadContinuation(name, kind) {
1180
- if (kind) {
1181
- return this.checkKeyword(kind) && this.peek(1).type === "Keyword" && this.peek(1).value === "function" && this.peek(2).type === "Identifier" && this.peek(2).value === name;
1182
- }
1210
+ * signature rather than an implementation)? */
1211
+ isOverloadContinuation(name) {
1183
1212
  return this.checkKeyword("function") && this.peek(1).type === "Identifier" && this.peek(1).value === name;
1184
1213
  }
1185
1214
  parseFunctionName() {
@@ -1223,8 +1252,7 @@ var Parser = class {
1223
1252
  parseTypeAliasStatement() {
1224
1253
  const start = this.current();
1225
1254
  this.advance();
1226
- const nameTok = this.expectIdentifier();
1227
- const name = { type: "Identifier", name: nameTok.value, ...spanFrom(nameTok, nameTok) };
1255
+ const name = this.parseTypeName();
1228
1256
  let generics = [];
1229
1257
  if (this.checkOperator("<")) {
1230
1258
  generics = this.parseGenericTypeParameterList();
@@ -2487,7 +2515,7 @@ var Analyzer = class {
2487
2515
  };
2488
2516
  }
2489
2517
  // ---------------- declaration / resolution primitives ----------------
2490
- declare(scope, name, kind, node, isConst = false) {
2518
+ declare(scope, name, kind, node, isConst = false, declaredBy) {
2491
2519
  if (scope.declarations.has(name) && scope !== this.globalScope) {
2492
2520
  this.diagnostics.push({
2493
2521
  node,
@@ -2496,7 +2524,7 @@ var Analyzer = class {
2496
2524
  });
2497
2525
  }
2498
2526
  const id = this.nextId++;
2499
- this.bindings.set(id, { id, name, kind, declarationNode: node, references: [], isConst });
2527
+ this.bindings.set(id, { id, name, kind, declarationNode: node, references: [], isConst, declaredBy });
2500
2528
  scope.declarations.set(name, id);
2501
2529
  return id;
2502
2530
  }
@@ -2530,6 +2558,19 @@ var Analyzer = class {
2530
2558
  const id = this.resolve(scope, identifier.name);
2531
2559
  this.bindingOf.set(identifier, id);
2532
2560
  this.bindings.get(id).references.push(identifier);
2561
+ if (this.typeQueryDepth === 0) this.checkTypeOnly(id, identifier);
2562
+ }
2563
+ /** Inside `typeof x` in a type, where a type-only import may be named. */
2564
+ typeQueryDepth = 0;
2565
+ /** A name from `import type` used as a value. */
2566
+ checkTypeOnly(id, node) {
2567
+ const b = this.bindings.get(id);
2568
+ if (b.declaredBy !== "type") return;
2569
+ this.diagnostics.push({
2570
+ node,
2571
+ message: `'${b.name}' is imported with 'import type' and can only be used as a type`,
2572
+ kind: "type-only"
2573
+ });
2533
2574
  }
2534
2575
  /** For assignment-like targets (`x = ...`, `function foo() end`): if
2535
2576
  * this resolved to a global with no declaration site yet, treat this
@@ -2546,14 +2587,34 @@ var Analyzer = class {
2546
2587
  this.bindingOf.set(identifier, id);
2547
2588
  this.bindings.get(id).references.push(identifier);
2548
2589
  this.recordPossibleGlobalDefinition(id, identifier);
2590
+ this.checkTypeOnly(id, identifier);
2549
2591
  this.checkConstAssign(id, identifier);
2550
2592
  }
2593
+ /** `Module.x = 1` through `import * as Module`: a module's exports belong
2594
+ * to it and are read-only, as in ES modules. Deeper writes (`Module.x.y`)
2595
+ * change the value, not the module, and are fine. */
2596
+ checkModuleWrite(target) {
2597
+ if (target.type !== "MemberExpression" && target.type !== "IndexExpression") return;
2598
+ if (target.object.type !== "Identifier") return;
2599
+ const id = this.bindingOf.get(target.object);
2600
+ if (id !== void 0 && this.bindings.get(id).declaredBy === "namespace") {
2601
+ this.moduleWriteError(target.object.name, target);
2602
+ }
2603
+ }
2604
+ moduleWriteError(name, node) {
2605
+ this.diagnostics.push({
2606
+ node,
2607
+ message: `Cannot assign to a member of '${name}' \u2014 a module's exports are read-only`,
2608
+ kind: "const-assign"
2609
+ });
2610
+ }
2551
2611
  checkConstAssign(id, node) {
2552
2612
  const b = this.bindings.get(id);
2613
+ if (b.declaredBy === "type") return;
2553
2614
  if (b.isConst) {
2554
2615
  this.diagnostics.push({
2555
2616
  node,
2556
- message: `Cannot assign to '${b.name}' \u2014 it is a const`,
2617
+ message: `Cannot assign to '${b.name}' \u2014 it is ${b.declaredBy === "import" || b.declaredBy === "namespace" ? "an import" : b.declaredBy === "function" ? "a function" : "a const"}`,
2557
2618
  kind: "const-assign"
2558
2619
  });
2559
2620
  }
@@ -2594,6 +2655,7 @@ var Analyzer = class {
2594
2655
  const id = this.resolve(scope, t.name);
2595
2656
  this.bindingOf.set(t, id);
2596
2657
  this.recordPossibleGlobalDefinition(id, t);
2658
+ this.checkTypeOnly(id, t);
2597
2659
  this.checkConstAssign(id, t);
2598
2660
  return;
2599
2661
  }
@@ -2637,7 +2699,7 @@ var Analyzer = class {
2637
2699
  return;
2638
2700
  }
2639
2701
  case "FunctionDeclaration": {
2640
- this.declare(scope, stmt.name.name, "local", stmt.name, stmt.kind === "const");
2702
+ this.declare(scope, stmt.name.name, "local", stmt.name, true, "function");
2641
2703
  for (const signature of stmt.signatures ?? []) this.visitSignature(signature, scope);
2642
2704
  this.visitFunctionBody(stmt.func, scope);
2643
2705
  return;
@@ -2647,6 +2709,11 @@ var Analyzer = class {
2647
2709
  this.referenceAsAssignmentTarget(scope, stmt.target.base);
2648
2710
  } else {
2649
2711
  this.reference(scope, stmt.target.base);
2712
+ const id = this.bindingOf.get(stmt.target.base);
2713
+ const depth = stmt.target.path.length + (stmt.target.method ? 1 : 0);
2714
+ if (id !== void 0 && depth === 1 && this.bindings.get(id).declaredBy === "namespace") {
2715
+ this.moduleWriteError(stmt.target.base.name, stmt.target);
2716
+ }
2650
2717
  }
2651
2718
  for (const signature of stmt.signatures ?? []) this.visitSignature(signature, scope);
2652
2719
  this.visitFunctionBody(stmt.func, scope, stmt.isMethod);
@@ -2661,6 +2728,7 @@ var Analyzer = class {
2661
2728
  this.assignPattern(scope, target);
2662
2729
  } else {
2663
2730
  this.visitExpression(target, scope);
2731
+ this.checkModuleWrite(target);
2664
2732
  }
2665
2733
  }
2666
2734
  return;
@@ -2673,6 +2741,7 @@ var Analyzer = class {
2673
2741
  if (id !== void 0) this.checkConstAssign(id, stmt.target);
2674
2742
  } else {
2675
2743
  this.visitExpression(stmt.target, scope);
2744
+ this.checkModuleWrite(stmt.target);
2676
2745
  }
2677
2746
  return;
2678
2747
  }
@@ -2726,16 +2795,23 @@ var Analyzer = class {
2726
2795
  case "DeclareStatement":
2727
2796
  this.visitType(stmt.valueType, scope);
2728
2797
  return;
2798
+ case "DeclareClassStatement":
2799
+ this.visitType(stmt.body, scope);
2800
+ return;
2729
2801
  case "TypeAliasStatement":
2730
2802
  case "ExportTypeAliasStatement":
2731
2803
  this.visitType(stmt.definition, scope);
2732
2804
  return;
2733
2805
  case "ImportStatement": {
2806
+ const typeOnly = stmt.isTypeOnly ? "type" : void 0;
2734
2807
  if (stmt.defaultImport) {
2735
- this.declare(scope, stmt.defaultImport.name, "local", stmt.defaultImport);
2808
+ this.declare(scope, stmt.defaultImport.name, "local", stmt.defaultImport, true, typeOnly ?? "import");
2809
+ }
2810
+ if (stmt.namespaceImport) {
2811
+ this.declare(scope, stmt.namespaceImport.name, "local", stmt.namespaceImport, true, typeOnly ?? "namespace");
2736
2812
  }
2737
2813
  for (const spec of stmt.specifiers) {
2738
- this.declare(scope, spec.local.name, "local", spec.local);
2814
+ this.declare(scope, spec.local.name, "local", spec.local, true, typeOnly ?? "import");
2739
2815
  }
2740
2816
  return;
2741
2817
  }
@@ -2794,7 +2870,12 @@ var Analyzer = class {
2794
2870
  return;
2795
2871
  }
2796
2872
  if (value.type === "TypeofTypeNode") {
2797
- this.visitExpression(value.expression, scope);
2873
+ this.typeQueryDepth++;
2874
+ try {
2875
+ this.visitExpression(value.expression, scope);
2876
+ } finally {
2877
+ this.typeQueryDepth--;
2878
+ }
2798
2879
  return;
2799
2880
  }
2800
2881
  for (const key of Object.keys(value)) {
@@ -2899,6 +2980,9 @@ function analyzeScopes(program, options = {}) {
2899
2980
  }
2900
2981
 
2901
2982
  // src/ast/typeModel.ts
2983
+ function isClassType(t) {
2984
+ return t.kind === "object" && t.class !== void 0;
2985
+ }
2902
2986
  function typeParam(name, constraint, isConst) {
2903
2987
  return { kind: "typeParam", name, constraint, isConst };
2904
2988
  }
@@ -2958,10 +3042,16 @@ function substitute(t, subst) {
2958
3042
  }
2959
3043
  case "function": {
2960
3044
  const inner = t.typeParams ? new Map([...subst].filter(([k]) => !t.typeParams.includes(k))) : subst;
3045
+ let params = t.params.map((p) => ({ ...p, type: substitute(p.type, inner) }));
3046
+ let varargs = t.varargs && substitute(t.varargs, inner);
3047
+ if (varargs?.kind === "tuple" && varargs.isPack) {
3048
+ params = [...params, ...varargs.elements.map((type) => ({ type }))];
3049
+ varargs = void 0;
3050
+ }
2961
3051
  return {
2962
3052
  kind: "function",
2963
- params: t.params.map((p) => ({ ...p, type: substitute(p.type, inner) })),
2964
- varargs: t.varargs && substitute(t.varargs, inner),
3053
+ params,
3054
+ varargs,
2965
3055
  returns: substitute(t.returns, inner),
2966
3056
  typeParams: t.typeParams,
2967
3057
  predicate: t.predicate && {
@@ -3041,7 +3131,8 @@ function unify(param, arg, vars, out) {
3041
3131
  }
3042
3132
  return;
3043
3133
  case "object":
3044
- if (arg.kind === "object") {
3134
+ if (param.class) return;
3135
+ if (arg.kind === "object" && !arg.class) {
3045
3136
  for (const [k, pv] of param.properties) {
3046
3137
  const av = arg.properties.get(k);
3047
3138
  if (av) unify(pv.type, av.type, vars, out);
@@ -3124,7 +3215,7 @@ function widen(t) {
3124
3215
  case "tuple":
3125
3216
  return tuple(t.elements.map(widen), t.isPack);
3126
3217
  case "object": {
3127
- if (t.frozen) return t;
3218
+ if (t.frozen || t.class) return t;
3128
3219
  const entries = [];
3129
3220
  for (const [k, v] of t.properties) entries.push([k, { ...v, type: widen(v.type) }]);
3130
3221
  const w = objectType(entries, t.indexer && { key: t.indexer.key, value: widen(t.indexer.value) });
@@ -3151,6 +3242,10 @@ function isAssignable(rawA, rawB) {
3151
3242
  if (expandAlias) {
3152
3243
  if (a.kind === "genericRef" && b.kind !== "genericRef") a = expandAlias(a);
3153
3244
  else if (b.kind === "genericRef" && a.kind !== "genericRef") b = expandAlias(b);
3245
+ else if (a.kind === "genericRef" && b.kind === "genericRef" && a.name !== b.name) {
3246
+ a = expandAlias(a);
3247
+ b = expandAlias(b);
3248
+ }
3154
3249
  if (a === b) return true;
3155
3250
  }
3156
3251
  for (let i = 0; i < comparing.length; i += 2) {
@@ -3206,6 +3301,8 @@ function isAssignableInner(a, b) {
3206
3301
  }
3207
3302
  if (a.kind === "object") {
3208
3303
  if (b.kind !== "object") return false;
3304
+ if (b.class) return a.class !== void 0 && a.class.ancestors.includes(b.class.name);
3305
+ if (a.class && (b.indexer || b.properties.size === 0)) return false;
3209
3306
  for (const [name, bp] of b.properties) {
3210
3307
  const ap = a.properties.get(name);
3211
3308
  if (!ap) {
@@ -3348,6 +3445,7 @@ function containsFreeTypeParam(t, seen, bound) {
3348
3445
  case "intersection":
3349
3446
  return t.types.some((m) => containsTypeParam(m, seen, bound));
3350
3447
  case "object":
3448
+ if (t.class) return false;
3351
3449
  return [...t.properties.values()].some((v) => containsTypeParam(v.type, seen, bound)) || !!t.indexer && (containsTypeParam(t.indexer.key, seen, bound) || containsTypeParam(t.indexer.value, seen, bound));
3352
3450
  case "function": {
3353
3451
  const inner = t.typeParams?.length ? /* @__PURE__ */ new Set([...bound, ...t.typeParams]) : bound;
@@ -3546,7 +3644,7 @@ function mergeObjectMembers(types) {
3546
3644
  return expanded !== void 0 && expanded !== t && collect(expanded);
3547
3645
  }
3548
3646
  if (t.kind === "intersection") return t.types.every(collect);
3549
- if (t.kind === "object") {
3647
+ if (t.kind === "object" && !t.class) {
3550
3648
  objects.push(t);
3551
3649
  return true;
3552
3650
  }
@@ -3758,6 +3856,62 @@ function keepsLiterals(paramType) {
3758
3856
  const members = paramType.constraint.kind === "union" ? paramType.constraint.types : [paramType.constraint];
3759
3857
  return members.some((m) => m.kind === "literal");
3760
3858
  }
3859
+ var AliasMap = class extends Map {
3860
+ pending = /* @__PURE__ */ new Map();
3861
+ defer(name, resolve5) {
3862
+ super.delete(name);
3863
+ this.pending.set(name, resolve5);
3864
+ }
3865
+ get(name) {
3866
+ const resolved = super.get(name);
3867
+ if (resolved !== void 0) return resolved;
3868
+ const resolve5 = this.pending.get(name);
3869
+ if (!resolve5) return void 0;
3870
+ this.pending.delete(name);
3871
+ const type = resolve5();
3872
+ super.set(name, type);
3873
+ return type;
3874
+ }
3875
+ has(name) {
3876
+ return super.has(name) || (this.pending?.has(name) ?? false);
3877
+ }
3878
+ set(name, type) {
3879
+ this.pending?.delete(name);
3880
+ return super.set(name, type);
3881
+ }
3882
+ delete(name) {
3883
+ const deferred = this.pending?.delete(name) ?? false;
3884
+ return super.delete(name) || deferred;
3885
+ }
3886
+ get size() {
3887
+ return super.size + (this.pending?.size ?? 0);
3888
+ }
3889
+ keys() {
3890
+ return [...super.keys(), ...this.pending?.keys() ?? []][Symbol.iterator]();
3891
+ }
3892
+ entries() {
3893
+ return [...this.keys()].map((name) => [name, this.get(name)])[Symbol.iterator]();
3894
+ }
3895
+ values() {
3896
+ return [...this.keys()].map((name) => this.get(name))[Symbol.iterator]();
3897
+ }
3898
+ forEach(callback, thisArg) {
3899
+ for (const [name, type] of this.entries()) callback.call(thisArg, type, name, this);
3900
+ }
3901
+ [Symbol.iterator]() {
3902
+ return this.entries();
3903
+ }
3904
+ };
3905
+ var METAMETHODS = {
3906
+ "+": "__add",
3907
+ "-": "__sub",
3908
+ "*": "__mul",
3909
+ "/": "__div",
3910
+ "//": "__idiv",
3911
+ "%": "__mod",
3912
+ "^": "__pow",
3913
+ "..": "__concat"
3914
+ };
3761
3915
  function posKey(name, line, column) {
3762
3916
  return `${name}@${line}:${column}`;
3763
3917
  }
@@ -3778,9 +3932,12 @@ var TypeAnalyzer = class {
3778
3932
  expectedTypeOf = /* @__PURE__ */ new Map();
3779
3933
  /** Public: each alias resolved once (generic aliases keep their params as
3780
3934
  * `typeParam` nodes in the body). */
3781
- aliases = /* @__PURE__ */ new Map();
3935
+ aliases = new AliasMap();
3782
3936
  /** Uninstantiated alias definitions, for `Name<Args>` instantiation. */
3783
3937
  aliasDefs = /* @__PURE__ */ new Map();
3938
+ /** See `resolveClass`. */
3939
+ classTypes = /* @__PURE__ */ new WeakMap();
3940
+ classMembers = /* @__PURE__ */ new WeakMap();
3784
3941
  /** Generic parameters currently in lexical scope (alias body / generic fn),
3785
3942
  * with their `extends` constraints resolved. */
3786
3943
  typeParamScope = [];
@@ -3877,6 +4034,13 @@ var TypeAnalyzer = class {
3877
4034
  if (stmt.type !== "ImportStatement") continue;
3878
4035
  const exports = this.moduleFor(stmt.source.value);
3879
4036
  if (!exports) continue;
4037
+ if (stmt.namespaceImport) {
4038
+ for (const [name, exported] of exports.types) {
4039
+ const qualified = `${stmt.namespaceImport.name}.${name}`;
4040
+ this.importedTypes.set(qualified, exported);
4041
+ this.aliases.set(qualified, exported.type);
4042
+ }
4043
+ }
3880
4044
  for (const s of stmt.specifiers) {
3881
4045
  const exported = exports.types.get(s.imported.name);
3882
4046
  if (exported) {
@@ -3890,24 +4054,123 @@ var TypeAnalyzer = class {
3890
4054
  for (const stmt of block.statements) {
3891
4055
  const alias = stmt.type === "TypeAliasStatement" ? stmt : stmt.type === "ExportTypeAliasStatement" ? stmt.alias : void 0;
3892
4056
  if (alias) this.aliasDefs.set(alias.name.name, { params: alias.generics, node: alias.definition });
4057
+ if (stmt.type === "DeclareClassStatement") {
4058
+ this.aliasDefs.set(stmt.name.name, { params: [], node: stmt.body, class: stmt });
4059
+ }
3893
4060
  }
3894
4061
  }
3895
- /** Seed global types from `declare` statements. Repeating a name builds an
3896
- * *overload set* (an intersection, in declaration order) rather than
3897
- * replacing which is how `typeof` gets one signature per result string. */
4062
+ /** A non-generic definition's type. */
4063
+ resolveDef(def) {
4064
+ return def.class ? this.classType(def.class) : this.resolveType(def.node);
4065
+ }
4066
+ /** One type per class declaration, so every mention of a class is the same
4067
+ * object — its own members included, which refer back to it. */
4068
+ classType(stmt) {
4069
+ return this.classTypes.get(stmt) ?? this.resolveClass(stmt);
4070
+ }
4071
+ /** A class's members are resolved the first time anyone asks for
4072
+ * `properties` — its own from its body, the inherited ones from its
4073
+ * superclass.
4074
+ *
4075
+ * Both have to wait. A definitions file for a whole engine declares
4076
+ * thousands of classes that all refer to one another; resolving each body
4077
+ * as soon as the class is named would resolve every class on every
4078
+ * analysis, when a script touches a handful. And classes refer to one
4079
+ * another constantly — `Object.IsA` mentions a map of every class, each
4080
+ * of which extends `Object` — so while one class resolves, one it extends
4081
+ * may itself be half-resolved; copying its members then would miss some
4082
+ * for good. */
4083
+ resolveClass(stmt) {
4084
+ const name = stmt.name.name;
4085
+ const { ancestors, cyclic } = this.classChain(stmt);
4086
+ const superclass = !cyclic && ancestors.length > 1 ? this.aliasDefs.get(ancestors[1])?.class : void 0;
4087
+ let own;
4088
+ let resolvingOwn = false;
4089
+ const ownMembers = () => {
4090
+ if (own || resolvingOwn) return own;
4091
+ resolvingOwn = true;
4092
+ try {
4093
+ own = this.resolveType(stmt.body);
4094
+ } finally {
4095
+ resolvingOwn = false;
4096
+ }
4097
+ return own;
4098
+ };
4099
+ let complete;
4100
+ const members = () => {
4101
+ if (complete) return complete;
4102
+ const mine = ownMembers();
4103
+ if (!mine) return void 0;
4104
+ const base = superclass ? this.classMembers.get(this.classType(superclass))?.() : void 0;
4105
+ if (superclass && !base) return void 0;
4106
+ return complete = {
4107
+ properties: new Map([...base?.properties ?? [], ...mine.properties]),
4108
+ indexer: mine.indexer ?? base?.indexer
4109
+ };
4110
+ };
4111
+ const type = { kind: "object", name, class: { name, superclass: superclass?.name.name, ancestors } };
4112
+ Object.defineProperties(type, {
4113
+ properties: { enumerable: true, get: () => members()?.properties ?? own?.properties ?? /* @__PURE__ */ new Map() },
4114
+ indexer: { enumerable: true, get: () => members()?.indexer ?? own?.indexer }
4115
+ });
4116
+ this.classTypes.set(stmt, type);
4117
+ this.classMembers.set(type, members);
4118
+ if (this.program.body.statements.includes(stmt)) ownMembers();
4119
+ return type;
4120
+ }
4121
+ /** `extends` must name a class, and the chain must end. */
4122
+ checkClass(stmt) {
4123
+ if (!stmt.superclass || !this.emitDiagnostics) return;
4124
+ const base = stmt.superclass.base;
4125
+ if (!this.aliasDefs.get(base)?.class) {
4126
+ const known = this.aliasDefs.has(base) || this.importedTypes.has(base);
4127
+ this.diagnostics.push({
4128
+ node: stmt.superclass,
4129
+ message: known ? `'${base}' is not a class; a class can only extend another class` : `Cannot find class '${base}'`
4130
+ });
4131
+ } else if (this.classChain(stmt).cyclic) {
4132
+ this.diagnostics.push({ node: stmt.superclass, message: `'${stmt.name.name}' cannot extend itself` });
4133
+ }
4134
+ }
4135
+ /** The class and the classes it extends, nearest first, read from the
4136
+ * declarations — no type has to be resolved to know them. The walk stops
4137
+ * at a superclass that is not a class. */
4138
+ classChain(stmt) {
4139
+ const ancestors = [stmt.name.name];
4140
+ for (let cls = stmt; cls?.superclass; ) {
4141
+ const base = cls.superclass.base;
4142
+ if (ancestors.includes(base)) return { ancestors, cyclic: true };
4143
+ cls = this.aliasDefs.get(base)?.class;
4144
+ if (!cls) break;
4145
+ ancestors.push(base);
4146
+ }
4147
+ return { ancestors, cyclic: false };
4148
+ }
4149
+ /** Seed global types from `declare` statements. Repeating a function name
4150
+ * builds an *overload set* (an intersection, in declaration order) rather
4151
+ * than replacing — which is how `typeof` gets one signature per result
4152
+ * string. Any other value is simply redeclared: a sourcemap's
4153
+ * `declare script: <this file's instance>` replaces the library's
4154
+ * `declare script: LuaSourceContainer`. */
3898
4155
  harvestDeclares(block) {
3899
4156
  for (const stmt of block.statements) {
3900
4157
  if (stmt.type !== "DeclareStatement") continue;
3901
4158
  const t = this.resolveType(stmt.valueType);
3902
4159
  const prev = this.libGlobalTypes.get(stmt.name);
3903
- this.libGlobalTypes.set(stmt.name, prev ? intersection([prev, t]) : t);
4160
+ const overload = prev && stmt.valueType.type === "FunctionTypeNode" && (prev.kind === "function" || prev.kind === "intersection");
4161
+ this.libGlobalTypes.set(stmt.name, overload ? intersection([prev, t]) : t);
3904
4162
  }
3905
4163
  }
3906
4164
  resolveAllAliases() {
3907
4165
  for (const [name, def] of this.aliasDefs) {
4166
+ if (def.class && !this.program.body.statements.includes(def.class)) {
4167
+ const cls = def.class;
4168
+ this.aliases.defer(name, () => this.classType(cls));
4169
+ continue;
4170
+ }
3908
4171
  if (containsTypeQuery(def.node)) continue;
3909
4172
  this.withTypeParams(def.params, () => {
3910
- this.aliases.set(name, this.resolveType(def.node));
4173
+ this.aliases.set(name, this.resolveDef(def));
3911
4174
  });
3912
4175
  }
3913
4176
  }
@@ -3917,7 +4180,7 @@ var TypeAnalyzer = class {
3917
4180
  for (const [name, def] of this.aliasDefs) {
3918
4181
  if (this.aliases.has(name)) continue;
3919
4182
  this.withTypeParams(def.params, () => {
3920
- this.aliases.set(name, this.resolveType(def.node));
4183
+ this.aliases.set(name, this.resolveDef(def));
3921
4184
  });
3922
4185
  }
3923
4186
  return this.aliases;
@@ -3945,10 +4208,7 @@ var TypeAnalyzer = class {
3945
4208
  /** Instantiate a generic alias: `Box<number>` -> `{ value: number }`. */
3946
4209
  instantiateAlias(def, args) {
3947
4210
  if (this.instantiationDepth > 20) return unknownType;
3948
- const subst = /* @__PURE__ */ new Map();
3949
- def.params.forEach((p, i) => {
3950
- subst.set(p.name, args[i] ?? (p.default ? this.resolveType(p.default) : unknownType));
3951
- });
4211
+ const subst = this.bindTypeArguments(def.params, args);
3952
4212
  this.instantiationDepth++;
3953
4213
  try {
3954
4214
  const body = this.withTypeParams(def.params, () => this.resolveType(def.node));
@@ -3957,6 +4217,34 @@ var TypeAnalyzer = class {
3957
4217
  this.instantiationDepth--;
3958
4218
  }
3959
4219
  }
4220
+ /** Pair written type arguments with the parameters they instantiate. A
4221
+ * pack parameter (`T...`) takes every argument from its position on, as
4222
+ * one pack: `Signal<Instance, string>` binds `T` to `(Instance, string)`,
4223
+ * and `Signal<()>` to the empty pack. Left out, a parameter takes its
4224
+ * default (`T... = ...any` is `any`), or `unknown`. */
4225
+ bindTypeArguments(params, args) {
4226
+ const subst = /* @__PURE__ */ new Map();
4227
+ params.forEach((p, i) => {
4228
+ let arg = args[i];
4229
+ if (p.isPack && i < args.length) {
4230
+ const rest = args.slice(i);
4231
+ const single = rest.length === 1 ? rest[0] : void 0;
4232
+ arg = single && (single.kind === "tuple" && single.isPack || single.kind === "typeParam" || single.kind === "any") ? single : tuple([...rest], true);
4233
+ }
4234
+ subst.set(p.name, arg ?? (p.default ? this.resolveType(p.default) : unknownType));
4235
+ });
4236
+ return subst;
4237
+ }
4238
+ /** An imported type, with its type arguments applied. */
4239
+ importedType(imported, typeArguments) {
4240
+ if (!imported.params.length) return imported.type;
4241
+ const subst = /* @__PURE__ */ new Map();
4242
+ imported.params.forEach((name, i) => {
4243
+ const arg = typeArguments[i];
4244
+ subst.set(name, arg ? this.resolveType(arg) : unknownType);
4245
+ });
4246
+ return this.reduceType(substitute(imported.type, subst));
4247
+ }
3960
4248
  // --------------------------------------------------------
3961
4249
  // TypeNode -> Type
3962
4250
  // --------------------------------------------------------
@@ -4007,17 +4295,17 @@ var TypeAnalyzer = class {
4007
4295
  });
4008
4296
  }
4009
4297
  const imported = this.importedTypes.get(node.base);
4010
- if (imported) {
4011
- if (!imported.params.length) return imported.type;
4012
- const subst = /* @__PURE__ */ new Map();
4013
- imported.params.forEach((name2, i) => {
4014
- const arg = node.typeArguments[i];
4015
- subst.set(name2, arg ? this.resolveType(arg) : unknownType);
4016
- });
4017
- return this.reduceType(substitute(imported.type, subst));
4018
- }
4298
+ if (imported) return this.importedType(imported, node.typeArguments);
4019
4299
  const lib = this.options.libTypes?.[node.base];
4020
4300
  if (lib) return lib;
4301
+ } else if (this.importedTypes.has(name)) {
4302
+ return this.importedType(this.importedTypes.get(name), node.typeArguments);
4303
+ } else if (this.aliasDefs.has(name)) {
4304
+ return this.expand({
4305
+ kind: "genericRef",
4306
+ name,
4307
+ typeArguments: node.typeArguments.map((a) => this.resolveType(a))
4308
+ });
4021
4309
  }
4022
4310
  return {
4023
4311
  kind: "genericRef",
@@ -4144,6 +4432,7 @@ var TypeAnalyzer = class {
4144
4432
  return this.resolveType(node.typeAnnotation);
4145
4433
  case "TypePackNode": {
4146
4434
  if (node.types.length === 1 && !node.hasVarargs) return this.resolveType(node.types[0]);
4435
+ if (!node.types.length && node.varargType) return this.resolveType(node.varargType);
4147
4436
  return tuple(node.types.map((t) => this.resolveType(t)), true);
4148
4437
  }
4149
4438
  }
@@ -4167,7 +4456,7 @@ var TypeAnalyzer = class {
4167
4456
  this.reduceDepth++;
4168
4457
  try {
4169
4458
  const result = this.reduceTypeInner(t);
4170
- this.reduceCache.set(t, result);
4459
+ if (result.kind !== "keyof") this.reduceCache.set(t, result);
4171
4460
  return result;
4172
4461
  } finally {
4173
4462
  this.reduceDepth--;
@@ -4179,6 +4468,7 @@ var TypeAnalyzer = class {
4179
4468
  case "keyof": {
4180
4469
  const target = this.reduceType(t.target);
4181
4470
  if (containsTypeParam(target)) return { kind: "keyof", target };
4471
+ if (target.kind === "genericRef" && this.resolvingAliases.has(target.name)) return t;
4182
4472
  return this.keysOf(target);
4183
4473
  }
4184
4474
  case "indexedAccess": {
@@ -4220,6 +4510,7 @@ var TypeAnalyzer = class {
4220
4510
  t.predicate
4221
4511
  );
4222
4512
  case "object": {
4513
+ if (t.class) return t;
4223
4514
  const entries = [];
4224
4515
  for (const [k, v] of t.properties) entries.push([k, { ...v, type: this.reduceType(v.type) }]);
4225
4516
  const reduced = objectType(entries, t.indexer && {
@@ -4359,6 +4650,7 @@ var TypeAnalyzer = class {
4359
4650
  t.typeParams
4360
4651
  );
4361
4652
  case "object": {
4653
+ if (t.class) return t;
4362
4654
  const entries = [];
4363
4655
  for (const [k, v] of t.properties) entries.push([k, { ...v, type: this.stripInfer(v.type, bindings) }]);
4364
4656
  return objectType(entries, t.indexer && {
@@ -4412,6 +4704,11 @@ var TypeAnalyzer = class {
4412
4704
  visitStatement(stmt, env) {
4413
4705
  switch (stmt.type) {
4414
4706
  case "VariableDeclaration": {
4707
+ stmt.names.forEach((target, i) => {
4708
+ if (target.type === "IdentifierPattern" && target.typeAnnotation && stmt.init[i]) {
4709
+ this.applyContext(stmt.init[i], this.resolveType(target.typeAnnotation));
4710
+ }
4711
+ });
4415
4712
  const { types: valueTypes, sources } = this.valueList(stmt.init, env);
4416
4713
  stmt.names.forEach((target, i) => {
4417
4714
  const inferred = valueTypes[i] ?? (stmt.init.length ? unknownType : nilType);
@@ -4472,6 +4769,16 @@ var TypeAnalyzer = class {
4472
4769
  return;
4473
4770
  }
4474
4771
  case "AssignmentStatement": {
4772
+ stmt.targets.forEach((target, i) => {
4773
+ const value = stmt.values[i];
4774
+ if (!value) return;
4775
+ if (target.type === "MemberExpression" || target.type === "IndexExpression") {
4776
+ this.applyContext(value, this.infer(target, env));
4777
+ } else if (target.type === "Identifier") {
4778
+ const id = this.bindingIdOf(target);
4779
+ if (id !== void 0 && this.annotated.has(id)) this.applyContext(value, this.bindingType.get(id));
4780
+ }
4781
+ });
4475
4782
  const { types: valueTypes, sources } = this.valueList(stmt.values, env);
4476
4783
  stmt.targets.forEach((target, i) => {
4477
4784
  const vt = valueTypes[i] ?? unknownType;
@@ -4596,6 +4903,14 @@ var TypeAnalyzer = class {
4596
4903
  };
4597
4904
  if (resolving && !exports) report(stmt.source, `Cannot find module '${specifier}'`);
4598
4905
  const usable = exports && !exports.partial ? exports : void 0;
4906
+ if (stmt.namespaceImport) {
4907
+ const id = this.bindingIdByName(stmt.namespaceImport.name, stmt.namespaceImport);
4908
+ if (id !== void 0) {
4909
+ const members = [...usable?.values ?? []].map(([name, type]) => [name, { type, optional: false, readonly: true }]);
4910
+ if (usable?.default) members.push(["default", { type: usable.default, optional: false, readonly: true }]);
4911
+ this.bindingType.set(id, usable ? objectType(members) : anyType);
4912
+ }
4913
+ }
4599
4914
  if (stmt.defaultImport) {
4600
4915
  if (usable && usable.default === void 0) {
4601
4916
  report(stmt.defaultImport, `Module '${specifier}' has no default export`);
@@ -4616,6 +4931,9 @@ var TypeAnalyzer = class {
4616
4931
  case "BreakStatement":
4617
4932
  this.breakStates[this.breakStates.length - 1]?.push(forkEnv(env));
4618
4933
  return;
4934
+ case "DeclareClassStatement":
4935
+ this.checkClass(stmt);
4936
+ return;
4619
4937
  case "ContinueStatement":
4620
4938
  case "TypeAliasStatement":
4621
4939
  case "ExportTypeAliasStatement":
@@ -4737,7 +5055,89 @@ var TypeAnalyzer = class {
4737
5055
  }
4738
5056
  if (p.pattern) return this.patternToType(p.pattern, env);
4739
5057
  if (p.default) return widen(this.infer(p.default, env));
4740
- return anyType;
5058
+ return this.contextualParams.get(p) ?? anyType;
5059
+ }
5060
+ /** What a function expression's unannotated parameters are, from where
5061
+ * it is written — see `applyContext`. */
5062
+ contextualParams = /* @__PURE__ */ new WeakMap();
5063
+ /** `expected` is the type the surroundings want for `expr`. A function
5064
+ * expression written there takes its unannotated parameters' types from
5065
+ * it, as in TypeScript: `signal:Connect(function(player) ... end)` knows
5066
+ * `player` from `Connect`'s callback type. Anything else is inferred as
5067
+ * usual. */
5068
+ applyContext(expr, expected) {
5069
+ let e = expr;
5070
+ while (e.type === "ParenthesizedExpression") e = e.expression;
5071
+ if (!expected) return;
5072
+ if (e.type === "ArrayExpression") return this.applyArrayContext(e, expected);
5073
+ if (e.type === "TableExpression") return this.applyTableContext(e, expected);
5074
+ if (e.type !== "FunctionExpression") return;
5075
+ const members = expected.kind === "union" ? expected.types : [expected];
5076
+ const signatures = members.flatMap((m) => this.overloadsOf(this.expand(m)));
5077
+ if (!signatures.length) return;
5078
+ e.func.params.forEach((p, k) => {
5079
+ if (p.typeAnnotation || p.pattern || p.default) return;
5080
+ const candidates = [];
5081
+ for (const signature of signatures) {
5082
+ const t2 = signature.params[k]?.type ?? signature.varargs;
5083
+ if (t2) candidates.push(t2);
5084
+ }
5085
+ if (!candidates.length) return;
5086
+ const t = union(candidates);
5087
+ this.contextualParams.set(p, containsTypeParam(t) ? anyType : t);
5088
+ });
5089
+ }
5090
+ /** What an array literal is expected to be: an empty one takes that type
5091
+ * outright — `let queue: thread[] = []` is a `thread[]`, as in TypeScript
5092
+ * — and the elements of any other get the element type as their own
5093
+ * context. */
5094
+ contextualArrays = /* @__PURE__ */ new WeakMap();
5095
+ applyArrayContext(e, expected) {
5096
+ const target = this.expectedMembers(expected).find((m) => m.kind === "array" || m.kind === "tuple");
5097
+ if (!target) return;
5098
+ if (!e.elements.length) {
5099
+ if (!containsTypeParam(target)) this.contextualArrays.set(e, target);
5100
+ return;
5101
+ }
5102
+ e.elements.forEach((element, i) => {
5103
+ if (element.type === "SpreadElement") return;
5104
+ const elementType = target.kind === "array" ? target.element : target.elements[i];
5105
+ this.applyContext(element, elementType);
5106
+ });
5107
+ }
5108
+ /** `{ list: [] }` where `{ list: thread[] }` is expected: each field's
5109
+ * value gets its property's type as context. */
5110
+ applyTableContext(e, expected) {
5111
+ const objects = this.expectedMembers(expected).filter((m) => m.kind === "object");
5112
+ if (!objects.length) return;
5113
+ for (const field of e.fields) {
5114
+ if (field.type !== "TableFieldNamed") continue;
5115
+ const key = field.key.type === "Identifier" ? field.key.name : field.key.value;
5116
+ const types = objects.flatMap((o) => {
5117
+ const property = o.properties.get(key);
5118
+ return property ? [property.type] : o.indexer ? [o.indexer.value] : [];
5119
+ });
5120
+ if (types.length) this.applyContext(field.value, union(types));
5121
+ }
5122
+ }
5123
+ /** The members of an expected type worth matching a literal against:
5124
+ * aliases seen through, `nil` left out. */
5125
+ expectedMembers(expected) {
5126
+ const t = this.expand(expected);
5127
+ const members = t.kind === "union" ? t.types : [t];
5128
+ return members.map((m) => this.expand(m)).filter((m) => !(m.kind === "primitive" && m.name === "nil"));
5129
+ }
5130
+ /** The parameter type each written argument lands on, across `fns`. */
5131
+ expectedArguments(written, fns, selfOf) {
5132
+ return written.map((_, j) => {
5133
+ const candidates = [];
5134
+ for (const f of fns) {
5135
+ const i = j + selfOf(f);
5136
+ const param = i < f.params.length ? this.boundParams(f)[i] : f.varargs;
5137
+ if (param) candidates.push(param);
5138
+ }
5139
+ return candidates.length ? union(candidates) : void 0;
5140
+ });
4741
5141
  }
4742
5142
  /** Synthesize a type from a destructuring pattern used without an
4743
5143
  * annotation (`function f({ a, b = 1 })`). */
@@ -4789,7 +5189,8 @@ var TypeAnalyzer = class {
4789
5189
  f.params.forEach((p, i) => {
4790
5190
  const arg = argTypes[i];
4791
5191
  if (arg === void 0) return;
4792
- unify(p.type, keepsLiterals(p.type) ? arg : widen(arg), vars, subst);
5192
+ const param = p.type.kind === "typeParam" && p.type.constraint ? { ...p.type, constraint: this.reduceType(p.type.constraint) } : p.type;
5193
+ unify(p.type, keepsLiterals(param) ? arg : widen(arg), vars, subst);
4793
5194
  });
4794
5195
  for (const name of f.typeParams ?? []) if (!subst.has(name)) subst.set(name, unknownType);
4795
5196
  return subst;
@@ -4850,12 +5251,13 @@ var TypeAnalyzer = class {
4850
5251
  return;
4851
5252
  }
4852
5253
  const t = value;
5254
+ if (t.kind === "object" && t.class) return;
4853
5255
  if (t.kind === "typeParam" && typeof t.name === "string" && bounds.has(t.name) && t.constraint && !containsTypeParam(t.constraint)) {
4854
5256
  bounds.set(t.name, this.reduceType(t.constraint));
4855
5257
  }
4856
5258
  for (const child of Object.values(value)) walk(child);
4857
5259
  };
4858
- for (const p of f.params) walk(p.type);
5260
+ for (const p of f.params) if (containsTypeParam(p.type)) walk(p.type);
4859
5261
  return f.params.map((p) => substitute(p.type, bounds));
4860
5262
  }
4861
5263
  /** Record what each written argument is expected to be — see
@@ -5194,7 +5596,7 @@ var TypeAnalyzer = class {
5194
5596
  this.expandCache.set(key, t);
5195
5597
  this.resolvingAliases.add(t.name);
5196
5598
  try {
5197
- const r = def.params.length ? this.instantiateAlias(def, t.typeArguments) : this.resolveType(def.node);
5599
+ const r = def.params.length ? this.instantiateAlias(def, t.typeArguments) : this.resolveDef(def);
5198
5600
  const named = def.params.length === 0 && (r.kind === "object" || r.kind === "intersection") && !r.name ? { ...r, name: t.name } : r;
5199
5601
  this.expandCache.set(key, named);
5200
5602
  return named;
@@ -5299,8 +5701,9 @@ var TypeAnalyzer = class {
5299
5701
  return this.resolveType(expr.typeAnnotation);
5300
5702
  }
5301
5703
  case "SatisfiesExpression": {
5302
- const actual = this.infer(expr.expression, env);
5303
5704
  const declared = this.resolveType(expr.typeAnnotation);
5705
+ this.applyContext(expr.expression, declared);
5706
+ const actual = this.infer(expr.expression, env);
5304
5707
  if (this.emitDiagnostics && declared.kind !== "any" && !this.fitsAnnotation(expr.expression, declared, actual, env)) {
5305
5708
  this.diagnostics.push({
5306
5709
  node: expr,
@@ -5317,9 +5720,9 @@ var TypeAnalyzer = class {
5317
5720
  case "not":
5318
5721
  return booleanType;
5319
5722
  case "-":
5320
- return numberType;
5723
+ return this.operatorResult(expr, "-", arg, void 0) ?? numberType;
5321
5724
  case "#":
5322
- return numberType;
5725
+ return this.operatorResult(expr, "#", arg, void 0) ?? numberType;
5323
5726
  }
5324
5727
  return arg;
5325
5728
  }
@@ -5341,7 +5744,7 @@ var TypeAnalyzer = class {
5341
5744
  const r = this.infer(expr.right, env);
5342
5745
  switch (op) {
5343
5746
  case "..":
5344
- return stringType;
5747
+ return this.operatorResult(expr, op, l, r) ?? stringType;
5345
5748
  case "==":
5346
5749
  case "~=":
5347
5750
  case "<":
@@ -5356,7 +5759,7 @@ var TypeAnalyzer = class {
5356
5759
  case "//":
5357
5760
  case "%":
5358
5761
  case "^":
5359
- return numberType;
5762
+ return this.operatorResult(expr, op, l, r) ?? numberType;
5360
5763
  }
5361
5764
  return union([l, r]);
5362
5765
  }
@@ -5375,8 +5778,10 @@ var TypeAnalyzer = class {
5375
5778
  }
5376
5779
  case "CallExpression": {
5377
5780
  const callee = this.infer(expr.callee, env);
5378
- const argTypes = expr.arguments.map((a) => this.infer(a, env));
5379
5781
  const fns = this.overloadsOf(callee);
5782
+ const expected = this.expectedArguments(expr.arguments, fns, () => 0);
5783
+ expr.arguments.forEach((a, i) => this.applyContext(a, expected[i]));
5784
+ const argTypes = expr.arguments.map((a) => this.infer(a, env));
5380
5785
  if (fns.length) {
5381
5786
  this.recordExpected(expr.arguments, fns, () => 0);
5382
5787
  const arityFits = this.checkArity(expr, fns, argTypes.length, 0);
@@ -5391,8 +5796,10 @@ var TypeAnalyzer = class {
5391
5796
  }
5392
5797
  case "MethodCallExpression": {
5393
5798
  const objType = this.infer(expr.object, env);
5394
- const argTypes = expr.arguments.map((a) => this.infer(a, env));
5395
5799
  const fns = this.overloadsOf(this.propertyType(objType, expr.method.name));
5800
+ const expected = this.expectedArguments(expr.arguments, fns, (f) => this.takesSelf(f) ? 1 : 0);
5801
+ expr.arguments.forEach((a, i) => this.applyContext(a, expected[i]));
5802
+ const argTypes = expr.arguments.map((a) => this.infer(a, env));
5396
5803
  if (fns.length) {
5397
5804
  const withSelf = (f) => this.takesSelf(f) ? [objType, ...argTypes] : argTypes;
5398
5805
  const selfOf = (f) => this.takesSelf(f) ? 1 : 0;
@@ -5424,6 +5831,8 @@ var TypeAnalyzer = class {
5424
5831
  }
5425
5832
  }
5426
5833
  inferArray(expr, env, asConst) {
5834
+ const contextual = this.contextualArrays.get(expr);
5835
+ if (contextual && !asConst) return contextual;
5427
5836
  const elems = [];
5428
5837
  let hadSpread = false;
5429
5838
  for (const el of expr.elements) {
@@ -5784,6 +6193,35 @@ var TypeAnalyzer = class {
5784
6193
  this.selfType = saved;
5785
6194
  }
5786
6195
  }
6196
+ /** What an operator on a value with metamethods gives: `a + b` calls
6197
+ * `__add` on `a`, or failing that on `b` with the operands swapped — the
6198
+ * order Luau tries them in. That is how `Vector3 + Vector3`, `CFrame *
6199
+ * Vector3` and `2 * vector` get their types from the declarations.
6200
+ * `undefined` when neither operand declares the metamethod; an operand
6201
+ * that declares it but accepts neither argument is reported. */
6202
+ operatorResult(node, op, left, right) {
6203
+ const name = right === void 0 ? op === "-" ? "__unm" : "__len" : METAMETHODS[op];
6204
+ if (!name) return void 0;
6205
+ const candidates = right === void 0 ? [[left, void 0]] : [[left, right], [right, left]];
6206
+ let declared;
6207
+ for (const [receiver, other] of candidates) {
6208
+ const t = this.expand(receiver);
6209
+ const method = t.kind === "object" ? t.properties.get(name) : void 0;
6210
+ if (!method) continue;
6211
+ declared ??= receiver;
6212
+ const args = other === void 0 ? [receiver] : [receiver, other];
6213
+ const picked = this.pickOverload(this.overloadsOf(method.type), args);
6214
+ if (picked) return this.callReturn(picked, args);
6215
+ }
6216
+ if (declared && this.emitDiagnostics) {
6217
+ this.diagnostics.push({
6218
+ node,
6219
+ message: right === void 0 ? `Operator '${op}' cannot be applied to type '${formatType(left)}'` : `Operator '${op}' cannot be applied to types '${formatType(left)}' and '${formatType(right)}'`
6220
+ });
6221
+ return anyType;
6222
+ }
6223
+ return void 0;
6224
+ }
5787
6225
  /** Does this signature take the receiver as its first parameter?
5788
6226
  *
5789
6227
  * Luau's `:` is sugar both ways: `function T:m(a)` declares
@@ -6048,13 +6486,13 @@ function resolveTypeLibraries(config, host = nodeHost) {
6048
6486
  else problems.push({ file: config.path, message: `'${entry}' has no ${ENTRY_FILE} (or 'luaut.types' in its package.json)`, ...entryPosition(config, entry) });
6049
6487
  continue;
6050
6488
  }
6051
- const names = entry.startsWith("@") || entry.includes("/") ? [entry] : [`@luaut/${entry}`, entry];
6052
- const found = names.map((name) => findPackage(name, config.directory, host)).find(Boolean);
6489
+ const name = entry.startsWith("@luaut/") ? entry : `@luaut/${entry}`;
6490
+ const found = findPackage(name, config.directory, host);
6053
6491
  if (found) addPackage(found.directory, found.file, /* @__PURE__ */ new Set());
6054
6492
  else {
6055
6493
  problems.push({
6056
6494
  file: config.path,
6057
- message: `Cannot find type library '${entry}'. Install it with: npm i -D ${names[0]}`,
6495
+ message: `Cannot find type library '${name}'. Install it with: npm i -D ${name}`,
6058
6496
  ...entryPosition(config, entry)
6059
6497
  });
6060
6498
  }
@@ -6157,7 +6595,6 @@ function sourceMapTypes(text, path, options) {
6157
6595
  const lines = [];
6158
6596
  const aliasOfFile = /* @__PURE__ */ new Map();
6159
6597
  const used = /* @__PURE__ */ new Set();
6160
- const canOmit = options.classes.has("Omit");
6161
6598
  const aliasOfNode = /* @__PURE__ */ new Map();
6162
6599
  const aliasFor = (segments) => {
6163
6600
  const base = `SourceMap_${segments.map((s) => s.replace(/[^A-Za-z0-9_]/g, "_")).join("_")}`;
@@ -6173,7 +6610,7 @@ function sourceMapTypes(text, path, options) {
6173
6610
  const className = IDENTIFIER.test(node.className) && options.classes.has(node.className) ? node.className : "Instance";
6174
6611
  const taken = options.membersOf?.(className) ?? INSTANCE_MEMBERS;
6175
6612
  const members = [];
6176
- if (parent && canOmit) members.push(`Parent: ${parent}`);
6613
+ if (parent) members.push(`Parent: ${parent}`);
6177
6614
  const named = /* @__PURE__ */ new Set();
6178
6615
  for (const child of node.children ?? []) {
6179
6616
  if (!isNode(child)) continue;
@@ -6182,8 +6619,7 @@ function sourceMapTypes(text, path, options) {
6182
6619
  named.add(child.name);
6183
6620
  members.push(`${child.name}: ${childAlias}`);
6184
6621
  }
6185
- const base = parent && canOmit ? `Omit<${className}, "Parent">` : className;
6186
- lines.push(`type ${alias} = ${members.length ? `${base} & { ${members.join(", ")} }` : base}`);
6622
+ lines.push(`declare class ${alias} extends ${className} { ${members.join(", ")} }`);
6187
6623
  return alias;
6188
6624
  };
6189
6625
  const rootAlias = visit(root, [root.name], void 0);
@@ -6261,6 +6697,7 @@ export {
6261
6697
  getBinding,
6262
6698
  intersection,
6263
6699
  isAssignable,
6700
+ isClassType,
6264
6701
  isGlobal,
6265
6702
  isPossiblyFalsy,
6266
6703
  isPossiblyTruthy,