@tilua/compiler 1.0.2 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -137,144 +137,6 @@ function isLuauName(name) {
137
137
  return /^[A-Za-z_][A-Za-z0-9_]*$/.test(name) && !LUAU_KEYWORDS.has(name);
138
138
  }
139
139
 
140
- // src/console.ts
141
- var CONSOLE_RUNTIME = `
142
- local __NAME__ = (function()
143
- local console = {}
144
- local MAX_DEPTH = 4
145
- local ESCAPES = { ["\\n"] = "\\\\n", ["\\r"] = "\\\\r", ["\\t"] = "\\\\t", ["\\""] = "\\\\\\"", ["\\\\"] = "\\\\\\\\" }
146
-
147
- local function quote(text)
148
- return '"' .. text:gsub('[%c"\\\\]', function(c) return ESCAPES[c] or ("\\\\%d"):format(c:byte()) end) .. '"'
149
- end
150
-
151
- -- A table with 1..n and nothing else prints as a list, as it was written.
152
- local function isArray(value)
153
- local count = 0
154
- for key in pairs(value) do
155
- if type(key) ~= "number" then return false end
156
- count = count + 1
157
- end
158
- return count == #value
159
- end
160
-
161
- local function isIdentifier(key)
162
- return type(key) == "string" and key:match("^[A-Za-z_][A-Za-z0-9_]*$") ~= nil
163
- end
164
-
165
- local render
166
-
167
- local function renderTable(value, meta, seen, depth, indent)
168
- if seen[value] then return "<cycle>" end
169
- if depth > MAX_DEPTH then return "..." end
170
- seen[value] = true
171
- local inner = indent .. " "
172
- local parts = {}
173
- if isArray(value) then
174
- for i = 1, #value do
175
- parts[#parts + 1] = inner .. render(value[i], nil, seen, depth + 1, inner)
176
- end
177
- else
178
- local keys = {}
179
- for key in pairs(value) do keys[#keys + 1] = key end
180
- table.sort(keys, function(a, b) return tostring(a) < tostring(b) end)
181
- for _, key in ipairs(keys) do
182
- local name = isIdentifier(key) and key or ("[" .. render(key, nil, seen, depth + 1, inner) .. "]")
183
- parts[#parts + 1] = inner .. name .. " = " .. render(value[key], nil, seen, depth + 1, inner)
184
- end
185
- end
186
- seen[value] = nil
187
- if #parts == 0 then return "{}" end
188
- local open = isArray(value) and "[" or "{"
189
- local close = isArray(value) and "]" or "}"
190
- return open .. "\\n" .. table.concat(parts, ",\\n") .. "\\n" .. indent .. close
191
- end
192
-
193
- -- \`meta\` is what the compiler knew about this value and the runtime cannot:
194
- -- a function's type, and the code it was written as.
195
- render = function(value, meta, seen, depth, indent)
196
- local kind = type(value)
197
- if kind == "string" then return quote(value) end
198
- if kind == "number" or kind == "boolean" or kind == "nil" then return tostring(value) end
199
- if kind == "function" then
200
- if meta == nil then return "function" end
201
- if meta[2] == nil or meta[2] == "" then return meta[1] end
202
- return meta[1] .. " " .. meta[2]
203
- end
204
- if kind == "table" then
205
- local shown = rawget(value, "__tostring") == nil and getmetatable(value)
206
- -- A value with its own \`__tostring\` (a Roblox Vector3, a class
207
- -- instance that says how it reads) is trusted to describe itself.
208
- if shown ~= nil and rawget(shown, "__tostring") ~= nil then return tostring(value) end
209
- return renderTable(value, meta, seen, depth, indent)
210
- end
211
- return tostring(value)
212
- end
213
-
214
- local function describe(meta, index, value)
215
- return render(value, meta ~= nil and meta[index] or nil, {}, 1, "")
216
- end
217
-
218
- local function join(meta, count, ...)
219
- local parts = {}
220
- for i = 1, count do
221
- parts[#parts + 1] = describe(meta, i, (select(i, ...)))
222
- end
223
- return table.concat(parts, " ")
224
- end
225
-
226
- -- A line of the bundle, as the place in the project that wrote it.
227
- local lines = __LINES__
228
- local function place(line)
229
- local origin = lines ~= nil and lines[tonumber(line)]
230
- if origin == nil then return nil end
231
- return origin[1] .. ":" .. tostring(origin[2])
232
- end
233
-
234
- -- Luau names the bundle in every frame; the project's files are what the
235
- -- reader knows. Frames the map has nothing for are dropped: they are this
236
- -- runtime's own, and the bundle's.
237
- local function trace(level)
238
- local raw = debug.traceback(nil, level)
239
- local out = {}
240
- for frame in raw:gmatch("[^\\n]+") do
241
- local line = frame:match(":(%d+)")
242
- local at = line ~= nil and place(line) or nil
243
- if at ~= nil then
244
- local name = frame:match("function ([%w_.:]+)")
245
- out[#out + 1] = " at " .. at .. (name ~= nil and (" (" .. name .. ")") or "")
246
- end
247
- end
248
- return table.concat(out, "\\n")
249
- end
250
-
251
- -- Never on the caller's thread: reporting a problem must not become one.
252
- local detach = (task ~= nil and task.spawn) or function(f) return f() end
253
-
254
- local function raise(prefix, meta, count, ...)
255
- local message = prefix .. join(meta, count, ...)
256
- local where = trace(3)
257
- detach(function()
258
- error(where ~= "" and (message .. "\\n" .. where) or message, 0)
259
- end)
260
- end
261
-
262
- function console.log(meta, ...)
263
- print(join(meta, select("#", ...), ...))
264
- end
265
-
266
- function console.warn(meta, ...)
267
- raise("[warn] ", meta, select("#", ...), ...)
268
- end
269
-
270
- function console.error(meta, ...)
271
- raise("", meta, select("#", ...), ...)
272
- end
273
-
274
- return console
275
- end)()
276
- `;
277
-
278
140
  // src/names.ts
279
141
  var Names = class _Names {
280
142
  constructor(taken) {
@@ -354,14 +216,18 @@ var Lowerer = class {
354
216
  const body = this.options.module ? this.module(this.options.module) : this.topLevel(this.source.body.statements);
355
217
  const helpers = this.helperDefinitions();
356
218
  const captured = [...this.builtins].map(([global, local2]) => local([local2], [identifier(global)]));
219
+ const scriptArgs = this.usesScriptArgs && !this.options.module ? [local(["scriptArgs"], [table([{ type: "TableFieldPositional", value: vararg() }])])] : [];
357
220
  return {
358
- statements: [...captured, ...helpers, ...this.classRuntimeStatements(), ...body],
221
+ statements: [...scriptArgs, ...captured, ...helpers, ...this.classRuntimeStatements(), ...body],
359
222
  exportsName: this.exportsName,
360
223
  module: this.info,
361
224
  diagnostics: this.diagnostics,
362
- origins: this.origins
225
+ origins: this.origins,
226
+ usesScriptArgs: this.usesScriptArgs
363
227
  };
364
228
  }
229
+ /** Whether the file reads `scriptArgs`, the language's own global. */
230
+ usesScriptArgs = false;
365
231
  report(node, message) {
366
232
  this.diagnostics.push({ message, line: node.line.start, column: node.column.start });
367
233
  }
@@ -422,60 +288,9 @@ var Lowerer = class {
422
288
  }
423
289
  loweringNames = /* @__PURE__ */ new Map();
424
290
  loweringUsed = [];
425
- /** What a library says `receiver:method(...)` is. The last library
426
- * loaded is asked first, so a project's own library can answer for a
427
- * method an earlier one also claims; no answer leaves an ordinary Luau
428
- * method call, which is what a value that answers to the method itself
429
- * wants (`text:upper()`). */
430
- /** `console:log(a, f)` and its two siblings.
431
- *
432
- * Handled here rather than through a library's lowering because what they
433
- * need is not a different callee but *more than was written*: a function
434
- * prints as its type and its code, and neither survives to runtime. The
435
- * compiler is the only thing that has both, so it passes them along:
436
- *
437
- * console:log(a, double)
438
- * -- becomes
439
- * tilua_console.log({ [2] = { "(x: number) => number", "(x: number) => x * 2" } }, a, double)
440
- *
441
- * The meta table is `nil` when nothing was worth saying, which is most
442
- * calls. `console` itself is never passed: it holds nothing. */
443
- consoleCall(node) {
444
- if (node.object.type !== "Identifier" || node.object.name !== "console") return void 0;
445
- const method = node.method.name;
446
- if (method !== "log" && method !== "warn" && method !== "error") return void 0;
447
- const binding = this.scopes.bindingOf.get(node.object);
448
- if (binding !== void 0 && this.scopes.bindings.get(binding)?.kind !== "global") return void 0;
449
- const meta = this.consoleMeta(node.arguments);
450
- return call(
451
- member(this.helper("console"), method),
452
- [meta ?? nil(), ...this.values(node.arguments)]
453
- );
454
- }
455
- /** What the compiler knows about the arguments and the runtime cannot:
456
- * for each function argument, the type it was inferred as and the code it
457
- * was written as. Keyed by position, so an argument that needs nothing
458
- * costs nothing. */
459
- consoleMeta(args) {
460
- const fields = [];
461
- args.forEach((argument, index2) => {
462
- const type = this.options.types?.typeOf.get(argument);
463
- if (!type || !isFunctionType(type)) return;
464
- const written = this.writtenSource(this.declarationOf(argument) ?? argument);
465
- fields.push({
466
- type: "TableFieldComputed",
467
- key: number(index2 + 1),
468
- value: table([
469
- { type: "TableFieldPositional", value: string((0, import_parser2.formatType)(type)) },
470
- { type: "TableFieldPositional", value: string(written) }
471
- ])
472
- });
473
- });
474
- return fields.length ? table(fields) : void 0;
475
- }
476
291
  /** What a name was declared as: the value of `const f = ...`, or the whole
477
- * `function f() { ... }`. Built once, on the first `console` call that
478
- * needs it — most files have none. */
292
+ * `function f() { ... }`. Built once, on the first call a library asks
293
+ * about — most files have none. */
479
294
  declarationOf(argument) {
480
295
  if (argument.type !== "Identifier") return void 0;
481
296
  if (!this.declarations) {
@@ -514,21 +329,131 @@ var Lowerer = class {
514
329
  const trim = (line) => line.startsWith(indent) ? line.slice(indent.length) : line;
515
330
  return [first, ...middle.map(trim), trim(last)].join("\n");
516
331
  }
332
+ /** Where `node` was written, as a library is told it. */
333
+ callSite(node) {
334
+ return { file: this.options.file, line: node.line.start, column: node.column.start };
335
+ }
336
+ /** What the compiler knows about each argument and the runtime cannot:
337
+ * its type, and the code it — or what it names — was written as. Built
338
+ * only if a library reads it. */
339
+ argumentInfo(args) {
340
+ return args.map((argument) => {
341
+ const declaration = this.declarationOf(argument);
342
+ const type = this.options.types?.typeOf.get(argument);
343
+ return {
344
+ type,
345
+ typeText: type ? (0, import_parser2.formatType)(type) : void 0,
346
+ source: this.writtenSource(argument),
347
+ declaration: declaration ? this.writtenSource(declaration) : void 0,
348
+ spread: argument.type === "SpreadElement"
349
+ };
350
+ });
351
+ }
352
+ /** The name a global is read by at `node`, or `undefined` when `node` is
353
+ * not a global — a local of the same name is the author's. */
354
+ globalName(node) {
355
+ if (node.type === "MemberExpression" && !node.optional) {
356
+ const base = this.globalName(node.object);
357
+ return base === void 0 ? void 0 : `${base}.${node.property.name}`;
358
+ }
359
+ if (node.type !== "Identifier") return void 0;
360
+ const id = this.scopes.bindingOf.get(node);
361
+ if (id === void 0) return node.name;
362
+ return this.scopes.bindings.get(id)?.kind === "global" ? node.name : void 0;
363
+ }
364
+ /** What a library says `receiver:method(...)` is. The last library
365
+ * loaded is asked first, so a project's own library can answer for a
366
+ * method an earlier one also claims; no answer leaves an ordinary Luau
367
+ * method call, which is what a value that answers to the method itself
368
+ * wants (`text:upper()`). */
517
369
  loweredMethodCall(node) {
518
370
  const lowerings = this.options.lowerings;
519
- if (!lowerings?.length) return void 0;
371
+ if (!lowerings?.some((l) => l.plugin.methodCall)) return void 0;
520
372
  const receiver = this.options.types?.typeOf.get(node.object);
373
+ const receiverGlobal = this.globalName(node.object);
374
+ const info = this.lazyArguments(node.arguments);
521
375
  for (let i = lowerings.length - 1; i >= 0; i--) {
522
- const answer = lowerings[i].plugin.methodCall?.({
376
+ const plugin = lowerings[i].plugin;
377
+ if (!plugin.methodCall) continue;
378
+ const answer = plugin.methodCall({
523
379
  method: node.method.name,
524
380
  receiver,
381
+ receiverGlobal,
525
382
  argumentCount: node.arguments.length,
383
+ at: this.callSite(node.method),
384
+ get arguments() {
385
+ return info();
386
+ },
387
+ use: (runtime2) => this.loweringRuntime(i, runtime2)
388
+ });
389
+ if (answer) {
390
+ return {
391
+ callee: answer.callee,
392
+ prepend: this.prepended(answer.prepend, i, node),
393
+ passReceiver: answer.passReceiver !== false
394
+ };
395
+ }
396
+ }
397
+ return void 0;
398
+ }
399
+ /** What a library says a call to a global — `print(x)` — is. */
400
+ loweredGlobalCall(node) {
401
+ const lowerings = this.options.lowerings;
402
+ if (!lowerings?.some((l) => l.plugin.globalCall)) return void 0;
403
+ const name = this.globalName(node.callee);
404
+ if (name === void 0) return void 0;
405
+ const info = this.lazyArguments(node.arguments);
406
+ for (let i = lowerings.length - 1; i >= 0; i--) {
407
+ const plugin = lowerings[i].plugin;
408
+ if (!plugin.globalCall) continue;
409
+ const answer = plugin.globalCall({
410
+ name,
411
+ at: this.callSite(node.callee),
412
+ get arguments() {
413
+ return info();
414
+ },
415
+ use: (runtime2) => this.loweringRuntime(i, runtime2)
416
+ });
417
+ if (answer) return { callee: answer.callee, prepend: this.prepended(answer.prepend, i, node) };
418
+ }
419
+ return void 0;
420
+ }
421
+ /** What a library says a global read as a value — `local p = print` — is. */
422
+ loweredGlobalValue(node) {
423
+ const lowerings = this.options.lowerings;
424
+ if (!lowerings?.some((l) => l.plugin.globalValue)) return void 0;
425
+ const name = this.globalName(node);
426
+ if (name === void 0) return void 0;
427
+ for (let i = lowerings.length - 1; i >= 0; i--) {
428
+ const answer = lowerings[i].plugin.globalValue?.({
429
+ name,
430
+ at: this.callSite(node),
526
431
  use: (runtime2) => this.loweringRuntime(i, runtime2)
527
432
  });
528
- if (answer) return { callee: answer.callee, passReceiver: answer.passReceiver !== false };
433
+ if (answer !== void 0) return this.libraryExpression(answer, i, node);
529
434
  }
530
435
  return void 0;
531
436
  }
437
+ lazyArguments(args) {
438
+ let info;
439
+ return () => info ??= this.argumentInfo(args);
440
+ }
441
+ prepended(sources, index2, node) {
442
+ return (sources ?? []).flatMap((source) => {
443
+ const expression = this.libraryExpression(source, index2, node);
444
+ return expression ? [expression] : [];
445
+ });
446
+ }
447
+ /** Luau a library wrote as an expression. One that does not parse is the
448
+ * library's mistake, reported at the call it was written for. */
449
+ libraryExpression(source, index2, node) {
450
+ try {
451
+ return (0, import_luau_parser.parseExpressionFromSource)(source);
452
+ } catch (error) {
453
+ this.report(node, `'${this.options.lowerings[index2].from}' lowered this to Luau that does not parse: ${JSON.stringify(source)} (${error.message})`);
454
+ return void 0;
455
+ }
456
+ }
532
457
  helperDefinitions() {
533
458
  const out = [];
534
459
  const assign2 = this.helpers.get("assign");
@@ -556,12 +481,9 @@ var Lowerer = class {
556
481
  });
557
482
  continue;
558
483
  }
559
- out.push(...(0, import_luau_parser.parse)(source.replace(/__NAME__/g, name)).body.statements);
560
- }
561
- const console = this.helpers.get("console");
562
- if (console) {
563
484
  const lines = this.options.module?.lines ?? "nil";
564
- out.push(...(0, import_luau_parser.parse)(CONSOLE_RUNTIME.replace(/__NAME__/g, console).replace(/__LINES__/g, lines)).body.statements);
485
+ const fail = this.options.module?.fail ?? "nil";
486
+ out.push(...(0, import_luau_parser.parse)(source.replace(/__NAME__/g, name).replace(/__LINES__/g, lines).replace(/__FAIL__/g, fail)).body.statements);
565
487
  }
566
488
  const concat = this.helpers.get("concat");
567
489
  if (concat) {
@@ -730,7 +652,7 @@ var Lowerer = class {
730
652
  break;
731
653
  }
732
654
  case "ReturnStatement":
733
- if (declaration.arguments.length) this.report(declaration, "A module cannot return a value; export it instead");
655
+ if (declaration.argument) this.report(declaration, "A module cannot return a value; export it instead");
734
656
  body.push(returns([]));
735
657
  break;
736
658
  case "ImportStatement":
@@ -909,7 +831,7 @@ var Lowerer = class {
909
831
  case "CallStatement": {
910
832
  const chain = optionalChain(node.expression);
911
833
  if (chain) return [this.optionalCallStatement(chain)];
912
- const expression = this.expression(node.expression);
834
+ const expression = this.callValues(node.expression);
913
835
  if (expression.type !== "CallExpression" && expression.type !== "MethodCallExpression") {
914
836
  this.report(node, "A statement must be a call");
915
837
  return [];
@@ -962,23 +884,26 @@ var Lowerer = class {
962
884
  }];
963
885
  case "GenericForStatement": {
964
886
  const prelude = [];
965
- const variables = node.variables.map((target) => {
966
- if (target.type === "IdentifierPattern") return this.name(target.name);
967
- const temp = this.names.fresh("item");
968
- prelude.push(...this.destructure(target, identifier(temp), "declare"));
969
- return temp;
970
- });
971
- if (variables.length === 1 && this.options.types?.iteratesValues.has(node)) {
972
- variables.unshift(this.names.fresh("_"));
887
+ let item;
888
+ if (node.variable.type === "IdentifierPattern") {
889
+ item = this.name(node.variable.name);
890
+ } else {
891
+ item = this.names.fresh("item");
892
+ prelude.push(...this.destructure(node.variable, identifier(item), "declare"));
973
893
  }
894
+ const form = this.options.types?.loops.get(node) ?? { walks: "values", viaIter: false };
895
+ let source = this.expression(node.iterator);
896
+ if (form.viaIter) source = methodCall(source, "__iter", []);
897
+ const variables = form.walks === "values" ? [this.names.fresh("_"), item] : [item];
898
+ const iterators = form.walks === "iteration" ? [call(this.builtin("unpack"), [source, number(1), number(3)])] : [source];
974
899
  return [genericFor(
975
900
  variables,
976
- node.iterators.map((e) => this.expression(e)),
901
+ iterators,
977
902
  this.loopBody51(this.block(node.body, prelude)).statements
978
903
  )];
979
904
  }
980
905
  case "ReturnStatement":
981
- return [returns(this.values(node.arguments))];
906
+ return [returns(node.argument ? [this.expression(node.argument)] : [])];
982
907
  case "BreakStatement":
983
908
  return [{ type: "BreakStatement", ...spanOf(node) }];
984
909
  case "ContinueStatement":
@@ -1022,7 +947,7 @@ var Lowerer = class {
1022
947
  // end
1023
948
  // function Dog.speak(this) ... end
1024
949
  //
1025
- // `new Dog(x)` is `Dog.new(x)`, `dog:speak()` passes the instance
950
+ // `Dog.new(x)` builds one, `dog:speak()` passes the instance
1026
951
  // as `this`, and `super.speak()` is `Animal.speak(this)` — the
1027
952
  // base's own function, run on this instance.
1028
953
  // ============================================================
@@ -1048,6 +973,7 @@ var Lowerer = class {
1048
973
  classRuntimeStatements() {
1049
974
  if (!this.classHelpers) return [];
1050
975
  const { build, accessors } = this.classHelpers;
976
+ const metamethods = `{ ${CLASS_METAMETHODS.map((name) => `"${name}"`).join(", ")} }`;
1051
977
  return (0, import_luau_parser.parse)(`
1052
978
  local function ${build}(base)
1053
979
  local class = { __getters = {}, __setters = {} }
@@ -1058,6 +984,9 @@ local function ${build}(base)
1058
984
  setmetatable(class, { __index = base })
1059
985
  setmetatable(class.__getters, { __index = base.__getters })
1060
986
  setmetatable(class.__setters, { __index = base.__setters })
987
+ for _, key in ipairs(${metamethods}) do
988
+ class[key] = base[key]
989
+ end
1061
990
  end
1062
991
  return class
1063
992
  end
@@ -1120,6 +1049,7 @@ end
1120
1049
  }
1121
1050
  break;
1122
1051
  case "ClassMethod":
1052
+ if (member2.isAbstract) break;
1123
1053
  out.push(this.functionStatement(
1124
1054
  member(self, member2.name.name),
1125
1055
  member2,
@@ -1141,7 +1071,7 @@ end
1141
1071
  }
1142
1072
  }
1143
1073
  out.push(this.classInit(node, self));
1144
- out.push(this.classNew(node, self));
1074
+ if (!node.isAbstract) out.push(this.classNew(node, self));
1145
1075
  out.push(callStatement(call(identifier(accessors), [self])));
1146
1076
  } finally {
1147
1077
  this.classContext = previous;
@@ -1419,7 +1349,11 @@ end
1419
1349
  }
1420
1350
  switch (node.type) {
1421
1351
  case "Identifier":
1422
- return this.reference(node);
1352
+ if (node.name === "scriptArgs" && this.isLanguageGlobal(node)) {
1353
+ this.usesScriptArgs = true;
1354
+ return identifier("scriptArgs");
1355
+ }
1356
+ return this.loweredGlobalValue(node) ?? this.reference(node);
1423
1357
  case "NilLiteral":
1424
1358
  return nil();
1425
1359
  case "BooleanLiteral":
@@ -1428,8 +1362,6 @@ end
1428
1362
  return number(node.value, this.numberRaw(node));
1429
1363
  case "StringLiteral":
1430
1364
  return string(node.value);
1431
- case "VarargExpression":
1432
- return vararg();
1433
1365
  // Only a recovering parse makes one, and a syntax error stops the build.
1434
1366
  case "ErrorExpression":
1435
1367
  this.report(node, "Syntax error");
@@ -1450,11 +1382,6 @@ end
1450
1382
  }
1451
1383
  case "UnaryExpression":
1452
1384
  return unary(node.operator, this.expression(node.argument));
1453
- case "NewExpression":
1454
- return call(
1455
- member(this.expression(node.callee), "new"),
1456
- this.values(node.arguments)
1457
- );
1458
1385
  case "SuperExpression":
1459
1386
  return this.superClassReference(node);
1460
1387
  case "SpreadElement":
@@ -1462,12 +1389,15 @@ end
1462
1389
  return nil();
1463
1390
  case "ClassExpression":
1464
1391
  return this.classExpression(node);
1465
- case "MemberExpression":
1466
- case "IndexExpression":
1467
1392
  case "CallExpression":
1468
- case "MethodCallExpression": {
1393
+ case "MethodCallExpression":
1394
+ return this.callValues(node);
1395
+ case "MemberExpression":
1396
+ case "IndexExpression": {
1469
1397
  const chain = optionalChain(node);
1470
1398
  if (chain) return this.optionalChainExpression(chain);
1399
+ const global = node.type === "MemberExpression" ? this.loweredGlobalValue(node) : void 0;
1400
+ if (global) return global;
1471
1401
  return this.link(node, this.expression(linkObject(node)));
1472
1402
  }
1473
1403
  case "ParenthesizedExpression":
@@ -1487,14 +1417,47 @@ end
1487
1417
  };
1488
1418
  }
1489
1419
  }
1420
+ /** A call. */
1421
+ callValues(node) {
1422
+ const chain = optionalChain(node);
1423
+ if (chain) return this.optionalChainExpression(chain);
1424
+ const object = linkObject(node);
1425
+ if (node.type === "CallExpression" && object.type === "Identifier") {
1426
+ return this.link(node, this.reference(object));
1427
+ }
1428
+ return this.link(node, this.expression(object));
1429
+ }
1430
+ /** 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)];
1441
+ }
1442
+ /** Is this `scriptArgs` the language's own, not a name the file declared? */
1443
+ isLanguageGlobal(node) {
1444
+ const id = this.bindingIdOf(node);
1445
+ return id === void 0 || this.scopes.bindings.get(id)?.kind === "global";
1446
+ }
1490
1447
  /** A list of values — a call's arguments, a `return`'s, a declaration's,
1491
1448
  * an assignment's. `f(a, ...xs)` is Lua's own last-value expansion,
1492
1449
  * `f(a, table.unpack(xs))`; a spread anywhere else cannot be, since only
1493
1450
  * the last value of a list expands, so the whole list is built as an
1494
- * array first and that is what expands. */
1495
- values(list) {
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) {
1496
1456
  const spreads = list.filter((a) => a.type === "SpreadElement");
1497
- if (!spreads.length) return list.map((a) => this.expression(a));
1457
+ if (!spreads.length) {
1458
+ const lowered = list.map((a) => this.expression(a));
1459
+ return asArguments ? this.keptToOne(list, lowered) : lowered;
1460
+ }
1498
1461
  const unpack = (value) => call(member(this.builtin("table"), "unpack"), [value]);
1499
1462
  const last = list[list.length - 1];
1500
1463
  if (spreads.length === 1 && last.type === "SpreadElement") {
@@ -1513,15 +1476,21 @@ end
1513
1476
  return member(object, node.property.name);
1514
1477
  case "IndexExpression":
1515
1478
  return index(object, this.expression(node.index));
1516
- case "CallExpression":
1517
- return call(object, this.values(node.arguments));
1479
+ case "CallExpression": {
1480
+ const lowered = this.loweredGlobalCall(node);
1481
+ const args = this.values(node.arguments, true);
1482
+ if (!lowered) return call(object, args);
1483
+ return call(calleePath(lowered.callee), [...lowered.prepend, ...args]);
1484
+ }
1518
1485
  case "MethodCallExpression": {
1519
- const consoleCall = this.consoleCall(node);
1520
- if (consoleCall) return consoleCall;
1521
- const args = this.values(node.arguments);
1522
1486
  const lowered = this.loweredMethodCall(node);
1487
+ const args = this.values(node.arguments, true);
1523
1488
  if (lowered) {
1524
- return call(calleePath(lowered.callee), lowered.passReceiver ? [object, ...args] : args);
1489
+ return call(calleePath(lowered.callee), [
1490
+ ...lowered.prepend,
1491
+ ...lowered.passReceiver ? [object] : [],
1492
+ ...args
1493
+ ]);
1525
1494
  }
1526
1495
  if (!isLuauName(node.method.name)) {
1527
1496
  this.report(node.method, `'${node.method.name}' is a Luau keyword and cannot be called with ':'`);
@@ -1749,7 +1718,8 @@ end
1749
1718
  * runs of plain elements and the spread arrays are joined by `concat`. */
1750
1719
  arrayExpression(node) {
1751
1720
  if (!node.elements.some((e) => e.type === "SpreadElement")) {
1752
- return table(node.elements.map((e) => ({ type: "TableFieldPositional", value: this.expression(e) })));
1721
+ const elements = node.elements;
1722
+ return table(this.keptToOne(elements, elements.map((e) => this.expression(e))).map((value) => ({ type: "TableFieldPositional", value })));
1753
1723
  }
1754
1724
  const parts = [];
1755
1725
  let current = [];
@@ -1934,10 +1904,6 @@ function mentions(expression, name) {
1934
1904
  walk(expression);
1935
1905
  return found;
1936
1906
  }
1937
- function isFunctionType(type) {
1938
- if (type.kind === "function") return true;
1939
- return type.kind === "intersection" && type.types.some((t) => t.kind === "function");
1940
- }
1941
1907
  function allNodes(root) {
1942
1908
  const out = [];
1943
1909
  const visit = (value) => {
@@ -1952,6 +1918,24 @@ function allNodes(root) {
1952
1918
  visit(root);
1953
1919
  return out;
1954
1920
  }
1921
+ var CLASS_METAMETHODS = [
1922
+ "__add",
1923
+ "__sub",
1924
+ "__mul",
1925
+ "__div",
1926
+ "__idiv",
1927
+ "__mod",
1928
+ "__pow",
1929
+ "__unm",
1930
+ "__concat",
1931
+ "__len",
1932
+ "__eq",
1933
+ "__lt",
1934
+ "__le",
1935
+ "__call",
1936
+ "__tostring",
1937
+ "__iter"
1938
+ ];
1955
1939
 
1956
1940
  // src/linemap.ts
1957
1941
  function statementsOf(node) {
@@ -2061,16 +2045,19 @@ async function bundle(options) {
2061
2045
  const file = pending.shift();
2062
2046
  if (modules.has(file)) continue;
2063
2047
  const source = sources.get(file);
2048
+ const shown = (0, import_node_path2.relative)(root, file).replace(/\\/g, "/");
2064
2049
  const lowered = lower(source.program, source.scopes, {
2065
2050
  names,
2066
2051
  types: analysis.types.get(file),
2067
2052
  lowerings,
2068
2053
  target: options.target ?? config?.target,
2069
2054
  source: source.text,
2055
+ file: shown,
2070
2056
  module: {
2071
2057
  name: source.name,
2072
2058
  require: requireExpression,
2073
2059
  lines: `${G}.lines`,
2060
+ fail: `${G}.fail`,
2074
2061
  resolve: (specifier) => {
2075
2062
  const target = (0, import_parser3.resolveModulePath)(file, specifier, config);
2076
2063
  if (!target || target.endsWith(".d.tilua") || !sources.has(target)) return void 0;
@@ -2080,7 +2067,6 @@ async function bundle(options) {
2080
2067
  }
2081
2068
  });
2082
2069
  for (const d of lowered.diagnostics) diagnostics.push({ file, ...d, category: "module" });
2083
- const shown = (0, import_node_path2.relative)(root, file).replace(/\\/g, "/");
2084
2070
  for (const statement of allStatements(lowered.statements)) {
2085
2071
  const line = lowered.origins.get(statement);
2086
2072
  if (line !== void 0) origins.set(statement, { file: shown, line });
@@ -2089,7 +2075,8 @@ async function bundle(options) {
2089
2075
  name: source.name,
2090
2076
  statements: lowered.statements,
2091
2077
  exportsName: lowered.exportsName,
2092
- info: lowered.module
2078
+ info: lowered.module,
2079
+ usesScriptArgs: lowered.usesScriptArgs
2093
2080
  });
2094
2081
  }
2095
2082
  for (const [file, fileDirectives] of directives) {
@@ -2114,6 +2101,9 @@ async function bundle(options) {
2114
2101
  }
2115
2102
  const program2 = (0, import_luau_parser2.parse)(runtime(G));
2116
2103
  const assignment = program2.body.statements[1];
2104
+ if ([...modules.values()].some((m) => m.usesScriptArgs)) {
2105
+ program2.body.statements.unshift(local(["scriptArgs"], [table([{ type: "TableFieldPositional", value: { type: "VarargExpression" } }])]));
2106
+ }
2117
2107
  const modulesTable = assignment.type === "AssignmentStatement" && assignment.values[0].type === "TableExpression" ? assignment.values[0].fields.find((f) => f.type === "TableFieldNamed" && f.name.name === "modules")?.value : void 0;
2118
2108
  if (!modulesTable) throw new Error("@tilua/compiler: the bundle runtime has no modules table");
2119
2109
  for (const module2 of modules.values()) {
@@ -2130,11 +2120,21 @@ async function bundle(options) {
2130
2120
  fields.push(field("load", functionExpression(functionBody([module2.exportsName], module2.statements, true))));
2131
2121
  modulesTable.fields.push({ type: "TableFieldComputed", key: string(module2.name), value: table(fields) });
2132
2122
  }
2133
- const start = call(requireExpression, [string(sources.get(entry).name)]);
2134
- program2.body.statements.push(modules.get(entry).info.exports ? returns([start]) : callStatement(start));
2123
+ const ok = names.fresh("ok");
2124
+ const result = names.fresh("result");
2125
+ const entryStatements = (0, import_luau_parser2.parse)(`
2126
+ local ${ok}, ${result} = xpcall(function()
2127
+ return ${G}.require(${JSON.stringify(sources.get(entry).name)})
2128
+ end, ${G}.fail)
2129
+ if not ${ok} then
2130
+ error(${result}, 0)
2131
+ end
2132
+ ${modules.get(entry).info.exports ? `return ${result}` : ""}
2133
+ `).body.statements;
2134
+ program2.body.statements.push(...entryStatements);
2135
2135
  const map = buildLineMap(program2, (0, import_luau_parser2.parse)((0, import_luau_parser2.print)(program2)), origins);
2136
2136
  if (map.size) {
2137
- program2.body.statements.splice(program2.body.statements.length - 1, 0, assign(
2137
+ program2.body.statements.splice(program2.body.statements.length - entryStatements.length, 0, assign(
2138
2138
  [member(identifier(G), "lines")],
2139
2139
  [(0, import_luau_parser2.parseExpressionFromSource)(lineMapSource(map))]
2140
2140
  ));
@@ -2146,6 +2146,52 @@ function runtime(G) {
2146
2146
  local ${G}
2147
2147
  ${G} = {
2148
2148
  modules = {},
2149
+ -- Filled in just before the entry runs: bundle line -> { file, line }.
2150
+ lines = nil,
2151
+ -- What an error nothing caught says, in the project's files: the bundle's
2152
+ -- own positions in the message are replaced by the places they came from,
2153
+ -- and a traceback of those places follows. A message that is not a string
2154
+ -- is left as it is, and a position that is not this bundle's (a library
2155
+ -- that raised its error with one already mapped) is left alone.
2156
+ fail = function(message)
2157
+ if type(message) ~= "string" then
2158
+ return message
2159
+ end
2160
+ local lines = ${G}.lines
2161
+ if lines == nil then
2162
+ return debug.traceback(message, 2)
2163
+ end
2164
+ local chunk
2165
+ if debug.info ~= nil then
2166
+ chunk = debug.info(1, "s")
2167
+ else
2168
+ chunk = debug.getinfo(1, "S").short_src
2169
+ end
2170
+ local prefix = chunk:gsub("%p", "%%%0")
2171
+ local function place(line)
2172
+ local origin = lines[tonumber(line)]
2173
+ if origin == nil then
2174
+ return nil
2175
+ end
2176
+ return origin[1] .. ":" .. tostring(origin[2])
2177
+ end
2178
+ local mapped = message:gsub(prefix .. ":(%d+)", function(line)
2179
+ return place(line)
2180
+ end)
2181
+ local frames = {}
2182
+ for frame in debug.traceback("", 2):gmatch("[^\\n]+") do
2183
+ local line = frame:match(prefix .. ":(%d+)")
2184
+ local at = line ~= nil and place(line) or nil
2185
+ if at ~= nil then
2186
+ local name = frame:match("function ([%w_.:]+)")
2187
+ frames[#frames + 1] = " at " .. at .. (name ~= nil and (" (" .. name .. ")") or "")
2188
+ end
2189
+ end
2190
+ if #frames == 0 then
2191
+ return mapped
2192
+ end
2193
+ return mapped .. "\\n" .. table.concat(frames, "\\n")
2194
+ end,
2149
2195
  records = {},
2150
2196
  require = function(name)
2151
2197
  local record = ${G}.records[name]