@tilua/compiler 1.1.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -65,10 +65,14 @@ JavaScript module the build loads.
65
65
  | `function f(n = 1, { x })` | `function f(n, arg) if n == nil then n = 1 end local x = arg.x ...` |
66
66
  | `const` / `let` | `local` |
67
67
  | `class C { … }` | one table for the class, one per instance — see below |
68
- | `new C(x)` | `C.new(x)` |
69
- | `[...]` | `{...}` — the varargs as an array |
68
+ | `return [a, b]` | `return { a, b }` — several results are an array |
69
+ | `for (const item in xs)` | `for _, item in xs do … end` — an array or a table walked for its values |
70
+ | `for (const item in step)` | `for item in step do … end` — an iterator function |
71
+ | `for (const [k, v] in pairs(t))` | `for item in unpack(pairs(t), 1, 3) do local k, v = item[1], item[2] … end` — an iteration is `[step, state, first]` |
72
+ | `pcall(f)`, and other globals Luau answers several values from | the type library's own function, answering one — see below |
70
73
  | `function f(a, ...rest)` | `function f(a, ...) local rest = {...}` |
71
- | `f(a, ...xs)` / `return ...xs` | `table.unpack(xs)` in the last position |
74
+ | `scriptArgs` | the chunk's own `...`, as `local scriptArgs = { ... }` |
75
+ | `f(a, ...xs)` | `table.unpack(xs)` in the last position |
72
76
  | `f(...xs, a)` | the list built first, then `table.unpack` of it |
73
77
  | `x as T`, `x satisfies T`, types, `declare` | removed |
74
78
  | `names:filter(f)` | whatever the type library that declared `filter` says — see below |
@@ -113,8 +117,16 @@ tilua_accessors(Dog)
113
117
 
114
118
  `__init` is what `super(...)` calls: it runs the constructor on an instance
115
119
  that already exists, so a derived class builds one table, not one per level.
116
- `new` is a real function on the class table — `new Dog(x)` and `Dog.new(x)`
117
- are the same call.
120
+ `new` is a real function on the class table, and `Dog.new(x)` is how an
121
+ instance is built — the way Luau builds one. An `abstract class` has no `new`,
122
+ and an `abstract` method no function: both exist only to be checked.
123
+
124
+ A method named for a metamethod — `__add`, `__tostring`, `__call`, … — is an
125
+ ordinary function on the class table, and since the class table is its
126
+ instances' metatable, it works as one. Luau reads metamethods off the
127
+ metatable with `rawget`, never through its `__index`, so a class would not
128
+ inherit its base's: `tilua_class` copies them into each class that extends
129
+ another, and one the class writes itself replaces the copy.
118
130
 
119
131
  Two links come with every class, and they cost an instance nothing because
120
132
  they live on the class table: `ClassObject`, which an instance reads through
@@ -180,14 +180,18 @@ var Lowerer = class {
180
180
  const body = this.options.module ? this.module(this.options.module) : this.topLevel(this.source.body.statements);
181
181
  const helpers = this.helperDefinitions();
182
182
  const captured = [...this.builtins].map(([global, local2]) => local([local2], [identifier(global)]));
183
+ const scriptArgs = this.usesScriptArgs && !this.options.module ? [local(["scriptArgs"], [table([{ type: "TableFieldPositional", value: vararg() }])])] : [];
183
184
  return {
184
- statements: [...captured, ...helpers, ...this.classRuntimeStatements(), ...body],
185
+ statements: [...scriptArgs, ...captured, ...helpers, ...this.classRuntimeStatements(), ...body],
185
186
  exportsName: this.exportsName,
186
187
  module: this.info,
187
188
  diagnostics: this.diagnostics,
188
- origins: this.origins
189
+ origins: this.origins,
190
+ usesScriptArgs: this.usesScriptArgs
189
191
  };
190
192
  }
193
+ /** Whether the file reads `scriptArgs`, the language's own global. */
194
+ usesScriptArgs = false;
191
195
  report(node, message) {
192
196
  this.diagnostics.push({ message, line: node.line.start, column: node.column.start });
193
197
  }
@@ -312,6 +316,10 @@ var Lowerer = class {
312
316
  /** The name a global is read by at `node`, or `undefined` when `node` is
313
317
  * not a global — a local of the same name is the author's. */
314
318
  globalName(node) {
319
+ if (node.type === "MemberExpression" && !node.optional) {
320
+ const base = this.globalName(node.object);
321
+ return base === void 0 ? void 0 : `${base}.${node.property.name}`;
322
+ }
315
323
  if (node.type !== "Identifier") return void 0;
316
324
  const id = this.scopes.bindingOf.get(node);
317
325
  if (id === void 0) return node.name;
@@ -608,7 +616,7 @@ var Lowerer = class {
608
616
  break;
609
617
  }
610
618
  case "ReturnStatement":
611
- if (declaration.arguments.length) this.report(declaration, "A module cannot return a value; export it instead");
619
+ if (declaration.argument) this.report(declaration, "A module cannot return a value; export it instead");
612
620
  body.push(returns([]));
613
621
  break;
614
622
  case "ImportStatement":
@@ -787,7 +795,7 @@ var Lowerer = class {
787
795
  case "CallStatement": {
788
796
  const chain = optionalChain(node.expression);
789
797
  if (chain) return [this.optionalCallStatement(chain)];
790
- const expression = this.expression(node.expression);
798
+ const expression = this.callValues(node.expression);
791
799
  if (expression.type !== "CallExpression" && expression.type !== "MethodCallExpression") {
792
800
  this.report(node, "A statement must be a call");
793
801
  return [];
@@ -840,23 +848,26 @@ var Lowerer = class {
840
848
  }];
841
849
  case "GenericForStatement": {
842
850
  const prelude = [];
843
- const variables = node.variables.map((target) => {
844
- if (target.type === "IdentifierPattern") return this.name(target.name);
845
- const temp = this.names.fresh("item");
846
- prelude.push(...this.destructure(target, identifier(temp), "declare"));
847
- return temp;
848
- });
849
- if (variables.length === 1 && this.options.types?.iteratesValues.has(node)) {
850
- variables.unshift(this.names.fresh("_"));
851
+ let item;
852
+ if (node.variable.type === "IdentifierPattern") {
853
+ item = this.name(node.variable.name);
854
+ } else {
855
+ item = this.names.fresh("item");
856
+ prelude.push(...this.destructure(node.variable, identifier(item), "declare"));
851
857
  }
858
+ const form = this.options.types?.loops.get(node) ?? { walks: "values", viaIter: false };
859
+ let source = this.expression(node.iterator);
860
+ if (form.viaIter) source = methodCall(source, "__iter", []);
861
+ const variables = form.walks === "values" ? [this.names.fresh("_"), item] : [item];
862
+ const iterators = form.walks === "iteration" ? [call(this.builtin("unpack"), [source, number(1), number(3)])] : [source];
852
863
  return [genericFor(
853
864
  variables,
854
- node.iterators.map((e) => this.expression(e)),
865
+ iterators,
855
866
  this.loopBody51(this.block(node.body, prelude)).statements
856
867
  )];
857
868
  }
858
869
  case "ReturnStatement":
859
- return [returns(this.values(node.arguments))];
870
+ return [returns(node.argument ? [this.expression(node.argument)] : [])];
860
871
  case "BreakStatement":
861
872
  return [{ type: "BreakStatement", ...spanOf(node) }];
862
873
  case "ContinueStatement":
@@ -900,7 +911,7 @@ var Lowerer = class {
900
911
  // end
901
912
  // function Dog.speak(this) ... end
902
913
  //
903
- // `new Dog(x)` is `Dog.new(x)`, `dog:speak()` passes the instance
914
+ // `Dog.new(x)` builds one, `dog:speak()` passes the instance
904
915
  // as `this`, and `super.speak()` is `Animal.speak(this)` — the
905
916
  // base's own function, run on this instance.
906
917
  // ============================================================
@@ -926,6 +937,7 @@ var Lowerer = class {
926
937
  classRuntimeStatements() {
927
938
  if (!this.classHelpers) return [];
928
939
  const { build, accessors } = this.classHelpers;
940
+ const metamethods = `{ ${CLASS_METAMETHODS.map((name) => `"${name}"`).join(", ")} }`;
929
941
  return parseLuau(`
930
942
  local function ${build}(base)
931
943
  local class = { __getters = {}, __setters = {} }
@@ -936,6 +948,9 @@ local function ${build}(base)
936
948
  setmetatable(class, { __index = base })
937
949
  setmetatable(class.__getters, { __index = base.__getters })
938
950
  setmetatable(class.__setters, { __index = base.__setters })
951
+ for _, key in ipairs(${metamethods}) do
952
+ class[key] = base[key]
953
+ end
939
954
  end
940
955
  return class
941
956
  end
@@ -998,6 +1013,7 @@ end
998
1013
  }
999
1014
  break;
1000
1015
  case "ClassMethod":
1016
+ if (member2.isAbstract) break;
1001
1017
  out.push(this.functionStatement(
1002
1018
  member(self, member2.name.name),
1003
1019
  member2,
@@ -1019,7 +1035,7 @@ end
1019
1035
  }
1020
1036
  }
1021
1037
  out.push(this.classInit(node, self));
1022
- out.push(this.classNew(node, self));
1038
+ if (!node.isAbstract) out.push(this.classNew(node, self));
1023
1039
  out.push(callStatement(call(identifier(accessors), [self])));
1024
1040
  } finally {
1025
1041
  this.classContext = previous;
@@ -1297,6 +1313,10 @@ end
1297
1313
  }
1298
1314
  switch (node.type) {
1299
1315
  case "Identifier":
1316
+ if (node.name === "scriptArgs" && this.isLanguageGlobal(node)) {
1317
+ this.usesScriptArgs = true;
1318
+ return identifier("scriptArgs");
1319
+ }
1300
1320
  return this.loweredGlobalValue(node) ?? this.reference(node);
1301
1321
  case "NilLiteral":
1302
1322
  return nil();
@@ -1306,8 +1326,6 @@ end
1306
1326
  return number(node.value, this.numberRaw(node));
1307
1327
  case "StringLiteral":
1308
1328
  return string(node.value);
1309
- case "VarargExpression":
1310
- return vararg();
1311
1329
  // Only a recovering parse makes one, and a syntax error stops the build.
1312
1330
  case "ErrorExpression":
1313
1331
  this.report(node, "Syntax error");
@@ -1328,11 +1346,6 @@ end
1328
1346
  }
1329
1347
  case "UnaryExpression":
1330
1348
  return unary(node.operator, this.expression(node.argument));
1331
- case "NewExpression":
1332
- return call(
1333
- member(this.expression(node.callee), "new"),
1334
- this.values(node.arguments)
1335
- );
1336
1349
  case "SuperExpression":
1337
1350
  return this.superClassReference(node);
1338
1351
  case "SpreadElement":
@@ -1340,17 +1353,16 @@ end
1340
1353
  return nil();
1341
1354
  case "ClassExpression":
1342
1355
  return this.classExpression(node);
1343
- case "MemberExpression":
1344
- case "IndexExpression":
1345
1356
  case "CallExpression":
1346
- case "MethodCallExpression": {
1357
+ case "MethodCallExpression":
1358
+ return this.callValues(node);
1359
+ case "MemberExpression":
1360
+ case "IndexExpression": {
1347
1361
  const chain = optionalChain(node);
1348
1362
  if (chain) return this.optionalChainExpression(chain);
1349
- const object = linkObject(node);
1350
- if (node.type === "CallExpression" && object.type === "Identifier") {
1351
- return this.link(node, this.reference(object));
1352
- }
1353
- return this.link(node, this.expression(object));
1363
+ const global = node.type === "MemberExpression" ? this.loweredGlobalValue(node) : void 0;
1364
+ if (global) return global;
1365
+ return this.link(node, this.expression(linkObject(node)));
1354
1366
  }
1355
1367
  case "ParenthesizedExpression":
1356
1368
  return parenthesized(this.expression(node.expression));
@@ -1369,14 +1381,47 @@ end
1369
1381
  };
1370
1382
  }
1371
1383
  }
1384
+ /** A call. */
1385
+ callValues(node) {
1386
+ const chain = optionalChain(node);
1387
+ if (chain) return this.optionalChainExpression(chain);
1388
+ const object = linkObject(node);
1389
+ if (node.type === "CallExpression" && object.type === "Identifier") {
1390
+ return this.link(node, this.reference(object));
1391
+ }
1392
+ return this.link(node, this.expression(object));
1393
+ }
1394
+ /** A call is one value in tilua, and Luau would hand on every value one
1395
+ * in the last place of an argument list or an array returns. Types say
1396
+ * how many a call returns; where a type says nothing — `any`, `unknown`
1397
+ * — the call is kept to one: `f((g()))`. */
1398
+ keptToOne(nodes, values) {
1399
+ const node = nodes[nodes.length - 1];
1400
+ const last = values[values.length - 1];
1401
+ if (!node || !last || !expandsToMany(last)) return values;
1402
+ const type = this.options.types?.typeOf.get(node);
1403
+ if (type && type.kind !== "any" && type.kind !== "unknown") return values;
1404
+ return [...values.slice(0, -1), parenthesized(last)];
1405
+ }
1406
+ /** Is this `scriptArgs` the language's own, not a name the file declared? */
1407
+ isLanguageGlobal(node) {
1408
+ const id = this.bindingIdOf(node);
1409
+ return id === void 0 || this.scopes.bindings.get(id)?.kind === "global";
1410
+ }
1372
1411
  /** A list of values — a call's arguments, a `return`'s, a declaration's,
1373
1412
  * an assignment's. `f(a, ...xs)` is Lua's own last-value expansion,
1374
1413
  * `f(a, table.unpack(xs))`; a spread anywhere else cannot be, since only
1375
1414
  * the last value of a list expands, so the whole list is built as an
1376
- * array first and that is what expands. */
1377
- values(list) {
1415
+ * array first and that is what expands.
1416
+ *
1417
+ * `arguments` is set for a call's arguments, where Luau hands on every
1418
+ * value the last one returns — see `keptToOne`. */
1419
+ values(list, asArguments = false) {
1378
1420
  const spreads = list.filter((a) => a.type === "SpreadElement");
1379
- if (!spreads.length) return list.map((a) => this.expression(a));
1421
+ if (!spreads.length) {
1422
+ const lowered = list.map((a) => this.expression(a));
1423
+ return asArguments ? this.keptToOne(list, lowered) : lowered;
1424
+ }
1380
1425
  const unpack = (value) => call(member(this.builtin("table"), "unpack"), [value]);
1381
1426
  const last = list[list.length - 1];
1382
1427
  if (spreads.length === 1 && last.type === "SpreadElement") {
@@ -1397,13 +1442,13 @@ end
1397
1442
  return index(object, this.expression(node.index));
1398
1443
  case "CallExpression": {
1399
1444
  const lowered = this.loweredGlobalCall(node);
1400
- const args = this.values(node.arguments);
1401
- if (lowered) return call(calleePath(lowered.callee), [...lowered.prepend, ...args]);
1402
- return call(object, args);
1445
+ const args = this.values(node.arguments, true);
1446
+ if (!lowered) return call(object, args);
1447
+ return call(calleePath(lowered.callee), [...lowered.prepend, ...args]);
1403
1448
  }
1404
1449
  case "MethodCallExpression": {
1405
1450
  const lowered = this.loweredMethodCall(node);
1406
- const args = this.values(node.arguments);
1451
+ const args = this.values(node.arguments, true);
1407
1452
  if (lowered) {
1408
1453
  return call(calleePath(lowered.callee), [
1409
1454
  ...lowered.prepend,
@@ -1637,7 +1682,8 @@ end
1637
1682
  * runs of plain elements and the spread arrays are joined by `concat`. */
1638
1683
  arrayExpression(node) {
1639
1684
  if (!node.elements.some((e) => e.type === "SpreadElement")) {
1640
- return table(node.elements.map((e) => ({ type: "TableFieldPositional", value: this.expression(e) })));
1685
+ const elements = node.elements;
1686
+ return table(this.keptToOne(elements, elements.map((e) => this.expression(e))).map((value) => ({ type: "TableFieldPositional", value })));
1641
1687
  }
1642
1688
  const parts = [];
1643
1689
  let current = [];
@@ -1836,6 +1882,24 @@ function allNodes(root) {
1836
1882
  visit(root);
1837
1883
  return out;
1838
1884
  }
1885
+ var CLASS_METAMETHODS = [
1886
+ "__add",
1887
+ "__sub",
1888
+ "__mul",
1889
+ "__div",
1890
+ "__idiv",
1891
+ "__mod",
1892
+ "__pow",
1893
+ "__unm",
1894
+ "__concat",
1895
+ "__len",
1896
+ "__eq",
1897
+ "__lt",
1898
+ "__le",
1899
+ "__call",
1900
+ "__tostring",
1901
+ "__iter"
1902
+ ];
1839
1903
 
1840
1904
  // src/lowering.ts
1841
1905
  import { pathToFileURL } from "url";
@@ -1865,7 +1929,7 @@ async function loadLowerings(modules) {
1865
1929
  }
1866
1930
 
1867
1931
  // src/bundle.ts
1868
- import { readFileSync } from "fs";
1932
+ import { readFileSync, statSync } from "fs";
1869
1933
  import { dirname, relative, resolve as resolve2 } from "path";
1870
1934
  import {
1871
1935
  parse,
@@ -1993,7 +2057,8 @@ async function bundle(options) {
1993
2057
  name: source.name,
1994
2058
  statements: lowered.statements,
1995
2059
  exportsName: lowered.exportsName,
1996
- info: lowered.module
2060
+ info: lowered.module,
2061
+ usesScriptArgs: lowered.usesScriptArgs
1997
2062
  });
1998
2063
  }
1999
2064
  for (const [file, fileDirectives] of directives) {
@@ -2018,6 +2083,9 @@ async function bundle(options) {
2018
2083
  }
2019
2084
  const program2 = parseLuau2(runtime(G));
2020
2085
  const assignment = program2.body.statements[1];
2086
+ if ([...modules.values()].some((m) => m.usesScriptArgs)) {
2087
+ program2.body.statements.unshift(local(["scriptArgs"], [table([{ type: "TableFieldPositional", value: { type: "VarargExpression" } }])]));
2088
+ }
2021
2089
  const modulesTable = assignment.type === "AssignmentStatement" && assignment.values[0].type === "TableExpression" ? assignment.values[0].fields.find((f) => f.type === "TableFieldNamed" && f.name.name === "modules")?.value : void 0;
2022
2090
  if (!modulesTable) throw new Error("@tilua/compiler: the bundle runtime has no modules table");
2023
2091
  for (const module of modules.values()) {
@@ -2154,7 +2222,22 @@ ${G} = {
2154
2222
  }
2155
2223
  `;
2156
2224
  }
2225
+ function stampOf(file) {
2226
+ try {
2227
+ const at = statSync(file);
2228
+ return `${at.size}:${at.mtimeMs}`;
2229
+ } catch {
2230
+ return void 0;
2231
+ }
2232
+ }
2233
+ var parsedFiles = /* @__PURE__ */ new Map();
2157
2234
  function parseFile(file, diagnostics, directives) {
2235
+ const stamp = stampOf(file);
2236
+ const cached = stamp !== void 0 ? parsedFiles.get(file) : void 0;
2237
+ if (cached && cached.stamp === stamp) {
2238
+ directives.set(file, cached.directives);
2239
+ return { program: cached.program, text: cached.text };
2240
+ }
2158
2241
  let text;
2159
2242
  try {
2160
2243
  text = readFileSync(file, "utf8");
@@ -2164,7 +2247,9 @@ function parseFile(file, diagnostics, directives) {
2164
2247
  }
2165
2248
  try {
2166
2249
  const program2 = parse(text);
2167
- directives.set(file, directivesOf(text));
2250
+ const found = directivesOf(text);
2251
+ directives.set(file, found);
2252
+ if (stamp !== void 0) parsedFiles.set(file, { stamp, program: program2, text, directives: found });
2168
2253
  return { program: program2, text };
2169
2254
  } catch (error) {
2170
2255
  if (error instanceof ParseError || error instanceof LexError) {
@@ -2178,22 +2263,93 @@ function parseFile(file, diagnostics, directives) {
2178
2263
  function importedSpecifiers(program2) {
2179
2264
  return program2.body.statements.flatMap((s) => s.type === "ImportStatement" || s.type === "ExportAllStatement" || s.type === "ExportNamedStatement" && s.source ? [s.source.value] : []);
2180
2265
  }
2266
+ var libraryCache = /* @__PURE__ */ new Map();
2267
+ function parsedLibrary(file) {
2268
+ let stamp;
2269
+ try {
2270
+ const at = statSync(file);
2271
+ stamp = `${at.size}:${at.mtimeMs}`;
2272
+ } catch {
2273
+ stamp = "";
2274
+ }
2275
+ const cached = libraryCache.get(file);
2276
+ if (cached && cached.stamp === stamp) return cached.program;
2277
+ const program2 = parse(readFileSync(file, "utf8"));
2278
+ libraryCache.set(file, { stamp, program: program2 });
2279
+ return program2;
2280
+ }
2281
+ var moduleAnalyses = /* @__PURE__ */ new Map();
2181
2282
  function analyzeModules(modules, config) {
2182
2283
  const out = [];
2183
2284
  const analyses = /* @__PURE__ */ new Map();
2184
2285
  const libraries = config ? resolveTypeLibraries(config) : { files: [], lowerings: [], problems: [] };
2185
2286
  for (const p of libraries.problems) out.push({ file: p.file, message: p.message, line: p.line ?? 1, column: p.column ?? 1, category: "config" });
2186
- const libs = libraries.files.map((file) => parse(readFileSync(file, "utf8")));
2287
+ const libs = libraries.files.map(parsedLibrary);
2187
2288
  const globals = libs.flatMap((lib) => lib.body.statements.flatMap((s) => s.type === "DeclareStatement" ? [s.name] : []));
2188
2289
  const byFile = new Map(modules.map((m) => [m.file, m]));
2189
2290
  const exportsCache = /* @__PURE__ */ new Map();
2190
2291
  const inProgress = /* @__PURE__ */ new Set();
2292
+ const analyzed = /* @__PURE__ */ new Map();
2293
+ let partials = 0;
2294
+ const readWhile = /* @__PURE__ */ new Map();
2295
+ const settingFor = `${libraries.files.join("|")}#${config?.directory ?? ""}#${JSON.stringify(config?.paths ?? {})}`;
2296
+ const analyzeModule = (file, program2) => {
2297
+ const done = analyzed.get(file);
2298
+ if (done) return done;
2299
+ const remembered = moduleAnalyses.get(file);
2300
+ if (remembered && remembered.setting === settingFor && libs.length === remembered.libs.length && remembered.libs.every((lib, i) => lib === libs[i]) && remembered.program === program2 && [...remembered.read].every(([read, stamp]) => stampOf(read) === stamp)) {
2301
+ analyzed.set(file, remembered.result);
2302
+ readWhile.set(file, remembered.read);
2303
+ return remembered.result;
2304
+ }
2305
+ const scopes = analyzeScopes(program2, { builtinGlobals: globals, reportUndeclared: libs.length > 0 });
2306
+ const said = [];
2307
+ for (const d of scopes.diagnostics) {
2308
+ if (d.kind !== "undeclared") continue;
2309
+ said.push({ file, message: d.message, line: d.node.line.start, column: d.node.column.start, category: "type" });
2310
+ }
2311
+ const before = partials;
2312
+ const types = analyzeTypes(program2, scopes, {
2313
+ libs,
2314
+ resolveModule: resolverFor(file),
2315
+ reportUnknownTypes: libs.length > 0
2316
+ });
2317
+ for (const d of types.diagnostics) {
2318
+ const at = d.node;
2319
+ said.push({ file, message: d.message, line: at.line.start, column: at.column.start, category: "type" });
2320
+ }
2321
+ const result = { types, diagnostics: said };
2322
+ if (partials === before) {
2323
+ analyzed.set(file, result);
2324
+ const read = readWhile.get(file) ?? /* @__PURE__ */ new Map();
2325
+ const own = stampOf(file);
2326
+ if (own !== void 0) read.set(file, own);
2327
+ readWhile.set(file, read);
2328
+ if ([...read.values()].every((stamp) => stamp !== void 0)) {
2329
+ moduleAnalyses.set(file, { setting: settingFor, libs: [...libs], program: program2, read, result });
2330
+ }
2331
+ }
2332
+ return result;
2333
+ };
2191
2334
  const resolverFor = (file) => (specifier) => {
2192
2335
  const target = resolveModulePath(file, specifier, config);
2193
2336
  if (!target) return void 0;
2194
- if (inProgress.has(target)) return { values: /* @__PURE__ */ new Map(), types: /* @__PURE__ */ new Map(), partial: true };
2337
+ const noteRead = () => {
2338
+ const read = readWhile.get(file) ?? /* @__PURE__ */ new Map();
2339
+ readWhile.set(file, read);
2340
+ const stamp = stampOf(target);
2341
+ if (stamp !== void 0) read.set(target, stamp);
2342
+ for (const [below, belowStamp] of readWhile.get(target) ?? []) read.set(below, belowStamp);
2343
+ };
2344
+ if (inProgress.has(target)) {
2345
+ partials++;
2346
+ return { values: /* @__PURE__ */ new Map(), types: /* @__PURE__ */ new Map(), partial: true };
2347
+ }
2195
2348
  const cached = exportsCache.get(target);
2196
- if (cached) return cached;
2349
+ if (cached) {
2350
+ noteRead();
2351
+ return cached;
2352
+ }
2197
2353
  let program2 = byFile.get(target)?.program;
2198
2354
  if (!program2) {
2199
2355
  try {
@@ -2205,30 +2361,19 @@ function analyzeModules(modules, config) {
2205
2361
  inProgress.add(target);
2206
2362
  try {
2207
2363
  const scopes = analyzeScopes(program2, { builtinGlobals: globals });
2208
- const types = analyzeTypes(program2, scopes, { libs, resolveModule: resolverFor(target), diagnostics: false });
2364
+ const types = byFile.has(target) ? analyzeModule(target, program2).types : analyzeTypes(program2, scopes, { libs, resolveModule: resolverFor(target), diagnostics: false });
2209
2365
  const exports = moduleExports(program2, scopes, types, resolverFor(target));
2210
2366
  exportsCache.set(target, exports);
2367
+ noteRead();
2211
2368
  return exports;
2212
2369
  } finally {
2213
2370
  inProgress.delete(target);
2214
2371
  }
2215
2372
  };
2216
2373
  for (const module of modules) {
2217
- const scopes = analyzeScopes(module.program, { builtinGlobals: globals, reportUndeclared: libs.length > 0 });
2218
- for (const d of scopes.diagnostics) {
2219
- if (d.kind !== "undeclared") continue;
2220
- out.push({ file: module.file, message: d.message, line: d.node.line.start, column: d.node.column.start, category: "type" });
2221
- }
2222
- const types = analyzeTypes(module.program, scopes, {
2223
- libs,
2224
- resolveModule: resolverFor(module.file),
2225
- reportUnknownTypes: libs.length > 0
2226
- });
2374
+ const { types, diagnostics } = analyzeModule(module.file, module.program);
2227
2375
  analyses.set(module.file, types);
2228
- for (const d of types.diagnostics) {
2229
- const at = d.node;
2230
- out.push({ file: module.file, message: d.message, line: at.line.start, column: at.column.start, category: "type" });
2231
- }
2376
+ out.push(...diagnostics);
2232
2377
  }
2233
2378
  return { types: analyses, diagnostics: out, lowerings: [...libraries.lowerings] };
2234
2379
  }