luaut-parser 1.0.0 → 1.2.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
@@ -54,6 +54,7 @@ __export(index_exports, {
54
54
  luauLib: () => luauLib,
55
55
  luautparser: () => luautparser,
56
56
  matchInfer: () => matchInfer,
57
+ moduleExports: () => moduleExports,
57
58
  narrowExclude: () => narrowExclude,
58
59
  narrowFalsy: () => narrowFalsy,
59
60
  narrowTo: () => narrowTo,
@@ -634,6 +635,17 @@ function spanFrom(start, end) {
634
635
  column: { start: start.column.start, end: end.column.end }
635
636
  };
636
637
  }
638
+ function tokenIdentifier(t) {
639
+ return { type: "Identifier", name: t.value, ...spanFrom(t, t) };
640
+ }
641
+ function nameIdentifier(name, at) {
642
+ return {
643
+ type: "Identifier",
644
+ name,
645
+ line: { start: at.line.start, end: at.line.start },
646
+ column: { start: at.column.start, end: at.column.start + name.length }
647
+ };
648
+ }
637
649
  var BINARY_PRECEDENCE = {
638
650
  "or": 1,
639
651
  "and": 2,
@@ -952,9 +964,11 @@ var Parser = class {
952
964
  const params = head.params.map((p) => ({
953
965
  type: "FunctionTypeParameter",
954
966
  name: p.name || void 0,
967
+ id: p.name ? nameIdentifier(p.name, p) : void 0,
955
968
  optional: p.optional,
956
- typeAnnotation: p.typeAnnotation ?? { type: "TypeReference", base: "any", typeArguments: [], ...spanFrom(start, start) },
957
- ...spanFrom(start, this.previous())
969
+ typeAnnotation: p.typeAnnotation ?? { type: "TypeReference", base: "any", typeArguments: [], line: p.line, column: p.column },
970
+ line: p.line,
971
+ column: p.column
958
972
  }));
959
973
  const valueType2 = {
960
974
  type: "FunctionTypeNode",
@@ -966,12 +980,12 @@ var Parser = class {
966
980
  predicate: head.predicate,
967
981
  ...spanFrom(start, this.previous())
968
982
  };
969
- return { type: "DeclareStatement", name: nameTok2.value, valueType: valueType2, ...spanFrom(start, this.previous()) };
983
+ return { type: "DeclareStatement", name: nameTok2.value, id: tokenIdentifier(nameTok2), valueType: valueType2, ...spanFrom(start, this.previous()) };
970
984
  }
971
985
  const nameTok = this.expectIdentifier();
972
986
  this.expectPunctuator(":");
973
987
  const valueType = this.parseType();
974
- return { type: "DeclareStatement", name: nameTok.value, valueType, ...spanFrom(start, this.previous()) };
988
+ return { type: "DeclareStatement", name: nameTok.value, id: tokenIdentifier(nameTok), valueType, ...spanFrom(start, this.previous()) };
975
989
  }
976
990
  // `import { a, b as c } from '...'` / `import Default from '...'` /
977
991
  // `import Default, { a } from '...'`. Compiled away entirely by the
@@ -1030,6 +1044,21 @@ var Parser = class {
1030
1044
  }
1031
1045
  return { type: "ImportSpecifier", imported, local, ...spanFrom(imported, local) };
1032
1046
  }
1047
+ /** `from "<path>"`: consumes `from` and the module string. */
1048
+ parseModuleSource() {
1049
+ this.advance();
1050
+ const sourceTok = this.current();
1051
+ if (sourceTok.type !== "Literal" || sourceTok.kind !== "string") {
1052
+ this.error("Expected string literal module path after 'from'");
1053
+ }
1054
+ this.advance();
1055
+ return {
1056
+ type: "StringLiteral",
1057
+ value: sourceTok.value,
1058
+ raw: sourceTok.raw,
1059
+ ...spanFrom(sourceTok, sourceTok)
1060
+ };
1061
+ }
1033
1062
  // `export const ...` / `export let ...` / `export const function ...` /
1034
1063
  // `export type ...` / `export default <expr>`
1035
1064
  parseExportStatement() {
@@ -1048,7 +1077,30 @@ var Parser = class {
1048
1077
  const declaration = this.parseVariableDeclaration();
1049
1078
  return { type: "ExportStatement", declaration, ...spanFrom(start, this.previous()) };
1050
1079
  }
1051
- this.error("Expected 'const', 'let', 'type', or 'default' after 'export'");
1080
+ if (this.checkPunctuator("{")) {
1081
+ this.advance();
1082
+ const specifiers = [];
1083
+ while (!this.checkPunctuator("}")) {
1084
+ const local = this.parseIdentifier();
1085
+ let exported = local;
1086
+ if (this.checkKeyword("as")) {
1087
+ this.advance();
1088
+ exported = this.parseIdentifier();
1089
+ }
1090
+ specifiers.push({ type: "ExportSpecifier", local, exported, ...spanFrom(local, exported) });
1091
+ if (!this.matchPunctuator(",")) break;
1092
+ }
1093
+ this.expectPunctuator("}");
1094
+ const source = this.checkKeyword("from") ? this.parseModuleSource() : void 0;
1095
+ return { type: "ExportNamedStatement", specifiers, source, ...spanFrom(start, this.previous()) };
1096
+ }
1097
+ if (this.checkOperator("*")) {
1098
+ this.advance();
1099
+ if (!this.checkKeyword("from")) this.error("Expected 'from' after 'export *'");
1100
+ const source = this.parseModuleSource();
1101
+ return { type: "ExportAllStatement", source, ...spanFrom(start, this.previous()) };
1102
+ }
1103
+ this.error("Expected 'const', 'let', 'type', 'default', '{' or '*' after 'export'");
1052
1104
  }
1053
1105
  // `const x = ...` / `let x, y = ...` / `const function f() ... end`.
1054
1106
  // luaut has no `local` — `const` bindings are immutable, `let` mutable.
@@ -2082,8 +2134,9 @@ var Parser = class {
2082
2134
  }
2083
2135
  if (this.checkIdentifierValue("infer") && this.peek(1).type === "Identifier") {
2084
2136
  this.advance();
2085
- const name = this.expectIdentifier().value;
2086
- return { type: "InferTypeNode", name, ...spanFrom(t, this.previous()) };
2137
+ const nameTok = this.expectIdentifier();
2138
+ const name = nameTok.value;
2139
+ return { type: "InferTypeNode", name, id: tokenIdentifier(nameTok), ...spanFrom(t, this.previous()) };
2087
2140
  }
2088
2141
  if (t.type === "Operator" && t.value === "<") {
2089
2142
  const generics = this.parseGenericTypeParameterList();
@@ -2120,6 +2173,16 @@ var Parser = class {
2120
2173
  this.expectPunctuator(")");
2121
2174
  return { type: "TypeofTypeNode", expression, ...spanFrom(t, this.previous()) };
2122
2175
  }
2176
+ if (t.type === "Identifier" && t.value === "typeof" && this.peek(1).type === "Identifier") {
2177
+ this.advance();
2178
+ let expression = this.parseIdentifier();
2179
+ while (this.checkPunctuator(".") && this.peek(1).type === "Identifier") {
2180
+ this.advance();
2181
+ const property = this.parseIdentifier();
2182
+ expression = { type: "MemberExpression", object: expression, property, ...spanFrom(expression, property) };
2183
+ }
2184
+ return { type: "TypeofTypeNode", expression, ...spanFrom(t, this.previous()) };
2185
+ }
2123
2186
  if (t.type === "Literal" && t.kind === "string") {
2124
2187
  this.advance();
2125
2188
  return { type: "TypeLiteralString", value: t.value, ...spanFrom(t, t) };
@@ -2182,17 +2245,21 @@ var Parser = class {
2182
2245
  let name;
2183
2246
  let optional2 = false;
2184
2247
  const named = this.checkType("Identifier") && this.peek(1).type === "Punctuator" && (this.peek(1).value === ":" || this.peek(1).value === "?" && this.peek(2).type === "Punctuator" && this.peek(2).value === ":");
2248
+ let nameTok;
2185
2249
  if (named) {
2186
- name = this.expectIdentifier().value;
2250
+ const tok = this.expectIdentifier();
2251
+ nameTok = tok;
2252
+ name = tok.value;
2187
2253
  optional2 = this.matchPunctuator("?");
2188
2254
  this.advance();
2189
2255
  }
2190
- const paramStart = this.current();
2256
+ const paramStart = nameTok ?? this.current();
2191
2257
  const typeAnnotation = this.parseType();
2192
2258
  params.push({
2193
2259
  type: "FunctionTypeParameter",
2194
2260
  name,
2195
2261
  typeAnnotation,
2262
+ id: nameTok && tokenIdentifier(nameTok),
2196
2263
  optional: optional2 || void 0,
2197
2264
  ...spanFrom(paramStart, this.previous())
2198
2265
  });
@@ -2254,7 +2321,8 @@ var Parser = class {
2254
2321
  readonly = false;
2255
2322
  }
2256
2323
  this.expectPunctuator("[");
2257
- const parameter = this.expectIdentifier().value;
2324
+ const parameterTok = this.expectIdentifier();
2325
+ const parameter = parameterTok.value;
2258
2326
  this.advance();
2259
2327
  const constraint = this.parseType();
2260
2328
  let nameType;
@@ -2278,6 +2346,7 @@ var Parser = class {
2278
2346
  return {
2279
2347
  type: "MappedTypeNode",
2280
2348
  parameter,
2349
+ parameterId: tokenIdentifier(parameterTok),
2281
2350
  constraint,
2282
2351
  nameType,
2283
2352
  template,
@@ -2304,30 +2373,43 @@ var Parser = class {
2304
2373
  this.expectPunctuator("{");
2305
2374
  const properties = [];
2306
2375
  while (!this.checkPunctuator("}")) {
2376
+ const propStart = this.current();
2307
2377
  if (this.checkPunctuator("[")) {
2308
2378
  this.advance();
2309
2379
  const keyType = this.parseType();
2310
2380
  this.expectPunctuator("]");
2311
2381
  this.expectPunctuator(":");
2312
2382
  const valueType = this.parseType();
2313
- properties.push({ type: "TableTypeIndexer", keyType, valueType });
2383
+ properties.push({ type: "TableTypeIndexer", keyType, valueType, ...spanFrom(propStart, this.previous()) });
2314
2384
  } else if (this.checkIdentifierValue("readonly") && this.peek(1).type === "Identifier") {
2315
2385
  this.advance();
2316
- const name = this.expectIdentifier().value;
2386
+ const keyTok = this.expectIdentifier();
2387
+ const name = keyTok.value;
2317
2388
  const optional2 = this.matchPunctuator("?");
2318
2389
  this.expectPunctuator(":");
2319
2390
  const valueType = this.parseType();
2320
- properties.push({ type: "TableTypeProperty", name, valueType, optional: optional2, readonly: true });
2391
+ properties.push({
2392
+ type: "TableTypeProperty",
2393
+ name,
2394
+ key: tokenIdentifier(keyTok),
2395
+ valueType,
2396
+ optional: optional2,
2397
+ readonly: true,
2398
+ ...spanFrom(propStart, this.previous())
2399
+ });
2321
2400
  } else if (this.checkType("Identifier") && (this.peek(1).type === "Punctuator" && this.peek(1).value === ":" || this.peek(1).type === "Punctuator" && this.peek(1).value === "?" && this.peek(2).type === "Punctuator" && this.peek(2).value === ":")) {
2322
- const name = this.expectIdentifier().value;
2401
+ const keyTok = this.expectIdentifier();
2402
+ const name = keyTok.value;
2323
2403
  const optional2 = this.matchPunctuator("?");
2324
2404
  this.expectPunctuator(":");
2325
2405
  const valueType = this.parseType();
2326
2406
  properties.push({
2327
2407
  type: "TableTypeProperty",
2328
2408
  name,
2409
+ key: tokenIdentifier(keyTok),
2329
2410
  valueType,
2330
- optional: optional2
2411
+ optional: optional2,
2412
+ ...spanFrom(propStart, this.previous())
2331
2413
  });
2332
2414
  } else {
2333
2415
  this.error("Expected object type property ('name: T' or '[K]: V'); use 'T[]' for arrays and '[T, U]' for tuples");
@@ -2389,6 +2471,7 @@ var Parser = class {
2389
2471
  list.push({
2390
2472
  type: "GenericTypeParameter",
2391
2473
  name: nameTok.value,
2474
+ id: tokenIdentifier(nameTok),
2392
2475
  isPack,
2393
2476
  isConst: isConst || void 0,
2394
2477
  constraint,
@@ -2518,6 +2601,15 @@ var Analyzer = class {
2518
2601
  }
2519
2602
  return this.getOrCreateGlobalBinding(name);
2520
2603
  }
2604
+ /** Like `resolve`, but never creates a global: `undefined` when no
2605
+ * enclosing scope declares the name. */
2606
+ lookup(scope, name) {
2607
+ for (let s = scope; s; s = s.parent) {
2608
+ const id = s.declarations.get(name);
2609
+ if (id !== void 0) return id;
2610
+ }
2611
+ return void 0;
2612
+ }
2521
2613
  getOrCreateGlobalBinding(name) {
2522
2614
  const existing = this.globalScope.declarations.get(name);
2523
2615
  if (existing !== void 0) return existing;
@@ -2633,12 +2725,14 @@ var Analyzer = class {
2633
2725
  switch (stmt.type) {
2634
2726
  case "VariableDeclaration": {
2635
2727
  for (const init of stmt.init) this.visitExpression(init, scope);
2728
+ for (const name of stmt.names) this.visitType(name.typeAnnotation, scope);
2636
2729
  const isConst = stmt.kind === "const";
2637
2730
  for (const name of stmt.names) this.declarePattern(scope, name, "local", scope, isConst);
2638
2731
  return;
2639
2732
  }
2640
2733
  case "FunctionDeclaration": {
2641
2734
  this.declare(scope, stmt.name.name, "local", stmt.name, stmt.kind === "const");
2735
+ for (const signature of stmt.signatures ?? []) this.visitSignature(signature, scope);
2642
2736
  this.visitFunctionBody(stmt.func, scope);
2643
2737
  return;
2644
2738
  }
@@ -2648,6 +2742,7 @@ var Analyzer = class {
2648
2742
  } else {
2649
2743
  this.reference(scope, stmt.target.base);
2650
2744
  }
2745
+ for (const signature of stmt.signatures ?? []) this.visitSignature(signature, scope);
2651
2746
  this.visitFunctionBody(stmt.func, scope, stmt.isMethod);
2652
2747
  return;
2653
2748
  }
@@ -2721,10 +2816,13 @@ var Analyzer = class {
2721
2816
  case "BreakStatement":
2722
2817
  case "ContinueStatement":
2723
2818
  case "ErrorStatement":
2819
+ return;
2724
2820
  case "DeclareStatement":
2821
+ this.visitType(stmt.valueType, scope);
2725
2822
  return;
2726
2823
  case "TypeAliasStatement":
2727
2824
  case "ExportTypeAliasStatement":
2825
+ this.visitType(stmt.definition, scope);
2728
2826
  return;
2729
2827
  case "ImportStatement": {
2730
2828
  if (stmt.defaultImport) {
@@ -2741,6 +2839,18 @@ var Analyzer = class {
2741
2839
  case "ExportDefaultStatement":
2742
2840
  this.visitExpression(stmt.declaration, scope);
2743
2841
  return;
2842
+ case "ExportNamedStatement":
2843
+ if (!stmt.source) {
2844
+ for (const specifier of stmt.specifiers) {
2845
+ const id = this.lookup(scope, specifier.local.name);
2846
+ if (id === void 0) continue;
2847
+ this.bindingOf.set(specifier.local, id);
2848
+ this.bindings.get(id).references.push(specifier.local);
2849
+ }
2850
+ }
2851
+ return;
2852
+ case "ExportAllStatement":
2853
+ return;
2744
2854
  }
2745
2855
  }
2746
2856
  // ---------------- functions ----------------
@@ -2748,6 +2858,7 @@ var Analyzer = class {
2748
2858
  const fnScope = childScope(outerScope);
2749
2859
  func.params.forEach((param, i) => {
2750
2860
  const kind = isMethod && i === 0 ? "self" : "param";
2861
+ this.visitType(param.typeAnnotation, fnScope);
2751
2862
  if (param.default) this.visitExpression(param.default, fnScope);
2752
2863
  if (param.pattern) {
2753
2864
  this.declarePattern(fnScope, param.pattern, kind, fnScope);
@@ -2755,8 +2866,37 @@ var Analyzer = class {
2755
2866
  this.declare(fnScope, param.name, kind, param);
2756
2867
  }
2757
2868
  });
2869
+ this.visitType(func.varargTypeAnnotation, fnScope);
2870
+ this.visitType(func.returnType, fnScope);
2758
2871
  this.visitBlock(func.body, fnScope);
2759
2872
  }
2873
+ /** An overload signature: no body and no bindings, but its types can hold
2874
+ * a `typeof x`. */
2875
+ visitSignature(signature, scope) {
2876
+ for (const param of signature.params) this.visitType(param.typeAnnotation, scope);
2877
+ this.visitType(signature.returnType, scope);
2878
+ }
2879
+ /** Resolve the value references inside a type. Only `typeof x` has any —
2880
+ * everything else in a type names types, which live in their own
2881
+ * namespace and are not this pass's business. */
2882
+ visitType(node, scope) {
2883
+ if (!node) return;
2884
+ const walk = (value) => {
2885
+ if (!value || typeof value !== "object") return;
2886
+ if (Array.isArray(value)) {
2887
+ for (const item of value) walk(item);
2888
+ return;
2889
+ }
2890
+ if (value.type === "TypeofTypeNode") {
2891
+ this.visitExpression(value.expression, scope);
2892
+ return;
2893
+ }
2894
+ for (const key of Object.keys(value)) {
2895
+ if (key !== "line" && key !== "column") walk(value[key]);
2896
+ }
2897
+ };
2898
+ walk(node);
2899
+ }
2760
2900
  // ---------------- expressions ----------------
2761
2901
  visitExpression(expr, scope) {
2762
2902
  switch (expr.type) {
@@ -2815,6 +2955,11 @@ var Analyzer = class {
2815
2955
  return;
2816
2956
  case "TypeAssertionExpression":
2817
2957
  this.visitExpression(expr.expression, scope);
2958
+ this.visitType(expr.typeAnnotation, scope);
2959
+ return;
2960
+ case "SatisfiesExpression":
2961
+ this.visitExpression(expr.expression, scope);
2962
+ this.visitType(expr.typeAnnotation, scope);
2818
2963
  return;
2819
2964
  case "IfElseExpression":
2820
2965
  for (const clause of expr.clauses) {
@@ -3485,6 +3630,94 @@ function formatKey(k) {
3485
3630
  function analyzeTypes(program, scopes, options = {}) {
3486
3631
  return new TypeAnalyzer(program, scopes, options).run();
3487
3632
  }
3633
+ function moduleExports(program, scopes, types, resolveModule) {
3634
+ const byDeclaration = /* @__PURE__ */ new Map();
3635
+ for (const binding of scopes.bindings.values()) {
3636
+ if (binding.declarationNode) byDeclaration.set(binding.declarationNode, binding.id);
3637
+ }
3638
+ const values = /* @__PURE__ */ new Map();
3639
+ const exportedTypes = /* @__PURE__ */ new Map();
3640
+ let defaultType;
3641
+ const stars = [];
3642
+ const setValue = (name, type) => {
3643
+ if (name === "default") defaultType = type;
3644
+ else values.set(name, type);
3645
+ };
3646
+ const reexport = (from, name, as) => {
3647
+ if (!from || from.partial) {
3648
+ setValue(as, anyType);
3649
+ return;
3650
+ }
3651
+ if (name === "default") {
3652
+ if (from.default) setValue(as, from.default);
3653
+ return;
3654
+ }
3655
+ const value = from.values.get(name);
3656
+ if (value) setValue(as, value);
3657
+ const type = from.types.get(name);
3658
+ if (type) exportedTypes.set(as, type);
3659
+ };
3660
+ const aliasParams = (name) => {
3661
+ for (const s of program.body.statements) {
3662
+ const alias = s.type === "TypeAliasStatement" ? s : s.type === "ExportTypeAliasStatement" ? s.alias : void 0;
3663
+ if (alias?.name.name === name) return alias.generics.map((g) => g.name);
3664
+ }
3665
+ return [];
3666
+ };
3667
+ const exportName = (declaration, name) => {
3668
+ const id = byDeclaration.get(declaration);
3669
+ values.set(name, (id !== void 0 ? types.bindingType.get(id) : void 0) ?? anyType);
3670
+ };
3671
+ const exportPattern = (target) => {
3672
+ switch (target.type) {
3673
+ case "IdentifierPattern":
3674
+ exportName(target, target.name);
3675
+ return;
3676
+ case "ObjectPattern":
3677
+ for (const p of target.properties) exportPattern(p.value);
3678
+ if (target.rest) exportPattern(target.rest);
3679
+ return;
3680
+ case "ArrayPattern":
3681
+ for (const el of target.elements) if (el) exportPattern(el.value);
3682
+ if (target.rest) exportPattern(target.rest);
3683
+ return;
3684
+ }
3685
+ };
3686
+ for (const stmt of program.body.statements) {
3687
+ if (stmt.type === "ExportStatement") {
3688
+ const declaration = stmt.declaration;
3689
+ if (declaration.type === "FunctionDeclaration") exportName(declaration.name, declaration.name.name);
3690
+ else for (const target of declaration.names) exportPattern(target);
3691
+ } else if (stmt.type === "ExportTypeAliasStatement") {
3692
+ const name = stmt.alias.name.name;
3693
+ const type = types.aliases.get(name);
3694
+ if (type) exportedTypes.set(name, { type, params: stmt.alias.generics.map((g) => g.name) });
3695
+ } else if (stmt.type === "ExportDefaultStatement") {
3696
+ defaultType = types.typeOf.get(stmt.declaration) ?? anyType;
3697
+ } else if (stmt.type === "ExportNamedStatement") {
3698
+ if (stmt.source) {
3699
+ const from = resolveModule?.(stmt.source.value);
3700
+ for (const s of stmt.specifiers) reexport(from, s.local.name, s.exported.name);
3701
+ } else {
3702
+ for (const s of stmt.specifiers) {
3703
+ const id = scopes.bindingOf.get(s.local);
3704
+ if (id !== void 0) setValue(s.exported.name, types.bindingType.get(id) ?? anyType);
3705
+ const alias = types.aliases.get(s.local.name);
3706
+ if (alias) exportedTypes.set(s.exported.name, { type: alias, params: aliasParams(s.local.name) });
3707
+ }
3708
+ }
3709
+ } else if (stmt.type === "ExportAllStatement") {
3710
+ stars.push(stmt.source.value);
3711
+ }
3712
+ }
3713
+ for (const specifier of stars) {
3714
+ const from = resolveModule?.(specifier);
3715
+ if (!from || from.partial) continue;
3716
+ for (const [name, type] of from.values) if (!values.has(name)) values.set(name, type);
3717
+ for (const [name, type] of from.types) if (!exportedTypes.has(name)) exportedTypes.set(name, type);
3718
+ }
3719
+ return { values, types: exportedTypes, default: defaultType };
3720
+ }
3488
3721
  function bindKey(id) {
3489
3722
  return `$${id}`;
3490
3723
  }
@@ -3598,6 +3831,8 @@ var TypeAnalyzer = class {
3598
3831
  typeOf = /* @__PURE__ */ new Map();
3599
3832
  bindingType = /* @__PURE__ */ new Map();
3600
3833
  narrowedTypeOf = /* @__PURE__ */ new Map();
3834
+ typeOfTypeNode = /* @__PURE__ */ new Map();
3835
+ expectedTypeOf = /* @__PURE__ */ new Map();
3601
3836
  /** Public: each alias resolved once (generic aliases keep their params as
3602
3837
  * `typeParam` nodes in the body). */
3603
3838
  aliases = /* @__PURE__ */ new Map();
@@ -3632,6 +3867,10 @@ var TypeAnalyzer = class {
3632
3867
  * (`type Tree = { children: Tree[] }`); it resolves to a nominal ref. */
3633
3868
  resolvingAliases = /* @__PURE__ */ new Set();
3634
3869
  diagnostics = [];
3870
+ /** `import`ed type names, from `resolveModule`. */
3871
+ importedTypes = /* @__PURE__ */ new Map();
3872
+ /** `resolveModule` results, one lookup per module path. */
3873
+ resolvedModules = /* @__PURE__ */ new Map();
3635
3874
  emitDiagnostics;
3636
3875
  /** Recursion guard for `preVisitBody`. */
3637
3876
  preVisitDepth = 0;
@@ -3640,6 +3879,7 @@ var TypeAnalyzer = class {
3640
3879
  this.registerAliasDefs(this.program.body);
3641
3880
  for (const lib of this.options.libs ?? []) this.harvestDeclares(lib.body);
3642
3881
  this.harvestDeclares(this.program.body);
3882
+ this.registerImportedTypes();
3643
3883
  this.resolveAllAliases();
3644
3884
  this.indexDeclarations();
3645
3885
  for (const [name, id] of this.scopes.globalsByName) {
@@ -3657,13 +3897,52 @@ var TypeAnalyzer = class {
3657
3897
  typeOf: this.typeOf,
3658
3898
  bindingType: this.bindingType,
3659
3899
  narrowedTypeOf: this.narrowedTypeOf,
3660
- aliases: this.aliases,
3900
+ typeOfTypeNode: this.typeOfTypeNode,
3901
+ expectedTypeOf: this.expectedTypeOf,
3902
+ aliases: this.resolveDeferredAliases(),
3661
3903
  diagnostics: this.diagnostics
3662
3904
  };
3663
3905
  }
3664
3906
  // --------------------------------------------------------
3665
3907
  // Aliases
3666
3908
  // --------------------------------------------------------
3909
+ moduleFor(specifier) {
3910
+ if (!this.resolvedModules.has(specifier)) {
3911
+ this.resolvedModules.set(specifier, this.options.resolveModule?.(specifier));
3912
+ }
3913
+ return this.resolvedModules.get(specifier);
3914
+ }
3915
+ /** `export ... from "./x"`: the module must exist, and so must each name. */
3916
+ checkReexport(source, names) {
3917
+ if (!this.options.resolveModule || !this.emitDiagnostics) return;
3918
+ const exports2 = this.moduleFor(source.value);
3919
+ if (!exports2) {
3920
+ this.diagnostics.push({ node: source, message: `Cannot find module '${source.value}'` });
3921
+ return;
3922
+ }
3923
+ if (exports2.partial) return;
3924
+ for (const name of names) {
3925
+ const found = name.name === "default" ? exports2.default !== void 0 : exports2.values.has(name.name) || exports2.types.has(name.name);
3926
+ if (!found) {
3927
+ this.diagnostics.push({ node: name, message: `Module '${source.value}' has no exported member '${name.name}'` });
3928
+ }
3929
+ }
3930
+ }
3931
+ registerImportedTypes() {
3932
+ if (!this.options.resolveModule) return;
3933
+ for (const stmt of this.program.body.statements) {
3934
+ if (stmt.type !== "ImportStatement") continue;
3935
+ const exports2 = this.moduleFor(stmt.source.value);
3936
+ if (!exports2) continue;
3937
+ for (const s of stmt.specifiers) {
3938
+ const exported = exports2.types.get(s.imported.name);
3939
+ if (exported) {
3940
+ this.importedTypes.set(s.local.name, exported);
3941
+ this.aliases.set(s.local.name, exported.type);
3942
+ }
3943
+ }
3944
+ }
3945
+ }
3667
3946
  registerAliasDefs(block) {
3668
3947
  for (const stmt of block.statements) {
3669
3948
  const alias = stmt.type === "TypeAliasStatement" ? stmt : stmt.type === "ExportTypeAliasStatement" ? stmt.alias : void 0;
@@ -3683,11 +3962,23 @@ var TypeAnalyzer = class {
3683
3962
  }
3684
3963
  resolveAllAliases() {
3685
3964
  for (const [name, def] of this.aliasDefs) {
3965
+ if (containsTypeQuery(def.node)) continue;
3686
3966
  this.withTypeParams(def.params, () => {
3687
3967
  this.aliases.set(name, this.resolveType(def.node));
3688
3968
  });
3689
3969
  }
3690
3970
  }
3971
+ /** The aliases `resolveAllAliases` left for later, now that every binding
3972
+ * has its type. */
3973
+ resolveDeferredAliases() {
3974
+ for (const [name, def] of this.aliasDefs) {
3975
+ if (this.aliases.has(name)) continue;
3976
+ this.withTypeParams(def.params, () => {
3977
+ this.aliases.set(name, this.resolveType(def.node));
3978
+ });
3979
+ }
3980
+ return this.aliases;
3981
+ }
3691
3982
  withTypeParams(params, fn2) {
3692
3983
  const start = this.typeParamScope.length;
3693
3984
  for (const p of params) this.typeParamScope.push({ name: p.name, isConst: p.isConst });
@@ -3727,6 +4018,11 @@ var TypeAnalyzer = class {
3727
4018
  // TypeNode -> Type
3728
4019
  // --------------------------------------------------------
3729
4020
  resolveType(node) {
4021
+ const type = this.resolveTypeNode(node);
4022
+ if (this.instantiationDepth === 0) this.typeOfTypeNode.set(node, type);
4023
+ return type;
4024
+ }
4025
+ resolveTypeNode(node) {
3730
4026
  switch (node.type) {
3731
4027
  case "TypeReference": {
3732
4028
  const name = node.namespace ? `${node.namespace}.${node.base}` : node.base;
@@ -3767,6 +4063,16 @@ var TypeAnalyzer = class {
3767
4063
  typeArguments: node.typeArguments.map((a) => this.resolveType(a))
3768
4064
  });
3769
4065
  }
4066
+ const imported = this.importedTypes.get(node.base);
4067
+ if (imported) {
4068
+ if (!imported.params.length) return imported.type;
4069
+ const subst = /* @__PURE__ */ new Map();
4070
+ imported.params.forEach((name2, i) => {
4071
+ const arg = node.typeArguments[i];
4072
+ subst.set(name2, arg ? this.resolveType(arg) : unknownType);
4073
+ });
4074
+ return this.reduceType(substitute(imported.type, subst));
4075
+ }
3770
4076
  const lib = this.options.libTypes?.[node.base];
3771
4077
  if (lib) return lib;
3772
4078
  }
@@ -4319,11 +4625,49 @@ var TypeAnalyzer = class {
4319
4625
  case "ExportDefaultStatement":
4320
4626
  this.infer(stmt.declaration, env);
4321
4627
  return;
4628
+ case "ExportNamedStatement": {
4629
+ if (stmt.source) {
4630
+ this.checkReexport(stmt.source, stmt.specifiers.map((s) => s.local));
4631
+ return;
4632
+ }
4633
+ for (const s of stmt.specifiers) {
4634
+ if (this.bindingIdOf(s.local) !== void 0) {
4635
+ this.infer(s.local, env);
4636
+ } else if (!this.aliasDefs.has(s.local.name) && !this.importedTypes.has(s.local.name)) {
4637
+ if (this.emitDiagnostics) {
4638
+ this.diagnostics.push({ node: s.local, message: `Cannot find name '${s.local.name}' to export` });
4639
+ }
4640
+ }
4641
+ }
4642
+ return;
4643
+ }
4644
+ case "ExportAllStatement":
4645
+ this.checkReexport(stmt.source, []);
4646
+ return;
4322
4647
  case "ImportStatement": {
4323
- const ids = [];
4324
- if (stmt.defaultImport) ids.push(this.bindingIdOf(stmt.defaultImport));
4325
- for (const s of stmt.specifiers) ids.push(this.bindingIdOf(s.local));
4326
- for (const id of ids) if (id !== void 0) this.bindingType.set(id, anyType);
4648
+ const resolving = this.options.resolveModule !== void 0;
4649
+ const exports2 = resolving ? this.moduleFor(stmt.source.value) : void 0;
4650
+ const specifier = stmt.source.value;
4651
+ const report = (node, message) => {
4652
+ if (this.emitDiagnostics) this.diagnostics.push({ node, message });
4653
+ };
4654
+ if (resolving && !exports2) report(stmt.source, `Cannot find module '${specifier}'`);
4655
+ const usable = exports2 && !exports2.partial ? exports2 : void 0;
4656
+ if (stmt.defaultImport) {
4657
+ if (usable && usable.default === void 0) {
4658
+ report(stmt.defaultImport, `Module '${specifier}' has no default export`);
4659
+ }
4660
+ const id = this.bindingIdByName(stmt.defaultImport.name, stmt.defaultImport);
4661
+ if (id !== void 0) this.bindingType.set(id, usable?.default ?? anyType);
4662
+ }
4663
+ for (const s of stmt.specifiers) {
4664
+ const value = usable?.values.get(s.imported.name);
4665
+ if (usable && !value && !usable.types.has(s.imported.name)) {
4666
+ report(s.imported, `Module '${specifier}' has no exported member '${s.imported.name}'`);
4667
+ }
4668
+ const id = this.bindingIdByName(s.local.name, s.local);
4669
+ if (id !== void 0) this.bindingType.set(id, value ?? anyType);
4670
+ }
4327
4671
  return;
4328
4672
  }
4329
4673
  case "BreakStatement":
@@ -4536,16 +4880,76 @@ var TypeAnalyzer = class {
4536
4880
  return void 0;
4537
4881
  }
4538
4882
  /** Can this signature be called with these argument types? The signature's
4539
- * own generic parameters act as wildcards they are what the call would
4540
- * infer, so they must not make the match fail. */
4883
+ * own type parameters stand for what the call would infer, so each is
4884
+ * checked only against its constraint `<K extends keyof Services>`
4885
+ * accepts `"Players"` but not `""`. */
4541
4886
  overloadAccepts(f, argTypes) {
4542
4887
  if (!f.varargs && argTypes.length > f.params.length) return false;
4543
- const wildcards = new Map((f.typeParams ?? []).map((n) => [n, anyType]));
4888
+ const params = this.boundParams(f);
4544
4889
  return f.params.every((p, i) => {
4545
4890
  if (argTypes[i] === void 0) return p.optional === true;
4546
- return isAssignable(argTypes[i], substitute(p.type, wildcards));
4891
+ return isAssignable(argTypes[i], params[i]);
4892
+ });
4893
+ }
4894
+ /** A signature's parameter types as a call site sees them before inference:
4895
+ * each type parameter replaced by its constraint, or by `any` when it has
4896
+ * none — or when the constraint mentions another type parameter, which a
4897
+ * lone argument cannot be checked against without false errors. */
4898
+ boundParams(f) {
4899
+ if (!f.typeParams?.length) return f.params.map((p) => p.type);
4900
+ const bounds = new Map(f.typeParams.map((name) => [name, anyType]));
4901
+ const seen = /* @__PURE__ */ new WeakSet();
4902
+ const walk = (value) => {
4903
+ if (!value || typeof value !== "object" || seen.has(value)) return;
4904
+ seen.add(value);
4905
+ if (value instanceof Map) {
4906
+ value.forEach(walk);
4907
+ return;
4908
+ }
4909
+ const t = value;
4910
+ if (t.kind === "typeParam" && typeof t.name === "string" && bounds.has(t.name) && t.constraint && !containsTypeParam(t.constraint)) {
4911
+ bounds.set(t.name, this.reduceType(t.constraint));
4912
+ }
4913
+ for (const child of Object.values(value)) walk(child);
4914
+ };
4915
+ for (const p of f.params) walk(p.type);
4916
+ return f.params.map((p) => substitute(p.type, bounds));
4917
+ }
4918
+ /** Record what each written argument is expected to be — see
4919
+ * `TypeAnalysis.expectedTypeOf`. */
4920
+ recordExpected(written, fns, selfOf) {
4921
+ written.forEach((arg, j) => {
4922
+ const candidates = [];
4923
+ for (const f of fns) {
4924
+ const i = j + selfOf(f);
4925
+ const param = i < f.params.length ? this.boundParams(f)[i] : f.varargs;
4926
+ if (param) candidates.push(param);
4927
+ }
4928
+ if (candidates.length) this.expectedTypeOf.set(arg, union(candidates));
4547
4929
  });
4548
4930
  }
4931
+ /** No signature accepts the call, and the argument count is not the
4932
+ * problem: say which argument is wrong, the way TypeScript does. */
4933
+ reportArguments(call, written, fns, argsFor, selfOf) {
4934
+ if (!this.emitDiagnostics) return;
4935
+ if (fns.length > 1) {
4936
+ this.diagnostics.push({ node: call, message: "No overload matches this call" });
4937
+ return;
4938
+ }
4939
+ const f = fns[0];
4940
+ const args = argsFor(f);
4941
+ const params = this.boundParams(f);
4942
+ const self = selfOf(f);
4943
+ for (let i = 0; i < f.params.length; i++) {
4944
+ const arg = args[i];
4945
+ if (arg === void 0 || isAssignable(arg, params[i])) continue;
4946
+ this.diagnostics.push({
4947
+ node: written[i - self] ?? call,
4948
+ message: `Argument of type '${formatType(arg)}' is not assignable to parameter of type '${briefType(params[i])}'`
4949
+ });
4950
+ return;
4951
+ }
4952
+ }
4549
4953
  /** A required parameter may not follow an optional one — otherwise the
4550
4954
  * optional one could never actually be omitted. Same rule as TypeScript,
4551
4955
  * and it applies to a default (`a = 1`) as much as to a `?`. */
@@ -4575,22 +4979,25 @@ var TypeAnalyzer = class {
4575
4979
  return { min, max: f.varargs ? void 0 : f.params.length };
4576
4980
  }
4577
4981
  /** Report a call that passes too few or too many arguments. Only fires
4578
- * when *no* overload accepts the call, so an overload set still reports
4579
- * once, against its first signature. */
4982
+ * when *no* overload accepts the count, so an overload set still reports
4983
+ * once, against its first signature. Returns whether the count fits, so
4984
+ * an argument's type is only complained about when its count is right. */
4580
4985
  checkArity(node, fns, argCount, selfArgs) {
4581
- if (!this.emitDiagnostics || !fns.length) return;
4986
+ if (!fns.length) return true;
4582
4987
  const fits = fns.some((f) => {
4583
4988
  const { min: min2, max: max2 } = this.arityOf(f);
4584
4989
  const n = argCount + selfArgs;
4585
4990
  return n >= min2 && (max2 === void 0 || n <= max2);
4586
4991
  });
4587
- if (fits) return;
4992
+ if (fits) return true;
4993
+ if (!this.emitDiagnostics) return false;
4588
4994
  const { min, max } = this.arityOf(fns[0]);
4589
4995
  const need = max === void 0 ? `at least ${min - selfArgs}` : min === max ? `${min - selfArgs}` : `${min - selfArgs}-${max - selfArgs}`;
4590
4996
  this.diagnostics.push({
4591
4997
  node,
4592
4998
  message: `Expected ${need} argument${need === "1" ? "" : "s"}, got ${argCount}`
4593
4999
  });
5000
+ return false;
4594
5001
  }
4595
5002
  signatureToFnType(sig) {
4596
5003
  const names = sig.generics.map((g) => g.name);
@@ -4626,11 +5033,18 @@ var TypeAnalyzer = class {
4626
5033
  inferFunctionBody(func, env) {
4627
5034
  const names = func.generics.map((g) => g.name);
4628
5035
  return this.withTypeParams(func.generics, () => {
4629
- const params = func.params.map((p) => ({
4630
- name: p.pattern ? void 0 : p.name,
4631
- type: this.paramType(p, env),
4632
- optional: p.optional || p.default !== void 0
4633
- }));
5036
+ const params = func.params.map((p) => {
5037
+ const type = this.paramType(p, env);
5038
+ if (!p.pattern) {
5039
+ const id = this.bindingIdByName(p.name, p);
5040
+ if (id !== void 0 && !this.bindingType.has(id)) this.bindingType.set(id, type);
5041
+ }
5042
+ return {
5043
+ name: p.pattern ? void 0 : p.name,
5044
+ type,
5045
+ optional: p.optional || p.default !== void 0
5046
+ };
5047
+ });
4634
5048
  const bodyEnv = forkEnv(env);
4635
5049
  for (const p of func.params) {
4636
5050
  if (p.pattern) this.bindPattern(p.pattern, this.paramType(p, bodyEnv), bodyEnv, "widen");
@@ -5021,11 +5435,13 @@ var TypeAnalyzer = class {
5021
5435
  const argTypes = expr.arguments.map((a) => this.infer(a, env));
5022
5436
  const fns = this.overloadsOf(callee);
5023
5437
  if (fns.length) {
5024
- this.checkArity(expr, fns, argTypes.length, 0);
5438
+ this.recordExpected(expr.arguments, fns, () => 0);
5439
+ const arityFits = this.checkArity(expr, fns, argTypes.length, 0);
5025
5440
  const picked = this.pickOverload(fns, argTypes);
5026
5441
  if (picked) {
5027
5442
  return this.callReturn(picked, this.constArgs(picked, expr.arguments, argTypes, env));
5028
5443
  }
5444
+ if (arityFits) this.reportArguments(expr, expr.arguments, fns, () => argTypes, () => 0);
5029
5445
  return union(fns.map((f) => this.callReturn(f, argTypes)));
5030
5446
  }
5031
5447
  return callee.kind === "any" ? anyType : unknownType;
@@ -5036,13 +5452,16 @@ var TypeAnalyzer = class {
5036
5452
  const fns = this.overloadsOf(this.propertyType(objType, expr.method.name));
5037
5453
  if (fns.length) {
5038
5454
  const withSelf = (f) => this.takesSelf(f) ? [objType, ...argTypes] : argTypes;
5039
- this.checkArity(expr, fns, argTypes.length, this.takesSelf(fns[0]) ? 1 : 0);
5455
+ const selfOf = (f) => this.takesSelf(f) ? 1 : 0;
5456
+ this.recordExpected(expr.arguments, fns, selfOf);
5457
+ const arityFits = this.checkArity(expr, fns, argTypes.length, this.takesSelf(fns[0]) ? 1 : 0);
5040
5458
  const picked = this.pickOverload(fns, argTypes, withSelf);
5041
5459
  if (picked) {
5042
5460
  const self = this.takesSelf(picked) ? 1 : 0;
5043
5461
  const written = this.constArgs(picked, expr.arguments, argTypes, env, self);
5044
5462
  return this.callReturn(picked, this.takesSelf(picked) ? [objType, ...written] : written);
5045
5463
  }
5464
+ if (arityFits) this.reportArguments(expr, expr.arguments, fns, withSelf, selfOf);
5046
5465
  return union(fns.map((f) => this.callReturn(f, withSelf(f))));
5047
5466
  }
5048
5467
  return objType.kind === "any" ? anyType : unknownType;
@@ -5484,6 +5903,19 @@ var TypeAnalyzer = class {
5484
5903
  return this.bindingByDecl.get(node) ?? this.bindingByPos.get(posKey(name, node.line.start, node.column.start));
5485
5904
  }
5486
5905
  };
5906
+ function containsTypeQuery(node) {
5907
+ if (!node || typeof node !== "object") return false;
5908
+ if (Array.isArray(node)) return node.some(containsTypeQuery);
5909
+ if (node.type === "TypeofTypeNode") return true;
5910
+ return Object.values(node).some(containsTypeQuery);
5911
+ }
5912
+ function briefType(t) {
5913
+ if (t.kind === "union" && t.types.length > 8) {
5914
+ const shown = t.types.slice(0, 6).map(formatType).join(" | ");
5915
+ return `${shown} | ... ${t.types.length - 6} more`;
5916
+ }
5917
+ return formatType(t);
5918
+ }
5487
5919
 
5488
5920
  // src/lib/luau.ts
5489
5921
  var import_node_fs = require("fs");
@@ -5549,6 +5981,7 @@ var index_default = luautparser;
5549
5981
  luauLib,
5550
5982
  luautparser,
5551
5983
  matchInfer,
5984
+ moduleExports,
5552
5985
  narrowExclude,
5553
5986
  narrowFalsy,
5554
5987
  narrowTo,