@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/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;
@@ -1660,7 +1852,7 @@ end
1660
1852
  isMethod,
1661
1853
  func,
1662
1854
  // `@native` and the like are Luau's.
1663
- attributes: this.lua51 ? void 0 : node.attributes,
1855
+ attributes: this.syntax.has("attributes") ? node.attributes : void 0,
1664
1856
  ...spanOf(node)
1665
1857
  };
1666
1858
  }
@@ -1900,7 +2092,7 @@ end
1900
2092
  if (join === "concat") return binary("..", left, right);
1901
2093
  if (join === "dynamic") return call(this.helper("add"), [this.keptToOne(left), this.keptToOne(right)]);
1902
2094
  }
1903
- if (this.lua51 && node.operator === "//") return this.floorDivide(left, right);
2095
+ if (!this.syntax.has("floorDivision") && node.operator === "//") return this.floorDivide(left, right);
1904
2096
  return binary(node.operator, left, right);
1905
2097
  }
1906
2098
  case "UnaryExpression":
@@ -1934,7 +2126,7 @@ end
1934
2126
  case "AsConstExpression":
1935
2127
  return this.expression(node.expression);
1936
2128
  case "IfElseExpression":
1937
- if (this.lua51) return this.ifElse51(node);
2129
+ if (!this.syntax.has("ifExpression")) return this.ifElse51(node);
1938
2130
  return {
1939
2131
  type: "IfElseExpression",
1940
2132
  clauses: node.clauses.map((c) => ({ condition: this.expression(c.condition), body: this.expression(c.body) })),
@@ -1981,7 +2173,7 @@ end
1981
2173
  const last2 = list.length - 1;
1982
2174
  return lowered.length && !many.has(list[last2]) ? [...lowered.slice(0, -1), this.keptToOne(lowered[last2])] : lowered;
1983
2175
  }
1984
- const unpack = (value) => call(this.lua51 ? this.builtin("unpack") : member(this.builtin("table"), "unpack"), [value]);
2176
+ const unpack = (value) => call(this.helper("unpack"), [value]);
1985
2177
  const last = list[list.length - 1];
1986
2178
  if (spreads.length === 1 && last.type === "SpreadElement") {
1987
2179
  return [...list.slice(0, -1).map((a) => this.expression(a)), unpack(this.expression(last.argument))];
@@ -2095,7 +2287,7 @@ end
2095
2287
  const segments = this.segments(chain);
2096
2288
  const base = this.expression(chain.base);
2097
2289
  const last = chain.links[chain.links.length - 1];
2098
- 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") {
2099
2291
  return {
2100
2292
  type: "IfElseExpression",
2101
2293
  clauses: [{ condition: binary("==", base, nil()), body: nil() }],
@@ -2122,7 +2314,7 @@ end
2122
2314
  nullish(node) {
2123
2315
  const left = this.keptToOne(this.expression(node.left));
2124
2316
  const right = this.keptToOne(this.expression(node.right));
2125
- if (!this.lua51 && left.type === "Identifier") {
2317
+ if (this.syntax.has("ifExpression") && left.type === "Identifier") {
2126
2318
  return {
2127
2319
  type: "IfElseExpression",
2128
2320
  clauses: [{ condition: binary("~=", left, nil()), body: left }],
@@ -2209,14 +2401,19 @@ end
2209
2401
  let target;
2210
2402
  if (node.target.type === "MemberExpression" || node.target.type === "IndexExpression") {
2211
2403
  const object2 = this.expression(node.target.object);
2212
- const objectName = this.names.fresh("target");
2213
- before.push(local([objectName], [object2]));
2214
- if (node.target.type === "MemberExpression") {
2215
- 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));
2216
2407
  } else {
2217
- const keyName = this.names.fresh("key");
2218
- before.push(local([keyName], [this.expression(node.target.index)]));
2219
- 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
+ }
2220
2417
  }
2221
2418
  } else {
2222
2419
  target = this.expression(node.target);
@@ -2237,7 +2434,7 @@ end
2237
2434
  * done, and breaks for real there. A body with no `continue` is left
2238
2435
  * exactly as it was. */
2239
2436
  loopBody51(body) {
2240
- if (!this.lua51 || !hasOwnJump(body.statements, "ContinueStatement")) return body;
2437
+ if (this.syntax.has("continue") || !hasOwnJump(body.statements, "ContinueStatement")) return body;
2241
2438
  const flag = hasOwnJump(body.statements, "BreakStatement") ? this.names.fresh("broke") : void 0;
2242
2439
  const once = repeatUntil(rewriteOwnJumps(body.statements, flag), boolean(true));
2243
2440
  if (!flag) return block([once]);
@@ -2289,7 +2486,7 @@ end
2289
2486
  * underscores and `0b1010` becomes the decimal it means. A hex literal
2290
2487
  * keeps its base — 5.1 reads those. */
2291
2488
  numberRaw(node) {
2292
- if (!this.lua51) return node.raw;
2489
+ if (this.syntax.has("numberLiterals")) return node.raw;
2293
2490
  if (/^0[bB]/.test(node.raw)) return String(node.value);
2294
2491
  return node.raw.replace(/_/g, "");
2295
2492
  }
@@ -2413,6 +2610,7 @@ function usesVararg(node) {
2413
2610
  return Object.values(record).some(usesVararg);
2414
2611
  }
2415
2612
  function calleePath(path) {
2613
+ if (!/^[A-Za-z_]\w*(\.[A-Za-z_]\w*)*$/.test(path)) return (0, import_luau_parser.parseExpressionFromSource)(path);
2416
2614
  const [head, ...rest] = path.split(".");
2417
2615
  return rest.reduce((value, name) => member(value, name), identifier(head));
2418
2616
  }
@@ -2542,10 +2740,10 @@ function declaredNames(statements) {
2542
2740
  }
2543
2741
  function mentions(expression, name) {
2544
2742
  let found = false;
2545
- const walk = (value) => {
2743
+ const walk2 = (value) => {
2546
2744
  if (found || !value || typeof value !== "object") return;
2547
2745
  if (Array.isArray(value)) {
2548
- for (const item of value) walk(item);
2746
+ for (const item of value) walk2(item);
2549
2747
  return;
2550
2748
  }
2551
2749
  const node = value;
@@ -2554,10 +2752,10 @@ function mentions(expression, name) {
2554
2752
  return;
2555
2753
  }
2556
2754
  for (const [key, child] of Object.entries(value)) {
2557
- if (key !== "line" && key !== "column") walk(child);
2755
+ if (key !== "line" && key !== "column") walk2(child);
2558
2756
  }
2559
2757
  };
2560
- walk(expression);
2758
+ walk2(expression);
2561
2759
  return found;
2562
2760
  }
2563
2761
  function allNodes(root) {
@@ -2593,6 +2791,269 @@ var CLASS_METAMETHODS = [
2593
2791
  "__iter"
2594
2792
  ];
2595
2793
 
2794
+ // src/treeshake.ts
2795
+ function walk(node, visit, parent) {
2796
+ if (!node || typeof node !== "object") return;
2797
+ if (Array.isArray(node)) {
2798
+ for (const item of node) walk(item, visit, parent);
2799
+ return;
2800
+ }
2801
+ const n = node;
2802
+ if (typeof n.type === "string" && visit(n, parent) === false) return;
2803
+ for (const [key, child] of Object.entries(n)) {
2804
+ if (key === "line" || key === "column") continue;
2805
+ walk(child, visit, typeof n.type === "string" ? n : parent);
2806
+ }
2807
+ }
2808
+ function pure(expression) {
2809
+ let ok = true;
2810
+ walk(expression, (node) => {
2811
+ if (!ok) return false;
2812
+ switch (node.type) {
2813
+ case "CallExpression":
2814
+ case "MethodCallExpression":
2815
+ case "ImportExpression":
2816
+ case "SuperExpression":
2817
+ ok = false;
2818
+ return false;
2819
+ case "FunctionExpression":
2820
+ case "FunctionBody":
2821
+ case "ClassMethod":
2822
+ case "ClassAccessor":
2823
+ case "ClassConstructor":
2824
+ return false;
2825
+ case "ClassField":
2826
+ if (!node.isStatic) return false;
2827
+ }
2828
+ });
2829
+ return ok;
2830
+ }
2831
+ function declarationOf(statement) {
2832
+ return statement.type === "ExportStatement" ? statement.declaration : statement;
2833
+ }
2834
+ function removable(statement) {
2835
+ const declaration = declarationOf(statement);
2836
+ switch (declaration.type) {
2837
+ case "FunctionDeclaration":
2838
+ return true;
2839
+ case "ClassDeclaration":
2840
+ return pure(declaration);
2841
+ case "VariableDeclaration":
2842
+ return declaration.init === void 0 || pure(declaration.init);
2843
+ case "ExportDefaultStatement":
2844
+ return pure(declaration.declaration);
2845
+ default:
2846
+ return false;
2847
+ }
2848
+ }
2849
+ function structural(statement) {
2850
+ switch (statement.type) {
2851
+ case "TypeAliasStatement":
2852
+ case "ExportTypeAliasStatement":
2853
+ case "DeclareStatement":
2854
+ case "DeclareClassStatement":
2855
+ case "DeclareMetatableStatement":
2856
+ case "ImportStatement":
2857
+ case "ExportNamedStatement":
2858
+ case "ExportAllStatement":
2859
+ return true;
2860
+ default:
2861
+ return false;
2862
+ }
2863
+ }
2864
+ function exportedNames(statement) {
2865
+ switch (statement.type) {
2866
+ case "ExportStatement": {
2867
+ const declaration = statement.declaration;
2868
+ if (declaration.type === "FunctionDeclaration" || declaration.type === "ClassDeclaration") return [declaration.name.name];
2869
+ const names = [];
2870
+ walk(declaration.name, (node) => {
2871
+ if (node.type === "IdentifierPattern") names.push(node.name);
2872
+ });
2873
+ return names;
2874
+ }
2875
+ case "ExportDefaultStatement":
2876
+ return ["default"];
2877
+ default:
2878
+ return [];
2879
+ }
2880
+ }
2881
+ function factsOf(module2) {
2882
+ const tops = module2.program.body.statements;
2883
+ const byNode = /* @__PURE__ */ new Map();
2884
+ for (const binding of module2.scopes.bindings.values()) {
2885
+ if (binding.declarationNode) byNode.set(binding.declarationNode, binding.id);
2886
+ }
2887
+ const declaredBy = /* @__PURE__ */ new Map();
2888
+ const imported = /* @__PURE__ */ new Map();
2889
+ for (const statement of tops) {
2890
+ const declaration = declarationOf(statement);
2891
+ const declare = (node) => {
2892
+ const id = node && byNode.get(node);
2893
+ if (id !== void 0) declaredBy.set(id, statement);
2894
+ };
2895
+ if (declaration.type === "FunctionDeclaration" || declaration.type === "ClassDeclaration") declare(declaration.name);
2896
+ else if (declaration.type === "VariableDeclaration") {
2897
+ walk(declaration.name, (node) => {
2898
+ if (node.type === "IdentifierPattern") declare(node);
2899
+ });
2900
+ } else if (declaration.type === "ImportStatement" && !declaration.isTypeOnly) {
2901
+ const target = module2.resolve(declaration.source.value);
2902
+ if (target === void 0) continue;
2903
+ const add = (node, name) => {
2904
+ const id = node && byNode.get(node);
2905
+ if (id !== void 0) imported.set(id, { target, name });
2906
+ };
2907
+ if (declaration.defaultImport) add(declaration.defaultImport, "default");
2908
+ if (declaration.namespaceImport) add(declaration.namespaceImport, "*");
2909
+ for (const s of declaration.specifiers) add(s.local, s.imported.name);
2910
+ }
2911
+ }
2912
+ const parents = /* @__PURE__ */ new WeakMap();
2913
+ walk(module2.program.body, (node, parent) => {
2914
+ if (parent) parents.set(node, parent);
2915
+ });
2916
+ const sideEffects = tops.some((s) => !structural(s) && !removable(s));
2917
+ return { module: module2, tops, declaredBy, imported, parents, sideEffects };
2918
+ }
2919
+ function treeShake(modules, entry) {
2920
+ const facts = new Map(modules.map((m) => [m.file, factsOf(m)]));
2921
+ const used = /* @__PURE__ */ new Map();
2922
+ const live = /* @__PURE__ */ new Map();
2923
+ const required = /* @__PURE__ */ new Set();
2924
+ let changed = true;
2925
+ const require2 = (file) => {
2926
+ if (required.has(file) || !facts.has(file)) return;
2927
+ required.add(file);
2928
+ changed = true;
2929
+ };
2930
+ const read3 = (file, name) => {
2931
+ if (!facts.has(file)) return;
2932
+ require2(file);
2933
+ const now = used.get(file);
2934
+ if (now === "all") return;
2935
+ if (name === "*") {
2936
+ used.set(file, "all");
2937
+ changed = true;
2938
+ return;
2939
+ }
2940
+ const names = now ?? /* @__PURE__ */ new Set();
2941
+ if (!names.has(name)) {
2942
+ names.add(name);
2943
+ used.set(file, names);
2944
+ changed = true;
2945
+ }
2946
+ };
2947
+ const isRead = (file, name) => {
2948
+ const now = used.get(file);
2949
+ return now === "all" || (now?.has(name) ?? false);
2950
+ };
2951
+ require2(entry);
2952
+ used.set(entry, "all");
2953
+ while (changed) {
2954
+ changed = false;
2955
+ for (const file of [...required]) {
2956
+ const f = facts.get(file);
2957
+ const kept = live.get(file) ?? /* @__PURE__ */ new Set();
2958
+ live.set(file, kept);
2959
+ const queue = [];
2960
+ const keep = (statement) => {
2961
+ if (!statement || kept.has(statement)) return;
2962
+ kept.add(statement);
2963
+ queue.push(statement);
2964
+ changed = true;
2965
+ };
2966
+ for (const statement of f.tops) {
2967
+ if (!structural(statement) && !removable(statement)) keep(statement);
2968
+ if (exportedNames(statement).some((name) => isRead(file, name))) keep(statement);
2969
+ const declaration = declarationOf(statement);
2970
+ if (declaration.type === "ImportStatement" && !declaration.isTypeOnly && !declaration.defaultImport && !declaration.namespaceImport && !declaration.specifiers.length) {
2971
+ const target = f.module.resolve(declaration.source.value);
2972
+ if (target !== void 0) require2(target);
2973
+ }
2974
+ if (declaration.type === "ExportNamedStatement" && !declaration.typeOnly) {
2975
+ const target = declaration.source ? f.module.resolve(declaration.source.value) : void 0;
2976
+ for (const s of declaration.specifiers) {
2977
+ if (!isRead(file, s.exported.name)) continue;
2978
+ if (declaration.source) {
2979
+ if (target !== void 0) read3(target, s.local.name);
2980
+ continue;
2981
+ }
2982
+ const id = f.module.scopes.bindingOf.get(s.local);
2983
+ const own = [...f.declaredBy].find(([bid, st]) => (id !== void 0 ? bid === id : false) || exportedLocal(st, s.local.name));
2984
+ if (own) keep(own[1]);
2985
+ const from = id !== void 0 ? f.imported.get(id) : void 0;
2986
+ if (from) read3(from.target, from.name);
2987
+ }
2988
+ }
2989
+ if (declaration.type === "ExportAllStatement") {
2990
+ const target = f.module.resolve(declaration.source.value);
2991
+ if (target === void 0) continue;
2992
+ const mine = used.get(file);
2993
+ if (mine === "all") read3(target, "*");
2994
+ else for (const name of mine ?? []) if (!ownExport(f, name)) read3(target, name);
2995
+ }
2996
+ }
2997
+ for (const statement of [...kept]) queue.push(statement);
2998
+ while (queue.length) {
2999
+ const statement = queue.pop();
3000
+ walk(statement, (node) => {
3001
+ if (node.type === "ImportExpression") {
3002
+ const source = node.source;
3003
+ const target = source?.type === "StringLiteral" ? f.module.resolve(source.value) : void 0;
3004
+ if (target !== void 0) read3(target, "*");
3005
+ return;
3006
+ }
3007
+ if (node.type !== "Identifier") return;
3008
+ const id = f.module.scopes.bindingOf.get(node);
3009
+ if (id === void 0) return;
3010
+ const declaring = f.declaredBy.get(id);
3011
+ if (declaring && declaring !== statement) keep(declaring);
3012
+ const from = f.imported.get(id);
3013
+ if (!from) return;
3014
+ if (from.name !== "*") return read3(from.target, from.name);
3015
+ const parent = f.parents.get(node);
3016
+ if (parent?.type === "MemberExpression" && parent.object === node) read3(from.target, parent.property.name);
3017
+ else read3(from.target, "*");
3018
+ });
3019
+ }
3020
+ for (const statement of f.tops) {
3021
+ if (statement.type !== "ImportStatement" || statement.isTypeOnly) continue;
3022
+ const target = f.module.resolve(statement.source.value);
3023
+ if (target !== void 0 && facts.get(target)?.sideEffects) require2(target);
3024
+ }
3025
+ }
3026
+ }
3027
+ const shaken = /* @__PURE__ */ new Map();
3028
+ for (const [file, f] of facts) {
3029
+ const kept = live.get(file) ?? /* @__PURE__ */ new Set();
3030
+ const removed = new Set(f.tops.filter((s) => !kept.has(s) && removable(s)));
3031
+ const dead = /* @__PURE__ */ new Set();
3032
+ for (const statement of removed) walk(statement, (node) => {
3033
+ if (node.type === "Identifier") dead.add(node);
3034
+ });
3035
+ shaken.set(file, { removed, dead });
3036
+ }
3037
+ return {
3038
+ modules: shaken,
3039
+ sideEffects: new Set([...facts].filter(([, f]) => f.sideEffects).map(([file]) => file)),
3040
+ required
3041
+ };
3042
+ }
3043
+ function exportedLocal(statement, name) {
3044
+ const declaration = declarationOf(statement);
3045
+ if (declaration.type === "FunctionDeclaration" || declaration.type === "ClassDeclaration") return declaration.name.name === name;
3046
+ if (declaration.type !== "VariableDeclaration") return false;
3047
+ let found = false;
3048
+ walk(declaration.name, (node) => {
3049
+ if (node.type === "IdentifierPattern" && node.name === name) found = true;
3050
+ });
3051
+ return found;
3052
+ }
3053
+ function ownExport(f, name) {
3054
+ return f.tops.some((statement) => exportedNames(statement).includes(name) || statement.type === "ExportNamedStatement" && statement.specifiers.some((s) => s.exported.name === name));
3055
+ }
3056
+
2596
3057
  // src/linemap.ts
2597
3058
  var import_parser6 = require("@tilua/parser");
2598
3059
  function statementsOf(node) {
@@ -2683,8 +3144,8 @@ async function bundle(options) {
2683
3144
  sources.set(file, { file, name: nameOf(file), text, program: program3, scopes });
2684
3145
  const dynamic = (0, import_parser7.importExpressions)(program3).flatMap((node) => node.source.type === "StringLiteral" ? [node.source.value] : []);
2685
3146
  for (const specifier of [...importedSpecifiers(program3), ...dynamic]) {
2686
- const target2 = (0, import_parser7.resolveModulePath)(file, specifier, config);
2687
- if (target2 && !target2.endsWith(".d.tilua")) queue.push(target2);
3147
+ const target = (0, import_parser7.resolveModulePath)(file, specifier, config);
3148
+ if (target && !target.endsWith(".d.tilua")) queue.push(target);
2688
3149
  }
2689
3150
  }
2690
3151
  const analysis = analyzeModules([...sources.values()], config);
@@ -2698,9 +3159,16 @@ async function bundle(options) {
2698
3159
  const requireExpression = member(identifier(G), "require");
2699
3160
  const importExpression = member(identifier(G), "import");
2700
3161
  const mode = options.output ?? config?.output ?? "single";
2701
- const target = options.target ?? config?.target ?? "luau";
3162
+ const byPath = syntaxOf(lowerings).has("requireByPath");
2702
3163
  const modules = /* @__PURE__ */ new Map();
2703
3164
  const origins = /* @__PURE__ */ new WeakMap();
3165
+ const shared = new SharedRuntime(names);
3166
+ const shaking = options.treeShaking ?? config?.treeShaking ?? false;
3167
+ const resolveIn = (file) => (specifier) => {
3168
+ const found = (0, import_parser7.resolveModulePath)(file, specifier, config);
3169
+ return found !== void 0 && sources.has(found) ? found : void 0;
3170
+ };
3171
+ const shaken = shaking ? treeShake([...sources.values()].map((s) => ({ file: s.file, program: s.program, scopes: s.scopes, resolve: resolveIn(s.file) })), entry) : void 0;
2704
3172
  const pending = [entry];
2705
3173
  while (pending.length) {
2706
3174
  const file = pending.shift();
@@ -2713,10 +3181,21 @@ async function bundle(options) {
2713
3181
  names,
2714
3182
  types: analysis.types.get(file),
2715
3183
  lowerings,
2716
- target,
2717
3184
  noLib: config?.noLib,
2718
3185
  source: source.text,
2719
3186
  file: shown,
3187
+ shared,
3188
+ shaken: shaken && {
3189
+ ...shaken.modules.get(file),
3190
+ runs: (specifier) => {
3191
+ const target = resolveIn(file)(specifier);
3192
+ return target !== void 0 && shaken.sideEffects.has(target);
3193
+ },
3194
+ needed: (specifier) => {
3195
+ const target = resolveIn(file)(specifier);
3196
+ return target !== void 0 && shaken.required.has(target);
3197
+ }
3198
+ },
2720
3199
  module: {
2721
3200
  name: source.name,
2722
3201
  require: requireExpression,
@@ -2747,6 +3226,7 @@ async function bundle(options) {
2747
3226
  exportsName: lowered2.exportsName,
2748
3227
  info: lowered2.module,
2749
3228
  usesScriptArgs: lowered2.usesScriptArgs,
3229
+ shared: lowered2.shared,
2750
3230
  imports,
2751
3231
  dynamicImports
2752
3232
  });
@@ -2772,16 +3252,17 @@ async function bundle(options) {
2772
3252
  if (diagnostics.some((d) => d.category === "syntax" || d.category === "scope" || d.category === "module")) {
2773
3253
  return { files: [], modules: moduleNames, diagnostics };
2774
3254
  }
3255
+ if (shaking) shared.shake(lowered.flatMap((m) => m.statements));
2775
3256
  const several = mode !== "single";
2776
3257
  const layout = layoutOf(mode, modules.get(entry), lowered, options.outFile);
2777
3258
  const usesScriptArgs = lowered.some((m) => m.usesScriptArgs);
2778
3259
  const elsewhere = /* @__PURE__ */ new Map();
2779
3260
  for (const file of layout.slice(1)) {
2780
- for (const module2 of file.modules) elsewhere.set(module2.name, loadPath(layout[0].path, file.path, target));
3261
+ for (const module2 of file.modules) elsewhere.set(module2.name, loadPath(layout[0].path, file.path, byPath));
2781
3262
  }
2782
3263
  const program2 = (0, import_luau_parser2.parse)(runtime(G, {
2783
3264
  several,
2784
- target,
3265
+ byPath,
2785
3266
  files: elsewhere,
2786
3267
  imports: lowered.some((m) => m.dynamicImports.size > 0)
2787
3268
  }));
@@ -2796,6 +3277,8 @@ async function bundle(options) {
2796
3277
  for (const module2 of layout[0].modules) {
2797
3278
  modulesTable.fields.push({ type: "TableFieldComputed", key: string(module2.name), value: moduleTable(module2) });
2798
3279
  }
3280
+ const helpers = shared.chunk(new Set(layout[0].modules.flatMap((m) => m.shared)));
3281
+ program2.body.statements.unshift(...helpers.declare);
2799
3282
  const ok = names.fresh("ok");
2800
3283
  const result2 = names.fresh("result");
2801
3284
  const entryStatements = (0, import_luau_parser2.parse)(`
@@ -2817,9 +3300,11 @@ ${modules.get(entry).info.exports ? `return ${result2}` : ""}
2817
3300
  if (several) mapStatements.push(...mapRegistration(G, layout[0], `${G}.lines`));
2818
3301
  program2.body.statements.splice(program2.body.statements.length - entryStatements.length, 0, ...mapStatements);
2819
3302
  }
3303
+ program2.body.statements.splice(program2.body.statements.length - entryStatements.length, 0, ...helpers.define);
2820
3304
  const files = [{ path: layout[0].path, code: (0, import_luau_parser2.print)(program2) + "\n", modules: layout[0].modules.map((m) => m.name) }];
2821
3305
  for (const file of layout.slice(1)) {
2822
- const body = [];
3306
+ const fileHelpers = shared.chunk(new Set(file.modules.flatMap((m) => m.shared)));
3307
+ const body = [...fileHelpers.declare];
2823
3308
  if (file.modules.some((m) => m.usesScriptArgs)) body.push(...(0, import_luau_parser2.parse)(`local scriptArgs = ${G}.scriptArgs`).body.statements);
2824
3309
  for (const module2 of file.modules) {
2825
3310
  body.push(assign(
@@ -2829,7 +3314,7 @@ ${modules.get(entry).info.exports ? `return ${result2}` : ""}
2829
3314
  }
2830
3315
  const chunk = program([returns([functionExpression(functionBody([G], body))])]);
2831
3316
  const lines = buildLineMap(chunk, (0, import_luau_parser2.parse)((0, import_luau_parser2.print)(chunk)), origins);
2832
- body.push(...mapRegistration(G, file, lineMapSource(lines)));
3317
+ body.push(...mapRegistration(G, file, lineMapSource(lines)), ...fileHelpers.define);
2833
3318
  files.push({ path: file.path, code: (0, import_luau_parser2.print)(chunk) + "\n", modules: file.modules.map((m) => m.name) });
2834
3319
  }
2835
3320
  return { code: files[0].code, files, modules: moduleNames, diagnostics };
@@ -2891,9 +3376,9 @@ function layoutOf(mode, entry, modules, outFile) {
2891
3376
  }
2892
3377
  return files;
2893
3378
  }
2894
- function loadPath(entryPath, file, target) {
3379
+ function loadPath(entryPath, file, byPath) {
2895
3380
  const path = import_node_path2.posix.relative(import_node_path2.posix.dirname(entryPath), file);
2896
- if (target === "lua51") return path;
3381
+ if (!byPath) return path;
2897
3382
  const bare = path.slice(0, path.length - import_node_path2.posix.extname(path).length);
2898
3383
  return bare.startsWith("../") ? bare : `./${bare}`;
2899
3384
  }
@@ -2906,8 +3391,8 @@ function mapRegistration(G, file, map) {
2906
3391
  ...file.modules.map((module2) => `${G}.maps[${(0, import_parser7.luauString)(module2.name)}] = ${G}.sources[#${G}.sources][2]`)
2907
3392
  ].join("\n")).body.statements;
2908
3393
  }
2909
- function loader(G, target) {
2910
- if (target === "lua51") {
3394
+ function loader(G, byPath) {
3395
+ if (!byPath) {
2911
3396
  return `
2912
3397
  -- From beside this file, wherever that is: its own path says where.
2913
3398
  local folder = ""
@@ -3115,7 +3600,7 @@ ${several ? `
3115
3600
  if ${G}.loaded[file] then
3116
3601
  return
3117
3602
  end
3118
- ${G}.loaded[file] = true${loader(G, options.target)}
3603
+ ${G}.loaded[file] = true${loader(G, options.byPath)}
3119
3604
  end,` : ""}${options.imports ? `
3120
3605
  -- \`import("./x")\`: the module's exports, asked for where the code runs
3121
3606
  -- rather than before its module does. \`require\` does the rest \u2014 and
@@ -3384,7 +3869,6 @@ async function compile(source, config) {
3384
3869
  types: (0, import_parser8.analyzeTypes)(program2, scopes, { libs, noLib, diagnostics: false }),
3385
3870
  noLib,
3386
3871
  lowerings,
3387
- target: resolved?.target,
3388
3872
  source
3389
3873
  });
3390
3874
  diagnostics.push(...lowered.diagnostics);