@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/index.cjs CHANGED
@@ -67,7 +67,7 @@ var index = (object2, key) => ({ type: "IndexExpression", object: object2, index
67
67
  var call = (callee, args) => ({ type: "CallExpression", callee, arguments: args, ...span() });
68
68
  var methodCall = (object2, method, args) => ({ type: "MethodCallExpression", object: object2, method: identifier(method), arguments: args, ...span() });
69
69
  var functionExpression = (func) => ({ type: "FunctionExpression", func, ...span() });
70
- var binary = (operator, left, right) => ({ type: "BinaryExpression", operator, left, right, ...span() });
70
+ var binary = (operator, left, right) => ({ type: "BinaryExpression", operator, left: operator === "^" && left.type === "UnaryExpression" ? parenthesized(left) : left, right, ...span() });
71
71
  var unary = (operator, argument) => ({ type: "UnaryExpression", operator, argument, ...span() });
72
72
  var parenthesized = (expression) => ({ type: "ParenthesizedExpression", expression, ...span() });
73
73
  var table = (fields) => ({ type: "TableExpression", fields, ...span() });
@@ -126,19 +126,19 @@ var Names = class _Names {
126
126
  * a name shared across modules (the module table) collides with none. */
127
127
  static from(...programs) {
128
128
  const taken = /* @__PURE__ */ new Set(["select", "pairs", "table", "tostring", "setmetatable", "error"]);
129
- const walk = (value) => {
129
+ const walk2 = (value) => {
130
130
  if (!value || typeof value !== "object") return;
131
131
  if (Array.isArray(value)) {
132
- for (const item of value) walk(item);
132
+ for (const item of value) walk2(item);
133
133
  return;
134
134
  }
135
135
  const node = value;
136
136
  if (typeof node.name === "string" && node.name) taken.add(node.name);
137
137
  for (const [key, child] of Object.entries(value)) {
138
- if (key !== "line" && key !== "column") walk(child);
138
+ if (key !== "line" && key !== "column") walk2(child);
139
139
  }
140
140
  };
141
- for (const program2 of programs) walk(program2);
141
+ for (const program2 of programs) walk2(program2);
142
142
  return new _Names(taken);
143
143
  }
144
144
  /** A copy that goes on to take names independently — one per module, each
@@ -146,6 +146,10 @@ var Names = class _Names {
146
146
  fork() {
147
147
  return new _Names(new Set(this.taken));
148
148
  }
149
+ /** `name`, chosen elsewhere, is not to be chosen again here. */
150
+ take(name) {
151
+ this.taken.add(name);
152
+ }
149
153
  /** `base`, or `base2`, `base3`, ... — whichever is free first. */
150
154
  fresh(base) {
151
155
  let name = base;
@@ -163,6 +167,7 @@ var implemented = (source) => new Set([...source.matchAll(/function __NAME__\.(\
163
167
  var array = read("array.luau");
164
168
  var string2 = read("string.luau");
165
169
  var object = read("object.luau");
170
+ var json = read("json.luau");
166
171
  var arrayMethods = implemented(array);
167
172
  var stringMethods = implemented(string2);
168
173
  var objectMethods = implemented(object);
@@ -195,7 +200,7 @@ function objectKeys(type) {
195
200
  return { known, open: String(open) };
196
201
  }
197
202
  var plugin = {
198
- runtime: { array, string: string2, object },
203
+ runtime: { array, string: string2, object, json },
199
204
  methodCall({ method, receiver, use }) {
200
205
  if (isString(receiver)) {
201
206
  return stringMethods.has(method) ? { callee: `${use("string")}.${method}` } : void 0;
@@ -284,6 +289,141 @@ var plugin2 = {
284
289
  var standardLowering = { plugin: plugin2, from: "tilua (standard library)" };
285
290
 
286
291
  // src/lower.ts
292
+ function syntaxOf(lowerings) {
293
+ return new Set((lowerings ?? []).flatMap((l) => l.plugin.syntax ?? []));
294
+ }
295
+ var SharedRuntime = class {
296
+ constructor(names) {
297
+ this.names = names;
298
+ }
299
+ names;
300
+ byKey = /* @__PURE__ */ new Map();
301
+ /** Each group of names and the statements that declare them as locals,
302
+ * in the order they were first defined: each after what it uses. */
303
+ definitions = [];
304
+ defined = /* @__PURE__ */ new Set();
305
+ /** The name the helper `key` goes by, in every module. */
306
+ name(key, base) {
307
+ let name = this.byKey.get(key);
308
+ if (!name) {
309
+ name = this.names.fresh(base);
310
+ this.byKey.set(key, name);
311
+ }
312
+ return name;
313
+ }
314
+ /** `statements` declare `names`. Only the first module to say so is heard:
315
+ * the others' are the same. */
316
+ define(names, statements) {
317
+ if (names.every((name) => this.defined.has(name))) return;
318
+ for (const name of names) this.defined.add(name);
319
+ this.definitions.push({ names, statements });
320
+ }
321
+ /** Tree shaking: leave out of each runtime table the members nothing
322
+ * reads. `code` is everything else the output holds — the modules —
323
+ * and a member is read when it, or a member read already, names it:
324
+ * `tilua_array.filter(xs, f)` keeps `filter`, and whatever `filter`
325
+ * itself calls. A table used as anything but `table.member` — handed
326
+ * on, walked, indexed by what only the running program knows — keeps
327
+ * every member. */
328
+ shake(code) {
329
+ const names = new Set(this.definitions.flatMap((group) => group.names));
330
+ const read3 = /* @__PURE__ */ new Map();
331
+ const use = (name, key) => {
332
+ const now = read3.get(name);
333
+ if (now === "all") return;
334
+ if (key === void 0) {
335
+ read3.set(name, "all");
336
+ return;
337
+ }
338
+ read3.set(name, (now ?? /* @__PURE__ */ new Set()).add(key));
339
+ };
340
+ const isRead = (name, key) => {
341
+ const now = read3.get(name);
342
+ return now === "all" || (now?.has(key) ?? false);
343
+ };
344
+ const scan = (value) => {
345
+ if (!value || typeof value !== "object") return;
346
+ if (Array.isArray(value)) return void value.forEach(scan);
347
+ const node = value;
348
+ const object2 = node.object;
349
+ if (object2?.type === "Identifier" && names.has(object2.name)) {
350
+ if (node.type === "MemberExpression") return use(object2.name, node.property.name);
351
+ if (node.type === "MethodCallExpression") {
352
+ use(object2.name, node.method.name);
353
+ return scan(node.arguments);
354
+ }
355
+ if (node.type === "IndexExpression") {
356
+ const index2 = node.index;
357
+ if (index2.type === "StringLiteral") return use(object2.name, String(index2.value));
358
+ }
359
+ }
360
+ if (node.type === "Identifier" && names.has(node.name)) return use(node.name);
361
+ if (node.type === "LocalFunctionStatement") return scan(node.func);
362
+ for (const [key, child] of Object.entries(node)) {
363
+ if (key !== "line" && key !== "column") scan(child);
364
+ }
365
+ };
366
+ const defines = (statement, own) => {
367
+ if (statement.type === "FunctionDeclarationStatement") {
368
+ const target = statement.target;
369
+ if (own.includes(target.base.name) && target.path.length === 1 && !target.method) {
370
+ return { name: target.base.name, key: target.path[0].name, value: statement.func };
371
+ }
372
+ }
373
+ if (statement.type === "AssignmentStatement" && statement.targets.length === 1 && statement.values.length === 1) {
374
+ const target = statement.targets[0];
375
+ if (target.object?.type === "Identifier" && own.includes(target.object.name)) {
376
+ if (target.type === "MemberExpression") return { name: target.object.name, key: target.property.name, value: statement.values[0] };
377
+ if (target.type === "IndexExpression" && target.index?.type === "StringLiteral") {
378
+ return { name: target.object.name, key: String(target.index.value), value: statement.values[0] };
379
+ }
380
+ }
381
+ }
382
+ return void 0;
383
+ };
384
+ scan(code);
385
+ const scanned = /* @__PURE__ */ new Set();
386
+ let more = true;
387
+ while (more) {
388
+ more = false;
389
+ for (const group of this.definitions) {
390
+ for (const statement of group.statements) {
391
+ if (scanned.has(statement)) continue;
392
+ const definition = defines(statement, group.names);
393
+ if (definition && !isRead(definition.name, definition.key)) continue;
394
+ scanned.add(statement);
395
+ more = true;
396
+ scan(definition ? definition.value : statement);
397
+ }
398
+ }
399
+ }
400
+ for (const group of this.definitions) {
401
+ group.statements = group.statements.filter((statement) => {
402
+ const definition = defines(statement, group.names);
403
+ return !definition || isRead(definition.name, definition.key);
404
+ });
405
+ }
406
+ }
407
+ /** Luau setting up the helpers `used` for a chunk whose code names them:
408
+ * `declare` goes before that code, which reads each as an upvalue, and
409
+ * `define` after it — before any of it runs, and after what a helper
410
+ * reads as it is set up (the line map). Each definition runs in a
411
+ * function of its own, so what it keeps to itself stays there. */
412
+ chunk(used) {
413
+ const groups = this.definitions.filter((group) => group.names.some((name) => used.has(name)));
414
+ const all = groups.flatMap((group) => group.names);
415
+ const declare = [];
416
+ for (let i = 0; i < all.length; i += 100) declare.push(local(all.slice(i, i + 100), []));
417
+ const define = groups.map((group) => assign(
418
+ group.names.map((name) => identifier(name)),
419
+ [call(parenthesized(functionExpression(functionBody([], [
420
+ ...group.statements,
421
+ returns(group.names.map((name) => identifier(name)))
422
+ ]))), [])]
423
+ ));
424
+ return { declare, define };
425
+ }
426
+ };
287
427
  function lower(program2, scopes, options = {}) {
288
428
  const result2 = new Lowerer(program2, scopes, options).run();
289
429
  dropUnreachable(result2.statements);
@@ -314,7 +454,7 @@ var Lowerer = class {
314
454
  this.source = source;
315
455
  this.scopes = scopes;
316
456
  this.options = options;
317
- this.lua51 = options.target === "lua51";
457
+ this.syntax = syntaxOf(options.lowerings);
318
458
  this.names = options.names ? options.names.fork() : Names.from(source);
319
459
  this.exportsName = this.names.fresh("exports");
320
460
  for (const binding of scopes.bindings.values()) {
@@ -339,6 +479,11 @@ var Lowerer = class {
339
479
  names;
340
480
  diagnostics = [];
341
481
  helpers = /* @__PURE__ */ new Map();
482
+ /** The shared helpers this file names. */
483
+ sharedUsed = /* @__PURE__ */ new Set();
484
+ /** While a shared helper is written: it is defined outside every module,
485
+ * where none of a module's own names shadows a global. */
486
+ rawGlobals = false;
342
487
  /** The binding each declaration node creates. */
343
488
  bindingByDeclaration = /* @__PURE__ */ new Map();
344
489
  /** tilua names that are Luau keywords, and what they are called instead. */
@@ -354,8 +499,8 @@ var Lowerer = class {
354
499
  tryFrames = [];
355
500
  /** The loops being lowered in the current function, innermost last. */
356
501
  loops = [];
357
- /** Whether the output has to be plain Lua 5.1 rather than Luau. */
358
- lua51;
502
+ /** What the output's Lua can write beyond Lua 5.1 (`syntaxOf`). */
503
+ syntax;
359
504
  /** The source line each emitted statement came from. */
360
505
  origins = /* @__PURE__ */ new WeakMap();
361
506
  /** The name each class written as a value is known by. */
@@ -363,15 +508,19 @@ var Lowerer = class {
363
508
  run() {
364
509
  const body = this.options.module ? this.module(this.options.module) : this.topLevel(this.source.body.statements);
365
510
  const scriptArgs = this.usesScriptArgs && !this.options.module ? [local(["scriptArgs"], [this.array(table([{ type: "TableFieldPositional", value: vararg() }]))])] : [];
511
+ this.rawGlobals = this.options.shared !== void 0;
366
512
  const helpers = this.helperDefinitions();
513
+ const classes = this.classRuntimeStatements();
514
+ this.rawGlobals = false;
367
515
  const captured = [...this.builtins].map(([global, local2]) => local([local2], [identifier(global)]));
368
516
  return {
369
- statements: [...captured, ...helpers, ...scriptArgs, ...this.classRuntimeStatements(), ...body],
517
+ statements: [...captured, ...helpers, ...scriptArgs, ...classes, ...body],
370
518
  exportsName: this.exportsName,
371
519
  module: this.info,
372
520
  diagnostics: this.diagnostics,
373
521
  origins: this.origins,
374
- usesScriptArgs: this.usesScriptArgs
522
+ usesScriptArgs: this.usesScriptArgs,
523
+ shared: [...this.sharedUsed]
375
524
  };
376
525
  }
377
526
  /** Whether the file reads `scriptArgs`, the language's own global. */
@@ -404,6 +553,7 @@ var Lowerer = class {
404
553
  /** A global the generated code calls — `pairs`, `table`, ... — under a name
405
554
  * the source cannot have shadowed. */
406
555
  builtin(global) {
556
+ if (this.rawGlobals) return identifier(global);
407
557
  let local2 = this.builtins.get(global);
408
558
  if (!local2) {
409
559
  const shadowed = [...this.scopes.bindings.values()].some((b) => b.name === global && b.kind !== "global");
@@ -415,16 +565,31 @@ var Lowerer = class {
415
565
  }
416
566
  /** `table.move`. Lua 5.1 has none, and gets a function that does the same. */
417
567
  move() {
418
- return this.lua51 ? this.helper("move") : member(this.builtin("table"), "move");
568
+ return this.helper("move");
419
569
  }
420
570
  helper(kind) {
421
571
  let name = this.helpers.get(kind);
422
572
  if (!name) {
423
- name = this.names.fresh(`tilua_${kind === "array" ? "asArray" : kind}`);
573
+ name = this.sharedName(`helper:${kind}`, `tilua_${kind === "array" ? "asArray" : kind}`);
424
574
  this.helpers.set(kind, name);
425
575
  }
426
576
  return identifier(name);
427
577
  }
578
+ /** A fresh name for a helper — in a bundle, the one every module calls
579
+ * that helper by. */
580
+ sharedName(key, base) {
581
+ const shared = this.options.shared;
582
+ if (!shared) return this.names.fresh(base);
583
+ const name = shared.name(key, base);
584
+ this.names.take(name);
585
+ this.sharedUsed.add(name);
586
+ return name;
587
+ }
588
+ /** `statements` declare `names`: in this file, or once for the bundle. */
589
+ define(names, statements, out) {
590
+ if (this.options.shared) this.options.shared.define(names, statements);
591
+ else out.push(...statements);
592
+ }
428
593
  // `typeof v` tells an array from an object, which Luau cannot: both are
429
594
  // tables. So every array the program makes — `[a, b]`, a rest parameter,
430
595
  // what `:map` answers — is written down as one, in a table that holds its
@@ -457,7 +622,8 @@ var Lowerer = class {
457
622
  const key = `${index2}:${runtime2}`;
458
623
  let name = this.loweringNames.get(key);
459
624
  if (!name) {
460
- name = this.names.fresh(`tilua_${runtime2.replace(/[^A-Za-z0-9_]/g, "_")}`);
625
+ const lines = this.lowerings[index2].plugin.runtime?.[runtime2]?.includes("__LINES__") ? this.options.module?.lines : "";
626
+ name = this.sharedName(`lowering:${key}:${lines}`, `tilua_${runtime2.replace(/[^A-Za-z0-9_]/g, "_")}`);
461
627
  this.loweringNames.set(key, name);
462
628
  this.loweringUsed.push({ index: index2, runtime: runtime2, name });
463
629
  }
@@ -657,19 +823,25 @@ var Lowerer = class {
657
823
  const runtimes = this.runtimeDefinitions();
658
824
  if (this.helpers.has("concat")) this.helper("array");
659
825
  if (this.helpers.has("concat")) this.move();
826
+ const unpack = this.helpers.get("unpack");
827
+ if (unpack) {
828
+ this.define([unpack], (0, import_luau_parser.parse)(
829
+ `local ${unpack} = ${this.builtin("table").name}.unpack or ${this.builtin("unpack").name}`
830
+ ).body.statements, out);
831
+ }
660
832
  const move = this.helpers.get("move");
661
833
  if (move) {
662
- out.push(...(0, import_luau_parser.parse)(`
663
- local function ${move}(from, first, last, at, to)
834
+ this.define([move], (0, import_luau_parser.parse)(`
835
+ local ${move} = ${this.builtin("table").name}.move or function(from, first, last, at, to)
664
836
  for i = first, last do
665
837
  to[at + i - first] = from[i]
666
838
  end
667
839
  return to
668
- end`).body.statements);
840
+ end`).body.statements, out);
669
841
  }
670
842
  const each = this.helpers.get("each");
671
843
  if (each) {
672
- out.push(...(0, import_luau_parser.parse)(`
844
+ this.define([each], (0, import_luau_parser.parse)(`
673
845
  local function ${each}(source, ...)
674
846
  if ${this.builtin("type").name}(source) == "function" then
675
847
  return source, ...
@@ -679,7 +851,7 @@ local function ${each}(source, ...)
679
851
  return meta.__iter(source)
680
852
  end
681
853
  return ${this.builtin("next").name}, source
682
- end`).body.statements);
854
+ end`).body.statements, out);
683
855
  }
684
856
  const marker = this.helpers.get("array");
685
857
  const typeOf = this.helpers.get("typeof");
@@ -687,19 +859,19 @@ end`).body.statements);
687
859
  const arrays = this.arrays();
688
860
  const own = this.helpers.get("arrays");
689
861
  if (own) {
690
- out.push(...(0, import_luau_parser.parse)(`local ${own} = ${this.builtin("setmetatable").name}({}, { __mode = "k" })`).body.statements);
862
+ this.define([own], (0, import_luau_parser.parse)(`local ${own} = ${this.builtin("setmetatable").name}({}, { __mode = "k" })`).body.statements, out);
691
863
  }
692
864
  if (marker) {
693
- out.push(...(0, import_luau_parser.parse)(`
865
+ this.define([marker], (0, import_luau_parser.parse)(`
694
866
  local function ${marker}(t)
695
867
  ${arrays}[t] = true
696
868
  return t
697
- end`).body.statements);
869
+ end`).body.statements, out);
698
870
  }
699
871
  if (typeOf) {
700
- out.push(...(0, import_luau_parser.parse)(`
872
+ this.define([typeOf], (0, import_luau_parser.parse)(`
701
873
  local function ${typeOf}(value, known)
702
- local name = ${this.builtin(this.lua51 ? "type" : "typeof").name}(value)
874
+ local name = (${this.builtin("typeof").name} or ${this.builtin("type").name})(value)
703
875
  if name ~= "table" then
704
876
  return name
705
877
  end
@@ -716,22 +888,22 @@ local function ${typeOf}(value, known)
716
888
  return "array"
717
889
  end
718
890
  return "object"
719
- end`).body.statements);
891
+ end`).body.statements, out);
720
892
  }
721
893
  }
722
894
  const add = this.helpers.get("add");
723
895
  if (add) {
724
- out.push(...(0, import_luau_parser.parse)(`
896
+ this.define([add], (0, import_luau_parser.parse)(`
725
897
  local function ${add}(a, b)
726
898
  if ${this.builtin("type").name}(a) == "string" or ${this.builtin("type").name}(b) == "string" then
727
899
  return ${this.builtin("tostring").name}(a) .. ${this.builtin("tostring").name}(b)
728
900
  end
729
901
  return a + b
730
- end`).body.statements);
902
+ end`).body.statements, out);
731
903
  }
732
904
  const instanceOf = this.helpers.get("instanceof");
733
905
  if (instanceOf) {
734
- out.push(...(0, import_luau_parser.parse)(`
906
+ this.define([instanceOf], (0, import_luau_parser.parse)(`
735
907
  local function ${instanceOf}(value, class)
736
908
  if ${this.builtin("type").name}(value) ~= "table" then
737
909
  return false
@@ -744,11 +916,11 @@ local function ${instanceOf}(value, class)
744
916
  current = current.ParentClass
745
917
  end
746
918
  return false
747
- end`).body.statements);
919
+ end`).body.statements, out);
748
920
  }
749
921
  const accessors = this.helpers.get("accessors");
750
922
  if (accessors) {
751
- out.push(...(0, import_luau_parser.parse)(`
923
+ this.define([accessors], (0, import_luau_parser.parse)(`
752
924
  local function ${accessors}(target, getters, setters)
753
925
  return ${this.builtin("setmetatable").name}(target, {
754
926
  -- Read by a spread, which copies what the getters give.
@@ -772,11 +944,11 @@ local function ${accessors}(target, getters, setters)
772
944
  ${this.builtin("rawset").name}(self, key, value)
773
945
  end,
774
946
  })
775
- end`).body.statements);
947
+ end`).body.statements, out);
776
948
  }
777
949
  const assign2 = this.helpers.get("assign");
778
950
  if (assign2) {
779
- out.push(...(0, import_luau_parser.parse)(`
951
+ this.define([assign2], (0, import_luau_parser.parse)(`
780
952
  local function ${assign2}(target, ...)
781
953
  for i = 1, ${this.builtin("select").name}("#", ...) do
782
954
  local source = (${this.builtin("select").name}(i, ...))
@@ -794,12 +966,12 @@ local function ${assign2}(target, ...)
794
966
  end
795
967
  end
796
968
  return target
797
- end`).body.statements);
969
+ end`).body.statements, out);
798
970
  }
799
- out.push(...runtimes);
971
+ for (const { names, statements } of runtimes) this.define(names, statements, out);
800
972
  const concat = this.helpers.get("concat");
801
973
  if (concat) {
802
- out.push(localFunction(concat, functionBody([], [
974
+ this.define([concat], [localFunction(concat, functionBody([], [
803
975
  local(["result"], [this.array(table([]))]),
804
976
  numericFor("i", number(1), selectCount(this.builtin("select")), [
805
977
  local(["part"], [call(this.builtin("select"), [identifier("i"), vararg()])]),
@@ -812,7 +984,7 @@ end`).body.statements);
812
984
  ]))
813
985
  ]),
814
986
  returns([identifier("result")])
815
- ], true)));
987
+ ], true))], out);
816
988
  }
817
989
  return out;
818
990
  }
@@ -832,7 +1004,12 @@ end`).body.statements);
832
1004
  }
833
1005
  const lines = this.options.module?.lines ?? "nil";
834
1006
  const fail = this.options.module?.fail ?? "nil";
835
- out.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);
1007
+ const group = {
1008
+ names: [name],
1009
+ 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
1010
+ };
1011
+ if (index2 === 0 && runtime2 === "json") out.unshift(group);
1012
+ else out.push(group);
836
1013
  }
837
1014
  return out;
838
1015
  }
@@ -847,7 +1024,8 @@ end`).body.statements);
847
1024
  * util = require("util") -- the imports
848
1025
  * ... -- the rest, in source order */
849
1026
  module(context) {
850
- const statements = this.source.body.statements;
1027
+ const shaken = this.options.shaken;
1028
+ const statements = shaken ? this.source.body.statements.filter((s) => !shaken.removed.has(s)) : this.source.body.statements;
851
1029
  const exportsTable = () => identifier(this.exportsName);
852
1030
  const { names, links, stars } = this.info;
853
1031
  const locals = [];
@@ -886,13 +1064,24 @@ end`).body.statements);
886
1064
  };
887
1065
  for (const statement of statements) {
888
1066
  if (statement.type !== "ImportStatement" || statement.isTypeOnly) continue;
1067
+ if (!statement.defaultImport && !statement.namespaceImport && !statement.specifiers.length) {
1068
+ const key2 = resolve4(statement.source);
1069
+ if (key2 !== void 0) load(key2);
1070
+ continue;
1071
+ }
889
1072
  const specifiers = [
890
1073
  ...statement.defaultImport ? [{ local: statement.defaultImport, imported: "default" }] : [],
891
1074
  ...statement.namespaceImport ? [{ local: statement.namespaceImport, imported: void 0 }] : [],
892
1075
  ...statement.specifiers.map((s) => ({ local: s.local, imported: s.imported.name }))
893
1076
  ];
894
- const used = specifiers.filter((s) => (this.bindingByDeclaration.get(s.local)?.references.length ?? 0) > 0);
895
- if (!used.length) continue;
1077
+ const used = specifiers.filter((s) => (this.bindingByDeclaration.get(s.local)?.references ?? []).some((reference) => !shaken?.dead.has(reference)));
1078
+ if (!used.length) {
1079
+ if (shaken?.runs(statement.source.value)) {
1080
+ const key2 = resolve4(statement.source);
1081
+ if (key2 !== void 0) load(key2);
1082
+ }
1083
+ continue;
1084
+ }
896
1085
  const key = resolve4(statement.source);
897
1086
  if (key === void 0) continue;
898
1087
  const local2 = moduleLocal(key);
@@ -934,6 +1123,7 @@ end`).body.statements);
934
1123
  break;
935
1124
  }
936
1125
  case "ExportAllStatement": {
1126
+ if (shaken && !shaken.needed(statement.source.value)) break;
937
1127
  const key = resolve4(statement.source);
938
1128
  if (key === void 0) break;
939
1129
  load(key);
@@ -1208,7 +1398,7 @@ end`).body.statements);
1208
1398
  case "VariableDeclaration":
1209
1399
  return this.variableDeclaration(node, "declare");
1210
1400
  case "FunctionDeclaration":
1211
- return [{ ...localFunction(this.name(node.name.name), this.functionBody(node.func)), attributes: this.lua51 ? void 0 : node.attributes }];
1401
+ return [{ ...localFunction(this.name(node.name.name), this.functionBody(node.func)), attributes: this.syntax.has("attributes") ? node.attributes : void 0 }];
1212
1402
  case "FunctionDeclarationStatement":
1213
1403
  return [this.functionDeclarationStatement(node)];
1214
1404
  case "ClassDeclaration":
@@ -1220,7 +1410,7 @@ end`).body.statements);
1220
1410
  const join = node.operator === "+=" ? this.options.types?.joins.get(node) : void 0;
1221
1411
  if (join === "dynamic") return this.dynamicAddition(node);
1222
1412
  const operator = join === "concat" ? "..=" : node.operator;
1223
- if (this.lua51) return this.compoundAssignment51(node, operator);
1413
+ if (!this.syntax.has("compoundAssignment")) return this.compoundAssignment51(node, operator);
1224
1414
  return [{
1225
1415
  type: "CompoundAssignmentStatement",
1226
1416
  operator,
@@ -1258,10 +1448,10 @@ end`).body.statements);
1258
1448
  case "RepeatStatement": {
1259
1449
  const body = this.block(node.body);
1260
1450
  const condition = this.expression(node.condition);
1261
- if (this.lua51 && hasOwnJump(body.statements, "ContinueStatement")) {
1451
+ if (!this.syntax.has("continue") && hasOwnJump(body.statements, "ContinueStatement")) {
1262
1452
  const hidden = declaredNames(body.statements).filter((name) => mentions(condition, name));
1263
1453
  if (hidden.length) {
1264
- 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.`);
1454
+ 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").`);
1265
1455
  }
1266
1456
  }
1267
1457
  return [{ type: "RepeatStatement", body: this.loopBody51(body), condition, ...spanOf(node) }];
@@ -1302,8 +1492,8 @@ end`).body.statements);
1302
1492
  let source = this.expression(node.iterator);
1303
1493
  if (form.viaIter) source = methodCall(source, "__iter", []);
1304
1494
  const variables = form.walks === "values" ? [this.names.fresh("_"), item] : [item];
1305
- let iterators = form.walks === "iteration" ? [call(this.builtin("unpack"), [source, number(1), number(3)])] : [source];
1306
- if (this.lua51 && form.walks === "values") {
1495
+ let iterators = form.walks === "iteration" ? [call(this.helper("unpack"), [source, number(1), number(3)])] : [source];
1496
+ if (!this.syntax.has("iteration") && form.walks === "values") {
1307
1497
  const kind = form.viaIter ? void 0 : this.options.types?.typeOf.get(node.iterator)?.kind;
1308
1498
  iterators = [call(kind === "array" || kind === "tuple" ? this.builtin("ipairs") : this.helper("each"), [source])];
1309
1499
  }
@@ -1459,8 +1649,8 @@ end`).body.statements);
1459
1649
  classHelpers;
1460
1650
  classRuntime() {
1461
1651
  return this.classHelpers ??= {
1462
- build: this.names.fresh("tilua_class"),
1463
- accessors: this.names.fresh("tilua_accessors")
1652
+ build: this.sharedName("class:build", "tilua_class"),
1653
+ accessors: this.sharedName("class:accessors", "tilua_accessors")
1464
1654
  };
1465
1655
  }
1466
1656
  /** The helpers, as Luau — emitted only in a file that declares a class.
@@ -1478,7 +1668,8 @@ end`).body.statements);
1478
1668
  if (!this.classHelpers) return [];
1479
1669
  const { build, accessors } = this.classHelpers;
1480
1670
  const metamethods = `{ ${CLASS_METAMETHODS.map((name) => `"${name}"`).join(", ")} }`;
1481
- return (0, import_luau_parser.parse)(`
1671
+ const out = [];
1672
+ this.define([build, accessors], (0, import_luau_parser.parse)(`
1482
1673
  local function ${build}(base, name)
1483
1674
  local class = { __getters = {}, __setters = {} }
1484
1675
  class.__index = class
@@ -1516,7 +1707,8 @@ local function ${accessors}(class)
1516
1707
  rawset(this, key, value)
1517
1708
  end
1518
1709
  end
1519
- `).body.statements;
1710
+ `).body.statements, out);
1711
+ return out;
1520
1712
  }
1521
1713
  /** The class being lowered, so `super` knows what to name. */
1522
1714
  classContext;
@@ -1544,6 +1736,7 @@ end
1544
1736
  string(called)
1545
1737
  ]);
1546
1738
  const out = [declare !== void 0 ? local([declare], [create]) : assign([self], [create])];
1739
+ const statics = [];
1547
1740
  const previous = this.classContext;
1548
1741
  this.classContext = node;
1549
1742
  try {
@@ -1551,7 +1744,7 @@ end
1551
1744
  switch (member2.type) {
1552
1745
  case "ClassField":
1553
1746
  if (member2.isStatic) {
1554
- out.push(assign(
1747
+ statics.push(assign(
1555
1748
  [member(self, member2.name.name)],
1556
1749
  [member2.init ? this.expression(member2.init) : nil()]
1557
1750
  ));
@@ -1582,6 +1775,7 @@ end
1582
1775
  out.push(this.classInit(node, self));
1583
1776
  if (!node.isAbstract) out.push(this.classNew(node, self));
1584
1777
  out.push(callStatement(call(identifier(accessors), [self])));
1778
+ out.push(...statics);
1585
1779
  } finally {
1586
1780
  this.classContext = previous;
1587
1781
  }
@@ -1658,7 +1852,7 @@ end
1658
1852
  isMethod,
1659
1853
  func,
1660
1854
  // `@native` and the like are Luau's.
1661
- attributes: this.lua51 ? void 0 : node.attributes,
1855
+ attributes: this.syntax.has("attributes") ? node.attributes : void 0,
1662
1856
  ...spanOf(node)
1663
1857
  };
1664
1858
  }
@@ -1898,7 +2092,7 @@ end
1898
2092
  if (join === "concat") return binary("..", left, right);
1899
2093
  if (join === "dynamic") return call(this.helper("add"), [this.keptToOne(left), this.keptToOne(right)]);
1900
2094
  }
1901
- if (this.lua51 && node.operator === "//") return this.floorDivide(left, right);
2095
+ if (!this.syntax.has("floorDivision") && node.operator === "//") return this.floorDivide(left, right);
1902
2096
  return binary(node.operator, left, right);
1903
2097
  }
1904
2098
  case "UnaryExpression":
@@ -1932,7 +2126,7 @@ end
1932
2126
  case "AsConstExpression":
1933
2127
  return this.expression(node.expression);
1934
2128
  case "IfElseExpression":
1935
- if (this.lua51) return this.ifElse51(node);
2129
+ if (!this.syntax.has("ifExpression")) return this.ifElse51(node);
1936
2130
  return {
1937
2131
  type: "IfElseExpression",
1938
2132
  clauses: node.clauses.map((c) => ({ condition: this.expression(c.condition), body: this.expression(c.body) })),
@@ -1979,7 +2173,7 @@ end
1979
2173
  const last2 = list.length - 1;
1980
2174
  return lowered.length && !many.has(list[last2]) ? [...lowered.slice(0, -1), this.keptToOne(lowered[last2])] : lowered;
1981
2175
  }
1982
- const unpack = (value) => call(this.lua51 ? this.builtin("unpack") : member(this.builtin("table"), "unpack"), [value]);
2176
+ const unpack = (value) => call(this.helper("unpack"), [value]);
1983
2177
  const last = list[list.length - 1];
1984
2178
  if (spreads.length === 1 && last.type === "SpreadElement") {
1985
2179
  return [...list.slice(0, -1).map((a) => this.expression(a)), unpack(this.expression(last.argument))];
@@ -2093,7 +2287,7 @@ end
2093
2287
  const segments = this.segments(chain);
2094
2288
  const base = this.expression(chain.base);
2095
2289
  const last = chain.links[chain.links.length - 1];
2096
- if (!this.lua51 && base.type === "Identifier" && segments.length === 1 && last.type !== "CallExpression" && last.type !== "MethodCallExpression") {
2290
+ if (this.syntax.has("ifExpression") && base.type === "Identifier" && segments.length === 1 && last.type !== "CallExpression" && last.type !== "MethodCallExpression") {
2097
2291
  return {
2098
2292
  type: "IfElseExpression",
2099
2293
  clauses: [{ condition: binary("==", base, nil()), body: nil() }],
@@ -2120,7 +2314,7 @@ end
2120
2314
  nullish(node) {
2121
2315
  const left = this.keptToOne(this.expression(node.left));
2122
2316
  const right = this.keptToOne(this.expression(node.right));
2123
- if (!this.lua51 && left.type === "Identifier") {
2317
+ if (this.syntax.has("ifExpression") && left.type === "Identifier") {
2124
2318
  return {
2125
2319
  type: "IfElseExpression",
2126
2320
  clauses: [{ condition: binary("~=", left, nil()), body: left }],
@@ -2207,14 +2401,19 @@ end
2207
2401
  let target;
2208
2402
  if (node.target.type === "MemberExpression" || node.target.type === "IndexExpression") {
2209
2403
  const object2 = this.expression(node.target.object);
2210
- const objectName = this.names.fresh("target");
2211
- before.push(local([objectName], [object2]));
2212
- if (node.target.type === "MemberExpression") {
2213
- target = member(identifier(objectName), node.target.property.name);
2404
+ const key = node.target.type === "IndexExpression" ? node.target.index : void 0;
2405
+ if (object2.type === "Identifier" && (key === void 0 || key.type === "StringLiteral" || key.type === "NumberLiteral")) {
2406
+ target = key === void 0 ? member(object2, node.target.property.name) : index(object2, this.expression(key));
2214
2407
  } else {
2215
- const keyName = this.names.fresh("key");
2216
- before.push(local([keyName], [this.expression(node.target.index)]));
2217
- target = index(identifier(objectName), identifier(keyName));
2408
+ const objectName = this.names.fresh("target");
2409
+ before.push(local([objectName], [object2]));
2410
+ if (node.target.type === "MemberExpression") {
2411
+ target = member(identifier(objectName), node.target.property.name);
2412
+ } else {
2413
+ const keyName = this.names.fresh("key");
2414
+ before.push(local([keyName], [this.expression(node.target.index)]));
2415
+ target = index(identifier(objectName), identifier(keyName));
2416
+ }
2218
2417
  }
2219
2418
  } else {
2220
2419
  target = this.expression(node.target);
@@ -2235,7 +2434,7 @@ end
2235
2434
  * done, and breaks for real there. A body with no `continue` is left
2236
2435
  * exactly as it was. */
2237
2436
  loopBody51(body) {
2238
- if (!this.lua51 || !hasOwnJump(body.statements, "ContinueStatement")) return body;
2437
+ if (this.syntax.has("continue") || !hasOwnJump(body.statements, "ContinueStatement")) return body;
2239
2438
  const flag = hasOwnJump(body.statements, "BreakStatement") ? this.names.fresh("broke") : void 0;
2240
2439
  const once = repeatUntil(rewriteOwnJumps(body.statements, flag), boolean(true));
2241
2440
  if (!flag) return block([once]);
@@ -2287,7 +2486,7 @@ end
2287
2486
  * underscores and `0b1010` becomes the decimal it means. A hex literal
2288
2487
  * keeps its base — 5.1 reads those. */
2289
2488
  numberRaw(node) {
2290
- if (!this.lua51) return node.raw;
2489
+ if (this.syntax.has("numberLiterals")) return node.raw;
2291
2490
  if (/^0[bB]/.test(node.raw)) return String(node.value);
2292
2491
  return node.raw.replace(/_/g, "");
2293
2492
  }
@@ -2540,10 +2739,10 @@ function declaredNames(statements) {
2540
2739
  }
2541
2740
  function mentions(expression, name) {
2542
2741
  let found = false;
2543
- const walk = (value) => {
2742
+ const walk2 = (value) => {
2544
2743
  if (found || !value || typeof value !== "object") return;
2545
2744
  if (Array.isArray(value)) {
2546
- for (const item of value) walk(item);
2745
+ for (const item of value) walk2(item);
2547
2746
  return;
2548
2747
  }
2549
2748
  const node = value;
@@ -2552,10 +2751,10 @@ function mentions(expression, name) {
2552
2751
  return;
2553
2752
  }
2554
2753
  for (const [key, child] of Object.entries(value)) {
2555
- if (key !== "line" && key !== "column") walk(child);
2754
+ if (key !== "line" && key !== "column") walk2(child);
2556
2755
  }
2557
2756
  };
2558
- walk(expression);
2757
+ walk2(expression);
2559
2758
  return found;
2560
2759
  }
2561
2760
  function allNodes(root) {
@@ -2591,6 +2790,269 @@ var CLASS_METAMETHODS = [
2591
2790
  "__iter"
2592
2791
  ];
2593
2792
 
2793
+ // src/treeshake.ts
2794
+ function walk(node, visit, parent) {
2795
+ if (!node || typeof node !== "object") return;
2796
+ if (Array.isArray(node)) {
2797
+ for (const item of node) walk(item, visit, parent);
2798
+ return;
2799
+ }
2800
+ const n = node;
2801
+ if (typeof n.type === "string" && visit(n, parent) === false) return;
2802
+ for (const [key, child] of Object.entries(n)) {
2803
+ if (key === "line" || key === "column") continue;
2804
+ walk(child, visit, typeof n.type === "string" ? n : parent);
2805
+ }
2806
+ }
2807
+ function pure(expression) {
2808
+ let ok = true;
2809
+ walk(expression, (node) => {
2810
+ if (!ok) return false;
2811
+ switch (node.type) {
2812
+ case "CallExpression":
2813
+ case "MethodCallExpression":
2814
+ case "ImportExpression":
2815
+ case "SuperExpression":
2816
+ ok = false;
2817
+ return false;
2818
+ case "FunctionExpression":
2819
+ case "FunctionBody":
2820
+ case "ClassMethod":
2821
+ case "ClassAccessor":
2822
+ case "ClassConstructor":
2823
+ return false;
2824
+ case "ClassField":
2825
+ if (!node.isStatic) return false;
2826
+ }
2827
+ });
2828
+ return ok;
2829
+ }
2830
+ function declarationOf(statement) {
2831
+ return statement.type === "ExportStatement" ? statement.declaration : statement;
2832
+ }
2833
+ function removable(statement) {
2834
+ const declaration = declarationOf(statement);
2835
+ switch (declaration.type) {
2836
+ case "FunctionDeclaration":
2837
+ return true;
2838
+ case "ClassDeclaration":
2839
+ return pure(declaration);
2840
+ case "VariableDeclaration":
2841
+ return declaration.init === void 0 || pure(declaration.init);
2842
+ case "ExportDefaultStatement":
2843
+ return pure(declaration.declaration);
2844
+ default:
2845
+ return false;
2846
+ }
2847
+ }
2848
+ function structural(statement) {
2849
+ switch (statement.type) {
2850
+ case "TypeAliasStatement":
2851
+ case "ExportTypeAliasStatement":
2852
+ case "DeclareStatement":
2853
+ case "DeclareClassStatement":
2854
+ case "DeclareMetatableStatement":
2855
+ case "ImportStatement":
2856
+ case "ExportNamedStatement":
2857
+ case "ExportAllStatement":
2858
+ return true;
2859
+ default:
2860
+ return false;
2861
+ }
2862
+ }
2863
+ function exportedNames(statement) {
2864
+ switch (statement.type) {
2865
+ case "ExportStatement": {
2866
+ const declaration = statement.declaration;
2867
+ if (declaration.type === "FunctionDeclaration" || declaration.type === "ClassDeclaration") return [declaration.name.name];
2868
+ const names = [];
2869
+ walk(declaration.name, (node) => {
2870
+ if (node.type === "IdentifierPattern") names.push(node.name);
2871
+ });
2872
+ return names;
2873
+ }
2874
+ case "ExportDefaultStatement":
2875
+ return ["default"];
2876
+ default:
2877
+ return [];
2878
+ }
2879
+ }
2880
+ function factsOf(module2) {
2881
+ const tops = module2.program.body.statements;
2882
+ const byNode = /* @__PURE__ */ new Map();
2883
+ for (const binding of module2.scopes.bindings.values()) {
2884
+ if (binding.declarationNode) byNode.set(binding.declarationNode, binding.id);
2885
+ }
2886
+ const declaredBy = /* @__PURE__ */ new Map();
2887
+ const imported = /* @__PURE__ */ new Map();
2888
+ for (const statement of tops) {
2889
+ const declaration = declarationOf(statement);
2890
+ const declare = (node) => {
2891
+ const id = node && byNode.get(node);
2892
+ if (id !== void 0) declaredBy.set(id, statement);
2893
+ };
2894
+ if (declaration.type === "FunctionDeclaration" || declaration.type === "ClassDeclaration") declare(declaration.name);
2895
+ else if (declaration.type === "VariableDeclaration") {
2896
+ walk(declaration.name, (node) => {
2897
+ if (node.type === "IdentifierPattern") declare(node);
2898
+ });
2899
+ } else if (declaration.type === "ImportStatement" && !declaration.isTypeOnly) {
2900
+ const target = module2.resolve(declaration.source.value);
2901
+ if (target === void 0) continue;
2902
+ const add = (node, name) => {
2903
+ const id = node && byNode.get(node);
2904
+ if (id !== void 0) imported.set(id, { target, name });
2905
+ };
2906
+ if (declaration.defaultImport) add(declaration.defaultImport, "default");
2907
+ if (declaration.namespaceImport) add(declaration.namespaceImport, "*");
2908
+ for (const s of declaration.specifiers) add(s.local, s.imported.name);
2909
+ }
2910
+ }
2911
+ const parents = /* @__PURE__ */ new WeakMap();
2912
+ walk(module2.program.body, (node, parent) => {
2913
+ if (parent) parents.set(node, parent);
2914
+ });
2915
+ const sideEffects = tops.some((s) => !structural(s) && !removable(s));
2916
+ return { module: module2, tops, declaredBy, imported, parents, sideEffects };
2917
+ }
2918
+ function treeShake(modules, entry) {
2919
+ const facts = new Map(modules.map((m) => [m.file, factsOf(m)]));
2920
+ const used = /* @__PURE__ */ new Map();
2921
+ const live = /* @__PURE__ */ new Map();
2922
+ const required = /* @__PURE__ */ new Set();
2923
+ let changed = true;
2924
+ const require2 = (file) => {
2925
+ if (required.has(file) || !facts.has(file)) return;
2926
+ required.add(file);
2927
+ changed = true;
2928
+ };
2929
+ const read3 = (file, name) => {
2930
+ if (!facts.has(file)) return;
2931
+ require2(file);
2932
+ const now = used.get(file);
2933
+ if (now === "all") return;
2934
+ if (name === "*") {
2935
+ used.set(file, "all");
2936
+ changed = true;
2937
+ return;
2938
+ }
2939
+ const names = now ?? /* @__PURE__ */ new Set();
2940
+ if (!names.has(name)) {
2941
+ names.add(name);
2942
+ used.set(file, names);
2943
+ changed = true;
2944
+ }
2945
+ };
2946
+ const isRead = (file, name) => {
2947
+ const now = used.get(file);
2948
+ return now === "all" || (now?.has(name) ?? false);
2949
+ };
2950
+ require2(entry);
2951
+ used.set(entry, "all");
2952
+ while (changed) {
2953
+ changed = false;
2954
+ for (const file of [...required]) {
2955
+ const f = facts.get(file);
2956
+ const kept = live.get(file) ?? /* @__PURE__ */ new Set();
2957
+ live.set(file, kept);
2958
+ const queue = [];
2959
+ const keep = (statement) => {
2960
+ if (!statement || kept.has(statement)) return;
2961
+ kept.add(statement);
2962
+ queue.push(statement);
2963
+ changed = true;
2964
+ };
2965
+ for (const statement of f.tops) {
2966
+ if (!structural(statement) && !removable(statement)) keep(statement);
2967
+ if (exportedNames(statement).some((name) => isRead(file, name))) keep(statement);
2968
+ const declaration = declarationOf(statement);
2969
+ if (declaration.type === "ImportStatement" && !declaration.isTypeOnly && !declaration.defaultImport && !declaration.namespaceImport && !declaration.specifiers.length) {
2970
+ const target = f.module.resolve(declaration.source.value);
2971
+ if (target !== void 0) require2(target);
2972
+ }
2973
+ if (declaration.type === "ExportNamedStatement" && !declaration.typeOnly) {
2974
+ const target = declaration.source ? f.module.resolve(declaration.source.value) : void 0;
2975
+ for (const s of declaration.specifiers) {
2976
+ if (!isRead(file, s.exported.name)) continue;
2977
+ if (declaration.source) {
2978
+ if (target !== void 0) read3(target, s.local.name);
2979
+ continue;
2980
+ }
2981
+ const id = f.module.scopes.bindingOf.get(s.local);
2982
+ const own = [...f.declaredBy].find(([bid, st]) => (id !== void 0 ? bid === id : false) || exportedLocal(st, s.local.name));
2983
+ if (own) keep(own[1]);
2984
+ const from = id !== void 0 ? f.imported.get(id) : void 0;
2985
+ if (from) read3(from.target, from.name);
2986
+ }
2987
+ }
2988
+ if (declaration.type === "ExportAllStatement") {
2989
+ const target = f.module.resolve(declaration.source.value);
2990
+ if (target === void 0) continue;
2991
+ const mine = used.get(file);
2992
+ if (mine === "all") read3(target, "*");
2993
+ else for (const name of mine ?? []) if (!ownExport(f, name)) read3(target, name);
2994
+ }
2995
+ }
2996
+ for (const statement of [...kept]) queue.push(statement);
2997
+ while (queue.length) {
2998
+ const statement = queue.pop();
2999
+ walk(statement, (node) => {
3000
+ if (node.type === "ImportExpression") {
3001
+ const source = node.source;
3002
+ const target = source?.type === "StringLiteral" ? f.module.resolve(source.value) : void 0;
3003
+ if (target !== void 0) read3(target, "*");
3004
+ return;
3005
+ }
3006
+ if (node.type !== "Identifier") return;
3007
+ const id = f.module.scopes.bindingOf.get(node);
3008
+ if (id === void 0) return;
3009
+ const declaring = f.declaredBy.get(id);
3010
+ if (declaring && declaring !== statement) keep(declaring);
3011
+ const from = f.imported.get(id);
3012
+ if (!from) return;
3013
+ if (from.name !== "*") return read3(from.target, from.name);
3014
+ const parent = f.parents.get(node);
3015
+ if (parent?.type === "MemberExpression" && parent.object === node) read3(from.target, parent.property.name);
3016
+ else read3(from.target, "*");
3017
+ });
3018
+ }
3019
+ for (const statement of f.tops) {
3020
+ if (statement.type !== "ImportStatement" || statement.isTypeOnly) continue;
3021
+ const target = f.module.resolve(statement.source.value);
3022
+ if (target !== void 0 && facts.get(target)?.sideEffects) require2(target);
3023
+ }
3024
+ }
3025
+ }
3026
+ const shaken = /* @__PURE__ */ new Map();
3027
+ for (const [file, f] of facts) {
3028
+ const kept = live.get(file) ?? /* @__PURE__ */ new Set();
3029
+ const removed = new Set(f.tops.filter((s) => !kept.has(s) && removable(s)));
3030
+ const dead = /* @__PURE__ */ new Set();
3031
+ for (const statement of removed) walk(statement, (node) => {
3032
+ if (node.type === "Identifier") dead.add(node);
3033
+ });
3034
+ shaken.set(file, { removed, dead });
3035
+ }
3036
+ return {
3037
+ modules: shaken,
3038
+ sideEffects: new Set([...facts].filter(([, f]) => f.sideEffects).map(([file]) => file)),
3039
+ required
3040
+ };
3041
+ }
3042
+ function exportedLocal(statement, name) {
3043
+ const declaration = declarationOf(statement);
3044
+ if (declaration.type === "FunctionDeclaration" || declaration.type === "ClassDeclaration") return declaration.name.name === name;
3045
+ if (declaration.type !== "VariableDeclaration") return false;
3046
+ let found = false;
3047
+ walk(declaration.name, (node) => {
3048
+ if (node.type === "IdentifierPattern" && node.name === name) found = true;
3049
+ });
3050
+ return found;
3051
+ }
3052
+ function ownExport(f, name) {
3053
+ return f.tops.some((statement) => exportedNames(statement).includes(name) || statement.type === "ExportNamedStatement" && statement.specifiers.some((s) => s.exported.name === name));
3054
+ }
3055
+
2594
3056
  // src/linemap.ts
2595
3057
  var import_parser6 = require("@tilua/parser");
2596
3058
  function statementsOf(node) {
@@ -2681,8 +3143,8 @@ async function bundle(options) {
2681
3143
  sources.set(file, { file, name: nameOf(file), text, program: program3, scopes });
2682
3144
  const dynamic = (0, import_parser7.importExpressions)(program3).flatMap((node) => node.source.type === "StringLiteral" ? [node.source.value] : []);
2683
3145
  for (const specifier of [...importedSpecifiers(program3), ...dynamic]) {
2684
- const target2 = (0, import_parser7.resolveModulePath)(file, specifier, config);
2685
- if (target2 && !target2.endsWith(".d.tilua")) queue.push(target2);
3146
+ const target = (0, import_parser7.resolveModulePath)(file, specifier, config);
3147
+ if (target && !target.endsWith(".d.tilua")) queue.push(target);
2686
3148
  }
2687
3149
  }
2688
3150
  const analysis = analyzeModules([...sources.values()], config);
@@ -2696,9 +3158,16 @@ async function bundle(options) {
2696
3158
  const requireExpression = member(identifier(G), "require");
2697
3159
  const importExpression = member(identifier(G), "import");
2698
3160
  const mode = options.output ?? config?.output ?? "single";
2699
- const target = options.target ?? config?.target ?? "luau";
3161
+ const byPath = syntaxOf(lowerings).has("requireByPath");
2700
3162
  const modules = /* @__PURE__ */ new Map();
2701
3163
  const origins = /* @__PURE__ */ new WeakMap();
3164
+ const shared = new SharedRuntime(names);
3165
+ const shaking = options.treeShaking ?? config?.treeShaking ?? false;
3166
+ const resolveIn = (file) => (specifier) => {
3167
+ const found = (0, import_parser7.resolveModulePath)(file, specifier, config);
3168
+ return found !== void 0 && sources.has(found) ? found : void 0;
3169
+ };
3170
+ const shaken = shaking ? treeShake([...sources.values()].map((s) => ({ file: s.file, program: s.program, scopes: s.scopes, resolve: resolveIn(s.file) })), entry) : void 0;
2702
3171
  const pending = [entry];
2703
3172
  while (pending.length) {
2704
3173
  const file = pending.shift();
@@ -2711,10 +3180,21 @@ async function bundle(options) {
2711
3180
  names,
2712
3181
  types: analysis.types.get(file),
2713
3182
  lowerings,
2714
- target,
2715
3183
  noLib: config?.noLib,
2716
3184
  source: source.text,
2717
3185
  file: shown,
3186
+ shared,
3187
+ shaken: shaken && {
3188
+ ...shaken.modules.get(file),
3189
+ runs: (specifier) => {
3190
+ const target = resolveIn(file)(specifier);
3191
+ return target !== void 0 && shaken.sideEffects.has(target);
3192
+ },
3193
+ needed: (specifier) => {
3194
+ const target = resolveIn(file)(specifier);
3195
+ return target !== void 0 && shaken.required.has(target);
3196
+ }
3197
+ },
2718
3198
  module: {
2719
3199
  name: source.name,
2720
3200
  require: requireExpression,
@@ -2745,6 +3225,7 @@ async function bundle(options) {
2745
3225
  exportsName: lowered2.exportsName,
2746
3226
  info: lowered2.module,
2747
3227
  usesScriptArgs: lowered2.usesScriptArgs,
3228
+ shared: lowered2.shared,
2748
3229
  imports,
2749
3230
  dynamicImports
2750
3231
  });
@@ -2770,16 +3251,17 @@ async function bundle(options) {
2770
3251
  if (diagnostics.some((d) => d.category === "syntax" || d.category === "scope" || d.category === "module")) {
2771
3252
  return { files: [], modules: moduleNames, diagnostics };
2772
3253
  }
3254
+ if (shaking) shared.shake(lowered.flatMap((m) => m.statements));
2773
3255
  const several = mode !== "single";
2774
3256
  const layout = layoutOf(mode, modules.get(entry), lowered, options.outFile);
2775
3257
  const usesScriptArgs = lowered.some((m) => m.usesScriptArgs);
2776
3258
  const elsewhere = /* @__PURE__ */ new Map();
2777
3259
  for (const file of layout.slice(1)) {
2778
- for (const module2 of file.modules) elsewhere.set(module2.name, loadPath(layout[0].path, file.path, target));
3260
+ for (const module2 of file.modules) elsewhere.set(module2.name, loadPath(layout[0].path, file.path, byPath));
2779
3261
  }
2780
3262
  const program2 = (0, import_luau_parser2.parse)(runtime(G, {
2781
3263
  several,
2782
- target,
3264
+ byPath,
2783
3265
  files: elsewhere,
2784
3266
  imports: lowered.some((m) => m.dynamicImports.size > 0)
2785
3267
  }));
@@ -2794,6 +3276,8 @@ async function bundle(options) {
2794
3276
  for (const module2 of layout[0].modules) {
2795
3277
  modulesTable.fields.push({ type: "TableFieldComputed", key: string(module2.name), value: moduleTable(module2) });
2796
3278
  }
3279
+ const helpers = shared.chunk(new Set(layout[0].modules.flatMap((m) => m.shared)));
3280
+ program2.body.statements.unshift(...helpers.declare);
2797
3281
  const ok = names.fresh("ok");
2798
3282
  const result2 = names.fresh("result");
2799
3283
  const entryStatements = (0, import_luau_parser2.parse)(`
@@ -2815,9 +3299,11 @@ ${modules.get(entry).info.exports ? `return ${result2}` : ""}
2815
3299
  if (several) mapStatements.push(...mapRegistration(G, layout[0], `${G}.lines`));
2816
3300
  program2.body.statements.splice(program2.body.statements.length - entryStatements.length, 0, ...mapStatements);
2817
3301
  }
3302
+ program2.body.statements.splice(program2.body.statements.length - entryStatements.length, 0, ...helpers.define);
2818
3303
  const files = [{ path: layout[0].path, code: (0, import_luau_parser2.print)(program2) + "\n", modules: layout[0].modules.map((m) => m.name) }];
2819
3304
  for (const file of layout.slice(1)) {
2820
- const body = [];
3305
+ const fileHelpers = shared.chunk(new Set(file.modules.flatMap((m) => m.shared)));
3306
+ const body = [...fileHelpers.declare];
2821
3307
  if (file.modules.some((m) => m.usesScriptArgs)) body.push(...(0, import_luau_parser2.parse)(`local scriptArgs = ${G}.scriptArgs`).body.statements);
2822
3308
  for (const module2 of file.modules) {
2823
3309
  body.push(assign(
@@ -2827,7 +3313,7 @@ ${modules.get(entry).info.exports ? `return ${result2}` : ""}
2827
3313
  }
2828
3314
  const chunk = program([returns([functionExpression(functionBody([G], body))])]);
2829
3315
  const lines = buildLineMap(chunk, (0, import_luau_parser2.parse)((0, import_luau_parser2.print)(chunk)), origins);
2830
- body.push(...mapRegistration(G, file, lineMapSource(lines)));
3316
+ body.push(...mapRegistration(G, file, lineMapSource(lines)), ...fileHelpers.define);
2831
3317
  files.push({ path: file.path, code: (0, import_luau_parser2.print)(chunk) + "\n", modules: file.modules.map((m) => m.name) });
2832
3318
  }
2833
3319
  return { code: files[0].code, files, modules: moduleNames, diagnostics };
@@ -2889,9 +3375,9 @@ function layoutOf(mode, entry, modules, outFile) {
2889
3375
  }
2890
3376
  return files;
2891
3377
  }
2892
- function loadPath(entryPath, file, target) {
3378
+ function loadPath(entryPath, file, byPath) {
2893
3379
  const path = import_node_path2.posix.relative(import_node_path2.posix.dirname(entryPath), file);
2894
- if (target === "lua51") return path;
3380
+ if (!byPath) return path;
2895
3381
  const bare = path.slice(0, path.length - import_node_path2.posix.extname(path).length);
2896
3382
  return bare.startsWith("../") ? bare : `./${bare}`;
2897
3383
  }
@@ -2904,8 +3390,8 @@ function mapRegistration(G, file, map) {
2904
3390
  ...file.modules.map((module2) => `${G}.maps[${(0, import_parser7.luauString)(module2.name)}] = ${G}.sources[#${G}.sources][2]`)
2905
3391
  ].join("\n")).body.statements;
2906
3392
  }
2907
- function loader(G, target) {
2908
- if (target === "lua51") {
3393
+ function loader(G, byPath) {
3394
+ if (!byPath) {
2909
3395
  return `
2910
3396
  -- From beside this file, wherever that is: its own path says where.
2911
3397
  local folder = ""
@@ -3113,7 +3599,7 @@ ${several ? `
3113
3599
  if ${G}.loaded[file] then
3114
3600
  return
3115
3601
  end
3116
- ${G}.loaded[file] = true${loader(G, options.target)}
3602
+ ${G}.loaded[file] = true${loader(G, options.byPath)}
3117
3603
  end,` : ""}${options.imports ? `
3118
3604
  -- \`import("./x")\`: the module's exports, asked for where the code runs
3119
3605
  -- rather than before its module does. \`require\` does the rest \u2014 and
@@ -3382,7 +3868,6 @@ async function compile(source, config) {
3382
3868
  types: (0, import_parser8.analyzeTypes)(program2, scopes, { libs, noLib, diagnostics: false }),
3383
3869
  noLib,
3384
3870
  lowerings,
3385
- target: resolved?.target,
3386
3871
  source
3387
3872
  });
3388
3873
  diagnostics.push(...lowered.diagnostics);