@thigasdevelopment/luam 0.19.1 → 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;
@@ -4595,10 +4638,40 @@ function parseClassMember(stream) {
4595
4638
  expectClassFieldBoundary(stream, token);
4596
4639
  return { kind: "class-field", name: token.value, annotation, value, decorators, isStatic, position: token.position };
4597
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;
4669
+ }
4598
4670
  function parseClassModifiers(stream, declaration) {
4599
4671
  while (stream.check("keyword") && CLASS_MODIFIERS.has(stream.current().value)) {
4600
4672
  if (stream.next().value === "extends") {
4601
4673
  declaration.superClass = stream.expect("identifier").value;
4674
+ declaration.superClassArguments = parseTypeArguments(stream);
4602
4675
  continue;
4603
4676
  }
4604
4677
  do {
@@ -4609,7 +4682,19 @@ function parseClassModifiers(stream, declaration) {
4609
4682
  function parseClassDeclaration(stream, decorators) {
4610
4683
  const position2 = stream.next().position;
4611
4684
  const name = stream.expect("identifier").value;
4612
- 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
+ };
4613
4698
  parseClassModifiers(stream, declaration);
4614
4699
  stream.expect("punctuation", "{");
4615
4700
  while (!stream.check("punctuation", "}") && !stream.isEof()) {
@@ -5069,8 +5154,8 @@ var TABLE_TYPE = { kind: "table" };
5069
5154
  function createArray(element) {
5070
5155
  return { kind: "array", element };
5071
5156
  }
5072
- function createNamed(name) {
5073
- return { kind: "named", name };
5157
+ function createNamed(name, typeArguments = []) {
5158
+ return typeArguments.length === 0 ? { kind: "named", name } : { kind: "named", name, typeArguments };
5074
5159
  }
5075
5160
  function createMap(key, value) {
5076
5161
  return { kind: "map", key, value };
@@ -5183,6 +5268,12 @@ function createUnion(options) {
5183
5268
  }
5184
5269
  return { kind: "union", options: flattened };
5185
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
+ }
5186
5277
  function typeToString(type) {
5187
5278
  if (type.kind === "array") {
5188
5279
  return `${typeToString(type.element)}[]`;
@@ -5202,7 +5293,11 @@ function typeToString(type) {
5202
5293
  if (type.kind === "boolean-literal" || type.kind === "number-literal") {
5203
5294
  return String(type.value);
5204
5295
  }
5205
- 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") {
5206
5301
  return type.name;
5207
5302
  }
5208
5303
  if (type.kind === "tuple") {
@@ -5292,6 +5387,59 @@ function isRecordAssignable(source, target, options) {
5292
5387
  }
5293
5388
  return true;
5294
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
+ }
5295
5443
  function isAssignable(source, target, options = { allowNil: false }) {
5296
5444
  if (source.kind === "any" || target.kind === "any" || target.kind === "unknown") {
5297
5445
  return true;
@@ -5299,9 +5447,6 @@ function isAssignable(source, target, options = { allowNil: false }) {
5299
5447
  if (source.kind === "tuple" || target.kind === "tuple") {
5300
5448
  return isAssignable(firstValueOf(source), firstValueOf(target), options);
5301
5449
  }
5302
- if (source.kind === "named" || target.kind === "named") {
5303
- return true;
5304
- }
5305
5450
  if (source.kind === "nil" && (options.allowNil || target.kind === "optional" || target.kind === "nil")) {
5306
5451
  return true;
5307
5452
  }
@@ -5320,6 +5465,15 @@ function isAssignable(source, target, options = { allowNil: false }) {
5320
5465
  if (target.kind === "optional") {
5321
5466
  return isAssignable(source, target.element, options);
5322
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
+ }
5323
5477
  if (target.kind === "table") {
5324
5478
  return isTableLike(source) || source.kind === "record";
5325
5479
  }
@@ -5458,7 +5612,8 @@ var RUNTIME_HELPERS = {
5458
5612
  math: { name: "math", file: "math.lua", injection: "automatic", features: ["number-extension"] },
5459
5613
  string: { name: "string", file: "string.lua", injection: "automatic", features: ["string-extension", "template-string"] },
5460
5614
  table: { name: "table", file: "table.lua", injection: "automatic", features: ["table-extension"] },
5461
- 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"] }
5462
5617
  };
5463
5618
  var DEVELOPMENT_RUNTIME_HELPERS = {
5464
5619
  "development-logs-client": {
@@ -5599,6 +5754,11 @@ var MANIFEST_FIELDS = [
5599
5754
  owner: "dependencies",
5600
5755
  allowEmpty: true
5601
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
+ }),
5602
5762
  table("engine", "Requirements the MTA server and client must meet.", ENGINE_FIELDS, { defaultValue: {}, owner: "engine" }),
5603
5763
  table("environment", "Files that declare and override the project environment.", ENVIRONMENT_FIELDS, { defaultValue: {}, owner: "environment" }),
5604
5764
  field("outDir", STRING_TYPE, "Directory the built resource is written to.", { defaultValue: DEFAULT_OUT_DIR, rule: "static-path", owner: "output" }),
@@ -5758,8 +5918,8 @@ function isNumber(type) {
5758
5918
  function isText(type) {
5759
5919
  return type.kind === "string" || type.kind === "string-literal" || type.kind === "any";
5760
5920
  }
5761
- function fail(operator, expected, left, right, describe) {
5762
- 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);
5763
5923
  return { evaluated: null, error: `"${operator}" expects ${expected} on both sides but received ${culprit}.` };
5764
5924
  }
5765
5925
  function arithmetic(operator, left, right) {
@@ -5798,22 +5958,22 @@ function applyOr(left, right) {
5798
5958
  const value = isTruthy(left.value) ? left.value : right.value;
5799
5959
  return { value, type: unionOf([left.truthy, right.type]), truthy: unionOf([left.truthy, right.truthy]), falsy: right.falsy };
5800
5960
  }
5801
- function applyOrdering(operator, left, right, describe) {
5961
+ function applyOrdering(operator, left, right, describe2) {
5802
5962
  const numeric = isNumber(left.type) && isNumber(right.type);
5803
5963
  const textual = isText(left.type) && isText(right.type);
5804
5964
  if (!numeric && !textual) {
5805
- 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);
5806
5966
  }
5807
5967
  return { evaluated: booleanValue(compare(operator, left.value, right.value)), error: null };
5808
5968
  }
5809
- function applyConcat(left, right, describe) {
5969
+ function applyConcat(left, right, describe2) {
5810
5970
  const usable = (type) => isText(type) || isNumber(type);
5811
5971
  if (!usable(left.type) || !usable(right.type)) {
5812
- 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);
5813
5973
  }
5814
5974
  return { evaluated: stringValue(`${left.value}${right.value}`), error: null };
5815
5975
  }
5816
- function applyBinary(operator, left, right, describe) {
5976
+ function applyBinary(operator, left, right, describe2) {
5817
5977
  if (operator === "and") {
5818
5978
  return { evaluated: applyAnd(left, right), error: null };
5819
5979
  }
@@ -5825,23 +5985,23 @@ function applyBinary(operator, left, right, describe) {
5825
5985
  return { evaluated: booleanValue(operator === "==" ? same : !same), error: null };
5826
5986
  }
5827
5987
  if (ORDERING.includes(operator)) {
5828
- return applyOrdering(operator, left, right, describe);
5988
+ return applyOrdering(operator, left, right, describe2);
5829
5989
  }
5830
5990
  if (operator === "..") {
5831
- return applyConcat(left, right, describe);
5991
+ return applyConcat(left, right, describe2);
5832
5992
  }
5833
5993
  if (!isNumber(left.type) || !isNumber(right.type)) {
5834
- return fail(operator, "a number", left.type, right.type, describe);
5994
+ return fail(operator, "a number", left.type, right.type, describe2);
5835
5995
  }
5836
5996
  return { evaluated: numberValue(arithmetic(operator, left.value, right.value)), error: null };
5837
5997
  }
5838
- function applyUnary(operator, operand, describe) {
5998
+ function applyUnary(operator, operand, describe2) {
5839
5999
  if (operator === "not") {
5840
6000
  return { evaluated: booleanValue(!isTruthy(operand.value)), error: null };
5841
6001
  }
5842
6002
  if (operator === "-") {
5843
6003
  if (!isNumber(operand.type)) {
5844
- 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)}.` };
5845
6005
  }
5846
6006
  return { evaluated: numberValue(-operand.value), error: null };
5847
6007
  }
@@ -6300,8 +6460,8 @@ var RuleWalk = class {
6300
6460
  }
6301
6461
  };
6302
6462
  function normalizeManifest(value, positions) {
6303
- const walk3 = new RuleWalk(positions);
6304
- 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 };
6305
6465
  }
6306
6466
 
6307
6467
  // ../compiler/src/manifest/manifest-analysis.ts
@@ -6537,6 +6697,7 @@ function validateConfig(value, positions) {
6537
6697
  sources: readSourceMapping(value),
6538
6698
  assets: readAssetMappings(value),
6539
6699
  dependencies,
6700
+ contracts: readString(value, "contracts") ?? "",
6540
6701
  engine: readEngine(value),
6541
6702
  environment: readEnvironmentFiles(value),
6542
6703
  outDir: readString(value, "outDir") ?? "",
@@ -6601,7 +6762,7 @@ import { tmpdir } from "node:os";
6601
6762
  import { join as join2 } from "node:path";
6602
6763
 
6603
6764
  // src/cli/version.ts
6604
- var VERSION = true ? "0.19.1" : "0.0.0-dev";
6765
+ var VERSION = true ? "0.19.2" : "0.0.0-dev";
6605
6766
  var PROGRAM_NAME = "luam";
6606
6767
  var PROGRAM_DESCRIPTION = "luam \u2014 the Luam compiler for Multi Theft Auto resources.";
6607
6768
 
@@ -6786,9 +6947,9 @@ function pluralize(count, singular, plural = `${singular}s`) {
6786
6947
  var MAX_ENTRIES_PER_FILE = 10;
6787
6948
  var MAX_FILES = 10;
6788
6949
  var GUTTER_WIDTH = 6;
6789
- function groupByFile(entries2) {
6950
+ function groupByFile(entries3) {
6790
6951
  const groups = /* @__PURE__ */ new Map();
6791
- for (const entry of entries2) {
6952
+ for (const entry of entries3) {
6792
6953
  const group = groups.get(entry.path);
6793
6954
  if (group === void 0) {
6794
6955
  groups.set(entry.path, [entry]);
@@ -6835,19 +6996,19 @@ function emit(reporter, entry, block) {
6835
6996
  }
6836
6997
  reporter.rawWarn(block);
6837
6998
  }
6838
- function reportGroup(reporter, path, entries2, options) {
6999
+ function reportGroup(reporter, path, entries3, options) {
6839
7000
  const limit = options.maxPerFile ?? MAX_ENTRIES_PER_FILE;
6840
7001
  const source = options.sources.get(path);
6841
7002
  reporter.raw(reporter.style.paint("strong", path));
6842
- for (const entry of entries2.slice(0, limit)) {
7003
+ for (const entry of entries3.slice(0, limit)) {
6843
7004
  emit(reporter, entry, entryBlock(reporter, entry, source));
6844
7005
  }
6845
- if (entries2.length > limit) {
6846
- 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.`);
6847
7008
  }
6848
7009
  }
6849
- function reportGroupedDiagnostics(reporter, entries2, options) {
6850
- const groups = [...groupByFile(entries2)];
7010
+ function reportGroupedDiagnostics(reporter, entries3, options) {
7011
+ const groups = [...groupByFile(entries3)];
6851
7012
  const limit = options.maxFiles ?? MAX_FILES;
6852
7013
  for (const [path, group] of groups.slice(0, limit)) {
6853
7014
  reportGroup(reporter, path, group, options);
@@ -6863,9 +7024,9 @@ function formatFileDiagnostic(entry) {
6863
7024
  const location = `${path}:${diagnostic.position.line}:${diagnostic.position.column}`;
6864
7025
  return `${location} ${diagnostic.severity} ${diagnostic.code}: ${diagnostic.message}`;
6865
7026
  }
6866
- function countFileDiagnostics(entries2) {
6867
- const errors = entries2.filter((entry) => entry.diagnostic.severity === "error").length;
6868
- 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 };
6869
7030
  }
6870
7031
  function countCliDiagnostics(diagnostics) {
6871
7032
  const errors = diagnostics.filter((diagnostic) => diagnostic.severity === "error").length;
@@ -6875,8 +7036,8 @@ function reportManifestDiagnostics(reporter, path, source, diagnostics) {
6875
7036
  if (diagnostics.length === 0) {
6876
7037
  return;
6877
7038
  }
6878
- const entries2 = diagnostics.map((diagnostic) => ({ path, diagnostic }));
6879
- 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]]));
6880
7041
  }
6881
7042
  function reportCliDiagnostics(reporter, diagnostics) {
6882
7043
  for (const diagnostic of diagnostics) {
@@ -6888,8 +7049,8 @@ function reportCliDiagnostics(reporter, diagnostics) {
6888
7049
  reporter.warn(line2);
6889
7050
  }
6890
7051
  }
6891
- function reportPlainFileDiagnostics(reporter, entries2) {
6892
- for (const entry of entries2) {
7052
+ function reportPlainFileDiagnostics(reporter, entries3) {
7053
+ for (const entry of entries3) {
6893
7054
  const line2 = formatFileDiagnostic(entry);
6894
7055
  if (entry.diagnostic.severity === "error") {
6895
7056
  reporter.rawError(line2);
@@ -6898,12 +7059,12 @@ function reportPlainFileDiagnostics(reporter, entries2) {
6898
7059
  reporter.rawWarn(line2);
6899
7060
  }
6900
7061
  }
6901
- function reportFileDiagnostics(reporter, entries2, sources) {
7062
+ function reportFileDiagnostics(reporter, entries3, sources) {
6902
7063
  if (!reporter.capability.interactive) {
6903
- reportPlainFileDiagnostics(reporter, entries2);
7064
+ reportPlainFileDiagnostics(reporter, entries3);
6904
7065
  return;
6905
7066
  }
6906
- reportGroupedDiagnostics(reporter, entries2, { sources });
7067
+ reportGroupedDiagnostics(reporter, entries3, { sources });
6907
7068
  }
6908
7069
  function formatCounts(counts) {
6909
7070
  return `${pluralize(counts.errors, "error")}, ${pluralize(counts.warnings, "warning")}`;
@@ -7154,8 +7315,153 @@ function addProjectOptions(command) {
7154
7315
  return command.addOption(cwdOption()).addOption(manifestOption()).addOption(colorOption());
7155
7316
  }
7156
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
+
7157
7463
  // src/build/helper-files.ts
7158
- import { existsSync as existsSync3, readFileSync as readFileSync3 } from "node:fs";
7464
+ import { existsSync as existsSync3, readFileSync as readFileSync4 } from "node:fs";
7159
7465
  import { fileURLToPath } from "node:url";
7160
7466
  function bundledHelperPath(file) {
7161
7467
  return fileURLToPath(new URL(`./lua/${file}`, import.meta.url));
@@ -7167,7 +7473,7 @@ function resolveHelperPath(helper, file) {
7167
7473
  return existsSync3(packaged) ? packaged : bundledHelperPath(file);
7168
7474
  }
7169
7475
  function readHelperSource(helper, file) {
7170
- return readFileSync3(resolveHelperPath(helper, file), "utf8");
7476
+ return readFileSync4(resolveHelperPath(helper, file), "utf8");
7171
7477
  }
7172
7478
 
7173
7479
  // src/build/build-phase.ts
@@ -7236,16 +7542,16 @@ function createPhaseTracker(listener = null) {
7236
7542
  }
7237
7543
 
7238
7544
  // src/build/project-inputs.ts
7239
- import { existsSync as existsSync4, readFileSync as readFileSync4 } from "node:fs";
7240
- 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";
7241
7547
 
7242
7548
  // src/build/asset-resolution.ts
7243
7549
  import { statSync as statSync3 } from "node:fs";
7244
- import { resolve as resolve5 } from "node:path";
7550
+ import { resolve as resolve6 } from "node:path";
7245
7551
 
7246
7552
  // src/build/project-files.ts
7247
7553
  import { readdirSync, statSync as statSync2 } from "node:fs";
7248
- import { join as join3, relative, resolve as resolve4 } from "node:path";
7554
+ import { join as join3, relative, resolve as resolve5 } from "node:path";
7249
7555
  function isDirectory(path) {
7250
7556
  try {
7251
7557
  return statSync2(path).isDirectory();
@@ -7257,14 +7563,14 @@ function relativePath(root, absolute) {
7257
7563
  return normalizePattern(relative(root, absolute));
7258
7564
  }
7259
7565
  function walk(root, directory, excluded, tree) {
7260
- let entries2;
7566
+ let entries3;
7261
7567
  try {
7262
- entries2 = readdirSync(directory, { withFileTypes: true });
7568
+ entries3 = readdirSync(directory, { withFileTypes: true });
7263
7569
  } catch (error) {
7264
7570
  tree.errors.push(`"${relativePath(root, directory)}" could not be read: ${error instanceof Error ? error.message : String(error)}`);
7265
7571
  return;
7266
7572
  }
7267
- for (const entry of entries2) {
7573
+ for (const entry of entries3) {
7268
7574
  const absolute = join3(directory, entry.name);
7269
7575
  const path = relativePath(root, absolute);
7270
7576
  if (path.length === 0 || isExcludedPath(path, excluded)) {
@@ -7281,7 +7587,7 @@ function listProjectFiles(root, roots, excluded = []) {
7281
7587
  const tree = { files: [], errors: [] };
7282
7588
  const seen = /* @__PURE__ */ new Set();
7283
7589
  for (const start of [...new Set(roots)].sort()) {
7284
- const absolute = resolve4(root, start);
7590
+ const absolute = resolve5(root, start);
7285
7591
  if (!isDirectory(absolute) || seen.has(absolute)) {
7286
7592
  continue;
7287
7593
  }
@@ -7425,9 +7731,23 @@ var OCCUPIED_SIDES = {
7425
7731
  function occupiedSides(contribution) {
7426
7732
  return OCCUPIED_SIDES[contribution.side];
7427
7733
  }
7428
- 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) {
7429
7742
  return directives.exports.map(
7430
- (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
+ })
7431
7751
  );
7432
7752
  }
7433
7753
  var INDENT = " ";
@@ -7461,8 +7781,8 @@ function attribute(name, value) {
7461
7781
  function textElement(name, value) {
7462
7782
  return `${INDENT}<${name}>${escapeXml(value)}</${name}>`;
7463
7783
  }
7464
- function section(text, entries2) {
7465
- return entries2.length === 0 ? [] : [`${INDENT}<!-- ${text} -->`, ...entries2];
7784
+ function section(text, entries3) {
7785
+ return entries3.length === 0 ? [] : [`${INDENT}<!-- ${text} -->`, ...entries3];
7466
7786
  }
7467
7787
  function infoElement(info) {
7468
7788
  const attributes = info.author === void 0 ? [] : [attribute("author", info.author)];
@@ -7590,12 +7910,12 @@ var MISSING_LOAD_ORDER = 'is listed in "loadOrder" but no source file or asset m
7590
7910
  function missingEntry(entry) {
7591
7911
  return { path: entry, diagnostic: createDiagnostic("project", "project-load-order-missing", `"${entry}" ${MISSING_LOAD_ORDER}`, FILE_START) };
7592
7912
  }
7593
- function resolveLoadOrder(entries2, scripts, assets) {
7913
+ function resolveLoadOrder(entries3, scripts, assets) {
7594
7914
  const byScript = new Map(scripts.map((script) => [normalizePath(script.source), script]));
7595
7915
  const byAsset = new Map(assets.map((asset) => [normalizePath(asset.source), asset]));
7596
7916
  const resolved2 = { scripts: [], assets: [], diagnostics: [] };
7597
7917
  const seen = /* @__PURE__ */ new Set();
7598
- for (const entry of entries2) {
7918
+ for (const entry of entries3) {
7599
7919
  const path = normalizePath(entry).replace(/^\.\//, "");
7600
7920
  const script = byScript.get(path);
7601
7921
  const asset = byAsset.get(path);
@@ -7671,11 +7991,11 @@ var ENTRY = /^([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)$/;
7671
7991
  var SENSITIVE = /(password|secret|token|key|credential|dsn|private)/i;
7672
7992
  var EMPTY_ENV_FILE = { entries: [], errors: [] };
7673
7993
  function unquote(raw) {
7674
- const quote2 = raw.slice(0, 1);
7675
- if (quote2 !== '"' && quote2 !== "'") {
7994
+ const quote3 = raw.slice(0, 1);
7995
+ if (quote3 !== '"' && quote3 !== "'") {
7676
7996
  return null;
7677
7997
  }
7678
- const closing = raw.lastIndexOf(quote2);
7998
+ const closing = raw.lastIndexOf(quote3);
7679
7999
  return closing > 0 ? raw.slice(1, closing) : null;
7680
8000
  }
7681
8001
  function stripComment(raw) {
@@ -7693,7 +8013,7 @@ function classify(raw) {
7693
8013
  return { value, kind: value.length > 0 && Number.isFinite(Number(value)) ? "number" : "string" };
7694
8014
  }
7695
8015
  function parseEnvFile(source) {
7696
- const entries2 = [];
8016
+ const entries3 = [];
7697
8017
  const errors = [];
7698
8018
  source.split(/\r?\n/).forEach((raw, index) => {
7699
8019
  const trimmed = raw.trim();
@@ -7707,9 +8027,9 @@ function parseEnvFile(source) {
7707
8027
  return;
7708
8028
  }
7709
8029
  const { value, kind } = classify(match[2]);
7710
- entries2.push({ key: match[1], value, kind, line: line2 });
8030
+ entries3.push({ key: match[1], value, kind, line: line2 });
7711
8031
  });
7712
- return { entries: entries2, errors };
8032
+ return { entries: entries3, errors };
7713
8033
  }
7714
8034
  function isSensitiveKey(key) {
7715
8035
  return SENSITIVE.test(key);
@@ -7875,14 +8195,14 @@ var MANIFEST_OUTPUT = "meta.xml";
7875
8195
  var HERE = ".";
7876
8196
  function isDirectory2(root, path) {
7877
8197
  try {
7878
- return statSync3(resolve5(root, path)).isDirectory();
8198
+ return statSync3(resolve6(root, path)).isDirectory();
7879
8199
  } catch {
7880
8200
  return false;
7881
8201
  }
7882
8202
  }
7883
8203
  function exists(root, path) {
7884
8204
  try {
7885
- statSync3(resolve5(root, path));
8205
+ statSync3(resolve6(root, path));
7886
8206
  return true;
7887
8207
  } catch {
7888
8208
  return false;
@@ -7950,10 +8270,10 @@ var CONFIGURATION_FILE = "config.lua";
7950
8270
  var MALFORMED_ENV = "build-env-malformed";
7951
8271
  var MISSING_ENV = "config-missing-env-file";
7952
8272
  function readTextFile(path) {
7953
- return existsSync4(path) ? readFileSync4(path, "utf8") : null;
8273
+ return existsSync4(path) ? readFileSync5(path, "utf8") : null;
7954
8274
  }
7955
8275
  function readEnvironment(root, file, required, diagnostics) {
7956
- const source = readTextFile(resolve6(root, file));
8276
+ const source = readTextFile(resolve7(root, file));
7957
8277
  if (source === null) {
7958
8278
  if (required) {
7959
8279
  diagnostics.push(cliError(MISSING_ENV, `"${file}" is configured under "environment" but does not exist. Create it or remove the setting.`));
@@ -7967,7 +8287,7 @@ function readEnvironment(root, file, required, diagnostics) {
7967
8287
  return parsed;
7968
8288
  }
7969
8289
  function readConfiguration(root, diagnostics) {
7970
- const content = readTextFile(resolve6(root, CONFIGURATION_FILE));
8290
+ const content = readTextFile(resolve7(root, CONFIGURATION_FILE));
7971
8291
  if (content === null) {
7972
8292
  return null;
7973
8293
  }
@@ -7986,14 +8306,14 @@ function readProjectInputs(root, options) {
7986
8306
  }
7987
8307
 
7988
8308
  // src/build/source-discovery.ts
7989
- import { readFileSync as readFileSync5 } from "node:fs";
7990
- import { resolve as resolve7 } from "node:path";
8309
+ import { readFileSync as readFileSync6 } from "node:fs";
8310
+ import { resolve as resolve8 } from "node:path";
7991
8311
 
7992
8312
  // ../compiler/src/project/source-mapping.ts
7993
8313
  function createSourceResolver(mapping) {
7994
8314
  const matchers = new Map(SOURCE_SIDES.map((environment) => [environment, createPatternMatcher(mapping[environment])]));
7995
8315
  const patterns = SOURCE_SIDES.flatMap((environment) => [...matchers.get(environment)?.patterns ?? []]);
7996
- function resolve17(path) {
8316
+ function resolve19(path) {
7997
8317
  const normalized = normalizePattern(path);
7998
8318
  const matches = [];
7999
8319
  for (const environment of SOURCE_SIDES) {
@@ -8007,8 +8327,8 @@ function createSourceResolver(mapping) {
8007
8327
  return {
8008
8328
  patterns,
8009
8329
  roots: watchRoots(patterns),
8010
- resolve: resolve17,
8011
- side: (path) => resolve17(path).environment
8330
+ resolve: resolve19,
8331
+ side: (path) => resolve19(path).environment
8012
8332
  };
8013
8333
  }
8014
8334
  function describeMatches(matches) {
@@ -8040,7 +8360,7 @@ function checkLiterals(root, resolver, found, diagnostics) {
8040
8360
  }
8041
8361
  function readSource(root, path, diagnostics) {
8042
8362
  try {
8043
- return readFileSync5(resolve7(root, path), "utf8");
8363
+ return readFileSync6(resolve8(root, path), "utf8");
8044
8364
  } catch (error) {
8045
8365
  diagnostics.push(cliError(UNREADABLE_SOURCE, `The source file "${path}" could not be read: ${error instanceof Error ? error.message : String(error)}`));
8046
8366
  return null;
@@ -8120,25 +8440,26 @@ function fn(parameters, returnType, minimumArguments2, isVariadic = false, param
8120
8440
  var EMPTY_PROJECT_DECLARATIONS = { globals: [] };
8121
8441
  var ENV_GLOBAL = "env";
8122
8442
  var VALUE_TYPES = { boolean: BOOLEAN, number: NUMBER, string: STRING };
8123
- function envMembers(entries2) {
8443
+ function envMembers(entries3) {
8124
8444
  const members = /* @__PURE__ */ new Map();
8125
- for (const entry of entries2) {
8445
+ for (const entry of entries3) {
8126
8446
  members.set(entry.key, { name: entry.key, type: VALUE_TYPES[entry.kind] });
8127
8447
  }
8128
8448
  return [...members.values()].sort((left, right) => left.name.localeCompare(right.name));
8129
8449
  }
8130
- function envDeclaration(entries2, origin) {
8131
- const env = record(ENV_GLOBAL, envMembers(entries2), origin);
8450
+ function envDeclaration(entries3, origin) {
8451
+ const env = record(ENV_GLOBAL, envMembers(entries3), origin);
8132
8452
  return { name: ENV_GLOBAL, environment: "server", source: "project", type: env };
8133
8453
  }
8134
- function projectDeclarations(entries2, origin) {
8135
- if (entries2 === null) {
8454
+ function projectDeclarations(entries3, origin) {
8455
+ if (entries3 === null) {
8136
8456
  return EMPTY_PROJECT_DECLARATIONS;
8137
8457
  }
8138
- return { globals: [envDeclaration(entries2, origin)] };
8458
+ return { globals: [envDeclaration(entries3, origin)] };
8139
8459
  }
8140
8460
 
8141
8461
  // ../compiler/src/checker/ambient.ts
8462
+ var EVENT_NAME_PREFIX = "event:";
8142
8463
  var EMPTY_AMBIENT = { classes: [], interfaces: [], enums: [], globals: [], events: [] };
8143
8464
  function ambientFromRegistry(registry) {
8144
8465
  return {
@@ -8149,9 +8470,9 @@ function ambientFromRegistry(registry) {
8149
8470
  events: registry.allEvents()
8150
8471
  };
8151
8472
  }
8152
- function withoutNames(entries2, excluded) {
8473
+ function withoutNames(entries3, excluded) {
8153
8474
  const names = new Set(excluded.map((entry) => entry.name));
8154
- return entries2.filter((entry) => !names.has(entry.name));
8475
+ return entries3.filter((entry) => !names.has(entry.name));
8155
8476
  }
8156
8477
  function ownDeclarations(registry, ambient) {
8157
8478
  const all = ambientFromRegistry(registry);
@@ -8328,6 +8649,9 @@ var Binder = class {
8328
8649
  declaredSymbols() {
8329
8650
  return [...this.discarded, ...this.scopes.flatMap((scope) => [...scope.values()])];
8330
8651
  }
8652
+ currentScopeNames() {
8653
+ return [...this.scopes[this.scopes.length - 1]?.keys() ?? []];
8654
+ }
8331
8655
  declare(symbol) {
8332
8656
  const scope = this.scopes[this.scopes.length - 1];
8333
8657
  scope?.set(symbol.name, symbol);
@@ -8366,6 +8690,211 @@ var Binder = class {
8366
8690
  }
8367
8691
  };
8368
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
+
8369
8898
  // ../mta-types/src/lua-standard.ts
8370
8899
  var LUA_GLOBALS = {
8371
8900
  _G: TABLE,
@@ -13203,6 +13732,18 @@ var DeclarationRegistry = class {
13203
13732
  }
13204
13733
  return null;
13205
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
+ }
13206
13747
  lookupStaticMember(name, member) {
13207
13748
  const visited = /* @__PURE__ */ new Set();
13208
13749
  let current = this.lookupClass(name);
@@ -13262,7 +13803,17 @@ function buildRegistry() {
13262
13803
  procedural: member.procedural
13263
13804
  });
13264
13805
  }
13265
- registry.declareClass({ name: declaration.name, superClass: declaration.parent, interfaces: [], members, statics: /* @__PURE__ */ new Map(), 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
+ });
13266
13817
  }
13267
13818
  return registry;
13268
13819
  }
@@ -13349,12 +13900,22 @@ function missingKeys(source, target) {
13349
13900
  }
13350
13901
  return missing2;
13351
13902
  }
13352
- 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) {
13353
13914
  if (source.kind !== "record" || source.isLiteral !== true) {
13354
13915
  return "";
13355
13916
  }
13356
13917
  const options = target.kind === "union" ? target.options : [target];
13357
- const candidates3 = options.filter((option) => option.kind === "record");
13918
+ const candidates3 = options.map((option) => expandCandidate(option, resolveNominal)).filter((option) => option !== null);
13358
13919
  const candidate = chooseCandidate(source, candidates3);
13359
13920
  if (candidate === null) {
13360
13921
  return "";
@@ -13402,39 +13963,6 @@ function mergeIntersection(context, parts, position2) {
13402
13963
  return createObjectType(members);
13403
13964
  }
13404
13965
 
13405
- // ../compiler/src/checker/type-substitution.ts
13406
- function substituteType(type, substitutions) {
13407
- if (type.kind === "named") {
13408
- return substitutions.get(type.name) ?? type;
13409
- }
13410
- if (type.kind === "array") {
13411
- return createArray(substituteType(type.element, substitutions));
13412
- }
13413
- if (type.kind === "map") {
13414
- return createMap(substituteType(type.key, substitutions), substituteType(type.value, substitutions));
13415
- }
13416
- if (type.kind === "optional") {
13417
- return createOptional(substituteType(type.element, substitutions));
13418
- }
13419
- if (type.kind === "union") {
13420
- return createUnion(type.options.map((option) => substituteType(option, substitutions)));
13421
- }
13422
- if (type.kind === "function") {
13423
- const parameters = type.parameters.map((parameter) => substituteType(parameter, substitutions));
13424
- const returnType = substituteType(type.returnType, substitutions);
13425
- const variadicType = type.variadicType === void 0 ? void 0 : substituteType(type.variadicType, substitutions);
13426
- return createFunction(parameters, returnType, type.minimumArguments, type.isVariadic, type.parameterNames, variadicType);
13427
- }
13428
- if (type.kind === "tuple") {
13429
- return createTuple(type.elements.map((element) => substituteType(element, substitutions)));
13430
- }
13431
- if (type.kind === "record") {
13432
- const members = new Map([...type.members].map(([name, member]) => [name, substituteType(member, substitutions)]));
13433
- return createRecord(type.name, members, type.origin);
13434
- }
13435
- return type;
13436
- }
13437
-
13438
13966
  // ../compiler/src/checker/context.ts
13439
13967
  var BUILTIN_TYPES = {
13440
13968
  any: ANY_TYPE,
@@ -13450,6 +13978,7 @@ var BUILTIN_TYPES = {
13450
13978
  };
13451
13979
  var TABLE_ANNOTATION = "table";
13452
13980
  var MAP_ARGUMENTS = 2;
13981
+ var MAX_GENERIC_DEPTH = 8;
13453
13982
  var PROJECT_POSITION = { line: 0, column: 0, offset: 0 };
13454
13983
  function nilHint(source, target, mode) {
13455
13984
  if (mode !== "strict" || source.kind !== "nil" || target.kind === "nil" || target.kind === "optional") {
@@ -13462,12 +13991,15 @@ var CheckContext = class {
13462
13991
  declarations = new DeclarationRegistry();
13463
13992
  diagnostics = [];
13464
13993
  reported = /* @__PURE__ */ new Set();
13465
- narrowings = [];
13994
+ flow = createFlow();
13466
13995
  types = /* @__PURE__ */ new Map();
13467
13996
  references = /* @__PURE__ */ new Set();
13468
13997
  declaredGlobals = /* @__PURE__ */ new Map();
13998
+ moduleGlobals = /* @__PURE__ */ new Map();
13469
13999
  externalReferences = /* @__PURE__ */ new Map();
13470
14000
  unknownTypes = /* @__PURE__ */ new Map();
14001
+ eventReferences = /* @__PURE__ */ new Set();
14002
+ globalReferences = /* @__PURE__ */ new Set();
13471
14003
  calledMembers = /* @__PURE__ */ new Set();
13472
14004
  staticAccess = /* @__PURE__ */ new Set();
13473
14005
  typeParameters = /* @__PURE__ */ new Set();
@@ -13475,6 +14007,7 @@ var CheckContext = class {
13475
14007
  mode;
13476
14008
  environment;
13477
14009
  mtaClasses;
14010
+ contracts = [];
13478
14011
  isDeclarationFile = false;
13479
14012
  returnStack = [];
13480
14013
  methodStack = [];
@@ -13500,33 +14033,28 @@ var CheckContext = class {
13500
14033
  declareModuleGlobal(symbol) {
13501
14034
  this.binder.declareGlobal(symbol);
13502
14035
  this.declaredGlobals.set(symbol.name, symbol.position);
14036
+ this.moduleGlobals.set(symbol.name, symbol.type);
13503
14037
  }
13504
- pushNarrowing(facts) {
13505
- this.narrowings.push(new Map(facts));
14038
+ get flowState() {
14039
+ return this.flow;
13506
14040
  }
13507
- get isNarrowed() {
13508
- return this.narrowings.length > 0;
14041
+ setFlow(state) {
14042
+ this.flow = state;
13509
14043
  }
13510
- popNarrowing() {
13511
- 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;
13512
14052
  }
13513
14053
  forgetNarrowing(path) {
13514
- for (const frame of this.narrowings) {
13515
- for (const key of [...frame.keys()]) {
13516
- if (key === path || key.startsWith(`${path}.`) || path.startsWith(`${key}.`)) {
13517
- frame.delete(key);
13518
- }
13519
- }
13520
- }
14054
+ forgetPath(this.flow, path);
13521
14055
  }
13522
14056
  narrowedType(path) {
13523
- for (let index = this.narrowings.length - 1; index >= 0; index -= 1) {
13524
- const found = this.narrowings[index]?.get(path);
13525
- if (found !== void 0) {
13526
- return found;
13527
- }
13528
- }
13529
- return null;
14057
+ return this.flow.facts.get(path) ?? null;
13530
14058
  }
13531
14059
  predeclareClass(info) {
13532
14060
  this.predeclared.set(info.name, info);
@@ -13593,6 +14121,12 @@ var CheckContext = class {
13593
14121
  isTypeParameter(name) {
13594
14122
  return this.typeParameters.has(name);
13595
14123
  }
14124
+ noteGlobalReference(name) {
14125
+ this.globalReferences.add(name);
14126
+ }
14127
+ noteEventReference(name) {
14128
+ this.eventReferences.add(name);
14129
+ }
13596
14130
  noteUnknownType(name, position2) {
13597
14131
  if (!this.unknownTypes.has(name)) {
13598
14132
  this.unknownTypes.set(name, position2);
@@ -13682,17 +14216,57 @@ var CheckContext = class {
13682
14216
  const parameters = annotation.parameters.map((parameter) => this.resolveAnnotation(parameter));
13683
14217
  const optional = parameters.findIndex((parameter) => parameter.kind === "optional");
13684
14218
  const minimum = optional === -1 ? parameters.length : optional;
13685
- return createFunction(parameters, this.resolveAnnotation(annotation.returnType), minimum, annotation.isVariadic);
14219
+ return createFunction(parameters, this.resolveAnnotation(annotation.returnType), minimum, annotation.isVariadic, [...annotation.parameterNames]);
13686
14220
  }
13687
14221
  return this.resolveNamedAnnotation(annotation.name, annotation.typeArguments, annotation.position);
13688
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
+ }
13689
14263
  expectAssignable(source, target, position2, subject) {
13690
- if (isAssignable(source, target, { allowNil: this.allowNil })) {
14264
+ if (isAssignable(source, target, this.assignability())) {
13691
14265
  return;
13692
14266
  }
13693
14267
  const received = isLiteralType(target) || target.kind === "union" ? source : widenLiteral(source);
13694
14268
  const message = `${subject} expects "${typeToString(target)}" but received "${typeToString(received)}".`;
13695
- 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))}`;
13696
14270
  this.report("check-type-mismatch", `${message}${hint}`, position2);
13697
14271
  }
13698
14272
  declareProject(project) {
@@ -13738,6 +14312,25 @@ var CheckContext = class {
13738
14312
  }
13739
14313
  return createMap(this.resolveAnnotation(key), this.resolveAnnotation(value));
13740
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
+ }
13741
14334
  resolveNamedAnnotation(name, typeArguments, position2) {
13742
14335
  const builtin = BUILTIN_TYPES[name];
13743
14336
  if (builtin !== void 0) {
@@ -13752,7 +14345,7 @@ var CheckContext = class {
13752
14345
  const alias = this.binder.lookupAlias(name);
13753
14346
  if (alias === null) {
13754
14347
  this.noteUnknownType(name, position2);
13755
- return createNamed(name);
14348
+ return this.resolveClassAnnotation(name, typeArguments, position2);
13756
14349
  }
13757
14350
  if (typeArguments.length !== alias.parameters.length) {
13758
14351
  const expected = alias.parameters.length === 1 ? "1 type argument" : `${alias.parameters.length} type arguments`;
@@ -14009,6 +14602,73 @@ function checkOverrides(context, info, statement) {
14009
14602
  }
14010
14603
  }
14011
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
+
14012
14672
  // ../compiler/src/checker/accessor-names.ts
14013
14673
  function capitalize(name) {
14014
14674
  return `${name.charAt(0).toUpperCase()}${name.slice(1)}`;
@@ -14093,6 +14753,25 @@ var KNOWN_DECORATORS = /* @__PURE__ */ new Map([
14093
14753
  diagnostics: [CONFLICT]
14094
14754
  }
14095
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
+ ],
14096
14775
  [
14097
14776
  "Serializable",
14098
14777
  {
@@ -14183,6 +14862,102 @@ var KNOWN_DECORATORS = /* @__PURE__ */ new Map([
14183
14862
  ]
14184
14863
  ]);
14185
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
+
14186
14961
  // ../compiler/src/checker/decorators.ts
14187
14962
  function memberExpression(field2) {
14188
14963
  return {
@@ -14248,6 +15023,46 @@ function validateDecorators(context, decorators, target) {
14248
15023
  function decoratorFor(name, classDecorators, fieldDecorators) {
14249
15024
  return fieldDecorators.find((decorator) => decorator.name === name) ?? classDecorators.find((decorator) => decorator.name === name) ?? null;
14250
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
+ }
14251
15066
  function expandClassDecorators(context, statement, fieldTypes) {
14252
15067
  const generated = [];
14253
15068
  const occupied = new Map(statement.members.filter((member) => member.kind === "class-method").map((member) => [member.name, member.name]));
@@ -14309,6 +15124,7 @@ function expandClassDecorators(context, statement, fieldTypes) {
14309
15124
  Serializable: ["toTable", typeName("table", statement), "serializable"],
14310
15125
  Deserialize: ["fromTable", typeName("void", statement), "deserialize"]
14311
15126
  };
15127
+ expandValidated(context, statement, classDecorators, fieldTypes, occupied, generated);
14312
15128
  for (const [decorator, [name, returnAnnotation, kind]] of Object.entries(classMethods)) {
14313
15129
  const found = classDecorators.find((value) => value.name === decorator);
14314
15130
  if (found !== void 0 && !occupied.has(name)) {
@@ -17567,6 +18383,9 @@ function checkEventUsage(context, expression) {
17567
18383
  return;
17568
18384
  }
17569
18385
  const name = eventName(expression);
18386
+ if (name !== null) {
18387
+ context.noteEventReference(name);
18388
+ }
17570
18389
  const custom = name === null ? null : context.declarations.lookupEvent(name);
17571
18390
  const declared = name === null ? null : eventEnvironment(name);
17572
18391
  if (name === null || custom?.environment === "shared" || custom?.environment === context.environment || declared === null || declared === context.environment) {
@@ -17709,6 +18528,105 @@ function specializeEventCall(context, expression, signature) {
17709
18528
  return candidate === null ? null : specialize(signature, candidate);
17710
18529
  }
17711
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
+
17712
18630
  // ../mta-types/src/library-members.ts
17713
18631
  var MATH_MEMBERS = {
17714
18632
  abs: fn([NUMBER], NUMBER, 1),
@@ -17983,12 +18901,13 @@ function resolveStaticMember(context, className, expression) {
17983
18901
  context.report("check-unknown-member", `Class "${className}" has no static member "${expression.property}".${hint}`, expression.position);
17984
18902
  return ANY_TYPE;
17985
18903
  }
17986
- function resolveNamedMember(context, name, expression) {
18904
+ function resolveNamedMember(context, receiver, expression) {
18905
+ const name = receiver.name;
17987
18906
  const enumeration = context.declarations.lookupEnum(name);
17988
18907
  if (enumeration !== null) {
17989
18908
  return checkEnumMember(context, name, enumeration.members, expression);
17990
18909
  }
17991
- const member = context.declarations.lookupMember(name, expression.property);
18910
+ const member = memberOf(context, receiver, expression.property);
17992
18911
  if (member !== null) {
17993
18912
  if (member.deprecated === true) {
17994
18913
  context.warn("check-deprecated-use", `Member "${expression.property}" is deprecated.`, expression.position);
@@ -18023,6 +18942,29 @@ function nativeConstructor(context, name) {
18023
18942
  const constructor = symbol.type.members.get(NATIVE_CONSTRUCTOR);
18024
18943
  return constructor === void 0 || constructor.kind !== "function" ? null : constructor;
18025
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
+ }
18026
18968
  function checkNewExpression(context, expression) {
18027
18969
  if (context.declarations.lookupClass(expression.className) === null) {
18028
18970
  const constructor2 = nativeConstructor(context, expression.className);
@@ -18041,13 +18983,20 @@ function checkNewExpression(context, expression) {
18041
18983
  const subject = pending === expression.className ? `Class "${pending}"` : `Class "${expression.className}" extends "${pending}", which`;
18042
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);
18043
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);
18044
18989
  const constructor = context.declarations.lookupMember(expression.className, "constructor");
18045
18990
  if (constructor !== null && constructor.type.kind === "function") {
18046
- 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
+ }
18047
18996
  } else {
18048
18997
  expression.args.forEach((argument) => checkExpression(context, argument));
18049
18998
  }
18050
- return createNamed(expression.className);
18999
+ return createNamed(expression.className, typeArguments);
18051
19000
  }
18052
19001
  function resolveSuperMethod(context, expression) {
18053
19002
  const frame = context.currentClassMethod();
@@ -18136,14 +19085,14 @@ function isShapeOption(context, option) {
18136
19085
  }
18137
19086
  return context.declarations.lookupInterface(option.name) !== null || context.declarations.lookupClass(option.name) !== null;
18138
19087
  }
18139
- function memberOf(context, option, property) {
19088
+ function memberOf2(context, option, property) {
18140
19089
  if (option.kind === "record") {
18141
19090
  return option.members.get(property) ?? null;
18142
19091
  }
18143
19092
  return option.kind === "named" ? context.declarations.lookupMember(option.name, property)?.type ?? null : null;
18144
19093
  }
18145
19094
  function optionMemberType(context, option, property) {
18146
- return isShapeOption(context, option) ? memberOf(context, option, property) : null;
19095
+ return isShapeOption(context, option) ? memberOf2(context, option, property) : null;
18147
19096
  }
18148
19097
  function resolveUnionMember(context, type, expression) {
18149
19098
  if (!type.options.every((option) => isShapeOption(context, option))) {
@@ -18152,7 +19101,7 @@ function resolveUnionMember(context, type, expression) {
18152
19101
  const resolved2 = [];
18153
19102
  const missing2 = [];
18154
19103
  for (const option of type.options) {
18155
- const member = memberOf(context, option, expression.property);
19104
+ const member = memberOf2(context, option, expression.property);
18156
19105
  if (member === null) {
18157
19106
  missing2.push(`"${typeToString(option)}"`);
18158
19107
  } else {
@@ -18319,26 +19268,6 @@ function conditionFacts(context, condition) {
18319
19268
  collectFacts(context, condition, facts);
18320
19269
  return facts;
18321
19270
  }
18322
- function alwaysExits(body) {
18323
- const last = body[body.length - 1];
18324
- if (last === void 0) {
18325
- return false;
18326
- }
18327
- if (last.kind === "return-statement" || last.kind === "break-statement") {
18328
- return true;
18329
- }
18330
- if (last.kind !== "if-statement" || last.alternate === null) {
18331
- return false;
18332
- }
18333
- return last.clauses.every((clause) => alwaysExits(clause.body)) && alwaysExits(last.alternate);
18334
- }
18335
- function guardFacts(context, statement) {
18336
- const [clause] = statement.kind === "if-statement" ? statement.clauses : [];
18337
- if (statement.kind !== "if-statement" || statement.alternate !== null || statement.clauses.length !== 1 || clause === void 0) {
18338
- return /* @__PURE__ */ new Map();
18339
- }
18340
- return alwaysExits(clause.body) ? negatedFacts(context, clause.condition) : /* @__PURE__ */ new Map();
18341
- }
18342
19271
  function negatedFacts(context, condition) {
18343
19272
  const facts = /* @__PURE__ */ new Map();
18344
19273
  if (condition.kind === "binary-expression" && condition.operator === "or") {
@@ -18369,23 +19298,58 @@ var ITERATOR_FUNCTIONS = /* @__PURE__ */ new Set(["pairs", "ipairs"]);
18369
19298
  function checkBlock(context, body) {
18370
19299
  context.binder.pushScope();
18371
19300
  checkStatements(context, body);
19301
+ const declared = context.binder.currentScopeNames();
18372
19302
  context.binder.popScope();
19303
+ for (const name of declared) {
19304
+ context.forgetNarrowing(name);
19305
+ }
18373
19306
  }
18374
- function checkNarrowedBlock(context, facts, body) {
18375
- context.pushNarrowing(facts);
19307
+ function branch(context, entry, facts, body) {
19308
+ const state = cloneFlow(entry);
19309
+ applyFacts(state, facts);
19310
+ context.setFlow(state);
18376
19311
  checkBlock(context, body);
18377
- context.popNarrowing();
19312
+ return context.flowState;
18378
19313
  }
18379
19314
  function checkIf(context, clauses, alternate) {
19315
+ const exits = [];
19316
+ let current = context.flowState;
18380
19317
  for (const clause of clauses) {
19318
+ context.setFlow(current);
18381
19319
  checkExpression(context, clause.condition);
18382
- 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;
18383
19325
  }
18384
19326
  if (alternate === null) {
18385
- return;
19327
+ exits.push(current);
19328
+ } else {
19329
+ context.setFlow(current);
19330
+ checkBlock(context, alternate);
19331
+ exits.push(context.flowState);
18386
19332
  }
18387
- const [only] = clauses;
18388
- 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);
18389
19353
  }
18390
19354
  function checkNumericFor(context, statement) {
18391
19355
  const bounds = [statement.start, statement.limit, statement.step].filter((value) => value !== null);
@@ -18396,10 +19360,13 @@ function checkNumericFor(context, statement) {
18396
19360
  }
18397
19361
  }
18398
19362
  forgetAssignedPaths(context, statement.body);
19363
+ const entry = context.flowState;
19364
+ context.setFlow(cloneFlow(entry));
18399
19365
  context.binder.pushScope();
18400
19366
  context.binder.declare({ name: statement.variable.name, type: NUMBER_TYPE, isLocal: true, position: statement.variable.position, origin: "local" });
18401
19367
  checkStatements(context, statement.body);
18402
19368
  context.binder.popScope();
19369
+ context.setFlow(entry);
18403
19370
  }
18404
19371
  function iteratedTypes(context, iterators) {
18405
19372
  const [iterator] = iterators;
@@ -18420,6 +19387,8 @@ function checkGenericFor(context, statement) {
18420
19387
  statement.iterators.forEach((iterator) => checkExpression(context, iterator));
18421
19388
  const iterated = iteratedTypes(context, statement.iterators);
18422
19389
  forgetAssignedPaths(context, statement.body);
19390
+ const entry = context.flowState;
19391
+ context.setFlow(cloneFlow(entry));
18423
19392
  context.binder.pushScope();
18424
19393
  statement.variables.forEach((variable, index) => {
18425
19394
  const inferred = iterated[index] ?? ANY_TYPE;
@@ -18428,6 +19397,7 @@ function checkGenericFor(context, statement) {
18428
19397
  });
18429
19398
  checkStatements(context, statement.body);
18430
19399
  context.binder.popScope();
19400
+ context.setFlow(entry);
18431
19401
  }
18432
19402
 
18433
19403
  // ../compiler/src/checker/events.ts
@@ -18488,15 +19458,19 @@ function minimumArguments(context, parameters) {
18488
19458
  }
18489
19459
  function buildFunctionType(context, parameters, returnAnnotation, contextual = null) {
18490
19460
  const isVariadic = parameters.some((parameter) => parameter.isVararg);
18491
- const types = parameters.filter((parameter) => !parameter.isVararg).map((parameter, index) => parameter.annotation === null ? contextual?.parameters[index] ?? ANY_TYPE : context.resolveAnnotation(parameter.annotation));
18492
- 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);
18493
19465
  }
18494
- function checkFunctionBody(context, parameters, returnAnnotation, body, signature, selfType) {
19466
+ function checkFunctionBody(context, parameters, returnAnnotation, body, signature, selfType2) {
18495
19467
  forgetAssignedPaths(context, body);
19468
+ const entry = context.flowState;
19469
+ context.setFlow(cloneFlow(entry));
18496
19470
  context.binder.pushScope();
18497
19471
  context.pushReturnType(returnAnnotation === null ? null : context.resolveAnnotation(returnAnnotation));
18498
- if (selfType !== null) {
18499
- 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 });
18500
19474
  }
18501
19475
  let parameterIndex = 0;
18502
19476
  for (const parameter of parameters) {
@@ -18512,6 +19486,7 @@ function checkFunctionBody(context, parameters, returnAnnotation, body, signatur
18512
19486
  signature.returnType = inferred;
18513
19487
  }
18514
19488
  context.binder.popScope();
19489
+ context.setFlow(entry);
18515
19490
  }
18516
19491
  function valueAt(values, valueTypes, index) {
18517
19492
  if (values[index] !== void 0) {
@@ -18535,6 +19510,9 @@ function checkLocal(context, statement) {
18535
19510
  context.expectAssignable(valueType, declared, value.position, `Variable "${declaration.name}"`);
18536
19511
  }
18537
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
+ }
18538
19516
  });
18539
19517
  }
18540
19518
  function checkCompoundOperand(context, operator, type, position2) {
@@ -18548,6 +19526,15 @@ function checkCompoundOperand(context, operator, type, position2) {
18548
19526
  context.report("check-invalid-operand", `Operator "${operator}" cannot be applied to "${typeToString(type)}".`, position2);
18549
19527
  }
18550
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
+ }
18551
19538
  function checkAssignment(context, statement) {
18552
19539
  const valueTypes = checkValueList(context, statement.values);
18553
19540
  statement.targets.forEach((target, index) => {
@@ -18583,6 +19570,7 @@ function checkAssignment(context, statement) {
18583
19570
  if (value !== void 0) {
18584
19571
  context.expectAssignable(valueType, declared, value.position, "Assignment");
18585
19572
  }
19573
+ recordAssignedFact(context, pathOf2(target), valueType, declared);
18586
19574
  });
18587
19575
  }
18588
19576
  function checkFunctionDeclaration(context, statement) {
@@ -18661,18 +19649,19 @@ function checkStatement(context, statement) {
18661
19649
  case "function-declaration":
18662
19650
  return checkFunctionDeclaration(context, statement);
18663
19651
  case "return-statement":
18664
- 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;
18665
19659
  case "do-statement":
18666
19660
  return checkBlock(context, statement.body);
18667
19661
  case "while-statement":
18668
- checkExpression(context, statement.condition);
18669
- forgetAssignedPaths(context, statement.body);
18670
- return checkBlock(context, statement.body);
19662
+ return checkWhile(context, statement);
18671
19663
  case "repeat-statement":
18672
- forgetAssignedPaths(context, statement.body);
18673
- checkBlock(context, statement.body);
18674
- checkExpression(context, statement.condition);
18675
- return;
19664
+ return checkRepeat(context, statement);
18676
19665
  case "if-statement":
18677
19666
  return checkIf(context, statement.clauses, statement.alternate);
18678
19667
  case "numeric-for-statement":
@@ -18702,18 +19691,8 @@ function checkStatement(context, statement) {
18702
19691
  }
18703
19692
  }
18704
19693
  function checkStatements(context, statements) {
18705
- let guards = 0;
18706
19694
  for (const statement of statements) {
18707
19695
  checkStatement(context, statement);
18708
- const facts = guardFacts(context, statement);
18709
- if (facts.size > 0) {
18710
- context.pushNarrowing(facts);
18711
- guards += 1;
18712
- }
18713
- }
18714
- while (guards > 0) {
18715
- context.popNarrowing();
18716
- guards -= 1;
18717
19696
  }
18718
19697
  }
18719
19698
 
@@ -18788,7 +19767,7 @@ function checkMember(context, expression) {
18788
19767
  if (objectType.kind === "map" && isAssignable(STRING_TYPE, objectType.key)) {
18789
19768
  return context.record(expression, objectType.value);
18790
19769
  }
18791
- const named2 = objectType.kind === "named" ? resolveNamedMember(context, objectType.name, expression) : null;
19770
+ const named2 = objectType.kind === "named" ? resolveNamedMember(context, objectType, expression) : null;
18792
19771
  if (named2 !== null) {
18793
19772
  return context.record(expression, named2);
18794
19773
  }
@@ -18845,10 +19824,14 @@ function isLegacySuperCall(expression) {
18845
19824
  }
18846
19825
  function checkMethodCall(context, expression, method, receiver) {
18847
19826
  if (receiver.kind !== "named") {
18848
- checkValueList(context, expression.args);
18849
- 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);
18850
19833
  }
18851
- const declared = context.declarations.lookupMember(receiver.name, method);
19834
+ const declared = memberOf(context, receiver, method);
18852
19835
  if (declared?.type.kind === "function") {
18853
19836
  if (declared.deprecated === true) {
18854
19837
  context.warn("check-deprecated-use", `Member "${method}" is deprecated.`, expression.position);
@@ -18867,6 +19850,10 @@ function checkMethodCall(context, expression, method, receiver) {
18867
19850
  return checkSignature(context, expression.args, member.type, expression.position);
18868
19851
  }
18869
19852
  function checkCall(context, expression) {
19853
+ const contracted = checkResourceCall(context, expression);
19854
+ if (contracted !== null) {
19855
+ return context.record(expression, contracted);
19856
+ }
18870
19857
  if (isLegacySuperCall(expression)) {
18871
19858
  checkValueList(context, expression.args);
18872
19859
  context.report("check-invalid-super", 'Call "super(...)" directly instead of "self:super(...)".', expression.position);
@@ -18967,6 +19954,9 @@ function checkMultiValueExpression(context, expression, expected = null) {
18967
19954
  if (symbol === null) {
18968
19955
  checkGlobalReference(context, expression.name, expression.position);
18969
19956
  }
19957
+ if (symbol === null || symbol.isLocal !== true) {
19958
+ context.noteGlobalReference(expression.name);
19959
+ }
18970
19960
  return context.record(expression, context.narrowedType(expression.name) ?? symbol?.type ?? ANY_TYPE);
18971
19961
  }
18972
19962
  case "member-expression":
@@ -19079,6 +20069,11 @@ function registerMembers(context, info, statement) {
19079
20069
  for (const member of [...statement.members, ...generated]) {
19080
20070
  const type = member.kind === "class-field" ? fieldTypes.get(member) ?? ANY_TYPE : member.isSynthetic ? syntheticMethodType(context, member, fieldTypes) : buildFunctionType(context, member.parameters, member.returnAnnotation);
19081
20071
  const decorators = member.decorators.map((decorator) => decorator.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);
19082
20077
  const space = member.isStatic ? info.statics : info.members;
19083
20078
  space.set(member.name, {
19084
20079
  name: member.name,
@@ -19106,9 +20101,22 @@ function declareBuilder(context, info, statement) {
19106
20101
  }
19107
20102
  }
19108
20103
  members.set("build", { name: "build", type: { kind: "function", parameters: [], minimumArguments: 0, isVariadic: false, returnType: createNamed(statement.name) }, isMethod: true, position: statement.position });
19109
- context.declarations.declareClass({ name, superClass: null, interfaces: [], members, statics: /* @__PURE__ */ new Map(), 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
+ });
19110
20115
  context.declareModuleGlobal({ name, type: createNamed(name), isLocal: false, position: statement.position });
19111
20116
  }
20117
+ function selfType(info) {
20118
+ return createNamed(info.name, info.typeParameters.map((parameter) => createNamed(parameter)));
20119
+ }
19112
20120
  function checkMethodBody(context, info, member) {
19113
20121
  const explicitSelf = member.parameters.find((parameter) => parameter.name === "self");
19114
20122
  if (explicitSelf !== void 0) {
@@ -19123,7 +20131,7 @@ function checkMethodBody(context, info, member) {
19123
20131
  return;
19124
20132
  }
19125
20133
  context.pushClassMethod({ className: info.name, methodName: member.name });
19126
- checkFunctionBody(context, member.parameters, member.returnAnnotation, member.body, signature, createNamed(info.name));
20134
+ checkFunctionBody(context, member.parameters, member.returnAnnotation, member.body, signature, selfType(info));
19127
20135
  context.popClassMethod();
19128
20136
  }
19129
20137
  function resolveSuperClass(context, statement) {
@@ -19146,6 +20154,10 @@ function resolveSuperClass(context, statement) {
19146
20154
  context.report("check-unknown-class", message, statement.position);
19147
20155
  return null;
19148
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
+ }
19149
20161
  function declareClassInfo(context, statement) {
19150
20162
  if (context.declarations.lookupClass(statement.name) !== null) {
19151
20163
  context.report("check-duplicate-class", `Class "${statement.name}" is already defined.`, statement.position);
@@ -19157,12 +20169,16 @@ function declareClassInfo(context, statement) {
19157
20169
  }
19158
20170
  const info = {
19159
20171
  name: statement.name,
20172
+ typeParameters: statement.typeParameters,
20173
+ typeConstraints: [],
19160
20174
  superClass: null,
20175
+ superArguments: [],
19161
20176
  interfaces: statement.interfaces,
19162
20177
  members: /* @__PURE__ */ new Map(),
19163
20178
  statics: /* @__PURE__ */ new Map(),
19164
20179
  position: statement.position
19165
20180
  };
20181
+ context.noteTypeParameters(statement.typeParameters);
19166
20182
  context.declarations.declareClass(info);
19167
20183
  context.declareModuleGlobal({ name: info.name, type: createNamed(info.name), isLocal: false, position: statement.position });
19168
20184
  return info;
@@ -19173,6 +20189,7 @@ function declareLocalClass(context, statement) {
19173
20189
  return null;
19174
20190
  }
19175
20191
  info.superClass = resolveSuperClass(context, statement);
20192
+ resolveClassHeader(context, info, statement);
19176
20193
  return info;
19177
20194
  }
19178
20195
  function checkClassDeclaration(context, statement) {
@@ -19286,6 +20303,7 @@ function predeclareModule(context, body) {
19286
20303
  const declared = declareHeaders(context, body);
19287
20304
  for (const entry of declared) {
19288
20305
  entry.info.superClass = resolveSuperClass(context, entry.statement);
20306
+ resolveClassHeader(context, entry.info, entry.statement);
19289
20307
  }
19290
20308
  for (const entry of declared) {
19291
20309
  breakCycle(context, entry.info);
@@ -19345,17 +20363,23 @@ function externalReferences(context) {
19345
20363
  }
19346
20364
  return external;
19347
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
+ }
19348
20370
  function check(program2, mode, environment = DEFAULT_ENVIRONMENT, options = {}) {
19349
20371
  const ambient = options.ambient ?? EMPTY_AMBIENT;
19350
20372
  const project = options.project ?? EMPTY_PROJECT_DECLARATIONS;
19351
20373
  const context = new CheckContext(mode, environment, ambient, project, options.oop === true);
19352
20374
  context.isDeclarationFile = options.isDeclarationFile === true;
20375
+ context.contracts = options.contracts ?? [];
19353
20376
  const structure = context.isDeclarationFile ? checkDeclarationFile(program2.body) : checkJumps(program2.body);
19354
20377
  const directives = collectDirectives(program2.body, { isDeclarationFile: context.isDeclarationFile });
19355
20378
  predeclareModule(context, program2.body);
19356
20379
  checkStatements(context, program2.body);
19357
20380
  reportUnknownTypes(context);
19358
20381
  reportUnusedSymbols(context, options.noUnusedLocals === true, options.noUnusedParameters === true);
20382
+ const external = externalReferences(context);
19359
20383
  return {
19360
20384
  diagnostics: sortDiagnostics([...structure, ...directives.diagnostics, ...context.diagnostics]),
19361
20385
  types: context.types,
@@ -19364,7 +20388,9 @@ function check(program2, mode, environment = DEFAULT_ENVIRONMENT, options = {})
19364
20388
  declarations: context.declarations,
19365
20389
  aliases: context.binder.resolvedAliases(),
19366
20390
  declaredGlobals: context.declaredGlobals,
19367
- externalReferences: externalReferences(context),
20391
+ moduleGlobals: context.moduleGlobals,
20392
+ externalReferences: external,
20393
+ referencedNames: referencedNames(context, external),
19368
20394
  directives: directives.directives,
19369
20395
  mode,
19370
20396
  environment,
@@ -19762,6 +20788,13 @@ function emitGeneratedMethod(state, className, member) {
19762
20788
  end
19763
20789
  end`;
19764
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
+ }
19765
20798
  if (member.generated?.kind === "to-string") {
19766
20799
  const values = names.map((name) => `'${name}=' .. tostring(self.${name})`).join(" .. ', ' .. ");
19767
20800
  const body = values.length === 0 ? "''" : values;
@@ -19797,24 +20830,24 @@ ${names.map((name) => ` self.${name} = values.${name}`).join("\n")}
19797
20830
  end`;
19798
20831
  }
19799
20832
  function emitMembers(state, statement) {
19800
- const entries2 = [];
20833
+ const entries3 = [];
19801
20834
  state.indent += 1;
19802
20835
  for (const member of statement.members) {
19803
20836
  if (member.kind === "class-method") {
19804
- entries2.push(indentLine(state, `${markSource(state, member.position.line, `${statement.name}${member.isStatic ? "." : ":"}${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)}`));
19805
20838
  continue;
19806
20839
  }
19807
20840
  if (member.decorators.some((decorator) => decorator.name === "Lazy")) {
19808
20841
  continue;
19809
20842
  }
19810
20843
  const value = member.value === null ? "nil" : emitExpression(state, member.value);
19811
- 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}`));
19812
20845
  }
19813
20846
  for (const generated of state.generatedMembers.get(statement) ?? []) {
19814
- 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)}`));
19815
20848
  }
19816
20849
  state.indent -= 1;
19817
- return entries2;
20850
+ return entries3;
19818
20851
  }
19819
20852
  function emitClassHeader(statement) {
19820
20853
  const name = emitString(statement.name);
@@ -19840,15 +20873,15 @@ ${assignments}
19840
20873
  return value
19841
20874
  end`);
19842
20875
  state.indent += 1;
19843
- const entries2 = methods.map((method) => indentLine(state, method));
20876
+ const entries3 = methods.map((method) => indentLine(state, method));
19844
20877
  state.indent -= 1;
19845
- return [`class '${name}' {`, entries2.join(",\n"), "}"].join("\n");
20878
+ return [`class '${name}' {`, entries3.join(",\n"), "}"].join("\n");
19846
20879
  }
19847
20880
  function emitClassDeclaration(state, statement) {
19848
20881
  requireHelper(state, "class");
19849
20882
  const header = emitClassHeader(statement);
19850
- const entries2 = emitMembers(state, statement);
19851
- 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");
19852
20885
  const builder = emitBuilder(state, statement);
19853
20886
  return builder === null ? declaration : `${declaration}
19854
20887
  ${builder}`;
@@ -20786,6 +21819,7 @@ function compile(source, options = {}) {
20786
21819
  const isDeclarationFile = options.filePath !== void 0 && isDeclarationPath(options.filePath);
20787
21820
  const checked = check(parsed.program, mode, environment, {
20788
21821
  ambient: options.ambient ?? EMPTY_AMBIENT,
21822
+ contracts: options.contracts ?? [],
20789
21823
  project: options.project ?? EMPTY_PROJECT_DECLARATIONS,
20790
21824
  isDeclarationFile,
20791
21825
  oop: settings.oop,
@@ -20799,7 +21833,9 @@ function compile(source, options = {}) {
20799
21833
  environment,
20800
21834
  declarations: ownDeclarations(checked.declarations, options.ambient),
20801
21835
  declaredGlobals: checked.declaredGlobals,
21836
+ moduleGlobals: checked.moduleGlobals,
20802
21837
  externalReferences: checked.externalReferences,
21838
+ referencedNames: checked.referencedNames,
20803
21839
  directives: checked.directives,
20804
21840
  topLevelReturn: findTopLevelReturn(parsed.program.body),
20805
21841
  events: checked.events
@@ -20890,6 +21926,199 @@ function fingerprintDeclarations(declarations) {
20890
21926
  const events = declarations.events.map(eventText);
20891
21927
  return hashString([...classes, ...interfaces, ...enums, ...globals, ...events].sort(compareText).join(";"));
20892
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
+ }
20893
22122
 
20894
22123
  // ../compiler/src/project/module-graph.ts
20895
22124
  function buildSymbolTable(modules) {
@@ -20950,52 +22179,12 @@ function validateModuleReferences(modules) {
20950
22179
  function flattenDiagnostics(modules) {
20951
22180
  return modules.flatMap((module) => module.diagnostics.map((diagnostic) => ({ path: module.path, diagnostic })));
20952
22181
  }
20953
- function optionsKey(options) {
20954
- return `${options.strict ? "strict" : ""}|${options.oop ? "oop" : ""}|${options.noUnusedLocals ? "nul" : ""}|${options.noUnusedParameters ? "nup" : ""}`;
20955
- }
20956
- function sharedEnums(collected) {
20957
- const declared = new Set(collected.flatMap((entry) => entry.declarations.enums.map((enumeration) => enumeration.name)));
20958
- if (declared.size === 0) {
20959
- return declared;
20960
- }
20961
- return new Set(collected.flatMap((entry) => entry.externalReferences.filter((name) => declared.has(name))));
20962
- }
20963
- function ambientKeys(collected, context) {
20964
- const keys = {};
20965
- const { project, references, options } = context;
20966
- const mode = context.development ? "development" : "release";
20967
- const projectKey = `${optionsKey(options)}|${mode}|${JSON.stringify(project.globals)}|${[...references].sort().join(",")}`;
20968
- for (const environment of ALL_ENVIRONMENTS) {
20969
- const visible = collected.filter((entry) => canReference(environment, entry.environment));
20970
- keys[environment] = hashString(`${projectKey}|${visible.map((entry) => `${entry.path}=${entry.fingerprint}`).join(";")}`);
20971
- }
20972
- return keys;
20973
- }
20974
- function declaresNothing(declarations) {
20975
- const { classes, interfaces, enums, globals, events } = declarations;
20976
- return classes.length === 0 && interfaces.length === 0 && enums.length === 0 && globals.length === 0 && events.length === 0;
20977
- }
20978
- function createAmbientResolver(collected) {
20979
- const visible = /* @__PURE__ */ new Map();
20980
- const merged = /* @__PURE__ */ new Map();
20981
- for (const environment of ALL_ENVIRONMENTS) {
20982
- const entries2 = collected.filter((entry) => canReference(environment, entry.environment) && !declaresNothing(entry.declarations));
20983
- visible.set(environment, entries2);
20984
- merged.set(environment, mergeAmbient(entries2.map((entry) => entry.declarations)));
20985
- }
20986
- return (entry) => {
20987
- if (declaresNothing(entry.declarations)) {
20988
- return merged.get(entry.environment) ?? EMPTY_AMBIENT;
20989
- }
20990
- const entries2 = visible.get(entry.environment) ?? [];
20991
- return mergeAmbient(entries2.filter((candidate) => candidate.path !== entry.path).map((candidate) => candidate.declarations));
20992
- };
20993
- }
20994
22182
  function compileModule(file, ambient, context) {
20995
22183
  const result = compile(file.source, {
20996
22184
  filePath: file.path,
20997
22185
  ...file.environment === void 0 ? {} : { environment: file.environment },
20998
22186
  ambient,
22187
+ contracts: context.contracts,
20999
22188
  project: context.project,
21000
22189
  projectReferences: context.projectReferences,
21001
22190
  compilerOptions: context.compilerOptions,
@@ -21009,7 +22198,7 @@ function compileModule(file, ambient, context) {
21009
22198
  requiredHelpers: result.requiredHelpers,
21010
22199
  declaredGlobals: result.declaredGlobals,
21011
22200
  externalReferences: result.externalReferences,
21012
- contributions: toContributions(result.directives, result.environment),
22201
+ contributions: toContributions(result.directives, result.environment, result.moduleGlobals),
21013
22202
  diagnostics: result.diagnostics,
21014
22203
  lines: result.lines,
21015
22204
  topLevelReturn: result.topLevelReturn,
@@ -21050,20 +22239,22 @@ function createProjectCache() {
21050
22239
  environment: result.environment,
21051
22240
  declarations: result.declarations,
21052
22241
  externalReferences: [...result.externalReferences.keys()],
21053
- fingerprint: fingerprintDeclarations(result.declarations)
22242
+ fingerprint: fingerprintDeclarations(result.declarations),
22243
+ provides: fingerprintByName(result.declarations),
22244
+ requires: result.referencedNames
21054
22245
  };
21055
22246
  declarationCache.set(file.path, entry);
21056
22247
  return entry;
21057
22248
  }
21058
22249
  function moduleFor(pair, context, reused) {
21059
22250
  const { file, entry } = pair;
21060
- const ambientKey = context.keys[entry.environment];
22251
+ const ambientKey = context.scope.keyFor(entry);
21061
22252
  const cached = moduleCache.get(file.path);
21062
22253
  if (cached !== void 0 && cached.hash === entry.hash && cached.ambientKey === ambientKey) {
21063
22254
  reused.count += 1;
21064
22255
  return cached.module;
21065
22256
  }
21066
- const module = compileModule(file, context.resolve(entry), context);
22257
+ const module = compileModule(file, context.scope.resolve(entry), context);
21067
22258
  moduleCache.set(file.path, { hash: entry.hash, ambientKey, module });
21068
22259
  return module;
21069
22260
  }
@@ -21077,9 +22268,10 @@ function createProjectCache() {
21077
22268
  const collected = pairs.map((pair) => pair.entry);
21078
22269
  const projectReferences = sharedEnums(collected);
21079
22270
  const development = options.development === true;
22271
+ const contracts = options.contracts ?? [];
21080
22272
  const context = {
21081
- keys: ambientKeys(collected, { project, references: projectReferences, options: compilerOptions, development }),
21082
- resolve: createAmbientResolver(collected),
22273
+ scope: createAmbientScope(collected, { project, references: projectReferences, options: compilerOptions, development, contracts }),
22274
+ contracts,
21083
22275
  project,
21084
22276
  projectReferences,
21085
22277
  compilerOptions,
@@ -21159,8 +22351,8 @@ function materialize(build, resource, includeMap) {
21159
22351
  const bundled = materializeBundles(resource, build.bundles, helperContent);
21160
22352
  return { ...build, scripts: bundled.scripts, map: includeMap ? bundled.map : null };
21161
22353
  }
21162
- function diagnosticSources(files, entries2) {
21163
- const paths = new Set(entries2.map((entry) => entry.path));
22354
+ function diagnosticSources(files, entries3) {
22355
+ const paths = new Set(entries3.map((entry) => entry.path));
21164
22356
  return new Map(files.filter((file) => paths.has(file.path)).map((file) => [file.path, file.source]));
21165
22357
  }
21166
22358
  function runCompile(root, config, options = {}) {
@@ -21168,10 +22360,11 @@ function runCompile(root, config, options = {}) {
21168
22360
  const tracker = options.tracker ?? createPhaseTracker();
21169
22361
  const started = performance.now();
21170
22362
  tracker.begin("discovery");
21171
- const excluded = [config.outDir];
22363
+ const excluded = [config.outDir, config.contracts];
21172
22364
  const sources = discoverSources(root, config.sources, excluded);
21173
22365
  const inputs = readProjectInputs(root, { assets: config.assets, environment: config.environment, excluded });
21174
- const diagnostics = [...sources.diagnostics, ...inputs.diagnostics];
22366
+ const contracts = readDependencyContracts(root, config);
22367
+ const diagnostics = [...sources.diagnostics, ...inputs.diagnostics, ...contracts.diagnostics];
21175
22368
  if (hasCliErrors(diagnostics)) {
21176
22369
  tracker.end("failed");
21177
22370
  return {
@@ -21184,13 +22377,15 @@ function runCompile(root, config, options = {}) {
21184
22377
  environmentTemplate: null,
21185
22378
  phases: tracker.durations(),
21186
22379
  sources: /* @__PURE__ */ new Map(),
21187
- map: null
22380
+ map: null,
22381
+ contract: null
21188
22382
  };
21189
22383
  }
21190
22384
  tracker.begin("compile", sources.files.length);
21191
22385
  const declarations = projectDeclarations(inputs.declared?.entries ?? null, config.environment.file);
21192
22386
  const project = cache3.compile(sources.files, {
21193
22387
  project: declarations,
22388
+ contracts: contracts.contracts,
21194
22389
  compilerOptions: config.compilerOptions,
21195
22390
  development: options.development === true,
21196
22391
  onProgress: (event) => tracker.advance(event.item, event.index, event.total)
@@ -21217,13 +22412,14 @@ function runCompile(root, config, options = {}) {
21217
22412
  environmentTemplate: inputs.deployed === null ? null : renderEnvironmentTemplate(inputs.deployed),
21218
22413
  phases: tracker.durations(),
21219
22414
  sources: diagnosticSources(sources.files, assembly.diagnostics),
21220
- map: build?.map ?? null
22415
+ map: build?.map ?? null,
22416
+ contract: build === null ? null : buildResourceAbi(config.name, project.modules.flatMap((module) => module.contributions))
21221
22417
  };
21222
22418
  }
21223
22419
 
21224
22420
  // src/build/resource-map-file.ts
21225
- import { existsSync as existsSync5, mkdirSync as mkdirSync2, readFileSync as readFileSync6, readdirSync as readdirSync2, unlinkSync, writeFileSync as writeFileSync3 } from "node:fs";
21226
- 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";
21227
22423
  var RESOURCE_MAP_SUFFIX = ".luam-map.json";
21228
22424
  function isObject(value) {
21229
22425
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -21288,10 +22484,10 @@ function reason(error) {
21288
22484
  return error instanceof Error ? error.message : String(error);
21289
22485
  }
21290
22486
  function resourceMapPath(root, config) {
21291
- return resolve8(root, config.outDir, `${config.name}${RESOURCE_MAP_SUFFIX}`);
22487
+ return resolve9(root, config.outDir, `${config.name}${RESOURCE_MAP_SUFFIX}`);
21292
22488
  }
21293
22489
  function explicitResourceMapPath(root, path) {
21294
- return isAbsolute3(path) ? path : resolve8(root, path);
22490
+ return isAbsolute3(path) ? path : resolve9(root, path);
21295
22491
  }
21296
22492
  function writeResourceMap(root, config, map) {
21297
22493
  const path = resourceMapPath(root, config);
@@ -21301,15 +22497,15 @@ function writeResourceMap(root, config, map) {
21301
22497
  }
21302
22498
  return;
21303
22499
  }
21304
- mkdirSync2(dirname2(path), { recursive: true });
22500
+ mkdirSync3(dirname3(path), { recursive: true });
21305
22501
  const content = serializeResourceMap(map);
21306
- if (!existsSync5(path) || readFileSync6(path, "utf8") !== content) {
21307
- writeFileSync3(path, content, "utf8");
22502
+ if (!existsSync5(path) || readFileSync7(path, "utf8") !== content) {
22503
+ writeFileSync4(path, content, "utf8");
21308
22504
  }
21309
22505
  }
21310
22506
  function readResourceMap(path) {
21311
22507
  try {
21312
- const value = JSON.parse(readFileSync6(path, "utf8"));
22508
+ const value = JSON.parse(readFileSync7(path, "utf8"));
21313
22509
  if (isObject(value) && Number.isInteger(value.version) && value.version !== RESOURCE_MAP_VERSION) {
21314
22510
  return { map: null, error: `Resource map version ${String(value.version)} is not supported.` };
21315
22511
  }
@@ -21323,13 +22519,13 @@ function isSearchable(name) {
21323
22519
  return !name.startsWith(".") && !SKIPPED_DIRECTORIES.has(name);
21324
22520
  }
21325
22521
  function collectResourceMaps(directory, found) {
21326
- let entries2;
22522
+ let entries3;
21327
22523
  try {
21328
- entries2 = readdirSync2(directory, { withFileTypes: true });
22524
+ entries3 = readdirSync2(directory, { withFileTypes: true });
21329
22525
  } catch {
21330
22526
  return;
21331
22527
  }
21332
- for (const entry of entries2) {
22528
+ for (const entry of entries3) {
21333
22529
  const absolute = join4(directory, entry.name);
21334
22530
  if (entry.isDirectory()) {
21335
22531
  if (isSearchable(entry.name)) {
@@ -21353,8 +22549,8 @@ function discoverResourceMap(root) {
21353
22549
  }
21354
22550
 
21355
22551
  // src/build/resource-writer.ts
21356
- import { existsSync as existsSync7, mkdirSync as mkdirSync3, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "node:fs";
21357
- 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";
21358
22554
 
21359
22555
  // src/build/lua-tokens.ts
21360
22556
  var WHITESPACE = /* @__PURE__ */ new Set([" ", " ", "\n", "\r", "\v", "\f"]);
@@ -21411,7 +22607,7 @@ function readLongBracket2(source, start, level, file) {
21411
22607
  return end + closing.length;
21412
22608
  }
21413
22609
  function readShortString(source, start, file) {
21414
- const quote2 = source[start];
22610
+ const quote3 = source[start];
21415
22611
  let index = start + 1;
21416
22612
  while (index < source.length) {
21417
22613
  const char = source[index];
@@ -21419,7 +22615,7 @@ function readShortString(source, start, file) {
21419
22615
  index += 2;
21420
22616
  continue;
21421
22617
  }
21422
- if (char === quote2) {
22618
+ if (char === quote3) {
21423
22619
  return index + 1;
21424
22620
  }
21425
22621
  if (char === "\n") {
@@ -21558,7 +22754,7 @@ function minifyLuaFiles(files) {
21558
22754
 
21559
22755
  // src/build/resource-prune.ts
21560
22756
  import { existsSync as existsSync6, readdirSync as readdirSync3, rmdirSync, unlinkSync as unlinkSync2 } from "node:fs";
21561
- 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";
21562
22758
  var GENERATED_MANIFEST = "meta.xml";
21563
22759
  var GENERATED_EXTENSION = ".lua";
21564
22760
  var PROTECTED = /* @__PURE__ */ new Set([ENVIRONMENT_FILE, ".env.local"]);
@@ -21575,14 +22771,14 @@ function isGenerated(relativePath2, options) {
21575
22771
  return options.generatedRoots.some((root) => isUnderRoot(relativePath2, root));
21576
22772
  }
21577
22773
  function removeEmptyDirectories(targetDir, directory) {
21578
- if (resolve9(directory) === resolve9(targetDir)) {
22774
+ if (resolve10(directory) === resolve10(targetDir)) {
21579
22775
  return;
21580
22776
  }
21581
22777
  if (readdirSync3(directory).length > 0) {
21582
22778
  return;
21583
22779
  }
21584
22780
  rmdirSync(directory);
21585
- removeEmptyDirectories(targetDir, resolve9(directory, ".."));
22781
+ removeEmptyDirectories(targetDir, resolve10(directory, ".."));
21586
22782
  }
21587
22783
  function pruneResource(targetDir, keep, options) {
21588
22784
  if (!existsSync6(targetDir)) {
@@ -21635,7 +22831,7 @@ function resourceFiles(build) {
21635
22831
  return files;
21636
22832
  }
21637
22833
  function resolveInside(targetDir, path) {
21638
- const absolute = resolve10(targetDir, path);
22834
+ const absolute = resolve11(targetDir, path);
21639
22835
  const inside = relative4(targetDir, absolute);
21640
22836
  if (inside.startsWith("..") || isAbsolute4(inside)) {
21641
22837
  throw new Error(`The generated file "${path}" resolves outside the resource directory "${targetDir}".`);
@@ -21643,11 +22839,11 @@ function resolveInside(targetDir, path) {
21643
22839
  return absolute;
21644
22840
  }
21645
22841
  function writeIfChanged(absolute, content) {
21646
- if (existsSync7(absolute) && readFileSync7(absolute).equals(content)) {
22842
+ if (existsSync7(absolute) && readFileSync8(absolute).equals(content)) {
21647
22843
  return false;
21648
22844
  }
21649
- mkdirSync3(dirname3(absolute), { recursive: true });
21650
- writeFileSync4(absolute, content);
22845
+ mkdirSync4(dirname4(absolute), { recursive: true });
22846
+ writeFileSync5(absolute, content);
21651
22847
  return true;
21652
22848
  }
21653
22849
  function writeEnvironmentFile(targetDir, template) {
@@ -21658,8 +22854,8 @@ function writeEnvironmentFile(targetDir, template) {
21658
22854
  if (existsSync7(absolute)) {
21659
22855
  return false;
21660
22856
  }
21661
- mkdirSync3(dirname3(absolute), { recursive: true });
21662
- writeFileSync4(absolute, template, "utf8");
22857
+ mkdirSync4(dirname4(absolute), { recursive: true });
22858
+ writeFileSync5(absolute, template, "utf8");
21663
22859
  return true;
21664
22860
  }
21665
22861
  function writeResource(targetDir, build, options) {
@@ -21683,7 +22879,7 @@ function writeResource(targetDir, build, options) {
21683
22879
  advance(path);
21684
22880
  }
21685
22881
  for (const asset of build.assets) {
21686
- 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)))) {
21687
22883
  written.push(asset.path);
21688
22884
  advance(asset.path);
21689
22885
  continue;
@@ -21800,16 +22996,16 @@ function reportBuildOutcome(context, outcome, action) {
21800
22996
  }
21801
22997
 
21802
22998
  // src/commands/resource-targets.ts
21803
- import { isAbsolute as isAbsolute5, resolve as resolve11 } from "node:path";
22999
+ import { isAbsolute as isAbsolute5, resolve as resolve12 } from "node:path";
21804
23000
  function resolveBuildTarget(root, config) {
21805
- return resolve11(root, config.outDir, config.name);
23001
+ return resolve12(root, config.outDir, config.name);
21806
23002
  }
21807
23003
  function resolveServerTarget(root, config) {
21808
23004
  if (config.serverPath === null) {
21809
23005
  return null;
21810
23006
  }
21811
- const serverRoot = isAbsolute5(config.serverPath) ? config.serverPath : resolve11(root, config.serverPath);
21812
- 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);
21813
23009
  }
21814
23010
 
21815
23011
  // src/reporting/progress-renderer.ts
@@ -21854,7 +23050,7 @@ function createProgressRenderer(reporter, options = {}) {
21854
23050
  paint(reporter.style.eraseLine());
21855
23051
  dirty = false;
21856
23052
  }
21857
- function render(event) {
23053
+ function render2(event) {
21858
23054
  const columns = Math.max(20, reporter.capability.columns - 1);
21859
23055
  const line2 = activeLine(reporter, event);
21860
23056
  paint(`${reporter.style.eraseLine()}${line2.slice(0, columns)}`);
@@ -21874,7 +23070,7 @@ function createProgressRenderer(reporter, options = {}) {
21874
23070
  if (frames > 0 && now() - lastPaintAt < throttleMs) {
21875
23071
  return;
21876
23072
  }
21877
- render(event);
23073
+ render2(event);
21878
23074
  }
21879
23075
  return {
21880
23076
  listen: (event) => {
@@ -21932,6 +23128,9 @@ async function runBuildCommand(context, options = {}) {
21932
23128
  return EXIT_DIAGNOSTICS;
21933
23129
  }
21934
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
+ }
21935
23134
  tracker.end();
21936
23135
  renderer.clear();
21937
23136
  reportBuildOutcome(context, outcome, "Build");
@@ -21982,6 +23181,298 @@ function registerCheckCommand(program2, runtime) {
21982
23181
  });
21983
23182
  }
21984
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
+
21985
23476
  // src/commands/ensure-runner.ts
21986
23477
  function hasChanges(result) {
21987
23478
  return result.written.length > 0 || result.removed.length > 0;
@@ -22006,7 +23497,7 @@ function restartResource(scope, serverConsole) {
22006
23497
  reporter.success(`Restarted "${scope.context.config.name}" through the owned server console.`);
22007
23498
  return true;
22008
23499
  }
22009
- function failed(outcome) {
23500
+ function failed2(outcome) {
22010
23501
  return { ok: false, outcome, sync: null, restarted: false };
22011
23502
  }
22012
23503
  async function runOnce(scope, target, cache3, options) {
@@ -22029,7 +23520,7 @@ async function runOnce(scope, target, cache3, options) {
22029
23520
  if (outcome.build === null) {
22030
23521
  reportBuildOutcome(context, outcome, "Build");
22031
23522
  reporter.warn("Skipping sync and restart because the build reported errors.");
22032
- return failed(outcome);
23523
+ return failed2(outcome);
22033
23524
  }
22034
23525
  const writeOptions = trackedWriteOptions(context.root, context.config, outcome.environmentTemplate, tracker);
22035
23526
  reportBuildOutcome(context, outcome, "Build");
@@ -22077,8 +23568,8 @@ function reportRebuildSeparator(reporter, at = /* @__PURE__ */ new Date()) {
22077
23568
  }
22078
23569
 
22079
23570
  // src/watch/source-watcher.ts
22080
- import { existsSync as existsSync8, watch } from "node:fs";
22081
- 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";
22082
23573
  var DEFAULT_DEBOUNCE_MS = 120;
22083
23574
  function watchSources(root, sources, onChange, debounceMs = DEFAULT_DEBOUNCE_MS) {
22084
23575
  const resolver = createSourceResolver(sources);
@@ -22094,12 +23585,12 @@ function watchSources(root, sources, onChange, debounceMs = DEFAULT_DEBOUNCE_MS)
22094
23585
  }, debounceMs);
22095
23586
  };
22096
23587
  const isWatched = (directory, filename) => {
22097
- const path = normalizePattern(relative5(root, resolve12(directory, filename)));
23588
+ const path = normalizePattern(relative6(root, resolve14(directory, filename)));
22098
23589
  return path.endsWith(SOURCE_EXTENSION) && resolver.resolve(path).matches.length > 0;
22099
23590
  };
22100
23591
  for (const entry of resolver.roots) {
22101
- const absolute = resolve12(root, entry);
22102
- if (!existsSync8(absolute)) {
23592
+ const absolute = resolve14(root, entry);
23593
+ if (!existsSync9(absolute)) {
22103
23594
  continue;
22104
23595
  }
22105
23596
  watchers.push(
@@ -22306,12 +23797,12 @@ function parseMtaLogLine(line2, activeResource, fallback = /* @__PURE__ */ new D
22306
23797
  }
22307
23798
 
22308
23799
  // src/logging/server-log-follower.ts
22309
- import { closeSync, existsSync as existsSync9, openSync, readSync, statSync as statSync4 } from "node:fs";
22310
- 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";
22311
23802
  var DEFAULT_POLL_INTERVAL_MS = 100;
22312
23803
  function resolveServerLogPath(root, serverPath) {
22313
- const server = isAbsolute6(serverPath) ? serverPath : resolve13(root, serverPath);
22314
- 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");
22315
23806
  }
22316
23807
  function identity(path) {
22317
23808
  const stats = statSync4(path);
@@ -22336,7 +23827,7 @@ function followServerLog(path, onLine, options = {}) {
22336
23827
  let initialized = false;
22337
23828
  let closed = false;
22338
23829
  const poll = () => {
22339
- if (closed || !existsSync9(path)) {
23830
+ if (closed || !existsSync10(path)) {
22340
23831
  return;
22341
23832
  }
22342
23833
  try {
@@ -22452,9 +23943,9 @@ function connectServerConsoleInput(input, output, interrupt) {
22452
23943
 
22453
23944
  // src/server/server-executable-resolver.ts
22454
23945
  import { realpathSync, statSync as statSync5 } from "node:fs";
22455
- 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";
22456
23947
  function isInside(root, path) {
22457
- const pathFromRoot = relative6(root, path);
23948
+ const pathFromRoot = relative7(root, path);
22458
23949
  return pathFromRoot.length === 0 || !pathFromRoot.startsWith("..") && !isAbsolute7(pathFromRoot);
22459
23950
  }
22460
23951
  function candidates2(platform) {
@@ -22467,14 +23958,14 @@ function candidates2(platform) {
22467
23958
  throw new Error(`Local MTA server startup is not supported on platform "${platform}". Use Windows or Linux.`);
22468
23959
  }
22469
23960
  function resolveServerExecutable(options) {
22470
- const serverRoot = resolve14(options.root, options.serverPath);
23961
+ const serverRoot = resolve16(options.root, options.serverPath);
22471
23962
  const names = options.configured === null ? candidates2(options.platform ?? process.platform) : [options.configured];
22472
23963
  const attempted = [];
22473
- 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)))) {
22474
23965
  throw new Error(`Configured development.server.executable must stay inside serverPath but received "${options.configured}".`);
22475
23966
  }
22476
23967
  for (const name of names) {
22477
- const path = resolve14(serverRoot, name);
23968
+ const path = resolve16(serverRoot, name);
22478
23969
  attempted.push(path);
22479
23970
  try {
22480
23971
  if (!statSync5(path).isFile()) {
@@ -22792,7 +24283,7 @@ function registerEnsureCommand(program2, runtime) {
22792
24283
  }
22793
24284
 
22794
24285
  // src/cli/registry/init-registration.ts
22795
- import { resolve as resolve16 } from "node:path";
24286
+ import { resolve as resolve18 } from "node:path";
22796
24287
 
22797
24288
  // src/commands/init-command.ts
22798
24289
  import { basename as basename2 } from "node:path";
@@ -22854,7 +24345,7 @@ function renderManifest(source, details) {
22854
24345
  }
22855
24346
 
22856
24347
  // src/scaffold/template-files.ts
22857
- import { existsSync as existsSync10, readFileSync as readFileSync8 } from "node:fs";
24348
+ import { existsSync as existsSync11, readFileSync as readFileSync10 } from "node:fs";
22858
24349
  import { fileURLToPath as fileURLToPath2 } from "node:url";
22859
24350
 
22860
24351
  // ../template/src/template.ts
@@ -22870,10 +24361,10 @@ function bundledTemplatePath(source) {
22870
24361
  }
22871
24362
  function resolveTemplatePath(source) {
22872
24363
  const packaged = fileURLToPath2(resolveTemplateUrl(source));
22873
- return existsSync10(packaged) ? packaged : bundledTemplatePath(source);
24364
+ return existsSync11(packaged) ? packaged : bundledTemplatePath(source);
22874
24365
  }
22875
24366
  function readTemplateSource(source) {
22876
- return readFileSync8(resolveTemplatePath(source), "utf8");
24367
+ return readFileSync10(resolveTemplatePath(source), "utf8");
22877
24368
  }
22878
24369
 
22879
24370
  // src/scaffold/scaffold-plan.ts
@@ -22889,19 +24380,19 @@ function buildScaffoldPlan(details) {
22889
24380
  }
22890
24381
 
22891
24382
  // src/scaffold/scaffold-writer.ts
22892
- import { existsSync as existsSync11, mkdirSync as mkdirSync4, writeFileSync as writeFileSync5 } from "node:fs";
22893
- 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";
22894
24385
  function writeScaffold(targetDir, plan, force) {
22895
24386
  const written = [];
22896
24387
  const skipped = [];
22897
24388
  for (const file of plan.files) {
22898
- const absolute = resolve15(targetDir, file.path);
22899
- if (!force && existsSync11(absolute)) {
24389
+ const absolute = resolve17(targetDir, file.path);
24390
+ if (!force && existsSync12(absolute)) {
22900
24391
  skipped.push(file.path);
22901
24392
  continue;
22902
24393
  }
22903
- mkdirSync4(dirname4(absolute), { recursive: true });
22904
- writeFileSync5(absolute, file.content, "utf8");
24394
+ mkdirSync6(dirname6(absolute), { recursive: true });
24395
+ writeFileSync7(absolute, file.content, "utf8");
22905
24396
  written.push(file.path);
22906
24397
  }
22907
24398
  return { written: written.sort((left, right) => left.localeCompare(right)), skipped: skipped.sort((left, right) => left.localeCompare(right)) };
@@ -22951,7 +24442,7 @@ function registerInitCommand(program2, runtime) {
22951
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());
22952
24443
  command.action(async (path, options) => {
22953
24444
  const root = commandRoot(runtime, options);
22954
- const target = path === void 0 ? root : resolve16(root, path);
24445
+ const target = path === void 0 ? root : resolve18(root, path);
22955
24446
  runtime.exitCode = await runInitCommand(target, runtime.logger, {
22956
24447
  name: options.name ?? null,
22957
24448
  force: options.force === true,
@@ -22969,7 +24460,7 @@ async function runSetupCommand(reporter, options) {
22969
24460
  reporter.detail("Supported commands: code, code-insiders, cursor, codium, windsurf.");
22970
24461
  return EXIT_DIAGNOSTICS;
22971
24462
  }
22972
- let failed2 = false;
24463
+ let failed3 = false;
22973
24464
  for (const editor of editors) {
22974
24465
  if (options.editorService.hasExtension(editor)) {
22975
24466
  reporter.success(`${editor.name}: Luam is already installed.`);
@@ -22988,12 +24479,12 @@ async function runSetupCommand(reporter, options) {
22988
24479
  const result = await options.editorService.install(editor);
22989
24480
  if (result.error !== null) {
22990
24481
  reporter.error(`${editor.name}: installation failed. ${result.error}`);
22991
- failed2 = true;
24482
+ failed3 = true;
22992
24483
  continue;
22993
24484
  }
22994
24485
  reporter.success(`${editor.name}: installed from the ${result.source}.`);
22995
24486
  }
22996
- return failed2 ? EXIT_DIAGNOSTICS : EXIT_OK;
24487
+ return failed3 ? EXIT_DIAGNOSTICS : EXIT_OK;
22997
24488
  }
22998
24489
 
22999
24490
  // src/editor/installation-prompt.ts
@@ -23075,12 +24566,12 @@ function registerServerCommand(program2, runtime) {
23075
24566
  }
23076
24567
 
23077
24568
  // src/commands/trace-command.ts
23078
- import { existsSync as existsSync12, readFileSync as readFileSync9 } from "node:fs";
24569
+ import { existsSync as existsSync13, readFileSync as readFileSync11 } from "node:fs";
23079
24570
  function defaultMapPath(root, options, reporter) {
23080
24571
  const loaded = loadManifest(root, { path: options.manifestPath, mode: manifestMode("trace"), env: options.env });
23081
24572
  if (loaded.config !== null) {
23082
24573
  const configured = resourceMapPath(root, loaded.config);
23083
- if (existsSync12(configured)) {
24574
+ if (existsSync13(configured)) {
23084
24575
  return configured;
23085
24576
  }
23086
24577
  }
@@ -23100,7 +24591,7 @@ function readStandardInput() {
23100
24591
  return "";
23101
24592
  }
23102
24593
  try {
23103
- return readFileSync9(0, "utf8");
24594
+ return readFileSync11(0, "utf8");
23104
24595
  } catch {
23105
24596
  return "";
23106
24597
  }
@@ -23134,30 +24625,30 @@ function runTraceCommand(root, reporter, options) {
23134
24625
  reporter.detail('A generated line cannot be resolved there. Reproduce the error under "luam dev" or "luam ensure" for a source position.');
23135
24626
  return EXIT_DIAGNOSTICS;
23136
24627
  }
23137
- let failed2 = false;
24628
+ let failed3 = false;
23138
24629
  for (const line2 of lines) {
23139
24630
  const generated = parseTracePosition(line2);
23140
24631
  if (generated === null) {
23141
24632
  reporter.error(`Trace could not find a file and line in "${line2}".`);
23142
- failed2 = true;
24633
+ failed3 = true;
23143
24634
  continue;
23144
24635
  }
23145
24636
  const file = generatedFile(loaded.map.resource, generated.file);
23146
24637
  const resolution = resolveResourcePosition(loaded.map, file, generated.line);
23147
24638
  if (resolution.status === "unknown-version") {
23148
24639
  reporter.error(`Resource map version ${resolution.version} is not supported.`);
23149
- failed2 = true;
24640
+ failed3 = true;
23150
24641
  continue;
23151
24642
  }
23152
24643
  if (resolution.status === "uncovered") {
23153
24644
  reporter.error(`Resource map does not cover "${generated.file}:${generated.line}".`);
23154
- failed2 = true;
24645
+ failed3 = true;
23155
24646
  continue;
23156
24647
  }
23157
24648
  const symbol = resolution.position.symbol === void 0 ? "" : ` (${resolution.position.symbol})`;
23158
24649
  reporter.raw(`${resolution.position.file}:${resolution.position.line}${symbol}`);
23159
24650
  }
23160
- return failed2 ? EXIT_DIAGNOSTICS : EXIT_OK;
24651
+ return failed3 ? EXIT_DIAGNOSTICS : EXIT_OK;
23161
24652
  }
23162
24653
 
23163
24654
  // src/cli/registry/trace-registration.ts
@@ -23178,6 +24669,7 @@ function registerTraceCommand(program2, runtime) {
23178
24669
  var COMMAND_REGISTRARS = [
23179
24670
  registerBuildCommand,
23180
24671
  registerCheckCommand,
24672
+ registerConfigCommand,
23181
24673
  registerDevCommand,
23182
24674
  registerDoctorCommand,
23183
24675
  registerEnsureCommand,