@tilua/compiler 6.0.1 → 7.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.
@@ -27,7 +27,7 @@ var index = (object2, key) => ({ type: "IndexExpression", object: object2, index
27
27
  var call = (callee, args) => ({ type: "CallExpression", callee, arguments: args, ...span() });
28
28
  var methodCall = (object2, method, args) => ({ type: "MethodCallExpression", object: object2, method: identifier(method), arguments: args, ...span() });
29
29
  var functionExpression = (func) => ({ type: "FunctionExpression", func, ...span() });
30
- var binary = (operator, left, right) => ({ type: "BinaryExpression", operator, left, right, ...span() });
30
+ var binary = (operator, left, right) => ({ type: "BinaryExpression", operator, left: operator === "^" && left.type === "UnaryExpression" ? parenthesized(left) : left, right, ...span() });
31
31
  var unary = (operator, argument) => ({ type: "UnaryExpression", operator, argument, ...span() });
32
32
  var parenthesized = (expression) => ({ type: "ParenthesizedExpression", expression, ...span() });
33
33
  var table = (fields) => ({ type: "TableExpression", fields, ...span() });
@@ -86,19 +86,19 @@ var Names = class _Names {
86
86
  * a name shared across modules (the module table) collides with none. */
87
87
  static from(...programs) {
88
88
  const taken = /* @__PURE__ */ new Set(["select", "pairs", "table", "tostring", "setmetatable", "error"]);
89
- const walk = (value) => {
89
+ const walk2 = (value) => {
90
90
  if (!value || typeof value !== "object") return;
91
91
  if (Array.isArray(value)) {
92
- for (const item of value) walk(item);
92
+ for (const item of value) walk2(item);
93
93
  return;
94
94
  }
95
95
  const node = value;
96
96
  if (typeof node.name === "string" && node.name) taken.add(node.name);
97
97
  for (const [key, child] of Object.entries(value)) {
98
- if (key !== "line" && key !== "column") walk(child);
98
+ if (key !== "line" && key !== "column") walk2(child);
99
99
  }
100
100
  };
101
- for (const program2 of programs) walk(program2);
101
+ for (const program2 of programs) walk2(program2);
102
102
  return new _Names(taken);
103
103
  }
104
104
  /** A copy that goes on to take names independently — one per module, each
@@ -106,6 +106,10 @@ var Names = class _Names {
106
106
  fork() {
107
107
  return new _Names(new Set(this.taken));
108
108
  }
109
+ /** `name`, chosen elsewhere, is not to be chosen again here. */
110
+ take(name) {
111
+ this.taken.add(name);
112
+ }
109
113
  /** `base`, or `base2`, `base3`, ... — whichever is free first. */
110
114
  fresh(base) {
111
115
  let name = base;
@@ -123,6 +127,7 @@ var implemented = (source) => new Set([...source.matchAll(/function __NAME__\.(\
123
127
  var array = read("array.luau");
124
128
  var string2 = read("string.luau");
125
129
  var object = read("object.luau");
130
+ var json = read("json.luau");
126
131
  var arrayMethods = implemented(array);
127
132
  var stringMethods = implemented(string2);
128
133
  var objectMethods = implemented(object);
@@ -155,7 +160,7 @@ function objectKeys(type) {
155
160
  return { known, open: String(open) };
156
161
  }
157
162
  var plugin = {
158
- runtime: { array, string: string2, object },
163
+ runtime: { array, string: string2, object, json },
159
164
  methodCall({ method, receiver, use }) {
160
165
  if (isString(receiver)) {
161
166
  return stringMethods.has(method) ? { callee: `${use("string")}.${method}` } : void 0;
@@ -244,6 +249,141 @@ var plugin2 = {
244
249
  var standardLowering = { plugin: plugin2, from: "tilua (standard library)" };
245
250
 
246
251
  // src/lower.ts
252
+ function syntaxOf(lowerings) {
253
+ return new Set((lowerings ?? []).flatMap((l) => l.plugin.syntax ?? []));
254
+ }
255
+ var SharedRuntime = class {
256
+ constructor(names) {
257
+ this.names = names;
258
+ }
259
+ names;
260
+ byKey = /* @__PURE__ */ new Map();
261
+ /** Each group of names and the statements that declare them as locals,
262
+ * in the order they were first defined: each after what it uses. */
263
+ definitions = [];
264
+ defined = /* @__PURE__ */ new Set();
265
+ /** The name the helper `key` goes by, in every module. */
266
+ name(key, base) {
267
+ let name = this.byKey.get(key);
268
+ if (!name) {
269
+ name = this.names.fresh(base);
270
+ this.byKey.set(key, name);
271
+ }
272
+ return name;
273
+ }
274
+ /** `statements` declare `names`. Only the first module to say so is heard:
275
+ * the others' are the same. */
276
+ define(names, statements) {
277
+ if (names.every((name) => this.defined.has(name))) return;
278
+ for (const name of names) this.defined.add(name);
279
+ this.definitions.push({ names, statements });
280
+ }
281
+ /** Tree shaking: leave out of each runtime table the members nothing
282
+ * reads. `code` is everything else the output holds — the modules —
283
+ * and a member is read when it, or a member read already, names it:
284
+ * `tilua_array.filter(xs, f)` keeps `filter`, and whatever `filter`
285
+ * itself calls. A table used as anything but `table.member` — handed
286
+ * on, walked, indexed by what only the running program knows — keeps
287
+ * every member. */
288
+ shake(code) {
289
+ const names = new Set(this.definitions.flatMap((group) => group.names));
290
+ const read3 = /* @__PURE__ */ new Map();
291
+ const use = (name, key) => {
292
+ const now = read3.get(name);
293
+ if (now === "all") return;
294
+ if (key === void 0) {
295
+ read3.set(name, "all");
296
+ return;
297
+ }
298
+ read3.set(name, (now ?? /* @__PURE__ */ new Set()).add(key));
299
+ };
300
+ const isRead = (name, key) => {
301
+ const now = read3.get(name);
302
+ return now === "all" || (now?.has(key) ?? false);
303
+ };
304
+ const scan = (value) => {
305
+ if (!value || typeof value !== "object") return;
306
+ if (Array.isArray(value)) return void value.forEach(scan);
307
+ const node = value;
308
+ const object2 = node.object;
309
+ if (object2?.type === "Identifier" && names.has(object2.name)) {
310
+ if (node.type === "MemberExpression") return use(object2.name, node.property.name);
311
+ if (node.type === "MethodCallExpression") {
312
+ use(object2.name, node.method.name);
313
+ return scan(node.arguments);
314
+ }
315
+ if (node.type === "IndexExpression") {
316
+ const index2 = node.index;
317
+ if (index2.type === "StringLiteral") return use(object2.name, String(index2.value));
318
+ }
319
+ }
320
+ if (node.type === "Identifier" && names.has(node.name)) return use(node.name);
321
+ if (node.type === "LocalFunctionStatement") return scan(node.func);
322
+ for (const [key, child] of Object.entries(node)) {
323
+ if (key !== "line" && key !== "column") scan(child);
324
+ }
325
+ };
326
+ const defines = (statement, own) => {
327
+ if (statement.type === "FunctionDeclarationStatement") {
328
+ const target = statement.target;
329
+ if (own.includes(target.base.name) && target.path.length === 1 && !target.method) {
330
+ return { name: target.base.name, key: target.path[0].name, value: statement.func };
331
+ }
332
+ }
333
+ if (statement.type === "AssignmentStatement" && statement.targets.length === 1 && statement.values.length === 1) {
334
+ const target = statement.targets[0];
335
+ if (target.object?.type === "Identifier" && own.includes(target.object.name)) {
336
+ if (target.type === "MemberExpression") return { name: target.object.name, key: target.property.name, value: statement.values[0] };
337
+ if (target.type === "IndexExpression" && target.index?.type === "StringLiteral") {
338
+ return { name: target.object.name, key: String(target.index.value), value: statement.values[0] };
339
+ }
340
+ }
341
+ }
342
+ return void 0;
343
+ };
344
+ scan(code);
345
+ const scanned = /* @__PURE__ */ new Set();
346
+ let more = true;
347
+ while (more) {
348
+ more = false;
349
+ for (const group of this.definitions) {
350
+ for (const statement of group.statements) {
351
+ if (scanned.has(statement)) continue;
352
+ const definition = defines(statement, group.names);
353
+ if (definition && !isRead(definition.name, definition.key)) continue;
354
+ scanned.add(statement);
355
+ more = true;
356
+ scan(definition ? definition.value : statement);
357
+ }
358
+ }
359
+ }
360
+ for (const group of this.definitions) {
361
+ group.statements = group.statements.filter((statement) => {
362
+ const definition = defines(statement, group.names);
363
+ return !definition || isRead(definition.name, definition.key);
364
+ });
365
+ }
366
+ }
367
+ /** Luau setting up the helpers `used` for a chunk whose code names them:
368
+ * `declare` goes before that code, which reads each as an upvalue, and
369
+ * `define` after it — before any of it runs, and after what a helper
370
+ * reads as it is set up (the line map). Each definition runs in a
371
+ * function of its own, so what it keeps to itself stays there. */
372
+ chunk(used) {
373
+ const groups = this.definitions.filter((group) => group.names.some((name) => used.has(name)));
374
+ const all = groups.flatMap((group) => group.names);
375
+ const declare = [];
376
+ for (let i = 0; i < all.length; i += 100) declare.push(local(all.slice(i, i + 100), []));
377
+ const define = groups.map((group) => assign(
378
+ group.names.map((name) => identifier(name)),
379
+ [call(parenthesized(functionExpression(functionBody([], [
380
+ ...group.statements,
381
+ returns(group.names.map((name) => identifier(name)))
382
+ ]))), [])]
383
+ ));
384
+ return { declare, define };
385
+ }
386
+ };
247
387
  function lower(program2, scopes, options = {}) {
248
388
  const result2 = new Lowerer(program2, scopes, options).run();
249
389
  dropUnreachable(result2.statements);
@@ -274,7 +414,7 @@ var Lowerer = class {
274
414
  this.source = source;
275
415
  this.scopes = scopes;
276
416
  this.options = options;
277
- this.lua51 = options.target === "lua51";
417
+ this.syntax = syntaxOf(options.lowerings);
278
418
  this.names = options.names ? options.names.fork() : Names.from(source);
279
419
  this.exportsName = this.names.fresh("exports");
280
420
  for (const binding of scopes.bindings.values()) {
@@ -299,6 +439,11 @@ var Lowerer = class {
299
439
  names;
300
440
  diagnostics = [];
301
441
  helpers = /* @__PURE__ */ new Map();
442
+ /** The shared helpers this file names. */
443
+ sharedUsed = /* @__PURE__ */ new Set();
444
+ /** While a shared helper is written: it is defined outside every module,
445
+ * where none of a module's own names shadows a global. */
446
+ rawGlobals = false;
302
447
  /** The binding each declaration node creates. */
303
448
  bindingByDeclaration = /* @__PURE__ */ new Map();
304
449
  /** tilua names that are Luau keywords, and what they are called instead. */
@@ -314,8 +459,8 @@ var Lowerer = class {
314
459
  tryFrames = [];
315
460
  /** The loops being lowered in the current function, innermost last. */
316
461
  loops = [];
317
- /** Whether the output has to be plain Lua 5.1 rather than Luau. */
318
- lua51;
462
+ /** What the output's Lua can write beyond Lua 5.1 (`syntaxOf`). */
463
+ syntax;
319
464
  /** The source line each emitted statement came from. */
320
465
  origins = /* @__PURE__ */ new WeakMap();
321
466
  /** The name each class written as a value is known by. */
@@ -323,15 +468,19 @@ var Lowerer = class {
323
468
  run() {
324
469
  const body = this.options.module ? this.module(this.options.module) : this.topLevel(this.source.body.statements);
325
470
  const scriptArgs = this.usesScriptArgs && !this.options.module ? [local(["scriptArgs"], [this.array(table([{ type: "TableFieldPositional", value: vararg() }]))])] : [];
471
+ this.rawGlobals = this.options.shared !== void 0;
326
472
  const helpers = this.helperDefinitions();
473
+ const classes = this.classRuntimeStatements();
474
+ this.rawGlobals = false;
327
475
  const captured = [...this.builtins].map(([global, local2]) => local([local2], [identifier(global)]));
328
476
  return {
329
- statements: [...captured, ...helpers, ...scriptArgs, ...this.classRuntimeStatements(), ...body],
477
+ statements: [...captured, ...helpers, ...scriptArgs, ...classes, ...body],
330
478
  exportsName: this.exportsName,
331
479
  module: this.info,
332
480
  diagnostics: this.diagnostics,
333
481
  origins: this.origins,
334
- usesScriptArgs: this.usesScriptArgs
482
+ usesScriptArgs: this.usesScriptArgs,
483
+ shared: [...this.sharedUsed]
335
484
  };
336
485
  }
337
486
  /** Whether the file reads `scriptArgs`, the language's own global. */
@@ -364,6 +513,7 @@ var Lowerer = class {
364
513
  /** A global the generated code calls — `pairs`, `table`, ... — under a name
365
514
  * the source cannot have shadowed. */
366
515
  builtin(global) {
516
+ if (this.rawGlobals) return identifier(global);
367
517
  let local2 = this.builtins.get(global);
368
518
  if (!local2) {
369
519
  const shadowed = [...this.scopes.bindings.values()].some((b) => b.name === global && b.kind !== "global");
@@ -375,16 +525,31 @@ var Lowerer = class {
375
525
  }
376
526
  /** `table.move`. Lua 5.1 has none, and gets a function that does the same. */
377
527
  move() {
378
- return this.lua51 ? this.helper("move") : member(this.builtin("table"), "move");
528
+ return this.helper("move");
379
529
  }
380
530
  helper(kind) {
381
531
  let name = this.helpers.get(kind);
382
532
  if (!name) {
383
- name = this.names.fresh(`tilua_${kind === "array" ? "asArray" : kind}`);
533
+ name = this.sharedName(`helper:${kind}`, `tilua_${kind === "array" ? "asArray" : kind}`);
384
534
  this.helpers.set(kind, name);
385
535
  }
386
536
  return identifier(name);
387
537
  }
538
+ /** A fresh name for a helper — in a bundle, the one every module calls
539
+ * that helper by. */
540
+ sharedName(key, base) {
541
+ const shared = this.options.shared;
542
+ if (!shared) return this.names.fresh(base);
543
+ const name = shared.name(key, base);
544
+ this.names.take(name);
545
+ this.sharedUsed.add(name);
546
+ return name;
547
+ }
548
+ /** `statements` declare `names`: in this file, or once for the bundle. */
549
+ define(names, statements, out) {
550
+ if (this.options.shared) this.options.shared.define(names, statements);
551
+ else out.push(...statements);
552
+ }
388
553
  // `typeof v` tells an array from an object, which Luau cannot: both are
389
554
  // tables. So every array the program makes — `[a, b]`, a rest parameter,
390
555
  // what `:map` answers — is written down as one, in a table that holds its
@@ -417,7 +582,8 @@ var Lowerer = class {
417
582
  const key = `${index2}:${runtime2}`;
418
583
  let name = this.loweringNames.get(key);
419
584
  if (!name) {
420
- name = this.names.fresh(`tilua_${runtime2.replace(/[^A-Za-z0-9_]/g, "_")}`);
585
+ const lines = this.lowerings[index2].plugin.runtime?.[runtime2]?.includes("__LINES__") ? this.options.module?.lines : "";
586
+ name = this.sharedName(`lowering:${key}:${lines}`, `tilua_${runtime2.replace(/[^A-Za-z0-9_]/g, "_")}`);
421
587
  this.loweringNames.set(key, name);
422
588
  this.loweringUsed.push({ index: index2, runtime: runtime2, name });
423
589
  }
@@ -617,19 +783,25 @@ var Lowerer = class {
617
783
  const runtimes = this.runtimeDefinitions();
618
784
  if (this.helpers.has("concat")) this.helper("array");
619
785
  if (this.helpers.has("concat")) this.move();
786
+ const unpack = this.helpers.get("unpack");
787
+ if (unpack) {
788
+ this.define([unpack], parseLuau(
789
+ `local ${unpack} = ${this.builtin("table").name}.unpack or ${this.builtin("unpack").name}`
790
+ ).body.statements, out);
791
+ }
620
792
  const move = this.helpers.get("move");
621
793
  if (move) {
622
- out.push(...parseLuau(`
623
- local function ${move}(from, first, last, at, to)
794
+ this.define([move], parseLuau(`
795
+ local ${move} = ${this.builtin("table").name}.move or function(from, first, last, at, to)
624
796
  for i = first, last do
625
797
  to[at + i - first] = from[i]
626
798
  end
627
799
  return to
628
- end`).body.statements);
800
+ end`).body.statements, out);
629
801
  }
630
802
  const each = this.helpers.get("each");
631
803
  if (each) {
632
- out.push(...parseLuau(`
804
+ this.define([each], parseLuau(`
633
805
  local function ${each}(source, ...)
634
806
  if ${this.builtin("type").name}(source) == "function" then
635
807
  return source, ...
@@ -639,7 +811,7 @@ local function ${each}(source, ...)
639
811
  return meta.__iter(source)
640
812
  end
641
813
  return ${this.builtin("next").name}, source
642
- end`).body.statements);
814
+ end`).body.statements, out);
643
815
  }
644
816
  const marker = this.helpers.get("array");
645
817
  const typeOf = this.helpers.get("typeof");
@@ -647,19 +819,19 @@ end`).body.statements);
647
819
  const arrays = this.arrays();
648
820
  const own = this.helpers.get("arrays");
649
821
  if (own) {
650
- out.push(...parseLuau(`local ${own} = ${this.builtin("setmetatable").name}({}, { __mode = "k" })`).body.statements);
822
+ this.define([own], parseLuau(`local ${own} = ${this.builtin("setmetatable").name}({}, { __mode = "k" })`).body.statements, out);
651
823
  }
652
824
  if (marker) {
653
- out.push(...parseLuau(`
825
+ this.define([marker], parseLuau(`
654
826
  local function ${marker}(t)
655
827
  ${arrays}[t] = true
656
828
  return t
657
- end`).body.statements);
829
+ end`).body.statements, out);
658
830
  }
659
831
  if (typeOf) {
660
- out.push(...parseLuau(`
832
+ this.define([typeOf], parseLuau(`
661
833
  local function ${typeOf}(value, known)
662
- local name = ${this.builtin(this.lua51 ? "type" : "typeof").name}(value)
834
+ local name = (${this.builtin("typeof").name} or ${this.builtin("type").name})(value)
663
835
  if name ~= "table" then
664
836
  return name
665
837
  end
@@ -676,22 +848,22 @@ local function ${typeOf}(value, known)
676
848
  return "array"
677
849
  end
678
850
  return "object"
679
- end`).body.statements);
851
+ end`).body.statements, out);
680
852
  }
681
853
  }
682
854
  const add = this.helpers.get("add");
683
855
  if (add) {
684
- out.push(...parseLuau(`
856
+ this.define([add], parseLuau(`
685
857
  local function ${add}(a, b)
686
858
  if ${this.builtin("type").name}(a) == "string" or ${this.builtin("type").name}(b) == "string" then
687
859
  return ${this.builtin("tostring").name}(a) .. ${this.builtin("tostring").name}(b)
688
860
  end
689
861
  return a + b
690
- end`).body.statements);
862
+ end`).body.statements, out);
691
863
  }
692
864
  const instanceOf = this.helpers.get("instanceof");
693
865
  if (instanceOf) {
694
- out.push(...parseLuau(`
866
+ this.define([instanceOf], parseLuau(`
695
867
  local function ${instanceOf}(value, class)
696
868
  if ${this.builtin("type").name}(value) ~= "table" then
697
869
  return false
@@ -704,11 +876,11 @@ local function ${instanceOf}(value, class)
704
876
  current = current.ParentClass
705
877
  end
706
878
  return false
707
- end`).body.statements);
879
+ end`).body.statements, out);
708
880
  }
709
881
  const accessors = this.helpers.get("accessors");
710
882
  if (accessors) {
711
- out.push(...parseLuau(`
883
+ this.define([accessors], parseLuau(`
712
884
  local function ${accessors}(target, getters, setters)
713
885
  return ${this.builtin("setmetatable").name}(target, {
714
886
  -- Read by a spread, which copies what the getters give.
@@ -732,11 +904,11 @@ local function ${accessors}(target, getters, setters)
732
904
  ${this.builtin("rawset").name}(self, key, value)
733
905
  end,
734
906
  })
735
- end`).body.statements);
907
+ end`).body.statements, out);
736
908
  }
737
909
  const assign2 = this.helpers.get("assign");
738
910
  if (assign2) {
739
- out.push(...parseLuau(`
911
+ this.define([assign2], parseLuau(`
740
912
  local function ${assign2}(target, ...)
741
913
  for i = 1, ${this.builtin("select").name}("#", ...) do
742
914
  local source = (${this.builtin("select").name}(i, ...))
@@ -754,12 +926,12 @@ local function ${assign2}(target, ...)
754
926
  end
755
927
  end
756
928
  return target
757
- end`).body.statements);
929
+ end`).body.statements, out);
758
930
  }
759
- out.push(...runtimes);
931
+ for (const { names, statements } of runtimes) this.define(names, statements, out);
760
932
  const concat = this.helpers.get("concat");
761
933
  if (concat) {
762
- out.push(localFunction(concat, functionBody([], [
934
+ this.define([concat], [localFunction(concat, functionBody([], [
763
935
  local(["result"], [this.array(table([]))]),
764
936
  numericFor("i", number(1), selectCount(this.builtin("select")), [
765
937
  local(["part"], [call(this.builtin("select"), [identifier("i"), vararg()])]),
@@ -772,7 +944,7 @@ end`).body.statements);
772
944
  ]))
773
945
  ]),
774
946
  returns([identifier("result")])
775
- ], true)));
947
+ ], true))], out);
776
948
  }
777
949
  return out;
778
950
  }
@@ -792,7 +964,12 @@ end`).body.statements);
792
964
  }
793
965
  const lines = this.options.module?.lines ?? "nil";
794
966
  const fail = this.options.module?.fail ?? "nil";
795
- out.push(...parseLuau(source.replace(/__NAME__/g, name).replace(/__LINES__/g, lines).replace(/__FAIL__/g, fail).replace(/__ARRAY__/g, () => this.helper("array").name)).body.statements);
967
+ const group = {
968
+ names: [name],
969
+ statements: parseLuau(source.replace(/__NAME__/g, name).replace(/__LINES__/g, lines).replace(/__FAIL__/g, fail).replace(/__ARRAYS__/g, () => this.arrays()).replace(/__ARRAY__/g, () => this.helper("array").name).replace(/__JSON__/g, () => this.loweringRuntime(0, "json"))).body.statements
970
+ };
971
+ if (index2 === 0 && runtime2 === "json") out.unshift(group);
972
+ else out.push(group);
796
973
  }
797
974
  return out;
798
975
  }
@@ -807,7 +984,8 @@ end`).body.statements);
807
984
  * util = require("util") -- the imports
808
985
  * ... -- the rest, in source order */
809
986
  module(context) {
810
- const statements = this.source.body.statements;
987
+ const shaken = this.options.shaken;
988
+ const statements = shaken ? this.source.body.statements.filter((s) => !shaken.removed.has(s)) : this.source.body.statements;
811
989
  const exportsTable = () => identifier(this.exportsName);
812
990
  const { names, links, stars } = this.info;
813
991
  const locals = [];
@@ -846,13 +1024,24 @@ end`).body.statements);
846
1024
  };
847
1025
  for (const statement of statements) {
848
1026
  if (statement.type !== "ImportStatement" || statement.isTypeOnly) continue;
1027
+ if (!statement.defaultImport && !statement.namespaceImport && !statement.specifiers.length) {
1028
+ const key2 = resolve3(statement.source);
1029
+ if (key2 !== void 0) load(key2);
1030
+ continue;
1031
+ }
849
1032
  const specifiers = [
850
1033
  ...statement.defaultImport ? [{ local: statement.defaultImport, imported: "default" }] : [],
851
1034
  ...statement.namespaceImport ? [{ local: statement.namespaceImport, imported: void 0 }] : [],
852
1035
  ...statement.specifiers.map((s) => ({ local: s.local, imported: s.imported.name }))
853
1036
  ];
854
- const used = specifiers.filter((s) => (this.bindingByDeclaration.get(s.local)?.references.length ?? 0) > 0);
855
- if (!used.length) continue;
1037
+ const used = specifiers.filter((s) => (this.bindingByDeclaration.get(s.local)?.references ?? []).some((reference) => !shaken?.dead.has(reference)));
1038
+ if (!used.length) {
1039
+ if (shaken?.runs(statement.source.value)) {
1040
+ const key2 = resolve3(statement.source);
1041
+ if (key2 !== void 0) load(key2);
1042
+ }
1043
+ continue;
1044
+ }
856
1045
  const key = resolve3(statement.source);
857
1046
  if (key === void 0) continue;
858
1047
  const local2 = moduleLocal(key);
@@ -894,6 +1083,7 @@ end`).body.statements);
894
1083
  break;
895
1084
  }
896
1085
  case "ExportAllStatement": {
1086
+ if (shaken && !shaken.needed(statement.source.value)) break;
897
1087
  const key = resolve3(statement.source);
898
1088
  if (key === void 0) break;
899
1089
  load(key);
@@ -1168,7 +1358,7 @@ end`).body.statements);
1168
1358
  case "VariableDeclaration":
1169
1359
  return this.variableDeclaration(node, "declare");
1170
1360
  case "FunctionDeclaration":
1171
- return [{ ...localFunction(this.name(node.name.name), this.functionBody(node.func)), attributes: this.lua51 ? void 0 : node.attributes }];
1361
+ return [{ ...localFunction(this.name(node.name.name), this.functionBody(node.func)), attributes: this.syntax.has("attributes") ? node.attributes : void 0 }];
1172
1362
  case "FunctionDeclarationStatement":
1173
1363
  return [this.functionDeclarationStatement(node)];
1174
1364
  case "ClassDeclaration":
@@ -1180,7 +1370,7 @@ end`).body.statements);
1180
1370
  const join = node.operator === "+=" ? this.options.types?.joins.get(node) : void 0;
1181
1371
  if (join === "dynamic") return this.dynamicAddition(node);
1182
1372
  const operator = join === "concat" ? "..=" : node.operator;
1183
- if (this.lua51) return this.compoundAssignment51(node, operator);
1373
+ if (!this.syntax.has("compoundAssignment")) return this.compoundAssignment51(node, operator);
1184
1374
  return [{
1185
1375
  type: "CompoundAssignmentStatement",
1186
1376
  operator,
@@ -1218,10 +1408,10 @@ end`).body.statements);
1218
1408
  case "RepeatStatement": {
1219
1409
  const body = this.block(node.body);
1220
1410
  const condition = this.expression(node.condition);
1221
- if (this.lua51 && hasOwnJump(body.statements, "ContinueStatement")) {
1411
+ if (!this.syntax.has("continue") && hasOwnJump(body.statements, "ContinueStatement")) {
1222
1412
  const hidden = declaredNames(body.statements).filter((name) => mentions(condition, name));
1223
1413
  if (hidden.length) {
1224
- this.report(node, `'continue' in a 'repeat' whose 'until' reads ${hidden.map((n) => `'${n}'`).join(", ")} cannot be compiled for Lua 5.1, which has no 'continue' to lower it to. Move what the condition reads out of the loop body, or use a 'while' loop.`);
1414
+ this.report(node, `'continue' in a 'repeat' whose 'until' reads ${hidden.map((n) => `'${n}'`).join(", ")} cannot be compiled to Lua, which has no 'continue' to lower it to. Move what the condition reads out of the loop body, or use a 'while' loop \u2014 or load a library whose Lua has 'continue' ("luau").`);
1225
1415
  }
1226
1416
  }
1227
1417
  return [{ type: "RepeatStatement", body: this.loopBody51(body), condition, ...spanOf(node) }];
@@ -1262,8 +1452,8 @@ end`).body.statements);
1262
1452
  let source = this.expression(node.iterator);
1263
1453
  if (form.viaIter) source = methodCall(source, "__iter", []);
1264
1454
  const variables = form.walks === "values" ? [this.names.fresh("_"), item] : [item];
1265
- let iterators = form.walks === "iteration" ? [call(this.builtin("unpack"), [source, number(1), number(3)])] : [source];
1266
- if (this.lua51 && form.walks === "values") {
1455
+ let iterators = form.walks === "iteration" ? [call(this.helper("unpack"), [source, number(1), number(3)])] : [source];
1456
+ if (!this.syntax.has("iteration") && form.walks === "values") {
1267
1457
  const kind = form.viaIter ? void 0 : this.options.types?.typeOf.get(node.iterator)?.kind;
1268
1458
  iterators = [call(kind === "array" || kind === "tuple" ? this.builtin("ipairs") : this.helper("each"), [source])];
1269
1459
  }
@@ -1419,8 +1609,8 @@ end`).body.statements);
1419
1609
  classHelpers;
1420
1610
  classRuntime() {
1421
1611
  return this.classHelpers ??= {
1422
- build: this.names.fresh("tilua_class"),
1423
- accessors: this.names.fresh("tilua_accessors")
1612
+ build: this.sharedName("class:build", "tilua_class"),
1613
+ accessors: this.sharedName("class:accessors", "tilua_accessors")
1424
1614
  };
1425
1615
  }
1426
1616
  /** The helpers, as Luau — emitted only in a file that declares a class.
@@ -1438,7 +1628,8 @@ end`).body.statements);
1438
1628
  if (!this.classHelpers) return [];
1439
1629
  const { build, accessors } = this.classHelpers;
1440
1630
  const metamethods = `{ ${CLASS_METAMETHODS.map((name) => `"${name}"`).join(", ")} }`;
1441
- return parseLuau(`
1631
+ const out = [];
1632
+ this.define([build, accessors], parseLuau(`
1442
1633
  local function ${build}(base, name)
1443
1634
  local class = { __getters = {}, __setters = {} }
1444
1635
  class.__index = class
@@ -1476,7 +1667,8 @@ local function ${accessors}(class)
1476
1667
  rawset(this, key, value)
1477
1668
  end
1478
1669
  end
1479
- `).body.statements;
1670
+ `).body.statements, out);
1671
+ return out;
1480
1672
  }
1481
1673
  /** The class being lowered, so `super` knows what to name. */
1482
1674
  classContext;
@@ -1504,6 +1696,7 @@ end
1504
1696
  string(called)
1505
1697
  ]);
1506
1698
  const out = [declare !== void 0 ? local([declare], [create]) : assign([self], [create])];
1699
+ const statics = [];
1507
1700
  const previous = this.classContext;
1508
1701
  this.classContext = node;
1509
1702
  try {
@@ -1511,7 +1704,7 @@ end
1511
1704
  switch (member2.type) {
1512
1705
  case "ClassField":
1513
1706
  if (member2.isStatic) {
1514
- out.push(assign(
1707
+ statics.push(assign(
1515
1708
  [member(self, member2.name.name)],
1516
1709
  [member2.init ? this.expression(member2.init) : nil()]
1517
1710
  ));
@@ -1542,6 +1735,7 @@ end
1542
1735
  out.push(this.classInit(node, self));
1543
1736
  if (!node.isAbstract) out.push(this.classNew(node, self));
1544
1737
  out.push(callStatement(call(identifier(accessors), [self])));
1738
+ out.push(...statics);
1545
1739
  } finally {
1546
1740
  this.classContext = previous;
1547
1741
  }
@@ -1618,7 +1812,7 @@ end
1618
1812
  isMethod,
1619
1813
  func,
1620
1814
  // `@native` and the like are Luau's.
1621
- attributes: this.lua51 ? void 0 : node.attributes,
1815
+ attributes: this.syntax.has("attributes") ? node.attributes : void 0,
1622
1816
  ...spanOf(node)
1623
1817
  };
1624
1818
  }
@@ -1858,7 +2052,7 @@ end
1858
2052
  if (join === "concat") return binary("..", left, right);
1859
2053
  if (join === "dynamic") return call(this.helper("add"), [this.keptToOne(left), this.keptToOne(right)]);
1860
2054
  }
1861
- if (this.lua51 && node.operator === "//") return this.floorDivide(left, right);
2055
+ if (!this.syntax.has("floorDivision") && node.operator === "//") return this.floorDivide(left, right);
1862
2056
  return binary(node.operator, left, right);
1863
2057
  }
1864
2058
  case "UnaryExpression":
@@ -1892,7 +2086,7 @@ end
1892
2086
  case "AsConstExpression":
1893
2087
  return this.expression(node.expression);
1894
2088
  case "IfElseExpression":
1895
- if (this.lua51) return this.ifElse51(node);
2089
+ if (!this.syntax.has("ifExpression")) return this.ifElse51(node);
1896
2090
  return {
1897
2091
  type: "IfElseExpression",
1898
2092
  clauses: node.clauses.map((c) => ({ condition: this.expression(c.condition), body: this.expression(c.body) })),
@@ -1939,7 +2133,7 @@ end
1939
2133
  const last2 = list.length - 1;
1940
2134
  return lowered.length && !many.has(list[last2]) ? [...lowered.slice(0, -1), this.keptToOne(lowered[last2])] : lowered;
1941
2135
  }
1942
- const unpack = (value) => call(this.lua51 ? this.builtin("unpack") : member(this.builtin("table"), "unpack"), [value]);
2136
+ const unpack = (value) => call(this.helper("unpack"), [value]);
1943
2137
  const last = list[list.length - 1];
1944
2138
  if (spreads.length === 1 && last.type === "SpreadElement") {
1945
2139
  return [...list.slice(0, -1).map((a) => this.expression(a)), unpack(this.expression(last.argument))];
@@ -2053,7 +2247,7 @@ end
2053
2247
  const segments = this.segments(chain);
2054
2248
  const base = this.expression(chain.base);
2055
2249
  const last = chain.links[chain.links.length - 1];
2056
- if (!this.lua51 && base.type === "Identifier" && segments.length === 1 && last.type !== "CallExpression" && last.type !== "MethodCallExpression") {
2250
+ if (this.syntax.has("ifExpression") && base.type === "Identifier" && segments.length === 1 && last.type !== "CallExpression" && last.type !== "MethodCallExpression") {
2057
2251
  return {
2058
2252
  type: "IfElseExpression",
2059
2253
  clauses: [{ condition: binary("==", base, nil()), body: nil() }],
@@ -2080,7 +2274,7 @@ end
2080
2274
  nullish(node) {
2081
2275
  const left = this.keptToOne(this.expression(node.left));
2082
2276
  const right = this.keptToOne(this.expression(node.right));
2083
- if (!this.lua51 && left.type === "Identifier") {
2277
+ if (this.syntax.has("ifExpression") && left.type === "Identifier") {
2084
2278
  return {
2085
2279
  type: "IfElseExpression",
2086
2280
  clauses: [{ condition: binary("~=", left, nil()), body: left }],
@@ -2167,14 +2361,19 @@ end
2167
2361
  let target;
2168
2362
  if (node.target.type === "MemberExpression" || node.target.type === "IndexExpression") {
2169
2363
  const object2 = this.expression(node.target.object);
2170
- const objectName = this.names.fresh("target");
2171
- before.push(local([objectName], [object2]));
2172
- if (node.target.type === "MemberExpression") {
2173
- target = member(identifier(objectName), node.target.property.name);
2364
+ const key = node.target.type === "IndexExpression" ? node.target.index : void 0;
2365
+ if (object2.type === "Identifier" && (key === void 0 || key.type === "StringLiteral" || key.type === "NumberLiteral")) {
2366
+ target = key === void 0 ? member(object2, node.target.property.name) : index(object2, this.expression(key));
2174
2367
  } else {
2175
- const keyName = this.names.fresh("key");
2176
- before.push(local([keyName], [this.expression(node.target.index)]));
2177
- target = index(identifier(objectName), identifier(keyName));
2368
+ const objectName = this.names.fresh("target");
2369
+ before.push(local([objectName], [object2]));
2370
+ if (node.target.type === "MemberExpression") {
2371
+ target = member(identifier(objectName), node.target.property.name);
2372
+ } else {
2373
+ const keyName = this.names.fresh("key");
2374
+ before.push(local([keyName], [this.expression(node.target.index)]));
2375
+ target = index(identifier(objectName), identifier(keyName));
2376
+ }
2178
2377
  }
2179
2378
  } else {
2180
2379
  target = this.expression(node.target);
@@ -2195,7 +2394,7 @@ end
2195
2394
  * done, and breaks for real there. A body with no `continue` is left
2196
2395
  * exactly as it was. */
2197
2396
  loopBody51(body) {
2198
- if (!this.lua51 || !hasOwnJump(body.statements, "ContinueStatement")) return body;
2397
+ if (this.syntax.has("continue") || !hasOwnJump(body.statements, "ContinueStatement")) return body;
2199
2398
  const flag = hasOwnJump(body.statements, "BreakStatement") ? this.names.fresh("broke") : void 0;
2200
2399
  const once = repeatUntil(rewriteOwnJumps(body.statements, flag), boolean(true));
2201
2400
  if (!flag) return block([once]);
@@ -2247,7 +2446,7 @@ end
2247
2446
  * underscores and `0b1010` becomes the decimal it means. A hex literal
2248
2447
  * keeps its base — 5.1 reads those. */
2249
2448
  numberRaw(node) {
2250
- if (!this.lua51) return node.raw;
2449
+ if (this.syntax.has("numberLiterals")) return node.raw;
2251
2450
  if (/^0[bB]/.test(node.raw)) return String(node.value);
2252
2451
  return node.raw.replace(/_/g, "");
2253
2452
  }
@@ -2500,10 +2699,10 @@ function declaredNames(statements) {
2500
2699
  }
2501
2700
  function mentions(expression, name) {
2502
2701
  let found = false;
2503
- const walk = (value) => {
2702
+ const walk2 = (value) => {
2504
2703
  if (found || !value || typeof value !== "object") return;
2505
2704
  if (Array.isArray(value)) {
2506
- for (const item of value) walk(item);
2705
+ for (const item of value) walk2(item);
2507
2706
  return;
2508
2707
  }
2509
2708
  const node = value;
@@ -2512,10 +2711,10 @@ function mentions(expression, name) {
2512
2711
  return;
2513
2712
  }
2514
2713
  for (const [key, child] of Object.entries(value)) {
2515
- if (key !== "line" && key !== "column") walk(child);
2714
+ if (key !== "line" && key !== "column") walk2(child);
2516
2715
  }
2517
2716
  };
2518
- walk(expression);
2717
+ walk2(expression);
2519
2718
  return found;
2520
2719
  }
2521
2720
  function allNodes(root) {
@@ -2598,6 +2797,269 @@ import {
2598
2797
  } from "@tilua/parser";
2599
2798
  import { parse as parseLuau2, parseExpressionFromSource as parseExpressionFromSource2, print } from "luau-parser";
2600
2799
 
2800
+ // src/treeshake.ts
2801
+ function walk(node, visit, parent) {
2802
+ if (!node || typeof node !== "object") return;
2803
+ if (Array.isArray(node)) {
2804
+ for (const item of node) walk(item, visit, parent);
2805
+ return;
2806
+ }
2807
+ const n = node;
2808
+ if (typeof n.type === "string" && visit(n, parent) === false) return;
2809
+ for (const [key, child] of Object.entries(n)) {
2810
+ if (key === "line" || key === "column") continue;
2811
+ walk(child, visit, typeof n.type === "string" ? n : parent);
2812
+ }
2813
+ }
2814
+ function pure(expression) {
2815
+ let ok = true;
2816
+ walk(expression, (node) => {
2817
+ if (!ok) return false;
2818
+ switch (node.type) {
2819
+ case "CallExpression":
2820
+ case "MethodCallExpression":
2821
+ case "ImportExpression":
2822
+ case "SuperExpression":
2823
+ ok = false;
2824
+ return false;
2825
+ case "FunctionExpression":
2826
+ case "FunctionBody":
2827
+ case "ClassMethod":
2828
+ case "ClassAccessor":
2829
+ case "ClassConstructor":
2830
+ return false;
2831
+ case "ClassField":
2832
+ if (!node.isStatic) return false;
2833
+ }
2834
+ });
2835
+ return ok;
2836
+ }
2837
+ function declarationOf(statement) {
2838
+ return statement.type === "ExportStatement" ? statement.declaration : statement;
2839
+ }
2840
+ function removable(statement) {
2841
+ const declaration = declarationOf(statement);
2842
+ switch (declaration.type) {
2843
+ case "FunctionDeclaration":
2844
+ return true;
2845
+ case "ClassDeclaration":
2846
+ return pure(declaration);
2847
+ case "VariableDeclaration":
2848
+ return declaration.init === void 0 || pure(declaration.init);
2849
+ case "ExportDefaultStatement":
2850
+ return pure(declaration.declaration);
2851
+ default:
2852
+ return false;
2853
+ }
2854
+ }
2855
+ function structural(statement) {
2856
+ switch (statement.type) {
2857
+ case "TypeAliasStatement":
2858
+ case "ExportTypeAliasStatement":
2859
+ case "DeclareStatement":
2860
+ case "DeclareClassStatement":
2861
+ case "DeclareMetatableStatement":
2862
+ case "ImportStatement":
2863
+ case "ExportNamedStatement":
2864
+ case "ExportAllStatement":
2865
+ return true;
2866
+ default:
2867
+ return false;
2868
+ }
2869
+ }
2870
+ function exportedNames(statement) {
2871
+ switch (statement.type) {
2872
+ case "ExportStatement": {
2873
+ const declaration = statement.declaration;
2874
+ if (declaration.type === "FunctionDeclaration" || declaration.type === "ClassDeclaration") return [declaration.name.name];
2875
+ const names = [];
2876
+ walk(declaration.name, (node) => {
2877
+ if (node.type === "IdentifierPattern") names.push(node.name);
2878
+ });
2879
+ return names;
2880
+ }
2881
+ case "ExportDefaultStatement":
2882
+ return ["default"];
2883
+ default:
2884
+ return [];
2885
+ }
2886
+ }
2887
+ function factsOf(module) {
2888
+ const tops = module.program.body.statements;
2889
+ const byNode = /* @__PURE__ */ new Map();
2890
+ for (const binding of module.scopes.bindings.values()) {
2891
+ if (binding.declarationNode) byNode.set(binding.declarationNode, binding.id);
2892
+ }
2893
+ const declaredBy = /* @__PURE__ */ new Map();
2894
+ const imported = /* @__PURE__ */ new Map();
2895
+ for (const statement of tops) {
2896
+ const declaration = declarationOf(statement);
2897
+ const declare = (node) => {
2898
+ const id = node && byNode.get(node);
2899
+ if (id !== void 0) declaredBy.set(id, statement);
2900
+ };
2901
+ if (declaration.type === "FunctionDeclaration" || declaration.type === "ClassDeclaration") declare(declaration.name);
2902
+ else if (declaration.type === "VariableDeclaration") {
2903
+ walk(declaration.name, (node) => {
2904
+ if (node.type === "IdentifierPattern") declare(node);
2905
+ });
2906
+ } else if (declaration.type === "ImportStatement" && !declaration.isTypeOnly) {
2907
+ const target = module.resolve(declaration.source.value);
2908
+ if (target === void 0) continue;
2909
+ const add = (node, name) => {
2910
+ const id = node && byNode.get(node);
2911
+ if (id !== void 0) imported.set(id, { target, name });
2912
+ };
2913
+ if (declaration.defaultImport) add(declaration.defaultImport, "default");
2914
+ if (declaration.namespaceImport) add(declaration.namespaceImport, "*");
2915
+ for (const s of declaration.specifiers) add(s.local, s.imported.name);
2916
+ }
2917
+ }
2918
+ const parents = /* @__PURE__ */ new WeakMap();
2919
+ walk(module.program.body, (node, parent) => {
2920
+ if (parent) parents.set(node, parent);
2921
+ });
2922
+ const sideEffects = tops.some((s) => !structural(s) && !removable(s));
2923
+ return { module, tops, declaredBy, imported, parents, sideEffects };
2924
+ }
2925
+ function treeShake(modules, entry) {
2926
+ const facts = new Map(modules.map((m) => [m.file, factsOf(m)]));
2927
+ const used = /* @__PURE__ */ new Map();
2928
+ const live = /* @__PURE__ */ new Map();
2929
+ const required = /* @__PURE__ */ new Set();
2930
+ let changed = true;
2931
+ const require2 = (file) => {
2932
+ if (required.has(file) || !facts.has(file)) return;
2933
+ required.add(file);
2934
+ changed = true;
2935
+ };
2936
+ const read3 = (file, name) => {
2937
+ if (!facts.has(file)) return;
2938
+ require2(file);
2939
+ const now = used.get(file);
2940
+ if (now === "all") return;
2941
+ if (name === "*") {
2942
+ used.set(file, "all");
2943
+ changed = true;
2944
+ return;
2945
+ }
2946
+ const names = now ?? /* @__PURE__ */ new Set();
2947
+ if (!names.has(name)) {
2948
+ names.add(name);
2949
+ used.set(file, names);
2950
+ changed = true;
2951
+ }
2952
+ };
2953
+ const isRead = (file, name) => {
2954
+ const now = used.get(file);
2955
+ return now === "all" || (now?.has(name) ?? false);
2956
+ };
2957
+ require2(entry);
2958
+ used.set(entry, "all");
2959
+ while (changed) {
2960
+ changed = false;
2961
+ for (const file of [...required]) {
2962
+ const f = facts.get(file);
2963
+ const kept = live.get(file) ?? /* @__PURE__ */ new Set();
2964
+ live.set(file, kept);
2965
+ const queue = [];
2966
+ const keep = (statement) => {
2967
+ if (!statement || kept.has(statement)) return;
2968
+ kept.add(statement);
2969
+ queue.push(statement);
2970
+ changed = true;
2971
+ };
2972
+ for (const statement of f.tops) {
2973
+ if (!structural(statement) && !removable(statement)) keep(statement);
2974
+ if (exportedNames(statement).some((name) => isRead(file, name))) keep(statement);
2975
+ const declaration = declarationOf(statement);
2976
+ if (declaration.type === "ImportStatement" && !declaration.isTypeOnly && !declaration.defaultImport && !declaration.namespaceImport && !declaration.specifiers.length) {
2977
+ const target = f.module.resolve(declaration.source.value);
2978
+ if (target !== void 0) require2(target);
2979
+ }
2980
+ if (declaration.type === "ExportNamedStatement" && !declaration.typeOnly) {
2981
+ const target = declaration.source ? f.module.resolve(declaration.source.value) : void 0;
2982
+ for (const s of declaration.specifiers) {
2983
+ if (!isRead(file, s.exported.name)) continue;
2984
+ if (declaration.source) {
2985
+ if (target !== void 0) read3(target, s.local.name);
2986
+ continue;
2987
+ }
2988
+ const id = f.module.scopes.bindingOf.get(s.local);
2989
+ const own = [...f.declaredBy].find(([bid, st]) => (id !== void 0 ? bid === id : false) || exportedLocal(st, s.local.name));
2990
+ if (own) keep(own[1]);
2991
+ const from = id !== void 0 ? f.imported.get(id) : void 0;
2992
+ if (from) read3(from.target, from.name);
2993
+ }
2994
+ }
2995
+ if (declaration.type === "ExportAllStatement") {
2996
+ const target = f.module.resolve(declaration.source.value);
2997
+ if (target === void 0) continue;
2998
+ const mine = used.get(file);
2999
+ if (mine === "all") read3(target, "*");
3000
+ else for (const name of mine ?? []) if (!ownExport(f, name)) read3(target, name);
3001
+ }
3002
+ }
3003
+ for (const statement of [...kept]) queue.push(statement);
3004
+ while (queue.length) {
3005
+ const statement = queue.pop();
3006
+ walk(statement, (node) => {
3007
+ if (node.type === "ImportExpression") {
3008
+ const source = node.source;
3009
+ const target = source?.type === "StringLiteral" ? f.module.resolve(source.value) : void 0;
3010
+ if (target !== void 0) read3(target, "*");
3011
+ return;
3012
+ }
3013
+ if (node.type !== "Identifier") return;
3014
+ const id = f.module.scopes.bindingOf.get(node);
3015
+ if (id === void 0) return;
3016
+ const declaring = f.declaredBy.get(id);
3017
+ if (declaring && declaring !== statement) keep(declaring);
3018
+ const from = f.imported.get(id);
3019
+ if (!from) return;
3020
+ if (from.name !== "*") return read3(from.target, from.name);
3021
+ const parent = f.parents.get(node);
3022
+ if (parent?.type === "MemberExpression" && parent.object === node) read3(from.target, parent.property.name);
3023
+ else read3(from.target, "*");
3024
+ });
3025
+ }
3026
+ for (const statement of f.tops) {
3027
+ if (statement.type !== "ImportStatement" || statement.isTypeOnly) continue;
3028
+ const target = f.module.resolve(statement.source.value);
3029
+ if (target !== void 0 && facts.get(target)?.sideEffects) require2(target);
3030
+ }
3031
+ }
3032
+ }
3033
+ const shaken = /* @__PURE__ */ new Map();
3034
+ for (const [file, f] of facts) {
3035
+ const kept = live.get(file) ?? /* @__PURE__ */ new Set();
3036
+ const removed = new Set(f.tops.filter((s) => !kept.has(s) && removable(s)));
3037
+ const dead = /* @__PURE__ */ new Set();
3038
+ for (const statement of removed) walk(statement, (node) => {
3039
+ if (node.type === "Identifier") dead.add(node);
3040
+ });
3041
+ shaken.set(file, { removed, dead });
3042
+ }
3043
+ return {
3044
+ modules: shaken,
3045
+ sideEffects: new Set([...facts].filter(([, f]) => f.sideEffects).map(([file]) => file)),
3046
+ required
3047
+ };
3048
+ }
3049
+ function exportedLocal(statement, name) {
3050
+ const declaration = declarationOf(statement);
3051
+ if (declaration.type === "FunctionDeclaration" || declaration.type === "ClassDeclaration") return declaration.name.name === name;
3052
+ if (declaration.type !== "VariableDeclaration") return false;
3053
+ let found = false;
3054
+ walk(declaration.name, (node) => {
3055
+ if (node.type === "IdentifierPattern" && node.name === name) found = true;
3056
+ });
3057
+ return found;
3058
+ }
3059
+ function ownExport(f, name) {
3060
+ return f.tops.some((statement) => exportedNames(statement).includes(name) || statement.type === "ExportNamedStatement" && statement.specifiers.some((s) => s.exported.name === name));
3061
+ }
3062
+
2601
3063
  // src/linemap.ts
2602
3064
  import { luauString as luauString3 } from "@tilua/parser";
2603
3065
  function statementsOf(node) {
@@ -2661,8 +3123,8 @@ async function bundle(options) {
2661
3123
  sources.set(file, { file, name: nameOf(file), text, program: program3, scopes });
2662
3124
  const dynamic = importExpressions(program3).flatMap((node) => node.source.type === "StringLiteral" ? [node.source.value] : []);
2663
3125
  for (const specifier of [...importedSpecifiers(program3), ...dynamic]) {
2664
- const target2 = resolveModulePath(file, specifier, config);
2665
- if (target2 && !target2.endsWith(".d.tilua")) queue.push(target2);
3126
+ const target = resolveModulePath(file, specifier, config);
3127
+ if (target && !target.endsWith(".d.tilua")) queue.push(target);
2666
3128
  }
2667
3129
  }
2668
3130
  const analysis = analyzeModules([...sources.values()], config);
@@ -2676,9 +3138,16 @@ async function bundle(options) {
2676
3138
  const requireExpression = member(identifier(G), "require");
2677
3139
  const importExpression = member(identifier(G), "import");
2678
3140
  const mode = options.output ?? config?.output ?? "single";
2679
- const target = options.target ?? config?.target ?? "luau";
3141
+ const byPath = syntaxOf(lowerings).has("requireByPath");
2680
3142
  const modules = /* @__PURE__ */ new Map();
2681
3143
  const origins = /* @__PURE__ */ new WeakMap();
3144
+ const shared = new SharedRuntime(names);
3145
+ const shaking = options.treeShaking ?? config?.treeShaking ?? false;
3146
+ const resolveIn = (file) => (specifier) => {
3147
+ const found = resolveModulePath(file, specifier, config);
3148
+ return found !== void 0 && sources.has(found) ? found : void 0;
3149
+ };
3150
+ const shaken = shaking ? treeShake([...sources.values()].map((s) => ({ file: s.file, program: s.program, scopes: s.scopes, resolve: resolveIn(s.file) })), entry) : void 0;
2682
3151
  const pending = [entry];
2683
3152
  while (pending.length) {
2684
3153
  const file = pending.shift();
@@ -2691,10 +3160,21 @@ async function bundle(options) {
2691
3160
  names,
2692
3161
  types: analysis.types.get(file),
2693
3162
  lowerings,
2694
- target,
2695
3163
  noLib: config?.noLib,
2696
3164
  source: source.text,
2697
3165
  file: shown,
3166
+ shared,
3167
+ shaken: shaken && {
3168
+ ...shaken.modules.get(file),
3169
+ runs: (specifier) => {
3170
+ const target = resolveIn(file)(specifier);
3171
+ return target !== void 0 && shaken.sideEffects.has(target);
3172
+ },
3173
+ needed: (specifier) => {
3174
+ const target = resolveIn(file)(specifier);
3175
+ return target !== void 0 && shaken.required.has(target);
3176
+ }
3177
+ },
2698
3178
  module: {
2699
3179
  name: source.name,
2700
3180
  require: requireExpression,
@@ -2725,6 +3205,7 @@ async function bundle(options) {
2725
3205
  exportsName: lowered2.exportsName,
2726
3206
  info: lowered2.module,
2727
3207
  usesScriptArgs: lowered2.usesScriptArgs,
3208
+ shared: lowered2.shared,
2728
3209
  imports,
2729
3210
  dynamicImports
2730
3211
  });
@@ -2750,16 +3231,17 @@ async function bundle(options) {
2750
3231
  if (diagnostics.some((d) => d.category === "syntax" || d.category === "scope" || d.category === "module")) {
2751
3232
  return { files: [], modules: moduleNames, diagnostics };
2752
3233
  }
3234
+ if (shaking) shared.shake(lowered.flatMap((m) => m.statements));
2753
3235
  const several = mode !== "single";
2754
3236
  const layout = layoutOf(mode, modules.get(entry), lowered, options.outFile);
2755
3237
  const usesScriptArgs = lowered.some((m) => m.usesScriptArgs);
2756
3238
  const elsewhere = /* @__PURE__ */ new Map();
2757
3239
  for (const file of layout.slice(1)) {
2758
- for (const module of file.modules) elsewhere.set(module.name, loadPath(layout[0].path, file.path, target));
3240
+ for (const module of file.modules) elsewhere.set(module.name, loadPath(layout[0].path, file.path, byPath));
2759
3241
  }
2760
3242
  const program2 = parseLuau2(runtime(G, {
2761
3243
  several,
2762
- target,
3244
+ byPath,
2763
3245
  files: elsewhere,
2764
3246
  imports: lowered.some((m) => m.dynamicImports.size > 0)
2765
3247
  }));
@@ -2774,6 +3256,8 @@ async function bundle(options) {
2774
3256
  for (const module of layout[0].modules) {
2775
3257
  modulesTable.fields.push({ type: "TableFieldComputed", key: string(module.name), value: moduleTable(module) });
2776
3258
  }
3259
+ const helpers = shared.chunk(new Set(layout[0].modules.flatMap((m) => m.shared)));
3260
+ program2.body.statements.unshift(...helpers.declare);
2777
3261
  const ok = names.fresh("ok");
2778
3262
  const result2 = names.fresh("result");
2779
3263
  const entryStatements = parseLuau2(`
@@ -2795,9 +3279,11 @@ ${modules.get(entry).info.exports ? `return ${result2}` : ""}
2795
3279
  if (several) mapStatements.push(...mapRegistration(G, layout[0], `${G}.lines`));
2796
3280
  program2.body.statements.splice(program2.body.statements.length - entryStatements.length, 0, ...mapStatements);
2797
3281
  }
3282
+ program2.body.statements.splice(program2.body.statements.length - entryStatements.length, 0, ...helpers.define);
2798
3283
  const files = [{ path: layout[0].path, code: print(program2) + "\n", modules: layout[0].modules.map((m) => m.name) }];
2799
3284
  for (const file of layout.slice(1)) {
2800
- const body = [];
3285
+ const fileHelpers = shared.chunk(new Set(file.modules.flatMap((m) => m.shared)));
3286
+ const body = [...fileHelpers.declare];
2801
3287
  if (file.modules.some((m) => m.usesScriptArgs)) body.push(...parseLuau2(`local scriptArgs = ${G}.scriptArgs`).body.statements);
2802
3288
  for (const module of file.modules) {
2803
3289
  body.push(assign(
@@ -2807,7 +3293,7 @@ ${modules.get(entry).info.exports ? `return ${result2}` : ""}
2807
3293
  }
2808
3294
  const chunk = program([returns([functionExpression(functionBody([G], body))])]);
2809
3295
  const lines = buildLineMap(chunk, parseLuau2(print(chunk)), origins);
2810
- body.push(...mapRegistration(G, file, lineMapSource(lines)));
3296
+ body.push(...mapRegistration(G, file, lineMapSource(lines)), ...fileHelpers.define);
2811
3297
  files.push({ path: file.path, code: print(chunk) + "\n", modules: file.modules.map((m) => m.name) });
2812
3298
  }
2813
3299
  return { code: files[0].code, files, modules: moduleNames, diagnostics };
@@ -2869,9 +3355,9 @@ function layoutOf(mode, entry, modules, outFile) {
2869
3355
  }
2870
3356
  return files;
2871
3357
  }
2872
- function loadPath(entryPath, file, target) {
3358
+ function loadPath(entryPath, file, byPath) {
2873
3359
  const path = posix.relative(posix.dirname(entryPath), file);
2874
- if (target === "lua51") return path;
3360
+ if (!byPath) return path;
2875
3361
  const bare = path.slice(0, path.length - posix.extname(path).length);
2876
3362
  return bare.startsWith("../") ? bare : `./${bare}`;
2877
3363
  }
@@ -2884,8 +3370,8 @@ function mapRegistration(G, file, map) {
2884
3370
  ...file.modules.map((module) => `${G}.maps[${luauString4(module.name)}] = ${G}.sources[#${G}.sources][2]`)
2885
3371
  ].join("\n")).body.statements;
2886
3372
  }
2887
- function loader(G, target) {
2888
- if (target === "lua51") {
3373
+ function loader(G, byPath) {
3374
+ if (!byPath) {
2889
3375
  return `
2890
3376
  -- From beside this file, wherever that is: its own path says where.
2891
3377
  local folder = ""
@@ -3093,7 +3579,7 @@ ${several ? `
3093
3579
  if ${G}.loaded[file] then
3094
3580
  return
3095
3581
  end
3096
- ${G}.loaded[file] = true${loader(G, options.target)}
3582
+ ${G}.loaded[file] = true${loader(G, options.byPath)}
3097
3583
  end,` : ""}${options.imports ? `
3098
3584
  -- \`import("./x")\`: the module's exports, asked for where the code runs
3099
3585
  -- rather than before its module does. \`require\` does the rest \u2014 and