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.cjs CHANGED
@@ -45,6 +45,7 @@ __export(index_exports, {
45
45
  getBinding: () => getBinding,
46
46
  intersection: () => intersection,
47
47
  isAssignable: () => isAssignable,
48
+ isClassType: () => isClassType,
48
49
  isGlobal: () => isGlobal,
49
50
  isPossiblyFalsy: () => isPossiblyFalsy,
50
51
  isPossiblyTruthy: () => isPossiblyTruthy,
@@ -888,23 +889,14 @@ var Parser = class {
888
889
  if (t.type === "Punctuator" && t.value === "@") {
889
890
  const { attributes, start } = this.parseAttributes();
890
891
  const next = this.current();
891
- if (next.type === "Keyword" && (next.value === "const" || next.value === "let")) {
892
- const stmt = this.parseVariableDeclaration();
893
- if (stmt.type === "FunctionDeclaration") {
894
- stmt.attributes = attributes;
895
- stmt.line.start = start.line.start;
896
- stmt.column.start = start.column.start;
897
- }
898
- return stmt;
899
- }
900
892
  if (next.type === "Keyword" && next.value === "function") {
901
- const stmt = this.parseFunctionDeclarationStatement();
893
+ const stmt = this.parseFunctionStatement();
902
894
  stmt.attributes = attributes;
903
895
  stmt.line.start = start.line.start;
904
896
  stmt.column.start = start.column.start;
905
897
  return stmt;
906
898
  }
907
- throw new ParseError("Expected 'function', 'const', or 'let' after attribute", next.line.start, next.column.start);
899
+ throw new ParseError("Expected 'function' after an attribute", next.line.start, next.column.start);
908
900
  }
909
901
  if (t.type === "Keyword") {
910
902
  switch (t.value) {
@@ -922,7 +914,7 @@ var Parser = class {
922
914
  case "for":
923
915
  return this.parseForStatement();
924
916
  case "function":
925
- return this.parseFunctionDeclarationStatement();
917
+ return this.parseFunctionStatement();
926
918
  case "return":
927
919
  return this.parseReturnStatement();
928
920
  case "import":
@@ -944,6 +936,9 @@ var Parser = class {
944
936
  }
945
937
  if (t.type === "Identifier" && t.value === "declare") {
946
938
  const p1 = this.peek(1);
939
+ if (p1.type === "Identifier" && p1.value === "class" && this.peek(2).type === "Identifier") {
940
+ return this.parseDeclareClassStatement();
941
+ }
947
942
  if (p1.type === "Identifier" || p1.type === "Keyword" && p1.value === "function") {
948
943
  return this.parseDeclareStatement();
949
944
  }
@@ -985,26 +980,66 @@ var Parser = class {
985
980
  const valueType = this.parseType();
986
981
  return { type: "DeclareStatement", name: nameTok.value, id: tokenIdentifier(nameTok), valueType, ...spanFrom(start, this.previous()) };
987
982
  }
983
+ /** A declared type's name. It may be qualified once — `Enum.Material` —
984
+ * which is how a definitions file names types under a namespace, and how
985
+ * they are then written (`const m: Enum.Material`). */
986
+ parseTypeName() {
987
+ const first = this.expectIdentifier();
988
+ if (this.checkPunctuator(".") && this.peek(1).type === "Identifier") {
989
+ this.advance();
990
+ const second = this.expectIdentifier();
991
+ return { type: "Identifier", name: `${first.value}.${second.value}`, ...spanFrom(first, second) };
992
+ }
993
+ return tokenIdentifier(first);
994
+ }
995
+ // `declare class Name extends Base { member: T, ... }`
996
+ parseDeclareClassStatement() {
997
+ const start = this.current();
998
+ this.advance();
999
+ this.advance();
1000
+ const name = this.parseTypeName();
1001
+ let superclass;
1002
+ if (this.checkIdentifierValue("extends")) {
1003
+ this.advance();
1004
+ const base = this.parseType();
1005
+ if (base.type !== "TypeReference") this.error("A class can only extend another class, written by name");
1006
+ superclass = base;
1007
+ }
1008
+ if (!this.checkPunctuator("{")) this.error("Expected '{' to start the class body");
1009
+ const body = this.parseTableType();
1010
+ if (body.type !== "TableTypeNode") this.error("A class body lists members ('name: T'), not a mapped type");
1011
+ return { type: "DeclareClassStatement", name, superclass, body, ...spanFrom(start, this.previous()) };
1012
+ }
988
1013
  // `import { a, b as c } from '...'` / `import Default from '...'` /
989
1014
  // `import Default, { a } from '...'`. Compiled away entirely by the
990
1015
  // bundler — never survives into emitted Luau.
991
1016
  parseImportStatement() {
992
1017
  const start = this.current();
993
1018
  this.advance();
1019
+ const next = this.peek(1);
1020
+ const isTypeOnly = this.checkIdentifierValue("type") && (next.type === "Punctuator" && next.value === "{" || next.type === "Operator" && next.value === "*" || next.type === "Identifier");
1021
+ if (isTypeOnly) this.advance();
994
1022
  let defaultImport;
995
1023
  const specifiers = [];
996
- if (this.checkType("Identifier")) {
997
- const nameTok = this.expectIdentifier();
998
- defaultImport = { type: "Identifier", name: nameTok.value, ...spanFrom(nameTok, nameTok) };
999
- if (this.matchPunctuator(",")) {
1000
- this.expectPunctuator("{");
1001
- this.parseImportSpecifierList(specifiers);
1002
- this.expectPunctuator("}");
1024
+ let namespaceImport;
1025
+ const parseBindings = () => {
1026
+ if (this.checkOperator("*")) {
1027
+ this.advance();
1028
+ if (!this.checkKeyword("as")) this.error("Expected 'as' after 'import *'");
1029
+ this.advance();
1030
+ namespaceImport = this.parseIdentifier();
1031
+ return;
1003
1032
  }
1004
- } else {
1005
1033
  this.expectPunctuator("{");
1006
1034
  this.parseImportSpecifierList(specifiers);
1007
1035
  this.expectPunctuator("}");
1036
+ };
1037
+ if (this.checkType("Identifier")) {
1038
+ const nameTok = this.expectIdentifier();
1039
+ defaultImport = { type: "Identifier", name: nameTok.value, ...spanFrom(nameTok, nameTok) };
1040
+ if (this.matchPunctuator(",")) parseBindings();
1041
+ } else {
1042
+ parseBindings();
1008
1043
  }
1009
1044
  if (!this.checkKeyword("from")) {
1010
1045
  this.error("Expected 'from' in import statement");
@@ -1021,7 +1056,15 @@ var Parser = class {
1021
1056
  raw: sourceTok.raw,
1022
1057
  ...spanFrom(sourceTok, sourceTok)
1023
1058
  };
1024
- return { type: "ImportStatement", defaultImport, specifiers, source, ...spanFrom(start, this.previous()) };
1059
+ return {
1060
+ type: "ImportStatement",
1061
+ defaultImport,
1062
+ namespaceImport,
1063
+ specifiers,
1064
+ source,
1065
+ isTypeOnly: isTypeOnly || void 0,
1066
+ ...spanFrom(start, this.previous())
1067
+ };
1025
1068
  }
1026
1069
  parseImportSpecifierList(out) {
1027
1070
  if (this.checkPunctuator("}")) return;
@@ -1057,7 +1100,7 @@ var Parser = class {
1057
1100
  ...spanFrom(sourceTok, sourceTok)
1058
1101
  };
1059
1102
  }
1060
- // `export const ...` / `export let ...` / `export const function ...` /
1103
+ // `export const ...` / `export let ...` / `export function ...` /
1061
1104
  // `export type ...` / `export default <expr>`
1062
1105
  parseExportStatement() {
1063
1106
  const start = this.current();
@@ -1075,6 +1118,11 @@ var Parser = class {
1075
1118
  const declaration = this.parseVariableDeclaration();
1076
1119
  return { type: "ExportStatement", declaration, ...spanFrom(start, this.previous()) };
1077
1120
  }
1121
+ if (this.checkKeyword("function")) {
1122
+ const declaration = this.parseFunctionStatement();
1123
+ if (declaration.type !== "FunctionDeclaration") this.error("An exported function needs a plain name: 'export function name()'");
1124
+ return { type: "ExportStatement", declaration, ...spanFrom(start, this.previous()) };
1125
+ }
1078
1126
  if (this.checkPunctuator("{")) {
1079
1127
  this.advance();
1080
1128
  const specifiers = [];
@@ -1098,15 +1146,15 @@ var Parser = class {
1098
1146
  const source = this.parseModuleSource();
1099
1147
  return { type: "ExportAllStatement", source, ...spanFrom(start, this.previous()) };
1100
1148
  }
1101
- this.error("Expected 'const', 'let', 'type', 'default', '{' or '*' after 'export'");
1149
+ this.error("Expected 'const', 'let', 'function', 'type', 'default', '{' or '*' after 'export'");
1102
1150
  }
1103
- // `const x = ...` / `let x, y = ...` / `const function f() ... end`.
1151
+ // `const x = ...` / `let x, y = ...`.
1104
1152
  // luaut has no `local` — `const` bindings are immutable, `let` mutable.
1105
1153
  parseVariableDeclaration() {
1106
1154
  const start = this.current();
1107
1155
  const kind = this.advance().value;
1108
- if (this.matchKeyword("function")) {
1109
- return this.parseFunctionDeclarationRest(start, kind);
1156
+ if (this.checkKeyword("function")) {
1157
+ this.error(`A function is declared as 'function name()'; '${kind}' does not apply to functions`);
1110
1158
  }
1111
1159
  const names = [this.parseBindingTarget(true)];
1112
1160
  while (this.matchPunctuator(",")) {
@@ -1120,31 +1168,6 @@ var Parser = class {
1120
1168
  }
1121
1169
  return { type: "VariableDeclaration", kind, names, init, ...spanFrom(start, this.previous()) };
1122
1170
  }
1123
- /** `const/let function` — `function` already consumed. Collects TS-style
1124
- * overload signatures. */
1125
- parseFunctionDeclarationRest(start, kind) {
1126
- const name = this.parseIdentifier();
1127
- const signatures = [];
1128
- while (true) {
1129
- const head = this.parseFunctionHead();
1130
- if (this.isOverloadContinuation(name.name, kind)) {
1131
- signatures.push(this.headToSignature(head));
1132
- this.advance();
1133
- this.expectKeyword("function");
1134
- this.parseIdentifier();
1135
- continue;
1136
- }
1137
- const func = this.headToBody(head);
1138
- return {
1139
- type: "FunctionDeclaration",
1140
- kind,
1141
- name,
1142
- func,
1143
- signatures: signatures.length ? signatures : void 0,
1144
- ...spanFrom(start, this.previous())
1145
- };
1146
- }
1147
- }
1148
1171
  parseIfStatement() {
1149
1172
  const start = this.current();
1150
1173
  this.expectKeyword("if");
@@ -1234,7 +1257,9 @@ var Parser = class {
1234
1257
  ...spanFrom(start, this.previous())
1235
1258
  };
1236
1259
  }
1237
- parseFunctionDeclarationStatement() {
1260
+ /** `function name() end` declares `name`; `function a.b() end` and
1261
+ * `function T:m() end` define a member. */
1262
+ parseFunctionStatement() {
1238
1263
  const start = this.current();
1239
1264
  this.expectKeyword("function");
1240
1265
  const target = this.parseFunctionName();
@@ -1250,6 +1275,15 @@ var Parser = class {
1250
1275
  continue;
1251
1276
  }
1252
1277
  const func = this.headToBody(head);
1278
+ if (simpleName !== void 0) {
1279
+ return {
1280
+ type: "FunctionDeclaration",
1281
+ name: target.base,
1282
+ func,
1283
+ signatures: signatures.length ? signatures : void 0,
1284
+ ...spanFrom(start, this.previous())
1285
+ };
1286
+ }
1253
1287
  if (isMethod) {
1254
1288
  func.params.unshift({ type: "FunctionParameter", name: "self", ...spanFrom(target, target) });
1255
1289
  func.isMethod = true;
@@ -1266,12 +1300,8 @@ var Parser = class {
1266
1300
  }
1267
1301
  /** After a bodyless function head, is the next token the start of another
1268
1302
  * declaration for the same simple `name` (making the head an overload
1269
- * signature rather than an implementation)? `kind` is set for a
1270
- * `const/let function` group, undefined for a bare `function` group. */
1271
- isOverloadContinuation(name, kind) {
1272
- if (kind) {
1273
- return this.checkKeyword(kind) && this.peek(1).type === "Keyword" && this.peek(1).value === "function" && this.peek(2).type === "Identifier" && this.peek(2).value === name;
1274
- }
1303
+ * signature rather than an implementation)? */
1304
+ isOverloadContinuation(name) {
1275
1305
  return this.checkKeyword("function") && this.peek(1).type === "Identifier" && this.peek(1).value === name;
1276
1306
  }
1277
1307
  parseFunctionName() {
@@ -1315,8 +1345,7 @@ var Parser = class {
1315
1345
  parseTypeAliasStatement() {
1316
1346
  const start = this.current();
1317
1347
  this.advance();
1318
- const nameTok = this.expectIdentifier();
1319
- const name = { type: "Identifier", name: nameTok.value, ...spanFrom(nameTok, nameTok) };
1348
+ const name = this.parseTypeName();
1320
1349
  let generics = [];
1321
1350
  if (this.checkOperator("<")) {
1322
1351
  generics = this.parseGenericTypeParameterList();
@@ -2579,7 +2608,7 @@ var Analyzer = class {
2579
2608
  };
2580
2609
  }
2581
2610
  // ---------------- declaration / resolution primitives ----------------
2582
- declare(scope, name, kind, node, isConst = false) {
2611
+ declare(scope, name, kind, node, isConst = false, declaredBy) {
2583
2612
  if (scope.declarations.has(name) && scope !== this.globalScope) {
2584
2613
  this.diagnostics.push({
2585
2614
  node,
@@ -2588,7 +2617,7 @@ var Analyzer = class {
2588
2617
  });
2589
2618
  }
2590
2619
  const id = this.nextId++;
2591
- this.bindings.set(id, { id, name, kind, declarationNode: node, references: [], isConst });
2620
+ this.bindings.set(id, { id, name, kind, declarationNode: node, references: [], isConst, declaredBy });
2592
2621
  scope.declarations.set(name, id);
2593
2622
  return id;
2594
2623
  }
@@ -2622,6 +2651,19 @@ var Analyzer = class {
2622
2651
  const id = this.resolve(scope, identifier.name);
2623
2652
  this.bindingOf.set(identifier, id);
2624
2653
  this.bindings.get(id).references.push(identifier);
2654
+ if (this.typeQueryDepth === 0) this.checkTypeOnly(id, identifier);
2655
+ }
2656
+ /** Inside `typeof x` in a type, where a type-only import may be named. */
2657
+ typeQueryDepth = 0;
2658
+ /** A name from `import type` used as a value. */
2659
+ checkTypeOnly(id, node) {
2660
+ const b = this.bindings.get(id);
2661
+ if (b.declaredBy !== "type") return;
2662
+ this.diagnostics.push({
2663
+ node,
2664
+ message: `'${b.name}' is imported with 'import type' and can only be used as a type`,
2665
+ kind: "type-only"
2666
+ });
2625
2667
  }
2626
2668
  /** For assignment-like targets (`x = ...`, `function foo() end`): if
2627
2669
  * this resolved to a global with no declaration site yet, treat this
@@ -2638,14 +2680,34 @@ var Analyzer = class {
2638
2680
  this.bindingOf.set(identifier, id);
2639
2681
  this.bindings.get(id).references.push(identifier);
2640
2682
  this.recordPossibleGlobalDefinition(id, identifier);
2683
+ this.checkTypeOnly(id, identifier);
2641
2684
  this.checkConstAssign(id, identifier);
2642
2685
  }
2686
+ /** `Module.x = 1` through `import * as Module`: a module's exports belong
2687
+ * to it and are read-only, as in ES modules. Deeper writes (`Module.x.y`)
2688
+ * change the value, not the module, and are fine. */
2689
+ checkModuleWrite(target) {
2690
+ if (target.type !== "MemberExpression" && target.type !== "IndexExpression") return;
2691
+ if (target.object.type !== "Identifier") return;
2692
+ const id = this.bindingOf.get(target.object);
2693
+ if (id !== void 0 && this.bindings.get(id).declaredBy === "namespace") {
2694
+ this.moduleWriteError(target.object.name, target);
2695
+ }
2696
+ }
2697
+ moduleWriteError(name, node) {
2698
+ this.diagnostics.push({
2699
+ node,
2700
+ message: `Cannot assign to a member of '${name}' \u2014 a module's exports are read-only`,
2701
+ kind: "const-assign"
2702
+ });
2703
+ }
2643
2704
  checkConstAssign(id, node) {
2644
2705
  const b = this.bindings.get(id);
2706
+ if (b.declaredBy === "type") return;
2645
2707
  if (b.isConst) {
2646
2708
  this.diagnostics.push({
2647
2709
  node,
2648
- message: `Cannot assign to '${b.name}' \u2014 it is a const`,
2710
+ message: `Cannot assign to '${b.name}' \u2014 it is ${b.declaredBy === "import" || b.declaredBy === "namespace" ? "an import" : b.declaredBy === "function" ? "a function" : "a const"}`,
2649
2711
  kind: "const-assign"
2650
2712
  });
2651
2713
  }
@@ -2686,6 +2748,7 @@ var Analyzer = class {
2686
2748
  const id = this.resolve(scope, t.name);
2687
2749
  this.bindingOf.set(t, id);
2688
2750
  this.recordPossibleGlobalDefinition(id, t);
2751
+ this.checkTypeOnly(id, t);
2689
2752
  this.checkConstAssign(id, t);
2690
2753
  return;
2691
2754
  }
@@ -2729,7 +2792,7 @@ var Analyzer = class {
2729
2792
  return;
2730
2793
  }
2731
2794
  case "FunctionDeclaration": {
2732
- this.declare(scope, stmt.name.name, "local", stmt.name, stmt.kind === "const");
2795
+ this.declare(scope, stmt.name.name, "local", stmt.name, true, "function");
2733
2796
  for (const signature of stmt.signatures ?? []) this.visitSignature(signature, scope);
2734
2797
  this.visitFunctionBody(stmt.func, scope);
2735
2798
  return;
@@ -2739,6 +2802,11 @@ var Analyzer = class {
2739
2802
  this.referenceAsAssignmentTarget(scope, stmt.target.base);
2740
2803
  } else {
2741
2804
  this.reference(scope, stmt.target.base);
2805
+ const id = this.bindingOf.get(stmt.target.base);
2806
+ const depth = stmt.target.path.length + (stmt.target.method ? 1 : 0);
2807
+ if (id !== void 0 && depth === 1 && this.bindings.get(id).declaredBy === "namespace") {
2808
+ this.moduleWriteError(stmt.target.base.name, stmt.target);
2809
+ }
2742
2810
  }
2743
2811
  for (const signature of stmt.signatures ?? []) this.visitSignature(signature, scope);
2744
2812
  this.visitFunctionBody(stmt.func, scope, stmt.isMethod);
@@ -2753,6 +2821,7 @@ var Analyzer = class {
2753
2821
  this.assignPattern(scope, target);
2754
2822
  } else {
2755
2823
  this.visitExpression(target, scope);
2824
+ this.checkModuleWrite(target);
2756
2825
  }
2757
2826
  }
2758
2827
  return;
@@ -2765,6 +2834,7 @@ var Analyzer = class {
2765
2834
  if (id !== void 0) this.checkConstAssign(id, stmt.target);
2766
2835
  } else {
2767
2836
  this.visitExpression(stmt.target, scope);
2837
+ this.checkModuleWrite(stmt.target);
2768
2838
  }
2769
2839
  return;
2770
2840
  }
@@ -2818,16 +2888,23 @@ var Analyzer = class {
2818
2888
  case "DeclareStatement":
2819
2889
  this.visitType(stmt.valueType, scope);
2820
2890
  return;
2891
+ case "DeclareClassStatement":
2892
+ this.visitType(stmt.body, scope);
2893
+ return;
2821
2894
  case "TypeAliasStatement":
2822
2895
  case "ExportTypeAliasStatement":
2823
2896
  this.visitType(stmt.definition, scope);
2824
2897
  return;
2825
2898
  case "ImportStatement": {
2899
+ const typeOnly = stmt.isTypeOnly ? "type" : void 0;
2826
2900
  if (stmt.defaultImport) {
2827
- this.declare(scope, stmt.defaultImport.name, "local", stmt.defaultImport);
2901
+ this.declare(scope, stmt.defaultImport.name, "local", stmt.defaultImport, true, typeOnly ?? "import");
2902
+ }
2903
+ if (stmt.namespaceImport) {
2904
+ this.declare(scope, stmt.namespaceImport.name, "local", stmt.namespaceImport, true, typeOnly ?? "namespace");
2828
2905
  }
2829
2906
  for (const spec of stmt.specifiers) {
2830
- this.declare(scope, spec.local.name, "local", spec.local);
2907
+ this.declare(scope, spec.local.name, "local", spec.local, true, typeOnly ?? "import");
2831
2908
  }
2832
2909
  return;
2833
2910
  }
@@ -2886,7 +2963,12 @@ var Analyzer = class {
2886
2963
  return;
2887
2964
  }
2888
2965
  if (value.type === "TypeofTypeNode") {
2889
- this.visitExpression(value.expression, scope);
2966
+ this.typeQueryDepth++;
2967
+ try {
2968
+ this.visitExpression(value.expression, scope);
2969
+ } finally {
2970
+ this.typeQueryDepth--;
2971
+ }
2890
2972
  return;
2891
2973
  }
2892
2974
  for (const key of Object.keys(value)) {
@@ -2991,6 +3073,9 @@ function analyzeScopes(program, options = {}) {
2991
3073
  }
2992
3074
 
2993
3075
  // src/ast/typeModel.ts
3076
+ function isClassType(t) {
3077
+ return t.kind === "object" && t.class !== void 0;
3078
+ }
2994
3079
  function typeParam(name, constraint, isConst) {
2995
3080
  return { kind: "typeParam", name, constraint, isConst };
2996
3081
  }
@@ -3050,10 +3135,16 @@ function substitute(t, subst) {
3050
3135
  }
3051
3136
  case "function": {
3052
3137
  const inner = t.typeParams ? new Map([...subst].filter(([k]) => !t.typeParams.includes(k))) : subst;
3138
+ let params = t.params.map((p) => ({ ...p, type: substitute(p.type, inner) }));
3139
+ let varargs = t.varargs && substitute(t.varargs, inner);
3140
+ if (varargs?.kind === "tuple" && varargs.isPack) {
3141
+ params = [...params, ...varargs.elements.map((type) => ({ type }))];
3142
+ varargs = void 0;
3143
+ }
3053
3144
  return {
3054
3145
  kind: "function",
3055
- params: t.params.map((p) => ({ ...p, type: substitute(p.type, inner) })),
3056
- varargs: t.varargs && substitute(t.varargs, inner),
3146
+ params,
3147
+ varargs,
3057
3148
  returns: substitute(t.returns, inner),
3058
3149
  typeParams: t.typeParams,
3059
3150
  predicate: t.predicate && {
@@ -3133,7 +3224,8 @@ function unify(param, arg, vars, out) {
3133
3224
  }
3134
3225
  return;
3135
3226
  case "object":
3136
- if (arg.kind === "object") {
3227
+ if (param.class) return;
3228
+ if (arg.kind === "object" && !arg.class) {
3137
3229
  for (const [k, pv] of param.properties) {
3138
3230
  const av = arg.properties.get(k);
3139
3231
  if (av) unify(pv.type, av.type, vars, out);
@@ -3216,7 +3308,7 @@ function widen(t) {
3216
3308
  case "tuple":
3217
3309
  return tuple(t.elements.map(widen), t.isPack);
3218
3310
  case "object": {
3219
- if (t.frozen) return t;
3311
+ if (t.frozen || t.class) return t;
3220
3312
  const entries = [];
3221
3313
  for (const [k, v] of t.properties) entries.push([k, { ...v, type: widen(v.type) }]);
3222
3314
  const w = objectType(entries, t.indexer && { key: t.indexer.key, value: widen(t.indexer.value) });
@@ -3243,6 +3335,10 @@ function isAssignable(rawA, rawB) {
3243
3335
  if (expandAlias) {
3244
3336
  if (a.kind === "genericRef" && b.kind !== "genericRef") a = expandAlias(a);
3245
3337
  else if (b.kind === "genericRef" && a.kind !== "genericRef") b = expandAlias(b);
3338
+ else if (a.kind === "genericRef" && b.kind === "genericRef" && a.name !== b.name) {
3339
+ a = expandAlias(a);
3340
+ b = expandAlias(b);
3341
+ }
3246
3342
  if (a === b) return true;
3247
3343
  }
3248
3344
  for (let i = 0; i < comparing.length; i += 2) {
@@ -3298,6 +3394,8 @@ function isAssignableInner(a, b) {
3298
3394
  }
3299
3395
  if (a.kind === "object") {
3300
3396
  if (b.kind !== "object") return false;
3397
+ if (b.class) return a.class !== void 0 && a.class.ancestors.includes(b.class.name);
3398
+ if (a.class && (b.indexer || b.properties.size === 0)) return false;
3301
3399
  for (const [name, bp] of b.properties) {
3302
3400
  const ap = a.properties.get(name);
3303
3401
  if (!ap) {
@@ -3440,6 +3538,7 @@ function containsFreeTypeParam(t, seen, bound) {
3440
3538
  case "intersection":
3441
3539
  return t.types.some((m) => containsTypeParam(m, seen, bound));
3442
3540
  case "object":
3541
+ if (t.class) return false;
3443
3542
  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));
3444
3543
  case "function": {
3445
3544
  const inner = t.typeParams?.length ? /* @__PURE__ */ new Set([...bound, ...t.typeParams]) : bound;
@@ -3638,7 +3737,7 @@ function mergeObjectMembers(types) {
3638
3737
  return expanded !== void 0 && expanded !== t && collect(expanded);
3639
3738
  }
3640
3739
  if (t.kind === "intersection") return t.types.every(collect);
3641
- if (t.kind === "object") {
3740
+ if (t.kind === "object" && !t.class) {
3642
3741
  objects.push(t);
3643
3742
  return true;
3644
3743
  }
@@ -3850,6 +3949,62 @@ function keepsLiterals(paramType) {
3850
3949
  const members = paramType.constraint.kind === "union" ? paramType.constraint.types : [paramType.constraint];
3851
3950
  return members.some((m) => m.kind === "literal");
3852
3951
  }
3952
+ var AliasMap = class extends Map {
3953
+ pending = /* @__PURE__ */ new Map();
3954
+ defer(name, resolve5) {
3955
+ super.delete(name);
3956
+ this.pending.set(name, resolve5);
3957
+ }
3958
+ get(name) {
3959
+ const resolved = super.get(name);
3960
+ if (resolved !== void 0) return resolved;
3961
+ const resolve5 = this.pending.get(name);
3962
+ if (!resolve5) return void 0;
3963
+ this.pending.delete(name);
3964
+ const type = resolve5();
3965
+ super.set(name, type);
3966
+ return type;
3967
+ }
3968
+ has(name) {
3969
+ return super.has(name) || (this.pending?.has(name) ?? false);
3970
+ }
3971
+ set(name, type) {
3972
+ this.pending?.delete(name);
3973
+ return super.set(name, type);
3974
+ }
3975
+ delete(name) {
3976
+ const deferred = this.pending?.delete(name) ?? false;
3977
+ return super.delete(name) || deferred;
3978
+ }
3979
+ get size() {
3980
+ return super.size + (this.pending?.size ?? 0);
3981
+ }
3982
+ keys() {
3983
+ return [...super.keys(), ...this.pending?.keys() ?? []][Symbol.iterator]();
3984
+ }
3985
+ entries() {
3986
+ return [...this.keys()].map((name) => [name, this.get(name)])[Symbol.iterator]();
3987
+ }
3988
+ values() {
3989
+ return [...this.keys()].map((name) => this.get(name))[Symbol.iterator]();
3990
+ }
3991
+ forEach(callback, thisArg) {
3992
+ for (const [name, type] of this.entries()) callback.call(thisArg, type, name, this);
3993
+ }
3994
+ [Symbol.iterator]() {
3995
+ return this.entries();
3996
+ }
3997
+ };
3998
+ var METAMETHODS = {
3999
+ "+": "__add",
4000
+ "-": "__sub",
4001
+ "*": "__mul",
4002
+ "/": "__div",
4003
+ "//": "__idiv",
4004
+ "%": "__mod",
4005
+ "^": "__pow",
4006
+ "..": "__concat"
4007
+ };
3853
4008
  function posKey(name, line, column) {
3854
4009
  return `${name}@${line}:${column}`;
3855
4010
  }
@@ -3870,9 +4025,12 @@ var TypeAnalyzer = class {
3870
4025
  expectedTypeOf = /* @__PURE__ */ new Map();
3871
4026
  /** Public: each alias resolved once (generic aliases keep their params as
3872
4027
  * `typeParam` nodes in the body). */
3873
- aliases = /* @__PURE__ */ new Map();
4028
+ aliases = new AliasMap();
3874
4029
  /** Uninstantiated alias definitions, for `Name<Args>` instantiation. */
3875
4030
  aliasDefs = /* @__PURE__ */ new Map();
4031
+ /** See `resolveClass`. */
4032
+ classTypes = /* @__PURE__ */ new WeakMap();
4033
+ classMembers = /* @__PURE__ */ new WeakMap();
3876
4034
  /** Generic parameters currently in lexical scope (alias body / generic fn),
3877
4035
  * with their `extends` constraints resolved. */
3878
4036
  typeParamScope = [];
@@ -3969,6 +4127,13 @@ var TypeAnalyzer = class {
3969
4127
  if (stmt.type !== "ImportStatement") continue;
3970
4128
  const exports2 = this.moduleFor(stmt.source.value);
3971
4129
  if (!exports2) continue;
4130
+ if (stmt.namespaceImport) {
4131
+ for (const [name, exported] of exports2.types) {
4132
+ const qualified = `${stmt.namespaceImport.name}.${name}`;
4133
+ this.importedTypes.set(qualified, exported);
4134
+ this.aliases.set(qualified, exported.type);
4135
+ }
4136
+ }
3972
4137
  for (const s of stmt.specifiers) {
3973
4138
  const exported = exports2.types.get(s.imported.name);
3974
4139
  if (exported) {
@@ -3982,24 +4147,123 @@ var TypeAnalyzer = class {
3982
4147
  for (const stmt of block.statements) {
3983
4148
  const alias = stmt.type === "TypeAliasStatement" ? stmt : stmt.type === "ExportTypeAliasStatement" ? stmt.alias : void 0;
3984
4149
  if (alias) this.aliasDefs.set(alias.name.name, { params: alias.generics, node: alias.definition });
4150
+ if (stmt.type === "DeclareClassStatement") {
4151
+ this.aliasDefs.set(stmt.name.name, { params: [], node: stmt.body, class: stmt });
4152
+ }
3985
4153
  }
3986
4154
  }
3987
- /** Seed global types from `declare` statements. Repeating a name builds an
3988
- * *overload set* (an intersection, in declaration order) rather than
3989
- * replacing which is how `typeof` gets one signature per result string. */
4155
+ /** A non-generic definition's type. */
4156
+ resolveDef(def) {
4157
+ return def.class ? this.classType(def.class) : this.resolveType(def.node);
4158
+ }
4159
+ /** One type per class declaration, so every mention of a class is the same
4160
+ * object — its own members included, which refer back to it. */
4161
+ classType(stmt) {
4162
+ return this.classTypes.get(stmt) ?? this.resolveClass(stmt);
4163
+ }
4164
+ /** A class's members are resolved the first time anyone asks for
4165
+ * `properties` — its own from its body, the inherited ones from its
4166
+ * superclass.
4167
+ *
4168
+ * Both have to wait. A definitions file for a whole engine declares
4169
+ * thousands of classes that all refer to one another; resolving each body
4170
+ * as soon as the class is named would resolve every class on every
4171
+ * analysis, when a script touches a handful. And classes refer to one
4172
+ * another constantly — `Object.IsA` mentions a map of every class, each
4173
+ * of which extends `Object` — so while one class resolves, one it extends
4174
+ * may itself be half-resolved; copying its members then would miss some
4175
+ * for good. */
4176
+ resolveClass(stmt) {
4177
+ const name = stmt.name.name;
4178
+ const { ancestors, cyclic } = this.classChain(stmt);
4179
+ const superclass = !cyclic && ancestors.length > 1 ? this.aliasDefs.get(ancestors[1])?.class : void 0;
4180
+ let own;
4181
+ let resolvingOwn = false;
4182
+ const ownMembers = () => {
4183
+ if (own || resolvingOwn) return own;
4184
+ resolvingOwn = true;
4185
+ try {
4186
+ own = this.resolveType(stmt.body);
4187
+ } finally {
4188
+ resolvingOwn = false;
4189
+ }
4190
+ return own;
4191
+ };
4192
+ let complete;
4193
+ const members = () => {
4194
+ if (complete) return complete;
4195
+ const mine = ownMembers();
4196
+ if (!mine) return void 0;
4197
+ const base = superclass ? this.classMembers.get(this.classType(superclass))?.() : void 0;
4198
+ if (superclass && !base) return void 0;
4199
+ return complete = {
4200
+ properties: new Map([...base?.properties ?? [], ...mine.properties]),
4201
+ indexer: mine.indexer ?? base?.indexer
4202
+ };
4203
+ };
4204
+ const type = { kind: "object", name, class: { name, superclass: superclass?.name.name, ancestors } };
4205
+ Object.defineProperties(type, {
4206
+ properties: { enumerable: true, get: () => members()?.properties ?? own?.properties ?? /* @__PURE__ */ new Map() },
4207
+ indexer: { enumerable: true, get: () => members()?.indexer ?? own?.indexer }
4208
+ });
4209
+ this.classTypes.set(stmt, type);
4210
+ this.classMembers.set(type, members);
4211
+ if (this.program.body.statements.includes(stmt)) ownMembers();
4212
+ return type;
4213
+ }
4214
+ /** `extends` must name a class, and the chain must end. */
4215
+ checkClass(stmt) {
4216
+ if (!stmt.superclass || !this.emitDiagnostics) return;
4217
+ const base = stmt.superclass.base;
4218
+ if (!this.aliasDefs.get(base)?.class) {
4219
+ const known = this.aliasDefs.has(base) || this.importedTypes.has(base);
4220
+ this.diagnostics.push({
4221
+ node: stmt.superclass,
4222
+ message: known ? `'${base}' is not a class; a class can only extend another class` : `Cannot find class '${base}'`
4223
+ });
4224
+ } else if (this.classChain(stmt).cyclic) {
4225
+ this.diagnostics.push({ node: stmt.superclass, message: `'${stmt.name.name}' cannot extend itself` });
4226
+ }
4227
+ }
4228
+ /** The class and the classes it extends, nearest first, read from the
4229
+ * declarations — no type has to be resolved to know them. The walk stops
4230
+ * at a superclass that is not a class. */
4231
+ classChain(stmt) {
4232
+ const ancestors = [stmt.name.name];
4233
+ for (let cls = stmt; cls?.superclass; ) {
4234
+ const base = cls.superclass.base;
4235
+ if (ancestors.includes(base)) return { ancestors, cyclic: true };
4236
+ cls = this.aliasDefs.get(base)?.class;
4237
+ if (!cls) break;
4238
+ ancestors.push(base);
4239
+ }
4240
+ return { ancestors, cyclic: false };
4241
+ }
4242
+ /** Seed global types from `declare` statements. Repeating a function name
4243
+ * builds an *overload set* (an intersection, in declaration order) rather
4244
+ * than replacing — which is how `typeof` gets one signature per result
4245
+ * string. Any other value is simply redeclared: a sourcemap's
4246
+ * `declare script: <this file's instance>` replaces the library's
4247
+ * `declare script: LuaSourceContainer`. */
3990
4248
  harvestDeclares(block) {
3991
4249
  for (const stmt of block.statements) {
3992
4250
  if (stmt.type !== "DeclareStatement") continue;
3993
4251
  const t = this.resolveType(stmt.valueType);
3994
4252
  const prev = this.libGlobalTypes.get(stmt.name);
3995
- this.libGlobalTypes.set(stmt.name, prev ? intersection([prev, t]) : t);
4253
+ const overload = prev && stmt.valueType.type === "FunctionTypeNode" && (prev.kind === "function" || prev.kind === "intersection");
4254
+ this.libGlobalTypes.set(stmt.name, overload ? intersection([prev, t]) : t);
3996
4255
  }
3997
4256
  }
3998
4257
  resolveAllAliases() {
3999
4258
  for (const [name, def] of this.aliasDefs) {
4259
+ if (def.class && !this.program.body.statements.includes(def.class)) {
4260
+ const cls = def.class;
4261
+ this.aliases.defer(name, () => this.classType(cls));
4262
+ continue;
4263
+ }
4000
4264
  if (containsTypeQuery(def.node)) continue;
4001
4265
  this.withTypeParams(def.params, () => {
4002
- this.aliases.set(name, this.resolveType(def.node));
4266
+ this.aliases.set(name, this.resolveDef(def));
4003
4267
  });
4004
4268
  }
4005
4269
  }
@@ -4009,7 +4273,7 @@ var TypeAnalyzer = class {
4009
4273
  for (const [name, def] of this.aliasDefs) {
4010
4274
  if (this.aliases.has(name)) continue;
4011
4275
  this.withTypeParams(def.params, () => {
4012
- this.aliases.set(name, this.resolveType(def.node));
4276
+ this.aliases.set(name, this.resolveDef(def));
4013
4277
  });
4014
4278
  }
4015
4279
  return this.aliases;
@@ -4037,10 +4301,7 @@ var TypeAnalyzer = class {
4037
4301
  /** Instantiate a generic alias: `Box<number>` -> `{ value: number }`. */
4038
4302
  instantiateAlias(def, args) {
4039
4303
  if (this.instantiationDepth > 20) return unknownType;
4040
- const subst = /* @__PURE__ */ new Map();
4041
- def.params.forEach((p, i) => {
4042
- subst.set(p.name, args[i] ?? (p.default ? this.resolveType(p.default) : unknownType));
4043
- });
4304
+ const subst = this.bindTypeArguments(def.params, args);
4044
4305
  this.instantiationDepth++;
4045
4306
  try {
4046
4307
  const body = this.withTypeParams(def.params, () => this.resolveType(def.node));
@@ -4049,6 +4310,34 @@ var TypeAnalyzer = class {
4049
4310
  this.instantiationDepth--;
4050
4311
  }
4051
4312
  }
4313
+ /** Pair written type arguments with the parameters they instantiate. A
4314
+ * pack parameter (`T...`) takes every argument from its position on, as
4315
+ * one pack: `Signal<Instance, string>` binds `T` to `(Instance, string)`,
4316
+ * and `Signal<()>` to the empty pack. Left out, a parameter takes its
4317
+ * default (`T... = ...any` is `any`), or `unknown`. */
4318
+ bindTypeArguments(params, args) {
4319
+ const subst = /* @__PURE__ */ new Map();
4320
+ params.forEach((p, i) => {
4321
+ let arg = args[i];
4322
+ if (p.isPack && i < args.length) {
4323
+ const rest = args.slice(i);
4324
+ const single = rest.length === 1 ? rest[0] : void 0;
4325
+ arg = single && (single.kind === "tuple" && single.isPack || single.kind === "typeParam" || single.kind === "any") ? single : tuple([...rest], true);
4326
+ }
4327
+ subst.set(p.name, arg ?? (p.default ? this.resolveType(p.default) : unknownType));
4328
+ });
4329
+ return subst;
4330
+ }
4331
+ /** An imported type, with its type arguments applied. */
4332
+ importedType(imported, typeArguments) {
4333
+ if (!imported.params.length) return imported.type;
4334
+ const subst = /* @__PURE__ */ new Map();
4335
+ imported.params.forEach((name, i) => {
4336
+ const arg = typeArguments[i];
4337
+ subst.set(name, arg ? this.resolveType(arg) : unknownType);
4338
+ });
4339
+ return this.reduceType(substitute(imported.type, subst));
4340
+ }
4052
4341
  // --------------------------------------------------------
4053
4342
  // TypeNode -> Type
4054
4343
  // --------------------------------------------------------
@@ -4099,17 +4388,17 @@ var TypeAnalyzer = class {
4099
4388
  });
4100
4389
  }
4101
4390
  const imported = this.importedTypes.get(node.base);
4102
- if (imported) {
4103
- if (!imported.params.length) return imported.type;
4104
- const subst = /* @__PURE__ */ new Map();
4105
- imported.params.forEach((name2, i) => {
4106
- const arg = node.typeArguments[i];
4107
- subst.set(name2, arg ? this.resolveType(arg) : unknownType);
4108
- });
4109
- return this.reduceType(substitute(imported.type, subst));
4110
- }
4391
+ if (imported) return this.importedType(imported, node.typeArguments);
4111
4392
  const lib = this.options.libTypes?.[node.base];
4112
4393
  if (lib) return lib;
4394
+ } else if (this.importedTypes.has(name)) {
4395
+ return this.importedType(this.importedTypes.get(name), node.typeArguments);
4396
+ } else if (this.aliasDefs.has(name)) {
4397
+ return this.expand({
4398
+ kind: "genericRef",
4399
+ name,
4400
+ typeArguments: node.typeArguments.map((a) => this.resolveType(a))
4401
+ });
4113
4402
  }
4114
4403
  return {
4115
4404
  kind: "genericRef",
@@ -4236,6 +4525,7 @@ var TypeAnalyzer = class {
4236
4525
  return this.resolveType(node.typeAnnotation);
4237
4526
  case "TypePackNode": {
4238
4527
  if (node.types.length === 1 && !node.hasVarargs) return this.resolveType(node.types[0]);
4528
+ if (!node.types.length && node.varargType) return this.resolveType(node.varargType);
4239
4529
  return tuple(node.types.map((t) => this.resolveType(t)), true);
4240
4530
  }
4241
4531
  }
@@ -4259,7 +4549,7 @@ var TypeAnalyzer = class {
4259
4549
  this.reduceDepth++;
4260
4550
  try {
4261
4551
  const result = this.reduceTypeInner(t);
4262
- this.reduceCache.set(t, result);
4552
+ if (result.kind !== "keyof") this.reduceCache.set(t, result);
4263
4553
  return result;
4264
4554
  } finally {
4265
4555
  this.reduceDepth--;
@@ -4271,6 +4561,7 @@ var TypeAnalyzer = class {
4271
4561
  case "keyof": {
4272
4562
  const target = this.reduceType(t.target);
4273
4563
  if (containsTypeParam(target)) return { kind: "keyof", target };
4564
+ if (target.kind === "genericRef" && this.resolvingAliases.has(target.name)) return t;
4274
4565
  return this.keysOf(target);
4275
4566
  }
4276
4567
  case "indexedAccess": {
@@ -4312,6 +4603,7 @@ var TypeAnalyzer = class {
4312
4603
  t.predicate
4313
4604
  );
4314
4605
  case "object": {
4606
+ if (t.class) return t;
4315
4607
  const entries = [];
4316
4608
  for (const [k, v] of t.properties) entries.push([k, { ...v, type: this.reduceType(v.type) }]);
4317
4609
  const reduced = objectType(entries, t.indexer && {
@@ -4451,6 +4743,7 @@ var TypeAnalyzer = class {
4451
4743
  t.typeParams
4452
4744
  );
4453
4745
  case "object": {
4746
+ if (t.class) return t;
4454
4747
  const entries = [];
4455
4748
  for (const [k, v] of t.properties) entries.push([k, { ...v, type: this.stripInfer(v.type, bindings) }]);
4456
4749
  return objectType(entries, t.indexer && {
@@ -4504,6 +4797,11 @@ var TypeAnalyzer = class {
4504
4797
  visitStatement(stmt, env) {
4505
4798
  switch (stmt.type) {
4506
4799
  case "VariableDeclaration": {
4800
+ stmt.names.forEach((target, i) => {
4801
+ if (target.type === "IdentifierPattern" && target.typeAnnotation && stmt.init[i]) {
4802
+ this.applyContext(stmt.init[i], this.resolveType(target.typeAnnotation));
4803
+ }
4804
+ });
4507
4805
  const { types: valueTypes, sources } = this.valueList(stmt.init, env);
4508
4806
  stmt.names.forEach((target, i) => {
4509
4807
  const inferred = valueTypes[i] ?? (stmt.init.length ? unknownType : nilType);
@@ -4564,6 +4862,16 @@ var TypeAnalyzer = class {
4564
4862
  return;
4565
4863
  }
4566
4864
  case "AssignmentStatement": {
4865
+ stmt.targets.forEach((target, i) => {
4866
+ const value = stmt.values[i];
4867
+ if (!value) return;
4868
+ if (target.type === "MemberExpression" || target.type === "IndexExpression") {
4869
+ this.applyContext(value, this.infer(target, env));
4870
+ } else if (target.type === "Identifier") {
4871
+ const id = this.bindingIdOf(target);
4872
+ if (id !== void 0 && this.annotated.has(id)) this.applyContext(value, this.bindingType.get(id));
4873
+ }
4874
+ });
4567
4875
  const { types: valueTypes, sources } = this.valueList(stmt.values, env);
4568
4876
  stmt.targets.forEach((target, i) => {
4569
4877
  const vt = valueTypes[i] ?? unknownType;
@@ -4688,6 +4996,14 @@ var TypeAnalyzer = class {
4688
4996
  };
4689
4997
  if (resolving && !exports2) report(stmt.source, `Cannot find module '${specifier}'`);
4690
4998
  const usable = exports2 && !exports2.partial ? exports2 : void 0;
4999
+ if (stmt.namespaceImport) {
5000
+ const id = this.bindingIdByName(stmt.namespaceImport.name, stmt.namespaceImport);
5001
+ if (id !== void 0) {
5002
+ const members = [...usable?.values ?? []].map(([name, type]) => [name, { type, optional: false, readonly: true }]);
5003
+ if (usable?.default) members.push(["default", { type: usable.default, optional: false, readonly: true }]);
5004
+ this.bindingType.set(id, usable ? objectType(members) : anyType);
5005
+ }
5006
+ }
4691
5007
  if (stmt.defaultImport) {
4692
5008
  if (usable && usable.default === void 0) {
4693
5009
  report(stmt.defaultImport, `Module '${specifier}' has no default export`);
@@ -4708,6 +5024,9 @@ var TypeAnalyzer = class {
4708
5024
  case "BreakStatement":
4709
5025
  this.breakStates[this.breakStates.length - 1]?.push(forkEnv(env));
4710
5026
  return;
5027
+ case "DeclareClassStatement":
5028
+ this.checkClass(stmt);
5029
+ return;
4711
5030
  case "ContinueStatement":
4712
5031
  case "TypeAliasStatement":
4713
5032
  case "ExportTypeAliasStatement":
@@ -4829,7 +5148,89 @@ var TypeAnalyzer = class {
4829
5148
  }
4830
5149
  if (p.pattern) return this.patternToType(p.pattern, env);
4831
5150
  if (p.default) return widen(this.infer(p.default, env));
4832
- return anyType;
5151
+ return this.contextualParams.get(p) ?? anyType;
5152
+ }
5153
+ /** What a function expression's unannotated parameters are, from where
5154
+ * it is written — see `applyContext`. */
5155
+ contextualParams = /* @__PURE__ */ new WeakMap();
5156
+ /** `expected` is the type the surroundings want for `expr`. A function
5157
+ * expression written there takes its unannotated parameters' types from
5158
+ * it, as in TypeScript: `signal:Connect(function(player) ... end)` knows
5159
+ * `player` from `Connect`'s callback type. Anything else is inferred as
5160
+ * usual. */
5161
+ applyContext(expr, expected) {
5162
+ let e = expr;
5163
+ while (e.type === "ParenthesizedExpression") e = e.expression;
5164
+ if (!expected) return;
5165
+ if (e.type === "ArrayExpression") return this.applyArrayContext(e, expected);
5166
+ if (e.type === "TableExpression") return this.applyTableContext(e, expected);
5167
+ if (e.type !== "FunctionExpression") return;
5168
+ const members = expected.kind === "union" ? expected.types : [expected];
5169
+ const signatures = members.flatMap((m) => this.overloadsOf(this.expand(m)));
5170
+ if (!signatures.length) return;
5171
+ e.func.params.forEach((p, k) => {
5172
+ if (p.typeAnnotation || p.pattern || p.default) return;
5173
+ const candidates = [];
5174
+ for (const signature of signatures) {
5175
+ const t2 = signature.params[k]?.type ?? signature.varargs;
5176
+ if (t2) candidates.push(t2);
5177
+ }
5178
+ if (!candidates.length) return;
5179
+ const t = union(candidates);
5180
+ this.contextualParams.set(p, containsTypeParam(t) ? anyType : t);
5181
+ });
5182
+ }
5183
+ /** What an array literal is expected to be: an empty one takes that type
5184
+ * outright — `let queue: thread[] = []` is a `thread[]`, as in TypeScript
5185
+ * — and the elements of any other get the element type as their own
5186
+ * context. */
5187
+ contextualArrays = /* @__PURE__ */ new WeakMap();
5188
+ applyArrayContext(e, expected) {
5189
+ const target = this.expectedMembers(expected).find((m) => m.kind === "array" || m.kind === "tuple");
5190
+ if (!target) return;
5191
+ if (!e.elements.length) {
5192
+ if (!containsTypeParam(target)) this.contextualArrays.set(e, target);
5193
+ return;
5194
+ }
5195
+ e.elements.forEach((element, i) => {
5196
+ if (element.type === "SpreadElement") return;
5197
+ const elementType = target.kind === "array" ? target.element : target.elements[i];
5198
+ this.applyContext(element, elementType);
5199
+ });
5200
+ }
5201
+ /** `{ list: [] }` where `{ list: thread[] }` is expected: each field's
5202
+ * value gets its property's type as context. */
5203
+ applyTableContext(e, expected) {
5204
+ const objects = this.expectedMembers(expected).filter((m) => m.kind === "object");
5205
+ if (!objects.length) return;
5206
+ for (const field of e.fields) {
5207
+ if (field.type !== "TableFieldNamed") continue;
5208
+ const key = field.key.type === "Identifier" ? field.key.name : field.key.value;
5209
+ const types = objects.flatMap((o) => {
5210
+ const property = o.properties.get(key);
5211
+ return property ? [property.type] : o.indexer ? [o.indexer.value] : [];
5212
+ });
5213
+ if (types.length) this.applyContext(field.value, union(types));
5214
+ }
5215
+ }
5216
+ /** The members of an expected type worth matching a literal against:
5217
+ * aliases seen through, `nil` left out. */
5218
+ expectedMembers(expected) {
5219
+ const t = this.expand(expected);
5220
+ const members = t.kind === "union" ? t.types : [t];
5221
+ return members.map((m) => this.expand(m)).filter((m) => !(m.kind === "primitive" && m.name === "nil"));
5222
+ }
5223
+ /** The parameter type each written argument lands on, across `fns`. */
5224
+ expectedArguments(written, fns, selfOf) {
5225
+ return written.map((_, j) => {
5226
+ const candidates = [];
5227
+ for (const f of fns) {
5228
+ const i = j + selfOf(f);
5229
+ const param = i < f.params.length ? this.boundParams(f)[i] : f.varargs;
5230
+ if (param) candidates.push(param);
5231
+ }
5232
+ return candidates.length ? union(candidates) : void 0;
5233
+ });
4833
5234
  }
4834
5235
  /** Synthesize a type from a destructuring pattern used without an
4835
5236
  * annotation (`function f({ a, b = 1 })`). */
@@ -4881,7 +5282,8 @@ var TypeAnalyzer = class {
4881
5282
  f.params.forEach((p, i) => {
4882
5283
  const arg = argTypes[i];
4883
5284
  if (arg === void 0) return;
4884
- unify(p.type, keepsLiterals(p.type) ? arg : widen(arg), vars, subst);
5285
+ const param = p.type.kind === "typeParam" && p.type.constraint ? { ...p.type, constraint: this.reduceType(p.type.constraint) } : p.type;
5286
+ unify(p.type, keepsLiterals(param) ? arg : widen(arg), vars, subst);
4885
5287
  });
4886
5288
  for (const name of f.typeParams ?? []) if (!subst.has(name)) subst.set(name, unknownType);
4887
5289
  return subst;
@@ -4942,12 +5344,13 @@ var TypeAnalyzer = class {
4942
5344
  return;
4943
5345
  }
4944
5346
  const t = value;
5347
+ if (t.kind === "object" && t.class) return;
4945
5348
  if (t.kind === "typeParam" && typeof t.name === "string" && bounds.has(t.name) && t.constraint && !containsTypeParam(t.constraint)) {
4946
5349
  bounds.set(t.name, this.reduceType(t.constraint));
4947
5350
  }
4948
5351
  for (const child of Object.values(value)) walk(child);
4949
5352
  };
4950
- for (const p of f.params) walk(p.type);
5353
+ for (const p of f.params) if (containsTypeParam(p.type)) walk(p.type);
4951
5354
  return f.params.map((p) => substitute(p.type, bounds));
4952
5355
  }
4953
5356
  /** Record what each written argument is expected to be — see
@@ -5286,7 +5689,7 @@ var TypeAnalyzer = class {
5286
5689
  this.expandCache.set(key, t);
5287
5690
  this.resolvingAliases.add(t.name);
5288
5691
  try {
5289
- const r = def.params.length ? this.instantiateAlias(def, t.typeArguments) : this.resolveType(def.node);
5692
+ const r = def.params.length ? this.instantiateAlias(def, t.typeArguments) : this.resolveDef(def);
5290
5693
  const named = def.params.length === 0 && (r.kind === "object" || r.kind === "intersection") && !r.name ? { ...r, name: t.name } : r;
5291
5694
  this.expandCache.set(key, named);
5292
5695
  return named;
@@ -5391,8 +5794,9 @@ var TypeAnalyzer = class {
5391
5794
  return this.resolveType(expr.typeAnnotation);
5392
5795
  }
5393
5796
  case "SatisfiesExpression": {
5394
- const actual = this.infer(expr.expression, env);
5395
5797
  const declared = this.resolveType(expr.typeAnnotation);
5798
+ this.applyContext(expr.expression, declared);
5799
+ const actual = this.infer(expr.expression, env);
5396
5800
  if (this.emitDiagnostics && declared.kind !== "any" && !this.fitsAnnotation(expr.expression, declared, actual, env)) {
5397
5801
  this.diagnostics.push({
5398
5802
  node: expr,
@@ -5409,9 +5813,9 @@ var TypeAnalyzer = class {
5409
5813
  case "not":
5410
5814
  return booleanType;
5411
5815
  case "-":
5412
- return numberType;
5816
+ return this.operatorResult(expr, "-", arg, void 0) ?? numberType;
5413
5817
  case "#":
5414
- return numberType;
5818
+ return this.operatorResult(expr, "#", arg, void 0) ?? numberType;
5415
5819
  }
5416
5820
  return arg;
5417
5821
  }
@@ -5433,7 +5837,7 @@ var TypeAnalyzer = class {
5433
5837
  const r = this.infer(expr.right, env);
5434
5838
  switch (op) {
5435
5839
  case "..":
5436
- return stringType;
5840
+ return this.operatorResult(expr, op, l, r) ?? stringType;
5437
5841
  case "==":
5438
5842
  case "~=":
5439
5843
  case "<":
@@ -5448,7 +5852,7 @@ var TypeAnalyzer = class {
5448
5852
  case "//":
5449
5853
  case "%":
5450
5854
  case "^":
5451
- return numberType;
5855
+ return this.operatorResult(expr, op, l, r) ?? numberType;
5452
5856
  }
5453
5857
  return union([l, r]);
5454
5858
  }
@@ -5467,8 +5871,10 @@ var TypeAnalyzer = class {
5467
5871
  }
5468
5872
  case "CallExpression": {
5469
5873
  const callee = this.infer(expr.callee, env);
5470
- const argTypes = expr.arguments.map((a) => this.infer(a, env));
5471
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));
5472
5878
  if (fns.length) {
5473
5879
  this.recordExpected(expr.arguments, fns, () => 0);
5474
5880
  const arityFits = this.checkArity(expr, fns, argTypes.length, 0);
@@ -5483,8 +5889,10 @@ var TypeAnalyzer = class {
5483
5889
  }
5484
5890
  case "MethodCallExpression": {
5485
5891
  const objType = this.infer(expr.object, env);
5486
- const argTypes = expr.arguments.map((a) => this.infer(a, env));
5487
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));
5488
5896
  if (fns.length) {
5489
5897
  const withSelf = (f) => this.takesSelf(f) ? [objType, ...argTypes] : argTypes;
5490
5898
  const selfOf = (f) => this.takesSelf(f) ? 1 : 0;
@@ -5516,6 +5924,8 @@ var TypeAnalyzer = class {
5516
5924
  }
5517
5925
  }
5518
5926
  inferArray(expr, env, asConst) {
5927
+ const contextual = this.contextualArrays.get(expr);
5928
+ if (contextual && !asConst) return contextual;
5519
5929
  const elems = [];
5520
5930
  let hadSpread = false;
5521
5931
  for (const el of expr.elements) {
@@ -5876,6 +6286,35 @@ var TypeAnalyzer = class {
5876
6286
  this.selfType = saved;
5877
6287
  }
5878
6288
  }
6289
+ /** What an operator on a value with metamethods gives: `a + b` calls
6290
+ * `__add` on `a`, or failing that on `b` with the operands swapped — the
6291
+ * order Luau tries them in. That is how `Vector3 + Vector3`, `CFrame *
6292
+ * Vector3` and `2 * vector` get their types from the declarations.
6293
+ * `undefined` when neither operand declares the metamethod; an operand
6294
+ * that declares it but accepts neither argument is reported. */
6295
+ operatorResult(node, op, left, right) {
6296
+ const name = right === void 0 ? op === "-" ? "__unm" : "__len" : METAMETHODS[op];
6297
+ if (!name) return void 0;
6298
+ const candidates = right === void 0 ? [[left, void 0]] : [[left, right], [right, left]];
6299
+ let declared;
6300
+ for (const [receiver, other] of candidates) {
6301
+ const t = this.expand(receiver);
6302
+ const method = t.kind === "object" ? t.properties.get(name) : void 0;
6303
+ if (!method) continue;
6304
+ declared ??= receiver;
6305
+ const args = other === void 0 ? [receiver] : [receiver, other];
6306
+ const picked = this.pickOverload(this.overloadsOf(method.type), args);
6307
+ if (picked) return this.callReturn(picked, args);
6308
+ }
6309
+ if (declared && this.emitDiagnostics) {
6310
+ this.diagnostics.push({
6311
+ node,
6312
+ 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)}'`
6313
+ });
6314
+ return anyType;
6315
+ }
6316
+ return void 0;
6317
+ }
5879
6318
  /** Does this signature take the receiver as its first parameter?
5880
6319
  *
5881
6320
  * Luau's `:` is sugar both ways: `function T:m(a)` declares
@@ -6140,13 +6579,13 @@ function resolveTypeLibraries(config, host = nodeHost) {
6140
6579
  else problems.push({ file: config.path, message: `'${entry}' has no ${ENTRY_FILE} (or 'luaut.types' in its package.json)`, ...entryPosition(config, entry) });
6141
6580
  continue;
6142
6581
  }
6143
- const names = entry.startsWith("@") || entry.includes("/") ? [entry] : [`@luaut/${entry}`, entry];
6144
- const found = names.map((name) => findPackage(name, config.directory, host)).find(Boolean);
6582
+ const name = entry.startsWith("@luaut/") ? entry : `@luaut/${entry}`;
6583
+ const found = findPackage(name, config.directory, host);
6145
6584
  if (found) addPackage(found.directory, found.file, /* @__PURE__ */ new Set());
6146
6585
  else {
6147
6586
  problems.push({
6148
6587
  file: config.path,
6149
- message: `Cannot find type library '${entry}'. Install it with: npm i -D ${names[0]}`,
6588
+ message: `Cannot find type library '${name}'. Install it with: npm i -D ${name}`,
6150
6589
  ...entryPosition(config, entry)
6151
6590
  });
6152
6591
  }
@@ -6249,7 +6688,6 @@ function sourceMapTypes(text, path, options) {
6249
6688
  const lines = [];
6250
6689
  const aliasOfFile = /* @__PURE__ */ new Map();
6251
6690
  const used = /* @__PURE__ */ new Set();
6252
- const canOmit = options.classes.has("Omit");
6253
6691
  const aliasOfNode = /* @__PURE__ */ new Map();
6254
6692
  const aliasFor = (segments) => {
6255
6693
  const base = `SourceMap_${segments.map((s) => s.replace(/[^A-Za-z0-9_]/g, "_")).join("_")}`;
@@ -6265,7 +6703,7 @@ function sourceMapTypes(text, path, options) {
6265
6703
  const className = IDENTIFIER.test(node.className) && options.classes.has(node.className) ? node.className : "Instance";
6266
6704
  const taken = options.membersOf?.(className) ?? INSTANCE_MEMBERS;
6267
6705
  const members = [];
6268
- if (parent && canOmit) members.push(`Parent: ${parent}`);
6706
+ if (parent) members.push(`Parent: ${parent}`);
6269
6707
  const named = /* @__PURE__ */ new Set();
6270
6708
  for (const child of node.children ?? []) {
6271
6709
  if (!isNode(child)) continue;
@@ -6274,8 +6712,7 @@ function sourceMapTypes(text, path, options) {
6274
6712
  named.add(child.name);
6275
6713
  members.push(`${child.name}: ${childAlias}`);
6276
6714
  }
6277
- const base = parent && canOmit ? `Omit<${className}, "Parent">` : className;
6278
- lines.push(`type ${alias} = ${members.length ? `${base} & { ${members.join(", ")} }` : base}`);
6715
+ lines.push(`declare class ${alias} extends ${className} { ${members.join(", ")} }`);
6279
6716
  return alias;
6280
6717
  };
6281
6718
  const rootAlias = visit(root, [root.name], void 0);
@@ -6353,6 +6790,7 @@ var index_default = luautparser;
6353
6790
  getBinding,
6354
6791
  intersection,
6355
6792
  isAssignable,
6793
+ isClassType,
6356
6794
  isGlobal,
6357
6795
  isPossiblyFalsy,
6358
6796
  isPossiblyTruthy,