@tilua/compiler 6.1.0 → 7.0.1

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;
@@ -1637,7 +1829,7 @@ end
1637
1829
  isMethod,
1638
1830
  func,
1639
1831
  // `@native` and the like are Luau's.
1640
- attributes: this.lua51 ? void 0 : node.attributes,
1832
+ attributes: this.syntax.has("attributes") ? node.attributes : void 0,
1641
1833
  ...spanOf(node)
1642
1834
  };
1643
1835
  }
@@ -1877,7 +2069,7 @@ end
1877
2069
  if (join2 === "concat") return binary("..", left, right);
1878
2070
  if (join2 === "dynamic") return call(this.helper("add"), [this.keptToOne(left), this.keptToOne(right)]);
1879
2071
  }
1880
- if (this.lua51 && node.operator === "//") return this.floorDivide(left, right);
2072
+ if (!this.syntax.has("floorDivision") && node.operator === "//") return this.floorDivide(left, right);
1881
2073
  return binary(node.operator, left, right);
1882
2074
  }
1883
2075
  case "UnaryExpression":
@@ -1911,7 +2103,7 @@ end
1911
2103
  case "AsConstExpression":
1912
2104
  return this.expression(node.expression);
1913
2105
  case "IfElseExpression":
1914
- if (this.lua51) return this.ifElse51(node);
2106
+ if (!this.syntax.has("ifExpression")) return this.ifElse51(node);
1915
2107
  return {
1916
2108
  type: "IfElseExpression",
1917
2109
  clauses: node.clauses.map((c) => ({ condition: this.expression(c.condition), body: this.expression(c.body) })),
@@ -1958,7 +2150,7 @@ end
1958
2150
  const last2 = list.length - 1;
1959
2151
  return lowered.length && !many.has(list[last2]) ? [...lowered.slice(0, -1), this.keptToOne(lowered[last2])] : lowered;
1960
2152
  }
1961
- const unpack = (value) => call(this.lua51 ? this.builtin("unpack") : member(this.builtin("table"), "unpack"), [value]);
2153
+ const unpack = (value) => call(this.helper("unpack"), [value]);
1962
2154
  const last = list[list.length - 1];
1963
2155
  if (spreads.length === 1 && last.type === "SpreadElement") {
1964
2156
  return [...list.slice(0, -1).map((a) => this.expression(a)), unpack(this.expression(last.argument))];
@@ -2072,7 +2264,7 @@ end
2072
2264
  const segments = this.segments(chain);
2073
2265
  const base = this.expression(chain.base);
2074
2266
  const last = chain.links[chain.links.length - 1];
2075
- 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") {
2076
2268
  return {
2077
2269
  type: "IfElseExpression",
2078
2270
  clauses: [{ condition: binary("==", base, nil()), body: nil() }],
@@ -2099,7 +2291,7 @@ end
2099
2291
  nullish(node) {
2100
2292
  const left = this.keptToOne(this.expression(node.left));
2101
2293
  const right = this.keptToOne(this.expression(node.right));
2102
- if (!this.lua51 && left.type === "Identifier") {
2294
+ if (this.syntax.has("ifExpression") && left.type === "Identifier") {
2103
2295
  return {
2104
2296
  type: "IfElseExpression",
2105
2297
  clauses: [{ condition: binary("~=", left, nil()), body: left }],
@@ -2186,14 +2378,19 @@ end
2186
2378
  let target;
2187
2379
  if (node.target.type === "MemberExpression" || node.target.type === "IndexExpression") {
2188
2380
  const object2 = this.expression(node.target.object);
2189
- const objectName = this.names.fresh("target");
2190
- before.push(local([objectName], [object2]));
2191
- if (node.target.type === "MemberExpression") {
2192
- 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));
2193
2384
  } else {
2194
- const keyName = this.names.fresh("key");
2195
- before.push(local([keyName], [this.expression(node.target.index)]));
2196
- 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
+ }
2197
2394
  }
2198
2395
  } else {
2199
2396
  target = this.expression(node.target);
@@ -2214,7 +2411,7 @@ end
2214
2411
  * done, and breaks for real there. A body with no `continue` is left
2215
2412
  * exactly as it was. */
2216
2413
  loopBody51(body) {
2217
- if (!this.lua51 || !hasOwnJump(body.statements, "ContinueStatement")) return body;
2414
+ if (this.syntax.has("continue") || !hasOwnJump(body.statements, "ContinueStatement")) return body;
2218
2415
  const flag = hasOwnJump(body.statements, "BreakStatement") ? this.names.fresh("broke") : void 0;
2219
2416
  const once = repeatUntil(rewriteOwnJumps(body.statements, flag), boolean(true));
2220
2417
  if (!flag) return block([once]);
@@ -2266,7 +2463,7 @@ end
2266
2463
  * underscores and `0b1010` becomes the decimal it means. A hex literal
2267
2464
  * keeps its base — 5.1 reads those. */
2268
2465
  numberRaw(node) {
2269
- if (!this.lua51) return node.raw;
2466
+ if (this.syntax.has("numberLiterals")) return node.raw;
2270
2467
  if (/^0[bB]/.test(node.raw)) return String(node.value);
2271
2468
  return node.raw.replace(/_/g, "");
2272
2469
  }
@@ -2390,6 +2587,7 @@ function usesVararg(node) {
2390
2587
  return Object.values(record).some(usesVararg);
2391
2588
  }
2392
2589
  function calleePath(path) {
2590
+ if (!/^[A-Za-z_]\w*(\.[A-Za-z_]\w*)*$/.test(path)) return (0, import_luau_parser.parseExpressionFromSource)(path);
2393
2591
  const [head, ...rest] = path.split(".");
2394
2592
  return rest.reduce((value, name) => member(value, name), identifier(head));
2395
2593
  }
@@ -2519,10 +2717,10 @@ function declaredNames(statements) {
2519
2717
  }
2520
2718
  function mentions(expression, name) {
2521
2719
  let found = false;
2522
- const walk = (value) => {
2720
+ const walk2 = (value) => {
2523
2721
  if (found || !value || typeof value !== "object") return;
2524
2722
  if (Array.isArray(value)) {
2525
- for (const item of value) walk(item);
2723
+ for (const item of value) walk2(item);
2526
2724
  return;
2527
2725
  }
2528
2726
  const node = value;
@@ -2531,10 +2729,10 @@ function mentions(expression, name) {
2531
2729
  return;
2532
2730
  }
2533
2731
  for (const [key, child] of Object.entries(value)) {
2534
- if (key !== "line" && key !== "column") walk(child);
2732
+ if (key !== "line" && key !== "column") walk2(child);
2535
2733
  }
2536
2734
  };
2537
- walk(expression);
2735
+ walk2(expression);
2538
2736
  return found;
2539
2737
  }
2540
2738
  function allNodes(root) {
@@ -2570,6 +2768,269 @@ var CLASS_METAMETHODS = [
2570
2768
  "__iter"
2571
2769
  ];
2572
2770
 
2771
+ // src/treeshake.ts
2772
+ function walk(node, visit, parent) {
2773
+ if (!node || typeof node !== "object") return;
2774
+ if (Array.isArray(node)) {
2775
+ for (const item of node) walk(item, visit, parent);
2776
+ return;
2777
+ }
2778
+ const n = node;
2779
+ if (typeof n.type === "string" && visit(n, parent) === false) return;
2780
+ for (const [key, child] of Object.entries(n)) {
2781
+ if (key === "line" || key === "column") continue;
2782
+ walk(child, visit, typeof n.type === "string" ? n : parent);
2783
+ }
2784
+ }
2785
+ function pure(expression) {
2786
+ let ok = true;
2787
+ walk(expression, (node) => {
2788
+ if (!ok) return false;
2789
+ switch (node.type) {
2790
+ case "CallExpression":
2791
+ case "MethodCallExpression":
2792
+ case "ImportExpression":
2793
+ case "SuperExpression":
2794
+ ok = false;
2795
+ return false;
2796
+ case "FunctionExpression":
2797
+ case "FunctionBody":
2798
+ case "ClassMethod":
2799
+ case "ClassAccessor":
2800
+ case "ClassConstructor":
2801
+ return false;
2802
+ case "ClassField":
2803
+ if (!node.isStatic) return false;
2804
+ }
2805
+ });
2806
+ return ok;
2807
+ }
2808
+ function declarationOf(statement) {
2809
+ return statement.type === "ExportStatement" ? statement.declaration : statement;
2810
+ }
2811
+ function removable(statement) {
2812
+ const declaration = declarationOf(statement);
2813
+ switch (declaration.type) {
2814
+ case "FunctionDeclaration":
2815
+ return true;
2816
+ case "ClassDeclaration":
2817
+ return pure(declaration);
2818
+ case "VariableDeclaration":
2819
+ return declaration.init === void 0 || pure(declaration.init);
2820
+ case "ExportDefaultStatement":
2821
+ return pure(declaration.declaration);
2822
+ default:
2823
+ return false;
2824
+ }
2825
+ }
2826
+ function structural(statement) {
2827
+ switch (statement.type) {
2828
+ case "TypeAliasStatement":
2829
+ case "ExportTypeAliasStatement":
2830
+ case "DeclareStatement":
2831
+ case "DeclareClassStatement":
2832
+ case "DeclareMetatableStatement":
2833
+ case "ImportStatement":
2834
+ case "ExportNamedStatement":
2835
+ case "ExportAllStatement":
2836
+ return true;
2837
+ default:
2838
+ return false;
2839
+ }
2840
+ }
2841
+ function exportedNames(statement) {
2842
+ switch (statement.type) {
2843
+ case "ExportStatement": {
2844
+ const declaration = statement.declaration;
2845
+ if (declaration.type === "FunctionDeclaration" || declaration.type === "ClassDeclaration") return [declaration.name.name];
2846
+ const names = [];
2847
+ walk(declaration.name, (node) => {
2848
+ if (node.type === "IdentifierPattern") names.push(node.name);
2849
+ });
2850
+ return names;
2851
+ }
2852
+ case "ExportDefaultStatement":
2853
+ return ["default"];
2854
+ default:
2855
+ return [];
2856
+ }
2857
+ }
2858
+ function factsOf(module2) {
2859
+ const tops = module2.program.body.statements;
2860
+ const byNode = /* @__PURE__ */ new Map();
2861
+ for (const binding of module2.scopes.bindings.values()) {
2862
+ if (binding.declarationNode) byNode.set(binding.declarationNode, binding.id);
2863
+ }
2864
+ const declaredBy = /* @__PURE__ */ new Map();
2865
+ const imported = /* @__PURE__ */ new Map();
2866
+ for (const statement of tops) {
2867
+ const declaration = declarationOf(statement);
2868
+ const declare = (node) => {
2869
+ const id = node && byNode.get(node);
2870
+ if (id !== void 0) declaredBy.set(id, statement);
2871
+ };
2872
+ if (declaration.type === "FunctionDeclaration" || declaration.type === "ClassDeclaration") declare(declaration.name);
2873
+ else if (declaration.type === "VariableDeclaration") {
2874
+ walk(declaration.name, (node) => {
2875
+ if (node.type === "IdentifierPattern") declare(node);
2876
+ });
2877
+ } else if (declaration.type === "ImportStatement" && !declaration.isTypeOnly) {
2878
+ const target = module2.resolve(declaration.source.value);
2879
+ if (target === void 0) continue;
2880
+ const add = (node, name) => {
2881
+ const id = node && byNode.get(node);
2882
+ if (id !== void 0) imported.set(id, { target, name });
2883
+ };
2884
+ if (declaration.defaultImport) add(declaration.defaultImport, "default");
2885
+ if (declaration.namespaceImport) add(declaration.namespaceImport, "*");
2886
+ for (const s of declaration.specifiers) add(s.local, s.imported.name);
2887
+ }
2888
+ }
2889
+ const parents = /* @__PURE__ */ new WeakMap();
2890
+ walk(module2.program.body, (node, parent) => {
2891
+ if (parent) parents.set(node, parent);
2892
+ });
2893
+ const sideEffects = tops.some((s) => !structural(s) && !removable(s));
2894
+ return { module: module2, tops, declaredBy, imported, parents, sideEffects };
2895
+ }
2896
+ function treeShake(modules, entry2) {
2897
+ const facts = new Map(modules.map((m) => [m.file, factsOf(m)]));
2898
+ const used = /* @__PURE__ */ new Map();
2899
+ const live = /* @__PURE__ */ new Map();
2900
+ const required = /* @__PURE__ */ new Set();
2901
+ let changed = true;
2902
+ const require2 = (file) => {
2903
+ if (required.has(file) || !facts.has(file)) return;
2904
+ required.add(file);
2905
+ changed = true;
2906
+ };
2907
+ const read3 = (file, name) => {
2908
+ if (!facts.has(file)) return;
2909
+ require2(file);
2910
+ const now = used.get(file);
2911
+ if (now === "all") return;
2912
+ if (name === "*") {
2913
+ used.set(file, "all");
2914
+ changed = true;
2915
+ return;
2916
+ }
2917
+ const names = now ?? /* @__PURE__ */ new Set();
2918
+ if (!names.has(name)) {
2919
+ names.add(name);
2920
+ used.set(file, names);
2921
+ changed = true;
2922
+ }
2923
+ };
2924
+ const isRead = (file, name) => {
2925
+ const now = used.get(file);
2926
+ return now === "all" || (now?.has(name) ?? false);
2927
+ };
2928
+ require2(entry2);
2929
+ used.set(entry2, "all");
2930
+ while (changed) {
2931
+ changed = false;
2932
+ for (const file of [...required]) {
2933
+ const f = facts.get(file);
2934
+ const kept = live.get(file) ?? /* @__PURE__ */ new Set();
2935
+ live.set(file, kept);
2936
+ const queue = [];
2937
+ const keep = (statement) => {
2938
+ if (!statement || kept.has(statement)) return;
2939
+ kept.add(statement);
2940
+ queue.push(statement);
2941
+ changed = true;
2942
+ };
2943
+ for (const statement of f.tops) {
2944
+ if (!structural(statement) && !removable(statement)) keep(statement);
2945
+ if (exportedNames(statement).some((name) => isRead(file, name))) keep(statement);
2946
+ const declaration = declarationOf(statement);
2947
+ if (declaration.type === "ImportStatement" && !declaration.isTypeOnly && !declaration.defaultImport && !declaration.namespaceImport && !declaration.specifiers.length) {
2948
+ const target = f.module.resolve(declaration.source.value);
2949
+ if (target !== void 0) require2(target);
2950
+ }
2951
+ if (declaration.type === "ExportNamedStatement" && !declaration.typeOnly) {
2952
+ const target = declaration.source ? f.module.resolve(declaration.source.value) : void 0;
2953
+ for (const s of declaration.specifiers) {
2954
+ if (!isRead(file, s.exported.name)) continue;
2955
+ if (declaration.source) {
2956
+ if (target !== void 0) read3(target, s.local.name);
2957
+ continue;
2958
+ }
2959
+ const id = f.module.scopes.bindingOf.get(s.local);
2960
+ const own = [...f.declaredBy].find(([bid, st]) => (id !== void 0 ? bid === id : false) || exportedLocal(st, s.local.name));
2961
+ if (own) keep(own[1]);
2962
+ const from = id !== void 0 ? f.imported.get(id) : void 0;
2963
+ if (from) read3(from.target, from.name);
2964
+ }
2965
+ }
2966
+ if (declaration.type === "ExportAllStatement") {
2967
+ const target = f.module.resolve(declaration.source.value);
2968
+ if (target === void 0) continue;
2969
+ const mine = used.get(file);
2970
+ if (mine === "all") read3(target, "*");
2971
+ else for (const name of mine ?? []) if (!ownExport(f, name)) read3(target, name);
2972
+ }
2973
+ }
2974
+ for (const statement of [...kept]) queue.push(statement);
2975
+ while (queue.length) {
2976
+ const statement = queue.pop();
2977
+ walk(statement, (node) => {
2978
+ if (node.type === "ImportExpression") {
2979
+ const source = node.source;
2980
+ const target = source?.type === "StringLiteral" ? f.module.resolve(source.value) : void 0;
2981
+ if (target !== void 0) read3(target, "*");
2982
+ return;
2983
+ }
2984
+ if (node.type !== "Identifier") return;
2985
+ const id = f.module.scopes.bindingOf.get(node);
2986
+ if (id === void 0) return;
2987
+ const declaring = f.declaredBy.get(id);
2988
+ if (declaring && declaring !== statement) keep(declaring);
2989
+ const from = f.imported.get(id);
2990
+ if (!from) return;
2991
+ if (from.name !== "*") return read3(from.target, from.name);
2992
+ const parent = f.parents.get(node);
2993
+ if (parent?.type === "MemberExpression" && parent.object === node) read3(from.target, parent.property.name);
2994
+ else read3(from.target, "*");
2995
+ });
2996
+ }
2997
+ for (const statement of f.tops) {
2998
+ if (statement.type !== "ImportStatement" || statement.isTypeOnly) continue;
2999
+ const target = f.module.resolve(statement.source.value);
3000
+ if (target !== void 0 && facts.get(target)?.sideEffects) require2(target);
3001
+ }
3002
+ }
3003
+ }
3004
+ const shaken = /* @__PURE__ */ new Map();
3005
+ for (const [file, f] of facts) {
3006
+ const kept = live.get(file) ?? /* @__PURE__ */ new Set();
3007
+ const removed = new Set(f.tops.filter((s) => !kept.has(s) && removable(s)));
3008
+ const dead = /* @__PURE__ */ new Set();
3009
+ for (const statement of removed) walk(statement, (node) => {
3010
+ if (node.type === "Identifier") dead.add(node);
3011
+ });
3012
+ shaken.set(file, { removed, dead });
3013
+ }
3014
+ return {
3015
+ modules: shaken,
3016
+ sideEffects: new Set([...facts].filter(([, f]) => f.sideEffects).map(([file]) => file)),
3017
+ required
3018
+ };
3019
+ }
3020
+ function exportedLocal(statement, name) {
3021
+ const declaration = declarationOf(statement);
3022
+ if (declaration.type === "FunctionDeclaration" || declaration.type === "ClassDeclaration") return declaration.name.name === name;
3023
+ if (declaration.type !== "VariableDeclaration") return false;
3024
+ let found = false;
3025
+ walk(declaration.name, (node) => {
3026
+ if (node.type === "IdentifierPattern" && node.name === name) found = true;
3027
+ });
3028
+ return found;
3029
+ }
3030
+ function ownExport(f, name) {
3031
+ return f.tops.some((statement) => exportedNames(statement).includes(name) || statement.type === "ExportNamedStatement" && statement.specifiers.some((s) => s.exported.name === name));
3032
+ }
3033
+
2573
3034
  // src/linemap.ts
2574
3035
  var import_parser6 = require("@tilua/parser");
2575
3036
  function statementsOf(node) {
@@ -2660,8 +3121,8 @@ async function bundle(options) {
2660
3121
  sources.set(file, { file, name: nameOf(file), text, program: program3, scopes });
2661
3122
  const dynamic = (0, import_parser7.importExpressions)(program3).flatMap((node) => node.source.type === "StringLiteral" ? [node.source.value] : []);
2662
3123
  for (const specifier of [...importedSpecifiers(program3), ...dynamic]) {
2663
- const target2 = (0, import_parser7.resolveModulePath)(file, specifier, config2);
2664
- if (target2 && !target2.endsWith(".d.tilua")) queue.push(target2);
3124
+ const target = (0, import_parser7.resolveModulePath)(file, specifier, config2);
3125
+ if (target && !target.endsWith(".d.tilua")) queue.push(target);
2665
3126
  }
2666
3127
  }
2667
3128
  const analysis = analyzeModules([...sources.values()], config2);
@@ -2675,9 +3136,16 @@ async function bundle(options) {
2675
3136
  const requireExpression = member(identifier(G), "require");
2676
3137
  const importExpression = member(identifier(G), "import");
2677
3138
  const mode = options.output ?? config2?.output ?? "single";
2678
- const target = options.target ?? config2?.target ?? "luau";
3139
+ const byPath = syntaxOf(lowerings).has("requireByPath");
2679
3140
  const modules = /* @__PURE__ */ new Map();
2680
3141
  const origins = /* @__PURE__ */ new WeakMap();
3142
+ const shared = new SharedRuntime(names);
3143
+ const shaking = options.treeShaking ?? config2?.treeShaking ?? false;
3144
+ const resolveIn = (file) => (specifier) => {
3145
+ const found = (0, import_parser7.resolveModulePath)(file, specifier, config2);
3146
+ return found !== void 0 && sources.has(found) ? found : void 0;
3147
+ };
3148
+ const shaken = shaking ? treeShake([...sources.values()].map((s) => ({ file: s.file, program: s.program, scopes: s.scopes, resolve: resolveIn(s.file) })), entry2) : void 0;
2681
3149
  const pending = [entry2];
2682
3150
  while (pending.length) {
2683
3151
  const file = pending.shift();
@@ -2690,10 +3158,21 @@ async function bundle(options) {
2690
3158
  names,
2691
3159
  types: analysis.types.get(file),
2692
3160
  lowerings,
2693
- target,
2694
3161
  noLib: config2?.noLib,
2695
3162
  source: source.text,
2696
3163
  file: shown,
3164
+ shared,
3165
+ shaken: shaken && {
3166
+ ...shaken.modules.get(file),
3167
+ runs: (specifier) => {
3168
+ const target = resolveIn(file)(specifier);
3169
+ return target !== void 0 && shaken.sideEffects.has(target);
3170
+ },
3171
+ needed: (specifier) => {
3172
+ const target = resolveIn(file)(specifier);
3173
+ return target !== void 0 && shaken.required.has(target);
3174
+ }
3175
+ },
2697
3176
  module: {
2698
3177
  name: source.name,
2699
3178
  require: requireExpression,
@@ -2724,6 +3203,7 @@ async function bundle(options) {
2724
3203
  exportsName: lowered2.exportsName,
2725
3204
  info: lowered2.module,
2726
3205
  usesScriptArgs: lowered2.usesScriptArgs,
3206
+ shared: lowered2.shared,
2727
3207
  imports,
2728
3208
  dynamicImports
2729
3209
  });
@@ -2749,16 +3229,17 @@ async function bundle(options) {
2749
3229
  if (diagnostics.some((d) => d.category === "syntax" || d.category === "scope" || d.category === "module")) {
2750
3230
  return { files: [], modules: moduleNames, diagnostics };
2751
3231
  }
3232
+ if (shaking) shared.shake(lowered.flatMap((m) => m.statements));
2752
3233
  const several = mode !== "single";
2753
3234
  const layout = layoutOf(mode, modules.get(entry2), lowered, options.outFile);
2754
3235
  const usesScriptArgs = lowered.some((m) => m.usesScriptArgs);
2755
3236
  const elsewhere = /* @__PURE__ */ new Map();
2756
3237
  for (const file of layout.slice(1)) {
2757
- for (const module2 of file.modules) elsewhere.set(module2.name, loadPath(layout[0].path, file.path, target));
3238
+ for (const module2 of file.modules) elsewhere.set(module2.name, loadPath(layout[0].path, file.path, byPath));
2758
3239
  }
2759
3240
  const program2 = (0, import_luau_parser2.parse)(runtime(G, {
2760
3241
  several,
2761
- target,
3242
+ byPath,
2762
3243
  files: elsewhere,
2763
3244
  imports: lowered.some((m) => m.dynamicImports.size > 0)
2764
3245
  }));
@@ -2773,6 +3254,8 @@ async function bundle(options) {
2773
3254
  for (const module2 of layout[0].modules) {
2774
3255
  modulesTable.fields.push({ type: "TableFieldComputed", key: string(module2.name), value: moduleTable(module2) });
2775
3256
  }
3257
+ const helpers = shared.chunk(new Set(layout[0].modules.flatMap((m) => m.shared)));
3258
+ program2.body.statements.unshift(...helpers.declare);
2776
3259
  const ok = names.fresh("ok");
2777
3260
  const result2 = names.fresh("result");
2778
3261
  const entryStatements = (0, import_luau_parser2.parse)(`
@@ -2794,9 +3277,11 @@ ${modules.get(entry2).info.exports ? `return ${result2}` : ""}
2794
3277
  if (several) mapStatements.push(...mapRegistration(G, layout[0], `${G}.lines`));
2795
3278
  program2.body.statements.splice(program2.body.statements.length - entryStatements.length, 0, ...mapStatements);
2796
3279
  }
3280
+ program2.body.statements.splice(program2.body.statements.length - entryStatements.length, 0, ...helpers.define);
2797
3281
  const files = [{ path: layout[0].path, code: (0, import_luau_parser2.print)(program2) + "\n", modules: layout[0].modules.map((m) => m.name) }];
2798
3282
  for (const file of layout.slice(1)) {
2799
- const body = [];
3283
+ const fileHelpers = shared.chunk(new Set(file.modules.flatMap((m) => m.shared)));
3284
+ const body = [...fileHelpers.declare];
2800
3285
  if (file.modules.some((m) => m.usesScriptArgs)) body.push(...(0, import_luau_parser2.parse)(`local scriptArgs = ${G}.scriptArgs`).body.statements);
2801
3286
  for (const module2 of file.modules) {
2802
3287
  body.push(assign(
@@ -2806,7 +3291,7 @@ ${modules.get(entry2).info.exports ? `return ${result2}` : ""}
2806
3291
  }
2807
3292
  const chunk = program([returns([functionExpression(functionBody([G], body))])]);
2808
3293
  const lines = buildLineMap(chunk, (0, import_luau_parser2.parse)((0, import_luau_parser2.print)(chunk)), origins);
2809
- body.push(...mapRegistration(G, file, lineMapSource(lines)));
3294
+ body.push(...mapRegistration(G, file, lineMapSource(lines)), ...fileHelpers.define);
2810
3295
  files.push({ path: file.path, code: (0, import_luau_parser2.print)(chunk) + "\n", modules: file.modules.map((m) => m.name) });
2811
3296
  }
2812
3297
  return { code: files[0].code, files, modules: moduleNames, diagnostics };
@@ -2868,9 +3353,9 @@ function layoutOf(mode, entry2, modules, outFile) {
2868
3353
  }
2869
3354
  return files;
2870
3355
  }
2871
- function loadPath(entryPath, file, target) {
3356
+ function loadPath(entryPath, file, byPath) {
2872
3357
  const path = import_node_path2.posix.relative(import_node_path2.posix.dirname(entryPath), file);
2873
- if (target === "lua51") return path;
3358
+ if (!byPath) return path;
2874
3359
  const bare = path.slice(0, path.length - import_node_path2.posix.extname(path).length);
2875
3360
  return bare.startsWith("../") ? bare : `./${bare}`;
2876
3361
  }
@@ -2883,8 +3368,8 @@ function mapRegistration(G, file, map) {
2883
3368
  ...file.modules.map((module2) => `${G}.maps[${(0, import_parser7.luauString)(module2.name)}] = ${G}.sources[#${G}.sources][2]`)
2884
3369
  ].join("\n")).body.statements;
2885
3370
  }
2886
- function loader(G, target) {
2887
- if (target === "lua51") {
3371
+ function loader(G, byPath) {
3372
+ if (!byPath) {
2888
3373
  return `
2889
3374
  -- From beside this file, wherever that is: its own path says where.
2890
3375
  local folder = ""
@@ -3092,7 +3577,7 @@ ${several ? `
3092
3577
  if ${G}.loaded[file] then
3093
3578
  return
3094
3579
  end
3095
- ${G}.loaded[file] = true${loader(G, options.target)}
3580
+ ${G}.loaded[file] = true${loader(G, options.byPath)}
3096
3581
  end,` : ""}${options.imports ? `
3097
3582
  -- \`import("./x")\`: the module's exports, asked for where the code runs
3098
3583
  -- rather than before its module does. \`require\` does the rest \u2014 and
@@ -3327,13 +3812,12 @@ function allStatements(statements) {
3327
3812
  }
3328
3813
 
3329
3814
  // src/cli.ts
3330
- var usage = "usage: tilua <entry> [--out <file>] [--output single|chunks|modules] [--config <tilua.config.json>] [--noCheck] [--target luau|lua51]";
3815
+ var usage = "usage: tilua <entry> [--out <file>] [--output single|chunks|modules] [--config <tilua.config.json>] [--noCheck]";
3331
3816
  var args = process.argv.slice(2);
3332
3817
  var entry;
3333
3818
  var out;
3334
3819
  var config;
3335
3820
  var typeCheck = true;
3336
- var buildTarget;
3337
3821
  var output;
3338
3822
  for (let i = 0; i < args.length; i++) {
3339
3823
  const arg = args[i];
@@ -3341,12 +3825,8 @@ for (let i = 0; i < args.length; i++) {
3341
3825
  else if (arg === "--config") config = args[++i];
3342
3826
  else if (arg === "--noCheck") typeCheck = false;
3343
3827
  else if (arg === "--target") {
3344
- const value = args[++i];
3345
- if (value !== "luau" && value !== "lua51") {
3346
- console.error(`--target must be 'luau' or 'lua51', not '${value}'`);
3347
- process.exit(2);
3348
- }
3349
- buildTarget = value;
3828
+ 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'`);
3829
+ process.exit(2);
3350
3830
  } else if (arg === "--output") {
3351
3831
  const value = args[++i];
3352
3832
  if (value !== "single" && value !== "chunks" && value !== "modules") {
@@ -3372,7 +3852,6 @@ async function main(from) {
3372
3852
  entry: from,
3373
3853
  config,
3374
3854
  typeCheck,
3375
- target: buildTarget,
3376
3855
  output: mode,
3377
3856
  outFile: entryFile !== void 0 ? (0, import_node_path3.basename)(entryFile) : void 0
3378
3857
  });