@tilua/compiler 2.4.0 → 3.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.
@@ -21,10 +21,10 @@ var nil = () => ({ type: "NilLiteral", ...span() });
21
21
  var boolean = (value) => ({ type: "BooleanLiteral", value, ...span() });
22
22
  var number = (value, raw = String(value)) => ({ type: "NumberLiteral", value, raw, ...span() });
23
23
  var string = (value) => ({ type: "StringLiteral", value, raw: "", ...span() });
24
- var member = (object, property) => isLuauName(property) ? { type: "MemberExpression", object, property: identifier(property), ...span() } : index(object, string(property));
25
- var index = (object, key) => ({ type: "IndexExpression", object, index: key, ...span() });
24
+ var member = (object2, property) => isLuauName(property) ? { type: "MemberExpression", object: object2, property: identifier(property), ...span() } : index(object2, string(property));
25
+ var index = (object2, key) => ({ type: "IndexExpression", object: object2, index: key, ...span() });
26
26
  var call = (callee, args) => ({ type: "CallExpression", callee, arguments: args, ...span() });
27
- var methodCall = (object, method, args) => ({ type: "MethodCallExpression", object, method: identifier(method), arguments: args, ...span() });
27
+ var methodCall = (object2, method, args) => ({ type: "MethodCallExpression", object: object2, method: identifier(method), arguments: args, ...span() });
28
28
  var functionExpression = (func) => ({ type: "FunctionExpression", func, ...span() });
29
29
  var binary = (operator, left, right) => ({ type: "BinaryExpression", operator, left, right, ...span() });
30
30
  var unary = (operator, argument) => ({ type: "UnaryExpression", operator, argument, ...span() });
@@ -140,6 +140,62 @@ var Names = class _Names {
140
140
  }
141
141
  };
142
142
 
143
+ // src/language.ts
144
+ import { readFileSync } from "fs";
145
+ var read = (name) => readFileSync(new URL(`../runtime/${name}`, import.meta.url), "utf8");
146
+ var implemented = (source) => new Set([...source.matchAll(/function __NAME__\.(\w+)/g)].map((m) => m[1]));
147
+ var array = read("array.luau");
148
+ var string2 = read("string.luau");
149
+ var object = read("object.luau");
150
+ var arrayMethods = implemented(array);
151
+ var stringMethods = implemented(string2);
152
+ var objectMethods = implemented(object);
153
+ function every(type, is, seen = /* @__PURE__ */ new Set()) {
154
+ if (!type || seen.has(type)) return false;
155
+ seen.add(type);
156
+ if (type.kind === "union") {
157
+ return type.types.filter((m) => !(m.kind === "primitive" && m.name === "nil")).every((m) => every(m, is, seen));
158
+ }
159
+ if (type.kind === "typeParam") return every(type.constraint, is, seen);
160
+ return is(type);
161
+ }
162
+ var isArray = (type) => every(type, (t) => t.kind === "array" || t.kind === "tuple");
163
+ var isString = (type) => every(type, (t) => t.kind === "primitive" && t.name === "string" || t.kind === "literal" && t.base === "string" || t.kind === "templateLiteral");
164
+ function objectKeys(type) {
165
+ const members = type?.kind === "union" ? type.types : type ? [type] : [];
166
+ const orders = [];
167
+ let open = false;
168
+ for (const member2 of members) {
169
+ const keys = [];
170
+ for (const part of member2.kind === "intersection" ? member2.types : [member2]) {
171
+ if (part.kind !== "object") continue;
172
+ for (const key of part.properties.keys()) if (!keys.includes(key)) keys.push(key);
173
+ if (part.indexer) open = true;
174
+ }
175
+ orders.push(keys);
176
+ }
177
+ const order = JSON.stringify(orders[0] ?? []);
178
+ const known = orders.length && orders.every((keys) => JSON.stringify(keys) === order) ? `{ ${orders[0].map((key) => JSON.stringify(key)).join(", ")} }` : "nil";
179
+ return { known, open: String(open) };
180
+ }
181
+ var plugin = {
182
+ runtime: { array, string: string2, object },
183
+ methodCall({ method, receiver, use }) {
184
+ if (isString(receiver)) {
185
+ return stringMethods.has(method) ? { callee: `${use("string")}.${method}` } : void 0;
186
+ }
187
+ if (isArray(receiver)) {
188
+ return arrayMethods.has(method) ? { callee: `${use("array")}.${method}` } : void 0;
189
+ }
190
+ if (objectMethods.has(method)) {
191
+ const keys = objectKeys(receiver);
192
+ return { callee: `${use("object")}.${method}`, prepend: [keys.known, keys.open] };
193
+ }
194
+ return void 0;
195
+ }
196
+ };
197
+ var languageLowering = { plugin, from: "tilua" };
198
+
143
199
  // src/lower.ts
144
200
  function lower(program2, scopes, options = {}) {
145
201
  return new Lowerer(program2, scopes, options).run();
@@ -323,21 +379,34 @@ var Lowerer = class {
323
379
  if (id === void 0) return node.name;
324
380
  return this.scopes.bindings.get(id)?.kind === "global" ? node.name : void 0;
325
381
  }
326
- /** What a library says `receiver:method(...)` is. The last library
327
- * loaded is asked first, so a project's own library can answer for a
328
- * method an earlier one also claims; no answer leaves an ordinary Luau
329
- * method call, which is what a value that answers to the method itself
330
- * wants (`text:upper()`). */
382
+ /** The language's own lowering, then each library's, in load order. A
383
+ * runtime is named by its lowering's place here (`loweringRuntime`). */
384
+ get lowerings() {
385
+ return this.allLowerings ??= [languageLowering, ...this.options.lowerings ?? []];
386
+ }
387
+ allLowerings;
388
+ /** What `receiver:method(...)` becomes, when Luau alone would not run it.
389
+ *
390
+ * A method the receiver's metatable gave it — the analyzer says so, and
391
+ * whose metatable it was (`methodSources`) — is asked of whoever declared
392
+ * that metatable, and only them: the language lowers its own
393
+ * (`names:filter(f)` becomes a call into its runtime), a library its own,
394
+ * and one the program declared is taken at its word. Any other method is
395
+ * a member of the receiver's own, which a library may still claim
396
+ * (`console:log`), the last library loaded first. No answer leaves an
397
+ * ordinary Luau method call: `text:upper()` reaches Lua's own. */
331
398
  loweredMethodCall(node) {
332
- const lowerings = this.options.lowerings;
333
- if (!lowerings?.some((l) => l.plugin.methodCall)) return void 0;
399
+ const lowerings = this.lowerings;
400
+ const source = this.options.types?.methodSources.get(node);
401
+ const asked = source?.origin === "language" ? [0] : source?.origin === "library" ? lowerings.flatMap((l, i) => l.library === source.library ? [i] : []) : source?.origin === "program" ? [] : lowerings.map((_, i) => i).slice(1).reverse();
402
+ if (!asked.some((i) => lowerings[i].plugin.methodCall)) return void 0;
334
403
  const receiver = this.options.types?.typeOf.get(node.object);
335
404
  const receiverGlobal = this.globalName(node.object);
336
405
  const info = this.lazyArguments(node.arguments);
337
- for (let i = lowerings.length - 1; i >= 0; i--) {
338
- const plugin = lowerings[i].plugin;
339
- if (!plugin.methodCall) continue;
340
- const answer = plugin.methodCall({
406
+ for (const i of asked) {
407
+ const plugin2 = lowerings[i].plugin;
408
+ if (!plugin2.methodCall) continue;
409
+ const answer = plugin2.methodCall({
341
410
  method: node.method.name,
342
411
  receiver,
343
412
  receiverGlobal,
@@ -360,15 +429,15 @@ var Lowerer = class {
360
429
  }
361
430
  /** What a library says a call to a global — `print(x)` — is. */
362
431
  loweredGlobalCall(node) {
363
- const lowerings = this.options.lowerings;
432
+ const lowerings = this.lowerings;
364
433
  if (!lowerings?.some((l) => l.plugin.globalCall)) return void 0;
365
434
  const name = this.globalName(node.callee);
366
435
  if (name === void 0) return void 0;
367
436
  const info = this.lazyArguments(node.arguments);
368
437
  for (let i = lowerings.length - 1; i >= 0; i--) {
369
- const plugin = lowerings[i].plugin;
370
- if (!plugin.globalCall) continue;
371
- const answer = plugin.globalCall({
438
+ const plugin2 = lowerings[i].plugin;
439
+ if (!plugin2.globalCall) continue;
440
+ const answer = plugin2.globalCall({
372
441
  name,
373
442
  at: this.callSite(node.callee),
374
443
  get arguments() {
@@ -382,7 +451,7 @@ var Lowerer = class {
382
451
  }
383
452
  /** What a library says a global read as a value — `local p = print` — is. */
384
453
  loweredGlobalValue(node) {
385
- const lowerings = this.options.lowerings;
454
+ const lowerings = this.lowerings;
386
455
  if (!lowerings?.some((l) => l.plugin.globalValue)) return void 0;
387
456
  const name = this.globalName(node);
388
457
  if (name === void 0) return void 0;
@@ -412,7 +481,7 @@ var Lowerer = class {
412
481
  try {
413
482
  return parseExpressionFromSource(source);
414
483
  } catch (error) {
415
- this.report(node, `'${this.options.lowerings[index2].from}' lowered this to Luau that does not parse: ${JSON.stringify(source)} (${error.message})`);
484
+ this.report(node, `'${this.lowerings[index2].from}' lowered this to Luau that does not parse: ${JSON.stringify(source)} (${error.message})`);
416
485
  return void 0;
417
486
  }
418
487
  }
@@ -433,8 +502,8 @@ var Lowerer = class {
433
502
  ], true)));
434
503
  }
435
504
  for (const { index: index2, runtime: runtime2, name } of this.loweringUsed) {
436
- const { plugin, from } = this.options.lowerings[index2];
437
- const source = plugin.runtime?.[runtime2];
505
+ const { plugin: plugin2, from } = this.lowerings[index2];
506
+ const source = plugin2.runtime?.[runtime2];
438
507
  if (source === void 0) {
439
508
  this.diagnostics.push({
440
509
  message: `'${from}' asked for a runtime it does not have: '${runtime2}'`,
@@ -769,6 +838,7 @@ var Lowerer = class {
769
838
  case "ExportTypeAliasStatement":
770
839
  case "DeclareStatement":
771
840
  case "DeclareClassStatement":
841
+ case "DeclareMetatableStatement":
772
842
  case "ErrorStatement":
773
843
  return [];
774
844
  case "VariableDeclaration":
@@ -1369,11 +1439,11 @@ end
1369
1439
  callValues(node) {
1370
1440
  const chain = optionalChain(node);
1371
1441
  if (chain) return this.optionalChainExpression(chain);
1372
- const object = linkObject(node);
1373
- if (node.type === "CallExpression" && object.type === "Identifier") {
1374
- return this.link(node, this.reference(object));
1442
+ const object2 = linkObject(node);
1443
+ if (node.type === "CallExpression" && object2.type === "Identifier") {
1444
+ return this.link(node, this.reference(object2));
1375
1445
  }
1376
- return this.link(node, this.expression(object));
1446
+ return this.link(node, this.expression(object2));
1377
1447
  }
1378
1448
  /** A call is one value in tilua, and Luau would hand on every value one
1379
1449
  * returns in the last place of an argument list or a `return`: it is
@@ -1412,16 +1482,16 @@ end
1412
1482
  }))];
1413
1483
  }
1414
1484
  /** One link of an access chain, read from `object`. */
1415
- link(node, object) {
1485
+ link(node, object2) {
1416
1486
  switch (node.type) {
1417
1487
  case "MemberExpression":
1418
- return member(object, node.property.name);
1488
+ return member(object2, node.property.name);
1419
1489
  case "IndexExpression":
1420
- return index(object, this.expression(node.index));
1490
+ return index(object2, this.expression(node.index));
1421
1491
  case "CallExpression": {
1422
1492
  const lowered = this.loweredGlobalCall(node);
1423
1493
  const args = this.arguments(node.arguments);
1424
- if (!lowered) return call(object, args);
1494
+ if (!lowered) return call(object2, args);
1425
1495
  return call(calleePath(lowered.callee), [...lowered.prepend, ...args]);
1426
1496
  }
1427
1497
  case "MethodCallExpression": {
@@ -1430,14 +1500,14 @@ end
1430
1500
  if (lowered) {
1431
1501
  return call(calleePath(lowered.callee), [
1432
1502
  ...lowered.prepend,
1433
- ...lowered.passReceiver ? [object] : [],
1503
+ ...lowered.passReceiver ? [object2] : [],
1434
1504
  ...args
1435
1505
  ]);
1436
1506
  }
1437
1507
  if (!isLuauName(node.method.name)) {
1438
1508
  this.report(node.method, `'${node.method.name}' is a Luau keyword and cannot be called with ':'`);
1439
1509
  }
1440
- return methodCall(object, node.method.name, args);
1510
+ return methodCall(object2, node.method.name, args);
1441
1511
  }
1442
1512
  }
1443
1513
  }
@@ -1458,8 +1528,8 @@ end
1458
1528
  }
1459
1529
  return out;
1460
1530
  }
1461
- applySegment(segment, object) {
1462
- return segment.reduce((value, link) => this.link(link, value), object);
1531
+ applySegment(segment, object2) {
1532
+ return segment.reduce((value, link) => this.link(link, value), object2);
1463
1533
  }
1464
1534
  /** `a?.b:m()` as a statement:
1465
1535
  * `do local ref = a if ref ~= nil then ref.b:m() end end` */
@@ -1535,9 +1605,9 @@ end
1535
1605
  const before = [];
1536
1606
  let target;
1537
1607
  if (node.target.type === "MemberExpression" || node.target.type === "IndexExpression") {
1538
- const object = this.expression(node.target.object);
1608
+ const object2 = this.expression(node.target.object);
1539
1609
  const objectName = this.names.fresh("target");
1540
- before.push(local([objectName], [object]));
1610
+ before.push(local([objectName], [object2]));
1541
1611
  if (node.target.type === "MemberExpression") {
1542
1612
  target = member(identifier(objectName), node.target.property.name);
1543
1613
  } else {
@@ -1918,15 +1988,15 @@ async function loadLowerings(modules) {
1918
1988
  for (const module of modules) {
1919
1989
  try {
1920
1990
  const loaded = await import(pathToFileURL(module.file).href);
1921
- const plugin = loaded.default;
1922
- if (!plugin || typeof plugin !== "object") {
1991
+ const plugin2 = loaded.default;
1992
+ if (!plugin2 || typeof plugin2 !== "object") {
1923
1993
  problems.push({
1924
1994
  file: module.file,
1925
1995
  message: `'${module.from}' lowering module has no default export`
1926
1996
  });
1927
1997
  continue;
1928
1998
  }
1929
- lowerings.push({ plugin, from: module.from });
1999
+ lowerings.push({ plugin: plugin2, from: module.from, library: module.library });
1930
2000
  } catch (error) {
1931
2001
  problems.push({
1932
2002
  file: module.file,
@@ -1938,7 +2008,7 @@ async function loadLowerings(modules) {
1938
2008
  }
1939
2009
 
1940
2010
  // src/bundle.ts
1941
- import { readFileSync, statSync } from "fs";
2011
+ import { readFileSync as readFileSync2, statSync } from "fs";
1942
2012
  import { dirname, relative, resolve as resolve2 } from "path";
1943
2013
  import {
1944
2014
  parse,
@@ -2249,7 +2319,7 @@ function parseFile(file, diagnostics, directives) {
2249
2319
  }
2250
2320
  let text;
2251
2321
  try {
2252
- text = readFileSync(file, "utf8");
2322
+ text = readFileSync2(file, "utf8");
2253
2323
  } catch {
2254
2324
  diagnostics.push({ file, message: "Cannot read file", line: 1, column: 1, category: "module" });
2255
2325
  return void 0;
@@ -2283,7 +2353,7 @@ function parsedLibrary(file) {
2283
2353
  }
2284
2354
  const cached = libraryCache.get(file);
2285
2355
  if (cached && cached.stamp === stamp) return cached.program;
2286
- const program2 = parse(readFileSync(file, "utf8"));
2356
+ const program2 = parse(readFileSync2(file, "utf8"));
2287
2357
  libraryCache.set(file, { stamp, program: program2 });
2288
2358
  return program2;
2289
2359
  }
@@ -2306,7 +2376,7 @@ function analyzeModules(modules, config) {
2306
2376
  const done = analyzed.get(file);
2307
2377
  if (done) return done;
2308
2378
  const remembered = moduleAnalyses.get(file);
2309
- if (remembered && remembered.setting === settingFor && libs.length === remembered.libs.length && remembered.libs.every((lib, i) => lib === libs[i]) && remembered.program === program2 && [...remembered.read].every(([read, stamp]) => stampOf(read) === stamp)) {
2379
+ if (remembered && remembered.setting === settingFor && libs.length === remembered.libs.length && remembered.libs.every((lib, i) => lib === libs[i]) && remembered.program === program2 && [...remembered.read].every(([read2, stamp]) => stampOf(read2) === stamp)) {
2310
2380
  analyzed.set(file, remembered.result);
2311
2381
  readWhile.set(file, remembered.read);
2312
2382
  return remembered.result;
@@ -2330,12 +2400,12 @@ function analyzeModules(modules, config) {
2330
2400
  const result = { types, diagnostics: said };
2331
2401
  if (partials === before) {
2332
2402
  analyzed.set(file, result);
2333
- const read = readWhile.get(file) ?? /* @__PURE__ */ new Map();
2403
+ const read2 = readWhile.get(file) ?? /* @__PURE__ */ new Map();
2334
2404
  const own = stampOf(file);
2335
- if (own !== void 0) read.set(file, own);
2336
- readWhile.set(file, read);
2337
- if ([...read.values()].every((stamp) => stamp !== void 0)) {
2338
- moduleAnalyses.set(file, { setting: settingFor, libs: [...libs], program: program2, read, result });
2405
+ if (own !== void 0) read2.set(file, own);
2406
+ readWhile.set(file, read2);
2407
+ if ([...read2.values()].every((stamp) => stamp !== void 0)) {
2408
+ moduleAnalyses.set(file, { setting: settingFor, libs: [...libs], program: program2, read: read2, result });
2339
2409
  }
2340
2410
  }
2341
2411
  return result;
@@ -2344,11 +2414,11 @@ function analyzeModules(modules, config) {
2344
2414
  const target = resolveModulePath(file, specifier, config);
2345
2415
  if (!target) return void 0;
2346
2416
  const noteRead = () => {
2347
- const read = readWhile.get(file) ?? /* @__PURE__ */ new Map();
2348
- readWhile.set(file, read);
2417
+ const read2 = readWhile.get(file) ?? /* @__PURE__ */ new Map();
2418
+ readWhile.set(file, read2);
2349
2419
  const stamp = stampOf(target);
2350
- if (stamp !== void 0) read.set(target, stamp);
2351
- for (const [below, belowStamp] of readWhile.get(target) ?? []) read.set(below, belowStamp);
2420
+ if (stamp !== void 0) read2.set(target, stamp);
2421
+ for (const [below, belowStamp] of readWhile.get(target) ?? []) read2.set(below, belowStamp);
2352
2422
  };
2353
2423
  if (inProgress.has(target)) {
2354
2424
  partials++;
@@ -2362,7 +2432,7 @@ function analyzeModules(modules, config) {
2362
2432
  let program2 = byFile.get(target)?.program;
2363
2433
  if (!program2) {
2364
2434
  try {
2365
- program2 = parse(readFileSync(target, "utf8"));
2435
+ program2 = parse(readFileSync2(target, "utf8"));
2366
2436
  } catch {
2367
2437
  return void 0;
2368
2438
  }