@tilua/compiler 2.2.0 → 2.4.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.
@@ -262,12 +262,10 @@ var Lowerer = class {
262
262
  for (const node of allNodes(this.source.body)) {
263
263
  const statement = node;
264
264
  if (statement.type === "VariableDeclaration") {
265
- statement.names.forEach((target, index2) => {
266
- const value = statement.init[index2];
267
- if (target.type !== "IdentifierPattern" || !value) return;
268
- const binding = this.bindingByDeclaration.get(target);
269
- if (binding) this.declarations.set(binding.id, value);
270
- });
265
+ const target = statement.name;
266
+ if (target.type !== "IdentifierPattern" || !statement.init) continue;
267
+ const binding = this.bindingByDeclaration.get(target);
268
+ if (binding) this.declarations.set(binding.id, statement.init);
271
269
  } else if (statement.type === "FunctionDeclaration") {
272
270
  const binding = this.bindingByDeclaration.get(statement.name);
273
271
  if (binding) this.declarations.set(binding.id, statement);
@@ -541,7 +539,7 @@ var Lowerer = class {
541
539
  const declaration = statement.declaration;
542
540
  if (declaration.type === "FunctionDeclaration" || declaration.type === "ClassDeclaration") {
543
541
  exportBinding(declaration.name, declaration.name.name);
544
- } else for (const pattern of declaration.names.flatMap(identifierPatterns)) exportBinding(pattern, pattern.name);
542
+ } else for (const pattern of identifierPatterns(declaration.name)) exportBinding(pattern, pattern.name);
545
543
  break;
546
544
  }
547
545
  case "ExportDefaultStatement":
@@ -577,7 +575,7 @@ var Lowerer = class {
577
575
  for (const statement of statements) {
578
576
  const declaration = statement.type === "ExportStatement" ? statement.declaration : statement;
579
577
  if (declaration.type === "VariableDeclaration") {
580
- for (const pattern of declaration.names.flatMap(identifierPatterns)) {
578
+ for (const pattern of identifierPatterns(declaration.name)) {
581
579
  if (!this.isRewritten(pattern)) locals.push(this.name(pattern.name));
582
580
  }
583
581
  } else if ((declaration.type === "FunctionDeclaration" || declaration.type === "ClassDeclaration") && !this.isRewritten(declaration.name)) {
@@ -674,7 +672,7 @@ var Lowerer = class {
674
672
  continue;
675
673
  }
676
674
  if (statement.type !== "VariableDeclaration") continue;
677
- const patterns = statement.names.flatMap(identifierPatterns);
675
+ const patterns = identifierPatterns(statement.name);
678
676
  const end = { line: { start: statement.line.end }, column: { start: statement.column.end } };
679
677
  const needed = patterns.some((pattern) => {
680
678
  const binding = this.bindingByDeclaration.get(pattern);
@@ -715,7 +713,7 @@ var Lowerer = class {
715
713
  const body = [];
716
714
  for (const statement of statements) {
717
715
  if (statement.type === "VariableDeclaration") {
718
- for (const pattern of statement.names.flatMap(identifierPatterns)) locals.push(this.name(pattern.name));
716
+ for (const pattern of identifierPatterns(statement.name)) locals.push(this.name(pattern.name));
719
717
  body.push(...this.variableDeclaration(statement, "assign"));
720
718
  } else if (statement.type === "FunctionDeclaration") {
721
719
  locals.push(this.name(statement.name.name));
@@ -848,12 +846,13 @@ var Lowerer = class {
848
846
  }];
849
847
  case "GenericForStatement": {
850
848
  const prelude = [];
849
+ const variable = declared(node.variable);
851
850
  let item;
852
- if (node.variable.type === "IdentifierPattern") {
853
- item = this.name(node.variable.name);
851
+ if (variable.type === "IdentifierPattern") {
852
+ item = this.name(variable.name);
854
853
  } else {
855
854
  item = this.names.fresh("item");
856
- prelude.push(...this.destructure(node.variable, identifier(item), "declare"));
855
+ prelude.push(...this.destructure(variable, identifier(item), "declare"));
857
856
  }
858
857
  const form = this.options.types?.loops.get(node) ?? { walks: "values", viaIter: false };
859
858
  let source = this.expression(node.iterator);
@@ -867,7 +866,7 @@ var Lowerer = class {
867
866
  )];
868
867
  }
869
868
  case "ReturnStatement":
870
- return [returns(node.argument ? [this.expression(node.argument)] : [])];
869
+ return [returns(node.argument ? [this.keptToOne(this.expression(node.argument))] : [])];
871
870
  case "BreakStatement":
872
871
  return [{ type: "BreakStatement", ...spanOf(node) }];
873
872
  case "ContinueStatement":
@@ -1116,63 +1115,37 @@ end
1116
1115
  ...spanOf(node)
1117
1116
  };
1118
1117
  }
1119
- /** `const a, { b } = x, y`. In "assign" mode — a module's top level, where
1120
- * every name is declared up front — nothing is declared, only assigned. */
1118
+ /** `const a = x`, `const { a, b } = x`. In "assign" mode — a module's top
1119
+ * level, where every name is declared up front — nothing is declared,
1120
+ * only assigned. */
1121
1121
  variableDeclaration(node, mode) {
1122
- if (mode === "assign" && !node.init.length) {
1123
- const exported = node.names.flatMap(identifierPatterns).filter((p) => this.isRewritten(p));
1122
+ const target = declared(node.name);
1123
+ if (!node.init) {
1124
+ const names = identifierPatterns(target);
1125
+ if (mode === "declare") return [local(names.map((p) => this.name(p.name)), [])];
1126
+ const exported = names.filter((p) => this.isRewritten(p));
1124
1127
  return exported.length ? [assign(exported.map((p) => this.reference(p)), exported.map(() => nil()))] : [];
1125
1128
  }
1126
- const init = this.values(node.init);
1127
- if (node.names.every((n) => n.type === "IdentifierPattern")) {
1128
- const names2 = node.names;
1129
- return mode === "declare" ? [local(names2.map((n) => this.name(n.name)), init)] : [assign(names2.map((n) => this.reference(n)), init)];
1130
- }
1131
- const only = node.names.length === 1 && node.init.length === 1 ? node.names[0] : void 0;
1132
- if (only && only.type !== "IdentifierPattern") {
1133
- if (init[0].type === "Identifier") return this.destructure(only, init[0], mode);
1134
- const temp = this.names.fresh("ref");
1135
- return [local([temp], init), ...this.destructure(only, identifier(temp), mode)];
1136
- }
1137
- const after = [];
1138
- const names = [];
1139
- const targets = [];
1140
- for (const target of node.names) {
1141
- if (target.type === "IdentifierPattern") {
1142
- names.push(this.name(target.name));
1143
- targets.push(this.reference(target));
1144
- continue;
1145
- }
1146
- const temp = this.names.fresh("ref");
1147
- names.push(temp);
1148
- targets.push(identifier(temp));
1149
- after.push(...this.destructure(target, identifier(temp), mode));
1129
+ const value = this.expression(node.init);
1130
+ if (target.type === "IdentifierPattern") {
1131
+ return mode === "declare" ? [local([this.name(target.name)], [value])] : [assign([this.reference(target)], [value])];
1150
1132
  }
1151
- if (mode === "declare") return [local(names, init), ...after];
1152
- const temps = node.names.flatMap((t, i) => t.type === "IdentifierPattern" ? [] : [names[i]]);
1153
- return [...temps.length ? [local(temps, [])] : [], assign(targets, init), ...after];
1133
+ if (value.type === "Identifier") return this.destructure(target, value, mode);
1134
+ const temp = this.names.fresh("ref");
1135
+ return [local([temp], [value]), ...this.destructure(target, identifier(temp), mode)];
1154
1136
  }
1155
1137
  assignment(node) {
1156
- if (node.targets.every((t) => t.type !== "ObjectPattern" && t.type !== "ArrayPattern")) {
1157
- return [assign(node.targets.map((t) => this.expression(t)), this.values(node.values))];
1138
+ const target = node.target;
1139
+ const value = this.expression(node.value);
1140
+ if (target.type !== "ObjectPattern" && target.type !== "ArrayPattern") {
1141
+ return [assign([this.expression(target)], [value])];
1158
1142
  }
1159
- const values = this.values(node.values);
1160
- const only = node.targets.length === 1 && values.length === 1 ? node.targets[0] : void 0;
1161
- if (only && (only.type === "ObjectPattern" || only.type === "ArrayPattern") && values[0].type === "Identifier") {
1162
- const statements = this.destructure(only, values[0], "assign");
1143
+ if (value.type === "Identifier") {
1144
+ const statements = this.destructure(target, value, "assign");
1163
1145
  return statements.some((s) => s.type === "LocalStatement") ? [doBlock(statements)] : statements;
1164
1146
  }
1165
- const temps = node.targets.map(() => this.names.fresh("ref"));
1166
- const body = [local(temps, values)];
1167
- node.targets.forEach((target, i) => {
1168
- const value = identifier(temps[i]);
1169
- if (target.type === "ObjectPattern" || target.type === "ArrayPattern") {
1170
- body.push(...this.destructure(target, value, "assign"));
1171
- } else {
1172
- body.push(assign([this.expression(target)], [value]));
1173
- }
1174
- });
1175
- return [doBlock(body)];
1147
+ const temp = this.names.fresh("ref");
1148
+ return [doBlock([local([temp], [value]), ...this.destructure(target, identifier(temp), "assign")])];
1176
1149
  }
1177
1150
  // --------------------------------------------------------
1178
1151
  // Destructuring
@@ -1188,6 +1161,12 @@ end
1188
1161
  const reads = [];
1189
1162
  const after = [];
1190
1163
  const bind = (target, value, fallback) => {
1164
+ if (target.type === "MemberExpression" || target.type === "IndexExpression") {
1165
+ const reference = this.expression(target);
1166
+ reads.push({ name: "", target: reference, value, temp: false });
1167
+ if (fallback) after.push(this.defaultValue(reference, fallback));
1168
+ return;
1169
+ }
1191
1170
  if (target.type === "IdentifierPattern") {
1192
1171
  const name2 = this.name(target.name);
1193
1172
  const reference = mode === "declare" ? identifier(name2) : this.reference(target);
@@ -1260,6 +1239,11 @@ end
1260
1239
  * a temporary that a nested pattern then destructures (`then`). The
1261
1240
  * statement creating it is pushed onto `after`. */
1262
1241
  restTarget(target, initial, mode, after) {
1242
+ if (target.type === "MemberExpression" || target.type === "IndexExpression") {
1243
+ const reference = this.expression(target);
1244
+ after.push(assign([reference], [initial]));
1245
+ return { target: reference, then: [] };
1246
+ }
1263
1247
  if (target.type === "IdentifierPattern") {
1264
1248
  if (mode === "declare") {
1265
1249
  const name2 = this.name(target.name);
@@ -1307,7 +1291,7 @@ end
1307
1291
  if (through !== void 0) {
1308
1292
  return call(member(this.superClassReference(node), through), [
1309
1293
  identifier(this.thisName()),
1310
- ...this.values(node.arguments)
1294
+ ...this.arguments(node.arguments)
1311
1295
  ]);
1312
1296
  }
1313
1297
  }
@@ -1392,35 +1376,29 @@ end
1392
1376
  return this.link(node, this.expression(object));
1393
1377
  }
1394
1378
  /** A call is one value in tilua, and Luau would hand on every value one
1395
- * in the last place of an argument list or an array returns. Types say
1396
- * how many a call returns; where a type says nothing — `any`, `unknown`
1397
- * — the call is kept to one: `f((g()))`. */
1398
- keptToOne(nodes, values) {
1399
- const node = nodes[nodes.length - 1];
1400
- const last = values[values.length - 1];
1401
- if (!node || !last || !expandsToMany(last)) return values;
1402
- const type = this.options.types?.typeOf.get(node);
1403
- if (type && type.kind !== "any" && type.kind !== "unknown") return values;
1404
- return [...values.slice(0, -1), parenthesized(last)];
1379
+ * returns in the last place of an argument list or a `return`: it is
1380
+ * kept to one there, `f((g()))`. Every value is taken only where the
1381
+ * program asks for them, as an array's last element: `[g()]`. */
1382
+ keptToOne(value) {
1383
+ return expandsToMany(value) ? parenthesized(value) : value;
1405
1384
  }
1406
1385
  /** Is this `scriptArgs` the language's own, not a name the file declared? */
1407
1386
  isLanguageGlobal(node) {
1408
1387
  const id = this.bindingIdOf(node);
1409
1388
  return id === void 0 || this.scopes.bindings.get(id)?.kind === "global";
1410
1389
  }
1411
- /** A list of values — a call's arguments, a `return`'s, a declaration's,
1412
- * an assignment's. `f(a, ...xs)` is Lua's own last-value expansion,
1390
+ /** A call's arguments. `f(a, ...xs)` is Lua's own last-value expansion,
1413
1391
  * `f(a, table.unpack(xs))`; a spread anywhere else cannot be, since only
1414
1392
  * the last value of a list expands, so the whole list is built as an
1415
- * array first and that is what expands.
1416
- *
1417
- * `arguments` is set for a call's arguments, where Luau hands on every
1418
- * value the last one returns — see `keptToOne`. */
1419
- values(list, asArguments = false) {
1393
+ * array first and that is what expands. A call written last is one
1394
+ * value — see `keptToOne`. */
1395
+ arguments(written) {
1396
+ const { items: list, many } = packs(written);
1420
1397
  const spreads = list.filter((a) => a.type === "SpreadElement");
1421
1398
  if (!spreads.length) {
1422
1399
  const lowered = list.map((a) => this.expression(a));
1423
- return asArguments ? this.keptToOne(list, lowered) : lowered;
1400
+ const last2 = list.length - 1;
1401
+ return lowered.length && !many.has(list[last2]) ? [...lowered.slice(0, -1), this.keptToOne(lowered[last2])] : lowered;
1424
1402
  }
1425
1403
  const unpack = (value) => call(member(this.builtin("table"), "unpack"), [value]);
1426
1404
  const last = list[list.length - 1];
@@ -1442,13 +1420,13 @@ end
1442
1420
  return index(object, this.expression(node.index));
1443
1421
  case "CallExpression": {
1444
1422
  const lowered = this.loweredGlobalCall(node);
1445
- const args = this.values(node.arguments, true);
1423
+ const args = this.arguments(node.arguments);
1446
1424
  if (!lowered) return call(object, args);
1447
1425
  return call(calleePath(lowered.callee), [...lowered.prepend, ...args]);
1448
1426
  }
1449
1427
  case "MethodCallExpression": {
1450
1428
  const lowered = this.loweredMethodCall(node);
1451
- const args = this.values(node.arguments, true);
1429
+ const args = this.arguments(node.arguments);
1452
1430
  if (lowered) {
1453
1431
  return call(calleePath(lowered.callee), [
1454
1432
  ...lowered.prepend,
@@ -1681,33 +1659,55 @@ end
1681
1659
  /** `[1, ...xs, 2]`. Without a spread it is a Luau sequence; with one, the
1682
1660
  * runs of plain elements and the spread arrays are joined by `concat`. */
1683
1661
  arrayExpression(node) {
1684
- if (!node.elements.some((e) => e.type === "SpreadElement")) {
1685
- const elements = node.elements;
1686
- return table(this.keptToOne(elements, elements.map((e) => this.expression(e))).map((value) => ({ type: "TableFieldPositional", value })));
1662
+ const elements = packs(node.elements).items;
1663
+ if (!elements.some((e) => e.type === "SpreadElement")) {
1664
+ return table(elements.map((e) => ({ type: "TableFieldPositional", value: this.expression(e) })));
1687
1665
  }
1688
1666
  const parts = [];
1689
1667
  let current = [];
1690
- const flush = () => {
1668
+ const flush = (last) => {
1691
1669
  if (!current.length) return;
1692
1670
  parts.push(table(current.map((e, i) => {
1693
1671
  const value = this.expression(e);
1694
- const last = i === current.length - 1;
1695
- return { type: "TableFieldPositional", value: last && expandsToMany(value) ? parenthesized(value) : value };
1672
+ return { type: "TableFieldPositional", value: last || i < current.length - 1 ? value : this.keptToOne(value) };
1696
1673
  })));
1697
1674
  current = [];
1698
1675
  };
1699
- for (const element of node.elements) {
1676
+ for (const element of elements) {
1700
1677
  if (element.type === "SpreadElement") {
1701
- flush();
1678
+ flush(false);
1702
1679
  parts.push(this.expression(element.argument));
1703
1680
  } else {
1704
1681
  current.push(element);
1705
1682
  }
1706
1683
  }
1707
- flush();
1684
+ flush(true);
1708
1685
  return call(this.helper("concat"), parts);
1709
1686
  }
1710
1687
  };
1688
+ function packs(list) {
1689
+ const items = [];
1690
+ const many = /* @__PURE__ */ new Set();
1691
+ const add = (elements) => {
1692
+ for (const element of elements) {
1693
+ if (element.type === "SpreadElement" && element.argument.type === "ArrayExpression") {
1694
+ const inner = element.argument.elements;
1695
+ add(inner);
1696
+ const last = inner[inner.length - 1];
1697
+ if (last && (last.type === "CallExpression" || last.type === "MethodCallExpression")) many.add(last);
1698
+ } else {
1699
+ items.push(element);
1700
+ }
1701
+ }
1702
+ };
1703
+ add(list);
1704
+ items.forEach((item, i) => {
1705
+ if (!many.has(item) || i === items.length - 1) return;
1706
+ many.delete(item);
1707
+ items[i] = { type: "SpreadElement", argument: { type: "ArrayExpression", elements: [item], ...spanOf(item) }, ...spanOf(item) };
1708
+ });
1709
+ return { items, many };
1710
+ }
1711
1711
  function isLink(node) {
1712
1712
  return node.type === "MemberExpression" || node.type === "IndexExpression" || node.type === "CallExpression" || node.type === "MethodCallExpression";
1713
1713
  }
@@ -1756,10 +1756,19 @@ function memberChain(e) {
1756
1756
  }
1757
1757
  return void 0;
1758
1758
  }
1759
+ function declared(target) {
1760
+ if (target.type === "MemberExpression" || target.type === "IndexExpression") {
1761
+ throw new Error("a declaration binds names, not a member or an index");
1762
+ }
1763
+ return target;
1764
+ }
1759
1765
  function identifierPatterns(target) {
1760
1766
  switch (target.type) {
1761
1767
  case "IdentifierPattern":
1762
1768
  return [target];
1769
+ case "MemberExpression":
1770
+ case "IndexExpression":
1771
+ return [];
1763
1772
  case "ObjectPattern":
1764
1773
  return [...target.properties.flatMap((p) => identifierPatterns(p.value)), ...target.rest ? identifierPatterns(target.rest) : []];
1765
1774
  case "ArrayPattern":
@@ -1773,7 +1782,7 @@ function topLevelDeclaration(statements, name) {
1773
1782
  for (const statement of statements) {
1774
1783
  const declaration = statement.type === "ExportStatement" ? statement.declaration : statement;
1775
1784
  if (declaration.type === "VariableDeclaration") {
1776
- const pattern = declaration.names.flatMap(identifierPatterns).find((p) => p.name === name);
1785
+ const pattern = identifierPatterns(declaration.name).find((p) => p.name === name);
1777
1786
  if (pattern) return { type: "Declaration", node: pattern };
1778
1787
  } else if (declaration.type === "FunctionDeclaration" && declaration.name.name === name) {
1779
1788
  return { type: "Declaration", node: declaration.name };
package/dist/cli.cjs CHANGED
@@ -274,12 +274,10 @@ var Lowerer = class {
274
274
  for (const node of allNodes(this.source.body)) {
275
275
  const statement = node;
276
276
  if (statement.type === "VariableDeclaration") {
277
- statement.names.forEach((target, index2) => {
278
- const value = statement.init[index2];
279
- if (target.type !== "IdentifierPattern" || !value) return;
280
- const binding = this.bindingByDeclaration.get(target);
281
- if (binding) this.declarations.set(binding.id, value);
282
- });
277
+ const target = statement.name;
278
+ if (target.type !== "IdentifierPattern" || !statement.init) continue;
279
+ const binding = this.bindingByDeclaration.get(target);
280
+ if (binding) this.declarations.set(binding.id, statement.init);
283
281
  } else if (statement.type === "FunctionDeclaration") {
284
282
  const binding = this.bindingByDeclaration.get(statement.name);
285
283
  if (binding) this.declarations.set(binding.id, statement);
@@ -553,7 +551,7 @@ var Lowerer = class {
553
551
  const declaration = statement.declaration;
554
552
  if (declaration.type === "FunctionDeclaration" || declaration.type === "ClassDeclaration") {
555
553
  exportBinding(declaration.name, declaration.name.name);
556
- } else for (const pattern of declaration.names.flatMap(identifierPatterns)) exportBinding(pattern, pattern.name);
554
+ } else for (const pattern of identifierPatterns(declaration.name)) exportBinding(pattern, pattern.name);
557
555
  break;
558
556
  }
559
557
  case "ExportDefaultStatement":
@@ -589,7 +587,7 @@ var Lowerer = class {
589
587
  for (const statement of statements) {
590
588
  const declaration = statement.type === "ExportStatement" ? statement.declaration : statement;
591
589
  if (declaration.type === "VariableDeclaration") {
592
- for (const pattern of declaration.names.flatMap(identifierPatterns)) {
590
+ for (const pattern of identifierPatterns(declaration.name)) {
593
591
  if (!this.isRewritten(pattern)) locals.push(this.name(pattern.name));
594
592
  }
595
593
  } else if ((declaration.type === "FunctionDeclaration" || declaration.type === "ClassDeclaration") && !this.isRewritten(declaration.name)) {
@@ -686,7 +684,7 @@ var Lowerer = class {
686
684
  continue;
687
685
  }
688
686
  if (statement.type !== "VariableDeclaration") continue;
689
- const patterns = statement.names.flatMap(identifierPatterns);
687
+ const patterns = identifierPatterns(statement.name);
690
688
  const end = { line: { start: statement.line.end }, column: { start: statement.column.end } };
691
689
  const needed = patterns.some((pattern) => {
692
690
  const binding = this.bindingByDeclaration.get(pattern);
@@ -727,7 +725,7 @@ var Lowerer = class {
727
725
  const body = [];
728
726
  for (const statement of statements) {
729
727
  if (statement.type === "VariableDeclaration") {
730
- for (const pattern of statement.names.flatMap(identifierPatterns)) locals.push(this.name(pattern.name));
728
+ for (const pattern of identifierPatterns(statement.name)) locals.push(this.name(pattern.name));
731
729
  body.push(...this.variableDeclaration(statement, "assign"));
732
730
  } else if (statement.type === "FunctionDeclaration") {
733
731
  locals.push(this.name(statement.name.name));
@@ -860,12 +858,13 @@ var Lowerer = class {
860
858
  }];
861
859
  case "GenericForStatement": {
862
860
  const prelude = [];
861
+ const variable = declared(node.variable);
863
862
  let item;
864
- if (node.variable.type === "IdentifierPattern") {
865
- item = this.name(node.variable.name);
863
+ if (variable.type === "IdentifierPattern") {
864
+ item = this.name(variable.name);
866
865
  } else {
867
866
  item = this.names.fresh("item");
868
- prelude.push(...this.destructure(node.variable, identifier(item), "declare"));
867
+ prelude.push(...this.destructure(variable, identifier(item), "declare"));
869
868
  }
870
869
  const form = this.options.types?.loops.get(node) ?? { walks: "values", viaIter: false };
871
870
  let source = this.expression(node.iterator);
@@ -879,7 +878,7 @@ var Lowerer = class {
879
878
  )];
880
879
  }
881
880
  case "ReturnStatement":
882
- return [returns(node.argument ? [this.expression(node.argument)] : [])];
881
+ return [returns(node.argument ? [this.keptToOne(this.expression(node.argument))] : [])];
883
882
  case "BreakStatement":
884
883
  return [{ type: "BreakStatement", ...spanOf(node) }];
885
884
  case "ContinueStatement":
@@ -1128,63 +1127,37 @@ end
1128
1127
  ...spanOf(node)
1129
1128
  };
1130
1129
  }
1131
- /** `const a, { b } = x, y`. In "assign" mode — a module's top level, where
1132
- * every name is declared up front — nothing is declared, only assigned. */
1130
+ /** `const a = x`, `const { a, b } = x`. In "assign" mode — a module's top
1131
+ * level, where every name is declared up front — nothing is declared,
1132
+ * only assigned. */
1133
1133
  variableDeclaration(node, mode) {
1134
- if (mode === "assign" && !node.init.length) {
1135
- const exported = node.names.flatMap(identifierPatterns).filter((p) => this.isRewritten(p));
1134
+ const target = declared(node.name);
1135
+ if (!node.init) {
1136
+ const names = identifierPatterns(target);
1137
+ if (mode === "declare") return [local(names.map((p) => this.name(p.name)), [])];
1138
+ const exported = names.filter((p) => this.isRewritten(p));
1136
1139
  return exported.length ? [assign(exported.map((p) => this.reference(p)), exported.map(() => nil()))] : [];
1137
1140
  }
1138
- const init = this.values(node.init);
1139
- if (node.names.every((n) => n.type === "IdentifierPattern")) {
1140
- const names2 = node.names;
1141
- return mode === "declare" ? [local(names2.map((n) => this.name(n.name)), init)] : [assign(names2.map((n) => this.reference(n)), init)];
1142
- }
1143
- const only = node.names.length === 1 && node.init.length === 1 ? node.names[0] : void 0;
1144
- if (only && only.type !== "IdentifierPattern") {
1145
- if (init[0].type === "Identifier") return this.destructure(only, init[0], mode);
1146
- const temp = this.names.fresh("ref");
1147
- return [local([temp], init), ...this.destructure(only, identifier(temp), mode)];
1148
- }
1149
- const after = [];
1150
- const names = [];
1151
- const targets = [];
1152
- for (const target of node.names) {
1153
- if (target.type === "IdentifierPattern") {
1154
- names.push(this.name(target.name));
1155
- targets.push(this.reference(target));
1156
- continue;
1157
- }
1158
- const temp = this.names.fresh("ref");
1159
- names.push(temp);
1160
- targets.push(identifier(temp));
1161
- after.push(...this.destructure(target, identifier(temp), mode));
1141
+ const value = this.expression(node.init);
1142
+ if (target.type === "IdentifierPattern") {
1143
+ return mode === "declare" ? [local([this.name(target.name)], [value])] : [assign([this.reference(target)], [value])];
1162
1144
  }
1163
- if (mode === "declare") return [local(names, init), ...after];
1164
- const temps = node.names.flatMap((t, i) => t.type === "IdentifierPattern" ? [] : [names[i]]);
1165
- return [...temps.length ? [local(temps, [])] : [], assign(targets, init), ...after];
1145
+ if (value.type === "Identifier") return this.destructure(target, value, mode);
1146
+ const temp = this.names.fresh("ref");
1147
+ return [local([temp], [value]), ...this.destructure(target, identifier(temp), mode)];
1166
1148
  }
1167
1149
  assignment(node) {
1168
- if (node.targets.every((t) => t.type !== "ObjectPattern" && t.type !== "ArrayPattern")) {
1169
- return [assign(node.targets.map((t) => this.expression(t)), this.values(node.values))];
1150
+ const target = node.target;
1151
+ const value = this.expression(node.value);
1152
+ if (target.type !== "ObjectPattern" && target.type !== "ArrayPattern") {
1153
+ return [assign([this.expression(target)], [value])];
1170
1154
  }
1171
- const values = this.values(node.values);
1172
- const only = node.targets.length === 1 && values.length === 1 ? node.targets[0] : void 0;
1173
- if (only && (only.type === "ObjectPattern" || only.type === "ArrayPattern") && values[0].type === "Identifier") {
1174
- const statements = this.destructure(only, values[0], "assign");
1155
+ if (value.type === "Identifier") {
1156
+ const statements = this.destructure(target, value, "assign");
1175
1157
  return statements.some((s) => s.type === "LocalStatement") ? [doBlock(statements)] : statements;
1176
1158
  }
1177
- const temps = node.targets.map(() => this.names.fresh("ref"));
1178
- const body = [local(temps, values)];
1179
- node.targets.forEach((target, i) => {
1180
- const value = identifier(temps[i]);
1181
- if (target.type === "ObjectPattern" || target.type === "ArrayPattern") {
1182
- body.push(...this.destructure(target, value, "assign"));
1183
- } else {
1184
- body.push(assign([this.expression(target)], [value]));
1185
- }
1186
- });
1187
- return [doBlock(body)];
1159
+ const temp = this.names.fresh("ref");
1160
+ return [doBlock([local([temp], [value]), ...this.destructure(target, identifier(temp), "assign")])];
1188
1161
  }
1189
1162
  // --------------------------------------------------------
1190
1163
  // Destructuring
@@ -1200,6 +1173,12 @@ end
1200
1173
  const reads = [];
1201
1174
  const after = [];
1202
1175
  const bind = (target, value, fallback) => {
1176
+ if (target.type === "MemberExpression" || target.type === "IndexExpression") {
1177
+ const reference = this.expression(target);
1178
+ reads.push({ name: "", target: reference, value, temp: false });
1179
+ if (fallback) after.push(this.defaultValue(reference, fallback));
1180
+ return;
1181
+ }
1203
1182
  if (target.type === "IdentifierPattern") {
1204
1183
  const name2 = this.name(target.name);
1205
1184
  const reference = mode === "declare" ? identifier(name2) : this.reference(target);
@@ -1272,6 +1251,11 @@ end
1272
1251
  * a temporary that a nested pattern then destructures (`then`). The
1273
1252
  * statement creating it is pushed onto `after`. */
1274
1253
  restTarget(target, initial, mode, after) {
1254
+ if (target.type === "MemberExpression" || target.type === "IndexExpression") {
1255
+ const reference = this.expression(target);
1256
+ after.push(assign([reference], [initial]));
1257
+ return { target: reference, then: [] };
1258
+ }
1275
1259
  if (target.type === "IdentifierPattern") {
1276
1260
  if (mode === "declare") {
1277
1261
  const name2 = this.name(target.name);
@@ -1319,7 +1303,7 @@ end
1319
1303
  if (through !== void 0) {
1320
1304
  return call(member(this.superClassReference(node), through), [
1321
1305
  identifier(this.thisName()),
1322
- ...this.values(node.arguments)
1306
+ ...this.arguments(node.arguments)
1323
1307
  ]);
1324
1308
  }
1325
1309
  }
@@ -1404,35 +1388,29 @@ end
1404
1388
  return this.link(node, this.expression(object));
1405
1389
  }
1406
1390
  /** A call is one value in tilua, and Luau would hand on every value one
1407
- * in the last place of an argument list or an array returns. Types say
1408
- * how many a call returns; where a type says nothing — `any`, `unknown`
1409
- * — the call is kept to one: `f((g()))`. */
1410
- keptToOne(nodes, values) {
1411
- const node = nodes[nodes.length - 1];
1412
- const last = values[values.length - 1];
1413
- if (!node || !last || !expandsToMany(last)) return values;
1414
- const type = this.options.types?.typeOf.get(node);
1415
- if (type && type.kind !== "any" && type.kind !== "unknown") return values;
1416
- return [...values.slice(0, -1), parenthesized(last)];
1391
+ * returns in the last place of an argument list or a `return`: it is
1392
+ * kept to one there, `f((g()))`. Every value is taken only where the
1393
+ * program asks for them, as an array's last element: `[g()]`. */
1394
+ keptToOne(value) {
1395
+ return expandsToMany(value) ? parenthesized(value) : value;
1417
1396
  }
1418
1397
  /** Is this `scriptArgs` the language's own, not a name the file declared? */
1419
1398
  isLanguageGlobal(node) {
1420
1399
  const id = this.bindingIdOf(node);
1421
1400
  return id === void 0 || this.scopes.bindings.get(id)?.kind === "global";
1422
1401
  }
1423
- /** A list of values — a call's arguments, a `return`'s, a declaration's,
1424
- * an assignment's. `f(a, ...xs)` is Lua's own last-value expansion,
1402
+ /** A call's arguments. `f(a, ...xs)` is Lua's own last-value expansion,
1425
1403
  * `f(a, table.unpack(xs))`; a spread anywhere else cannot be, since only
1426
1404
  * the last value of a list expands, so the whole list is built as an
1427
- * array first and that is what expands.
1428
- *
1429
- * `arguments` is set for a call's arguments, where Luau hands on every
1430
- * value the last one returns — see `keptToOne`. */
1431
- values(list, asArguments = false) {
1405
+ * array first and that is what expands. A call written last is one
1406
+ * value — see `keptToOne`. */
1407
+ arguments(written) {
1408
+ const { items: list, many } = packs(written);
1432
1409
  const spreads = list.filter((a) => a.type === "SpreadElement");
1433
1410
  if (!spreads.length) {
1434
1411
  const lowered = list.map((a) => this.expression(a));
1435
- return asArguments ? this.keptToOne(list, lowered) : lowered;
1412
+ const last2 = list.length - 1;
1413
+ return lowered.length && !many.has(list[last2]) ? [...lowered.slice(0, -1), this.keptToOne(lowered[last2])] : lowered;
1436
1414
  }
1437
1415
  const unpack = (value) => call(member(this.builtin("table"), "unpack"), [value]);
1438
1416
  const last = list[list.length - 1];
@@ -1454,13 +1432,13 @@ end
1454
1432
  return index(object, this.expression(node.index));
1455
1433
  case "CallExpression": {
1456
1434
  const lowered = this.loweredGlobalCall(node);
1457
- const args2 = this.values(node.arguments, true);
1435
+ const args2 = this.arguments(node.arguments);
1458
1436
  if (!lowered) return call(object, args2);
1459
1437
  return call(calleePath(lowered.callee), [...lowered.prepend, ...args2]);
1460
1438
  }
1461
1439
  case "MethodCallExpression": {
1462
1440
  const lowered = this.loweredMethodCall(node);
1463
- const args2 = this.values(node.arguments, true);
1441
+ const args2 = this.arguments(node.arguments);
1464
1442
  if (lowered) {
1465
1443
  return call(calleePath(lowered.callee), [
1466
1444
  ...lowered.prepend,
@@ -1693,33 +1671,55 @@ end
1693
1671
  /** `[1, ...xs, 2]`. Without a spread it is a Luau sequence; with one, the
1694
1672
  * runs of plain elements and the spread arrays are joined by `concat`. */
1695
1673
  arrayExpression(node) {
1696
- if (!node.elements.some((e) => e.type === "SpreadElement")) {
1697
- const elements = node.elements;
1698
- return table(this.keptToOne(elements, elements.map((e) => this.expression(e))).map((value) => ({ type: "TableFieldPositional", value })));
1674
+ const elements = packs(node.elements).items;
1675
+ if (!elements.some((e) => e.type === "SpreadElement")) {
1676
+ return table(elements.map((e) => ({ type: "TableFieldPositional", value: this.expression(e) })));
1699
1677
  }
1700
1678
  const parts = [];
1701
1679
  let current = [];
1702
- const flush = () => {
1680
+ const flush = (last) => {
1703
1681
  if (!current.length) return;
1704
1682
  parts.push(table(current.map((e, i) => {
1705
1683
  const value = this.expression(e);
1706
- const last = i === current.length - 1;
1707
- return { type: "TableFieldPositional", value: last && expandsToMany(value) ? parenthesized(value) : value };
1684
+ return { type: "TableFieldPositional", value: last || i < current.length - 1 ? value : this.keptToOne(value) };
1708
1685
  })));
1709
1686
  current = [];
1710
1687
  };
1711
- for (const element of node.elements) {
1688
+ for (const element of elements) {
1712
1689
  if (element.type === "SpreadElement") {
1713
- flush();
1690
+ flush(false);
1714
1691
  parts.push(this.expression(element.argument));
1715
1692
  } else {
1716
1693
  current.push(element);
1717
1694
  }
1718
1695
  }
1719
- flush();
1696
+ flush(true);
1720
1697
  return call(this.helper("concat"), parts);
1721
1698
  }
1722
1699
  };
1700
+ function packs(list) {
1701
+ const items = [];
1702
+ const many = /* @__PURE__ */ new Set();
1703
+ const add = (elements) => {
1704
+ for (const element of elements) {
1705
+ if (element.type === "SpreadElement" && element.argument.type === "ArrayExpression") {
1706
+ const inner = element.argument.elements;
1707
+ add(inner);
1708
+ const last = inner[inner.length - 1];
1709
+ if (last && (last.type === "CallExpression" || last.type === "MethodCallExpression")) many.add(last);
1710
+ } else {
1711
+ items.push(element);
1712
+ }
1713
+ }
1714
+ };
1715
+ add(list);
1716
+ items.forEach((item, i) => {
1717
+ if (!many.has(item) || i === items.length - 1) return;
1718
+ many.delete(item);
1719
+ items[i] = { type: "SpreadElement", argument: { type: "ArrayExpression", elements: [item], ...spanOf(item) }, ...spanOf(item) };
1720
+ });
1721
+ return { items, many };
1722
+ }
1723
1723
  function isLink(node) {
1724
1724
  return node.type === "MemberExpression" || node.type === "IndexExpression" || node.type === "CallExpression" || node.type === "MethodCallExpression";
1725
1725
  }
@@ -1768,10 +1768,19 @@ function memberChain(e) {
1768
1768
  }
1769
1769
  return void 0;
1770
1770
  }
1771
+ function declared(target) {
1772
+ if (target.type === "MemberExpression" || target.type === "IndexExpression") {
1773
+ throw new Error("a declaration binds names, not a member or an index");
1774
+ }
1775
+ return target;
1776
+ }
1771
1777
  function identifierPatterns(target) {
1772
1778
  switch (target.type) {
1773
1779
  case "IdentifierPattern":
1774
1780
  return [target];
1781
+ case "MemberExpression":
1782
+ case "IndexExpression":
1783
+ return [];
1775
1784
  case "ObjectPattern":
1776
1785
  return [...target.properties.flatMap((p) => identifierPatterns(p.value)), ...target.rest ? identifierPatterns(target.rest) : []];
1777
1786
  case "ArrayPattern":
@@ -1785,7 +1794,7 @@ function topLevelDeclaration(statements, name) {
1785
1794
  for (const statement of statements) {
1786
1795
  const declaration = statement.type === "ExportStatement" ? statement.declaration : statement;
1787
1796
  if (declaration.type === "VariableDeclaration") {
1788
- const pattern = declaration.names.flatMap(identifierPatterns).find((p) => p.name === name);
1797
+ const pattern = identifierPatterns(declaration.name).find((p) => p.name === name);
1789
1798
  if (pattern) return { type: "Declaration", node: pattern };
1790
1799
  } else if (declaration.type === "FunctionDeclaration" && declaration.name.name === name) {
1791
1800
  return { type: "Declaration", node: declaration.name };
package/dist/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  bundle
4
- } from "./chunk-TBSJRVKA.js";
4
+ } from "./chunk-3ZQ3EITC.js";
5
5
 
6
6
  // src/cli.ts
7
7
  import { writeFileSync } from "fs";
package/dist/index.cjs CHANGED
@@ -298,12 +298,10 @@ var Lowerer = class {
298
298
  for (const node of allNodes(this.source.body)) {
299
299
  const statement = node;
300
300
  if (statement.type === "VariableDeclaration") {
301
- statement.names.forEach((target, index2) => {
302
- const value = statement.init[index2];
303
- if (target.type !== "IdentifierPattern" || !value) return;
304
- const binding = this.bindingByDeclaration.get(target);
305
- if (binding) this.declarations.set(binding.id, value);
306
- });
301
+ const target = statement.name;
302
+ if (target.type !== "IdentifierPattern" || !statement.init) continue;
303
+ const binding = this.bindingByDeclaration.get(target);
304
+ if (binding) this.declarations.set(binding.id, statement.init);
307
305
  } else if (statement.type === "FunctionDeclaration") {
308
306
  const binding = this.bindingByDeclaration.get(statement.name);
309
307
  if (binding) this.declarations.set(binding.id, statement);
@@ -577,7 +575,7 @@ var Lowerer = class {
577
575
  const declaration = statement.declaration;
578
576
  if (declaration.type === "FunctionDeclaration" || declaration.type === "ClassDeclaration") {
579
577
  exportBinding(declaration.name, declaration.name.name);
580
- } else for (const pattern of declaration.names.flatMap(identifierPatterns)) exportBinding(pattern, pattern.name);
578
+ } else for (const pattern of identifierPatterns(declaration.name)) exportBinding(pattern, pattern.name);
581
579
  break;
582
580
  }
583
581
  case "ExportDefaultStatement":
@@ -613,7 +611,7 @@ var Lowerer = class {
613
611
  for (const statement of statements) {
614
612
  const declaration = statement.type === "ExportStatement" ? statement.declaration : statement;
615
613
  if (declaration.type === "VariableDeclaration") {
616
- for (const pattern of declaration.names.flatMap(identifierPatterns)) {
614
+ for (const pattern of identifierPatterns(declaration.name)) {
617
615
  if (!this.isRewritten(pattern)) locals.push(this.name(pattern.name));
618
616
  }
619
617
  } else if ((declaration.type === "FunctionDeclaration" || declaration.type === "ClassDeclaration") && !this.isRewritten(declaration.name)) {
@@ -710,7 +708,7 @@ var Lowerer = class {
710
708
  continue;
711
709
  }
712
710
  if (statement.type !== "VariableDeclaration") continue;
713
- const patterns = statement.names.flatMap(identifierPatterns);
711
+ const patterns = identifierPatterns(statement.name);
714
712
  const end = { line: { start: statement.line.end }, column: { start: statement.column.end } };
715
713
  const needed = patterns.some((pattern) => {
716
714
  const binding = this.bindingByDeclaration.get(pattern);
@@ -751,7 +749,7 @@ var Lowerer = class {
751
749
  const body = [];
752
750
  for (const statement of statements) {
753
751
  if (statement.type === "VariableDeclaration") {
754
- for (const pattern of statement.names.flatMap(identifierPatterns)) locals.push(this.name(pattern.name));
752
+ for (const pattern of identifierPatterns(statement.name)) locals.push(this.name(pattern.name));
755
753
  body.push(...this.variableDeclaration(statement, "assign"));
756
754
  } else if (statement.type === "FunctionDeclaration") {
757
755
  locals.push(this.name(statement.name.name));
@@ -884,12 +882,13 @@ var Lowerer = class {
884
882
  }];
885
883
  case "GenericForStatement": {
886
884
  const prelude = [];
885
+ const variable = declared(node.variable);
887
886
  let item;
888
- if (node.variable.type === "IdentifierPattern") {
889
- item = this.name(node.variable.name);
887
+ if (variable.type === "IdentifierPattern") {
888
+ item = this.name(variable.name);
890
889
  } else {
891
890
  item = this.names.fresh("item");
892
- prelude.push(...this.destructure(node.variable, identifier(item), "declare"));
891
+ prelude.push(...this.destructure(variable, identifier(item), "declare"));
893
892
  }
894
893
  const form = this.options.types?.loops.get(node) ?? { walks: "values", viaIter: false };
895
894
  let source = this.expression(node.iterator);
@@ -903,7 +902,7 @@ var Lowerer = class {
903
902
  )];
904
903
  }
905
904
  case "ReturnStatement":
906
- return [returns(node.argument ? [this.expression(node.argument)] : [])];
905
+ return [returns(node.argument ? [this.keptToOne(this.expression(node.argument))] : [])];
907
906
  case "BreakStatement":
908
907
  return [{ type: "BreakStatement", ...spanOf(node) }];
909
908
  case "ContinueStatement":
@@ -1152,63 +1151,37 @@ end
1152
1151
  ...spanOf(node)
1153
1152
  };
1154
1153
  }
1155
- /** `const a, { b } = x, y`. In "assign" mode — a module's top level, where
1156
- * every name is declared up front — nothing is declared, only assigned. */
1154
+ /** `const a = x`, `const { a, b } = x`. In "assign" mode — a module's top
1155
+ * level, where every name is declared up front — nothing is declared,
1156
+ * only assigned. */
1157
1157
  variableDeclaration(node, mode) {
1158
- if (mode === "assign" && !node.init.length) {
1159
- const exported = node.names.flatMap(identifierPatterns).filter((p) => this.isRewritten(p));
1158
+ const target = declared(node.name);
1159
+ if (!node.init) {
1160
+ const names = identifierPatterns(target);
1161
+ if (mode === "declare") return [local(names.map((p) => this.name(p.name)), [])];
1162
+ const exported = names.filter((p) => this.isRewritten(p));
1160
1163
  return exported.length ? [assign(exported.map((p) => this.reference(p)), exported.map(() => nil()))] : [];
1161
1164
  }
1162
- const init = this.values(node.init);
1163
- if (node.names.every((n) => n.type === "IdentifierPattern")) {
1164
- const names2 = node.names;
1165
- return mode === "declare" ? [local(names2.map((n) => this.name(n.name)), init)] : [assign(names2.map((n) => this.reference(n)), init)];
1166
- }
1167
- const only = node.names.length === 1 && node.init.length === 1 ? node.names[0] : void 0;
1168
- if (only && only.type !== "IdentifierPattern") {
1169
- if (init[0].type === "Identifier") return this.destructure(only, init[0], mode);
1170
- const temp = this.names.fresh("ref");
1171
- return [local([temp], init), ...this.destructure(only, identifier(temp), mode)];
1172
- }
1173
- const after = [];
1174
- const names = [];
1175
- const targets = [];
1176
- for (const target of node.names) {
1177
- if (target.type === "IdentifierPattern") {
1178
- names.push(this.name(target.name));
1179
- targets.push(this.reference(target));
1180
- continue;
1181
- }
1182
- const temp = this.names.fresh("ref");
1183
- names.push(temp);
1184
- targets.push(identifier(temp));
1185
- after.push(...this.destructure(target, identifier(temp), mode));
1165
+ const value = this.expression(node.init);
1166
+ if (target.type === "IdentifierPattern") {
1167
+ return mode === "declare" ? [local([this.name(target.name)], [value])] : [assign([this.reference(target)], [value])];
1186
1168
  }
1187
- if (mode === "declare") return [local(names, init), ...after];
1188
- const temps = node.names.flatMap((t, i) => t.type === "IdentifierPattern" ? [] : [names[i]]);
1189
- return [...temps.length ? [local(temps, [])] : [], assign(targets, init), ...after];
1169
+ if (value.type === "Identifier") return this.destructure(target, value, mode);
1170
+ const temp = this.names.fresh("ref");
1171
+ return [local([temp], [value]), ...this.destructure(target, identifier(temp), mode)];
1190
1172
  }
1191
1173
  assignment(node) {
1192
- if (node.targets.every((t) => t.type !== "ObjectPattern" && t.type !== "ArrayPattern")) {
1193
- return [assign(node.targets.map((t) => this.expression(t)), this.values(node.values))];
1174
+ const target = node.target;
1175
+ const value = this.expression(node.value);
1176
+ if (target.type !== "ObjectPattern" && target.type !== "ArrayPattern") {
1177
+ return [assign([this.expression(target)], [value])];
1194
1178
  }
1195
- const values = this.values(node.values);
1196
- const only = node.targets.length === 1 && values.length === 1 ? node.targets[0] : void 0;
1197
- if (only && (only.type === "ObjectPattern" || only.type === "ArrayPattern") && values[0].type === "Identifier") {
1198
- const statements = this.destructure(only, values[0], "assign");
1179
+ if (value.type === "Identifier") {
1180
+ const statements = this.destructure(target, value, "assign");
1199
1181
  return statements.some((s) => s.type === "LocalStatement") ? [doBlock(statements)] : statements;
1200
1182
  }
1201
- const temps = node.targets.map(() => this.names.fresh("ref"));
1202
- const body = [local(temps, values)];
1203
- node.targets.forEach((target, i) => {
1204
- const value = identifier(temps[i]);
1205
- if (target.type === "ObjectPattern" || target.type === "ArrayPattern") {
1206
- body.push(...this.destructure(target, value, "assign"));
1207
- } else {
1208
- body.push(assign([this.expression(target)], [value]));
1209
- }
1210
- });
1211
- return [doBlock(body)];
1183
+ const temp = this.names.fresh("ref");
1184
+ return [doBlock([local([temp], [value]), ...this.destructure(target, identifier(temp), "assign")])];
1212
1185
  }
1213
1186
  // --------------------------------------------------------
1214
1187
  // Destructuring
@@ -1224,6 +1197,12 @@ end
1224
1197
  const reads = [];
1225
1198
  const after = [];
1226
1199
  const bind = (target, value, fallback) => {
1200
+ if (target.type === "MemberExpression" || target.type === "IndexExpression") {
1201
+ const reference = this.expression(target);
1202
+ reads.push({ name: "", target: reference, value, temp: false });
1203
+ if (fallback) after.push(this.defaultValue(reference, fallback));
1204
+ return;
1205
+ }
1227
1206
  if (target.type === "IdentifierPattern") {
1228
1207
  const name2 = this.name(target.name);
1229
1208
  const reference = mode === "declare" ? identifier(name2) : this.reference(target);
@@ -1296,6 +1275,11 @@ end
1296
1275
  * a temporary that a nested pattern then destructures (`then`). The
1297
1276
  * statement creating it is pushed onto `after`. */
1298
1277
  restTarget(target, initial, mode, after) {
1278
+ if (target.type === "MemberExpression" || target.type === "IndexExpression") {
1279
+ const reference = this.expression(target);
1280
+ after.push(assign([reference], [initial]));
1281
+ return { target: reference, then: [] };
1282
+ }
1299
1283
  if (target.type === "IdentifierPattern") {
1300
1284
  if (mode === "declare") {
1301
1285
  const name2 = this.name(target.name);
@@ -1343,7 +1327,7 @@ end
1343
1327
  if (through !== void 0) {
1344
1328
  return call(member(this.superClassReference(node), through), [
1345
1329
  identifier(this.thisName()),
1346
- ...this.values(node.arguments)
1330
+ ...this.arguments(node.arguments)
1347
1331
  ]);
1348
1332
  }
1349
1333
  }
@@ -1428,35 +1412,29 @@ end
1428
1412
  return this.link(node, this.expression(object));
1429
1413
  }
1430
1414
  /** A call is one value in tilua, and Luau would hand on every value one
1431
- * in the last place of an argument list or an array returns. Types say
1432
- * how many a call returns; where a type says nothing — `any`, `unknown`
1433
- * — the call is kept to one: `f((g()))`. */
1434
- keptToOne(nodes, values) {
1435
- const node = nodes[nodes.length - 1];
1436
- const last = values[values.length - 1];
1437
- if (!node || !last || !expandsToMany(last)) return values;
1438
- const type = this.options.types?.typeOf.get(node);
1439
- if (type && type.kind !== "any" && type.kind !== "unknown") return values;
1440
- return [...values.slice(0, -1), parenthesized(last)];
1415
+ * returns in the last place of an argument list or a `return`: it is
1416
+ * kept to one there, `f((g()))`. Every value is taken only where the
1417
+ * program asks for them, as an array's last element: `[g()]`. */
1418
+ keptToOne(value) {
1419
+ return expandsToMany(value) ? parenthesized(value) : value;
1441
1420
  }
1442
1421
  /** Is this `scriptArgs` the language's own, not a name the file declared? */
1443
1422
  isLanguageGlobal(node) {
1444
1423
  const id = this.bindingIdOf(node);
1445
1424
  return id === void 0 || this.scopes.bindings.get(id)?.kind === "global";
1446
1425
  }
1447
- /** A list of values — a call's arguments, a `return`'s, a declaration's,
1448
- * an assignment's. `f(a, ...xs)` is Lua's own last-value expansion,
1426
+ /** A call's arguments. `f(a, ...xs)` is Lua's own last-value expansion,
1449
1427
  * `f(a, table.unpack(xs))`; a spread anywhere else cannot be, since only
1450
1428
  * the last value of a list expands, so the whole list is built as an
1451
- * array first and that is what expands.
1452
- *
1453
- * `arguments` is set for a call's arguments, where Luau hands on every
1454
- * value the last one returns — see `keptToOne`. */
1455
- values(list, asArguments = false) {
1429
+ * array first and that is what expands. A call written last is one
1430
+ * value — see `keptToOne`. */
1431
+ arguments(written) {
1432
+ const { items: list, many } = packs(written);
1456
1433
  const spreads = list.filter((a) => a.type === "SpreadElement");
1457
1434
  if (!spreads.length) {
1458
1435
  const lowered = list.map((a) => this.expression(a));
1459
- return asArguments ? this.keptToOne(list, lowered) : lowered;
1436
+ const last2 = list.length - 1;
1437
+ return lowered.length && !many.has(list[last2]) ? [...lowered.slice(0, -1), this.keptToOne(lowered[last2])] : lowered;
1460
1438
  }
1461
1439
  const unpack = (value) => call(member(this.builtin("table"), "unpack"), [value]);
1462
1440
  const last = list[list.length - 1];
@@ -1478,13 +1456,13 @@ end
1478
1456
  return index(object, this.expression(node.index));
1479
1457
  case "CallExpression": {
1480
1458
  const lowered = this.loweredGlobalCall(node);
1481
- const args = this.values(node.arguments, true);
1459
+ const args = this.arguments(node.arguments);
1482
1460
  if (!lowered) return call(object, args);
1483
1461
  return call(calleePath(lowered.callee), [...lowered.prepend, ...args]);
1484
1462
  }
1485
1463
  case "MethodCallExpression": {
1486
1464
  const lowered = this.loweredMethodCall(node);
1487
- const args = this.values(node.arguments, true);
1465
+ const args = this.arguments(node.arguments);
1488
1466
  if (lowered) {
1489
1467
  return call(calleePath(lowered.callee), [
1490
1468
  ...lowered.prepend,
@@ -1717,33 +1695,55 @@ end
1717
1695
  /** `[1, ...xs, 2]`. Without a spread it is a Luau sequence; with one, the
1718
1696
  * runs of plain elements and the spread arrays are joined by `concat`. */
1719
1697
  arrayExpression(node) {
1720
- if (!node.elements.some((e) => e.type === "SpreadElement")) {
1721
- const elements = node.elements;
1722
- return table(this.keptToOne(elements, elements.map((e) => this.expression(e))).map((value) => ({ type: "TableFieldPositional", value })));
1698
+ const elements = packs(node.elements).items;
1699
+ if (!elements.some((e) => e.type === "SpreadElement")) {
1700
+ return table(elements.map((e) => ({ type: "TableFieldPositional", value: this.expression(e) })));
1723
1701
  }
1724
1702
  const parts = [];
1725
1703
  let current = [];
1726
- const flush = () => {
1704
+ const flush = (last) => {
1727
1705
  if (!current.length) return;
1728
1706
  parts.push(table(current.map((e, i) => {
1729
1707
  const value = this.expression(e);
1730
- const last = i === current.length - 1;
1731
- return { type: "TableFieldPositional", value: last && expandsToMany(value) ? parenthesized(value) : value };
1708
+ return { type: "TableFieldPositional", value: last || i < current.length - 1 ? value : this.keptToOne(value) };
1732
1709
  })));
1733
1710
  current = [];
1734
1711
  };
1735
- for (const element of node.elements) {
1712
+ for (const element of elements) {
1736
1713
  if (element.type === "SpreadElement") {
1737
- flush();
1714
+ flush(false);
1738
1715
  parts.push(this.expression(element.argument));
1739
1716
  } else {
1740
1717
  current.push(element);
1741
1718
  }
1742
1719
  }
1743
- flush();
1720
+ flush(true);
1744
1721
  return call(this.helper("concat"), parts);
1745
1722
  }
1746
1723
  };
1724
+ function packs(list) {
1725
+ const items = [];
1726
+ const many = /* @__PURE__ */ new Set();
1727
+ const add = (elements) => {
1728
+ for (const element of elements) {
1729
+ if (element.type === "SpreadElement" && element.argument.type === "ArrayExpression") {
1730
+ const inner = element.argument.elements;
1731
+ add(inner);
1732
+ const last = inner[inner.length - 1];
1733
+ if (last && (last.type === "CallExpression" || last.type === "MethodCallExpression")) many.add(last);
1734
+ } else {
1735
+ items.push(element);
1736
+ }
1737
+ }
1738
+ };
1739
+ add(list);
1740
+ items.forEach((item, i) => {
1741
+ if (!many.has(item) || i === items.length - 1) return;
1742
+ many.delete(item);
1743
+ items[i] = { type: "SpreadElement", argument: { type: "ArrayExpression", elements: [item], ...spanOf(item) }, ...spanOf(item) };
1744
+ });
1745
+ return { items, many };
1746
+ }
1747
1747
  function isLink(node) {
1748
1748
  return node.type === "MemberExpression" || node.type === "IndexExpression" || node.type === "CallExpression" || node.type === "MethodCallExpression";
1749
1749
  }
@@ -1792,10 +1792,19 @@ function memberChain(e) {
1792
1792
  }
1793
1793
  return void 0;
1794
1794
  }
1795
+ function declared(target) {
1796
+ if (target.type === "MemberExpression" || target.type === "IndexExpression") {
1797
+ throw new Error("a declaration binds names, not a member or an index");
1798
+ }
1799
+ return target;
1800
+ }
1795
1801
  function identifierPatterns(target) {
1796
1802
  switch (target.type) {
1797
1803
  case "IdentifierPattern":
1798
1804
  return [target];
1805
+ case "MemberExpression":
1806
+ case "IndexExpression":
1807
+ return [];
1799
1808
  case "ObjectPattern":
1800
1809
  return [...target.properties.flatMap((p) => identifierPatterns(p.value)), ...target.rest ? identifierPatterns(target.rest) : []];
1801
1810
  case "ArrayPattern":
@@ -1809,7 +1818,7 @@ function topLevelDeclaration(statements, name) {
1809
1818
  for (const statement of statements) {
1810
1819
  const declaration = statement.type === "ExportStatement" ? statement.declaration : statement;
1811
1820
  if (declaration.type === "VariableDeclaration") {
1812
- const pattern = declaration.names.flatMap(identifierPatterns).find((p) => p.name === name);
1821
+ const pattern = identifierPatterns(declaration.name).find((p) => p.name === name);
1813
1822
  if (pattern) return { type: "Declaration", node: pattern };
1814
1823
  } else if (declaration.type === "FunctionDeclaration" && declaration.name.name === name) {
1815
1824
  return { type: "Declaration", node: declaration.name };
package/dist/index.js CHANGED
@@ -4,7 +4,7 @@ import {
4
4
  lower,
5
5
  program,
6
6
  resolveConfig
7
- } from "./chunk-TBSJRVKA.js";
7
+ } from "./chunk-3ZQ3EITC.js";
8
8
 
9
9
  // src/compile.ts
10
10
  import { readFileSync } from "fs";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tilua/compiler",
3
- "version": "2.2.0",
3
+ "version": "2.4.0",
4
4
  "description": "Compiles tilua to Luau: lowers the @tilua/parser AST to a luau-parser AST and prints it",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.js",
@@ -37,7 +37,7 @@
37
37
  "type": "module",
38
38
  "dependencies": {
39
39
  "luau-parser": "^1.0.3",
40
- "@tilua/parser": "^2.2.0"
40
+ "@tilua/parser": "^2.4.0"
41
41
  },
42
42
  "devDependencies": {
43
43
  "@types/node": "^22.20.2",