@thigasdevelopment/luam 0.19.0 → 0.19.2

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/luam.mjs CHANGED
@@ -1644,8 +1644,8 @@ Expecting one of '${allowedValues.join("', '")}'`);
1644
1644
  * @param {boolean} [combine] - if `true` or omitted, an optional value can be specified directly after the flag.
1645
1645
  * @return {Command} `this` command for chaining
1646
1646
  */
1647
- combineFlagAndOptionalValue(combine = true) {
1648
- this._combineFlagAndOptionalValue = !!combine;
1647
+ combineFlagAndOptionalValue(combine2 = true) {
1648
+ this._combineFlagAndOptionalValue = !!combine2;
1649
1649
  return this;
1650
1650
  }
1651
1651
  /**
@@ -3143,6 +3143,7 @@ var DEFAULT_ENVIRONMENT_FILE = ".env";
3143
3143
  var DEFAULT_LOCAL_ENVIRONMENT_FILE = ".env.local";
3144
3144
  var DEFAULT_ASSET_DESTINATION = ".";
3145
3145
  var DEFAULT_OUT_DIR = "build";
3146
+ var DEFAULT_CONTRACTS_DIR = ".luam/contracts";
3146
3147
  var DEFAULT_RESOURCES_DIR = "mods/deathmatch/resources";
3147
3148
  var DEFAULT_COMPILER_OPTIONS = {
3148
3149
  strict: true,
@@ -3492,14 +3493,14 @@ function readEscape(cursor) {
3492
3493
  }
3493
3494
  function scanQuotedString(cursor, diagnostics) {
3494
3495
  const position2 = cursor.position();
3495
- const quote2 = cursor.peek();
3496
+ const quote3 = cursor.peek();
3496
3497
  cursor.advance();
3497
3498
  let value = "";
3498
- while (!cursor.isEof() && cursor.peek() !== quote2 && !isLineBreak(cursor.peek())) {
3499
+ while (!cursor.isEof() && cursor.peek() !== quote3 && !isLineBreak(cursor.peek())) {
3499
3500
  value += cursor.peek() === "\\" ? readEscape(cursor) : cursor.advance();
3500
3501
  }
3501
- if (cursor.peek() !== quote2) {
3502
- const message = `Unterminated string. Close it with ${quote2}.`;
3502
+ if (cursor.peek() !== quote3) {
3503
+ const message = `Unterminated string. Close it with ${quote3}.`;
3503
3504
  diagnostics.push(createDiagnostic("lexer", "lex-unterminated-string", message, position2));
3504
3505
  return value;
3505
3506
  }
@@ -3827,19 +3828,21 @@ function parseTypeName(stream) {
3827
3828
  return { kind: "type-name", name: token.value, typeArguments, position: token.position };
3828
3829
  }
3829
3830
  function parseParameterType(stream) {
3831
+ let name = null;
3830
3832
  if (stream.check("identifier") && stream.checkAhead(1, "punctuation", ":")) {
3831
- stream.next();
3833
+ name = stream.next().value;
3832
3834
  stream.next();
3833
3835
  }
3834
- return parseTypeAnnotation(stream);
3836
+ return { name, annotation: parseTypeAnnotation(stream) };
3835
3837
  }
3836
3838
  function parseFunctionType(stream) {
3837
3839
  const position2 = stream.expect("identifier", FUNCTION_TYPE).position;
3838
3840
  if (!stream.check("punctuation", "(")) {
3839
- return { kind: "type-function", parameters: [], isVariadic: true, returnType: null, position: position2 };
3841
+ return { kind: "type-function", parameters: [], parameterNames: [], isVariadic: true, returnType: null, position: position2 };
3840
3842
  }
3841
3843
  stream.next();
3842
3844
  const parameters = [];
3845
+ const parameterNames = [];
3843
3846
  let isVariadic = false;
3844
3847
  while (!stream.check("punctuation", ")") && !stream.isEof()) {
3845
3848
  if (stream.match("operator", "...")) {
@@ -3848,7 +3851,9 @@ function parseFunctionType(stream) {
3848
3851
  parseTypeAnnotation(stream);
3849
3852
  }
3850
3853
  } else {
3851
- parameters.push(parseParameterType(stream));
3854
+ const parameter = parseParameterType(stream);
3855
+ parameters.push(parameter.annotation);
3856
+ parameterNames.push(parameter.name);
3852
3857
  }
3853
3858
  if (!stream.match("punctuation", ",")) {
3854
3859
  break;
@@ -3856,7 +3861,7 @@ function parseFunctionType(stream) {
3856
3861
  }
3857
3862
  stream.expect("punctuation", ")");
3858
3863
  const returnType = stream.match("punctuation", ":") ? parseTypeAnnotation(stream) : null;
3859
- return { kind: "type-function", parameters, isVariadic, returnType, position: position2 };
3864
+ return { kind: "type-function", parameters, parameterNames, isVariadic, returnType, position: position2 };
3860
3865
  }
3861
3866
  function parseGroupedType(stream) {
3862
3867
  const position2 = stream.expect("punctuation", "(").position;
@@ -3989,10 +3994,18 @@ function parseNamedAnnotation(stream, declaration) {
3989
3994
  if (optional === null && annotation?.kind === "type-optional") {
3990
3995
  stream.report("parse-optional-position", `Write optional ${label2} as "name?: Type", not "name: Type?".`, annotation.position);
3991
3996
  }
3997
+ if (optional !== null && annotation?.kind === "type-optional") {
3998
+ const message = `The marker on the name already allows "nil". Remove the "?" after the type.`;
3999
+ stream.report("parse-redundant-optional", message, annotation.position);
4000
+ }
3992
4001
  if (optional !== null && annotation !== null) {
3993
4002
  stream.eraseFrom(checkpoint);
3994
4003
  }
3995
- return optional === null || annotation === null ? annotation : { kind: "type-optional", element: annotation, position: optional.position };
4004
+ if (optional === null || annotation === null) {
4005
+ return annotation;
4006
+ }
4007
+ const element = annotation.kind === "type-optional" ? annotation.element : annotation;
4008
+ return { kind: "type-optional", element, position: optional.position };
3996
4009
  }
3997
4010
 
3998
4011
  // ../compiler/src/parser/function-expression.ts
@@ -4146,7 +4159,8 @@ function parsePrimary(stream) {
4146
4159
  if (token.kind === "keyword" && token.value === "new" && stream.checkAhead(1, "identifier")) {
4147
4160
  stream.next();
4148
4161
  const className = stream.next().value;
4149
- return { kind: "new-expression", className, args: parseArguments(stream), position: token.position };
4162
+ const typeArguments = parseNewTypeArguments(stream);
4163
+ return { kind: "new-expression", className, typeArguments, args: parseArguments(stream), position: token.position };
4150
4164
  }
4151
4165
  if (token.kind === "keyword" && CALLABLE_KEYWORDS.has(token.value) && stream.checkAhead(1, "punctuation", "(")) {
4152
4166
  stream.next();
@@ -4178,6 +4192,20 @@ function parsePrimary(stream) {
4178
4192
  }
4179
4193
  throw stream.error(`Unexpected "${stream.describeCurrent()}" in expression.`, "parse-unexpected-token");
4180
4194
  }
4195
+ function parseNewTypeArguments(stream) {
4196
+ const args = [];
4197
+ if (!stream.check("operator", "<")) {
4198
+ return args;
4199
+ }
4200
+ const checkpoint = stream.checkpoint();
4201
+ stream.next();
4202
+ do {
4203
+ args.push(parseTypeAnnotation(stream));
4204
+ } while (stream.match("punctuation", ","));
4205
+ stream.expect("operator", ">");
4206
+ stream.eraseFrom(checkpoint);
4207
+ return args;
4208
+ }
4181
4209
  function parseArguments(stream) {
4182
4210
  if (stream.check("string") || stream.check("template") || stream.check("punctuation", "{")) {
4183
4211
  return [parsePrimary(stream)];
@@ -4464,8 +4492,23 @@ var TokenStream = class {
4464
4492
  // ../compiler/src/parser/declarations.ts
4465
4493
  var DECLARATION_NAMES = /* @__PURE__ */ new Set(["class", "interface", "enum"]);
4466
4494
  var CLASS_MODIFIERS = /* @__PURE__ */ new Set(["extends", "implements"]);
4495
+ function skipTypeParameters(stream, offset) {
4496
+ if (!stream.checkAhead(offset, "operator", "<")) {
4497
+ return offset;
4498
+ }
4499
+ let depth = 0;
4500
+ let index = offset;
4501
+ do {
4502
+ const token = stream.peek(index);
4503
+ depth += token.kind === "operator" && token.value === "<" ? 1 : 0;
4504
+ depth -= token.kind === "operator" && token.value === ">" ? 1 : 0;
4505
+ index += 1;
4506
+ } while (depth > 0 && stream.peek(index).kind !== "eof");
4507
+ return index;
4508
+ }
4467
4509
  function isClassHeader(stream, offset = 0) {
4468
- return stream.checkAhead(offset + 2, "punctuation", "{") || stream.checkAhead(offset + 2, "keyword", "extends") || stream.checkAhead(offset + 2, "keyword", "implements");
4510
+ const after = skipTypeParameters(stream, offset + 2);
4511
+ return stream.checkAhead(after, "punctuation", "{") || stream.checkAhead(after, "keyword", "extends") || stream.checkAhead(after, "keyword", "implements");
4469
4512
  }
4470
4513
  function isDeclarationStart(stream) {
4471
4514
  let offset = 0;
@@ -4521,7 +4564,7 @@ function parseDecorators(stream) {
4521
4564
  }
4522
4565
  return decorators;
4523
4566
  }
4524
- function parseClassMethod(stream, token, decorators) {
4567
+ function parseClassMethod(stream, token, decorators, isStatic) {
4525
4568
  stream.expect("operator", "=");
4526
4569
  const expression = parseFunctionExpression(stream);
4527
4570
  return {
@@ -4529,6 +4572,7 @@ function parseClassMethod(stream, token, decorators) {
4529
4572
  name: token.value,
4530
4573
  isConstructor: token.value === "constructor",
4531
4574
  isSynthetic: false,
4575
+ isStatic,
4532
4576
  parameters: expression.parameters,
4533
4577
  returnAnnotation: expression.returnAnnotation,
4534
4578
  body: expression.body,
@@ -4536,7 +4580,7 @@ function parseClassMethod(stream, token, decorators) {
4536
4580
  position: token.position
4537
4581
  };
4538
4582
  }
4539
- function parseBraceClassMethod(stream, token, decorators) {
4583
+ function parseBraceClassMethod(stream, token, decorators, isStatic) {
4540
4584
  const parameters = parseParameters(stream);
4541
4585
  const returnAnnotation = parseOptionalAnnotation(stream);
4542
4586
  const body = parseBraceBlock(stream);
@@ -4545,6 +4589,7 @@ function parseBraceClassMethod(stream, token, decorators) {
4545
4589
  name: token.value,
4546
4590
  isConstructor: token.value === "constructor",
4547
4591
  isSynthetic: false,
4592
+ isStatic,
4548
4593
  parameters,
4549
4594
  returnAnnotation,
4550
4595
  body,
@@ -4563,25 +4608,70 @@ function expectClassFieldBoundary(stream, token) {
4563
4608
  }
4564
4609
  throw stream.error(`Expected a line break or separator after class member "${token.value}".`, "parse-unexpected-token");
4565
4610
  }
4611
+ var STATIC_MODIFIER = "static";
4612
+ function parseStaticModifier(stream) {
4613
+ if (!stream.check("identifier", STATIC_MODIFIER)) {
4614
+ return false;
4615
+ }
4616
+ const modifier = stream.current();
4617
+ if (!stream.checkName(1) || stream.peek(1).position.line !== modifier.position.line) {
4618
+ return false;
4619
+ }
4620
+ const checkpoint = stream.checkpoint();
4621
+ stream.next();
4622
+ stream.eraseToCurrent(checkpoint);
4623
+ return true;
4624
+ }
4566
4625
  function parseClassMember(stream) {
4567
4626
  const decorators = parseDecorators(stream);
4627
+ const isStatic = parseStaticModifier(stream);
4568
4628
  const token = stream.expectName();
4569
4629
  if (stream.check("punctuation", "(")) {
4570
4630
  stream.report("parse-class-method-form", `Write class member "${token.value}" as "${token.value} = function (...) ... end".`, token.position);
4571
- return parseBraceClassMethod(stream, token, decorators);
4631
+ return parseBraceClassMethod(stream, token, decorators, isStatic);
4572
4632
  }
4573
4633
  if (stream.check("operator", "=") && stream.checkAhead(1, "keyword", "function")) {
4574
- return parseClassMethod(stream, token, decorators);
4634
+ return parseClassMethod(stream, token, decorators, isStatic);
4575
4635
  }
4576
4636
  const annotation = parseFieldAnnotation(stream);
4577
4637
  const value = stream.match("operator", "=") ? parseExpression(stream) : null;
4578
4638
  expectClassFieldBoundary(stream, token);
4579
- return { kind: "class-field", name: token.value, annotation, value, decorators, position: token.position };
4639
+ return { kind: "class-field", name: token.value, annotation, value, decorators, isStatic, position: token.position };
4640
+ }
4641
+ function parseTypeParameters(stream) {
4642
+ const list = { names: [], constraints: [] };
4643
+ if (!stream.check("operator", "<")) {
4644
+ return list;
4645
+ }
4646
+ const checkpoint = stream.checkpoint();
4647
+ stream.next();
4648
+ do {
4649
+ list.names.push(stream.expect("identifier").value);
4650
+ list.constraints.push(stream.match("keyword", "extends") ? parseTypeAnnotation(stream) : null);
4651
+ } while (stream.match("punctuation", ","));
4652
+ stream.expect("operator", ">");
4653
+ stream.eraseFrom(checkpoint);
4654
+ return list;
4655
+ }
4656
+ function parseTypeArguments(stream) {
4657
+ const args = [];
4658
+ if (!stream.check("operator", "<")) {
4659
+ return args;
4660
+ }
4661
+ const checkpoint = stream.checkpoint();
4662
+ stream.next();
4663
+ do {
4664
+ args.push(parseTypeAnnotation(stream));
4665
+ } while (stream.match("punctuation", ","));
4666
+ stream.expect("operator", ">");
4667
+ stream.eraseFrom(checkpoint);
4668
+ return args;
4580
4669
  }
4581
4670
  function parseClassModifiers(stream, declaration) {
4582
4671
  while (stream.check("keyword") && CLASS_MODIFIERS.has(stream.current().value)) {
4583
4672
  if (stream.next().value === "extends") {
4584
4673
  declaration.superClass = stream.expect("identifier").value;
4674
+ declaration.superClassArguments = parseTypeArguments(stream);
4585
4675
  continue;
4586
4676
  }
4587
4677
  do {
@@ -4592,7 +4682,19 @@ function parseClassModifiers(stream, declaration) {
4592
4682
  function parseClassDeclaration(stream, decorators) {
4593
4683
  const position2 = stream.next().position;
4594
4684
  const name = stream.expect("identifier").value;
4595
- const declaration = { kind: "class-declaration", name, superClass: null, interfaces: [], members: [], decorators, position: position2 };
4685
+ const typeParameters = parseTypeParameters(stream);
4686
+ const declaration = {
4687
+ kind: "class-declaration",
4688
+ name,
4689
+ typeParameters: typeParameters.names,
4690
+ typeConstraints: typeParameters.constraints,
4691
+ superClass: null,
4692
+ superClassArguments: [],
4693
+ interfaces: [],
4694
+ members: [],
4695
+ decorators,
4696
+ position: position2
4697
+ };
4596
4698
  parseClassModifiers(stream, declaration);
4597
4699
  stream.expect("punctuation", "{");
4598
4700
  while (!stream.check("punctuation", "}") && !stream.isEof()) {
@@ -5052,8 +5154,8 @@ var TABLE_TYPE = { kind: "table" };
5052
5154
  function createArray(element) {
5053
5155
  return { kind: "array", element };
5054
5156
  }
5055
- function createNamed(name) {
5056
- return { kind: "named", name };
5157
+ function createNamed(name, typeArguments = []) {
5158
+ return typeArguments.length === 0 ? { kind: "named", name } : { kind: "named", name, typeArguments };
5057
5159
  }
5058
5160
  function createMap(key, value) {
5059
5161
  return { kind: "map", key, value };
@@ -5166,6 +5268,12 @@ function createUnion(options) {
5166
5268
  }
5167
5269
  return { kind: "union", options: flattened };
5168
5270
  }
5271
+ function namedNesting(type) {
5272
+ if (type.kind !== "named") {
5273
+ return 0;
5274
+ }
5275
+ return 1 + Math.max(0, ...(type.typeArguments ?? []).map(namedNesting));
5276
+ }
5169
5277
  function typeToString(type) {
5170
5278
  if (type.kind === "array") {
5171
5279
  return `${typeToString(type.element)}[]`;
@@ -5185,7 +5293,11 @@ function typeToString(type) {
5185
5293
  if (type.kind === "boolean-literal" || type.kind === "number-literal") {
5186
5294
  return String(type.value);
5187
5295
  }
5188
- if (type.kind === "named" || type.kind === "record") {
5296
+ if (type.kind === "named") {
5297
+ const args = type.typeArguments ?? [];
5298
+ return args.length === 0 ? type.name : `${type.name}<${args.map(typeToString).join(", ")}>`;
5299
+ }
5300
+ if (type.kind === "record") {
5189
5301
  return type.name;
5190
5302
  }
5191
5303
  if (type.kind === "tuple") {
@@ -5275,6 +5387,59 @@ function isRecordAssignable(source, target, options) {
5275
5387
  }
5276
5388
  return true;
5277
5389
  }
5390
+ function isNamedAssignable(source, target, options) {
5391
+ const sourceArguments = source.typeArguments ?? [];
5392
+ const targetArguments = target.typeArguments ?? [];
5393
+ if (source.name !== target.name || sourceArguments.length === 0 || targetArguments.length !== sourceArguments.length) {
5394
+ return true;
5395
+ }
5396
+ return sourceArguments.every((argument, index) => isAssignable(argument, targetArguments[index] ?? ANY_TYPE, options));
5397
+ }
5398
+ function asRecord(name, members) {
5399
+ return { kind: "record", name, origin: null, members };
5400
+ }
5401
+ function shapeOf(name, options) {
5402
+ return options.resolveNominal?.(name) ?? null;
5403
+ }
5404
+ function withVisit(options, pair) {
5405
+ return { ...options, visited: /* @__PURE__ */ new Set([...options.visited ?? [], pair]) };
5406
+ }
5407
+ function isNominalAssignable(source, target, options) {
5408
+ const pair = `${source.name}>${target.name}`;
5409
+ if (options.visited?.has(pair) === true) {
5410
+ return true;
5411
+ }
5412
+ const declared = shapeOf(target.name, options);
5413
+ const actual = shapeOf(source.name, options);
5414
+ if (declared === null || actual === null) {
5415
+ return true;
5416
+ }
5417
+ if (declared.kind === "class" || actual.kind === "class") {
5418
+ return actual.kind === "class" && actual.ancestors.has(target.name);
5419
+ }
5420
+ return isRecordAssignable(asRecord(source.name, actual.members), asRecord(target.name, declared.members), withVisit(options, pair));
5421
+ }
5422
+ var SCALAR_KINDS = /* @__PURE__ */ new Set(["string", "number", "boolean", "thread", "userdata", "void", "nil", "function"]);
5423
+ function isNamedSourceAssignable(source, target, options) {
5424
+ const actual = shapeOf(source.name, options);
5425
+ if (actual === null) {
5426
+ return true;
5427
+ }
5428
+ if (actual.kind === "class") {
5429
+ return !SCALAR_KINDS.has(target.kind) && !isLiteralType(target);
5430
+ }
5431
+ return isAssignable(asRecord(source.name, actual.members), target, options);
5432
+ }
5433
+ function isNamedTargetAssignable(source, target, options) {
5434
+ const declared = shapeOf(target.name, options);
5435
+ if (declared === null) {
5436
+ return true;
5437
+ }
5438
+ if (declared.kind === "class") {
5439
+ return false;
5440
+ }
5441
+ return isAssignable(source, asRecord(target.name, declared.members), options);
5442
+ }
5278
5443
  function isAssignable(source, target, options = { allowNil: false }) {
5279
5444
  if (source.kind === "any" || target.kind === "any" || target.kind === "unknown") {
5280
5445
  return true;
@@ -5282,9 +5447,6 @@ function isAssignable(source, target, options = { allowNil: false }) {
5282
5447
  if (source.kind === "tuple" || target.kind === "tuple") {
5283
5448
  return isAssignable(firstValueOf(source), firstValueOf(target), options);
5284
5449
  }
5285
- if (source.kind === "named" || target.kind === "named") {
5286
- return true;
5287
- }
5288
5450
  if (source.kind === "nil" && (options.allowNil || target.kind === "optional" || target.kind === "nil")) {
5289
5451
  return true;
5290
5452
  }
@@ -5303,6 +5465,15 @@ function isAssignable(source, target, options = { allowNil: false }) {
5303
5465
  if (target.kind === "optional") {
5304
5466
  return isAssignable(source, target.element, options);
5305
5467
  }
5468
+ if (source.kind === "named" && target.kind === "named") {
5469
+ return source.name === target.name ? isNamedAssignable(source, target, options) : isNominalAssignable(source, target, options);
5470
+ }
5471
+ if (source.kind === "named") {
5472
+ return isNamedSourceAssignable(source, target, options);
5473
+ }
5474
+ if (target.kind === "named") {
5475
+ return isNamedTargetAssignable(source, target, options);
5476
+ }
5306
5477
  if (target.kind === "table") {
5307
5478
  return isTableLike(source) || source.kind === "record";
5308
5479
  }
@@ -5441,7 +5612,8 @@ var RUNTIME_HELPERS = {
5441
5612
  math: { name: "math", file: "math.lua", injection: "automatic", features: ["number-extension"] },
5442
5613
  string: { name: "string", file: "string.lua", injection: "automatic", features: ["string-extension", "template-string"] },
5443
5614
  table: { name: "table", file: "table.lua", injection: "automatic", features: ["table-extension"] },
5444
- threads: { name: "threads", file: "threads.lua", injection: "reference", features: ["thread"], globals: ["sleep", "Threads"] }
5615
+ threads: { name: "threads", file: "threads.lua", injection: "reference", features: ["thread"], globals: ["sleep", "Threads"] },
5616
+ validate: { name: "validate", file: "validate.lua", injection: "automatic", features: ["boundary-validation"] }
5445
5617
  };
5446
5618
  var DEVELOPMENT_RUNTIME_HELPERS = {
5447
5619
  "development-logs-client": {
@@ -5582,6 +5754,11 @@ var MANIFEST_FIELDS = [
5582
5754
  owner: "dependencies",
5583
5755
  allowEmpty: true
5584
5756
  }),
5757
+ field("contracts", STRING_TYPE, "Directory the export contract is written to and dependency contracts are read from.", {
5758
+ defaultValue: DEFAULT_CONTRACTS_DIR,
5759
+ rule: "contained-path",
5760
+ owner: "dependencies"
5761
+ }),
5585
5762
  table("engine", "Requirements the MTA server and client must meet.", ENGINE_FIELDS, { defaultValue: {}, owner: "engine" }),
5586
5763
  table("environment", "Files that declare and override the project environment.", ENVIRONMENT_FIELDS, { defaultValue: {}, owner: "environment" }),
5587
5764
  field("outDir", STRING_TYPE, "Directory the built resource is written to.", { defaultValue: DEFAULT_OUT_DIR, rule: "static-path", owner: "output" }),
@@ -5741,8 +5918,8 @@ function isNumber(type) {
5741
5918
  function isText(type) {
5742
5919
  return type.kind === "string" || type.kind === "string-literal" || type.kind === "any";
5743
5920
  }
5744
- function fail(operator, expected, left, right, describe) {
5745
- const culprit = describe(isNumber(left) || isText(left) ? right : left);
5921
+ function fail(operator, expected, left, right, describe2) {
5922
+ const culprit = describe2(isNumber(left) || isText(left) ? right : left);
5746
5923
  return { evaluated: null, error: `"${operator}" expects ${expected} on both sides but received ${culprit}.` };
5747
5924
  }
5748
5925
  function arithmetic(operator, left, right) {
@@ -5781,22 +5958,22 @@ function applyOr(left, right) {
5781
5958
  const value = isTruthy(left.value) ? left.value : right.value;
5782
5959
  return { value, type: unionOf([left.truthy, right.type]), truthy: unionOf([left.truthy, right.truthy]), falsy: right.falsy };
5783
5960
  }
5784
- function applyOrdering(operator, left, right, describe) {
5961
+ function applyOrdering(operator, left, right, describe2) {
5785
5962
  const numeric = isNumber(left.type) && isNumber(right.type);
5786
5963
  const textual = isText(left.type) && isText(right.type);
5787
5964
  if (!numeric && !textual) {
5788
- return fail(operator, "two numbers or two strings", left.type, right.type, describe);
5965
+ return fail(operator, "two numbers or two strings", left.type, right.type, describe2);
5789
5966
  }
5790
5967
  return { evaluated: booleanValue(compare(operator, left.value, right.value)), error: null };
5791
5968
  }
5792
- function applyConcat(left, right, describe) {
5969
+ function applyConcat(left, right, describe2) {
5793
5970
  const usable = (type) => isText(type) || isNumber(type);
5794
5971
  if (!usable(left.type) || !usable(right.type)) {
5795
- return fail("..", "a string or a number", left.type, right.type, describe);
5972
+ return fail("..", "a string or a number", left.type, right.type, describe2);
5796
5973
  }
5797
5974
  return { evaluated: stringValue(`${left.value}${right.value}`), error: null };
5798
5975
  }
5799
- function applyBinary(operator, left, right, describe) {
5976
+ function applyBinary(operator, left, right, describe2) {
5800
5977
  if (operator === "and") {
5801
5978
  return { evaluated: applyAnd(left, right), error: null };
5802
5979
  }
@@ -5808,23 +5985,23 @@ function applyBinary(operator, left, right, describe) {
5808
5985
  return { evaluated: booleanValue(operator === "==" ? same : !same), error: null };
5809
5986
  }
5810
5987
  if (ORDERING.includes(operator)) {
5811
- return applyOrdering(operator, left, right, describe);
5988
+ return applyOrdering(operator, left, right, describe2);
5812
5989
  }
5813
5990
  if (operator === "..") {
5814
- return applyConcat(left, right, describe);
5991
+ return applyConcat(left, right, describe2);
5815
5992
  }
5816
5993
  if (!isNumber(left.type) || !isNumber(right.type)) {
5817
- return fail(operator, "a number", left.type, right.type, describe);
5994
+ return fail(operator, "a number", left.type, right.type, describe2);
5818
5995
  }
5819
5996
  return { evaluated: numberValue(arithmetic(operator, left.value, right.value)), error: null };
5820
5997
  }
5821
- function applyUnary(operator, operand, describe) {
5998
+ function applyUnary(operator, operand, describe2) {
5822
5999
  if (operator === "not") {
5823
6000
  return { evaluated: booleanValue(!isTruthy(operand.value)), error: null };
5824
6001
  }
5825
6002
  if (operator === "-") {
5826
6003
  if (!isNumber(operand.type)) {
5827
- return { evaluated: null, error: `"-" expects a number but received ${describe(operand.type)}.` };
6004
+ return { evaluated: null, error: `"-" expects a number but received ${describe2(operand.type)}.` };
5828
6005
  }
5829
6006
  return { evaluated: numberValue(-operand.value), error: null };
5830
6007
  }
@@ -6283,8 +6460,8 @@ var RuleWalk = class {
6283
6460
  }
6284
6461
  };
6285
6462
  function normalizeManifest(value, positions) {
6286
- const walk3 = new RuleWalk(positions);
6287
- return { value: walk3.fields(MANIFEST_FIELDS, value, "", ""), diagnostics: walk3.diagnostics };
6463
+ const walk4 = new RuleWalk(positions);
6464
+ return { value: walk4.fields(MANIFEST_FIELDS, value, "", ""), diagnostics: walk4.diagnostics };
6288
6465
  }
6289
6466
 
6290
6467
  // ../compiler/src/manifest/manifest-analysis.ts
@@ -6520,6 +6697,7 @@ function validateConfig(value, positions) {
6520
6697
  sources: readSourceMapping(value),
6521
6698
  assets: readAssetMappings(value),
6522
6699
  dependencies,
6700
+ contracts: readString(value, "contracts") ?? "",
6523
6701
  engine: readEngine(value),
6524
6702
  environment: readEnvironmentFiles(value),
6525
6703
  outDir: readString(value, "outDir") ?? "",
@@ -6584,7 +6762,7 @@ import { tmpdir } from "node:os";
6584
6762
  import { join as join2 } from "node:path";
6585
6763
 
6586
6764
  // src/cli/version.ts
6587
- var VERSION = true ? "0.19.0" : "0.0.0-dev";
6765
+ var VERSION = true ? "0.19.2" : "0.0.0-dev";
6588
6766
  var PROGRAM_NAME = "luam";
6589
6767
  var PROGRAM_DESCRIPTION = "luam \u2014 the Luam compiler for Multi Theft Auto resources.";
6590
6768
 
@@ -6769,9 +6947,9 @@ function pluralize(count, singular, plural = `${singular}s`) {
6769
6947
  var MAX_ENTRIES_PER_FILE = 10;
6770
6948
  var MAX_FILES = 10;
6771
6949
  var GUTTER_WIDTH = 6;
6772
- function groupByFile(entries2) {
6950
+ function groupByFile(entries3) {
6773
6951
  const groups = /* @__PURE__ */ new Map();
6774
- for (const entry of entries2) {
6952
+ for (const entry of entries3) {
6775
6953
  const group = groups.get(entry.path);
6776
6954
  if (group === void 0) {
6777
6955
  groups.set(entry.path, [entry]);
@@ -6818,19 +6996,19 @@ function emit(reporter, entry, block) {
6818
6996
  }
6819
6997
  reporter.rawWarn(block);
6820
6998
  }
6821
- function reportGroup(reporter, path, entries2, options) {
6999
+ function reportGroup(reporter, path, entries3, options) {
6822
7000
  const limit = options.maxPerFile ?? MAX_ENTRIES_PER_FILE;
6823
7001
  const source = options.sources.get(path);
6824
7002
  reporter.raw(reporter.style.paint("strong", path));
6825
- for (const entry of entries2.slice(0, limit)) {
7003
+ for (const entry of entries3.slice(0, limit)) {
6826
7004
  emit(reporter, entry, entryBlock(reporter, entry, source));
6827
7005
  }
6828
- if (entries2.length > limit) {
6829
- reporter.detail(` ... and ${pluralize(entries2.length - limit, "more diagnostic")} in this file.`);
7006
+ if (entries3.length > limit) {
7007
+ reporter.detail(` ... and ${pluralize(entries3.length - limit, "more diagnostic")} in this file.`);
6830
7008
  }
6831
7009
  }
6832
- function reportGroupedDiagnostics(reporter, entries2, options) {
6833
- const groups = [...groupByFile(entries2)];
7010
+ function reportGroupedDiagnostics(reporter, entries3, options) {
7011
+ const groups = [...groupByFile(entries3)];
6834
7012
  const limit = options.maxFiles ?? MAX_FILES;
6835
7013
  for (const [path, group] of groups.slice(0, limit)) {
6836
7014
  reportGroup(reporter, path, group, options);
@@ -6846,9 +7024,9 @@ function formatFileDiagnostic(entry) {
6846
7024
  const location = `${path}:${diagnostic.position.line}:${diagnostic.position.column}`;
6847
7025
  return `${location} ${diagnostic.severity} ${diagnostic.code}: ${diagnostic.message}`;
6848
7026
  }
6849
- function countFileDiagnostics(entries2) {
6850
- const errors = entries2.filter((entry) => entry.diagnostic.severity === "error").length;
6851
- return { errors, warnings: entries2.length - errors };
7027
+ function countFileDiagnostics(entries3) {
7028
+ const errors = entries3.filter((entry) => entry.diagnostic.severity === "error").length;
7029
+ return { errors, warnings: entries3.length - errors };
6852
7030
  }
6853
7031
  function countCliDiagnostics(diagnostics) {
6854
7032
  const errors = diagnostics.filter((diagnostic) => diagnostic.severity === "error").length;
@@ -6858,8 +7036,8 @@ function reportManifestDiagnostics(reporter, path, source, diagnostics) {
6858
7036
  if (diagnostics.length === 0) {
6859
7037
  return;
6860
7038
  }
6861
- const entries2 = diagnostics.map((diagnostic) => ({ path, diagnostic }));
6862
- reportFileDiagnostics(reporter, entries2, /* @__PURE__ */ new Map([[path, source]]));
7039
+ const entries3 = diagnostics.map((diagnostic) => ({ path, diagnostic }));
7040
+ reportFileDiagnostics(reporter, entries3, /* @__PURE__ */ new Map([[path, source]]));
6863
7041
  }
6864
7042
  function reportCliDiagnostics(reporter, diagnostics) {
6865
7043
  for (const diagnostic of diagnostics) {
@@ -6871,8 +7049,8 @@ function reportCliDiagnostics(reporter, diagnostics) {
6871
7049
  reporter.warn(line2);
6872
7050
  }
6873
7051
  }
6874
- function reportPlainFileDiagnostics(reporter, entries2) {
6875
- for (const entry of entries2) {
7052
+ function reportPlainFileDiagnostics(reporter, entries3) {
7053
+ for (const entry of entries3) {
6876
7054
  const line2 = formatFileDiagnostic(entry);
6877
7055
  if (entry.diagnostic.severity === "error") {
6878
7056
  reporter.rawError(line2);
@@ -6881,12 +7059,12 @@ function reportPlainFileDiagnostics(reporter, entries2) {
6881
7059
  reporter.rawWarn(line2);
6882
7060
  }
6883
7061
  }
6884
- function reportFileDiagnostics(reporter, entries2, sources) {
7062
+ function reportFileDiagnostics(reporter, entries3, sources) {
6885
7063
  if (!reporter.capability.interactive) {
6886
- reportPlainFileDiagnostics(reporter, entries2);
7064
+ reportPlainFileDiagnostics(reporter, entries3);
6887
7065
  return;
6888
7066
  }
6889
- reportGroupedDiagnostics(reporter, entries2, { sources });
7067
+ reportGroupedDiagnostics(reporter, entries3, { sources });
6890
7068
  }
6891
7069
  function formatCounts(counts) {
6892
7070
  return `${pluralize(counts.errors, "error")}, ${pluralize(counts.warnings, "warning")}`;
@@ -7137,8 +7315,153 @@ function addProjectOptions(command) {
7137
7315
  return command.addOption(cwdOption()).addOption(manifestOption()).addOption(colorOption());
7138
7316
  }
7139
7317
 
7318
+ // src/build/contract-files.ts
7319
+ import { mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "node:fs";
7320
+ import { dirname as dirname2, resolve as resolve4 } from "node:path";
7321
+
7322
+ // ../compiler/src/project/export-abi.ts
7323
+ var ABI_VERSION = 1;
7324
+ var ABI_EXTENSION = ".abi.json";
7325
+ var MAX_ABI_BYTES = 262144;
7326
+ var MAX_EXPORTS = 512;
7327
+ var MAX_PARAMETERS = 32;
7328
+ var MAX_TYPE_LENGTH = 512;
7329
+ var NAME_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;
7330
+ var RESOURCE_PATTERN = /^[A-Za-z0-9_-]+$/;
7331
+ var SIDES = /* @__PURE__ */ new Set(["server", "client", "shared"]);
7332
+ function serializeAbi(abi) {
7333
+ const exports = [...abi.exports].sort((left, right) => left.name < right.name ? -1 : left.name > right.name ? 1 : 0);
7334
+ return `${JSON.stringify({ abi: abi.abi, resource: abi.resource, exports }, null, 4)}
7335
+ `;
7336
+ }
7337
+ function readString2(value, limit) {
7338
+ return typeof value === "string" && value.length > 0 && value.length <= limit ? value : null;
7339
+ }
7340
+ function readParameter(value) {
7341
+ if (typeof value !== "object" || value === null) {
7342
+ return null;
7343
+ }
7344
+ const entry = value;
7345
+ const name = readString2(entry.name, 64);
7346
+ const type = readString2(entry.type, MAX_TYPE_LENGTH);
7347
+ return name === null || type === null || !NAME_PATTERN.test(name) ? null : { name, type };
7348
+ }
7349
+ function readExport(value) {
7350
+ if (typeof value !== "object" || value === null) {
7351
+ return null;
7352
+ }
7353
+ const entry = value;
7354
+ const name = readString2(entry.name, 128);
7355
+ const side2 = readString2(entry.side, 16);
7356
+ const returns = readString2(entry.returns, MAX_TYPE_LENGTH);
7357
+ const parameters = Array.isArray(entry.parameters) ? entry.parameters : null;
7358
+ if (name === null || !NAME_PATTERN.test(name) || side2 === null || !SIDES.has(side2) || returns === null || parameters === null) {
7359
+ return null;
7360
+ }
7361
+ if (parameters.length > MAX_PARAMETERS) {
7362
+ return null;
7363
+ }
7364
+ const read = parameters.map(readParameter);
7365
+ if (read.some((parameter) => parameter === null)) {
7366
+ return null;
7367
+ }
7368
+ return {
7369
+ name,
7370
+ side: side2,
7371
+ http: entry.http === true,
7372
+ parameters: read,
7373
+ minimumArguments: typeof entry.minimumArguments === "number" && Number.isInteger(entry.minimumArguments) ? entry.minimumArguments : 0,
7374
+ variadic: entry.variadic === true,
7375
+ returns
7376
+ };
7377
+ }
7378
+ function parseResourceAbi(text) {
7379
+ if (text.length > MAX_ABI_BYTES) {
7380
+ return null;
7381
+ }
7382
+ let parsed;
7383
+ try {
7384
+ parsed = JSON.parse(text);
7385
+ } catch {
7386
+ return null;
7387
+ }
7388
+ if (typeof parsed !== "object" || parsed === null) {
7389
+ return null;
7390
+ }
7391
+ const document = parsed;
7392
+ const resource = readString2(document.resource, 64);
7393
+ const entries3 = Array.isArray(document.exports) ? document.exports : null;
7394
+ if (document.abi !== ABI_VERSION || resource === null || !RESOURCE_PATTERN.test(resource) || entries3 === null || entries3.length > MAX_EXPORTS) {
7395
+ return null;
7396
+ }
7397
+ const read = entries3.map(readExport);
7398
+ if (read.some((entry) => entry === null)) {
7399
+ return null;
7400
+ }
7401
+ return { abi: ABI_VERSION, resource, exports: read };
7402
+ }
7403
+ function abiFileName(resource) {
7404
+ return `${resource}${ABI_EXTENSION}`;
7405
+ }
7406
+ function lookupAbiExport(contracts, resource, name) {
7407
+ return contracts.find((entry) => entry.resource === resource)?.exports.find((entry) => entry.name === name) ?? null;
7408
+ }
7409
+ function knowsResource(contracts, resource) {
7410
+ return contracts.some((entry) => entry.resource === resource);
7411
+ }
7412
+ function buildResourceAbi(resource, contributions) {
7413
+ const exports = contributions.map((contribution) => {
7414
+ const signature = contribution.signature;
7415
+ return {
7416
+ name: contribution.name,
7417
+ side: contribution.side,
7418
+ http: contribution.http,
7419
+ parameters: signature?.parameters ?? [],
7420
+ minimumArguments: signature?.minimumArguments ?? 0,
7421
+ variadic: signature?.variadic ?? true,
7422
+ returns: signature?.returns ?? "any"
7423
+ };
7424
+ });
7425
+ return { abi: ABI_VERSION, resource, exports };
7426
+ }
7427
+
7428
+ // src/build/contract-files.ts
7429
+ function contractPath(root, config, resource) {
7430
+ return resolve4(root, config.contracts, abiFileName(resource));
7431
+ }
7432
+ function readDependencyContracts(root, config) {
7433
+ const contracts = [];
7434
+ const diagnostics = [];
7435
+ for (const dependency of config.dependencies) {
7436
+ const path = contractPath(root, config, dependency);
7437
+ let text;
7438
+ try {
7439
+ text = readFileSync3(path, "utf8");
7440
+ } catch {
7441
+ continue;
7442
+ }
7443
+ const parsed = parseResourceAbi(text);
7444
+ if (parsed === null) {
7445
+ diagnostics.push(cliWarning("build-invalid-contract", `The export contract for "${dependency}" at "${path}" could not be read and was ignored.`));
7446
+ continue;
7447
+ }
7448
+ if (parsed.resource !== dependency) {
7449
+ diagnostics.push(cliWarning("build-invalid-contract", `The export contract at "${path}" names resource "${parsed.resource}" and was ignored.`));
7450
+ continue;
7451
+ }
7452
+ contracts.push(parsed);
7453
+ }
7454
+ return { contracts, diagnostics };
7455
+ }
7456
+ function writeResourceContract(root, config, contract) {
7457
+ const path = contractPath(root, config, config.name);
7458
+ mkdirSync2(dirname2(path), { recursive: true });
7459
+ writeFileSync3(path, serializeAbi(contract), "utf8");
7460
+ return path;
7461
+ }
7462
+
7140
7463
  // src/build/helper-files.ts
7141
- import { existsSync as existsSync3, readFileSync as readFileSync3 } from "node:fs";
7464
+ import { existsSync as existsSync3, readFileSync as readFileSync4 } from "node:fs";
7142
7465
  import { fileURLToPath } from "node:url";
7143
7466
  function bundledHelperPath(file) {
7144
7467
  return fileURLToPath(new URL(`./lua/${file}`, import.meta.url));
@@ -7150,7 +7473,7 @@ function resolveHelperPath(helper, file) {
7150
7473
  return existsSync3(packaged) ? packaged : bundledHelperPath(file);
7151
7474
  }
7152
7475
  function readHelperSource(helper, file) {
7153
- return readFileSync3(resolveHelperPath(helper, file), "utf8");
7476
+ return readFileSync4(resolveHelperPath(helper, file), "utf8");
7154
7477
  }
7155
7478
 
7156
7479
  // src/build/build-phase.ts
@@ -7219,16 +7542,16 @@ function createPhaseTracker(listener = null) {
7219
7542
  }
7220
7543
 
7221
7544
  // src/build/project-inputs.ts
7222
- import { existsSync as existsSync4, readFileSync as readFileSync4 } from "node:fs";
7223
- import { resolve as resolve6 } from "node:path";
7545
+ import { existsSync as existsSync4, readFileSync as readFileSync5 } from "node:fs";
7546
+ import { resolve as resolve7 } from "node:path";
7224
7547
 
7225
7548
  // src/build/asset-resolution.ts
7226
7549
  import { statSync as statSync3 } from "node:fs";
7227
- import { resolve as resolve5 } from "node:path";
7550
+ import { resolve as resolve6 } from "node:path";
7228
7551
 
7229
7552
  // src/build/project-files.ts
7230
7553
  import { readdirSync, statSync as statSync2 } from "node:fs";
7231
- import { join as join3, relative, resolve as resolve4 } from "node:path";
7554
+ import { join as join3, relative, resolve as resolve5 } from "node:path";
7232
7555
  function isDirectory(path) {
7233
7556
  try {
7234
7557
  return statSync2(path).isDirectory();
@@ -7240,14 +7563,14 @@ function relativePath(root, absolute) {
7240
7563
  return normalizePattern(relative(root, absolute));
7241
7564
  }
7242
7565
  function walk(root, directory, excluded, tree) {
7243
- let entries2;
7566
+ let entries3;
7244
7567
  try {
7245
- entries2 = readdirSync(directory, { withFileTypes: true });
7568
+ entries3 = readdirSync(directory, { withFileTypes: true });
7246
7569
  } catch (error) {
7247
7570
  tree.errors.push(`"${relativePath(root, directory)}" could not be read: ${error instanceof Error ? error.message : String(error)}`);
7248
7571
  return;
7249
7572
  }
7250
- for (const entry of entries2) {
7573
+ for (const entry of entries3) {
7251
7574
  const absolute = join3(directory, entry.name);
7252
7575
  const path = relativePath(root, absolute);
7253
7576
  if (path.length === 0 || isExcludedPath(path, excluded)) {
@@ -7264,7 +7587,7 @@ function listProjectFiles(root, roots, excluded = []) {
7264
7587
  const tree = { files: [], errors: [] };
7265
7588
  const seen = /* @__PURE__ */ new Set();
7266
7589
  for (const start of [...new Set(roots)].sort()) {
7267
- const absolute = resolve4(root, start);
7590
+ const absolute = resolve5(root, start);
7268
7591
  if (!isDirectory(absolute) || seen.has(absolute)) {
7269
7592
  continue;
7270
7593
  }
@@ -7408,9 +7731,23 @@ var OCCUPIED_SIDES = {
7408
7731
  function occupiedSides(contribution) {
7409
7732
  return OCCUPIED_SIDES[contribution.side];
7410
7733
  }
7411
- function toContributions(directives, environment) {
7734
+ function exportSignature(type) {
7735
+ if (type === void 0 || type.kind !== "function") {
7736
+ return null;
7737
+ }
7738
+ const parameters = type.parameters.map((parameter, index) => ({ name: type.parameterNames?.[index] ?? `arg${index + 1}`, type: typeToString(parameter) }));
7739
+ return { parameters, minimumArguments: type.minimumArguments, variadic: type.isVariadic, returns: typeToString(type.returnType) };
7740
+ }
7741
+ function toContributions(directives, environment, globals) {
7412
7742
  return directives.exports.map(
7413
- (entry) => ({ kind: "export", name: entry.name, http: entry.http, side: environment, position: entry.position })
7743
+ (entry) => ({
7744
+ kind: "export",
7745
+ name: entry.name,
7746
+ http: entry.http,
7747
+ side: environment,
7748
+ signature: exportSignature(globals.get(entry.name)),
7749
+ position: entry.position
7750
+ })
7414
7751
  );
7415
7752
  }
7416
7753
  var INDENT = " ";
@@ -7444,8 +7781,8 @@ function attribute(name, value) {
7444
7781
  function textElement(name, value) {
7445
7782
  return `${INDENT}<${name}>${escapeXml(value)}</${name}>`;
7446
7783
  }
7447
- function section(text, entries2) {
7448
- return entries2.length === 0 ? [] : [`${INDENT}<!-- ${text} -->`, ...entries2];
7784
+ function section(text, entries3) {
7785
+ return entries3.length === 0 ? [] : [`${INDENT}<!-- ${text} -->`, ...entries3];
7449
7786
  }
7450
7787
  function infoElement(info) {
7451
7788
  const attributes = info.author === void 0 ? [] : [attribute("author", info.author)];
@@ -7573,12 +7910,12 @@ var MISSING_LOAD_ORDER = 'is listed in "loadOrder" but no source file or asset m
7573
7910
  function missingEntry(entry) {
7574
7911
  return { path: entry, diagnostic: createDiagnostic("project", "project-load-order-missing", `"${entry}" ${MISSING_LOAD_ORDER}`, FILE_START) };
7575
7912
  }
7576
- function resolveLoadOrder(entries2, scripts, assets) {
7913
+ function resolveLoadOrder(entries3, scripts, assets) {
7577
7914
  const byScript = new Map(scripts.map((script) => [normalizePath(script.source), script]));
7578
7915
  const byAsset = new Map(assets.map((asset) => [normalizePath(asset.source), asset]));
7579
7916
  const resolved2 = { scripts: [], assets: [], diagnostics: [] };
7580
7917
  const seen = /* @__PURE__ */ new Set();
7581
- for (const entry of entries2) {
7918
+ for (const entry of entries3) {
7582
7919
  const path = normalizePath(entry).replace(/^\.\//, "");
7583
7920
  const script = byScript.get(path);
7584
7921
  const asset = byAsset.get(path);
@@ -7654,11 +7991,11 @@ var ENTRY = /^([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)$/;
7654
7991
  var SENSITIVE = /(password|secret|token|key|credential|dsn|private)/i;
7655
7992
  var EMPTY_ENV_FILE = { entries: [], errors: [] };
7656
7993
  function unquote(raw) {
7657
- const quote2 = raw.slice(0, 1);
7658
- if (quote2 !== '"' && quote2 !== "'") {
7994
+ const quote3 = raw.slice(0, 1);
7995
+ if (quote3 !== '"' && quote3 !== "'") {
7659
7996
  return null;
7660
7997
  }
7661
- const closing = raw.lastIndexOf(quote2);
7998
+ const closing = raw.lastIndexOf(quote3);
7662
7999
  return closing > 0 ? raw.slice(1, closing) : null;
7663
8000
  }
7664
8001
  function stripComment(raw) {
@@ -7676,7 +8013,7 @@ function classify(raw) {
7676
8013
  return { value, kind: value.length > 0 && Number.isFinite(Number(value)) ? "number" : "string" };
7677
8014
  }
7678
8015
  function parseEnvFile(source) {
7679
- const entries2 = [];
8016
+ const entries3 = [];
7680
8017
  const errors = [];
7681
8018
  source.split(/\r?\n/).forEach((raw, index) => {
7682
8019
  const trimmed = raw.trim();
@@ -7690,9 +8027,9 @@ function parseEnvFile(source) {
7690
8027
  return;
7691
8028
  }
7692
8029
  const { value, kind } = classify(match[2]);
7693
- entries2.push({ key: match[1], value, kind, line: line2 });
8030
+ entries3.push({ key: match[1], value, kind, line: line2 });
7694
8031
  });
7695
- return { entries: entries2, errors };
8032
+ return { entries: entries3, errors };
7696
8033
  }
7697
8034
  function isSensitiveKey(key) {
7698
8035
  return SENSITIVE.test(key);
@@ -7858,14 +8195,14 @@ var MANIFEST_OUTPUT = "meta.xml";
7858
8195
  var HERE = ".";
7859
8196
  function isDirectory2(root, path) {
7860
8197
  try {
7861
- return statSync3(resolve5(root, path)).isDirectory();
8198
+ return statSync3(resolve6(root, path)).isDirectory();
7862
8199
  } catch {
7863
8200
  return false;
7864
8201
  }
7865
8202
  }
7866
8203
  function exists(root, path) {
7867
8204
  try {
7868
- statSync3(resolve5(root, path));
8205
+ statSync3(resolve6(root, path));
7869
8206
  return true;
7870
8207
  } catch {
7871
8208
  return false;
@@ -7933,10 +8270,10 @@ var CONFIGURATION_FILE = "config.lua";
7933
8270
  var MALFORMED_ENV = "build-env-malformed";
7934
8271
  var MISSING_ENV = "config-missing-env-file";
7935
8272
  function readTextFile(path) {
7936
- return existsSync4(path) ? readFileSync4(path, "utf8") : null;
8273
+ return existsSync4(path) ? readFileSync5(path, "utf8") : null;
7937
8274
  }
7938
8275
  function readEnvironment(root, file, required, diagnostics) {
7939
- const source = readTextFile(resolve6(root, file));
8276
+ const source = readTextFile(resolve7(root, file));
7940
8277
  if (source === null) {
7941
8278
  if (required) {
7942
8279
  diagnostics.push(cliError(MISSING_ENV, `"${file}" is configured under "environment" but does not exist. Create it or remove the setting.`));
@@ -7950,7 +8287,7 @@ function readEnvironment(root, file, required, diagnostics) {
7950
8287
  return parsed;
7951
8288
  }
7952
8289
  function readConfiguration(root, diagnostics) {
7953
- const content = readTextFile(resolve6(root, CONFIGURATION_FILE));
8290
+ const content = readTextFile(resolve7(root, CONFIGURATION_FILE));
7954
8291
  if (content === null) {
7955
8292
  return null;
7956
8293
  }
@@ -7969,14 +8306,14 @@ function readProjectInputs(root, options) {
7969
8306
  }
7970
8307
 
7971
8308
  // src/build/source-discovery.ts
7972
- import { readFileSync as readFileSync5 } from "node:fs";
7973
- import { resolve as resolve7 } from "node:path";
8309
+ import { readFileSync as readFileSync6 } from "node:fs";
8310
+ import { resolve as resolve8 } from "node:path";
7974
8311
 
7975
8312
  // ../compiler/src/project/source-mapping.ts
7976
8313
  function createSourceResolver(mapping) {
7977
8314
  const matchers = new Map(SOURCE_SIDES.map((environment) => [environment, createPatternMatcher(mapping[environment])]));
7978
8315
  const patterns = SOURCE_SIDES.flatMap((environment) => [...matchers.get(environment)?.patterns ?? []]);
7979
- function resolve17(path) {
8316
+ function resolve19(path) {
7980
8317
  const normalized = normalizePattern(path);
7981
8318
  const matches = [];
7982
8319
  for (const environment of SOURCE_SIDES) {
@@ -7990,8 +8327,8 @@ function createSourceResolver(mapping) {
7990
8327
  return {
7991
8328
  patterns,
7992
8329
  roots: watchRoots(patterns),
7993
- resolve: resolve17,
7994
- side: (path) => resolve17(path).environment
8330
+ resolve: resolve19,
8331
+ side: (path) => resolve19(path).environment
7995
8332
  };
7996
8333
  }
7997
8334
  function describeMatches(matches) {
@@ -8023,7 +8360,7 @@ function checkLiterals(root, resolver, found, diagnostics) {
8023
8360
  }
8024
8361
  function readSource(root, path, diagnostics) {
8025
8362
  try {
8026
- return readFileSync5(resolve7(root, path), "utf8");
8363
+ return readFileSync6(resolve8(root, path), "utf8");
8027
8364
  } catch (error) {
8028
8365
  diagnostics.push(cliError(UNREADABLE_SOURCE, `The source file "${path}" could not be read: ${error instanceof Error ? error.message : String(error)}`));
8029
8366
  return null;
@@ -8103,25 +8440,26 @@ function fn(parameters, returnType, minimumArguments2, isVariadic = false, param
8103
8440
  var EMPTY_PROJECT_DECLARATIONS = { globals: [] };
8104
8441
  var ENV_GLOBAL = "env";
8105
8442
  var VALUE_TYPES = { boolean: BOOLEAN, number: NUMBER, string: STRING };
8106
- function envMembers(entries2) {
8443
+ function envMembers(entries3) {
8107
8444
  const members = /* @__PURE__ */ new Map();
8108
- for (const entry of entries2) {
8445
+ for (const entry of entries3) {
8109
8446
  members.set(entry.key, { name: entry.key, type: VALUE_TYPES[entry.kind] });
8110
8447
  }
8111
8448
  return [...members.values()].sort((left, right) => left.name.localeCompare(right.name));
8112
8449
  }
8113
- function envDeclaration(entries2, origin) {
8114
- const env = record(ENV_GLOBAL, envMembers(entries2), origin);
8450
+ function envDeclaration(entries3, origin) {
8451
+ const env = record(ENV_GLOBAL, envMembers(entries3), origin);
8115
8452
  return { name: ENV_GLOBAL, environment: "server", source: "project", type: env };
8116
8453
  }
8117
- function projectDeclarations(entries2, origin) {
8118
- if (entries2 === null) {
8454
+ function projectDeclarations(entries3, origin) {
8455
+ if (entries3 === null) {
8119
8456
  return EMPTY_PROJECT_DECLARATIONS;
8120
8457
  }
8121
- return { globals: [envDeclaration(entries2, origin)] };
8458
+ return { globals: [envDeclaration(entries3, origin)] };
8122
8459
  }
8123
8460
 
8124
8461
  // ../compiler/src/checker/ambient.ts
8462
+ var EVENT_NAME_PREFIX = "event:";
8125
8463
  var EMPTY_AMBIENT = { classes: [], interfaces: [], enums: [], globals: [], events: [] };
8126
8464
  function ambientFromRegistry(registry) {
8127
8465
  return {
@@ -8132,9 +8470,9 @@ function ambientFromRegistry(registry) {
8132
8470
  events: registry.allEvents()
8133
8471
  };
8134
8472
  }
8135
- function withoutNames(entries2, excluded) {
8473
+ function withoutNames(entries3, excluded) {
8136
8474
  const names = new Set(excluded.map((entry) => entry.name));
8137
- return entries2.filter((entry) => !names.has(entry.name));
8475
+ return entries3.filter((entry) => !names.has(entry.name));
8138
8476
  }
8139
8477
  function ownDeclarations(registry, ambient) {
8140
8478
  const all = ambientFromRegistry(registry);
@@ -8311,6 +8649,9 @@ var Binder = class {
8311
8649
  declaredSymbols() {
8312
8650
  return [...this.discarded, ...this.scopes.flatMap((scope) => [...scope.values()])];
8313
8651
  }
8652
+ currentScopeNames() {
8653
+ return [...this.scopes[this.scopes.length - 1]?.keys() ?? []];
8654
+ }
8314
8655
  declare(symbol) {
8315
8656
  const scope = this.scopes[this.scopes.length - 1];
8316
8657
  scope?.set(symbol.name, symbol);
@@ -8349,6 +8690,211 @@ var Binder = class {
8349
8690
  }
8350
8691
  };
8351
8692
 
8693
+ // ../compiler/src/checker/flow.ts
8694
+ function createFlow() {
8695
+ return { facts: /* @__PURE__ */ new Map(), reachable: true };
8696
+ }
8697
+ function cloneFlow(state) {
8698
+ return { facts: new Map(state.facts), reachable: state.reachable };
8699
+ }
8700
+ function applyFacts(state, facts) {
8701
+ for (const [path, type] of facts) {
8702
+ state.facts.set(path, type);
8703
+ }
8704
+ }
8705
+ function forgetPath(state, path) {
8706
+ for (const key of [...state.facts.keys()]) {
8707
+ if (key === path || key.startsWith(`${path}.`) || path.startsWith(`${key}.`)) {
8708
+ state.facts.delete(key);
8709
+ }
8710
+ }
8711
+ }
8712
+ function joinFlows(states) {
8713
+ const live = states.filter((state) => state.reachable);
8714
+ const [first, ...rest] = live;
8715
+ if (first === void 0) {
8716
+ return { facts: /* @__PURE__ */ new Map(), reachable: false };
8717
+ }
8718
+ const facts = /* @__PURE__ */ new Map();
8719
+ for (const [path, type] of first.facts) {
8720
+ const others = rest.map((state) => state.facts.get(path));
8721
+ if (others.some((other) => other === void 0)) {
8722
+ continue;
8723
+ }
8724
+ facts.set(path, createUnion([type, ...others]));
8725
+ }
8726
+ return { facts, reachable: true };
8727
+ }
8728
+ function optionsOf(declared) {
8729
+ if (declared.kind === "union") {
8730
+ return declared.options;
8731
+ }
8732
+ return declared.kind === "optional" ? [declared.element, NIL_TYPE] : [];
8733
+ }
8734
+ function assignmentFact(narrowed, declared) {
8735
+ const matched = optionsOf(declared).filter((option) => isAssignable(narrowed, option));
8736
+ const [only] = matched;
8737
+ return matched.length === 1 && only !== void 0 ? only : null;
8738
+ }
8739
+
8740
+ // ../compiler/src/checker/type-substitution.ts
8741
+ function substituteType(type, substitutions) {
8742
+ if (type.kind === "named") {
8743
+ const args = type.typeArguments ?? [];
8744
+ if (args.length > 0) {
8745
+ return createNamed(type.name, args.map((argument) => substituteType(argument, substitutions)));
8746
+ }
8747
+ return substitutions.get(type.name) ?? type;
8748
+ }
8749
+ if (type.kind === "array") {
8750
+ return createArray(substituteType(type.element, substitutions));
8751
+ }
8752
+ if (type.kind === "map") {
8753
+ return createMap(substituteType(type.key, substitutions), substituteType(type.value, substitutions));
8754
+ }
8755
+ if (type.kind === "optional") {
8756
+ return createOptional(substituteType(type.element, substitutions));
8757
+ }
8758
+ if (type.kind === "union") {
8759
+ return createUnion(type.options.map((option) => substituteType(option, substitutions)));
8760
+ }
8761
+ if (type.kind === "function") {
8762
+ const parameters = type.parameters.map((parameter) => substituteType(parameter, substitutions));
8763
+ const returnType = substituteType(type.returnType, substitutions);
8764
+ const variadicType = type.variadicType === void 0 ? void 0 : substituteType(type.variadicType, substitutions);
8765
+ return createFunction(parameters, returnType, type.minimumArguments, type.isVariadic, type.parameterNames, variadicType);
8766
+ }
8767
+ if (type.kind === "tuple") {
8768
+ return createTuple(type.elements.map((element) => substituteType(element, substitutions)));
8769
+ }
8770
+ if (type.kind === "record") {
8771
+ const members = new Map([...type.members].map(([name, member]) => [name, substituteType(member, substitutions)]));
8772
+ return createRecord(type.name, members, type.origin);
8773
+ }
8774
+ return type;
8775
+ }
8776
+
8777
+ // ../compiler/src/checker/generic-class.ts
8778
+ var MAX_DEPTH = 32;
8779
+ function classSubstitutions(info, typeArguments) {
8780
+ return new Map(info.typeParameters.map((parameter, index) => [parameter, typeArguments[index] ?? ANY_TYPE]));
8781
+ }
8782
+ function parentSubstitutions(registry, info, current) {
8783
+ const parent = info.superClass === null ? null : registry.lookupClass(info.superClass);
8784
+ if (parent === null) {
8785
+ return null;
8786
+ }
8787
+ return classSubstitutions(parent, info.superArguments.map((argument) => substituteType(argument, current)));
8788
+ }
8789
+ function substitutionsFor(registry, receiver, owner) {
8790
+ const info = registry.lookupClass(receiver.name);
8791
+ if (info === null) {
8792
+ return /* @__PURE__ */ new Map();
8793
+ }
8794
+ let current = info;
8795
+ let substitutions = classSubstitutions(info, receiver.typeArguments ?? []);
8796
+ for (let depth = 0; current !== null && depth < MAX_DEPTH; depth += 1) {
8797
+ if (current.name === owner) {
8798
+ return substitutions;
8799
+ }
8800
+ const next = parentSubstitutions(registry, current, substitutions);
8801
+ if (next === null) {
8802
+ return /* @__PURE__ */ new Map();
8803
+ }
8804
+ substitutions = next;
8805
+ current = current.superClass === null ? null : registry.lookupClass(current.superClass);
8806
+ }
8807
+ return /* @__PURE__ */ new Map();
8808
+ }
8809
+ function specializeMember(context, receiver, member, property) {
8810
+ const owner = context.declarations.lookupMemberOwner(receiver.name, property);
8811
+ if (owner === null || owner === receiver.name) {
8812
+ const own = context.declarations.lookupClass(receiver.name);
8813
+ if (own === null || own.typeParameters.length === 0 || (receiver.typeArguments ?? []).length === 0) {
8814
+ return member;
8815
+ }
8816
+ return { ...member, type: substituteType(member.type, classSubstitutions(own, receiver.typeArguments ?? [])) };
8817
+ }
8818
+ return { ...member, type: substituteType(member.type, substitutionsFor(context.declarations, receiver, owner)) };
8819
+ }
8820
+ function memberOf(context, receiver, property) {
8821
+ const member = context.declarations.lookupMember(receiver.name, property);
8822
+ return member === null ? null : specializeMember(context, receiver, member, property);
8823
+ }
8824
+ function unify(parameter, argument, names, bindings) {
8825
+ if (parameter.kind === "named" && names.has(parameter.name)) {
8826
+ if (!bindings.has(parameter.name) && argument.kind !== "nil") {
8827
+ bindings.set(parameter.name, argument);
8828
+ }
8829
+ return;
8830
+ }
8831
+ if (parameter.kind === "array" && argument.kind === "array") {
8832
+ unify(parameter.element, argument.element, names, bindings);
8833
+ return;
8834
+ }
8835
+ if (parameter.kind === "optional") {
8836
+ unify(parameter.element, argument.kind === "optional" ? argument.element : argument, names, bindings);
8837
+ return;
8838
+ }
8839
+ if (parameter.kind === "map" && argument.kind === "map") {
8840
+ unify(parameter.key, argument.key, names, bindings);
8841
+ unify(parameter.value, argument.value, names, bindings);
8842
+ return;
8843
+ }
8844
+ if (parameter.kind === "named" && argument.kind === "named" && parameter.name === argument.name) {
8845
+ const parameterArguments = parameter.typeArguments ?? [];
8846
+ const argumentArguments = argument.typeArguments ?? [];
8847
+ parameterArguments.forEach((entry, index) => unify(entry, argumentArguments[index] ?? ANY_TYPE, names, bindings));
8848
+ return;
8849
+ }
8850
+ if (parameter.kind === "function" && argument.kind === "function") {
8851
+ parameter.parameters.forEach((entry, index) => unify(entry, argument.parameters[index] ?? ANY_TYPE, names, bindings));
8852
+ unify(parameter.returnType, argument.returnType, names, bindings);
8853
+ }
8854
+ }
8855
+ function inferTypeArguments(typeParameters, parameters, args) {
8856
+ const names = new Set(typeParameters);
8857
+ const bindings = /* @__PURE__ */ new Map();
8858
+ parameters.forEach((parameter, index) => {
8859
+ const argument = args[index];
8860
+ if (argument !== void 0) {
8861
+ unify(parameter, widenInferred(argument), names, bindings);
8862
+ }
8863
+ });
8864
+ return typeParameters.map((parameter) => bindings.get(parameter) ?? ANY_TYPE);
8865
+ }
8866
+ function inheritsFrom(context, name, target) {
8867
+ let current = context.declarations.lookupClass(name);
8868
+ for (let depth = 0; current !== null && depth < MAX_DEPTH; depth += 1) {
8869
+ if (current.name === target || current.interfaces.includes(target)) {
8870
+ return true;
8871
+ }
8872
+ current = current.superClass === null ? null : context.declarations.lookupClass(current.superClass);
8873
+ }
8874
+ return context.declarations.interfaceExtends(name, target);
8875
+ }
8876
+ function satisfies(context, argument, constraint) {
8877
+ if (argument.kind === "named" && constraint.kind === "named") {
8878
+ return argument.name === constraint.name || inheritsFrom(context, argument.name, constraint.name);
8879
+ }
8880
+ return isAssignable(argument, constraint, { allowNil: false });
8881
+ }
8882
+ function checkTypeConstraints(context, name, typeArguments, position2) {
8883
+ const info = context.declarations.lookupClass(name);
8884
+ if (info === null) {
8885
+ return;
8886
+ }
8887
+ info.typeConstraints.forEach((constraint, index) => {
8888
+ const argument = typeArguments[index];
8889
+ if (constraint === null || argument === void 0 || argument.kind === "any" || satisfies(context, argument, constraint)) {
8890
+ return;
8891
+ }
8892
+ const parameter = info.typeParameters[index] ?? "T";
8893
+ const message = `Type argument "${typeToString(argument)}" does not satisfy "${parameter} extends ${typeToString(constraint)}" on class "${name}".`;
8894
+ context.report("check-generic-constraint", message, position2);
8895
+ });
8896
+ }
8897
+
8352
8898
  // ../mta-types/src/lua-standard.ts
8353
8899
  var LUA_GLOBALS = {
8354
8900
  _G: TABLE,
@@ -8455,8 +9001,8 @@ var MTA_AUDIO_CLIENT = {
8455
9001
  isSoundPaused: fn([named("Element")], BOOLEAN, 1),
8456
9002
  playSFX: fn([STRING, NUMBER, NUMBER, BOOLEAN], named("Element"), 3),
8457
9003
  playSFX3D: fn([STRING, NUMBER, NUMBER, NUMBER, NUMBER, NUMBER, BOOLEAN], named("Element"), 6),
8458
- playSound: fn([STRING, BOOLEAN, BOOLEAN], named("Element"), 1),
8459
- playSound3D: fn([STRING, NUMBER, NUMBER, NUMBER, BOOLEAN, BOOLEAN], named("Element"), 4),
9004
+ playSound: fn([STRING, BOOLEAN, BOOLEAN], named("Sound"), 1),
9005
+ playSound3D: fn([STRING, NUMBER, NUMBER, NUMBER, BOOLEAN, BOOLEAN], named("Sound3D"), 4),
8460
9006
  playSoundFrontEnd: fn([NUMBER], BOOLEAN, 1),
8461
9007
  setRadioChannel: fn([NUMBER], BOOLEAN, 1),
8462
9008
  setSoundEffectEnabled: fn(
@@ -8521,7 +9067,7 @@ var MTA_BLIP_CLIENT = {
8521
9067
  var MTA_BROWSER_CLIENT = {
8522
9068
  canBrowserNavigateBack: fn([named("Browser")], BOOLEAN, 1),
8523
9069
  canBrowserNavigateForward: fn([named("Browser")], BOOLEAN, 1),
8524
- createBrowser: fn([NUMBER, NUMBER, BOOLEAN, BOOLEAN], named("Element"), 3),
9070
+ createBrowser: fn([NUMBER, NUMBER, BOOLEAN, BOOLEAN], named("Browser"), 3),
8525
9071
  executeBrowserJavascript: fn([named("Browser"), STRING], BOOLEAN, 2),
8526
9072
  focusBrowser: fn([named("Browser")], BOOLEAN, 1),
8527
9073
  getBrowserProperty: fn([named("Browser"), STRING], BOOLEAN, 2),
@@ -8611,10 +9157,10 @@ var MTA_DISCORD_CLIENT = {
8611
9157
  // ../mta-types/src/generated/api/mta-drawing-client.ts
8612
9158
  var MTA_DRAWING_CLIENT = {
8613
9159
  dxConvertPixels: fn([STRING, STRING, NUMBER], STRING, 2),
8614
- dxCreateFont: fn([STRING, NUMBER, BOOLEAN, STRING], named("Element"), 1),
8615
- dxCreateRenderTarget: fn([NUMBER, NUMBER, BOOLEAN], named("Element"), 2),
8616
- dxCreateScreenSource: fn([NUMBER, NUMBER], named("Element"), 2),
8617
- dxCreateShader: fn([STRING, NUMBER, NUMBER, BOOLEAN, STRING], tupleOf([named("Element"), STRING]), 1),
9160
+ dxCreateFont: fn([STRING, NUMBER, BOOLEAN, STRING], named("DxFont"), 1),
9161
+ dxCreateRenderTarget: fn([NUMBER, NUMBER, BOOLEAN], named("DxRenderTarget"), 2),
9162
+ dxCreateScreenSource: fn([NUMBER, NUMBER], named("DxScreenSource"), 2),
9163
+ dxCreateShader: fn([STRING, NUMBER, NUMBER, BOOLEAN, STRING], tupleOf([named("DxShader"), STRING]), 1),
8618
9164
  dxCreateTexture: fn(
8619
9165
  [
8620
9166
  STRING,
@@ -9232,22 +9778,22 @@ var MTA_GUI_CLIENT = {
9232
9778
  guiComboBoxSetOpen: fn([named("Element"), BOOLEAN], BOOLEAN, 2),
9233
9779
  guiComboBoxSetSelected: fn([named("Element"), NUMBER], BOOLEAN, 2),
9234
9780
  guiCreateBrowser: fn([NUMBER, NUMBER, NUMBER, NUMBER, BOOLEAN, BOOLEAN, BOOLEAN, named("GuiElement")], named("GuiBrowser"), 6),
9235
- guiCreateButton: fn([NUMBER, NUMBER, NUMBER, NUMBER, STRING, BOOLEAN, named("GuiElement")], named("Element"), 5),
9236
- guiCreateCheckBox: fn([NUMBER, NUMBER, NUMBER, NUMBER, STRING, BOOLEAN, BOOLEAN, named("GuiElement")], named("Element"), 6),
9237
- guiCreateComboBox: fn([NUMBER, NUMBER, NUMBER, NUMBER, STRING, BOOLEAN, named("GuiElement")], named("Element"), 5),
9238
- guiCreateEdit: fn([NUMBER, NUMBER, NUMBER, NUMBER, STRING, BOOLEAN, named("GuiElement")], named("Element"), 5),
9239
- guiCreateFont: fn([STRING, NUMBER], named("Element"), 1),
9240
- guiCreateGridList: fn([NUMBER, NUMBER, NUMBER, NUMBER, BOOLEAN, named("GuiElement")], named("Element"), 4),
9241
- guiCreateLabel: fn([NUMBER, NUMBER, NUMBER, NUMBER, STRING, BOOLEAN, named("GuiElement")], named("Element"), 5),
9781
+ guiCreateButton: fn([NUMBER, NUMBER, NUMBER, NUMBER, STRING, BOOLEAN, named("GuiElement")], named("GuiButton"), 5),
9782
+ guiCreateCheckBox: fn([NUMBER, NUMBER, NUMBER, NUMBER, STRING, BOOLEAN, BOOLEAN, named("GuiElement")], named("GuiCheckbox"), 6),
9783
+ guiCreateComboBox: fn([NUMBER, NUMBER, NUMBER, NUMBER, STRING, BOOLEAN, named("GuiElement")], named("GuiCombobox"), 5),
9784
+ guiCreateEdit: fn([NUMBER, NUMBER, NUMBER, NUMBER, STRING, BOOLEAN, named("GuiElement")], named("GuiEdit"), 5),
9785
+ guiCreateFont: fn([STRING, NUMBER], named("GuiFont"), 1),
9786
+ guiCreateGridList: fn([NUMBER, NUMBER, NUMBER, NUMBER, BOOLEAN, named("GuiElement")], named("GuiGridList"), 4),
9787
+ guiCreateLabel: fn([NUMBER, NUMBER, NUMBER, NUMBER, STRING, BOOLEAN, named("GuiElement")], named("GuiLabel"), 5),
9242
9788
  guiCreateMemo: fn([NUMBER, NUMBER, NUMBER, NUMBER, STRING, BOOLEAN, named("GuiElement")], named("GuiMemo"), 5),
9243
9789
  guiCreateProgressBar: fn([NUMBER, NUMBER, NUMBER, NUMBER, BOOLEAN, named("GuiElement")], named("Element"), 4),
9244
- guiCreateRadioButton: fn([NUMBER, NUMBER, NUMBER, NUMBER, STRING, BOOLEAN, named("GuiElement")], named("Element"), 5),
9790
+ guiCreateRadioButton: fn([NUMBER, NUMBER, NUMBER, NUMBER, STRING, BOOLEAN, named("GuiElement")], named("GuiRadioButton"), 5),
9245
9791
  guiCreateScrollBar: fn([NUMBER, NUMBER, NUMBER, NUMBER, BOOLEAN, BOOLEAN, named("GuiElement")], named("GuiElement"), 5),
9246
9792
  guiCreateScrollPane: fn([NUMBER, NUMBER, NUMBER, NUMBER, BOOLEAN, named("GuiElement")], named("Element"), 4),
9247
- guiCreateStaticImage: fn([NUMBER, NUMBER, NUMBER, NUMBER, STRING, BOOLEAN, named("GuiElement")], named("Element"), 5),
9248
- guiCreateTab: fn([STRING, named("GuiElement")], named("Element"), 2),
9249
- guiCreateTabPanel: fn([NUMBER, NUMBER, NUMBER, NUMBER, BOOLEAN, named("GuiElement")], named("Element"), 4),
9250
- guiCreateWindow: fn([NUMBER, NUMBER, NUMBER, NUMBER, STRING, BOOLEAN], named("Element"), 5),
9793
+ guiCreateStaticImage: fn([NUMBER, NUMBER, NUMBER, NUMBER, STRING, BOOLEAN, named("GuiElement")], named("GuiStaticImage"), 5),
9794
+ guiCreateTab: fn([STRING, named("GuiElement")], named("GuiTab"), 2),
9795
+ guiCreateTabPanel: fn([NUMBER, NUMBER, NUMBER, NUMBER, BOOLEAN, named("GuiElement")], named("GuiTabPanel"), 4),
9796
+ guiCreateWindow: fn([NUMBER, NUMBER, NUMBER, NUMBER, STRING, BOOLEAN], named("GuiWindow"), 5),
9251
9797
  guiDeleteTab: fn([named("Element"), named("Element")], BOOLEAN, 2),
9252
9798
  guiEditGetCaretIndex: fn([named("Element")], NUMBER, 1),
9253
9799
  guiEditGetMaxLength: fn([named("GuiEdit")], NUMBER, 1),
@@ -11590,7 +12136,7 @@ var MTA_OOP_1 = [
11590
12136
  ),
11591
12137
  oopClass(
11592
12138
  "DxFont",
11593
- null,
12139
+ "Element",
11594
12140
  [
11595
12141
  oopMethod("getHeight", "client", "dxGetFontHeight", fn([NUMBER, ANY], NUMBER, 0)),
11596
12142
  oopMethod("getSize", "client", "dxGetTextSize", fn([STRING, NUMBER, NUMBER, NUMBER, ANY, BOOLEAN, BOOLEAN], tupleOf([NUMBER, NUMBER]), 1)),
@@ -11601,14 +12147,14 @@ var MTA_OOP_1 = [
11601
12147
  ),
11602
12148
  oopClass(
11603
12149
  "DxRenderTarget",
11604
- null,
12150
+ "Element",
11605
12151
  [],
11606
12152
  [],
11607
12153
  oopConstructor("client", fn([NUMBER, NUMBER, BOOLEAN], named("DxRenderTarget"), 2), "dxCreateRenderTarget")
11608
12154
  ),
11609
12155
  oopClass(
11610
12156
  "DxScreenSource",
11611
- null,
12157
+ "Element",
11612
12158
  [
11613
12159
  oopMethod("update", "client", "dxUpdateScreenSource", fn([BOOLEAN], BOOLEAN, 0))
11614
12160
  ],
@@ -11621,14 +12167,14 @@ var MTA_OOP_1 = [
11621
12167
  var MTA_OOP_2 = [
11622
12168
  oopClass(
11623
12169
  "DxShader",
11624
- null,
12170
+ "Element",
11625
12171
  [],
11626
12172
  [],
11627
12173
  oopConstructor("client", fn([STRING, NUMBER, NUMBER, BOOLEAN, STRING], named("DxShader"), 1), "dxCreateShader")
11628
12174
  ),
11629
12175
  oopClass(
11630
12176
  "DxTexture",
11631
- null,
12177
+ "Element",
11632
12178
  [
11633
12179
  oopMethod("getPixels", "client", "dxGetTexturePixels", fn([ANY, ANY, NUMBER, NUMBER, NUMBER, ANY], STRING, 1)),
11634
12180
  oopMethod("setEdge", "client", "dxSetTextureEdge", fn([STRING, NUMBER], BOOLEAN, 1)),
@@ -11917,7 +12463,7 @@ var MTA_OOP_3 = [
11917
12463
  ),
11918
12464
  oopClass(
11919
12465
  "GuiButton",
11920
- null,
12466
+ "GuiElement",
11921
12467
  [],
11922
12468
  [],
11923
12469
  oopConstructor("client", fn([NUMBER, NUMBER, NUMBER, NUMBER, STRING, BOOLEAN, named("Element")], named("GuiButton"), 6), "guiCreateButton")
@@ -11974,7 +12520,7 @@ var MTA_OOP_3 = [
11974
12520
  ),
11975
12521
  oopClass(
11976
12522
  "GuiElement",
11977
- null,
12523
+ "Element",
11978
12524
  [
11979
12525
  oopProperty("alpha", "client", "guiGetAlpha", NUMBER),
11980
12526
  oopMethod("blur", "client", "guiBlur", fn([], BOOLEAN, 0)),
@@ -12027,7 +12573,7 @@ var MTA_OOP_3 = [
12027
12573
  ),
12028
12574
  oopClass(
12029
12575
  "GuiFont",
12030
- null,
12576
+ "Element",
12031
12577
  [],
12032
12578
  [],
12033
12579
  oopConstructor("client", fn([STRING, NUMBER], named("GuiFont"), 1), "guiCreateFont")
@@ -12115,7 +12661,7 @@ var MTA_OOP_3 = [
12115
12661
  ),
12116
12662
  oopClass(
12117
12663
  "GuiRadioButton",
12118
- null,
12664
+ "GuiElement",
12119
12665
  [
12120
12666
  oopMethod("getSelected", "client", "guiRadioButtonGetSelected", fn([], BOOLEAN, 0)),
12121
12667
  oopProperty("selected", "client", "guiRadioButtonGetSelected", BOOLEAN),
@@ -12176,7 +12722,7 @@ var MTA_OOP_4 = [
12176
12722
  ),
12177
12723
  oopClass(
12178
12724
  "Light",
12179
- null,
12725
+ "Element",
12180
12726
  [
12181
12727
  oopProperty("color", "client", "getLightColor", tupleOf([NUMBER, NUMBER, NUMBER])),
12182
12728
  oopProperty("direction", "client", "getLightDirection", tupleOf([NUMBER, NUMBER, NUMBER])),
@@ -13186,6 +13732,46 @@ var DeclarationRegistry = class {
13186
13732
  }
13187
13733
  return null;
13188
13734
  }
13735
+ lookupMemberOwner(name, member) {
13736
+ const visited = /* @__PURE__ */ new Set();
13737
+ let current = this.lookupClass(name);
13738
+ while (current !== null && !visited.has(current.name)) {
13739
+ if (current.members.has(member)) {
13740
+ return current.name;
13741
+ }
13742
+ visited.add(current.name);
13743
+ current = current.superClass === null ? null : this.lookupClass(current.superClass);
13744
+ }
13745
+ return null;
13746
+ }
13747
+ lookupStaticMember(name, member) {
13748
+ const visited = /* @__PURE__ */ new Set();
13749
+ let current = this.lookupClass(name);
13750
+ while (current !== null && !visited.has(current.name)) {
13751
+ const found = current.statics.get(member);
13752
+ if (found !== void 0) {
13753
+ return found;
13754
+ }
13755
+ visited.add(current.name);
13756
+ current = current.superClass === null ? null : this.lookupClass(current.superClass);
13757
+ }
13758
+ return null;
13759
+ }
13760
+ collectStatics(name) {
13761
+ const collected = /* @__PURE__ */ new Map();
13762
+ const visited = /* @__PURE__ */ new Set();
13763
+ let current = this.lookupClass(name);
13764
+ while (current !== null && !visited.has(current.name)) {
13765
+ for (const [member, info] of current.statics) {
13766
+ if (!collected.has(member)) {
13767
+ collected.set(member, info);
13768
+ }
13769
+ }
13770
+ visited.add(current.name);
13771
+ current = current.superClass === null ? null : this.lookupClass(current.superClass);
13772
+ }
13773
+ return [...collected.values()];
13774
+ }
13189
13775
  lookupMember(name, member) {
13190
13776
  const visited = /* @__PURE__ */ new Set();
13191
13777
  const found = this.lookupClassMember(name, member, visited);
@@ -13217,7 +13803,17 @@ function buildRegistry() {
13217
13803
  procedural: member.procedural
13218
13804
  });
13219
13805
  }
13220
- registry.declareClass({ name: declaration.name, superClass: declaration.parent, interfaces: [], members, position: MTA_POSITION });
13806
+ registry.declareClass({
13807
+ name: declaration.name,
13808
+ typeParameters: [],
13809
+ typeConstraints: [],
13810
+ superClass: declaration.parent,
13811
+ superArguments: [],
13812
+ interfaces: [],
13813
+ members,
13814
+ statics: /* @__PURE__ */ new Map(),
13815
+ position: MTA_POSITION
13816
+ });
13221
13817
  }
13222
13818
  return registry;
13223
13819
  }
@@ -13304,12 +13900,22 @@ function missingKeys(source, target) {
13304
13900
  }
13305
13901
  return missing2;
13306
13902
  }
13307
- function missingKeyHint(source, target) {
13903
+ function expandCandidate(option, resolveNominal) {
13904
+ if (option.kind === "record") {
13905
+ return option;
13906
+ }
13907
+ if (option.kind !== "named") {
13908
+ return null;
13909
+ }
13910
+ const shape = resolveNominal?.(option.name) ?? null;
13911
+ return shape === null || shape.kind !== "interface" ? null : { kind: "record", name: option.name, origin: null, members: shape.members };
13912
+ }
13913
+ function missingKeyHint(source, target, resolveNominal) {
13308
13914
  if (source.kind !== "record" || source.isLiteral !== true) {
13309
13915
  return "";
13310
13916
  }
13311
13917
  const options = target.kind === "union" ? target.options : [target];
13312
- const candidates3 = options.filter((option) => option.kind === "record");
13918
+ const candidates3 = options.map((option) => expandCandidate(option, resolveNominal)).filter((option) => option !== null);
13313
13919
  const candidate = chooseCandidate(source, candidates3);
13314
13920
  if (candidate === null) {
13315
13921
  return "";
@@ -13357,39 +13963,6 @@ function mergeIntersection(context, parts, position2) {
13357
13963
  return createObjectType(members);
13358
13964
  }
13359
13965
 
13360
- // ../compiler/src/checker/type-substitution.ts
13361
- function substituteType(type, substitutions) {
13362
- if (type.kind === "named") {
13363
- return substitutions.get(type.name) ?? type;
13364
- }
13365
- if (type.kind === "array") {
13366
- return createArray(substituteType(type.element, substitutions));
13367
- }
13368
- if (type.kind === "map") {
13369
- return createMap(substituteType(type.key, substitutions), substituteType(type.value, substitutions));
13370
- }
13371
- if (type.kind === "optional") {
13372
- return createOptional(substituteType(type.element, substitutions));
13373
- }
13374
- if (type.kind === "union") {
13375
- return createUnion(type.options.map((option) => substituteType(option, substitutions)));
13376
- }
13377
- if (type.kind === "function") {
13378
- const parameters = type.parameters.map((parameter) => substituteType(parameter, substitutions));
13379
- const returnType = substituteType(type.returnType, substitutions);
13380
- const variadicType = type.variadicType === void 0 ? void 0 : substituteType(type.variadicType, substitutions);
13381
- return createFunction(parameters, returnType, type.minimumArguments, type.isVariadic, type.parameterNames, variadicType);
13382
- }
13383
- if (type.kind === "tuple") {
13384
- return createTuple(type.elements.map((element) => substituteType(element, substitutions)));
13385
- }
13386
- if (type.kind === "record") {
13387
- const members = new Map([...type.members].map(([name, member]) => [name, substituteType(member, substitutions)]));
13388
- return createRecord(type.name, members, type.origin);
13389
- }
13390
- return type;
13391
- }
13392
-
13393
13966
  // ../compiler/src/checker/context.ts
13394
13967
  var BUILTIN_TYPES = {
13395
13968
  any: ANY_TYPE,
@@ -13405,6 +13978,7 @@ var BUILTIN_TYPES = {
13405
13978
  };
13406
13979
  var TABLE_ANNOTATION = "table";
13407
13980
  var MAP_ARGUMENTS = 2;
13981
+ var MAX_GENERIC_DEPTH = 8;
13408
13982
  var PROJECT_POSITION = { line: 0, column: 0, offset: 0 };
13409
13983
  function nilHint(source, target, mode) {
13410
13984
  if (mode !== "strict" || source.kind !== "nil" || target.kind === "nil" || target.kind === "optional") {
@@ -13417,18 +13991,23 @@ var CheckContext = class {
13417
13991
  declarations = new DeclarationRegistry();
13418
13992
  diagnostics = [];
13419
13993
  reported = /* @__PURE__ */ new Set();
13420
- narrowings = [];
13994
+ flow = createFlow();
13421
13995
  types = /* @__PURE__ */ new Map();
13422
13996
  references = /* @__PURE__ */ new Set();
13423
13997
  declaredGlobals = /* @__PURE__ */ new Map();
13998
+ moduleGlobals = /* @__PURE__ */ new Map();
13424
13999
  externalReferences = /* @__PURE__ */ new Map();
13425
14000
  unknownTypes = /* @__PURE__ */ new Map();
14001
+ eventReferences = /* @__PURE__ */ new Set();
14002
+ globalReferences = /* @__PURE__ */ new Set();
13426
14003
  calledMembers = /* @__PURE__ */ new Set();
14004
+ staticAccess = /* @__PURE__ */ new Set();
13427
14005
  typeParameters = /* @__PURE__ */ new Set();
13428
14006
  generatedMembers = /* @__PURE__ */ new Map();
13429
14007
  mode;
13430
14008
  environment;
13431
14009
  mtaClasses;
14010
+ contracts = [];
13432
14011
  isDeclarationFile = false;
13433
14012
  returnStack = [];
13434
14013
  methodStack = [];
@@ -13454,33 +14033,28 @@ var CheckContext = class {
13454
14033
  declareModuleGlobal(symbol) {
13455
14034
  this.binder.declareGlobal(symbol);
13456
14035
  this.declaredGlobals.set(symbol.name, symbol.position);
14036
+ this.moduleGlobals.set(symbol.name, symbol.type);
13457
14037
  }
13458
- pushNarrowing(facts) {
13459
- this.narrowings.push(new Map(facts));
14038
+ get flowState() {
14039
+ return this.flow;
13460
14040
  }
13461
- get isNarrowed() {
13462
- return this.narrowings.length > 0;
14041
+ setFlow(state) {
14042
+ this.flow = state;
13463
14043
  }
13464
- popNarrowing() {
13465
- this.narrowings.pop();
14044
+ applyFlowFacts(facts) {
14045
+ applyFacts(this.flow, facts);
14046
+ }
14047
+ markUnreachable() {
14048
+ this.flow.reachable = false;
14049
+ }
14050
+ get isNarrowed() {
14051
+ return this.flow.facts.size > 0;
13466
14052
  }
13467
14053
  forgetNarrowing(path) {
13468
- for (const frame of this.narrowings) {
13469
- for (const key of [...frame.keys()]) {
13470
- if (key === path || key.startsWith(`${path}.`) || path.startsWith(`${key}.`)) {
13471
- frame.delete(key);
13472
- }
13473
- }
13474
- }
14054
+ forgetPath(this.flow, path);
13475
14055
  }
13476
14056
  narrowedType(path) {
13477
- for (let index = this.narrowings.length - 1; index >= 0; index -= 1) {
13478
- const found = this.narrowings[index]?.get(path);
13479
- if (found !== void 0) {
13480
- return found;
13481
- }
13482
- }
13483
- return null;
14057
+ return this.flow.facts.get(path) ?? null;
13484
14058
  }
13485
14059
  predeclareClass(info) {
13486
14060
  this.predeclared.set(info.name, info);
@@ -13490,20 +14064,20 @@ var CheckContext = class {
13490
14064
  this.predeclared.delete(name);
13491
14065
  return info;
13492
14066
  }
13493
- isPredeclaredClass(name) {
13494
- return this.predeclared.has(name);
13495
- }
13496
- awaitsDeclaration(name) {
14067
+ pendingDeclarationOf(name) {
13497
14068
  const seen = /* @__PURE__ */ new Set();
13498
14069
  let current = this.declarations.lookupClass(name);
13499
14070
  while (current !== null && !seen.has(current.name)) {
13500
14071
  if (this.predeclared.has(current.name)) {
13501
- return true;
14072
+ return current.name;
13502
14073
  }
13503
14074
  seen.add(current.name);
13504
14075
  current = current.superClass === null ? null : this.declarations.lookupClass(current.superClass);
13505
14076
  }
13506
- return false;
14077
+ return null;
14078
+ }
14079
+ awaitsDeclaration(name) {
14080
+ return this.pendingDeclarationOf(name) !== null;
13507
14081
  }
13508
14082
  insideFunction() {
13509
14083
  return this.returnStack.length > 0;
@@ -13547,6 +14121,12 @@ var CheckContext = class {
13547
14121
  isTypeParameter(name) {
13548
14122
  return this.typeParameters.has(name);
13549
14123
  }
14124
+ noteGlobalReference(name) {
14125
+ this.globalReferences.add(name);
14126
+ }
14127
+ noteEventReference(name) {
14128
+ this.eventReferences.add(name);
14129
+ }
13550
14130
  noteUnknownType(name, position2) {
13551
14131
  if (!this.unknownTypes.has(name)) {
13552
14132
  this.unknownTypes.set(name, position2);
@@ -13636,17 +14216,57 @@ var CheckContext = class {
13636
14216
  const parameters = annotation.parameters.map((parameter) => this.resolveAnnotation(parameter));
13637
14217
  const optional = parameters.findIndex((parameter) => parameter.kind === "optional");
13638
14218
  const minimum = optional === -1 ? parameters.length : optional;
13639
- return createFunction(parameters, this.resolveAnnotation(annotation.returnType), minimum, annotation.isVariadic);
14219
+ return createFunction(parameters, this.resolveAnnotation(annotation.returnType), minimum, annotation.isVariadic, [...annotation.parameterNames]);
13640
14220
  }
13641
14221
  return this.resolveNamedAnnotation(annotation.name, annotation.typeArguments, annotation.position);
13642
14222
  }
14223
+ nominalShape(name) {
14224
+ const declaredInterface = this.declarations.lookupInterface(name);
14225
+ if (declaredInterface !== null) {
14226
+ const ancestors = this.declarations.allInterfaces().filter((other) => this.declarations.interfaceExtends(name, other.name));
14227
+ return {
14228
+ kind: "interface",
14229
+ members: new Map(this.declarations.collectInterfaceContract(name).map((member) => [member.name, member.type])),
14230
+ ancestors: new Set(ancestors.map((other) => other.name))
14231
+ };
14232
+ }
14233
+ const declaredClass = this.declarations.lookupClass(name);
14234
+ if (declaredClass === null) {
14235
+ return null;
14236
+ }
14237
+ return {
14238
+ kind: "class",
14239
+ members: new Map(this.declarations.collectMembers(name).map((member) => [member.name, member.type])),
14240
+ ancestors: this.classAncestors(name)
14241
+ };
14242
+ }
14243
+ classAncestors(name) {
14244
+ const ancestors = /* @__PURE__ */ new Set();
14245
+ let current = this.declarations.lookupClass(name);
14246
+ while (current !== null && !ancestors.has(current.name)) {
14247
+ ancestors.add(current.name);
14248
+ for (const contract of current.interfaces) {
14249
+ ancestors.add(contract);
14250
+ for (const other of this.declarations.allInterfaces()) {
14251
+ if (this.declarations.interfaceExtends(contract, other.name)) {
14252
+ ancestors.add(other.name);
14253
+ }
14254
+ }
14255
+ }
14256
+ current = current.superClass === null ? null : this.declarations.lookupClass(current.superClass);
14257
+ }
14258
+ return ancestors;
14259
+ }
14260
+ assignability() {
14261
+ return { allowNil: this.allowNil, resolveNominal: (name) => this.nominalShape(name) };
14262
+ }
13643
14263
  expectAssignable(source, target, position2, subject) {
13644
- if (isAssignable(source, target, { allowNil: this.allowNil })) {
14264
+ if (isAssignable(source, target, this.assignability())) {
13645
14265
  return;
13646
14266
  }
13647
14267
  const received = isLiteralType(target) || target.kind === "union" ? source : widenLiteral(source);
13648
14268
  const message = `${subject} expects "${typeToString(target)}" but received "${typeToString(received)}".`;
13649
- const hint = `${nilHint(source, target, this.mode)}${missingKeyHint(source, target)}`;
14269
+ const hint = `${nilHint(source, target, this.mode)}${missingKeyHint(source, target, (name) => this.nominalShape(name))}`;
13650
14270
  this.report("check-type-mismatch", `${message}${hint}`, position2);
13651
14271
  }
13652
14272
  declareProject(project) {
@@ -13692,6 +14312,25 @@ var CheckContext = class {
13692
14312
  }
13693
14313
  return createMap(this.resolveAnnotation(key), this.resolveAnnotation(value));
13694
14314
  }
14315
+ resolveClassAnnotation(name, typeArguments, position2) {
14316
+ const declared = this.declarations.lookupClass(name)?.typeParameters ?? [];
14317
+ const resolved2 = typeArguments.map((argument) => this.resolveAnnotation(argument));
14318
+ if (declared.length === 0) {
14319
+ return createNamed(name);
14320
+ }
14321
+ if (resolved2.length !== declared.length) {
14322
+ const expected = declared.length === 1 ? "1 type argument" : `${declared.length} type arguments`;
14323
+ this.report("check-generic-arity", `Class "${name}" expects ${expected} but received ${resolved2.length}.`, position2);
14324
+ }
14325
+ const specialized = createNamed(name, declared.map((unused, index) => resolved2[index] ?? ANY_TYPE));
14326
+ if (namedNesting(specialized) > MAX_GENERIC_DEPTH) {
14327
+ const message = `Class "${name}" is nested more than ${MAX_GENERIC_DEPTH} levels deep here. Name the inner type with a "type" alias.`;
14328
+ this.report("check-generic-depth", message, position2);
14329
+ return createNamed(name);
14330
+ }
14331
+ checkTypeConstraints(this, name, specialized.typeArguments ?? [], position2);
14332
+ return specialized;
14333
+ }
13695
14334
  resolveNamedAnnotation(name, typeArguments, position2) {
13696
14335
  const builtin = BUILTIN_TYPES[name];
13697
14336
  if (builtin !== void 0) {
@@ -13706,7 +14345,7 @@ var CheckContext = class {
13706
14345
  const alias = this.binder.lookupAlias(name);
13707
14346
  if (alias === null) {
13708
14347
  this.noteUnknownType(name, position2);
13709
- return createNamed(name);
14348
+ return this.resolveClassAnnotation(name, typeArguments, position2);
13710
14349
  }
13711
14350
  if (typeArguments.length !== alias.parameters.length) {
13712
14351
  const expected = alias.parameters.length === 1 ? "1 type argument" : `${alias.parameters.length} type arguments`;
@@ -13963,6 +14602,73 @@ function checkOverrides(context, info, statement) {
13963
14602
  }
13964
14603
  }
13965
14604
 
14605
+ // ../compiler/src/checker/metamethods.ts
14606
+ var CONTRACTS = [
14607
+ { name: "__tostring", parameters: 0, returns: "string", documentation: 'Renders the instance for "tostring" and string coercion.' },
14608
+ { name: "__eq", parameters: 1, returns: "boolean", documentation: 'Compares two instances of the same class with "==".' },
14609
+ { name: "__lt", parameters: 1, returns: "boolean", documentation: 'Orders two instances with "<" and ">".' },
14610
+ { name: "__le", parameters: 1, returns: "boolean", documentation: 'Orders two instances with "<=" and ">=".' },
14611
+ { name: "__len", parameters: 0, returns: "number", documentation: 'Answers the length operator "#".' },
14612
+ { name: "__concat", parameters: 1, returns: "any", documentation: 'Answers the concatenation operator "..".' },
14613
+ { name: "__unm", parameters: 0, returns: "any", documentation: "Answers unary minus." },
14614
+ { name: "__add", parameters: 1, returns: "any", documentation: 'Answers "+".' },
14615
+ { name: "__sub", parameters: 1, returns: "any", documentation: 'Answers "-".' },
14616
+ { name: "__mul", parameters: 1, returns: "any", documentation: 'Answers "*".' },
14617
+ { name: "__div", parameters: 1, returns: "any", documentation: 'Answers "/".' },
14618
+ { name: "__mod", parameters: 1, returns: "any", documentation: 'Answers "%".' },
14619
+ { name: "__pow", parameters: 1, returns: "any", documentation: 'Answers "^".' }
14620
+ ];
14621
+ var BLOCKED = {
14622
+ __index: "it replaces member lookup, which the class helper owns",
14623
+ __newindex: "it swallows a field write, which the class helper owns",
14624
+ __call: "it makes an instance callable, which hides construction",
14625
+ __gc: "Lua 5.1 does not run it for a table",
14626
+ __metatable: "it hides the metatable the class helper needs",
14627
+ __mode: "it turns instances into weak references the class helper cannot track"
14628
+ };
14629
+ var METAMETHOD_CONTRACTS = new Map(CONTRACTS.map((contract) => [contract.name, contract]));
14630
+ function isMetamethodName(name) {
14631
+ return name.startsWith("__");
14632
+ }
14633
+ function isAllowedMetamethod(name) {
14634
+ return METAMETHOD_CONTRACTS.has(name);
14635
+ }
14636
+ function isMetamethodMember(member) {
14637
+ return member.kind === "class-method" && !member.isStatic && isAllowedMetamethod(member.name);
14638
+ }
14639
+ function reportReturn(context, className, contract, type, member) {
14640
+ if (contract.returns === "any" || type.kind === "any" || type.kind === contract.returns) {
14641
+ return;
14642
+ }
14643
+ const message = `Metamethod "${contract.name}" of class "${className}" must return "${contract.returns}" but returns "${typeToString(type)}".`;
14644
+ context.report("check-invalid-metamethod", message, member.position);
14645
+ }
14646
+ function checkMetamethod(context, className, member, type) {
14647
+ const contract = METAMETHOD_CONTRACTS.get(member.name);
14648
+ if (contract === void 0 || type.kind !== "function") {
14649
+ return;
14650
+ }
14651
+ if (type.parameters.length !== contract.parameters) {
14652
+ const expected = contract.parameters === 1 ? "1 parameter" : `${contract.parameters} parameters`;
14653
+ const message = `Metamethod "${contract.name}" of class "${className}" takes ${expected} beside "self" but declares ${type.parameters.length}.`;
14654
+ context.report("check-invalid-metamethod", message, member.position);
14655
+ }
14656
+ reportReturn(context, className, contract, type.returnType, member);
14657
+ }
14658
+ function reportRejectedMetamethod(context, className, member) {
14659
+ if (member.kind !== "class-method" || !isMetamethodName(member.name) || isAllowedMetamethod(member.name)) {
14660
+ return;
14661
+ }
14662
+ const blocked = BLOCKED[member.name];
14663
+ if (blocked !== void 0) {
14664
+ context.report("check-blocked-metamethod", `Metamethod "${member.name}" cannot be declared on class "${className}" because ${blocked}.`, member.position);
14665
+ return;
14666
+ }
14667
+ const known = [...METAMETHOD_CONTRACTS.keys()].map((name) => `"${name}"`).join(", ");
14668
+ const message = `"${member.name}" is not a metamethod Luam exposes on class "${className}". Declared metamethods are ${known}.`;
14669
+ context.report("check-blocked-metamethod", message, member.position);
14670
+ }
14671
+
13966
14672
  // ../compiler/src/checker/accessor-names.ts
13967
14673
  function capitalize(name) {
13968
14674
  return `${name.charAt(0).toUpperCase()}${name.slice(1)}`;
@@ -14047,6 +14753,25 @@ var KNOWN_DECORATORS = /* @__PURE__ */ new Map([
14047
14753
  diagnostics: [CONFLICT]
14048
14754
  }
14049
14755
  ],
14756
+ [
14757
+ "Validated",
14758
+ {
14759
+ name: "Validated",
14760
+ documentation: "Generates runtime checks for the declared field types.",
14761
+ targets: ["class"],
14762
+ generates: [
14763
+ "`ClassName.validate(value: table): ClassName`, returning the value or raising a Lua error naming the path and the expected type.",
14764
+ "`ClassName.matches(value: table): boolean`, the same check without raising."
14765
+ ],
14766
+ rules: [
14767
+ "Both are static, so they are called on the class and never on an instance.",
14768
+ "The check walks the declared field types with a depth, entry-count and string-length limit, and a failure names the path and the expected type but never the value.",
14769
+ "A field whose type has no runtime shape is `check-unreifiable-type`, reported before anything is emitted.",
14770
+ "It is the only decorator that adds runtime code beyond the class helper; a class without it emits nothing extra."
14771
+ ],
14772
+ diagnostics: [CONFLICT, "`check-unreifiable-type` when a field type cannot be checked at runtime."]
14773
+ }
14774
+ ],
14050
14775
  [
14051
14776
  "Serializable",
14052
14777
  {
@@ -14137,6 +14862,102 @@ var KNOWN_DECORATORS = /* @__PURE__ */ new Map([
14137
14862
  ]
14138
14863
  ]);
14139
14864
 
14865
+ // ../compiler/src/checker/validation-descriptor.ts
14866
+ var MAX_EXPANSION = 8;
14867
+ var PRIMITIVE_KINDS = {
14868
+ nil: "nil",
14869
+ boolean: "boolean",
14870
+ number: "number",
14871
+ string: "string",
14872
+ table: "table",
14873
+ thread: "thread",
14874
+ userdata: "userdata",
14875
+ function: "function"
14876
+ };
14877
+ function quote2(value) {
14878
+ return `'${value.replace(/([\\'])/g, "\\$1").replace(/\n/g, "\\n")}'`;
14879
+ }
14880
+ function failed(type) {
14881
+ return { lua: null, unreifiable: typeToString(type) };
14882
+ }
14883
+ function entries2(parts) {
14884
+ return `{ ${parts.join(", ")} }`;
14885
+ }
14886
+ function combine(parts, render2) {
14887
+ const broken = parts.find((part) => part.lua === null);
14888
+ if (broken !== void 0) {
14889
+ return broken;
14890
+ }
14891
+ return { lua: render2(parts.map((part) => part.lua)), unreifiable: null };
14892
+ }
14893
+ function namedDescriptor(context, type, name, seen) {
14894
+ if (context.declarations.lookupClass(name) !== null) {
14895
+ return { lua: entries2([`kind = 'instance'`, `name = ${quote2(name)}`]), unreifiable: null };
14896
+ }
14897
+ if (context.declarations.lookupEnum(name) !== null) {
14898
+ return { lua: entries2([`kind = 'number'`]), unreifiable: null };
14899
+ }
14900
+ if (context.declarations.lookupInterface(name) === null || seen.has(name) || seen.size >= MAX_EXPANSION) {
14901
+ return failed(type);
14902
+ }
14903
+ const members = context.declarations.collectMembers(name);
14904
+ const nested = /* @__PURE__ */ new Set([...seen, name]);
14905
+ const parts = members.map((member) => describe(context, member.type, nested));
14906
+ return combine(parts, (values) => {
14907
+ const rendered = members.map((member, index) => entries2([`key = ${quote2(member.name)}`, `value = ${values[index] ?? ""}`]));
14908
+ return entries2([`kind = 'record'`, `name = ${quote2(name)}`, `members = ${entries2(rendered)}`]);
14909
+ });
14910
+ }
14911
+ function describe(context, type, seen = /* @__PURE__ */ new Set()) {
14912
+ const primitive = PRIMITIVE_KINDS[type.kind];
14913
+ if (primitive !== void 0) {
14914
+ return { lua: entries2([`kind = ${quote2(primitive)}`]), unreifiable: null };
14915
+ }
14916
+ if (type.kind === "string-literal") {
14917
+ return { lua: entries2([`kind = 'literal'`, `value = ${quote2(type.value)}`]), unreifiable: null };
14918
+ }
14919
+ if (type.kind === "boolean-literal" || type.kind === "number-literal") {
14920
+ return { lua: entries2([`kind = 'literal'`, `value = ${String(type.value)}`]), unreifiable: null };
14921
+ }
14922
+ if (type.kind === "optional") {
14923
+ return combine([describe(context, type.element, seen)], (values) => entries2([`kind = 'optional'`, `element = ${values[0] ?? ""}`]));
14924
+ }
14925
+ if (type.kind === "array") {
14926
+ return combine([describe(context, type.element, seen)], (values) => entries2([`kind = 'array'`, `element = ${values[0] ?? ""}`]));
14927
+ }
14928
+ if (type.kind === "map") {
14929
+ return combine(
14930
+ [describe(context, type.key, seen), describe(context, type.value, seen)],
14931
+ (values) => entries2([`kind = 'map'`, `key = ${values[0] ?? ""}`, `value = ${values[1] ?? ""}`])
14932
+ );
14933
+ }
14934
+ if (type.kind === "union") {
14935
+ return combine(
14936
+ type.options.map((option) => describe(context, option, seen)),
14937
+ (values) => entries2([`kind = 'union'`, `options = ${entries2(values)}`])
14938
+ );
14939
+ }
14940
+ if (type.kind === "record") {
14941
+ const members = [...type.members];
14942
+ const parts = members.map(([, member]) => describe(context, member, seen));
14943
+ return combine(parts, (values) => {
14944
+ const rendered = members.map(([key], index) => entries2([`key = ${quote2(key)}`, `value = ${values[index] ?? ""}`]));
14945
+ return entries2([`kind = 'record'`, `name = ${quote2(type.name)}`, `members = ${entries2(rendered)}`]);
14946
+ });
14947
+ }
14948
+ if (type.kind === "named") {
14949
+ return namedDescriptor(context, type, type.name, seen);
14950
+ }
14951
+ return failed(type);
14952
+ }
14953
+ function classDescriptor(context, className, fields) {
14954
+ const parts = fields.map((field2) => describe(context, field2.type));
14955
+ return combine(parts, (values) => {
14956
+ const rendered = fields.map((field2, index) => entries2([`key = ${quote2(field2.name)}`, `value = ${values[index] ?? ""}`]));
14957
+ return entries2([`kind = 'record'`, `name = ${quote2(className)}`, `members = ${entries2(rendered)}`]);
14958
+ });
14959
+ }
14960
+
14140
14961
  // ../compiler/src/checker/decorators.ts
14141
14962
  function memberExpression(field2) {
14142
14963
  return {
@@ -14163,6 +14984,7 @@ function accessorMethod(field2, decorator, name) {
14163
14984
  name,
14164
14985
  isConstructor: false,
14165
14986
  isSynthetic: true,
14987
+ isStatic: false,
14166
14988
  parameters: decorator === "Getter" ? [] : [value],
14167
14989
  returnAnnotation: decorator === "Getter" ? field2.annotation : voidAnnotation,
14168
14990
  body,
@@ -14171,7 +14993,7 @@ function accessorMethod(field2, decorator, name) {
14171
14993
  };
14172
14994
  }
14173
14995
  function generatedMethod(field2, name, parameters, returnAnnotation, kind, fields) {
14174
- return { kind: "class-method", name, isConstructor: false, isSynthetic: true, parameters, returnAnnotation, body: [], decorators: [], generated: { kind, fields }, position: field2.position };
14996
+ return { kind: "class-method", name, isConstructor: false, isSynthetic: true, isStatic: false, parameters, returnAnnotation, body: [], decorators: [], generated: { kind, fields }, position: field2.position };
14175
14997
  }
14176
14998
  function typeName(name, node) {
14177
14999
  return { kind: "type-name", name, typeArguments: [], position: node.position };
@@ -14201,6 +15023,46 @@ function validateDecorators(context, decorators, target) {
14201
15023
  function decoratorFor(name, classDecorators, fieldDecorators) {
14202
15024
  return fieldDecorators.find((decorator) => decorator.name === name) ?? classDecorators.find((decorator) => decorator.name === name) ?? null;
14203
15025
  }
15026
+ function staticMethod(statement, name, returnAnnotation, kind, descriptor) {
15027
+ const value = { name: "value", annotation: typeName("table", statement), isVararg: false, position: statement.position };
15028
+ return {
15029
+ kind: "class-method",
15030
+ name,
15031
+ isConstructor: false,
15032
+ isSynthetic: true,
15033
+ isStatic: true,
15034
+ parameters: [value],
15035
+ returnAnnotation,
15036
+ body: [],
15037
+ decorators: [],
15038
+ generated: { kind, fields: [], descriptor },
15039
+ position: statement.position
15040
+ };
15041
+ }
15042
+ function expandValidated(context, statement, classDecorators, fieldTypes, occupied, generated) {
15043
+ const found = classDecorators.find((decorator) => decorator.name === "Validated");
15044
+ if (found === void 0) {
15045
+ return;
15046
+ }
15047
+ const fields = [...fieldTypes].map(([field2, type]) => ({ name: field2.name, type, position: field2.position }));
15048
+ const described = classDescriptor(context, statement.name, fields);
15049
+ if (described.lua === null) {
15050
+ const message = `Decorator "@Validated" cannot check type "${described.unreifiable}", which has no runtime shape. Remove the field, or narrow it to a checkable type.`;
15051
+ context.report("check-unreifiable-type", message, found.position);
15052
+ return;
15053
+ }
15054
+ for (const [name, kind, annotation] of [
15055
+ ["validate", "validate", typeName(statement.name, statement)],
15056
+ ["matches", "matches", typeName("boolean", statement)]
15057
+ ]) {
15058
+ if (occupied.has(name)) {
15059
+ context.report("check-decorator-conflict", `Decorator "@Validated" would generate "${name}", which is already declared by "${occupied.get(name)}".`, found.position);
15060
+ continue;
15061
+ }
15062
+ occupied.set(name, statement.name);
15063
+ generated.push(staticMethod(statement, name, annotation, kind, described.lua));
15064
+ }
15065
+ }
14204
15066
  function expandClassDecorators(context, statement, fieldTypes) {
14205
15067
  const generated = [];
14206
15068
  const occupied = new Map(statement.members.filter((member) => member.kind === "class-method").map((member) => [member.name, member.name]));
@@ -14262,6 +15124,7 @@ function expandClassDecorators(context, statement, fieldTypes) {
14262
15124
  Serializable: ["toTable", typeName("table", statement), "serializable"],
14263
15125
  Deserialize: ["fromTable", typeName("void", statement), "deserialize"]
14264
15126
  };
15127
+ expandValidated(context, statement, classDecorators, fieldTypes, occupied, generated);
14265
15128
  for (const [decorator, [name, returnAnnotation, kind]] of Object.entries(classMethods)) {
14266
15129
  const found = classDecorators.find((value) => value.name === decorator);
14267
15130
  if (found !== void 0 && !occupied.has(name)) {
@@ -17520,6 +18383,9 @@ function checkEventUsage(context, expression) {
17520
18383
  return;
17521
18384
  }
17522
18385
  const name = eventName(expression);
18386
+ if (name !== null) {
18387
+ context.noteEventReference(name);
18388
+ }
17523
18389
  const custom = name === null ? null : context.declarations.lookupEvent(name);
17524
18390
  const declared = name === null ? null : eventEnvironment(name);
17525
18391
  if (name === null || custom?.environment === "shared" || custom?.environment === context.environment || declared === null || declared === context.environment) {
@@ -17662,6 +18528,105 @@ function specializeEventCall(context, expression, signature) {
17662
18528
  return candidate === null ? null : specialize(signature, candidate);
17663
18529
  }
17664
18530
 
18531
+ // ../compiler/src/checker/method-receiver.ts
18532
+ var SELF_PARAMETER = "self";
18533
+ function resolveNonNominalMethod(receiver, method) {
18534
+ if (receiver.kind !== "record") {
18535
+ return null;
18536
+ }
18537
+ const member = receiver.members.get(method);
18538
+ return member !== void 0 && member.kind === "function" ? member : null;
18539
+ }
18540
+ function withoutSelfParameter(signature) {
18541
+ const names = signature.parameterNames;
18542
+ if (names === void 0 || names[0] !== SELF_PARAMETER) {
18543
+ return signature;
18544
+ }
18545
+ const minimum = Math.max(0, signature.minimumArguments - 1);
18546
+ return createFunction(signature.parameters.slice(1), signature.returnType, minimum, signature.isVariadic, names.slice(1), signature.variadicType);
18547
+ }
18548
+
18549
+ // ../compiler/src/checker/resource-exports.ts
18550
+ var RESOURCE_LOOKUP = "getResourceFromName";
18551
+ var EXPORTS_TABLE = "exports";
18552
+ var CALL_FUNCTION = "call";
18553
+ function literalString(expression) {
18554
+ return expression?.kind === "string-literal" ? expression.value : null;
18555
+ }
18556
+ function resourceOfLookup(expression) {
18557
+ if (expression?.kind !== "call-expression" || expression.callee.kind !== "identifier" || expression.callee.name !== RESOURCE_LOOKUP) {
18558
+ return null;
18559
+ }
18560
+ return literalString(expression.args[0]);
18561
+ }
18562
+ function resourceOfExports(expression) {
18563
+ if (expression.kind === "member-expression" && expression.object.kind === "identifier" && expression.object.name === EXPORTS_TABLE) {
18564
+ return expression.property;
18565
+ }
18566
+ if (expression.kind !== "index-expression" || expression.object.kind !== "identifier" || expression.object.name !== EXPORTS_TABLE) {
18567
+ return null;
18568
+ }
18569
+ return literalString(expression.index);
18570
+ }
18571
+ function abiType(context, text) {
18572
+ const scanned = scan(text);
18573
+ if (scanned.diagnostics.length > 0) {
18574
+ return ANY_TYPE;
18575
+ }
18576
+ try {
18577
+ return context.resolveAnnotation(parseTypeAnnotation(new TokenStream(scanned.tokens)));
18578
+ } catch {
18579
+ return ANY_TYPE;
18580
+ }
18581
+ }
18582
+ function signatureOf(context, entry) {
18583
+ const parameters = entry.parameters.map((parameter) => abiType(context, parameter.type));
18584
+ const names = entry.parameters.map((parameter) => parameter.name);
18585
+ const minimum = Math.min(Math.max(entry.minimumArguments, 0), parameters.length);
18586
+ return createFunction(parameters, abiType(context, entry.returns), minimum, entry.variadic, names);
18587
+ }
18588
+ function checkSide(context, entry, resource, expression) {
18589
+ if (canReference(context.environment, entry.side)) {
18590
+ return;
18591
+ }
18592
+ const message = `Export "${entry.name}" of resource "${resource}" is ${entry.side}-only and cannot be called from a "${context.environment}" file.`;
18593
+ context.report("check-resource-export-side", message, expression.position);
18594
+ }
18595
+ function checkKnownExport(context, resource, name, args, expression) {
18596
+ const entry = lookupAbiExport(context.contracts, resource, name);
18597
+ if (entry === null) {
18598
+ const known = context.contracts.find((contract) => contract.resource === resource)?.exports.map((declared) => `"${declared.name}"`).join(", ");
18599
+ const hint = known === void 0 || known.length === 0 ? " It exports nothing." : ` It exports ${known}.`;
18600
+ checkValueList(context, args);
18601
+ context.report("check-unknown-resource-export", `Resource "${resource}" has no export "${name}".${hint}`, expression.position);
18602
+ return ANY_TYPE;
18603
+ }
18604
+ checkSide(context, entry, resource, expression);
18605
+ return checkSignature(context, args, signatureOf(context, entry), expression.position);
18606
+ }
18607
+ function checkResourceCall(context, expression) {
18608
+ if (context.contracts.length === 0) {
18609
+ return null;
18610
+ }
18611
+ if (expression.method !== null) {
18612
+ const resource2 = resourceOfExports(expression.callee);
18613
+ if (resource2 === null || !knowsResource(context.contracts, resource2)) {
18614
+ return null;
18615
+ }
18616
+ return checkKnownExport(context, resource2, expression.method, expression.args, expression);
18617
+ }
18618
+ if (expression.callee.kind !== "identifier" || expression.callee.name !== CALL_FUNCTION) {
18619
+ return null;
18620
+ }
18621
+ const resource = resourceOfLookup(expression.args[0]);
18622
+ const name = literalString(expression.args[1]);
18623
+ if (resource === null || name === null || !knowsResource(context.contracts, resource)) {
18624
+ return null;
18625
+ }
18626
+ checkValueList(context, expression.args.slice(0, 2));
18627
+ return checkKnownExport(context, resource, name, expression.args.slice(2), expression);
18628
+ }
18629
+
17665
18630
  // ../mta-types/src/library-members.ts
17666
18631
  var MATH_MEMBERS = {
17667
18632
  abs: fn([NUMBER], NUMBER, 1),
@@ -17731,11 +18696,11 @@ var ELEMENT_TYPES = [
17731
18696
  { name: "Camera", parent: "Element" },
17732
18697
  { name: "ColShape", parent: "Element" },
17733
18698
  { name: "Connection", parent: null },
17734
- { name: "DxFont", parent: null },
17735
- { name: "DxRenderTarget", parent: null },
17736
- { name: "DxScreenSource", parent: null },
17737
- { name: "DxShader", parent: null },
17738
- { name: "DxTexture", parent: null },
18699
+ { name: "DxFont", parent: "Element" },
18700
+ { name: "DxRenderTarget", parent: "Element" },
18701
+ { name: "DxScreenSource", parent: "Element" },
18702
+ { name: "DxShader", parent: "Element" },
18703
+ { name: "DxTexture", parent: "Element" },
17739
18704
  { name: "Effect", parent: "Element" },
17740
18705
  { name: "Element", parent: null },
17741
18706
  { name: "Engine", parent: null },
@@ -17744,21 +18709,21 @@ var ELEMENT_TYPES = [
17744
18709
  { name: "EngineTXD", parent: null },
17745
18710
  { name: "File", parent: null },
17746
18711
  { name: "GuiBrowser", parent: "GuiElement" },
17747
- { name: "GuiButton", parent: null },
18712
+ { name: "GuiButton", parent: "GuiElement" },
17748
18713
  { name: "GuiCheckbox", parent: "GuiElement" },
17749
18714
  { name: "GuiCombobox", parent: "GuiElement" },
17750
18715
  { name: "GuiEdit", parent: "GuiElement" },
17751
- { name: "GuiElement", parent: null },
17752
- { name: "GuiFont", parent: null },
18716
+ { name: "GuiElement", parent: "Element" },
18717
+ { name: "GuiFont", parent: "Element" },
17753
18718
  { name: "GuiGridList", parent: "GuiElement" },
17754
18719
  { name: "GuiLabel", parent: "GuiElement" },
17755
18720
  { name: "GuiMemo", parent: "GuiElement" },
17756
- { name: "GuiRadioButton", parent: null },
18721
+ { name: "GuiRadioButton", parent: "GuiElement" },
17757
18722
  { name: "GuiStaticImage", parent: "GuiElement" },
17758
18723
  { name: "GuiTab", parent: "GuiElement" },
17759
18724
  { name: "GuiTabPanel", parent: "GuiElement" },
17760
18725
  { name: "GuiWindow", parent: "GuiElement" },
17761
- { name: "Light", parent: null },
18726
+ { name: "Light", parent: "Element" },
17762
18727
  { name: "Marker", parent: "Element" },
17763
18728
  { name: "Material", parent: "Element" },
17764
18729
  { name: "Object", parent: "Element" },
@@ -17914,25 +18879,54 @@ function checkInterfaceMember(context, name, members, expression) {
17914
18879
  context.report("check-unknown-member", `Interface "${name}" has no member "${expression.property}". Declared members: ${keys}.`, expression.position);
17915
18880
  return ANY_TYPE;
17916
18881
  }
17917
- function resolveNamedMember(context, name, expression) {
17918
- const enumeration = context.declarations.lookupEnum(name);
17919
- if (enumeration !== null) {
17920
- return checkEnumMember(context, name, enumeration.members, expression);
17921
- }
17922
- const member = context.declarations.lookupMember(name, expression.property);
18882
+ function isUserClassReference(context, name) {
18883
+ const symbol = context.binder.lookup(name);
18884
+ return symbol !== null && !symbol.isLocal && context.declarations.lookupClass(name) !== null;
18885
+ }
18886
+ function resolveStaticMember(context, className, expression) {
18887
+ const member = context.declarations.lookupStaticMember(className, expression.property);
17923
18888
  if (member !== null) {
17924
18889
  if (member.deprecated === true) {
17925
18890
  context.warn("check-deprecated-use", `Member "${expression.property}" is deprecated.`, expression.position);
17926
18891
  }
18892
+ context.staticAccess.add(expression);
17927
18893
  return member.type;
17928
18894
  }
17929
- if (isMtaElement(context, name)) {
17930
- return resolveMtaMember(context, name, expression.property, expression.position)?.type ?? ANY_TYPE;
17931
- }
17932
- if (context.awaitsDeclaration(name)) {
18895
+ if (context.awaitsDeclaration(className)) {
18896
+ context.staticAccess.add(expression);
17933
18897
  return ANY_TYPE;
17934
18898
  }
17935
- const contract = context.declarations.lookupClass(name) === null ? context.declarations.lookupInterface(name) : null;
18899
+ const instance = context.declarations.lookupMember(className, expression.property);
18900
+ const hint = instance === null ? "" : ` It is an instance member, so read it from a value of "${className}".`;
18901
+ context.report("check-unknown-member", `Class "${className}" has no static member "${expression.property}".${hint}`, expression.position);
18902
+ return ANY_TYPE;
18903
+ }
18904
+ function resolveNamedMember(context, receiver, expression) {
18905
+ const name = receiver.name;
18906
+ const enumeration = context.declarations.lookupEnum(name);
18907
+ if (enumeration !== null) {
18908
+ return checkEnumMember(context, name, enumeration.members, expression);
18909
+ }
18910
+ const member = memberOf(context, receiver, expression.property);
18911
+ if (member !== null) {
18912
+ if (member.deprecated === true) {
18913
+ context.warn("check-deprecated-use", `Member "${expression.property}" is deprecated.`, expression.position);
18914
+ }
18915
+ return member.type;
18916
+ }
18917
+ if (isMtaElement(context, name)) {
18918
+ return resolveMtaMember(context, name, expression.property, expression.position)?.type ?? ANY_TYPE;
18919
+ }
18920
+ const staticMember = context.declarations.lookupStaticMember(name, expression.property);
18921
+ if (staticMember !== null) {
18922
+ const message = `"${expression.property}" is a static member of class "${name}". Read it as "${name}.${expression.property}".`;
18923
+ context.report("check-static-receiver", message, expression.position);
18924
+ return staticMember.type;
18925
+ }
18926
+ if (context.awaitsDeclaration(name)) {
18927
+ return ANY_TYPE;
18928
+ }
18929
+ const contract = context.declarations.lookupClass(name) === null ? context.declarations.lookupInterface(name) : null;
17936
18930
  if (contract === null) {
17937
18931
  return null;
17938
18932
  }
@@ -17948,6 +18942,29 @@ function nativeConstructor(context, name) {
17948
18942
  const constructor = symbol.type.members.get(NATIVE_CONSTRUCTOR);
17949
18943
  return constructor === void 0 || constructor.kind !== "function" ? null : constructor;
17950
18944
  }
18945
+ function constructorParameters(context, className) {
18946
+ const constructor = context.declarations.lookupMember(className, "constructor");
18947
+ return constructor?.type.kind === "function" ? constructor.type.parameters : [];
18948
+ }
18949
+ function resolveConstructorArguments(context, expression, typeParameters) {
18950
+ const explicit = expression.typeArguments.map((argument) => context.resolveAnnotation(argument));
18951
+ if (typeParameters.length === 0) {
18952
+ if (explicit.length > 0) {
18953
+ context.report("check-generic-arity", `Class "${expression.className}" does not accept type arguments.`, expression.position);
18954
+ }
18955
+ return [];
18956
+ }
18957
+ if (explicit.length === typeParameters.length) {
18958
+ return explicit;
18959
+ }
18960
+ if (explicit.length > 0) {
18961
+ const expected = typeParameters.length === 1 ? "1 type argument" : `${typeParameters.length} type arguments`;
18962
+ context.report("check-generic-arity", `Class "${expression.className}" expects ${expected} but received ${explicit.length}.`, expression.position);
18963
+ return typeParameters.map((unused, index) => explicit[index] ?? ANY_TYPE);
18964
+ }
18965
+ const argumentTypes = expression.args.map((argument) => checkExpression(context, argument));
18966
+ return inferTypeArguments(typeParameters, constructorParameters(context, expression.className), argumentTypes);
18967
+ }
17951
18968
  function checkNewExpression(context, expression) {
17952
18969
  if (context.declarations.lookupClass(expression.className) === null) {
17953
18970
  const constructor2 = nativeConstructor(context, expression.className);
@@ -17961,17 +18978,25 @@ function checkNewExpression(context, expression) {
17961
18978
  context.report("check-unknown-class", `Class "${expression.className}" is not defined.`, expression.position);
17962
18979
  return ANY_TYPE;
17963
18980
  }
17964
- if (context.isPredeclaredClass(expression.className) && !context.insideFunction()) {
17965
- const message = `Class "${expression.className}" is declared further down this file, so it does not exist yet at this point.`;
17966
- context.report("check-class-before-declaration", message, expression.position);
18981
+ const pending = context.insideFunction() ? null : context.pendingDeclarationOf(expression.className);
18982
+ if (pending !== null) {
18983
+ const subject = pending === expression.className ? `Class "${pending}"` : `Class "${expression.className}" extends "${pending}", which`;
18984
+ context.report("check-class-before-declaration", `${subject} is declared further down this file, so it does not exist yet at this point.`, expression.position);
17967
18985
  }
18986
+ const info = context.declarations.lookupClass(expression.className);
18987
+ const typeArguments = resolveConstructorArguments(context, expression, info?.typeParameters ?? []);
18988
+ checkTypeConstraints(context, expression.className, typeArguments, expression.position);
17968
18989
  const constructor = context.declarations.lookupMember(expression.className, "constructor");
17969
18990
  if (constructor !== null && constructor.type.kind === "function") {
17970
- checkSignature(context, expression.args, constructor.type, expression.position);
18991
+ const substitutions = info === null ? /* @__PURE__ */ new Map() : classSubstitutions(info, typeArguments);
18992
+ const signature = substituteType(constructor.type, substitutions);
18993
+ if (signature.kind === "function") {
18994
+ checkSignature(context, expression.args, signature, expression.position);
18995
+ }
17971
18996
  } else {
17972
18997
  expression.args.forEach((argument) => checkExpression(context, argument));
17973
18998
  }
17974
- return createNamed(expression.className);
18999
+ return createNamed(expression.className, typeArguments);
17975
19000
  }
17976
19001
  function resolveSuperMethod(context, expression) {
17977
19002
  const frame = context.currentClassMethod();
@@ -18060,14 +19085,14 @@ function isShapeOption(context, option) {
18060
19085
  }
18061
19086
  return context.declarations.lookupInterface(option.name) !== null || context.declarations.lookupClass(option.name) !== null;
18062
19087
  }
18063
- function memberOf(context, option, property) {
19088
+ function memberOf2(context, option, property) {
18064
19089
  if (option.kind === "record") {
18065
19090
  return option.members.get(property) ?? null;
18066
19091
  }
18067
19092
  return option.kind === "named" ? context.declarations.lookupMember(option.name, property)?.type ?? null : null;
18068
19093
  }
18069
19094
  function optionMemberType(context, option, property) {
18070
- return isShapeOption(context, option) ? memberOf(context, option, property) : null;
19095
+ return isShapeOption(context, option) ? memberOf2(context, option, property) : null;
18071
19096
  }
18072
19097
  function resolveUnionMember(context, type, expression) {
18073
19098
  if (!type.options.every((option) => isShapeOption(context, option))) {
@@ -18076,7 +19101,7 @@ function resolveUnionMember(context, type, expression) {
18076
19101
  const resolved2 = [];
18077
19102
  const missing2 = [];
18078
19103
  for (const option of type.options) {
18079
- const member = memberOf(context, option, expression.property);
19104
+ const member = memberOf2(context, option, expression.property);
18080
19105
  if (member === null) {
18081
19106
  missing2.push(`"${typeToString(option)}"`);
18082
19107
  } else {
@@ -18243,26 +19268,6 @@ function conditionFacts(context, condition) {
18243
19268
  collectFacts(context, condition, facts);
18244
19269
  return facts;
18245
19270
  }
18246
- function alwaysExits(body) {
18247
- const last = body[body.length - 1];
18248
- if (last === void 0) {
18249
- return false;
18250
- }
18251
- if (last.kind === "return-statement" || last.kind === "break-statement") {
18252
- return true;
18253
- }
18254
- if (last.kind !== "if-statement" || last.alternate === null) {
18255
- return false;
18256
- }
18257
- return last.clauses.every((clause) => alwaysExits(clause.body)) && alwaysExits(last.alternate);
18258
- }
18259
- function guardFacts(context, statement) {
18260
- const [clause] = statement.kind === "if-statement" ? statement.clauses : [];
18261
- if (statement.kind !== "if-statement" || statement.alternate !== null || statement.clauses.length !== 1 || clause === void 0) {
18262
- return /* @__PURE__ */ new Map();
18263
- }
18264
- return alwaysExits(clause.body) ? negatedFacts(context, clause.condition) : /* @__PURE__ */ new Map();
18265
- }
18266
19271
  function negatedFacts(context, condition) {
18267
19272
  const facts = /* @__PURE__ */ new Map();
18268
19273
  if (condition.kind === "binary-expression" && condition.operator === "or") {
@@ -18293,23 +19298,58 @@ var ITERATOR_FUNCTIONS = /* @__PURE__ */ new Set(["pairs", "ipairs"]);
18293
19298
  function checkBlock(context, body) {
18294
19299
  context.binder.pushScope();
18295
19300
  checkStatements(context, body);
19301
+ const declared = context.binder.currentScopeNames();
18296
19302
  context.binder.popScope();
19303
+ for (const name of declared) {
19304
+ context.forgetNarrowing(name);
19305
+ }
18297
19306
  }
18298
- function checkNarrowedBlock(context, facts, body) {
18299
- context.pushNarrowing(facts);
19307
+ function branch(context, entry, facts, body) {
19308
+ const state = cloneFlow(entry);
19309
+ applyFacts(state, facts);
19310
+ context.setFlow(state);
18300
19311
  checkBlock(context, body);
18301
- context.popNarrowing();
19312
+ return context.flowState;
18302
19313
  }
18303
19314
  function checkIf(context, clauses, alternate) {
19315
+ const exits = [];
19316
+ let current = context.flowState;
18304
19317
  for (const clause of clauses) {
19318
+ context.setFlow(current);
18305
19319
  checkExpression(context, clause.condition);
18306
- checkNarrowedBlock(context, conditionFacts(context, clause.condition), clause.body);
19320
+ const taken = conditionFacts(context, clause.condition);
19321
+ const skipped = cloneFlow(current);
19322
+ applyFacts(skipped, negatedFacts(context, clause.condition));
19323
+ exits.push(branch(context, current, taken, clause.body));
19324
+ current = skipped;
18307
19325
  }
18308
19326
  if (alternate === null) {
18309
- return;
19327
+ exits.push(current);
19328
+ } else {
19329
+ context.setFlow(current);
19330
+ checkBlock(context, alternate);
19331
+ exits.push(context.flowState);
18310
19332
  }
18311
- const [only] = clauses;
18312
- checkNarrowedBlock(context, clauses.length === 1 && only !== void 0 ? negatedFacts(context, only.condition) : /* @__PURE__ */ new Map(), alternate);
19333
+ context.setFlow(joinFlows(exits));
19334
+ }
19335
+ function checkLoopBody(context, body, facts) {
19336
+ forgetAssignedPaths(context, body);
19337
+ const entry = context.flowState;
19338
+ const state = cloneFlow(entry);
19339
+ applyFacts(state, facts);
19340
+ context.setFlow(state);
19341
+ checkBlock(context, body);
19342
+ context.setFlow(entry);
19343
+ }
19344
+ function checkWhile(context, statement) {
19345
+ forgetAssignedPaths(context, statement.body);
19346
+ checkExpression(context, statement.condition);
19347
+ checkLoopBody(context, statement.body, conditionFacts(context, statement.condition));
19348
+ context.applyFlowFacts(negatedFacts(context, statement.condition));
19349
+ }
19350
+ function checkRepeat(context, statement) {
19351
+ checkLoopBody(context, statement.body, /* @__PURE__ */ new Map());
19352
+ checkExpression(context, statement.condition);
18313
19353
  }
18314
19354
  function checkNumericFor(context, statement) {
18315
19355
  const bounds = [statement.start, statement.limit, statement.step].filter((value) => value !== null);
@@ -18320,10 +19360,13 @@ function checkNumericFor(context, statement) {
18320
19360
  }
18321
19361
  }
18322
19362
  forgetAssignedPaths(context, statement.body);
19363
+ const entry = context.flowState;
19364
+ context.setFlow(cloneFlow(entry));
18323
19365
  context.binder.pushScope();
18324
19366
  context.binder.declare({ name: statement.variable.name, type: NUMBER_TYPE, isLocal: true, position: statement.variable.position, origin: "local" });
18325
19367
  checkStatements(context, statement.body);
18326
19368
  context.binder.popScope();
19369
+ context.setFlow(entry);
18327
19370
  }
18328
19371
  function iteratedTypes(context, iterators) {
18329
19372
  const [iterator] = iterators;
@@ -18344,6 +19387,8 @@ function checkGenericFor(context, statement) {
18344
19387
  statement.iterators.forEach((iterator) => checkExpression(context, iterator));
18345
19388
  const iterated = iteratedTypes(context, statement.iterators);
18346
19389
  forgetAssignedPaths(context, statement.body);
19390
+ const entry = context.flowState;
19391
+ context.setFlow(cloneFlow(entry));
18347
19392
  context.binder.pushScope();
18348
19393
  statement.variables.forEach((variable, index) => {
18349
19394
  const inferred = iterated[index] ?? ANY_TYPE;
@@ -18352,6 +19397,7 @@ function checkGenericFor(context, statement) {
18352
19397
  });
18353
19398
  checkStatements(context, statement.body);
18354
19399
  context.binder.popScope();
19400
+ context.setFlow(entry);
18355
19401
  }
18356
19402
 
18357
19403
  // ../compiler/src/checker/events.ts
@@ -18412,15 +19458,19 @@ function minimumArguments(context, parameters) {
18412
19458
  }
18413
19459
  function buildFunctionType(context, parameters, returnAnnotation, contextual = null) {
18414
19460
  const isVariadic = parameters.some((parameter) => parameter.isVararg);
18415
- const types = parameters.filter((parameter) => !parameter.isVararg).map((parameter, index) => parameter.annotation === null ? contextual?.parameters[index] ?? ANY_TYPE : context.resolveAnnotation(parameter.annotation));
18416
- return createFunction(types, context.resolveAnnotation(returnAnnotation), minimumArguments(context, parameters), isVariadic);
19461
+ const named2 = parameters.filter((parameter) => !parameter.isVararg);
19462
+ const types = named2.map((parameter, index) => parameter.annotation === null ? contextual?.parameters[index] ?? ANY_TYPE : context.resolveAnnotation(parameter.annotation));
19463
+ const names = named2.map((parameter) => parameter.name);
19464
+ return createFunction(types, context.resolveAnnotation(returnAnnotation), minimumArguments(context, parameters), isVariadic, names);
18417
19465
  }
18418
- function checkFunctionBody(context, parameters, returnAnnotation, body, signature, selfType) {
19466
+ function checkFunctionBody(context, parameters, returnAnnotation, body, signature, selfType2) {
18419
19467
  forgetAssignedPaths(context, body);
19468
+ const entry = context.flowState;
19469
+ context.setFlow(cloneFlow(entry));
18420
19470
  context.binder.pushScope();
18421
19471
  context.pushReturnType(returnAnnotation === null ? null : context.resolveAnnotation(returnAnnotation));
18422
- if (selfType !== null) {
18423
- context.binder.declare({ name: "self", type: selfType, isLocal: true, position: ORIGIN });
19472
+ if (selfType2 !== null) {
19473
+ context.binder.declare({ name: "self", type: selfType2, isLocal: true, position: ORIGIN });
18424
19474
  }
18425
19475
  let parameterIndex = 0;
18426
19476
  for (const parameter of parameters) {
@@ -18436,6 +19486,7 @@ function checkFunctionBody(context, parameters, returnAnnotation, body, signatur
18436
19486
  signature.returnType = inferred;
18437
19487
  }
18438
19488
  context.binder.popScope();
19489
+ context.setFlow(entry);
18439
19490
  }
18440
19491
  function valueAt(values, valueTypes, index) {
18441
19492
  if (values[index] !== void 0) {
@@ -18459,6 +19510,9 @@ function checkLocal(context, statement) {
18459
19510
  context.expectAssignable(valueType, declared, value.position, `Variable "${declaration.name}"`);
18460
19511
  }
18461
19512
  context.binder.declare({ name: declaration.name, type: declared, isLocal: true, position: declaration.position, origin: "local" });
19513
+ if (value !== void 0) {
19514
+ recordAssignedFact(context, declaration.name, valueType, declared);
19515
+ }
18462
19516
  });
18463
19517
  }
18464
19518
  function checkCompoundOperand(context, operator, type, position2) {
@@ -18472,6 +19526,15 @@ function checkCompoundOperand(context, operator, type, position2) {
18472
19526
  context.report("check-invalid-operand", `Operator "${operator}" cannot be applied to "${typeToString(type)}".`, position2);
18473
19527
  }
18474
19528
  }
19529
+ function recordAssignedFact(context, path, valueType, declared) {
19530
+ if (path === null || valueType.kind === "any") {
19531
+ return;
19532
+ }
19533
+ const fact = assignmentFact(widenInferred(valueType), declared);
19534
+ if (fact !== null) {
19535
+ context.applyFlowFacts(/* @__PURE__ */ new Map([[path, fact]]));
19536
+ }
19537
+ }
18475
19538
  function checkAssignment(context, statement) {
18476
19539
  const valueTypes = checkValueList(context, statement.values);
18477
19540
  statement.targets.forEach((target, index) => {
@@ -18507,6 +19570,7 @@ function checkAssignment(context, statement) {
18507
19570
  if (value !== void 0) {
18508
19571
  context.expectAssignable(valueType, declared, value.position, "Assignment");
18509
19572
  }
19573
+ recordAssignedFact(context, pathOf2(target), valueType, declared);
18510
19574
  });
18511
19575
  }
18512
19576
  function checkFunctionDeclaration(context, statement) {
@@ -18585,18 +19649,19 @@ function checkStatement(context, statement) {
18585
19649
  case "function-declaration":
18586
19650
  return checkFunctionDeclaration(context, statement);
18587
19651
  case "return-statement":
18588
- return checkReturn(context, statement);
19652
+ checkReturn(context, statement);
19653
+ context.markUnreachable();
19654
+ return;
19655
+ case "break-statement":
19656
+ case "continue-statement":
19657
+ context.markUnreachable();
19658
+ return;
18589
19659
  case "do-statement":
18590
19660
  return checkBlock(context, statement.body);
18591
19661
  case "while-statement":
18592
- checkExpression(context, statement.condition);
18593
- forgetAssignedPaths(context, statement.body);
18594
- return checkBlock(context, statement.body);
19662
+ return checkWhile(context, statement);
18595
19663
  case "repeat-statement":
18596
- forgetAssignedPaths(context, statement.body);
18597
- checkBlock(context, statement.body);
18598
- checkExpression(context, statement.condition);
18599
- return;
19664
+ return checkRepeat(context, statement);
18600
19665
  case "if-statement":
18601
19666
  return checkIf(context, statement.clauses, statement.alternate);
18602
19667
  case "numeric-for-statement":
@@ -18626,18 +19691,8 @@ function checkStatement(context, statement) {
18626
19691
  }
18627
19692
  }
18628
19693
  function checkStatements(context, statements) {
18629
- let guards = 0;
18630
19694
  for (const statement of statements) {
18631
19695
  checkStatement(context, statement);
18632
- const facts = guardFacts(context, statement);
18633
- if (facts.size > 0) {
18634
- context.pushNarrowing(facts);
18635
- guards += 1;
18636
- }
18637
- }
18638
- while (guards > 0) {
18639
- context.popNarrowing();
18640
- guards -= 1;
18641
19696
  }
18642
19697
  }
18643
19698
 
@@ -18681,6 +19736,10 @@ function checkMember(context, expression) {
18681
19736
  checkExpression(context, expression.object);
18682
19737
  return context.record(expression, narrowed);
18683
19738
  }
19739
+ if (expression.object.kind === "identifier" && isUserClassReference(context, expression.object.name)) {
19740
+ context.references.add(expression.object.name);
19741
+ return context.record(expression, resolveStaticMember(context, expression.object.name, expression));
19742
+ }
18684
19743
  if (expression.object.kind === "identifier" && isMtaClassReference(context, expression.object.name)) {
18685
19744
  context.references.add(expression.object.name);
18686
19745
  return context.record(expression, resolveMtaStaticMember(context, expression.object.name, expression.property, expression.position)?.type ?? ANY_TYPE);
@@ -18708,7 +19767,7 @@ function checkMember(context, expression) {
18708
19767
  if (objectType.kind === "map" && isAssignable(STRING_TYPE, objectType.key)) {
18709
19768
  return context.record(expression, objectType.value);
18710
19769
  }
18711
- const named2 = objectType.kind === "named" ? resolveNamedMember(context, objectType.name, expression) : null;
19770
+ const named2 = objectType.kind === "named" ? resolveNamedMember(context, objectType, expression) : null;
18712
19771
  if (named2 !== null) {
18713
19772
  return context.record(expression, named2);
18714
19773
  }
@@ -18765,10 +19824,14 @@ function isLegacySuperCall(expression) {
18765
19824
  }
18766
19825
  function checkMethodCall(context, expression, method, receiver) {
18767
19826
  if (receiver.kind !== "named") {
18768
- checkValueList(context, expression.args);
18769
- return ANY_TYPE;
19827
+ const signature = resolveNonNominalMethod(receiver, method);
19828
+ if (signature === null) {
19829
+ checkValueList(context, expression.args);
19830
+ return ANY_TYPE;
19831
+ }
19832
+ return checkSignature(context, expression.args, withoutSelfParameter(signature), expression.position);
18770
19833
  }
18771
- const declared = context.declarations.lookupMember(receiver.name, method);
19834
+ const declared = memberOf(context, receiver, method);
18772
19835
  if (declared?.type.kind === "function") {
18773
19836
  if (declared.deprecated === true) {
18774
19837
  context.warn("check-deprecated-use", `Member "${method}" is deprecated.`, expression.position);
@@ -18787,6 +19850,10 @@ function checkMethodCall(context, expression, method, receiver) {
18787
19850
  return checkSignature(context, expression.args, member.type, expression.position);
18788
19851
  }
18789
19852
  function checkCall(context, expression) {
19853
+ const contracted = checkResourceCall(context, expression);
19854
+ if (contracted !== null) {
19855
+ return context.record(expression, contracted);
19856
+ }
18790
19857
  if (isLegacySuperCall(expression)) {
18791
19858
  checkValueList(context, expression.args);
18792
19859
  context.report("check-invalid-super", 'Call "super(...)" directly instead of "self:super(...)".', expression.position);
@@ -18804,6 +19871,13 @@ function checkCall(context, expression) {
18804
19871
  }
18805
19872
  return context.record(expression, checkSignature(context, expression.args, constructor, expression.position));
18806
19873
  }
19874
+ if (expression.method !== null && expression.callee.kind === "identifier" && isUserClassReference(context, expression.callee.name)) {
19875
+ const name = expression.callee.name;
19876
+ const message = `Call the static member "${expression.method}" as "${name}.${expression.method}(...)". A class value has no "self" to pass.`;
19877
+ context.report("check-static-receiver", message, expression.position);
19878
+ checkValueList(context, expression.args);
19879
+ return context.record(expression, ANY_TYPE);
19880
+ }
18807
19881
  if (expression.method === null) {
18808
19882
  context.calledMembers.add(expression.callee);
18809
19883
  }
@@ -18880,6 +19954,9 @@ function checkMultiValueExpression(context, expression, expected = null) {
18880
19954
  if (symbol === null) {
18881
19955
  checkGlobalReference(context, expression.name, expression.position);
18882
19956
  }
19957
+ if (symbol === null || symbol.isLocal !== true) {
19958
+ context.noteGlobalReference(expression.name);
19959
+ }
18883
19960
  return context.record(expression, context.narrowedType(expression.name) ?? symbol?.type ?? ANY_TYPE);
18884
19961
  }
18885
19962
  case "member-expression":
@@ -18960,6 +20037,26 @@ function syntheticMethodType(context, member, fieldTypes) {
18960
20037
  }
18961
20038
  return buildFunctionType(context, member.parameters, member.returnAnnotation);
18962
20039
  }
20040
+ function checkMemberSpaces(context, info, statement) {
20041
+ for (const member of statement.members) {
20042
+ if (member.isStatic && info.members.has(member.name)) {
20043
+ context.report("check-duplicate-class-member", `Class "${info.name}" declares "${member.name}" as both a static and an instance member.`, member.position);
20044
+ }
20045
+ if (!member.isStatic || info.superClass === null) {
20046
+ continue;
20047
+ }
20048
+ const inherited = context.declarations.lookupStaticMember(info.superClass, member.name);
20049
+ const declared = info.statics.get(member.name);
20050
+ if (inherited === void 0 || inherited === null || declared === void 0) {
20051
+ continue;
20052
+ }
20053
+ const options = { allowNil: context.allowNil };
20054
+ if (!isAssignable(declared.type, inherited.type, options) || !isAssignable(inherited.type, declared.type, options)) {
20055
+ const message = `Static member "${member.name}" must match "${typeToString(inherited.type)}" declared by class "${info.superClass}".`;
20056
+ context.report("check-invalid-override", message, member.position);
20057
+ }
20058
+ }
20059
+ }
18963
20060
  function registerMembers(context, info, statement) {
18964
20061
  const fieldTypes = /* @__PURE__ */ new Map();
18965
20062
  for (const member of statement.members) {
@@ -18972,7 +20069,13 @@ function registerMembers(context, info, statement) {
18972
20069
  for (const member of [...statement.members, ...generated]) {
18973
20070
  const type = member.kind === "class-field" ? fieldTypes.get(member) ?? ANY_TYPE : member.isSynthetic ? syntheticMethodType(context, member, fieldTypes) : buildFunctionType(context, member.parameters, member.returnAnnotation);
18974
20071
  const decorators = member.decorators.map((decorator) => decorator.name);
18975
- info.members.set(member.name, {
20072
+ if (member.kind === "class-method" && isMetamethodMember(member)) {
20073
+ checkMetamethod(context, info.name, member, type);
20074
+ continue;
20075
+ }
20076
+ reportRejectedMetamethod(context, info.name, member);
20077
+ const space = member.isStatic ? info.statics : info.members;
20078
+ space.set(member.name, {
18976
20079
  name: member.name,
18977
20080
  type,
18978
20081
  isMethod: member.kind === "class-method",
@@ -18981,6 +20084,7 @@ function registerMembers(context, info, statement) {
18981
20084
  deprecated: decorators.includes("Deprecated")
18982
20085
  });
18983
20086
  }
20087
+ checkMemberSpaces(context, info, statement);
18984
20088
  return generated;
18985
20089
  }
18986
20090
  function declareBuilder(context, info, statement) {
@@ -18997,20 +20101,37 @@ function declareBuilder(context, info, statement) {
18997
20101
  }
18998
20102
  }
18999
20103
  members.set("build", { name: "build", type: { kind: "function", parameters: [], minimumArguments: 0, isVariadic: false, returnType: createNamed(statement.name) }, isMethod: true, position: statement.position });
19000
- context.declarations.declareClass({ name, superClass: null, interfaces: [], members, position: statement.position });
20104
+ context.declarations.declareClass({
20105
+ name,
20106
+ typeParameters: [],
20107
+ typeConstraints: [],
20108
+ superClass: null,
20109
+ superArguments: [],
20110
+ interfaces: [],
20111
+ members,
20112
+ statics: /* @__PURE__ */ new Map(),
20113
+ position: statement.position
20114
+ });
19001
20115
  context.declareModuleGlobal({ name, type: createNamed(name), isLocal: false, position: statement.position });
19002
20116
  }
20117
+ function selfType(info) {
20118
+ return createNamed(info.name, info.typeParameters.map((parameter) => createNamed(parameter)));
20119
+ }
19003
20120
  function checkMethodBody(context, info, member) {
19004
20121
  const explicitSelf = member.parameters.find((parameter) => parameter.name === "self");
19005
20122
  if (explicitSelf !== void 0) {
19006
20123
  context.report("check-explicit-self-parameter", 'Class methods receive "self" automatically; remove it from the parameter list.', explicitSelf.position);
19007
20124
  }
19008
- const signature = info.members.get(member.name)?.type;
20125
+ const signature = (member.isStatic ? info.statics : info.members).get(member.name)?.type;
19009
20126
  if (signature?.kind !== "function") {
19010
20127
  return;
19011
20128
  }
20129
+ if (member.isStatic) {
20130
+ checkFunctionBody(context, member.parameters, member.returnAnnotation, member.body, signature, null);
20131
+ return;
20132
+ }
19012
20133
  context.pushClassMethod({ className: info.name, methodName: member.name });
19013
- checkFunctionBody(context, member.parameters, member.returnAnnotation, member.body, signature, createNamed(info.name));
20134
+ checkFunctionBody(context, member.parameters, member.returnAnnotation, member.body, signature, selfType(info));
19014
20135
  context.popClassMethod();
19015
20136
  }
19016
20137
  function resolveSuperClass(context, statement) {
@@ -19033,6 +20154,10 @@ function resolveSuperClass(context, statement) {
19033
20154
  context.report("check-unknown-class", message, statement.position);
19034
20155
  return null;
19035
20156
  }
20157
+ function resolveClassHeader(context, info, statement) {
20158
+ info.superArguments = statement.superClassArguments.map((argument) => context.resolveAnnotation(argument));
20159
+ info.typeConstraints = statement.typeConstraints.map((constraint) => constraint === null ? null : context.resolveAnnotation(constraint));
20160
+ }
19036
20161
  function declareClassInfo(context, statement) {
19037
20162
  if (context.declarations.lookupClass(statement.name) !== null) {
19038
20163
  context.report("check-duplicate-class", `Class "${statement.name}" is already defined.`, statement.position);
@@ -19044,11 +20169,16 @@ function declareClassInfo(context, statement) {
19044
20169
  }
19045
20170
  const info = {
19046
20171
  name: statement.name,
20172
+ typeParameters: statement.typeParameters,
20173
+ typeConstraints: [],
19047
20174
  superClass: null,
20175
+ superArguments: [],
19048
20176
  interfaces: statement.interfaces,
19049
20177
  members: /* @__PURE__ */ new Map(),
20178
+ statics: /* @__PURE__ */ new Map(),
19050
20179
  position: statement.position
19051
20180
  };
20181
+ context.noteTypeParameters(statement.typeParameters);
19052
20182
  context.declarations.declareClass(info);
19053
20183
  context.declareModuleGlobal({ name: info.name, type: createNamed(info.name), isLocal: false, position: statement.position });
19054
20184
  return info;
@@ -19059,6 +20189,7 @@ function declareLocalClass(context, statement) {
19059
20189
  return null;
19060
20190
  }
19061
20191
  info.superClass = resolveSuperClass(context, statement);
20192
+ resolveClassHeader(context, info, statement);
19062
20193
  return info;
19063
20194
  }
19064
20195
  function checkClassDeclaration(context, statement) {
@@ -19172,6 +20303,7 @@ function predeclareModule(context, body) {
19172
20303
  const declared = declareHeaders(context, body);
19173
20304
  for (const entry of declared) {
19174
20305
  entry.info.superClass = resolveSuperClass(context, entry.statement);
20306
+ resolveClassHeader(context, entry.info, entry.statement);
19175
20307
  }
19176
20308
  for (const entry of declared) {
19177
20309
  breakCycle(context, entry.info);
@@ -19231,25 +20363,34 @@ function externalReferences(context) {
19231
20363
  }
19232
20364
  return external;
19233
20365
  }
20366
+ function referencedNames(context, external) {
20367
+ const events = [...context.eventReferences].map((name) => `${EVENT_NAME_PREFIX}${name}`);
20368
+ return /* @__PURE__ */ new Set([...external.keys(), ...context.globalReferences, ...context.unknownTypes.keys(), ...events]);
20369
+ }
19234
20370
  function check(program2, mode, environment = DEFAULT_ENVIRONMENT, options = {}) {
19235
20371
  const ambient = options.ambient ?? EMPTY_AMBIENT;
19236
20372
  const project = options.project ?? EMPTY_PROJECT_DECLARATIONS;
19237
20373
  const context = new CheckContext(mode, environment, ambient, project, options.oop === true);
19238
20374
  context.isDeclarationFile = options.isDeclarationFile === true;
20375
+ context.contracts = options.contracts ?? [];
19239
20376
  const structure = context.isDeclarationFile ? checkDeclarationFile(program2.body) : checkJumps(program2.body);
19240
20377
  const directives = collectDirectives(program2.body, { isDeclarationFile: context.isDeclarationFile });
19241
20378
  predeclareModule(context, program2.body);
19242
20379
  checkStatements(context, program2.body);
19243
20380
  reportUnknownTypes(context);
19244
20381
  reportUnusedSymbols(context, options.noUnusedLocals === true, options.noUnusedParameters === true);
20382
+ const external = externalReferences(context);
19245
20383
  return {
19246
20384
  diagnostics: sortDiagnostics([...structure, ...directives.diagnostics, ...context.diagnostics]),
19247
20385
  types: context.types,
19248
20386
  references: context.references,
20387
+ staticAccess: context.staticAccess,
19249
20388
  declarations: context.declarations,
19250
20389
  aliases: context.binder.resolvedAliases(),
19251
20390
  declaredGlobals: context.declaredGlobals,
19252
- externalReferences: externalReferences(context),
20391
+ moduleGlobals: context.moduleGlobals,
20392
+ externalReferences: external,
20393
+ referencedNames: referencedNames(context, external),
19253
20394
  directives: directives.directives,
19254
20395
  mode,
19255
20396
  environment,
@@ -19362,8 +20503,8 @@ function finalizeEmission(code, markers, sourceLineOffset = 0) {
19362
20503
 
19363
20504
  // ../compiler/src/emitter/state.ts
19364
20505
  var INDENT2 = " ";
19365
- function createEmitState(types, references, generatedMembers) {
19366
- return { types, references, generatedMembers, helpers: /* @__PURE__ */ new Set(), indent: 0, markers: [], symbol: void 0, loopWrap: false };
20506
+ function createEmitState(types, references, generatedMembers, staticAccess = /* @__PURE__ */ new Set()) {
20507
+ return { types, references, staticAccess, generatedMembers, helpers: /* @__PURE__ */ new Set(), indent: 0, markers: [], symbol: void 0, loopWrap: false };
19367
20508
  }
19368
20509
  function requireHelper(state, helper) {
19369
20510
  if (helper !== null) {
@@ -19515,6 +20656,10 @@ function emitTable(state, expression) {
19515
20656
  return `{ ${fields.join(", ")} }`;
19516
20657
  }
19517
20658
  function emitMember(state, expression) {
20659
+ if (state.staticAccess.has(expression) && expression.object.kind === "identifier") {
20660
+ requireHelper(state, "class");
20661
+ return `getClass(${emitString(expression.object.name)}).${expression.property}`;
20662
+ }
19518
20663
  const extension = resolvePropertyExtension(typeOf(state, expression.object), expression.property);
19519
20664
  const object = emitExpression(state, expression.object, UNARY_PRECEDENCE);
19520
20665
  if (extension === null) {
@@ -19609,7 +20754,8 @@ function emitMethod(state, className, member) {
19609
20754
  if (member.generated !== void 0) {
19610
20755
  return emitGeneratedMethod(state, className, member);
19611
20756
  }
19612
- return withSymbol(state, `${className}:${member.name}`, () => emitFunctionBody(state, [self, ...member.parameters], member.body, `${member.name} = function`));
20757
+ const parameters = member.isStatic ? member.parameters : [self, ...member.parameters];
20758
+ return withSymbol(state, `${className}${member.isStatic ? "." : ":"}${member.name}`, () => emitFunctionBody(state, parameters, member.body, `${member.name} = function`));
19613
20759
  }
19614
20760
  function emitGeneratedMethod(state, className, member) {
19615
20761
  const fields = member.generated?.fields ?? [];
@@ -19642,6 +20788,13 @@ function emitGeneratedMethod(state, className, member) {
19642
20788
  end
19643
20789
  end`;
19644
20790
  }
20791
+ if (member.generated?.kind === "validate" || member.generated?.kind === "matches") {
20792
+ const helper = member.generated.kind === "validate" ? "__luam_validate" : "__luam_matches";
20793
+ requireHelper(state, "validate");
20794
+ return `${member.name} = function(value)
20795
+ return ${helper}(value, ${member.generated.descriptor ?? "{ kind = 'table' }"})
20796
+ end`;
20797
+ }
19645
20798
  if (member.generated?.kind === "to-string") {
19646
20799
  const values = names.map((name) => `'${name}=' .. tostring(self.${name})`).join(" .. ', ' .. ");
19647
20800
  const body = values.length === 0 ? "''" : values;
@@ -19677,24 +20830,24 @@ ${names.map((name) => ` self.${name} = values.${name}`).join("\n")}
19677
20830
  end`;
19678
20831
  }
19679
20832
  function emitMembers(state, statement) {
19680
- const entries2 = [];
20833
+ const entries3 = [];
19681
20834
  state.indent += 1;
19682
20835
  for (const member of statement.members) {
19683
20836
  if (member.kind === "class-method") {
19684
- entries2.push(indentLine(state, `${markSource(state, member.position.line, `${statement.name}:${member.name}`)}${emitMethod(state, statement.name, member)}`));
20837
+ entries3.push(indentLine(state, `${markSource(state, member.position.line, `${statement.name}${member.isStatic ? "." : ":"}${member.name}`)}${emitMethod(state, statement.name, member)}`));
19685
20838
  continue;
19686
20839
  }
19687
20840
  if (member.decorators.some((decorator) => decorator.name === "Lazy")) {
19688
20841
  continue;
19689
20842
  }
19690
20843
  const value = member.value === null ? "nil" : emitExpression(state, member.value);
19691
- entries2.push(indentLine(state, `${markSource(state, member.position.line, statement.name)}${member.name} = ${value}`));
20844
+ entries3.push(indentLine(state, `${markSource(state, member.position.line, statement.name)}${member.name} = ${value}`));
19692
20845
  }
19693
20846
  for (const generated of state.generatedMembers.get(statement) ?? []) {
19694
- entries2.push(indentLine(state, `${markSource(state, generated.position.line, `${statement.name}:${generated.name}`)}${emitMethod(state, statement.name, generated)}`));
20847
+ entries3.push(indentLine(state, `${markSource(state, generated.position.line, `${statement.name}:${generated.name}`)}${emitMethod(state, statement.name, generated)}`));
19695
20848
  }
19696
20849
  state.indent -= 1;
19697
- return entries2;
20850
+ return entries3;
19698
20851
  }
19699
20852
  function emitClassHeader(statement) {
19700
20853
  const name = emitString(statement.name);
@@ -19720,15 +20873,15 @@ ${assignments}
19720
20873
  return value
19721
20874
  end`);
19722
20875
  state.indent += 1;
19723
- const entries2 = methods.map((method) => indentLine(state, method));
20876
+ const entries3 = methods.map((method) => indentLine(state, method));
19724
20877
  state.indent -= 1;
19725
- return [`class '${name}' {`, entries2.join(",\n"), "}"].join("\n");
20878
+ return [`class '${name}' {`, entries3.join(",\n"), "}"].join("\n");
19726
20879
  }
19727
20880
  function emitClassDeclaration(state, statement) {
19728
20881
  requireHelper(state, "class");
19729
20882
  const header = emitClassHeader(statement);
19730
- const entries2 = emitMembers(state, statement);
19731
- const declaration = entries2.length === 0 ? `${header} {}` : [`${header} {`, entries2.join(",\n"), indentLine(state, "}")].join("\n");
20883
+ const entries3 = emitMembers(state, statement);
20884
+ const declaration = entries3.length === 0 ? `${header} {}` : [`${header} {`, entries3.join(",\n"), indentLine(state, "}")].join("\n");
19732
20885
  const builder = emitBuilder(state, statement);
19733
20886
  return builder === null ? declaration : `${declaration}
19734
20887
  ${builder}`;
@@ -19923,8 +21076,8 @@ function emitBlock(state, statements) {
19923
21076
  }
19924
21077
  return lines;
19925
21078
  }
19926
- function emit2(program2, types, references, generatedMembers = /* @__PURE__ */ new Map(), sourceLineOffset = 0) {
19927
- const state = createEmitState(types, references, generatedMembers);
21079
+ function emit2(program2, types, references, generatedMembers = /* @__PURE__ */ new Map(), sourceLineOffset = 0, staticAccess = /* @__PURE__ */ new Set()) {
21080
+ const state = createEmitState(types, references, generatedMembers, staticAccess);
19928
21081
  const lines = emitBlock(state, program2.body);
19929
21082
  const markedCode = lines.length === 0 ? "" : `${lines.join("\n")}
19930
21083
  `;
@@ -20176,7 +21329,7 @@ function erasedEdit(source, span) {
20176
21329
  }
20177
21330
  function canonicalEdit(input, statement, span) {
20178
21331
  const { source } = input;
20179
- const emitted = emit2({ ...input.program, body: [statement] }, input.types, input.references, input.generatedMembers, statement.position.line - 1);
21332
+ const emitted = emit2({ ...input.program, body: [statement] }, input.types, input.references, input.generatedMembers, statement.position.line - 1, input.staticAccess);
20180
21333
  const trimmed = span.end < source.length && emitted.code.endsWith("\n") ? emitted.code.slice(0, -1) : emitted.code;
20181
21334
  if (trimmed.length === 0) {
20182
21335
  return erasedEdit(source, span);
@@ -20211,15 +21364,16 @@ function builderClassText(input, statement) {
20211
21364
  }
20212
21365
 
20213
21366
  // ../compiler/src/emitter/preserve-guards.ts
20214
- function isPreservableExpression(expression, types) {
21367
+ var NO_STATIC_ACCESS = /* @__PURE__ */ new Set();
21368
+ function isPreservableExpression(expression, types, statics = NO_STATIC_ACCESS) {
20215
21369
  switch (expression.kind) {
20216
21370
  case "template-literal":
20217
21371
  case "new-expression":
20218
21372
  return false;
20219
21373
  case "member-expression":
20220
- return resolvePropertyExtension(types.get(expression.object) ?? null, expression.property) === null && isPreservableExpression(expression.object, types);
21374
+ return !statics.has(expression) && resolvePropertyExtension(types.get(expression.object) ?? null, expression.property) === null && isPreservableExpression(expression.object, types, statics);
20221
21375
  case "index-expression":
20222
- return isPreservableExpression(expression.object, types) && isPreservableExpression(expression.index, types);
21376
+ return isPreservableExpression(expression.object, types, statics) && isPreservableExpression(expression.index, types, statics);
20223
21377
  case "call-expression":
20224
21378
  if (expression.method === null && expression.callee.kind === "identifier" && expression.callee.name === "super") {
20225
21379
  return false;
@@ -20227,43 +21381,43 @@ function isPreservableExpression(expression, types) {
20227
21381
  if (expression.callee.kind === "member-expression" && resolveCallExtension(types.get(expression.callee.object) ?? null, expression.callee.property) !== null) {
20228
21382
  return false;
20229
21383
  }
20230
- return isPreservableExpression(expression.callee, types) && expression.args.every((argument) => isPreservableExpression(argument, types));
21384
+ return isPreservableExpression(expression.callee, types, statics) && expression.args.every((argument) => isPreservableExpression(argument, types, statics));
20231
21385
  case "table-expression":
20232
21386
  return expression.fields.every(
20233
- (field2) => (field2.key === null || isPreservableExpression(field2.key, types)) && isPreservableExpression(field2.value, types)
21387
+ (field2) => (field2.key === null || isPreservableExpression(field2.key, types, statics)) && isPreservableExpression(field2.value, types, statics)
20234
21388
  );
20235
21389
  case "binary-expression":
20236
- return isPreservableExpression(expression.left, types) && isPreservableExpression(expression.right, types);
21390
+ return isPreservableExpression(expression.left, types, statics) && isPreservableExpression(expression.right, types, statics);
20237
21391
  case "unary-expression":
20238
- return isPreservableExpression(expression.operand, types);
21392
+ return isPreservableExpression(expression.operand, types, statics);
20239
21393
  case "group-expression":
20240
- return isPreservableExpression(expression.expression, types);
21394
+ return isPreservableExpression(expression.expression, types, statics);
20241
21395
  default:
20242
21396
  return true;
20243
21397
  }
20244
21398
  }
20245
- function every(expressions, types) {
20246
- return expressions.every((expression) => isPreservableExpression(expression, types));
21399
+ function every(expressions, types, statics) {
21400
+ return expressions.every((expression) => isPreservableExpression(expression, types, statics));
20247
21401
  }
20248
- function isPreservableStatement(statement, types) {
21402
+ function isPreservableStatement(statement, types, statics = NO_STATIC_ACCESS) {
20249
21403
  switch (statement.kind) {
20250
21404
  case "local-statement":
20251
- return every(statement.values, types);
21405
+ return every(statement.values, types, statics);
20252
21406
  case "assignment-statement":
20253
- return statement.operator === "=" && every(statement.targets, types) && every(statement.values, types);
21407
+ return statement.operator === "=" && every(statement.targets, types, statics) && every(statement.values, types, statics);
20254
21408
  case "call-statement":
20255
- return isPreservableExpression(statement.expression, types);
21409
+ return isPreservableExpression(statement.expression, types, statics);
20256
21410
  case "return-statement":
20257
- return every(statement.values, types);
21411
+ return every(statement.values, types, statics);
20258
21412
  case "while-statement":
20259
21413
  case "repeat-statement":
20260
- return isPreservableExpression(statement.condition, types);
21414
+ return isPreservableExpression(statement.condition, types, statics);
20261
21415
  case "if-statement":
20262
- return statement.clauses.every((clause) => isPreservableExpression(clause.condition, types));
21416
+ return statement.clauses.every((clause) => isPreservableExpression(clause.condition, types, statics));
20263
21417
  case "numeric-for-statement":
20264
- return isPreservableExpression(statement.start, types) && isPreservableExpression(statement.limit, types) && (statement.step === null || isPreservableExpression(statement.step, types));
21418
+ return isPreservableExpression(statement.start, types, statics) && isPreservableExpression(statement.limit, types, statics) && (statement.step === null || isPreservableExpression(statement.step, types, statics));
20265
21419
  case "generic-for-statement":
20266
- return every(statement.iterators, types);
21420
+ return every(statement.iterators, types, statics);
20267
21421
  case "break-statement":
20268
21422
  case "declare-statement":
20269
21423
  case "do-statement":
@@ -20391,7 +21545,7 @@ function classEdits(input, statement, spans) {
20391
21545
  edits.push({ start: span.start, end, replacement: isLazyField(member) ? longComment(text) : blankSpan(text) });
20392
21546
  continue;
20393
21547
  }
20394
- if (member.kind === "class-method") {
21548
+ if (member.kind === "class-method" && !member.isStatic) {
20395
21549
  const self = selfEdit(source, member, span);
20396
21550
  if (self === null) {
20397
21551
  return null;
@@ -20505,7 +21659,7 @@ function surgery(collector, statement) {
20505
21659
  const edits2 = classEdits(input, statement, { span, members: input.spans });
20506
21660
  return edits2 === null ? null : { edits: edits2, wrapsBody: false };
20507
21661
  }
20508
- if (loopBody(statement) === null || !isPreservableStatement(statement, input.types)) {
21662
+ if (loopBody(statement) === null || !isPreservableStatement(statement, input.types, input.staticAccess)) {
20509
21663
  return null;
20510
21664
  }
20511
21665
  const edits = loopEdits(input, statement);
@@ -20534,7 +21688,7 @@ function visit(collector, statement, wrapped) {
20534
21688
  descend(collector, statement, false, surgical.wrapsBody);
20535
21689
  return;
20536
21690
  }
20537
- if (isPreservableStatement(statement, collector.input.types) && keepsScaffolding(statement)) {
21691
+ if (isPreservableStatement(statement, collector.input.types, collector.input.staticAccess) && keepsScaffolding(statement)) {
20538
21692
  const inherited = statement.kind === "do-statement" || statement.kind === "if-statement" ? wrapped : false;
20539
21693
  descend(collector, statement, inherited, false);
20540
21694
  return;
@@ -20665,6 +21819,7 @@ function compile(source, options = {}) {
20665
21819
  const isDeclarationFile = options.filePath !== void 0 && isDeclarationPath(options.filePath);
20666
21820
  const checked = check(parsed.program, mode, environment, {
20667
21821
  ambient: options.ambient ?? EMPTY_AMBIENT,
21822
+ contracts: options.contracts ?? [],
20668
21823
  project: options.project ?? EMPTY_PROJECT_DECLARATIONS,
20669
21824
  isDeclarationFile,
20670
21825
  oop: settings.oop,
@@ -20678,7 +21833,9 @@ function compile(source, options = {}) {
20678
21833
  environment,
20679
21834
  declarations: ownDeclarations(checked.declarations, options.ambient),
20680
21835
  declaredGlobals: checked.declaredGlobals,
21836
+ moduleGlobals: checked.moduleGlobals,
20681
21837
  externalReferences: checked.externalReferences,
21838
+ referencedNames: checked.referencedNames,
20682
21839
  directives: checked.directives,
20683
21840
  topLevelReturn: findTopLevelReturn(parsed.program.body),
20684
21841
  events: checked.events
@@ -20687,7 +21844,7 @@ function compile(source, options = {}) {
20687
21844
  return { ...shared, code: null, requiredHelpers: [], lines: [] };
20688
21845
  }
20689
21846
  const references = reachedNames(checked.references, options.projectReferences);
20690
- const emitted = emit2(parsed.program, checked.types, references, checked.generatedMembers);
21847
+ const emitted = emit2(parsed.program, checked.types, references, checked.generatedMembers, 0, checked.staticAccess);
20691
21848
  const preserved = emitPreservingSource({
20692
21849
  source,
20693
21850
  program: parsed.program,
@@ -20697,6 +21854,7 @@ function compile(source, options = {}) {
20697
21854
  types: checked.types,
20698
21855
  references,
20699
21856
  generatedMembers: checked.generatedMembers,
21857
+ staticAccess: checked.staticAccess,
20700
21858
  development: options.development === true
20701
21859
  });
20702
21860
  const required = new Set(emitted.requiredHelpers);
@@ -20768,6 +21926,199 @@ function fingerprintDeclarations(declarations) {
20768
21926
  const events = declarations.events.map(eventText);
20769
21927
  return hashString([...classes, ...interfaces, ...enums, ...globals, ...events].sort(compareText).join(";"));
20770
21928
  }
21929
+ function appendText(entries3, name, text) {
21930
+ const existing = entries3.get(name);
21931
+ entries3.set(name, existing === void 0 ? text : `${existing}|${text}`);
21932
+ }
21933
+ function fingerprintByName(declarations) {
21934
+ const entries3 = /* @__PURE__ */ new Map();
21935
+ for (const info of declarations.classes) {
21936
+ appendText(entries3, info.name, classText(info));
21937
+ }
21938
+ for (const info of declarations.interfaces) {
21939
+ appendText(entries3, info.name, interfaceText(info));
21940
+ }
21941
+ for (const info of declarations.enums) {
21942
+ appendText(entries3, info.name, enumText(info));
21943
+ }
21944
+ for (const info of declarations.globals) {
21945
+ appendText(entries3, info.name, globalText(info));
21946
+ }
21947
+ for (const info of declarations.events) {
21948
+ appendText(entries3, `${EVENT_NAME_PREFIX}${info.name}`, eventText(info));
21949
+ }
21950
+ return new Map([...entries3].map(([name, text]) => [name, hashString(text)]));
21951
+ }
21952
+
21953
+ // ../compiler/src/project/dependency-graph.ts
21954
+ var MAX_CLOSURE_SIZE = 512;
21955
+ function indexOwners(nodes, environment) {
21956
+ const owners = /* @__PURE__ */ new Map();
21957
+ for (const node of nodes) {
21958
+ if (!canReference(environment, node.environment)) {
21959
+ continue;
21960
+ }
21961
+ for (const name of node.provides.keys()) {
21962
+ const existing = owners.get(name);
21963
+ if (existing === void 0) {
21964
+ owners.set(name, [node]);
21965
+ continue;
21966
+ }
21967
+ existing.push(node);
21968
+ }
21969
+ }
21970
+ return owners;
21971
+ }
21972
+ function walk3(node, owners) {
21973
+ const visited = /* @__PURE__ */ new Set();
21974
+ const pending = [...node.requires, ...node.provides.keys()];
21975
+ while (pending.length > 0) {
21976
+ const name = pending.pop();
21977
+ if (name === void 0 || visited.has(name)) {
21978
+ continue;
21979
+ }
21980
+ visited.add(name);
21981
+ if (visited.size > MAX_CLOSURE_SIZE) {
21982
+ return null;
21983
+ }
21984
+ for (const owner of owners.get(name) ?? []) {
21985
+ for (const required of owner.requires) {
21986
+ if (!visited.has(required)) {
21987
+ pending.push(required);
21988
+ }
21989
+ }
21990
+ }
21991
+ }
21992
+ return visited;
21993
+ }
21994
+ function ownerText(owners, name) {
21995
+ const list = owners.get(name) ?? [];
21996
+ return `${name}=${list.map((owner) => `${owner.path}:${owner.provides.get(name) ?? ""}`).sort().join("+")}`;
21997
+ }
21998
+ function createDependencyGraph(nodes) {
21999
+ const byPath = new Map(nodes.map((node) => [node.path, node]));
22000
+ const indexes = /* @__PURE__ */ new Map();
22001
+ const closures = /* @__PURE__ */ new Map();
22002
+ const keys = /* @__PURE__ */ new Map();
22003
+ function ownersFor(environment) {
22004
+ const cached = indexes.get(environment);
22005
+ if (cached !== void 0) {
22006
+ return cached;
22007
+ }
22008
+ const owners = indexOwners(nodes, environment);
22009
+ indexes.set(environment, owners);
22010
+ return owners;
22011
+ }
22012
+ function closureOf(path) {
22013
+ if (closures.has(path)) {
22014
+ return closures.get(path) ?? null;
22015
+ }
22016
+ const node = byPath.get(path);
22017
+ const closure = node === void 0 ? null : walk3(node, ownersFor(node.environment));
22018
+ closures.set(path, closure);
22019
+ return closure;
22020
+ }
22021
+ function keyOf(path) {
22022
+ if (keys.has(path)) {
22023
+ return keys.get(path) ?? null;
22024
+ }
22025
+ const node = byPath.get(path);
22026
+ const closure = closureOf(path);
22027
+ if (node === void 0 || closure === null) {
22028
+ keys.set(path, null);
22029
+ return null;
22030
+ }
22031
+ const owners = ownersFor(node.environment);
22032
+ const key = hashString([...closure].sort().map((name) => ownerText(owners, name)).join(";"));
22033
+ keys.set(path, key);
22034
+ return key;
22035
+ }
22036
+ return {
22037
+ closureOf,
22038
+ keyOf,
22039
+ dependentsOf: (names) => {
22040
+ const dependents = /* @__PURE__ */ new Set();
22041
+ for (const node of nodes) {
22042
+ const closure = closureOf(node.path);
22043
+ if (closure === null) {
22044
+ dependents.add(node.path);
22045
+ continue;
22046
+ }
22047
+ for (const name of names) {
22048
+ if (closure.has(name)) {
22049
+ dependents.add(node.path);
22050
+ break;
22051
+ }
22052
+ }
22053
+ }
22054
+ return dependents;
22055
+ },
22056
+ providedBy: (paths) => {
22057
+ const names = /* @__PURE__ */ new Set();
22058
+ for (const path of paths) {
22059
+ for (const name of byPath.get(path)?.provides.keys() ?? []) {
22060
+ names.add(name);
22061
+ }
22062
+ }
22063
+ return names;
22064
+ }
22065
+ };
22066
+ }
22067
+
22068
+ // ../compiler/src/project/ambient-scope.ts
22069
+ function optionsKey(options) {
22070
+ return `${options.strict ? "strict" : ""}|${options.oop ? "oop" : ""}|${options.noUnusedLocals ? "nul" : ""}|${options.noUnusedParameters ? "nup" : ""}`;
22071
+ }
22072
+ function declaresNothing(declarations) {
22073
+ const { classes, interfaces, enums, globals, events } = declarations;
22074
+ return classes.length === 0 && interfaces.length === 0 && enums.length === 0 && globals.length === 0 && events.length === 0;
22075
+ }
22076
+ function sharedEnums(collected) {
22077
+ const declared = new Set(collected.flatMap((entry) => entry.declarations.enums.map((enumeration) => enumeration.name)));
22078
+ if (declared.size === 0) {
22079
+ return declared;
22080
+ }
22081
+ return new Set(collected.flatMap((entry) => entry.externalReferences.filter((name) => declared.has(name))));
22082
+ }
22083
+ function baseKey(context) {
22084
+ const mode = context.development ? "development" : "release";
22085
+ const contracts = context.contracts.map((contract) => serializeAbi(contract)).join(";");
22086
+ return `${optionsKey(context.options)}|${mode}|${JSON.stringify(context.project.globals)}|${[...context.references].sort().join(",")}|${hashString(contracts)}`;
22087
+ }
22088
+ function environmentKeys(collected) {
22089
+ const keys = {};
22090
+ for (const environment of ALL_ENVIRONMENTS) {
22091
+ const visible = collected.filter((entry) => canReference(environment, entry.environment));
22092
+ keys[environment] = hashString(visible.map((entry) => `${entry.path}=${entry.fingerprint}`).join(";"));
22093
+ }
22094
+ return keys;
22095
+ }
22096
+ function createResolver(collected) {
22097
+ const visible = /* @__PURE__ */ new Map();
22098
+ const merged = /* @__PURE__ */ new Map();
22099
+ for (const environment of ALL_ENVIRONMENTS) {
22100
+ const entries3 = collected.filter((entry) => canReference(environment, entry.environment) && !declaresNothing(entry.declarations));
22101
+ visible.set(environment, entries3);
22102
+ merged.set(environment, mergeAmbient(entries3.map((entry) => entry.declarations)));
22103
+ }
22104
+ return (entry) => {
22105
+ if (declaresNothing(entry.declarations)) {
22106
+ return merged.get(entry.environment) ?? EMPTY_AMBIENT;
22107
+ }
22108
+ const entries3 = visible.get(entry.environment) ?? [];
22109
+ return mergeAmbient(entries3.filter((candidate) => candidate.path !== entry.path).map((candidate) => candidate.declarations));
22110
+ };
22111
+ }
22112
+ function createAmbientScope(collected, context) {
22113
+ const graph = createDependencyGraph(collected);
22114
+ const base = baseKey(context);
22115
+ const fallback = environmentKeys(collected);
22116
+ return {
22117
+ graph,
22118
+ keyFor: (entry) => hashString(`${base}|${graph.keyOf(entry.path) ?? fallback[entry.environment]}`),
22119
+ resolve: createResolver(collected)
22120
+ };
22121
+ }
20771
22122
 
20772
22123
  // ../compiler/src/project/module-graph.ts
20773
22124
  function buildSymbolTable(modules) {
@@ -20828,52 +22179,12 @@ function validateModuleReferences(modules) {
20828
22179
  function flattenDiagnostics(modules) {
20829
22180
  return modules.flatMap((module) => module.diagnostics.map((diagnostic) => ({ path: module.path, diagnostic })));
20830
22181
  }
20831
- function optionsKey(options) {
20832
- return `${options.strict ? "strict" : ""}|${options.oop ? "oop" : ""}|${options.noUnusedLocals ? "nul" : ""}|${options.noUnusedParameters ? "nup" : ""}`;
20833
- }
20834
- function sharedEnums(collected) {
20835
- const declared = new Set(collected.flatMap((entry) => entry.declarations.enums.map((enumeration) => enumeration.name)));
20836
- if (declared.size === 0) {
20837
- return declared;
20838
- }
20839
- return new Set(collected.flatMap((entry) => entry.externalReferences.filter((name) => declared.has(name))));
20840
- }
20841
- function ambientKeys(collected, context) {
20842
- const keys = {};
20843
- const { project, references, options } = context;
20844
- const mode = context.development ? "development" : "release";
20845
- const projectKey = `${optionsKey(options)}|${mode}|${JSON.stringify(project.globals)}|${[...references].sort().join(",")}`;
20846
- for (const environment of ALL_ENVIRONMENTS) {
20847
- const visible = collected.filter((entry) => canReference(environment, entry.environment));
20848
- keys[environment] = hashString(`${projectKey}|${visible.map((entry) => `${entry.path}=${entry.fingerprint}`).join(";")}`);
20849
- }
20850
- return keys;
20851
- }
20852
- function declaresNothing(declarations) {
20853
- const { classes, interfaces, enums, globals, events } = declarations;
20854
- return classes.length === 0 && interfaces.length === 0 && enums.length === 0 && globals.length === 0 && events.length === 0;
20855
- }
20856
- function createAmbientResolver(collected) {
20857
- const visible = /* @__PURE__ */ new Map();
20858
- const merged = /* @__PURE__ */ new Map();
20859
- for (const environment of ALL_ENVIRONMENTS) {
20860
- const entries2 = collected.filter((entry) => canReference(environment, entry.environment) && !declaresNothing(entry.declarations));
20861
- visible.set(environment, entries2);
20862
- merged.set(environment, mergeAmbient(entries2.map((entry) => entry.declarations)));
20863
- }
20864
- return (entry) => {
20865
- if (declaresNothing(entry.declarations)) {
20866
- return merged.get(entry.environment) ?? EMPTY_AMBIENT;
20867
- }
20868
- const entries2 = visible.get(entry.environment) ?? [];
20869
- return mergeAmbient(entries2.filter((candidate) => candidate.path !== entry.path).map((candidate) => candidate.declarations));
20870
- };
20871
- }
20872
22182
  function compileModule(file, ambient, context) {
20873
22183
  const result = compile(file.source, {
20874
22184
  filePath: file.path,
20875
22185
  ...file.environment === void 0 ? {} : { environment: file.environment },
20876
22186
  ambient,
22187
+ contracts: context.contracts,
20877
22188
  project: context.project,
20878
22189
  projectReferences: context.projectReferences,
20879
22190
  compilerOptions: context.compilerOptions,
@@ -20887,7 +22198,7 @@ function compileModule(file, ambient, context) {
20887
22198
  requiredHelpers: result.requiredHelpers,
20888
22199
  declaredGlobals: result.declaredGlobals,
20889
22200
  externalReferences: result.externalReferences,
20890
- contributions: toContributions(result.directives, result.environment),
22201
+ contributions: toContributions(result.directives, result.environment, result.moduleGlobals),
20891
22202
  diagnostics: result.diagnostics,
20892
22203
  lines: result.lines,
20893
22204
  topLevelReturn: result.topLevelReturn,
@@ -20928,20 +22239,22 @@ function createProjectCache() {
20928
22239
  environment: result.environment,
20929
22240
  declarations: result.declarations,
20930
22241
  externalReferences: [...result.externalReferences.keys()],
20931
- fingerprint: fingerprintDeclarations(result.declarations)
22242
+ fingerprint: fingerprintDeclarations(result.declarations),
22243
+ provides: fingerprintByName(result.declarations),
22244
+ requires: result.referencedNames
20932
22245
  };
20933
22246
  declarationCache.set(file.path, entry);
20934
22247
  return entry;
20935
22248
  }
20936
22249
  function moduleFor(pair, context, reused) {
20937
22250
  const { file, entry } = pair;
20938
- const ambientKey = context.keys[entry.environment];
22251
+ const ambientKey = context.scope.keyFor(entry);
20939
22252
  const cached = moduleCache.get(file.path);
20940
22253
  if (cached !== void 0 && cached.hash === entry.hash && cached.ambientKey === ambientKey) {
20941
22254
  reused.count += 1;
20942
22255
  return cached.module;
20943
22256
  }
20944
- const module = compileModule(file, context.resolve(entry), context);
22257
+ const module = compileModule(file, context.scope.resolve(entry), context);
20945
22258
  moduleCache.set(file.path, { hash: entry.hash, ambientKey, module });
20946
22259
  return module;
20947
22260
  }
@@ -20955,9 +22268,10 @@ function createProjectCache() {
20955
22268
  const collected = pairs.map((pair) => pair.entry);
20956
22269
  const projectReferences = sharedEnums(collected);
20957
22270
  const development = options.development === true;
22271
+ const contracts = options.contracts ?? [];
20958
22272
  const context = {
20959
- keys: ambientKeys(collected, { project, references: projectReferences, options: compilerOptions, development }),
20960
- resolve: createAmbientResolver(collected),
22273
+ scope: createAmbientScope(collected, { project, references: projectReferences, options: compilerOptions, development, contracts }),
22274
+ contracts,
20961
22275
  project,
20962
22276
  projectReferences,
20963
22277
  compilerOptions,
@@ -21037,8 +22351,8 @@ function materialize(build, resource, includeMap) {
21037
22351
  const bundled = materializeBundles(resource, build.bundles, helperContent);
21038
22352
  return { ...build, scripts: bundled.scripts, map: includeMap ? bundled.map : null };
21039
22353
  }
21040
- function diagnosticSources(files, entries2) {
21041
- const paths = new Set(entries2.map((entry) => entry.path));
22354
+ function diagnosticSources(files, entries3) {
22355
+ const paths = new Set(entries3.map((entry) => entry.path));
21042
22356
  return new Map(files.filter((file) => paths.has(file.path)).map((file) => [file.path, file.source]));
21043
22357
  }
21044
22358
  function runCompile(root, config, options = {}) {
@@ -21046,10 +22360,11 @@ function runCompile(root, config, options = {}) {
21046
22360
  const tracker = options.tracker ?? createPhaseTracker();
21047
22361
  const started = performance.now();
21048
22362
  tracker.begin("discovery");
21049
- const excluded = [config.outDir];
22363
+ const excluded = [config.outDir, config.contracts];
21050
22364
  const sources = discoverSources(root, config.sources, excluded);
21051
22365
  const inputs = readProjectInputs(root, { assets: config.assets, environment: config.environment, excluded });
21052
- const diagnostics = [...sources.diagnostics, ...inputs.diagnostics];
22366
+ const contracts = readDependencyContracts(root, config);
22367
+ const diagnostics = [...sources.diagnostics, ...inputs.diagnostics, ...contracts.diagnostics];
21053
22368
  if (hasCliErrors(diagnostics)) {
21054
22369
  tracker.end("failed");
21055
22370
  return {
@@ -21062,13 +22377,15 @@ function runCompile(root, config, options = {}) {
21062
22377
  environmentTemplate: null,
21063
22378
  phases: tracker.durations(),
21064
22379
  sources: /* @__PURE__ */ new Map(),
21065
- map: null
22380
+ map: null,
22381
+ contract: null
21066
22382
  };
21067
22383
  }
21068
22384
  tracker.begin("compile", sources.files.length);
21069
22385
  const declarations = projectDeclarations(inputs.declared?.entries ?? null, config.environment.file);
21070
22386
  const project = cache3.compile(sources.files, {
21071
22387
  project: declarations,
22388
+ contracts: contracts.contracts,
21072
22389
  compilerOptions: config.compilerOptions,
21073
22390
  development: options.development === true,
21074
22391
  onProgress: (event) => tracker.advance(event.item, event.index, event.total)
@@ -21095,13 +22412,14 @@ function runCompile(root, config, options = {}) {
21095
22412
  environmentTemplate: inputs.deployed === null ? null : renderEnvironmentTemplate(inputs.deployed),
21096
22413
  phases: tracker.durations(),
21097
22414
  sources: diagnosticSources(sources.files, assembly.diagnostics),
21098
- map: build?.map ?? null
22415
+ map: build?.map ?? null,
22416
+ contract: build === null ? null : buildResourceAbi(config.name, project.modules.flatMap((module) => module.contributions))
21099
22417
  };
21100
22418
  }
21101
22419
 
21102
22420
  // src/build/resource-map-file.ts
21103
- import { existsSync as existsSync5, mkdirSync as mkdirSync2, readFileSync as readFileSync6, readdirSync as readdirSync2, unlinkSync, writeFileSync as writeFileSync3 } from "node:fs";
21104
- import { dirname as dirname2, isAbsolute as isAbsolute3, join as join4, posix, relative as relative2, resolve as resolve8 } from "node:path";
22421
+ import { existsSync as existsSync5, mkdirSync as mkdirSync3, readFileSync as readFileSync7, readdirSync as readdirSync2, unlinkSync, writeFileSync as writeFileSync4 } from "node:fs";
22422
+ import { dirname as dirname3, isAbsolute as isAbsolute3, join as join4, posix, relative as relative2, resolve as resolve9 } from "node:path";
21105
22423
  var RESOURCE_MAP_SUFFIX = ".luam-map.json";
21106
22424
  function isObject(value) {
21107
22425
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -21166,10 +22484,10 @@ function reason(error) {
21166
22484
  return error instanceof Error ? error.message : String(error);
21167
22485
  }
21168
22486
  function resourceMapPath(root, config) {
21169
- return resolve8(root, config.outDir, `${config.name}${RESOURCE_MAP_SUFFIX}`);
22487
+ return resolve9(root, config.outDir, `${config.name}${RESOURCE_MAP_SUFFIX}`);
21170
22488
  }
21171
22489
  function explicitResourceMapPath(root, path) {
21172
- return isAbsolute3(path) ? path : resolve8(root, path);
22490
+ return isAbsolute3(path) ? path : resolve9(root, path);
21173
22491
  }
21174
22492
  function writeResourceMap(root, config, map) {
21175
22493
  const path = resourceMapPath(root, config);
@@ -21179,15 +22497,15 @@ function writeResourceMap(root, config, map) {
21179
22497
  }
21180
22498
  return;
21181
22499
  }
21182
- mkdirSync2(dirname2(path), { recursive: true });
22500
+ mkdirSync3(dirname3(path), { recursive: true });
21183
22501
  const content = serializeResourceMap(map);
21184
- if (!existsSync5(path) || readFileSync6(path, "utf8") !== content) {
21185
- writeFileSync3(path, content, "utf8");
22502
+ if (!existsSync5(path) || readFileSync7(path, "utf8") !== content) {
22503
+ writeFileSync4(path, content, "utf8");
21186
22504
  }
21187
22505
  }
21188
22506
  function readResourceMap(path) {
21189
22507
  try {
21190
- const value = JSON.parse(readFileSync6(path, "utf8"));
22508
+ const value = JSON.parse(readFileSync7(path, "utf8"));
21191
22509
  if (isObject(value) && Number.isInteger(value.version) && value.version !== RESOURCE_MAP_VERSION) {
21192
22510
  return { map: null, error: `Resource map version ${String(value.version)} is not supported.` };
21193
22511
  }
@@ -21201,13 +22519,13 @@ function isSearchable(name) {
21201
22519
  return !name.startsWith(".") && !SKIPPED_DIRECTORIES.has(name);
21202
22520
  }
21203
22521
  function collectResourceMaps(directory, found) {
21204
- let entries2;
22522
+ let entries3;
21205
22523
  try {
21206
- entries2 = readdirSync2(directory, { withFileTypes: true });
22524
+ entries3 = readdirSync2(directory, { withFileTypes: true });
21207
22525
  } catch {
21208
22526
  return;
21209
22527
  }
21210
- for (const entry of entries2) {
22528
+ for (const entry of entries3) {
21211
22529
  const absolute = join4(directory, entry.name);
21212
22530
  if (entry.isDirectory()) {
21213
22531
  if (isSearchable(entry.name)) {
@@ -21231,8 +22549,8 @@ function discoverResourceMap(root) {
21231
22549
  }
21232
22550
 
21233
22551
  // src/build/resource-writer.ts
21234
- import { existsSync as existsSync7, mkdirSync as mkdirSync3, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "node:fs";
21235
- import { dirname as dirname3, isAbsolute as isAbsolute4, relative as relative4, resolve as resolve10 } from "node:path";
22552
+ import { existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync8, writeFileSync as writeFileSync5 } from "node:fs";
22553
+ import { dirname as dirname4, isAbsolute as isAbsolute4, relative as relative4, resolve as resolve11 } from "node:path";
21236
22554
 
21237
22555
  // src/build/lua-tokens.ts
21238
22556
  var WHITESPACE = /* @__PURE__ */ new Set([" ", " ", "\n", "\r", "\v", "\f"]);
@@ -21289,7 +22607,7 @@ function readLongBracket2(source, start, level, file) {
21289
22607
  return end + closing.length;
21290
22608
  }
21291
22609
  function readShortString(source, start, file) {
21292
- const quote2 = source[start];
22610
+ const quote3 = source[start];
21293
22611
  let index = start + 1;
21294
22612
  while (index < source.length) {
21295
22613
  const char = source[index];
@@ -21297,7 +22615,7 @@ function readShortString(source, start, file) {
21297
22615
  index += 2;
21298
22616
  continue;
21299
22617
  }
21300
- if (char === quote2) {
22618
+ if (char === quote3) {
21301
22619
  return index + 1;
21302
22620
  }
21303
22621
  if (char === "\n") {
@@ -21436,7 +22754,7 @@ function minifyLuaFiles(files) {
21436
22754
 
21437
22755
  // src/build/resource-prune.ts
21438
22756
  import { existsSync as existsSync6, readdirSync as readdirSync3, rmdirSync, unlinkSync as unlinkSync2 } from "node:fs";
21439
- import { join as join5, relative as relative3, resolve as resolve9 } from "node:path";
22757
+ import { join as join5, relative as relative3, resolve as resolve10 } from "node:path";
21440
22758
  var GENERATED_MANIFEST = "meta.xml";
21441
22759
  var GENERATED_EXTENSION = ".lua";
21442
22760
  var PROTECTED = /* @__PURE__ */ new Set([ENVIRONMENT_FILE, ".env.local"]);
@@ -21453,14 +22771,14 @@ function isGenerated(relativePath2, options) {
21453
22771
  return options.generatedRoots.some((root) => isUnderRoot(relativePath2, root));
21454
22772
  }
21455
22773
  function removeEmptyDirectories(targetDir, directory) {
21456
- if (resolve9(directory) === resolve9(targetDir)) {
22774
+ if (resolve10(directory) === resolve10(targetDir)) {
21457
22775
  return;
21458
22776
  }
21459
22777
  if (readdirSync3(directory).length > 0) {
21460
22778
  return;
21461
22779
  }
21462
22780
  rmdirSync(directory);
21463
- removeEmptyDirectories(targetDir, resolve9(directory, ".."));
22781
+ removeEmptyDirectories(targetDir, resolve10(directory, ".."));
21464
22782
  }
21465
22783
  function pruneResource(targetDir, keep, options) {
21466
22784
  if (!existsSync6(targetDir)) {
@@ -21513,7 +22831,7 @@ function resourceFiles(build) {
21513
22831
  return files;
21514
22832
  }
21515
22833
  function resolveInside(targetDir, path) {
21516
- const absolute = resolve10(targetDir, path);
22834
+ const absolute = resolve11(targetDir, path);
21517
22835
  const inside = relative4(targetDir, absolute);
21518
22836
  if (inside.startsWith("..") || isAbsolute4(inside)) {
21519
22837
  throw new Error(`The generated file "${path}" resolves outside the resource directory "${targetDir}".`);
@@ -21521,11 +22839,11 @@ function resolveInside(targetDir, path) {
21521
22839
  return absolute;
21522
22840
  }
21523
22841
  function writeIfChanged(absolute, content) {
21524
- if (existsSync7(absolute) && readFileSync7(absolute).equals(content)) {
22842
+ if (existsSync7(absolute) && readFileSync8(absolute).equals(content)) {
21525
22843
  return false;
21526
22844
  }
21527
- mkdirSync3(dirname3(absolute), { recursive: true });
21528
- writeFileSync4(absolute, content);
22845
+ mkdirSync4(dirname4(absolute), { recursive: true });
22846
+ writeFileSync5(absolute, content);
21529
22847
  return true;
21530
22848
  }
21531
22849
  function writeEnvironmentFile(targetDir, template) {
@@ -21536,8 +22854,8 @@ function writeEnvironmentFile(targetDir, template) {
21536
22854
  if (existsSync7(absolute)) {
21537
22855
  return false;
21538
22856
  }
21539
- mkdirSync3(dirname3(absolute), { recursive: true });
21540
- writeFileSync4(absolute, template, "utf8");
22857
+ mkdirSync4(dirname4(absolute), { recursive: true });
22858
+ writeFileSync5(absolute, template, "utf8");
21541
22859
  return true;
21542
22860
  }
21543
22861
  function writeResource(targetDir, build, options) {
@@ -21561,7 +22879,7 @@ function writeResource(targetDir, build, options) {
21561
22879
  advance(path);
21562
22880
  }
21563
22881
  for (const asset of build.assets) {
21564
- if (writeIfChanged(resolveInside(targetDir, asset.path), readFileSync7(resolve10(options.root, asset.source)))) {
22882
+ if (writeIfChanged(resolveInside(targetDir, asset.path), readFileSync8(resolve11(options.root, asset.source)))) {
21565
22883
  written.push(asset.path);
21566
22884
  advance(asset.path);
21567
22885
  continue;
@@ -21678,16 +22996,16 @@ function reportBuildOutcome(context, outcome, action) {
21678
22996
  }
21679
22997
 
21680
22998
  // src/commands/resource-targets.ts
21681
- import { isAbsolute as isAbsolute5, resolve as resolve11 } from "node:path";
22999
+ import { isAbsolute as isAbsolute5, resolve as resolve12 } from "node:path";
21682
23000
  function resolveBuildTarget(root, config) {
21683
- return resolve11(root, config.outDir, config.name);
23001
+ return resolve12(root, config.outDir, config.name);
21684
23002
  }
21685
23003
  function resolveServerTarget(root, config) {
21686
23004
  if (config.serverPath === null) {
21687
23005
  return null;
21688
23006
  }
21689
- const serverRoot = isAbsolute5(config.serverPath) ? config.serverPath : resolve11(root, config.serverPath);
21690
- return resolve11(serverRoot, config.resourcesDir, config.name);
23007
+ const serverRoot = isAbsolute5(config.serverPath) ? config.serverPath : resolve12(root, config.serverPath);
23008
+ return resolve12(serverRoot, config.resourcesDir, config.name);
21691
23009
  }
21692
23010
 
21693
23011
  // src/reporting/progress-renderer.ts
@@ -21732,7 +23050,7 @@ function createProgressRenderer(reporter, options = {}) {
21732
23050
  paint(reporter.style.eraseLine());
21733
23051
  dirty = false;
21734
23052
  }
21735
- function render(event) {
23053
+ function render2(event) {
21736
23054
  const columns = Math.max(20, reporter.capability.columns - 1);
21737
23055
  const line2 = activeLine(reporter, event);
21738
23056
  paint(`${reporter.style.eraseLine()}${line2.slice(0, columns)}`);
@@ -21752,7 +23070,7 @@ function createProgressRenderer(reporter, options = {}) {
21752
23070
  if (frames > 0 && now() - lastPaintAt < throttleMs) {
21753
23071
  return;
21754
23072
  }
21755
- render(event);
23073
+ render2(event);
21756
23074
  }
21757
23075
  return {
21758
23076
  listen: (event) => {
@@ -21810,6 +23128,9 @@ async function runBuildCommand(context, options = {}) {
21810
23128
  return EXIT_DIAGNOSTICS;
21811
23129
  }
21812
23130
  writeResourceMap(context.root, context.config, writtenMap(outcome.map, minify));
23131
+ if (outcome.contract !== null && outcome.contract.exports.length > 0) {
23132
+ writeResourceContract(context.root, context.config, outcome.contract);
23133
+ }
21813
23134
  tracker.end();
21814
23135
  renderer.clear();
21815
23136
  reportBuildOutcome(context, outcome, "Build");
@@ -21860,6 +23181,298 @@ function registerCheckCommand(program2, runtime) {
21860
23181
  });
21861
23182
  }
21862
23183
 
23184
+ // src/commands/config-command.ts
23185
+ import { existsSync as existsSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync9, writeFileSync as writeFileSync6 } from "node:fs";
23186
+ import { dirname as dirname5, relative as relative5, resolve as resolve13 } from "node:path";
23187
+
23188
+ // ../compiler/src/project/config-reader.ts
23189
+ var Reader = class {
23190
+ text;
23191
+ index = 0;
23192
+ problems = [];
23193
+ constructor(text) {
23194
+ this.text = text;
23195
+ }
23196
+ position() {
23197
+ const before = this.text.slice(0, this.index);
23198
+ const lines = before.split("\n");
23199
+ return { line: lines.length, column: (lines[lines.length - 1]?.length ?? 0) + 1 };
23200
+ }
23201
+ fail(message) {
23202
+ const { line: line2, column } = this.position();
23203
+ this.problems.push({ line: line2, column, message });
23204
+ return null;
23205
+ }
23206
+ skipComment() {
23207
+ if (!this.text.startsWith("--", this.index)) {
23208
+ return false;
23209
+ }
23210
+ if (this.text.startsWith("--[[", this.index)) {
23211
+ const end2 = this.text.indexOf("]]", this.index + 4);
23212
+ this.index = end2 === -1 ? this.text.length : end2 + 2;
23213
+ return true;
23214
+ }
23215
+ const end = this.text.indexOf("\n", this.index);
23216
+ this.index = end === -1 ? this.text.length : end + 1;
23217
+ return true;
23218
+ }
23219
+ skip() {
23220
+ for (; ; ) {
23221
+ const before = this.index;
23222
+ while (this.index < this.text.length && /\s/.test(this.text[this.index] ?? "")) {
23223
+ this.index += 1;
23224
+ }
23225
+ this.skipComment();
23226
+ if (this.index === before) {
23227
+ return;
23228
+ }
23229
+ }
23230
+ }
23231
+ done() {
23232
+ this.skip();
23233
+ return this.index >= this.text.length;
23234
+ }
23235
+ peek() {
23236
+ this.skip();
23237
+ return this.text[this.index] ?? "";
23238
+ }
23239
+ take(value) {
23240
+ this.skip();
23241
+ if (!this.text.startsWith(value, this.index)) {
23242
+ return false;
23243
+ }
23244
+ this.index += value.length;
23245
+ return true;
23246
+ }
23247
+ word() {
23248
+ this.skip();
23249
+ const match = /^[A-Za-z_][A-Za-z0-9_]*/.exec(this.text.slice(this.index));
23250
+ if (match === null) {
23251
+ return null;
23252
+ }
23253
+ this.index += match[0].length;
23254
+ return match[0];
23255
+ }
23256
+ literal() {
23257
+ this.skip();
23258
+ const rest = this.text.slice(this.index);
23259
+ const string = /^(\[\[[\s\S]*?\]\]|'(?:\\.|[^'\\])*'|"(?:\\.|[^"\\])*")/.exec(rest);
23260
+ if (string !== null) {
23261
+ this.index += string[0].length;
23262
+ return "string";
23263
+ }
23264
+ const number = /^-?(?:0[xX][0-9a-fA-F]+|\d+\.?\d*(?:[eE][-+]?\d+)?|\.\d+)/.exec(rest);
23265
+ if (number !== null) {
23266
+ this.index += number[0].length;
23267
+ return "number";
23268
+ }
23269
+ const word = /^(true|false|nil)\b/.exec(rest);
23270
+ if (word === null) {
23271
+ return null;
23272
+ }
23273
+ this.index += word[0].length;
23274
+ return word[0] === "nil" ? "nil" : "boolean";
23275
+ }
23276
+ key() {
23277
+ this.skip();
23278
+ if (this.take("[")) {
23279
+ const rest = this.text.slice(this.index);
23280
+ const quoted2 = /^'([A-Za-z_][A-Za-z0-9_]*)'|^"([A-Za-z_][A-Za-z0-9_]*)"/.exec(rest);
23281
+ if (quoted2 === null) {
23282
+ return null;
23283
+ }
23284
+ this.index += quoted2[0].length;
23285
+ return this.take("]") && this.take("=") ? quoted2[1] ?? quoted2[2] ?? null : null;
23286
+ }
23287
+ const start = this.index;
23288
+ const word = this.word();
23289
+ if (word === null || !this.take("=") || this.peek() === "=") {
23290
+ this.index = start;
23291
+ return null;
23292
+ }
23293
+ return word;
23294
+ }
23295
+ };
23296
+
23297
+ // ../compiler/src/project/config-extractor.ts
23298
+ var GENERATED_MARKER = '# Generated by "luam config" from';
23299
+ var MAX_SOURCE_BYTES = 262144;
23300
+ var MAX_DEPTH2 = 8;
23301
+ var MAX_ENTRIES = 512;
23302
+ function renderRecord(fields) {
23303
+ return `{ ${fields.map(([name, type]) => `${name}: ${type}`).join(", ")} }`;
23304
+ }
23305
+ function render(value) {
23306
+ if (value.kind === "literal") {
23307
+ return value.type === "nil" ? "any" : value.type;
23308
+ }
23309
+ if (value.kind === "array") {
23310
+ return `${value.element}[]`;
23311
+ }
23312
+ return value.kind === "record" && value.fields.length > 0 ? renderRecord(value.fields) : "table";
23313
+ }
23314
+ function unify2(types) {
23315
+ const unique = [...new Set(types)];
23316
+ return unique.length === 1 ? unique[0] ?? "any" : "any";
23317
+ }
23318
+ function readTable2(reader, depth) {
23319
+ if (depth > MAX_DEPTH2) {
23320
+ return reader.fail(`A table nested deeper than ${MAX_DEPTH2} levels is not extracted.`);
23321
+ }
23322
+ const fields = [];
23323
+ const elements = [];
23324
+ for (let count = 0; ; count += 1) {
23325
+ if (reader.take("}")) {
23326
+ break;
23327
+ }
23328
+ if (count > MAX_ENTRIES) {
23329
+ return reader.fail(`A table with more than ${MAX_ENTRIES} entries is not extracted.`);
23330
+ }
23331
+ const key = reader.key();
23332
+ const value = readValue(reader, depth + 1);
23333
+ if (value === null) {
23334
+ return null;
23335
+ }
23336
+ if (key === null) {
23337
+ elements.push(render(value));
23338
+ } else {
23339
+ fields.push([key, render(value)]);
23340
+ }
23341
+ if (!reader.take(",") && !reader.take(";") && reader.peek() !== "}") {
23342
+ return reader.fail('Expected "," or "}" in a table.');
23343
+ }
23344
+ }
23345
+ if (fields.length > 0 && elements.length > 0) {
23346
+ return { kind: "table" };
23347
+ }
23348
+ return elements.length > 0 ? { kind: "array", element: unify2(elements) } : { kind: "record", fields };
23349
+ }
23350
+ function readValue(reader, depth) {
23351
+ if (reader.take("{")) {
23352
+ return readTable2(reader, depth);
23353
+ }
23354
+ const literal2 = reader.literal();
23355
+ if (literal2 === null) {
23356
+ return reader.fail("Only literals and table constructors are extracted. Declare this value by hand.");
23357
+ }
23358
+ if (reader.peek() === "." || reader.peek() === "(" || reader.peek() === "+") {
23359
+ return reader.fail("An expression is not extracted. Declare this value by hand.");
23360
+ }
23361
+ return { kind: "literal", type: literal2 };
23362
+ }
23363
+ function extractConfigDeclarations(source) {
23364
+ if (source.length > MAX_SOURCE_BYTES) {
23365
+ return { declarations: [], problems: [{ line: 1, column: 1, message: `A file larger than ${MAX_SOURCE_BYTES} bytes is not extracted.` }] };
23366
+ }
23367
+ const reader = new Reader(source);
23368
+ const declarations = /* @__PURE__ */ new Map();
23369
+ while (!reader.done()) {
23370
+ reader.take("local");
23371
+ const name = reader.word();
23372
+ if (name === null || !/^[A-Za-z_][A-Za-z0-9_]*$/.test(name) || !reader.take("=")) {
23373
+ reader.fail("Only a top-level assignment of a literal or a table is extracted.");
23374
+ break;
23375
+ }
23376
+ const value = readValue(reader, 1);
23377
+ if (value === null) {
23378
+ break;
23379
+ }
23380
+ declarations.set(name, render(value));
23381
+ reader.take(";");
23382
+ }
23383
+ return { declarations: [...declarations].map(([name, type]) => ({ name, type })), problems: reader.problems };
23384
+ }
23385
+ function renderConfigDeclarations(source, declarations) {
23386
+ const lines = declarations.map((declaration) => `declare ${declaration.name}: ${declaration.type}`);
23387
+ return `${GENERATED_MARKER} "${source}". Do not edit.
23388
+
23389
+ ${lines.join("\n")}
23390
+ `;
23391
+ }
23392
+ function isGeneratedDeclaration(text) {
23393
+ return text.startsWith(GENERATED_MARKER);
23394
+ }
23395
+
23396
+ // src/commands/config-command.ts
23397
+ var DEFAULT_CONFIG_SOURCE = "config.lua";
23398
+ var DEFAULT_CONFIG_OUTPUT = "config.d.luam";
23399
+ function escapes(root, path) {
23400
+ const inside = relative5(root, path);
23401
+ return inside.startsWith("..") || resolve13(root, inside) !== path;
23402
+ }
23403
+ function readSource2(root, source, reporter) {
23404
+ const path = resolve13(root, source);
23405
+ if (escapes(root, path)) {
23406
+ reporter.error(`Config declarations read only inside the project, but "${source}" points outside it.`);
23407
+ return null;
23408
+ }
23409
+ if (!existsSync8(path)) {
23410
+ reporter.error(`Config declarations found no file at "${source}".`);
23411
+ return null;
23412
+ }
23413
+ try {
23414
+ return readFileSync9(path, "utf8");
23415
+ } catch {
23416
+ reporter.error(`Config declarations could not read "${source}".`);
23417
+ return null;
23418
+ }
23419
+ }
23420
+ function writable(root, out, reporter) {
23421
+ const path = resolve13(root, out);
23422
+ if (escapes(root, path)) {
23423
+ reporter.error(`Config declarations write only inside the project, but "${out}" points outside it.`);
23424
+ return null;
23425
+ }
23426
+ if (existsSync8(path) && !isGeneratedDeclaration(readFileSync9(path, "utf8"))) {
23427
+ reporter.error(`"${out}" was not generated by this command and was left alone. Choose another path with --out.`);
23428
+ return null;
23429
+ }
23430
+ return path;
23431
+ }
23432
+ function runConfigCommand(root, reporter, options = {}) {
23433
+ const source = options.source ?? DEFAULT_CONFIG_SOURCE;
23434
+ const out = options.out ?? DEFAULT_CONFIG_OUTPUT;
23435
+ const text = readSource2(root, source, reporter);
23436
+ if (text === null) {
23437
+ return EXIT_DIAGNOSTICS;
23438
+ }
23439
+ const extracted = extractConfigDeclarations(text);
23440
+ for (const problem of extracted.problems) {
23441
+ reporter.warn(`${source}:${problem.line}:${problem.column} ${problem.message}`);
23442
+ }
23443
+ if (extracted.declarations.length === 0) {
23444
+ reporter.error(`Config declarations found nothing to declare in "${source}".`);
23445
+ return EXIT_DIAGNOSTICS;
23446
+ }
23447
+ const rendered = renderConfigDeclarations(source, extracted.declarations);
23448
+ if (options.write !== true) {
23449
+ reporter.info(rendered.trimEnd());
23450
+ reporter.info(`Config declarations would write ${extracted.declarations.length} name(s) to "${out}". Pass --write to save it.`);
23451
+ return EXIT_OK;
23452
+ }
23453
+ const path = writable(root, out, reporter);
23454
+ if (path === null) {
23455
+ return EXIT_DIAGNOSTICS;
23456
+ }
23457
+ mkdirSync5(dirname5(path), { recursive: true });
23458
+ writeFileSync6(path, rendered, "utf8");
23459
+ reporter.info(`Config declarations wrote ${extracted.declarations.length} name(s) to "${out}".`);
23460
+ return EXIT_OK;
23461
+ }
23462
+
23463
+ // src/cli/registry/config-registration.ts
23464
+ function registerConfigCommand(program2, runtime) {
23465
+ const command = program2.command("config").description('Derive a declaration file from the literal data in a native "config.lua".');
23466
+ addProjectOptions(command).addOption(valueOption("--source <path>", 'Native Lua file to read. Defaults to "config.lua".')).addOption(valueOption("--out <path>", 'Declaration file to write. Defaults to "config.d.luam".')).addOption(new Option("--write", "Write the declaration file instead of printing it."));
23467
+ command.action((options) => {
23468
+ runtime.exitCode = runConfigCommand(commandRoot(runtime, options), runtime.reporter, {
23469
+ source: options.source ?? null,
23470
+ out: options.out ?? null,
23471
+ write: options.write === true
23472
+ });
23473
+ });
23474
+ }
23475
+
21863
23476
  // src/commands/ensure-runner.ts
21864
23477
  function hasChanges(result) {
21865
23478
  return result.written.length > 0 || result.removed.length > 0;
@@ -21884,7 +23497,7 @@ function restartResource(scope, serverConsole) {
21884
23497
  reporter.success(`Restarted "${scope.context.config.name}" through the owned server console.`);
21885
23498
  return true;
21886
23499
  }
21887
- function failed(outcome) {
23500
+ function failed2(outcome) {
21888
23501
  return { ok: false, outcome, sync: null, restarted: false };
21889
23502
  }
21890
23503
  async function runOnce(scope, target, cache3, options) {
@@ -21907,7 +23520,7 @@ async function runOnce(scope, target, cache3, options) {
21907
23520
  if (outcome.build === null) {
21908
23521
  reportBuildOutcome(context, outcome, "Build");
21909
23522
  reporter.warn("Skipping sync and restart because the build reported errors.");
21910
- return failed(outcome);
23523
+ return failed2(outcome);
21911
23524
  }
21912
23525
  const writeOptions = trackedWriteOptions(context.root, context.config, outcome.environmentTemplate, tracker);
21913
23526
  reportBuildOutcome(context, outcome, "Build");
@@ -21955,8 +23568,8 @@ function reportRebuildSeparator(reporter, at = /* @__PURE__ */ new Date()) {
21955
23568
  }
21956
23569
 
21957
23570
  // src/watch/source-watcher.ts
21958
- import { existsSync as existsSync8, watch } from "node:fs";
21959
- import { relative as relative5, resolve as resolve12 } from "node:path";
23571
+ import { existsSync as existsSync9, watch } from "node:fs";
23572
+ import { relative as relative6, resolve as resolve14 } from "node:path";
21960
23573
  var DEFAULT_DEBOUNCE_MS = 120;
21961
23574
  function watchSources(root, sources, onChange, debounceMs = DEFAULT_DEBOUNCE_MS) {
21962
23575
  const resolver = createSourceResolver(sources);
@@ -21972,12 +23585,12 @@ function watchSources(root, sources, onChange, debounceMs = DEFAULT_DEBOUNCE_MS)
21972
23585
  }, debounceMs);
21973
23586
  };
21974
23587
  const isWatched = (directory, filename) => {
21975
- const path = normalizePattern(relative5(root, resolve12(directory, filename)));
23588
+ const path = normalizePattern(relative6(root, resolve14(directory, filename)));
21976
23589
  return path.endsWith(SOURCE_EXTENSION) && resolver.resolve(path).matches.length > 0;
21977
23590
  };
21978
23591
  for (const entry of resolver.roots) {
21979
- const absolute = resolve12(root, entry);
21980
- if (!existsSync8(absolute)) {
23592
+ const absolute = resolve14(root, entry);
23593
+ if (!existsSync9(absolute)) {
21981
23594
  continue;
21982
23595
  }
21983
23596
  watchers.push(
@@ -22184,12 +23797,12 @@ function parseMtaLogLine(line2, activeResource, fallback = /* @__PURE__ */ new D
22184
23797
  }
22185
23798
 
22186
23799
  // src/logging/server-log-follower.ts
22187
- import { closeSync, existsSync as existsSync9, openSync, readSync, statSync as statSync4 } from "node:fs";
22188
- import { isAbsolute as isAbsolute6, resolve as resolve13 } from "node:path";
23800
+ import { closeSync, existsSync as existsSync10, openSync, readSync, statSync as statSync4 } from "node:fs";
23801
+ import { isAbsolute as isAbsolute6, resolve as resolve15 } from "node:path";
22189
23802
  var DEFAULT_POLL_INTERVAL_MS = 100;
22190
23803
  function resolveServerLogPath(root, serverPath) {
22191
- const server = isAbsolute6(serverPath) ? serverPath : resolve13(root, serverPath);
22192
- return resolve13(server, "mods/deathmatch/logs/server.log");
23804
+ const server = isAbsolute6(serverPath) ? serverPath : resolve15(root, serverPath);
23805
+ return resolve15(server, "mods/deathmatch/logs/server.log");
22193
23806
  }
22194
23807
  function identity(path) {
22195
23808
  const stats = statSync4(path);
@@ -22214,7 +23827,7 @@ function followServerLog(path, onLine, options = {}) {
22214
23827
  let initialized = false;
22215
23828
  let closed = false;
22216
23829
  const poll = () => {
22217
- if (closed || !existsSync9(path)) {
23830
+ if (closed || !existsSync10(path)) {
22218
23831
  return;
22219
23832
  }
22220
23833
  try {
@@ -22330,9 +23943,9 @@ function connectServerConsoleInput(input, output, interrupt) {
22330
23943
 
22331
23944
  // src/server/server-executable-resolver.ts
22332
23945
  import { realpathSync, statSync as statSync5 } from "node:fs";
22333
- import { isAbsolute as isAbsolute7, relative as relative6, resolve as resolve14 } from "node:path";
23946
+ import { isAbsolute as isAbsolute7, relative as relative7, resolve as resolve16 } from "node:path";
22334
23947
  function isInside(root, path) {
22335
- const pathFromRoot = relative6(root, path);
23948
+ const pathFromRoot = relative7(root, path);
22336
23949
  return pathFromRoot.length === 0 || !pathFromRoot.startsWith("..") && !isAbsolute7(pathFromRoot);
22337
23950
  }
22338
23951
  function candidates2(platform) {
@@ -22345,14 +23958,14 @@ function candidates2(platform) {
22345
23958
  throw new Error(`Local MTA server startup is not supported on platform "${platform}". Use Windows or Linux.`);
22346
23959
  }
22347
23960
  function resolveServerExecutable(options) {
22348
- const serverRoot = resolve14(options.root, options.serverPath);
23961
+ const serverRoot = resolve16(options.root, options.serverPath);
22349
23962
  const names = options.configured === null ? candidates2(options.platform ?? process.platform) : [options.configured];
22350
23963
  const attempted = [];
22351
- if (options.configured !== null && (isAbsolute7(options.configured) || !isInside(serverRoot, resolve14(serverRoot, options.configured)))) {
23964
+ if (options.configured !== null && (isAbsolute7(options.configured) || !isInside(serverRoot, resolve16(serverRoot, options.configured)))) {
22352
23965
  throw new Error(`Configured development.server.executable must stay inside serverPath but received "${options.configured}".`);
22353
23966
  }
22354
23967
  for (const name of names) {
22355
- const path = resolve14(serverRoot, name);
23968
+ const path = resolve16(serverRoot, name);
22356
23969
  attempted.push(path);
22357
23970
  try {
22358
23971
  if (!statSync5(path).isFile()) {
@@ -22670,7 +24283,7 @@ function registerEnsureCommand(program2, runtime) {
22670
24283
  }
22671
24284
 
22672
24285
  // src/cli/registry/init-registration.ts
22673
- import { resolve as resolve16 } from "node:path";
24286
+ import { resolve as resolve18 } from "node:path";
22674
24287
 
22675
24288
  // src/commands/init-command.ts
22676
24289
  import { basename as basename2 } from "node:path";
@@ -22732,7 +24345,7 @@ function renderManifest(source, details) {
22732
24345
  }
22733
24346
 
22734
24347
  // src/scaffold/template-files.ts
22735
- import { existsSync as existsSync10, readFileSync as readFileSync8 } from "node:fs";
24348
+ import { existsSync as existsSync11, readFileSync as readFileSync10 } from "node:fs";
22736
24349
  import { fileURLToPath as fileURLToPath2 } from "node:url";
22737
24350
 
22738
24351
  // ../template/src/template.ts
@@ -22748,10 +24361,10 @@ function bundledTemplatePath(source) {
22748
24361
  }
22749
24362
  function resolveTemplatePath(source) {
22750
24363
  const packaged = fileURLToPath2(resolveTemplateUrl(source));
22751
- return existsSync10(packaged) ? packaged : bundledTemplatePath(source);
24364
+ return existsSync11(packaged) ? packaged : bundledTemplatePath(source);
22752
24365
  }
22753
24366
  function readTemplateSource(source) {
22754
- return readFileSync8(resolveTemplatePath(source), "utf8");
24367
+ return readFileSync10(resolveTemplatePath(source), "utf8");
22755
24368
  }
22756
24369
 
22757
24370
  // src/scaffold/scaffold-plan.ts
@@ -22767,19 +24380,19 @@ function buildScaffoldPlan(details) {
22767
24380
  }
22768
24381
 
22769
24382
  // src/scaffold/scaffold-writer.ts
22770
- import { existsSync as existsSync11, mkdirSync as mkdirSync4, writeFileSync as writeFileSync5 } from "node:fs";
22771
- import { dirname as dirname4, resolve as resolve15 } from "node:path";
24383
+ import { existsSync as existsSync12, mkdirSync as mkdirSync6, writeFileSync as writeFileSync7 } from "node:fs";
24384
+ import { dirname as dirname6, resolve as resolve17 } from "node:path";
22772
24385
  function writeScaffold(targetDir, plan, force) {
22773
24386
  const written = [];
22774
24387
  const skipped = [];
22775
24388
  for (const file of plan.files) {
22776
- const absolute = resolve15(targetDir, file.path);
22777
- if (!force && existsSync11(absolute)) {
24389
+ const absolute = resolve17(targetDir, file.path);
24390
+ if (!force && existsSync12(absolute)) {
22778
24391
  skipped.push(file.path);
22779
24392
  continue;
22780
24393
  }
22781
- mkdirSync4(dirname4(absolute), { recursive: true });
22782
- writeFileSync5(absolute, file.content, "utf8");
24394
+ mkdirSync6(dirname6(absolute), { recursive: true });
24395
+ writeFileSync7(absolute, file.content, "utf8");
22783
24396
  written.push(file.path);
22784
24397
  }
22785
24398
  return { written: written.sort((left, right) => left.localeCompare(right)), skipped: skipped.sort((left, right) => left.localeCompare(right)) };
@@ -22829,7 +24442,7 @@ function registerInitCommand(program2, runtime) {
22829
24442
  command.addOption(cwdOption()).addOption(valueOption("--name <name>", "Resource name. Defaults to the destination directory name.")).addOption(new Option("--force", "Overwrite files that already exist.")).addOption(new Option("-y, --yes", "Accept the defaults without prompting.")).addOption(colorOption());
22830
24443
  command.action(async (path, options) => {
22831
24444
  const root = commandRoot(runtime, options);
22832
- const target = path === void 0 ? root : resolve16(root, path);
24445
+ const target = path === void 0 ? root : resolve18(root, path);
22833
24446
  runtime.exitCode = await runInitCommand(target, runtime.logger, {
22834
24447
  name: options.name ?? null,
22835
24448
  force: options.force === true,
@@ -22847,7 +24460,7 @@ async function runSetupCommand(reporter, options) {
22847
24460
  reporter.detail("Supported commands: code, code-insiders, cursor, codium, windsurf.");
22848
24461
  return EXIT_DIAGNOSTICS;
22849
24462
  }
22850
- let failed2 = false;
24463
+ let failed3 = false;
22851
24464
  for (const editor of editors) {
22852
24465
  if (options.editorService.hasExtension(editor)) {
22853
24466
  reporter.success(`${editor.name}: Luam is already installed.`);
@@ -22866,12 +24479,12 @@ async function runSetupCommand(reporter, options) {
22866
24479
  const result = await options.editorService.install(editor);
22867
24480
  if (result.error !== null) {
22868
24481
  reporter.error(`${editor.name}: installation failed. ${result.error}`);
22869
- failed2 = true;
24482
+ failed3 = true;
22870
24483
  continue;
22871
24484
  }
22872
24485
  reporter.success(`${editor.name}: installed from the ${result.source}.`);
22873
24486
  }
22874
- return failed2 ? EXIT_DIAGNOSTICS : EXIT_OK;
24487
+ return failed3 ? EXIT_DIAGNOSTICS : EXIT_OK;
22875
24488
  }
22876
24489
 
22877
24490
  // src/editor/installation-prompt.ts
@@ -22953,12 +24566,12 @@ function registerServerCommand(program2, runtime) {
22953
24566
  }
22954
24567
 
22955
24568
  // src/commands/trace-command.ts
22956
- import { existsSync as existsSync12, readFileSync as readFileSync9 } from "node:fs";
24569
+ import { existsSync as existsSync13, readFileSync as readFileSync11 } from "node:fs";
22957
24570
  function defaultMapPath(root, options, reporter) {
22958
24571
  const loaded = loadManifest(root, { path: options.manifestPath, mode: manifestMode("trace"), env: options.env });
22959
24572
  if (loaded.config !== null) {
22960
24573
  const configured = resourceMapPath(root, loaded.config);
22961
- if (existsSync12(configured)) {
24574
+ if (existsSync13(configured)) {
22962
24575
  return configured;
22963
24576
  }
22964
24577
  }
@@ -22978,7 +24591,7 @@ function readStandardInput() {
22978
24591
  return "";
22979
24592
  }
22980
24593
  try {
22981
- return readFileSync9(0, "utf8");
24594
+ return readFileSync11(0, "utf8");
22982
24595
  } catch {
22983
24596
  return "";
22984
24597
  }
@@ -23012,30 +24625,30 @@ function runTraceCommand(root, reporter, options) {
23012
24625
  reporter.detail('A generated line cannot be resolved there. Reproduce the error under "luam dev" or "luam ensure" for a source position.');
23013
24626
  return EXIT_DIAGNOSTICS;
23014
24627
  }
23015
- let failed2 = false;
24628
+ let failed3 = false;
23016
24629
  for (const line2 of lines) {
23017
24630
  const generated = parseTracePosition(line2);
23018
24631
  if (generated === null) {
23019
24632
  reporter.error(`Trace could not find a file and line in "${line2}".`);
23020
- failed2 = true;
24633
+ failed3 = true;
23021
24634
  continue;
23022
24635
  }
23023
24636
  const file = generatedFile(loaded.map.resource, generated.file);
23024
24637
  const resolution = resolveResourcePosition(loaded.map, file, generated.line);
23025
24638
  if (resolution.status === "unknown-version") {
23026
24639
  reporter.error(`Resource map version ${resolution.version} is not supported.`);
23027
- failed2 = true;
24640
+ failed3 = true;
23028
24641
  continue;
23029
24642
  }
23030
24643
  if (resolution.status === "uncovered") {
23031
24644
  reporter.error(`Resource map does not cover "${generated.file}:${generated.line}".`);
23032
- failed2 = true;
24645
+ failed3 = true;
23033
24646
  continue;
23034
24647
  }
23035
24648
  const symbol = resolution.position.symbol === void 0 ? "" : ` (${resolution.position.symbol})`;
23036
24649
  reporter.raw(`${resolution.position.file}:${resolution.position.line}${symbol}`);
23037
24650
  }
23038
- return failed2 ? EXIT_DIAGNOSTICS : EXIT_OK;
24651
+ return failed3 ? EXIT_DIAGNOSTICS : EXIT_OK;
23039
24652
  }
23040
24653
 
23041
24654
  // src/cli/registry/trace-registration.ts
@@ -23056,6 +24669,7 @@ function registerTraceCommand(program2, runtime) {
23056
24669
  var COMMAND_REGISTRARS = [
23057
24670
  registerBuildCommand,
23058
24671
  registerCheckCommand,
24672
+ registerConfigCommand,
23059
24673
  registerDevCommand,
23060
24674
  registerDoctorCommand,
23061
24675
  registerEnsureCommand,