@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.
package/dist/cli.cjs CHANGED
@@ -44,7 +44,7 @@ var index = (object2, key) => ({ type: "IndexExpression", object: object2, index
44
44
  var call = (callee, args2) => ({ type: "CallExpression", callee, arguments: args2, ...span() });
45
45
  var methodCall = (object2, method, args2) => ({ type: "MethodCallExpression", object: object2, method: identifier(method), arguments: args2, ...span() });
46
46
  var functionExpression = (func) => ({ type: "FunctionExpression", func, ...span() });
47
- var binary = (operator, left, right) => ({ type: "BinaryExpression", operator, left, right, ...span() });
47
+ var binary = (operator, left, right) => ({ type: "BinaryExpression", operator, left: operator === "^" && left.type === "UnaryExpression" ? parenthesized(left) : left, right, ...span() });
48
48
  var unary = (operator, argument) => ({ type: "UnaryExpression", operator, argument, ...span() });
49
49
  var parenthesized = (expression) => ({ type: "ParenthesizedExpression", expression, ...span() });
50
50
  var table = (fields) => ({ type: "TableExpression", fields, ...span() });
@@ -103,19 +103,19 @@ var Names = class _Names {
103
103
  * a name shared across modules (the module table) collides with none. */
104
104
  static from(...programs) {
105
105
  const taken = /* @__PURE__ */ new Set(["select", "pairs", "table", "tostring", "setmetatable", "error"]);
106
- const walk = (value) => {
106
+ const walk2 = (value) => {
107
107
  if (!value || typeof value !== "object") return;
108
108
  if (Array.isArray(value)) {
109
- for (const item of value) walk(item);
109
+ for (const item of value) walk2(item);
110
110
  return;
111
111
  }
112
112
  const node = value;
113
113
  if (typeof node.name === "string" && node.name) taken.add(node.name);
114
114
  for (const [key, child] of Object.entries(value)) {
115
- if (key !== "line" && key !== "column") walk(child);
115
+ if (key !== "line" && key !== "column") walk2(child);
116
116
  }
117
117
  };
118
- for (const program2 of programs) walk(program2);
118
+ for (const program2 of programs) walk2(program2);
119
119
  return new _Names(taken);
120
120
  }
121
121
  /** A copy that goes on to take names independently — one per module, each
@@ -123,6 +123,10 @@ var Names = class _Names {
123
123
  fork() {
124
124
  return new _Names(new Set(this.taken));
125
125
  }
126
+ /** `name`, chosen elsewhere, is not to be chosen again here. */
127
+ take(name) {
128
+ this.taken.add(name);
129
+ }
126
130
  /** `base`, or `base2`, `base3`, ... — whichever is free first. */
127
131
  fresh(base) {
128
132
  let name = base;
@@ -140,6 +144,7 @@ var implemented = (source) => new Set([...source.matchAll(/function __NAME__\.(\
140
144
  var array = read("array.luau");
141
145
  var string2 = read("string.luau");
142
146
  var object = read("object.luau");
147
+ var json = read("json.luau");
143
148
  var arrayMethods = implemented(array);
144
149
  var stringMethods = implemented(string2);
145
150
  var objectMethods = implemented(object);
@@ -172,7 +177,7 @@ function objectKeys(type) {
172
177
  return { known, open: String(open) };
173
178
  }
174
179
  var plugin = {
175
- runtime: { array, string: string2, object },
180
+ runtime: { array, string: string2, object, json },
176
181
  methodCall({ method, receiver, use }) {
177
182
  if (isString(receiver)) {
178
183
  return stringMethods.has(method) ? { callee: `${use("string")}.${method}` } : void 0;
@@ -261,6 +266,141 @@ var plugin2 = {
261
266
  var standardLowering = { plugin: plugin2, from: "tilua (standard library)" };
262
267
 
263
268
  // src/lower.ts
269
+ function syntaxOf(lowerings) {
270
+ return new Set((lowerings ?? []).flatMap((l) => l.plugin.syntax ?? []));
271
+ }
272
+ var SharedRuntime = class {
273
+ constructor(names) {
274
+ this.names = names;
275
+ }
276
+ names;
277
+ byKey = /* @__PURE__ */ new Map();
278
+ /** Each group of names and the statements that declare them as locals,
279
+ * in the order they were first defined: each after what it uses. */
280
+ definitions = [];
281
+ defined = /* @__PURE__ */ new Set();
282
+ /** The name the helper `key` goes by, in every module. */
283
+ name(key, base) {
284
+ let name = this.byKey.get(key);
285
+ if (!name) {
286
+ name = this.names.fresh(base);
287
+ this.byKey.set(key, name);
288
+ }
289
+ return name;
290
+ }
291
+ /** `statements` declare `names`. Only the first module to say so is heard:
292
+ * the others' are the same. */
293
+ define(names, statements) {
294
+ if (names.every((name) => this.defined.has(name))) return;
295
+ for (const name of names) this.defined.add(name);
296
+ this.definitions.push({ names, statements });
297
+ }
298
+ /** Tree shaking: leave out of each runtime table the members nothing
299
+ * reads. `code` is everything else the output holds — the modules —
300
+ * and a member is read when it, or a member read already, names it:
301
+ * `tilua_array.filter(xs, f)` keeps `filter`, and whatever `filter`
302
+ * itself calls. A table used as anything but `table.member` — handed
303
+ * on, walked, indexed by what only the running program knows — keeps
304
+ * every member. */
305
+ shake(code) {
306
+ const names = new Set(this.definitions.flatMap((group) => group.names));
307
+ const read3 = /* @__PURE__ */ new Map();
308
+ const use = (name, key) => {
309
+ const now = read3.get(name);
310
+ if (now === "all") return;
311
+ if (key === void 0) {
312
+ read3.set(name, "all");
313
+ return;
314
+ }
315
+ read3.set(name, (now ?? /* @__PURE__ */ new Set()).add(key));
316
+ };
317
+ const isRead = (name, key) => {
318
+ const now = read3.get(name);
319
+ return now === "all" || (now?.has(key) ?? false);
320
+ };
321
+ const scan = (value) => {
322
+ if (!value || typeof value !== "object") return;
323
+ if (Array.isArray(value)) return void value.forEach(scan);
324
+ const node = value;
325
+ const object2 = node.object;
326
+ if (object2?.type === "Identifier" && names.has(object2.name)) {
327
+ if (node.type === "MemberExpression") return use(object2.name, node.property.name);
328
+ if (node.type === "MethodCallExpression") {
329
+ use(object2.name, node.method.name);
330
+ return scan(node.arguments);
331
+ }
332
+ if (node.type === "IndexExpression") {
333
+ const index2 = node.index;
334
+ if (index2.type === "StringLiteral") return use(object2.name, String(index2.value));
335
+ }
336
+ }
337
+ if (node.type === "Identifier" && names.has(node.name)) return use(node.name);
338
+ if (node.type === "LocalFunctionStatement") return scan(node.func);
339
+ for (const [key, child] of Object.entries(node)) {
340
+ if (key !== "line" && key !== "column") scan(child);
341
+ }
342
+ };
343
+ const defines = (statement, own) => {
344
+ if (statement.type === "FunctionDeclarationStatement") {
345
+ const target = statement.target;
346
+ if (own.includes(target.base.name) && target.path.length === 1 && !target.method) {
347
+ return { name: target.base.name, key: target.path[0].name, value: statement.func };
348
+ }
349
+ }
350
+ if (statement.type === "AssignmentStatement" && statement.targets.length === 1 && statement.values.length === 1) {
351
+ const target = statement.targets[0];
352
+ if (target.object?.type === "Identifier" && own.includes(target.object.name)) {
353
+ if (target.type === "MemberExpression") return { name: target.object.name, key: target.property.name, value: statement.values[0] };
354
+ if (target.type === "IndexExpression" && target.index?.type === "StringLiteral") {
355
+ return { name: target.object.name, key: String(target.index.value), value: statement.values[0] };
356
+ }
357
+ }
358
+ }
359
+ return void 0;
360
+ };
361
+ scan(code);
362
+ const scanned = /* @__PURE__ */ new Set();
363
+ let more = true;
364
+ while (more) {
365
+ more = false;
366
+ for (const group of this.definitions) {
367
+ for (const statement of group.statements) {
368
+ if (scanned.has(statement)) continue;
369
+ const definition = defines(statement, group.names);
370
+ if (definition && !isRead(definition.name, definition.key)) continue;
371
+ scanned.add(statement);
372
+ more = true;
373
+ scan(definition ? definition.value : statement);
374
+ }
375
+ }
376
+ }
377
+ for (const group of this.definitions) {
378
+ group.statements = group.statements.filter((statement) => {
379
+ const definition = defines(statement, group.names);
380
+ return !definition || isRead(definition.name, definition.key);
381
+ });
382
+ }
383
+ }
384
+ /** Luau setting up the helpers `used` for a chunk whose code names them:
385
+ * `declare` goes before that code, which reads each as an upvalue, and
386
+ * `define` after it — before any of it runs, and after what a helper
387
+ * reads as it is set up (the line map). Each definition runs in a
388
+ * function of its own, so what it keeps to itself stays there. */
389
+ chunk(used) {
390
+ const groups = this.definitions.filter((group) => group.names.some((name) => used.has(name)));
391
+ const all = groups.flatMap((group) => group.names);
392
+ const declare = [];
393
+ for (let i = 0; i < all.length; i += 100) declare.push(local(all.slice(i, i + 100), []));
394
+ const define = groups.map((group) => assign(
395
+ group.names.map((name) => identifier(name)),
396
+ [call(parenthesized(functionExpression(functionBody([], [
397
+ ...group.statements,
398
+ returns(group.names.map((name) => identifier(name)))
399
+ ]))), [])]
400
+ ));
401
+ return { declare, define };
402
+ }
403
+ };
264
404
  function lower(program2, scopes, options = {}) {
265
405
  const result2 = new Lowerer(program2, scopes, options).run();
266
406
  dropUnreachable(result2.statements);
@@ -291,7 +431,7 @@ var Lowerer = class {
291
431
  this.source = source;
292
432
  this.scopes = scopes;
293
433
  this.options = options;
294
- this.lua51 = options.target === "lua51";
434
+ this.syntax = syntaxOf(options.lowerings);
295
435
  this.names = options.names ? options.names.fork() : Names.from(source);
296
436
  this.exportsName = this.names.fresh("exports");
297
437
  for (const binding of scopes.bindings.values()) {
@@ -316,6 +456,11 @@ var Lowerer = class {
316
456
  names;
317
457
  diagnostics = [];
318
458
  helpers = /* @__PURE__ */ new Map();
459
+ /** The shared helpers this file names. */
460
+ sharedUsed = /* @__PURE__ */ new Set();
461
+ /** While a shared helper is written: it is defined outside every module,
462
+ * where none of a module's own names shadows a global. */
463
+ rawGlobals = false;
319
464
  /** The binding each declaration node creates. */
320
465
  bindingByDeclaration = /* @__PURE__ */ new Map();
321
466
  /** tilua names that are Luau keywords, and what they are called instead. */
@@ -331,8 +476,8 @@ var Lowerer = class {
331
476
  tryFrames = [];
332
477
  /** The loops being lowered in the current function, innermost last. */
333
478
  loops = [];
334
- /** Whether the output has to be plain Lua 5.1 rather than Luau. */
335
- lua51;
479
+ /** What the output's Lua can write beyond Lua 5.1 (`syntaxOf`). */
480
+ syntax;
336
481
  /** The source line each emitted statement came from. */
337
482
  origins = /* @__PURE__ */ new WeakMap();
338
483
  /** The name each class written as a value is known by. */
@@ -340,15 +485,19 @@ var Lowerer = class {
340
485
  run() {
341
486
  const body = this.options.module ? this.module(this.options.module) : this.topLevel(this.source.body.statements);
342
487
  const scriptArgs = this.usesScriptArgs && !this.options.module ? [local(["scriptArgs"], [this.array(table([{ type: "TableFieldPositional", value: vararg() }]))])] : [];
488
+ this.rawGlobals = this.options.shared !== void 0;
343
489
  const helpers = this.helperDefinitions();
490
+ const classes = this.classRuntimeStatements();
491
+ this.rawGlobals = false;
344
492
  const captured = [...this.builtins].map(([global, local2]) => local([local2], [identifier(global)]));
345
493
  return {
346
- statements: [...captured, ...helpers, ...scriptArgs, ...this.classRuntimeStatements(), ...body],
494
+ statements: [...captured, ...helpers, ...scriptArgs, ...classes, ...body],
347
495
  exportsName: this.exportsName,
348
496
  module: this.info,
349
497
  diagnostics: this.diagnostics,
350
498
  origins: this.origins,
351
- usesScriptArgs: this.usesScriptArgs
499
+ usesScriptArgs: this.usesScriptArgs,
500
+ shared: [...this.sharedUsed]
352
501
  };
353
502
  }
354
503
  /** Whether the file reads `scriptArgs`, the language's own global. */
@@ -381,6 +530,7 @@ var Lowerer = class {
381
530
  /** A global the generated code calls — `pairs`, `table`, ... — under a name
382
531
  * the source cannot have shadowed. */
383
532
  builtin(global) {
533
+ if (this.rawGlobals) return identifier(global);
384
534
  let local2 = this.builtins.get(global);
385
535
  if (!local2) {
386
536
  const shadowed = [...this.scopes.bindings.values()].some((b) => b.name === global && b.kind !== "global");
@@ -392,16 +542,31 @@ var Lowerer = class {
392
542
  }
393
543
  /** `table.move`. Lua 5.1 has none, and gets a function that does the same. */
394
544
  move() {
395
- return this.lua51 ? this.helper("move") : member(this.builtin("table"), "move");
545
+ return this.helper("move");
396
546
  }
397
547
  helper(kind) {
398
548
  let name = this.helpers.get(kind);
399
549
  if (!name) {
400
- name = this.names.fresh(`tilua_${kind === "array" ? "asArray" : kind}`);
550
+ name = this.sharedName(`helper:${kind}`, `tilua_${kind === "array" ? "asArray" : kind}`);
401
551
  this.helpers.set(kind, name);
402
552
  }
403
553
  return identifier(name);
404
554
  }
555
+ /** A fresh name for a helper — in a bundle, the one every module calls
556
+ * that helper by. */
557
+ sharedName(key, base) {
558
+ const shared = this.options.shared;
559
+ if (!shared) return this.names.fresh(base);
560
+ const name = shared.name(key, base);
561
+ this.names.take(name);
562
+ this.sharedUsed.add(name);
563
+ return name;
564
+ }
565
+ /** `statements` declare `names`: in this file, or once for the bundle. */
566
+ define(names, statements, out2) {
567
+ if (this.options.shared) this.options.shared.define(names, statements);
568
+ else out2.push(...statements);
569
+ }
405
570
  // `typeof v` tells an array from an object, which Luau cannot: both are
406
571
  // tables. So every array the program makes — `[a, b]`, a rest parameter,
407
572
  // what `:map` answers — is written down as one, in a table that holds its
@@ -434,7 +599,8 @@ var Lowerer = class {
434
599
  const key = `${index2}:${runtime2}`;
435
600
  let name = this.loweringNames.get(key);
436
601
  if (!name) {
437
- name = this.names.fresh(`tilua_${runtime2.replace(/[^A-Za-z0-9_]/g, "_")}`);
602
+ const lines = this.lowerings[index2].plugin.runtime?.[runtime2]?.includes("__LINES__") ? this.options.module?.lines : "";
603
+ name = this.sharedName(`lowering:${key}:${lines}`, `tilua_${runtime2.replace(/[^A-Za-z0-9_]/g, "_")}`);
438
604
  this.loweringNames.set(key, name);
439
605
  this.loweringUsed.push({ index: index2, runtime: runtime2, name });
440
606
  }
@@ -634,19 +800,25 @@ var Lowerer = class {
634
800
  const runtimes = this.runtimeDefinitions();
635
801
  if (this.helpers.has("concat")) this.helper("array");
636
802
  if (this.helpers.has("concat")) this.move();
803
+ const unpack = this.helpers.get("unpack");
804
+ if (unpack) {
805
+ this.define([unpack], (0, import_luau_parser.parse)(
806
+ `local ${unpack} = ${this.builtin("table").name}.unpack or ${this.builtin("unpack").name}`
807
+ ).body.statements, out2);
808
+ }
637
809
  const move = this.helpers.get("move");
638
810
  if (move) {
639
- out2.push(...(0, import_luau_parser.parse)(`
640
- local function ${move}(from, first, last, at, to)
811
+ this.define([move], (0, import_luau_parser.parse)(`
812
+ local ${move} = ${this.builtin("table").name}.move or function(from, first, last, at, to)
641
813
  for i = first, last do
642
814
  to[at + i - first] = from[i]
643
815
  end
644
816
  return to
645
- end`).body.statements);
817
+ end`).body.statements, out2);
646
818
  }
647
819
  const each = this.helpers.get("each");
648
820
  if (each) {
649
- out2.push(...(0, import_luau_parser.parse)(`
821
+ this.define([each], (0, import_luau_parser.parse)(`
650
822
  local function ${each}(source, ...)
651
823
  if ${this.builtin("type").name}(source) == "function" then
652
824
  return source, ...
@@ -656,7 +828,7 @@ local function ${each}(source, ...)
656
828
  return meta.__iter(source)
657
829
  end
658
830
  return ${this.builtin("next").name}, source
659
- end`).body.statements);
831
+ end`).body.statements, out2);
660
832
  }
661
833
  const marker = this.helpers.get("array");
662
834
  const typeOf = this.helpers.get("typeof");
@@ -664,19 +836,19 @@ end`).body.statements);
664
836
  const arrays = this.arrays();
665
837
  const own = this.helpers.get("arrays");
666
838
  if (own) {
667
- out2.push(...(0, import_luau_parser.parse)(`local ${own} = ${this.builtin("setmetatable").name}({}, { __mode = "k" })`).body.statements);
839
+ this.define([own], (0, import_luau_parser.parse)(`local ${own} = ${this.builtin("setmetatable").name}({}, { __mode = "k" })`).body.statements, out2);
668
840
  }
669
841
  if (marker) {
670
- out2.push(...(0, import_luau_parser.parse)(`
842
+ this.define([marker], (0, import_luau_parser.parse)(`
671
843
  local function ${marker}(t)
672
844
  ${arrays}[t] = true
673
845
  return t
674
- end`).body.statements);
846
+ end`).body.statements, out2);
675
847
  }
676
848
  if (typeOf) {
677
- out2.push(...(0, import_luau_parser.parse)(`
849
+ this.define([typeOf], (0, import_luau_parser.parse)(`
678
850
  local function ${typeOf}(value, known)
679
- local name = ${this.builtin(this.lua51 ? "type" : "typeof").name}(value)
851
+ local name = (${this.builtin("typeof").name} or ${this.builtin("type").name})(value)
680
852
  if name ~= "table" then
681
853
  return name
682
854
  end
@@ -693,22 +865,22 @@ local function ${typeOf}(value, known)
693
865
  return "array"
694
866
  end
695
867
  return "object"
696
- end`).body.statements);
868
+ end`).body.statements, out2);
697
869
  }
698
870
  }
699
871
  const add = this.helpers.get("add");
700
872
  if (add) {
701
- out2.push(...(0, import_luau_parser.parse)(`
873
+ this.define([add], (0, import_luau_parser.parse)(`
702
874
  local function ${add}(a, b)
703
875
  if ${this.builtin("type").name}(a) == "string" or ${this.builtin("type").name}(b) == "string" then
704
876
  return ${this.builtin("tostring").name}(a) .. ${this.builtin("tostring").name}(b)
705
877
  end
706
878
  return a + b
707
- end`).body.statements);
879
+ end`).body.statements, out2);
708
880
  }
709
881
  const instanceOf = this.helpers.get("instanceof");
710
882
  if (instanceOf) {
711
- out2.push(...(0, import_luau_parser.parse)(`
883
+ this.define([instanceOf], (0, import_luau_parser.parse)(`
712
884
  local function ${instanceOf}(value, class)
713
885
  if ${this.builtin("type").name}(value) ~= "table" then
714
886
  return false
@@ -721,11 +893,11 @@ local function ${instanceOf}(value, class)
721
893
  current = current.ParentClass
722
894
  end
723
895
  return false
724
- end`).body.statements);
896
+ end`).body.statements, out2);
725
897
  }
726
898
  const accessors = this.helpers.get("accessors");
727
899
  if (accessors) {
728
- out2.push(...(0, import_luau_parser.parse)(`
900
+ this.define([accessors], (0, import_luau_parser.parse)(`
729
901
  local function ${accessors}(target, getters, setters)
730
902
  return ${this.builtin("setmetatable").name}(target, {
731
903
  -- Read by a spread, which copies what the getters give.
@@ -749,11 +921,11 @@ local function ${accessors}(target, getters, setters)
749
921
  ${this.builtin("rawset").name}(self, key, value)
750
922
  end,
751
923
  })
752
- end`).body.statements);
924
+ end`).body.statements, out2);
753
925
  }
754
926
  const assign2 = this.helpers.get("assign");
755
927
  if (assign2) {
756
- out2.push(...(0, import_luau_parser.parse)(`
928
+ this.define([assign2], (0, import_luau_parser.parse)(`
757
929
  local function ${assign2}(target, ...)
758
930
  for i = 1, ${this.builtin("select").name}("#", ...) do
759
931
  local source = (${this.builtin("select").name}(i, ...))
@@ -771,12 +943,12 @@ local function ${assign2}(target, ...)
771
943
  end
772
944
  end
773
945
  return target
774
- end`).body.statements);
946
+ end`).body.statements, out2);
775
947
  }
776
- out2.push(...runtimes);
948
+ for (const { names, statements } of runtimes) this.define(names, statements, out2);
777
949
  const concat = this.helpers.get("concat");
778
950
  if (concat) {
779
- out2.push(localFunction(concat, functionBody([], [
951
+ this.define([concat], [localFunction(concat, functionBody([], [
780
952
  local(["result"], [this.array(table([]))]),
781
953
  numericFor("i", number(1), selectCount(this.builtin("select")), [
782
954
  local(["part"], [call(this.builtin("select"), [identifier("i"), vararg()])]),
@@ -789,7 +961,7 @@ end`).body.statements);
789
961
  ]))
790
962
  ]),
791
963
  returns([identifier("result")])
792
- ], true)));
964
+ ], true))], out2);
793
965
  }
794
966
  return out2;
795
967
  }
@@ -809,7 +981,12 @@ end`).body.statements);
809
981
  }
810
982
  const lines = this.options.module?.lines ?? "nil";
811
983
  const fail = this.options.module?.fail ?? "nil";
812
- out2.push(...(0, import_luau_parser.parse)(source.replace(/__NAME__/g, name).replace(/__LINES__/g, lines).replace(/__FAIL__/g, fail).replace(/__ARRAY__/g, () => this.helper("array").name)).body.statements);
984
+ const group = {
985
+ names: [name],
986
+ statements: (0, import_luau_parser.parse)(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
987
+ };
988
+ if (index2 === 0 && runtime2 === "json") out2.unshift(group);
989
+ else out2.push(group);
813
990
  }
814
991
  return out2;
815
992
  }
@@ -824,7 +1001,8 @@ end`).body.statements);
824
1001
  * util = require("util") -- the imports
825
1002
  * ... -- the rest, in source order */
826
1003
  module(context) {
827
- const statements = this.source.body.statements;
1004
+ const shaken = this.options.shaken;
1005
+ const statements = shaken ? this.source.body.statements.filter((s) => !shaken.removed.has(s)) : this.source.body.statements;
828
1006
  const exportsTable = () => identifier(this.exportsName);
829
1007
  const { names, links, stars } = this.info;
830
1008
  const locals = [];
@@ -863,13 +1041,24 @@ end`).body.statements);
863
1041
  };
864
1042
  for (const statement of statements) {
865
1043
  if (statement.type !== "ImportStatement" || statement.isTypeOnly) continue;
1044
+ if (!statement.defaultImport && !statement.namespaceImport && !statement.specifiers.length) {
1045
+ const key2 = resolve4(statement.source);
1046
+ if (key2 !== void 0) load(key2);
1047
+ continue;
1048
+ }
866
1049
  const specifiers = [
867
1050
  ...statement.defaultImport ? [{ local: statement.defaultImport, imported: "default" }] : [],
868
1051
  ...statement.namespaceImport ? [{ local: statement.namespaceImport, imported: void 0 }] : [],
869
1052
  ...statement.specifiers.map((s) => ({ local: s.local, imported: s.imported.name }))
870
1053
  ];
871
- const used = specifiers.filter((s) => (this.bindingByDeclaration.get(s.local)?.references.length ?? 0) > 0);
872
- if (!used.length) continue;
1054
+ const used = specifiers.filter((s) => (this.bindingByDeclaration.get(s.local)?.references ?? []).some((reference) => !shaken?.dead.has(reference)));
1055
+ if (!used.length) {
1056
+ if (shaken?.runs(statement.source.value)) {
1057
+ const key2 = resolve4(statement.source);
1058
+ if (key2 !== void 0) load(key2);
1059
+ }
1060
+ continue;
1061
+ }
873
1062
  const key = resolve4(statement.source);
874
1063
  if (key === void 0) continue;
875
1064
  const local2 = moduleLocal(key);
@@ -911,6 +1100,7 @@ end`).body.statements);
911
1100
  break;
912
1101
  }
913
1102
  case "ExportAllStatement": {
1103
+ if (shaken && !shaken.needed(statement.source.value)) break;
914
1104
  const key = resolve4(statement.source);
915
1105
  if (key === void 0) break;
916
1106
  load(key);
@@ -1185,7 +1375,7 @@ end`).body.statements);
1185
1375
  case "VariableDeclaration":
1186
1376
  return this.variableDeclaration(node, "declare");
1187
1377
  case "FunctionDeclaration":
1188
- return [{ ...localFunction(this.name(node.name.name), this.functionBody(node.func)), attributes: this.lua51 ? void 0 : node.attributes }];
1378
+ return [{ ...localFunction(this.name(node.name.name), this.functionBody(node.func)), attributes: this.syntax.has("attributes") ? node.attributes : void 0 }];
1189
1379
  case "FunctionDeclarationStatement":
1190
1380
  return [this.functionDeclarationStatement(node)];
1191
1381
  case "ClassDeclaration":
@@ -1197,7 +1387,7 @@ end`).body.statements);
1197
1387
  const join2 = node.operator === "+=" ? this.options.types?.joins.get(node) : void 0;
1198
1388
  if (join2 === "dynamic") return this.dynamicAddition(node);
1199
1389
  const operator = join2 === "concat" ? "..=" : node.operator;
1200
- if (this.lua51) return this.compoundAssignment51(node, operator);
1390
+ if (!this.syntax.has("compoundAssignment")) return this.compoundAssignment51(node, operator);
1201
1391
  return [{
1202
1392
  type: "CompoundAssignmentStatement",
1203
1393
  operator,
@@ -1235,10 +1425,10 @@ end`).body.statements);
1235
1425
  case "RepeatStatement": {
1236
1426
  const body = this.block(node.body);
1237
1427
  const condition = this.expression(node.condition);
1238
- if (this.lua51 && hasOwnJump(body.statements, "ContinueStatement")) {
1428
+ if (!this.syntax.has("continue") && hasOwnJump(body.statements, "ContinueStatement")) {
1239
1429
  const hidden = declaredNames(body.statements).filter((name) => mentions(condition, name));
1240
1430
  if (hidden.length) {
1241
- 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.`);
1431
+ 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").`);
1242
1432
  }
1243
1433
  }
1244
1434
  return [{ type: "RepeatStatement", body: this.loopBody51(body), condition, ...spanOf(node) }];
@@ -1279,8 +1469,8 @@ end`).body.statements);
1279
1469
  let source = this.expression(node.iterator);
1280
1470
  if (form.viaIter) source = methodCall(source, "__iter", []);
1281
1471
  const variables = form.walks === "values" ? [this.names.fresh("_"), item] : [item];
1282
- let iterators = form.walks === "iteration" ? [call(this.builtin("unpack"), [source, number(1), number(3)])] : [source];
1283
- if (this.lua51 && form.walks === "values") {
1472
+ let iterators = form.walks === "iteration" ? [call(this.helper("unpack"), [source, number(1), number(3)])] : [source];
1473
+ if (!this.syntax.has("iteration") && form.walks === "values") {
1284
1474
  const kind = form.viaIter ? void 0 : this.options.types?.typeOf.get(node.iterator)?.kind;
1285
1475
  iterators = [call(kind === "array" || kind === "tuple" ? this.builtin("ipairs") : this.helper("each"), [source])];
1286
1476
  }
@@ -1436,8 +1626,8 @@ end`).body.statements);
1436
1626
  classHelpers;
1437
1627
  classRuntime() {
1438
1628
  return this.classHelpers ??= {
1439
- build: this.names.fresh("tilua_class"),
1440
- accessors: this.names.fresh("tilua_accessors")
1629
+ build: this.sharedName("class:build", "tilua_class"),
1630
+ accessors: this.sharedName("class:accessors", "tilua_accessors")
1441
1631
  };
1442
1632
  }
1443
1633
  /** The helpers, as Luau — emitted only in a file that declares a class.
@@ -1455,7 +1645,8 @@ end`).body.statements);
1455
1645
  if (!this.classHelpers) return [];
1456
1646
  const { build, accessors } = this.classHelpers;
1457
1647
  const metamethods = `{ ${CLASS_METAMETHODS.map((name) => `"${name}"`).join(", ")} }`;
1458
- return (0, import_luau_parser.parse)(`
1648
+ const out2 = [];
1649
+ this.define([build, accessors], (0, import_luau_parser.parse)(`
1459
1650
  local function ${build}(base, name)
1460
1651
  local class = { __getters = {}, __setters = {} }
1461
1652
  class.__index = class
@@ -1493,7 +1684,8 @@ local function ${accessors}(class)
1493
1684
  rawset(this, key, value)
1494
1685
  end
1495
1686
  end
1496
- `).body.statements;
1687
+ `).body.statements, out2);
1688
+ return out2;
1497
1689
  }
1498
1690
  /** The class being lowered, so `super` knows what to name. */
1499
1691
  classContext;
@@ -1521,6 +1713,7 @@ end
1521
1713
  string(called)
1522
1714
  ]);
1523
1715
  const out2 = [declare !== void 0 ? local([declare], [create]) : assign([self], [create])];
1716
+ const statics = [];
1524
1717
  const previous = this.classContext;
1525
1718
  this.classContext = node;
1526
1719
  try {
@@ -1528,7 +1721,7 @@ end
1528
1721
  switch (member2.type) {
1529
1722
  case "ClassField":
1530
1723
  if (member2.isStatic) {
1531
- out2.push(assign(
1724
+ statics.push(assign(
1532
1725
  [member(self, member2.name.name)],
1533
1726
  [member2.init ? this.expression(member2.init) : nil()]
1534
1727
  ));
@@ -1559,6 +1752,7 @@ end
1559
1752
  out2.push(this.classInit(node, self));
1560
1753
  if (!node.isAbstract) out2.push(this.classNew(node, self));
1561
1754
  out2.push(callStatement(call(identifier(accessors), [self])));
1755
+ out2.push(...statics);
1562
1756
  } finally {
1563
1757
  this.classContext = previous;
1564
1758
  }
@@ -1635,7 +1829,7 @@ end
1635
1829
  isMethod,
1636
1830
  func,
1637
1831
  // `@native` and the like are Luau's.
1638
- attributes: this.lua51 ? void 0 : node.attributes,
1832
+ attributes: this.syntax.has("attributes") ? node.attributes : void 0,
1639
1833
  ...spanOf(node)
1640
1834
  };
1641
1835
  }
@@ -1875,7 +2069,7 @@ end
1875
2069
  if (join2 === "concat") return binary("..", left, right);
1876
2070
  if (join2 === "dynamic") return call(this.helper("add"), [this.keptToOne(left), this.keptToOne(right)]);
1877
2071
  }
1878
- if (this.lua51 && node.operator === "//") return this.floorDivide(left, right);
2072
+ if (!this.syntax.has("floorDivision") && node.operator === "//") return this.floorDivide(left, right);
1879
2073
  return binary(node.operator, left, right);
1880
2074
  }
1881
2075
  case "UnaryExpression":
@@ -1909,7 +2103,7 @@ end
1909
2103
  case "AsConstExpression":
1910
2104
  return this.expression(node.expression);
1911
2105
  case "IfElseExpression":
1912
- if (this.lua51) return this.ifElse51(node);
2106
+ if (!this.syntax.has("ifExpression")) return this.ifElse51(node);
1913
2107
  return {
1914
2108
  type: "IfElseExpression",
1915
2109
  clauses: node.clauses.map((c) => ({ condition: this.expression(c.condition), body: this.expression(c.body) })),
@@ -1956,7 +2150,7 @@ end
1956
2150
  const last2 = list.length - 1;
1957
2151
  return lowered.length && !many.has(list[last2]) ? [...lowered.slice(0, -1), this.keptToOne(lowered[last2])] : lowered;
1958
2152
  }
1959
- const unpack = (value) => call(this.lua51 ? this.builtin("unpack") : member(this.builtin("table"), "unpack"), [value]);
2153
+ const unpack = (value) => call(this.helper("unpack"), [value]);
1960
2154
  const last = list[list.length - 1];
1961
2155
  if (spreads.length === 1 && last.type === "SpreadElement") {
1962
2156
  return [...list.slice(0, -1).map((a) => this.expression(a)), unpack(this.expression(last.argument))];
@@ -2070,7 +2264,7 @@ end
2070
2264
  const segments = this.segments(chain);
2071
2265
  const base = this.expression(chain.base);
2072
2266
  const last = chain.links[chain.links.length - 1];
2073
- if (!this.lua51 && base.type === "Identifier" && segments.length === 1 && last.type !== "CallExpression" && last.type !== "MethodCallExpression") {
2267
+ if (this.syntax.has("ifExpression") && base.type === "Identifier" && segments.length === 1 && last.type !== "CallExpression" && last.type !== "MethodCallExpression") {
2074
2268
  return {
2075
2269
  type: "IfElseExpression",
2076
2270
  clauses: [{ condition: binary("==", base, nil()), body: nil() }],
@@ -2097,7 +2291,7 @@ end
2097
2291
  nullish(node) {
2098
2292
  const left = this.keptToOne(this.expression(node.left));
2099
2293
  const right = this.keptToOne(this.expression(node.right));
2100
- if (!this.lua51 && left.type === "Identifier") {
2294
+ if (this.syntax.has("ifExpression") && left.type === "Identifier") {
2101
2295
  return {
2102
2296
  type: "IfElseExpression",
2103
2297
  clauses: [{ condition: binary("~=", left, nil()), body: left }],
@@ -2184,14 +2378,19 @@ end
2184
2378
  let target;
2185
2379
  if (node.target.type === "MemberExpression" || node.target.type === "IndexExpression") {
2186
2380
  const object2 = this.expression(node.target.object);
2187
- const objectName = this.names.fresh("target");
2188
- before.push(local([objectName], [object2]));
2189
- if (node.target.type === "MemberExpression") {
2190
- target = member(identifier(objectName), node.target.property.name);
2381
+ const key = node.target.type === "IndexExpression" ? node.target.index : void 0;
2382
+ if (object2.type === "Identifier" && (key === void 0 || key.type === "StringLiteral" || key.type === "NumberLiteral")) {
2383
+ target = key === void 0 ? member(object2, node.target.property.name) : index(object2, this.expression(key));
2191
2384
  } else {
2192
- const keyName = this.names.fresh("key");
2193
- before.push(local([keyName], [this.expression(node.target.index)]));
2194
- target = index(identifier(objectName), identifier(keyName));
2385
+ const objectName = this.names.fresh("target");
2386
+ before.push(local([objectName], [object2]));
2387
+ if (node.target.type === "MemberExpression") {
2388
+ target = member(identifier(objectName), node.target.property.name);
2389
+ } else {
2390
+ const keyName = this.names.fresh("key");
2391
+ before.push(local([keyName], [this.expression(node.target.index)]));
2392
+ target = index(identifier(objectName), identifier(keyName));
2393
+ }
2195
2394
  }
2196
2395
  } else {
2197
2396
  target = this.expression(node.target);
@@ -2212,7 +2411,7 @@ end
2212
2411
  * done, and breaks for real there. A body with no `continue` is left
2213
2412
  * exactly as it was. */
2214
2413
  loopBody51(body) {
2215
- if (!this.lua51 || !hasOwnJump(body.statements, "ContinueStatement")) return body;
2414
+ if (this.syntax.has("continue") || !hasOwnJump(body.statements, "ContinueStatement")) return body;
2216
2415
  const flag = hasOwnJump(body.statements, "BreakStatement") ? this.names.fresh("broke") : void 0;
2217
2416
  const once = repeatUntil(rewriteOwnJumps(body.statements, flag), boolean(true));
2218
2417
  if (!flag) return block([once]);
@@ -2264,7 +2463,7 @@ end
2264
2463
  * underscores and `0b1010` becomes the decimal it means. A hex literal
2265
2464
  * keeps its base — 5.1 reads those. */
2266
2465
  numberRaw(node) {
2267
- if (!this.lua51) return node.raw;
2466
+ if (this.syntax.has("numberLiterals")) return node.raw;
2268
2467
  if (/^0[bB]/.test(node.raw)) return String(node.value);
2269
2468
  return node.raw.replace(/_/g, "");
2270
2469
  }
@@ -2517,10 +2716,10 @@ function declaredNames(statements) {
2517
2716
  }
2518
2717
  function mentions(expression, name) {
2519
2718
  let found = false;
2520
- const walk = (value) => {
2719
+ const walk2 = (value) => {
2521
2720
  if (found || !value || typeof value !== "object") return;
2522
2721
  if (Array.isArray(value)) {
2523
- for (const item of value) walk(item);
2722
+ for (const item of value) walk2(item);
2524
2723
  return;
2525
2724
  }
2526
2725
  const node = value;
@@ -2529,10 +2728,10 @@ function mentions(expression, name) {
2529
2728
  return;
2530
2729
  }
2531
2730
  for (const [key, child] of Object.entries(value)) {
2532
- if (key !== "line" && key !== "column") walk(child);
2731
+ if (key !== "line" && key !== "column") walk2(child);
2533
2732
  }
2534
2733
  };
2535
- walk(expression);
2734
+ walk2(expression);
2536
2735
  return found;
2537
2736
  }
2538
2737
  function allNodes(root) {
@@ -2568,6 +2767,269 @@ var CLASS_METAMETHODS = [
2568
2767
  "__iter"
2569
2768
  ];
2570
2769
 
2770
+ // src/treeshake.ts
2771
+ function walk(node, visit, parent) {
2772
+ if (!node || typeof node !== "object") return;
2773
+ if (Array.isArray(node)) {
2774
+ for (const item of node) walk(item, visit, parent);
2775
+ return;
2776
+ }
2777
+ const n = node;
2778
+ if (typeof n.type === "string" && visit(n, parent) === false) return;
2779
+ for (const [key, child] of Object.entries(n)) {
2780
+ if (key === "line" || key === "column") continue;
2781
+ walk(child, visit, typeof n.type === "string" ? n : parent);
2782
+ }
2783
+ }
2784
+ function pure(expression) {
2785
+ let ok = true;
2786
+ walk(expression, (node) => {
2787
+ if (!ok) return false;
2788
+ switch (node.type) {
2789
+ case "CallExpression":
2790
+ case "MethodCallExpression":
2791
+ case "ImportExpression":
2792
+ case "SuperExpression":
2793
+ ok = false;
2794
+ return false;
2795
+ case "FunctionExpression":
2796
+ case "FunctionBody":
2797
+ case "ClassMethod":
2798
+ case "ClassAccessor":
2799
+ case "ClassConstructor":
2800
+ return false;
2801
+ case "ClassField":
2802
+ if (!node.isStatic) return false;
2803
+ }
2804
+ });
2805
+ return ok;
2806
+ }
2807
+ function declarationOf(statement) {
2808
+ return statement.type === "ExportStatement" ? statement.declaration : statement;
2809
+ }
2810
+ function removable(statement) {
2811
+ const declaration = declarationOf(statement);
2812
+ switch (declaration.type) {
2813
+ case "FunctionDeclaration":
2814
+ return true;
2815
+ case "ClassDeclaration":
2816
+ return pure(declaration);
2817
+ case "VariableDeclaration":
2818
+ return declaration.init === void 0 || pure(declaration.init);
2819
+ case "ExportDefaultStatement":
2820
+ return pure(declaration.declaration);
2821
+ default:
2822
+ return false;
2823
+ }
2824
+ }
2825
+ function structural(statement) {
2826
+ switch (statement.type) {
2827
+ case "TypeAliasStatement":
2828
+ case "ExportTypeAliasStatement":
2829
+ case "DeclareStatement":
2830
+ case "DeclareClassStatement":
2831
+ case "DeclareMetatableStatement":
2832
+ case "ImportStatement":
2833
+ case "ExportNamedStatement":
2834
+ case "ExportAllStatement":
2835
+ return true;
2836
+ default:
2837
+ return false;
2838
+ }
2839
+ }
2840
+ function exportedNames(statement) {
2841
+ switch (statement.type) {
2842
+ case "ExportStatement": {
2843
+ const declaration = statement.declaration;
2844
+ if (declaration.type === "FunctionDeclaration" || declaration.type === "ClassDeclaration") return [declaration.name.name];
2845
+ const names = [];
2846
+ walk(declaration.name, (node) => {
2847
+ if (node.type === "IdentifierPattern") names.push(node.name);
2848
+ });
2849
+ return names;
2850
+ }
2851
+ case "ExportDefaultStatement":
2852
+ return ["default"];
2853
+ default:
2854
+ return [];
2855
+ }
2856
+ }
2857
+ function factsOf(module2) {
2858
+ const tops = module2.program.body.statements;
2859
+ const byNode = /* @__PURE__ */ new Map();
2860
+ for (const binding of module2.scopes.bindings.values()) {
2861
+ if (binding.declarationNode) byNode.set(binding.declarationNode, binding.id);
2862
+ }
2863
+ const declaredBy = /* @__PURE__ */ new Map();
2864
+ const imported = /* @__PURE__ */ new Map();
2865
+ for (const statement of tops) {
2866
+ const declaration = declarationOf(statement);
2867
+ const declare = (node) => {
2868
+ const id = node && byNode.get(node);
2869
+ if (id !== void 0) declaredBy.set(id, statement);
2870
+ };
2871
+ if (declaration.type === "FunctionDeclaration" || declaration.type === "ClassDeclaration") declare(declaration.name);
2872
+ else if (declaration.type === "VariableDeclaration") {
2873
+ walk(declaration.name, (node) => {
2874
+ if (node.type === "IdentifierPattern") declare(node);
2875
+ });
2876
+ } else if (declaration.type === "ImportStatement" && !declaration.isTypeOnly) {
2877
+ const target = module2.resolve(declaration.source.value);
2878
+ if (target === void 0) continue;
2879
+ const add = (node, name) => {
2880
+ const id = node && byNode.get(node);
2881
+ if (id !== void 0) imported.set(id, { target, name });
2882
+ };
2883
+ if (declaration.defaultImport) add(declaration.defaultImport, "default");
2884
+ if (declaration.namespaceImport) add(declaration.namespaceImport, "*");
2885
+ for (const s of declaration.specifiers) add(s.local, s.imported.name);
2886
+ }
2887
+ }
2888
+ const parents = /* @__PURE__ */ new WeakMap();
2889
+ walk(module2.program.body, (node, parent) => {
2890
+ if (parent) parents.set(node, parent);
2891
+ });
2892
+ const sideEffects = tops.some((s) => !structural(s) && !removable(s));
2893
+ return { module: module2, tops, declaredBy, imported, parents, sideEffects };
2894
+ }
2895
+ function treeShake(modules, entry2) {
2896
+ const facts = new Map(modules.map((m) => [m.file, factsOf(m)]));
2897
+ const used = /* @__PURE__ */ new Map();
2898
+ const live = /* @__PURE__ */ new Map();
2899
+ const required = /* @__PURE__ */ new Set();
2900
+ let changed = true;
2901
+ const require2 = (file) => {
2902
+ if (required.has(file) || !facts.has(file)) return;
2903
+ required.add(file);
2904
+ changed = true;
2905
+ };
2906
+ const read3 = (file, name) => {
2907
+ if (!facts.has(file)) return;
2908
+ require2(file);
2909
+ const now = used.get(file);
2910
+ if (now === "all") return;
2911
+ if (name === "*") {
2912
+ used.set(file, "all");
2913
+ changed = true;
2914
+ return;
2915
+ }
2916
+ const names = now ?? /* @__PURE__ */ new Set();
2917
+ if (!names.has(name)) {
2918
+ names.add(name);
2919
+ used.set(file, names);
2920
+ changed = true;
2921
+ }
2922
+ };
2923
+ const isRead = (file, name) => {
2924
+ const now = used.get(file);
2925
+ return now === "all" || (now?.has(name) ?? false);
2926
+ };
2927
+ require2(entry2);
2928
+ used.set(entry2, "all");
2929
+ while (changed) {
2930
+ changed = false;
2931
+ for (const file of [...required]) {
2932
+ const f = facts.get(file);
2933
+ const kept = live.get(file) ?? /* @__PURE__ */ new Set();
2934
+ live.set(file, kept);
2935
+ const queue = [];
2936
+ const keep = (statement) => {
2937
+ if (!statement || kept.has(statement)) return;
2938
+ kept.add(statement);
2939
+ queue.push(statement);
2940
+ changed = true;
2941
+ };
2942
+ for (const statement of f.tops) {
2943
+ if (!structural(statement) && !removable(statement)) keep(statement);
2944
+ if (exportedNames(statement).some((name) => isRead(file, name))) keep(statement);
2945
+ const declaration = declarationOf(statement);
2946
+ if (declaration.type === "ImportStatement" && !declaration.isTypeOnly && !declaration.defaultImport && !declaration.namespaceImport && !declaration.specifiers.length) {
2947
+ const target = f.module.resolve(declaration.source.value);
2948
+ if (target !== void 0) require2(target);
2949
+ }
2950
+ if (declaration.type === "ExportNamedStatement" && !declaration.typeOnly) {
2951
+ const target = declaration.source ? f.module.resolve(declaration.source.value) : void 0;
2952
+ for (const s of declaration.specifiers) {
2953
+ if (!isRead(file, s.exported.name)) continue;
2954
+ if (declaration.source) {
2955
+ if (target !== void 0) read3(target, s.local.name);
2956
+ continue;
2957
+ }
2958
+ const id = f.module.scopes.bindingOf.get(s.local);
2959
+ const own = [...f.declaredBy].find(([bid, st]) => (id !== void 0 ? bid === id : false) || exportedLocal(st, s.local.name));
2960
+ if (own) keep(own[1]);
2961
+ const from = id !== void 0 ? f.imported.get(id) : void 0;
2962
+ if (from) read3(from.target, from.name);
2963
+ }
2964
+ }
2965
+ if (declaration.type === "ExportAllStatement") {
2966
+ const target = f.module.resolve(declaration.source.value);
2967
+ if (target === void 0) continue;
2968
+ const mine = used.get(file);
2969
+ if (mine === "all") read3(target, "*");
2970
+ else for (const name of mine ?? []) if (!ownExport(f, name)) read3(target, name);
2971
+ }
2972
+ }
2973
+ for (const statement of [...kept]) queue.push(statement);
2974
+ while (queue.length) {
2975
+ const statement = queue.pop();
2976
+ walk(statement, (node) => {
2977
+ if (node.type === "ImportExpression") {
2978
+ const source = node.source;
2979
+ const target = source?.type === "StringLiteral" ? f.module.resolve(source.value) : void 0;
2980
+ if (target !== void 0) read3(target, "*");
2981
+ return;
2982
+ }
2983
+ if (node.type !== "Identifier") return;
2984
+ const id = f.module.scopes.bindingOf.get(node);
2985
+ if (id === void 0) return;
2986
+ const declaring = f.declaredBy.get(id);
2987
+ if (declaring && declaring !== statement) keep(declaring);
2988
+ const from = f.imported.get(id);
2989
+ if (!from) return;
2990
+ if (from.name !== "*") return read3(from.target, from.name);
2991
+ const parent = f.parents.get(node);
2992
+ if (parent?.type === "MemberExpression" && parent.object === node) read3(from.target, parent.property.name);
2993
+ else read3(from.target, "*");
2994
+ });
2995
+ }
2996
+ for (const statement of f.tops) {
2997
+ if (statement.type !== "ImportStatement" || statement.isTypeOnly) continue;
2998
+ const target = f.module.resolve(statement.source.value);
2999
+ if (target !== void 0 && facts.get(target)?.sideEffects) require2(target);
3000
+ }
3001
+ }
3002
+ }
3003
+ const shaken = /* @__PURE__ */ new Map();
3004
+ for (const [file, f] of facts) {
3005
+ const kept = live.get(file) ?? /* @__PURE__ */ new Set();
3006
+ const removed = new Set(f.tops.filter((s) => !kept.has(s) && removable(s)));
3007
+ const dead = /* @__PURE__ */ new Set();
3008
+ for (const statement of removed) walk(statement, (node) => {
3009
+ if (node.type === "Identifier") dead.add(node);
3010
+ });
3011
+ shaken.set(file, { removed, dead });
3012
+ }
3013
+ return {
3014
+ modules: shaken,
3015
+ sideEffects: new Set([...facts].filter(([, f]) => f.sideEffects).map(([file]) => file)),
3016
+ required
3017
+ };
3018
+ }
3019
+ function exportedLocal(statement, name) {
3020
+ const declaration = declarationOf(statement);
3021
+ if (declaration.type === "FunctionDeclaration" || declaration.type === "ClassDeclaration") return declaration.name.name === name;
3022
+ if (declaration.type !== "VariableDeclaration") return false;
3023
+ let found = false;
3024
+ walk(declaration.name, (node) => {
3025
+ if (node.type === "IdentifierPattern" && node.name === name) found = true;
3026
+ });
3027
+ return found;
3028
+ }
3029
+ function ownExport(f, name) {
3030
+ return f.tops.some((statement) => exportedNames(statement).includes(name) || statement.type === "ExportNamedStatement" && statement.specifiers.some((s) => s.exported.name === name));
3031
+ }
3032
+
2571
3033
  // src/linemap.ts
2572
3034
  var import_parser6 = require("@tilua/parser");
2573
3035
  function statementsOf(node) {
@@ -2658,8 +3120,8 @@ async function bundle(options) {
2658
3120
  sources.set(file, { file, name: nameOf(file), text, program: program3, scopes });
2659
3121
  const dynamic = (0, import_parser7.importExpressions)(program3).flatMap((node) => node.source.type === "StringLiteral" ? [node.source.value] : []);
2660
3122
  for (const specifier of [...importedSpecifiers(program3), ...dynamic]) {
2661
- const target2 = (0, import_parser7.resolveModulePath)(file, specifier, config2);
2662
- if (target2 && !target2.endsWith(".d.tilua")) queue.push(target2);
3123
+ const target = (0, import_parser7.resolveModulePath)(file, specifier, config2);
3124
+ if (target && !target.endsWith(".d.tilua")) queue.push(target);
2663
3125
  }
2664
3126
  }
2665
3127
  const analysis = analyzeModules([...sources.values()], config2);
@@ -2673,9 +3135,16 @@ async function bundle(options) {
2673
3135
  const requireExpression = member(identifier(G), "require");
2674
3136
  const importExpression = member(identifier(G), "import");
2675
3137
  const mode = options.output ?? config2?.output ?? "single";
2676
- const target = options.target ?? config2?.target ?? "luau";
3138
+ const byPath = syntaxOf(lowerings).has("requireByPath");
2677
3139
  const modules = /* @__PURE__ */ new Map();
2678
3140
  const origins = /* @__PURE__ */ new WeakMap();
3141
+ const shared = new SharedRuntime(names);
3142
+ const shaking = options.treeShaking ?? config2?.treeShaking ?? false;
3143
+ const resolveIn = (file) => (specifier) => {
3144
+ const found = (0, import_parser7.resolveModulePath)(file, specifier, config2);
3145
+ return found !== void 0 && sources.has(found) ? found : void 0;
3146
+ };
3147
+ const shaken = shaking ? treeShake([...sources.values()].map((s) => ({ file: s.file, program: s.program, scopes: s.scopes, resolve: resolveIn(s.file) })), entry2) : void 0;
2679
3148
  const pending = [entry2];
2680
3149
  while (pending.length) {
2681
3150
  const file = pending.shift();
@@ -2688,10 +3157,21 @@ async function bundle(options) {
2688
3157
  names,
2689
3158
  types: analysis.types.get(file),
2690
3159
  lowerings,
2691
- target,
2692
3160
  noLib: config2?.noLib,
2693
3161
  source: source.text,
2694
3162
  file: shown,
3163
+ shared,
3164
+ shaken: shaken && {
3165
+ ...shaken.modules.get(file),
3166
+ runs: (specifier) => {
3167
+ const target = resolveIn(file)(specifier);
3168
+ return target !== void 0 && shaken.sideEffects.has(target);
3169
+ },
3170
+ needed: (specifier) => {
3171
+ const target = resolveIn(file)(specifier);
3172
+ return target !== void 0 && shaken.required.has(target);
3173
+ }
3174
+ },
2695
3175
  module: {
2696
3176
  name: source.name,
2697
3177
  require: requireExpression,
@@ -2722,6 +3202,7 @@ async function bundle(options) {
2722
3202
  exportsName: lowered2.exportsName,
2723
3203
  info: lowered2.module,
2724
3204
  usesScriptArgs: lowered2.usesScriptArgs,
3205
+ shared: lowered2.shared,
2725
3206
  imports,
2726
3207
  dynamicImports
2727
3208
  });
@@ -2747,16 +3228,17 @@ async function bundle(options) {
2747
3228
  if (diagnostics.some((d) => d.category === "syntax" || d.category === "scope" || d.category === "module")) {
2748
3229
  return { files: [], modules: moduleNames, diagnostics };
2749
3230
  }
3231
+ if (shaking) shared.shake(lowered.flatMap((m) => m.statements));
2750
3232
  const several = mode !== "single";
2751
3233
  const layout = layoutOf(mode, modules.get(entry2), lowered, options.outFile);
2752
3234
  const usesScriptArgs = lowered.some((m) => m.usesScriptArgs);
2753
3235
  const elsewhere = /* @__PURE__ */ new Map();
2754
3236
  for (const file of layout.slice(1)) {
2755
- for (const module2 of file.modules) elsewhere.set(module2.name, loadPath(layout[0].path, file.path, target));
3237
+ for (const module2 of file.modules) elsewhere.set(module2.name, loadPath(layout[0].path, file.path, byPath));
2756
3238
  }
2757
3239
  const program2 = (0, import_luau_parser2.parse)(runtime(G, {
2758
3240
  several,
2759
- target,
3241
+ byPath,
2760
3242
  files: elsewhere,
2761
3243
  imports: lowered.some((m) => m.dynamicImports.size > 0)
2762
3244
  }));
@@ -2771,6 +3253,8 @@ async function bundle(options) {
2771
3253
  for (const module2 of layout[0].modules) {
2772
3254
  modulesTable.fields.push({ type: "TableFieldComputed", key: string(module2.name), value: moduleTable(module2) });
2773
3255
  }
3256
+ const helpers = shared.chunk(new Set(layout[0].modules.flatMap((m) => m.shared)));
3257
+ program2.body.statements.unshift(...helpers.declare);
2774
3258
  const ok = names.fresh("ok");
2775
3259
  const result2 = names.fresh("result");
2776
3260
  const entryStatements = (0, import_luau_parser2.parse)(`
@@ -2792,9 +3276,11 @@ ${modules.get(entry2).info.exports ? `return ${result2}` : ""}
2792
3276
  if (several) mapStatements.push(...mapRegistration(G, layout[0], `${G}.lines`));
2793
3277
  program2.body.statements.splice(program2.body.statements.length - entryStatements.length, 0, ...mapStatements);
2794
3278
  }
3279
+ program2.body.statements.splice(program2.body.statements.length - entryStatements.length, 0, ...helpers.define);
2795
3280
  const files = [{ path: layout[0].path, code: (0, import_luau_parser2.print)(program2) + "\n", modules: layout[0].modules.map((m) => m.name) }];
2796
3281
  for (const file of layout.slice(1)) {
2797
- const body = [];
3282
+ const fileHelpers = shared.chunk(new Set(file.modules.flatMap((m) => m.shared)));
3283
+ const body = [...fileHelpers.declare];
2798
3284
  if (file.modules.some((m) => m.usesScriptArgs)) body.push(...(0, import_luau_parser2.parse)(`local scriptArgs = ${G}.scriptArgs`).body.statements);
2799
3285
  for (const module2 of file.modules) {
2800
3286
  body.push(assign(
@@ -2804,7 +3290,7 @@ ${modules.get(entry2).info.exports ? `return ${result2}` : ""}
2804
3290
  }
2805
3291
  const chunk = program([returns([functionExpression(functionBody([G], body))])]);
2806
3292
  const lines = buildLineMap(chunk, (0, import_luau_parser2.parse)((0, import_luau_parser2.print)(chunk)), origins);
2807
- body.push(...mapRegistration(G, file, lineMapSource(lines)));
3293
+ body.push(...mapRegistration(G, file, lineMapSource(lines)), ...fileHelpers.define);
2808
3294
  files.push({ path: file.path, code: (0, import_luau_parser2.print)(chunk) + "\n", modules: file.modules.map((m) => m.name) });
2809
3295
  }
2810
3296
  return { code: files[0].code, files, modules: moduleNames, diagnostics };
@@ -2866,9 +3352,9 @@ function layoutOf(mode, entry2, modules, outFile) {
2866
3352
  }
2867
3353
  return files;
2868
3354
  }
2869
- function loadPath(entryPath, file, target) {
3355
+ function loadPath(entryPath, file, byPath) {
2870
3356
  const path = import_node_path2.posix.relative(import_node_path2.posix.dirname(entryPath), file);
2871
- if (target === "lua51") return path;
3357
+ if (!byPath) return path;
2872
3358
  const bare = path.slice(0, path.length - import_node_path2.posix.extname(path).length);
2873
3359
  return bare.startsWith("../") ? bare : `./${bare}`;
2874
3360
  }
@@ -2881,8 +3367,8 @@ function mapRegistration(G, file, map) {
2881
3367
  ...file.modules.map((module2) => `${G}.maps[${(0, import_parser7.luauString)(module2.name)}] = ${G}.sources[#${G}.sources][2]`)
2882
3368
  ].join("\n")).body.statements;
2883
3369
  }
2884
- function loader(G, target) {
2885
- if (target === "lua51") {
3370
+ function loader(G, byPath) {
3371
+ if (!byPath) {
2886
3372
  return `
2887
3373
  -- From beside this file, wherever that is: its own path says where.
2888
3374
  local folder = ""
@@ -3090,7 +3576,7 @@ ${several ? `
3090
3576
  if ${G}.loaded[file] then
3091
3577
  return
3092
3578
  end
3093
- ${G}.loaded[file] = true${loader(G, options.target)}
3579
+ ${G}.loaded[file] = true${loader(G, options.byPath)}
3094
3580
  end,` : ""}${options.imports ? `
3095
3581
  -- \`import("./x")\`: the module's exports, asked for where the code runs
3096
3582
  -- rather than before its module does. \`require\` does the rest \u2014 and
@@ -3325,13 +3811,12 @@ function allStatements(statements) {
3325
3811
  }
3326
3812
 
3327
3813
  // src/cli.ts
3328
- var usage = "usage: tilua <entry> [--out <file>] [--output single|chunks|modules] [--config <tilua.config.json>] [--noCheck] [--target luau|lua51]";
3814
+ var usage = "usage: tilua <entry> [--out <file>] [--output single|chunks|modules] [--config <tilua.config.json>] [--noCheck]";
3329
3815
  var args = process.argv.slice(2);
3330
3816
  var entry;
3331
3817
  var out;
3332
3818
  var config;
3333
3819
  var typeCheck = true;
3334
- var buildTarget;
3335
3820
  var output;
3336
3821
  for (let i = 0; i < args.length; i++) {
3337
3822
  const arg = args[i];
@@ -3339,12 +3824,8 @@ for (let i = 0; i < args.length; i++) {
3339
3824
  else if (arg === "--config") config = args[++i];
3340
3825
  else if (arg === "--noCheck") typeCheck = false;
3341
3826
  else if (arg === "--target") {
3342
- const value = args[++i];
3343
- if (value !== "luau" && value !== "lua51") {
3344
- console.error(`--target must be 'luau' or 'lua51', not '${value}'`);
3345
- process.exit(2);
3346
- }
3347
- buildTarget = value;
3827
+ console.error(`--target is gone: the output runs on Lua 5.1 to 5.4, LuaJIT and Luau, and a library says what more its Lua has \u2014 add "luau" to the config's 'types'`);
3828
+ process.exit(2);
3348
3829
  } else if (arg === "--output") {
3349
3830
  const value = args[++i];
3350
3831
  if (value !== "single" && value !== "chunks" && value !== "modules") {
@@ -3370,7 +3851,6 @@ async function main(from) {
3370
3851
  entry: from,
3371
3852
  config,
3372
3853
  typeCheck,
3373
- target: buildTarget,
3374
3854
  output: mode,
3375
3855
  outFile: entryFile !== void 0 ? (0, import_node_path3.basename)(entryFile) : void 0
3376
3856
  });