@thigasdevelopment/luam 0.17.0 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/luam.mjs CHANGED
@@ -4515,6 +4515,10 @@ function parseDecorators(stream) {
4515
4515
  skipDecoratorArguments(stream);
4516
4516
  }
4517
4517
  }
4518
+ const last = decorators[decorators.length - 1];
4519
+ if (last !== void 0 && !stream.isEof() && stream.current().position.line === last.position.line) {
4520
+ stream.report("parse-decorator-line", `Expected a line break after "@${last.name}".`, stream.current().position);
4521
+ }
4518
4522
  return decorators;
4519
4523
  }
4520
4524
  function parseClassMethod(stream, token, decorators) {
@@ -4653,7 +4657,7 @@ function parseInterfaceDeclaration(stream) {
4653
4657
  stream.eraseFrom(checkpoint, "declaration");
4654
4658
  return declaration;
4655
4659
  }
4656
- function parseEnumDeclaration(stream) {
4660
+ function parseEnumDeclaration(stream, isLocal = false) {
4657
4661
  const position2 = stream.next().position;
4658
4662
  const name = stream.expect("identifier").value;
4659
4663
  const members = [];
@@ -4666,7 +4670,7 @@ function parseEnumDeclaration(stream) {
4666
4670
  }
4667
4671
  }
4668
4672
  stream.expect("punctuation", "}");
4669
- return { kind: "enum-declaration", name, members, position: position2 };
4673
+ return { kind: "enum-declaration", name, isLocal, members, position: position2 };
4670
4674
  }
4671
4675
  function parseDeclaration(stream) {
4672
4676
  const decorators = parseDecorators(stream);
@@ -4829,7 +4833,10 @@ function parseKeywordStatement(stream, value) {
4829
4833
  const token = stream.current();
4830
4834
  if (value === "local") {
4831
4835
  stream.next();
4832
- return stream.check("keyword", "function") ? parseFunctionDeclaration(stream, true) : parseLocalStatement(stream, token.position);
4836
+ if (stream.check("keyword", "function")) {
4837
+ return parseFunctionDeclaration(stream, true);
4838
+ }
4839
+ return stream.check("keyword", "enum") ? parseEnumDeclaration(stream, true) : parseLocalStatement(stream, token.position);
4833
4840
  }
4834
4841
  if (value === "function") {
4835
4842
  return parseFunctionDeclaration(stream, false);
@@ -5558,7 +5565,7 @@ var MANIFEST_FIELDS = [
5558
5565
  field("author", OPTIONAL_STRING, "Author written to the generated meta.xml.", { owner: "identity" }),
5559
5566
  field("version", OPTIONAL_STRING, "Version written to the generated meta.xml.", { owner: "identity" }),
5560
5567
  field("description", OPTIONAL_STRING, "Description written to the generated meta.xml.", { owner: "identity" }),
5561
- table("compilerOptions", "Settings that change how the source is checked and emitted.", COMPILER_OPTION_FIELDS, { defaultValue: {}, owner: "compiler" }),
5568
+ table("compiler", "Settings that change how the source is checked and emitted.", COMPILER_OPTION_FIELDS, { defaultValue: {}, owner: "compiler" }),
5562
5569
  table("sources", "Paths and patterns that make up the project, grouped by the side each file runs on.", SOURCE_FIELDS, {
5563
5570
  defaultValue: {},
5564
5571
  owner: "sources"
@@ -5603,7 +5610,8 @@ var MANIFEST_FIELDS = [
5603
5610
  var MANIFEST_RECORD = recordType("Manifest", MANIFEST_FIELDS);
5604
5611
  var ENV_MEMBER_TYPE = OPTIONAL_STRING;
5605
5612
  var REMOVED_FIELDS = {
5606
- oop: 'Move it to "compilerOptions = { oop = true }".',
5613
+ oop: 'Move it to "compiler = { oop = true }".',
5614
+ compilerOptions: 'Rename it to "compiler = { ... }".',
5607
5615
  sourceDirs: 'Replace it with "sources = { server = { ... }, client = { ... }, shared = { ... } }", listing paths or patterns per side.',
5608
5616
  assetDirs: `Replace it with "assets = { { from = 'assets/**/*', to = 'assets' } }", naming a destination for each entry.`,
5609
5617
  mta: `Replace it with "engine = { minVersion = '1.6.0' }".`,
@@ -6411,7 +6419,7 @@ function flag(source, name, fallback) {
6411
6419
  return readBoolean(source, name) ?? fallback;
6412
6420
  }
6413
6421
  function readCompilerOptions(value) {
6414
- const source = readTable(value, "compilerOptions") ?? EMPTY;
6422
+ const source = readTable(value, "compiler") ?? EMPTY;
6415
6423
  return {
6416
6424
  strict: flag(source, "strict", DEFAULT_COMPILER_OPTIONS.strict),
6417
6425
  oop: flag(source, "oop", DEFAULT_COMPILER_OPTIONS.oop),
@@ -6576,7 +6584,7 @@ import { tmpdir } from "node:os";
6576
6584
  import { join as join2 } from "node:path";
6577
6585
 
6578
6586
  // src/cli/version.ts
6579
- var VERSION = true ? "0.17.0" : "0.0.0-dev";
6587
+ var VERSION = true ? "0.18.0" : "0.0.0-dev";
6580
6588
  var PROGRAM_NAME = "luam";
6581
6589
  var PROGRAM_DESCRIPTION = "luam \u2014 the Luam compiler for Multi Theft Auto resources.";
6582
6590
 
@@ -8119,7 +8127,7 @@ function ambientFromRegistry(registry) {
8119
8127
  return {
8120
8128
  classes: registry.allClasses(),
8121
8129
  interfaces: registry.allInterfaces(),
8122
- enums: registry.allEnums(),
8130
+ enums: registry.allEnums().filter((info) => info.isLocal !== true),
8123
8131
  globals: registry.allGlobals(),
8124
8132
  events: registry.allEvents()
8125
8133
  };
@@ -13425,8 +13433,10 @@ var CheckContext = class {
13425
13433
  returnStack = [];
13426
13434
  methodStack = [];
13427
13435
  projectEnvironments = /* @__PURE__ */ new Map();
13436
+ predeclared = /* @__PURE__ */ new Map();
13428
13437
  ambientClasses = /* @__PURE__ */ new Set();
13429
13438
  ambientInterfaces = /* @__PURE__ */ new Set();
13439
+ ambientEnums = /* @__PURE__ */ new Set();
13430
13440
  constructor(mode, environment, ambient = EMPTY_AMBIENT, project = EMPTY_PROJECT_DECLARATIONS, oop = false) {
13431
13441
  this.mode = mode;
13432
13442
  this.environment = environment;
@@ -13465,12 +13475,41 @@ var CheckContext = class {
13465
13475
  }
13466
13476
  return null;
13467
13477
  }
13478
+ predeclareClass(info) {
13479
+ this.predeclared.set(info.name, info);
13480
+ }
13481
+ takePredeclaredClass(name) {
13482
+ const info = this.predeclared.get(name) ?? null;
13483
+ this.predeclared.delete(name);
13484
+ return info;
13485
+ }
13486
+ isPredeclaredClass(name) {
13487
+ return this.predeclared.has(name);
13488
+ }
13489
+ awaitsDeclaration(name) {
13490
+ const seen = /* @__PURE__ */ new Set();
13491
+ let current = this.declarations.lookupClass(name);
13492
+ while (current !== null && !seen.has(current.name)) {
13493
+ if (this.predeclared.has(current.name)) {
13494
+ return true;
13495
+ }
13496
+ seen.add(current.name);
13497
+ current = current.superClass === null ? null : this.declarations.lookupClass(current.superClass);
13498
+ }
13499
+ return false;
13500
+ }
13501
+ insideFunction() {
13502
+ return this.returnStack.length > 0;
13503
+ }
13468
13504
  isAmbientClass(name) {
13469
13505
  return this.ambientClasses.has(name);
13470
13506
  }
13471
13507
  isAmbientInterface(name) {
13472
13508
  return this.ambientInterfaces.has(name);
13473
13509
  }
13510
+ isAmbientEnum(name) {
13511
+ return this.ambientEnums.has(name);
13512
+ }
13474
13513
  noteExternalReference(name, position2) {
13475
13514
  if (!this.externalReferences.has(name)) {
13476
13515
  this.externalReferences.set(name, position2);
@@ -13623,6 +13662,7 @@ var CheckContext = class {
13623
13662
  this.declarations.declareInterface(info);
13624
13663
  }
13625
13664
  for (const info of ambient.enums) {
13665
+ this.ambientEnums.add(info.name);
13626
13666
  this.declarations.declareEnum(info);
13627
13667
  this.binder.declareGlobal({ name: info.name, type: createNamed(info.name), isLocal: false, position: info.position });
13628
13668
  }
@@ -13929,23 +13969,168 @@ function accessorNames(fieldName, isBoolean) {
13929
13969
  };
13930
13970
  }
13931
13971
 
13932
- // ../compiler/src/checker/decorators.ts
13972
+ // ../compiler/src/checker/decorator-catalog.ts
13973
+ var CONFLICT = "`check-decorator-conflict` when a generated name is already declared by a hand-written member.";
13974
+ var SPREAD = "On a class it applies to every field of that class; on a field it applies to that field alone.";
13975
+ var NOTHING = "Nothing. The emitted Lua is unchanged; the decorator only makes the checker validate how the class is used.";
13933
13976
  var KNOWN_DECORATORS = /* @__PURE__ */ new Map([
13934
- ["Getter", { name: "Getter", documentation: "Generates a typed getter for each decorated field." }],
13935
- ["Setter", { name: "Setter", documentation: "Generates a typed setter for each decorated field." }],
13936
- ["FluentSetter", { name: "FluentSetter", documentation: "Generates a setter that returns self." }],
13937
- ["ToString", { name: "ToString", documentation: "Generates a shallow string representation." }],
13938
- ["Equals", { name: "Equals", documentation: "Generates shallow field equality." }],
13939
- ["Clone", { name: "Clone", documentation: "Generates a shallow clone." }],
13940
- ["Serializable", { name: "Serializable", documentation: "Generates toTable()." }],
13941
- ["Deserialize", { name: "Deserialize", documentation: "Generates fromTable(values)." }],
13942
- ["Lazy", { name: "Lazy", documentation: "Generates a caching getter for an initialized field." }],
13943
- ["Observable", { name: "Observable", documentation: "Generates a notifying setter and listener registration." }],
13944
- ["ReadOnly", { name: "ReadOnly", documentation: "Prevents writes outside methods of the declaring class." }],
13945
- ["Deprecated", { name: "Deprecated", documentation: "Reports a warning when the decorated member is used." }],
13946
- ["Override", { name: "Override", documentation: "Requires a matching superclass method." }],
13947
- ["Builder", { name: "Builder", documentation: "Generates a companion builder class." }]
13977
+ [
13978
+ "Getter",
13979
+ {
13980
+ name: "Getter",
13981
+ documentation: "Generates a typed getter for each decorated field.",
13982
+ targets: ["class", "field"],
13983
+ generates: ["`getField(): FieldType`, returning the field unchanged.", "A boolean field uses `isField()`, and a boolean already named `isReady` keeps `isReady()` instead of `isIsReady()`."],
13984
+ rules: [SPREAD, "The accessor is an ordinary member: completion after `:` lists it, and go-to-definition lands on the field that generated it."],
13985
+ diagnostics: [CONFLICT]
13986
+ }
13987
+ ],
13988
+ [
13989
+ "Setter",
13990
+ {
13991
+ name: "Setter",
13992
+ documentation: "Generates a typed setter for each decorated field.",
13993
+ targets: ["class", "field"],
13994
+ generates: ["`setField(value: FieldType): void`, assigning the field.", "A boolean field named `isReady` becomes `setReady(value)`."],
13995
+ rules: [SPREAD, "The parameter is typed from the field, so a wrong argument is `check-type-mismatch`, exactly as for a hand-written method."],
13996
+ diagnostics: [CONFLICT]
13997
+ }
13998
+ ],
13999
+ [
14000
+ "FluentSetter",
14001
+ {
14002
+ name: "FluentSetter",
14003
+ documentation: "Generates a chainable setter that returns the same instance.",
14004
+ targets: ["field"],
14005
+ generates: ["`withField(value: FieldType): OwnerClass`, assigning the field and returning `self`."],
14006
+ rules: ["The returned value is the same instance, never a copy, so calls chain: `session:withTimeout(60):withTimeout(90)`.", "It does not replace `@Setter`; declare both when `setField` is wanted as well."],
14007
+ diagnostics: [CONFLICT]
14008
+ }
14009
+ ],
14010
+ [
14011
+ "ToString",
14012
+ {
14013
+ name: "ToString",
14014
+ documentation: "Generates a shallow string representation.",
14015
+ targets: ["class"],
14016
+ generates: ["`toString(): string`, formatted as `ClassName{field=value, field=value}`."],
14017
+ rules: ["Every field of the class is included, in declaration order, through `tostring`.", "It is shallow: a table field is rendered as its `tostring` address, not as its contents."],
14018
+ diagnostics: [CONFLICT]
14019
+ }
14020
+ ],
14021
+ [
14022
+ "Equals",
14023
+ {
14024
+ name: "Equals",
14025
+ documentation: "Generates shallow field equality.",
14026
+ targets: ["class"],
14027
+ generates: ["`equals(other: OwnerClass): boolean`, comparing every field with `==`."],
14028
+ rules: ["It is shallow: two tables with the same contents are equal only when they are the same table.", "It does not overload the Lua `==` operator; call `equals` explicitly."],
14029
+ diagnostics: [CONFLICT]
14030
+ }
14031
+ ],
14032
+ [
14033
+ "Clone",
14034
+ {
14035
+ name: "Clone",
14036
+ documentation: "Generates a shallow clone.",
14037
+ targets: ["class"],
14038
+ generates: ["`clone(): OwnerClass`, a new instance carrying the same field values."],
14039
+ rules: ["The copy is built with `new OwnerClass()` and no constructor arguments, then every field is assigned.", "It is shallow: a table stored in a field is the same table in the clone."],
14040
+ diagnostics: [CONFLICT]
14041
+ }
14042
+ ],
14043
+ [
14044
+ "Serializable",
14045
+ {
14046
+ name: "Serializable",
14047
+ documentation: "Generates a plain-table view of the fields.",
14048
+ targets: ["class"],
14049
+ generates: ["`toTable(): table`, a plain table keyed by field name."],
14050
+ rules: ["It is shallow: a table stored in a field is the same table in the result.", "It pairs with `@Deserialize` for a round trip."],
14051
+ diagnostics: [CONFLICT]
14052
+ }
14053
+ ],
14054
+ [
14055
+ "Deserialize",
14056
+ {
14057
+ name: "Deserialize",
14058
+ documentation: "Generates field assignment from a plain table.",
14059
+ targets: ["class"],
14060
+ generates: ["`fromTable(values: table): void`, assigning the fields of the current instance."],
14061
+ rules: ["It assigns onto the existing instance and returns nothing.", "Every field is assigned, so a key missing from `values` sets that field to `nil`."],
14062
+ diagnostics: [CONFLICT]
14063
+ }
14064
+ ],
14065
+ [
14066
+ "Lazy",
14067
+ {
14068
+ name: "Lazy",
14069
+ documentation: "Generates a caching getter for an initialized field.",
14070
+ targets: ["field"],
14071
+ generates: ["`getField(): FieldType`, running the initializer on first access and reusing the result afterwards."],
14072
+ rules: ["The initializer is not emitted as an initial class value; the getter runs it while the field is still `nil`.", "Naming follows `@Getter`, so a boolean field yields `isField()`."],
14073
+ diagnostics: ["`check-lazy-initializer` when the decorated field has no initializer.", CONFLICT]
14074
+ }
14075
+ ],
14076
+ [
14077
+ "Observable",
14078
+ {
14079
+ name: "Observable",
14080
+ documentation: "Generates a notifying setter and listener registration.",
14081
+ targets: ["field"],
14082
+ generates: ["`setField(value: FieldType): void`, assigning the field and then calling every listener with the new value.", "`onFieldChanged(listener): void`, appending a listener."],
14083
+ rules: ["Listeners live on the instance and run in registration order.", "Only the generated setter notifies; assigning `instance.field = value` directly does not."],
14084
+ diagnostics: [CONFLICT]
14085
+ }
14086
+ ],
14087
+ [
14088
+ "ReadOnly",
14089
+ {
14090
+ name: "ReadOnly",
14091
+ documentation: "Prevents writes outside methods of the declaring class.",
14092
+ targets: ["field"],
14093
+ generates: [NOTHING],
14094
+ rules: ["Reads are always allowed.", "Methods of the declaring class may assign the field; every other place may not."],
14095
+ diagnostics: ["`check-readonly-assignment` on an assignment outside the declaring class."]
14096
+ }
14097
+ ],
14098
+ [
14099
+ "Deprecated",
14100
+ {
14101
+ name: "Deprecated",
14102
+ documentation: "Reports a warning when the decorated member is used.",
14103
+ targets: ["field", "method"],
14104
+ generates: [NOTHING],
14105
+ rules: ["The report lands on every use site, not on the declaration.", "It is a warning, so it never blocks the build."],
14106
+ diagnostics: ["`check-deprecated-use` at every read or call of the member."]
14107
+ }
14108
+ ],
14109
+ [
14110
+ "Override",
14111
+ {
14112
+ name: "Override",
14113
+ documentation: "Requires a matching superclass method.",
14114
+ targets: ["method"],
14115
+ generates: [NOTHING],
14116
+ rules: ["The class must declare a parent with `extends`, and that parent must declare the method with an identical signature.", "It does not change dispatch; call the parent implementation with `super(...)`."],
14117
+ diagnostics: ["`check-invalid-override` when the superclass has no such method or the signature differs."]
14118
+ }
14119
+ ],
14120
+ [
14121
+ "Builder",
14122
+ {
14123
+ name: "Builder",
14124
+ documentation: "Generates a companion builder class.",
14125
+ targets: ["class"],
14126
+ generates: ["`OwnerClassBuilder`, a separate class declared beside the decorated one.", "`withField(value: FieldType): OwnerClassBuilder` for every field, and `build(): OwnerClass`."],
14127
+ rules: ["The builder adds no member to the decorated class; construct it with `new OwnerClassBuilder()`.", "`build()` calls `new OwnerClass()` with no constructor arguments, then assigns the collected fields."],
14128
+ diagnostics: [CONFLICT]
14129
+ }
14130
+ ]
13948
14131
  ]);
14132
+
14133
+ // ../compiler/src/checker/decorators.ts
13949
14134
  function memberExpression(field2) {
13950
14135
  return {
13951
14136
  kind: "member-expression",
@@ -13988,7 +14173,8 @@ function validateDecorators(context, decorators, target) {
13988
14173
  const valid = [];
13989
14174
  const seen = /* @__PURE__ */ new Set();
13990
14175
  for (const decorator of decorators) {
13991
- if (!KNOWN_DECORATORS.has(decorator.name)) {
14176
+ const definition = KNOWN_DECORATORS.get(decorator.name);
14177
+ if (definition === void 0) {
13992
14178
  context.report("check-unknown-decorator", `Unknown decorator "@${decorator.name}".`, decorator.position);
13993
14179
  continue;
13994
14180
  }
@@ -13997,13 +14183,7 @@ function validateDecorators(context, decorators, target) {
13997
14183
  continue;
13998
14184
  }
13999
14185
  seen.add(decorator.name);
14000
- const classOnly = /* @__PURE__ */ new Set(["ToString", "Equals", "Clone", "Serializable", "Deserialize", "Builder"]);
14001
- const methodOnly = /* @__PURE__ */ new Set(["Override"]);
14002
- const memberOnly = /* @__PURE__ */ new Set(["Deprecated"]);
14003
- const classAllowed = /* @__PURE__ */ new Set(["Getter", "Setter", ...classOnly]);
14004
- const fieldAllowed = /* @__PURE__ */ new Set(["Getter", "Setter", "FluentSetter", "Lazy", "Observable", "ReadOnly", ...memberOnly]);
14005
- const methodAllowed = /* @__PURE__ */ new Set([...methodOnly, ...memberOnly]);
14006
- if (target === "class" && !classAllowed.has(decorator.name) || target === "field" && !fieldAllowed.has(decorator.name) || target === "method" && !methodAllowed.has(decorator.name)) {
14186
+ if (!definition.targets.includes(target)) {
14007
14187
  context.report("check-decorator-target", `Decorator "@${decorator.name}" is not valid on this ${target}.`, decorator.position);
14008
14188
  continue;
14009
14189
  }
@@ -17469,7 +17649,7 @@ function isElementType(name) {
17469
17649
  }
17470
17650
 
17471
17651
  // ../compiler/src/checker/oop-members.ts
17472
- var OOP_HINT = 'Set "compilerOptions = { oop = true }" in .luam.manifest to enable the MTA OOP API.';
17652
+ var OOP_HINT = 'Set "compiler = { oop = true }" in .luam.manifest to enable the MTA OOP API.';
17473
17653
  function scopeLabel2(environment) {
17474
17654
  return environment === "shared" ? "shared" : `${environment}-only`;
17475
17655
  }
@@ -17609,6 +17789,9 @@ function resolveNamedMember(context, name, expression) {
17609
17789
  if (isMtaElement(context, name)) {
17610
17790
  return resolveMtaMember(context, name, expression.property, expression.position)?.type ?? ANY_TYPE;
17611
17791
  }
17792
+ if (context.awaitsDeclaration(name)) {
17793
+ return ANY_TYPE;
17794
+ }
17612
17795
  const contract = context.declarations.lookupClass(name) === null ? context.declarations.lookupInterface(name) : null;
17613
17796
  if (contract === null) {
17614
17797
  return null;
@@ -17638,6 +17821,10 @@ function checkNewExpression(context, expression) {
17638
17821
  context.report("check-unknown-class", `Class "${expression.className}" is not defined.`, expression.position);
17639
17822
  return ANY_TYPE;
17640
17823
  }
17824
+ if (context.isPredeclaredClass(expression.className) && !context.insideFunction()) {
17825
+ const message = `Class "${expression.className}" is declared further down this file, so it does not exist yet at this point.`;
17826
+ context.report("check-class-before-declaration", message, expression.position);
17827
+ }
17641
17828
  const constructor = context.declarations.lookupMember(expression.className, "constructor");
17642
17829
  if (constructor !== null && constructor.type.kind === "function") {
17643
17830
  checkSignature(context, expression.args, constructor.type, expression.position);
@@ -17723,30 +17910,6 @@ function checkUnary(context, operator, operand, expression) {
17723
17910
  return context.record(expression, NUMBER_TYPE);
17724
17911
  }
17725
17912
 
17726
- // ../compiler/src/checker/template.ts
17727
- function parseInterpolation(segment) {
17728
- const separator = segment.value.indexOf(":");
17729
- const path = (separator === -1 ? segment.value : segment.value.slice(0, separator)).trim();
17730
- const fallback = separator === -1 ? null : segment.value.slice(separator + 1).trim();
17731
- const root = path.split(".")[0] ?? "";
17732
- return { raw: segment.value, path, root, fallback, position: segment.position };
17733
- }
17734
- function collectInterpolations(segments) {
17735
- return segments.filter((segment) => segment.kind === "interpolation").map(parseInterpolation);
17736
- }
17737
- function collectTemplateRoots(segments) {
17738
- const roots = [];
17739
- for (const interpolation of collectInterpolations(segments)) {
17740
- if (interpolation.root.length > 0 && !roots.includes(interpolation.root)) {
17741
- roots.push(interpolation.root);
17742
- }
17743
- }
17744
- return roots;
17745
- }
17746
- function templateLiteralText(segments) {
17747
- return segments.map((segment) => segment.kind === "text" ? segment.value : `\${${segment.value}}`).join("");
17748
- }
17749
-
17750
17913
  // ../compiler/src/checker/union-members.ts
17751
17914
  function isShapeOption(context, option) {
17752
17915
  if (option.kind === "record") {
@@ -17788,990 +17951,1073 @@ function resolveUnionMember(context, type, expression) {
17788
17951
  return ANY_TYPE;
17789
17952
  }
17790
17953
 
17791
- // ../compiler/src/checker/expressions.ts
17792
- var EXTENSION_RESULTS = {
17793
- boolean: BOOLEAN_TYPE,
17794
- number: NUMBER_TYPE,
17795
- string: STRING_TYPE,
17796
- table: TABLE_TYPE
17797
- };
17798
- function isNativeConstruction(context, expression) {
17799
- const object = expression.object;
17800
- if (expression.property !== NATIVE_CONSTRUCTOR || object.kind !== "identifier") {
17801
- return false;
17802
- }
17803
- return nativeConstructor(context, object.name) !== null;
17804
- }
17805
- function checkMember(context, expression) {
17806
- if (expression.object.kind === "identifier" && isMtaClassReference(context, expression.object.name)) {
17807
- context.references.add(expression.object.name);
17808
- return context.record(expression, resolveMtaStaticMember(context, expression.object.name, expression.property, expression.position)?.type ?? ANY_TYPE);
17954
+ // ../compiler/src/checker/discriminant.ts
17955
+ function literalValue(expression) {
17956
+ if (expression.kind === "string-literal") {
17957
+ return createStringLiteral(expression.value);
17809
17958
  }
17810
- if (isNativeConstruction(context, expression) && expression.object.kind === "identifier") {
17811
- const name = expression.object.name;
17812
- const message = `Construct "${name}" with "new ${name}(...)". The "${name}.new(...)" form is not part of the language.`;
17813
- context.report("check-native-constructor", message, expression.position);
17959
+ if (expression.kind === "boolean-literal") {
17960
+ return createBooleanLiteral(expression.value);
17814
17961
  }
17815
- const library = resolveLibraryMember(context, expression);
17816
- if (library !== null) {
17817
- checkExpression(context, expression.object);
17818
- return context.record(expression, library);
17962
+ return expression.kind === "number-literal" ? createNumberLiteral(expression.value) : null;
17963
+ }
17964
+ function discriminantTest(left, right) {
17965
+ if (left.kind !== "member-expression" || left.object.kind !== "identifier") {
17966
+ return null;
17819
17967
  }
17820
- const objectType = checkExpression(context, expression.object);
17821
- if (objectType.kind === "record") {
17822
- return context.record(expression, resolveRecordMember(context, objectType, expression));
17968
+ const value = literalValue(right);
17969
+ return value === null ? null : { name: left.object.name, property: left.property, value };
17970
+ }
17971
+ function subjectType(context, name) {
17972
+ const type = context.narrowedType(name) ?? context.binder.lookup(name)?.type ?? null;
17973
+ return type === null ? null : withoutNil(type);
17974
+ }
17975
+ function filterOptions(context, test, keep) {
17976
+ const subject = subjectType(context, test.name);
17977
+ if (subject === null || subject.kind !== "union") {
17978
+ return null;
17823
17979
  }
17824
- if (objectType.kind === "union") {
17825
- const option = resolveUnionMember(context, objectType, expression);
17826
- if (option !== null) {
17827
- return context.record(expression, option);
17980
+ const matched = [];
17981
+ for (const option of subject.options) {
17982
+ const member = optionMemberType(context, option, test.property);
17983
+ if (member === null) {
17984
+ return null;
17985
+ }
17986
+ if (isAssignable(test.value, member) === keep) {
17987
+ matched.push(option);
17828
17988
  }
17829
17989
  }
17830
- if (objectType.kind === "map" && isAssignable(STRING_TYPE, objectType.key)) {
17831
- return context.record(expression, objectType.value);
17832
- }
17833
- const named2 = objectType.kind === "named" ? resolveNamedMember(context, objectType.name, expression) : null;
17834
- if (named2 !== null) {
17835
- return context.record(expression, named2);
17990
+ return matched.length === 0 || matched.length === subject.options.length ? null : createUnion(matched);
17991
+ }
17992
+ function discriminantFact(context, left, right, keep) {
17993
+ const test = discriminantTest(left, right) ?? discriminantTest(right, left);
17994
+ if (test === null) {
17995
+ return null;
17836
17996
  }
17837
- const extension = extensionFor(objectType, expression.property);
17838
- if (extension !== null) {
17839
- reportExtensionForm(context, expression, extension);
17997
+ const narrowed = filterOptions(context, test, keep);
17998
+ return narrowed === null ? null : [test.name, narrowed];
17999
+ }
18000
+
18001
+ // ../compiler/src/checker/narrowing.ts
18002
+ var GUARD_FUNCTION = "type";
18003
+ var GUARD_TYPES = {
18004
+ boolean: BOOLEAN_TYPE,
18005
+ function: createFunction([], ANY_TYPE, 0, true),
18006
+ nil: NIL_TYPE,
18007
+ number: NUMBER_TYPE,
18008
+ string: STRING_TYPE,
18009
+ table: TABLE_TYPE,
18010
+ thread: THREAD_TYPE,
18011
+ userdata: USERDATA_TYPE
18012
+ };
18013
+ function declaredType(context, name) {
18014
+ return context.binder.lookup(name)?.type ?? null;
18015
+ }
18016
+ function guardedName(expression) {
18017
+ if (expression.kind !== "call-expression" || expression.callee.kind !== "identifier" || expression.callee.name !== GUARD_FUNCTION) {
18018
+ return null;
17840
18019
  }
17841
- return context.record(expression, extension === null ? ANY_TYPE : EXTENSION_RESULTS[extension.result]);
18020
+ const [argument] = expression.args;
18021
+ return argument !== void 0 && argument.kind === "identifier" ? argument.name : null;
17842
18022
  }
17843
- function countArguments(total) {
17844
- return total === 1 ? "1 argument" : `${total} arguments`;
18023
+ function guardFact(left, right) {
18024
+ const name = guardedName(left);
18025
+ const value = right.kind === "string-literal" ? GUARD_TYPES[right.value] : void 0;
18026
+ return name === null || value === void 0 ? null : [name, value];
17845
18027
  }
17846
- function checkSignature(context, args, signature, position2) {
17847
- const argumentTypes = checkValueList(context, args, signature.parameters);
17848
- if (argumentTypes.length < signature.minimumArguments) {
17849
- const message = `This call expects at least ${countArguments(signature.minimumArguments)} but received ${argumentTypes.length}.`;
17850
- context.report("check-argument-count", message, position2);
18028
+ function nilComparison(left, right) {
18029
+ if (left.kind === "identifier" && right.kind === "nil-literal") {
18030
+ return left.name;
17851
18031
  }
17852
- if (!signature.isVariadic && argumentTypes.length > signature.parameters.length) {
17853
- const message = `This call expects at most ${countArguments(signature.parameters.length)} but received ${argumentTypes.length}.`;
17854
- context.report("check-argument-count", message, position2);
18032
+ return right.kind === "identifier" && left.kind === "nil-literal" ? right.name : null;
18033
+ }
18034
+ function present2(context, name, facts) {
18035
+ const declared = declaredType(context, name);
18036
+ if (declared !== null) {
18037
+ facts.set(name, withoutNil(declared));
17855
18038
  }
17856
- signature.parameters.forEach((parameter, index) => {
17857
- const argumentType = argumentTypes[index];
17858
- const argument = args[index];
17859
- if (argumentType !== void 0 && argument !== void 0) {
17860
- context.expectAssignable(argumentType, parameter, argument.position, `Argument ${index + 1}`);
17861
- }
17862
- });
17863
- if (signature.isVariadic && signature.variadicType !== void 0) {
17864
- for (let index = signature.parameters.length; index < argumentTypes.length; index += 1) {
17865
- const argumentType = argumentTypes[index];
17866
- const argument = args[Math.min(index, args.length - 1)];
17867
- if (argumentType !== void 0 && argument !== void 0) {
17868
- context.expectAssignable(argumentType, signature.variadicType, argument.position, `Argument ${index + 1}`);
17869
- }
18039
+ }
18040
+ function mergeAlternatives(left, right, facts) {
18041
+ for (const [name, type] of left) {
18042
+ const other = right.get(name);
18043
+ if (other !== void 0) {
18044
+ facts.set(name, createUnion([type, other]));
17870
18045
  }
17871
18046
  }
17872
- return signature.returnType;
17873
18047
  }
17874
- function checkArguments(context, expression, calleeType) {
17875
- if (calleeType.kind !== "function") {
17876
- checkValueList(context, expression.args);
17877
- reportNotCallable(context, expression, calleeType);
17878
- return ANY_TYPE;
18048
+ function applyDiscriminant(context, left, right, keep, facts) {
18049
+ const fact = discriminantFact(context, left, right, keep);
18050
+ if (fact !== null) {
18051
+ facts.set(fact[0], fact[1]);
17879
18052
  }
17880
- return checkSignature(context, expression.args, calleeType, expression.position);
17881
18053
  }
17882
- function isSuperCall(expression) {
17883
- return expression.method === null && expression.callee.kind === "identifier" && expression.callee.name === "super";
17884
- }
17885
- function isLegacySuperCall(expression) {
17886
- return expression.method === "super" && expression.callee.kind === "identifier" && expression.callee.name === "self";
17887
- }
17888
- function checkMethodCall(context, expression, method, receiver) {
17889
- if (receiver.kind !== "named") {
17890
- checkValueList(context, expression.args);
17891
- return ANY_TYPE;
18054
+ function collectFacts(context, expression, facts) {
18055
+ if (expression.kind === "group-expression") {
18056
+ collectFacts(context, expression.expression, facts);
18057
+ return;
17892
18058
  }
17893
- const declared = context.declarations.lookupMember(receiver.name, method);
17894
- if (declared?.type.kind === "function") {
17895
- if (declared.deprecated === true) {
17896
- context.warn("check-deprecated-use", `Member "${method}" is deprecated.`, expression.position);
17897
- }
17898
- return checkSignature(context, expression.args, declared.type, expression.position);
18059
+ if (expression.kind === "identifier") {
18060
+ present2(context, expression.name, facts);
18061
+ return;
17899
18062
  }
17900
- if (!isMtaElement(context, receiver.name)) {
17901
- checkValueList(context, expression.args);
17902
- return ANY_TYPE;
18063
+ if (expression.kind !== "binary-expression") {
18064
+ return;
17903
18065
  }
17904
- const member = resolveMtaMember(context, receiver.name, method, expression.position);
17905
- if (member === null || member.type.kind !== "function") {
17906
- checkValueList(context, expression.args);
17907
- return ANY_TYPE;
18066
+ if (expression.operator === "and") {
18067
+ collectFacts(context, expression.left, facts);
18068
+ collectFacts(context, expression.right, facts);
18069
+ return;
17908
18070
  }
17909
- return checkSignature(context, expression.args, member.type, expression.position);
17910
- }
17911
- function checkCall(context, expression) {
17912
- if (isLegacySuperCall(expression)) {
17913
- checkValueList(context, expression.args);
17914
- context.report("check-invalid-super", 'Call "super(...)" directly instead of "self:super(...)".', expression.position);
17915
- return context.record(expression, ANY_TYPE);
18071
+ if (expression.operator === "or") {
18072
+ mergeAlternatives(conditionFacts(context, expression.left), conditionFacts(context, expression.right), facts);
18073
+ return;
17916
18074
  }
17917
- if (isSuperCall(expression)) {
17918
- return context.record(expression, checkSuperCall(context, expression));
18075
+ if (expression.operator === "==") {
18076
+ const guard = guardFact(expression.left, expression.right) ?? guardFact(expression.right, expression.left);
18077
+ const missing = nilComparison(expression.left, expression.right);
18078
+ if (guard !== null) {
18079
+ facts.set(guard[0], guard[1]);
18080
+ } else if (missing !== null) {
18081
+ facts.set(missing, NIL_TYPE);
18082
+ } else {
18083
+ applyDiscriminant(context, expression.left, expression.right, true, facts);
18084
+ }
18085
+ return;
17919
18086
  }
17920
- if (expression.method === null && expression.callee.kind === "identifier" && isMtaClassReference(context, expression.callee.name)) {
17921
- context.references.add(expression.callee.name);
17922
- const constructor = resolveMtaConstructor(context, expression.callee.name, expression.position);
17923
- if (constructor === null) {
17924
- checkValueList(context, expression.args);
17925
- return context.record(expression, ANY_TYPE);
18087
+ if (expression.operator === "~=") {
18088
+ const name = nilComparison(expression.left, expression.right);
18089
+ if (name !== null) {
18090
+ present2(context, name, facts);
18091
+ } else {
18092
+ applyDiscriminant(context, expression.left, expression.right, false, facts);
17926
18093
  }
17927
- return context.record(expression, checkSignature(context, expression.args, constructor, expression.position));
17928
18094
  }
17929
- if (expression.method === null) {
17930
- context.calledMembers.add(expression.callee);
18095
+ }
18096
+ function conditionFacts(context, condition) {
18097
+ const facts = /* @__PURE__ */ new Map();
18098
+ collectFacts(context, condition, facts);
18099
+ return facts;
18100
+ }
18101
+ function alwaysExits(body) {
18102
+ const last = body[body.length - 1];
18103
+ if (last === void 0) {
18104
+ return false;
17931
18105
  }
17932
- const calleeType = checkExpression(context, expression.callee);
17933
- if (expression.method !== null) {
17934
- return context.record(expression, checkMethodCall(context, expression, expression.method, calleeType));
18106
+ if (last.kind === "return-statement" || last.kind === "break-statement") {
18107
+ return true;
17935
18108
  }
17936
- if (expression.callee.kind === "identifier" && context.binder.isBuiltinReference(expression.callee.name)) {
17937
- checkEventUsage(context, expression);
17938
- const specialized = specializeEventCall(context, expression, calleeType);
17939
- return context.record(expression, checkArguments(context, expression, specialized ?? calleeType));
18109
+ if (last.kind !== "if-statement" || last.alternate === null) {
18110
+ return false;
17940
18111
  }
17941
- return context.record(expression, checkArguments(context, expression, calleeType));
18112
+ return last.clauses.every((clause) => alwaysExits(clause.body)) && alwaysExits(last.alternate);
17942
18113
  }
17943
- function checkTable(context, expression) {
17944
- const valueTypes = expression.fields.map((field2) => {
17945
- if (field2.key !== null) {
17946
- checkExpression(context, field2.key);
17947
- }
17948
- return checkExpression(context, field2.value);
17949
- });
17950
- const isRecord = expression.fields.every((field2) => field2.name !== null);
17951
- if (isRecord) {
17952
- const members = /* @__PURE__ */ new Map();
17953
- expression.fields.forEach((field2, index) => {
17954
- if (field2.name !== null) {
17955
- members.set(field2.name, valueTypes[index] ?? ANY_TYPE);
17956
- }
17957
- });
17958
- return context.record(expression, createLiteralRecord(members));
17959
- }
17960
- const isArray = expression.fields.every((field2) => field2.key === null && field2.name === null);
17961
- if (!isArray) {
17962
- return context.record(expression, TABLE_TYPE);
18114
+ function guardFacts(context, statement) {
18115
+ const [clause] = statement.kind === "if-statement" ? statement.clauses : [];
18116
+ if (statement.kind !== "if-statement" || statement.alternate !== null || statement.clauses.length !== 1 || clause === void 0) {
18117
+ return /* @__PURE__ */ new Map();
17963
18118
  }
17964
- return context.record(expression, createArray(createUnion(valueTypes)));
18119
+ return alwaysExits(clause.body) ? negatedFacts(context, clause.condition) : /* @__PURE__ */ new Map();
17965
18120
  }
17966
- function checkTemplate(context, expression) {
17967
- for (const interpolation of collectInterpolations(expression.segments)) {
17968
- if (interpolation.root.length === 0) {
17969
- const message = 'Template interpolation is empty. Write "${name}", "${object.field}", or "${name:fallback}".';
17970
- context.report("check-empty-interpolation", message, interpolation.position);
17971
- continue;
17972
- }
17973
- if (context.binder.lookup(interpolation.root) === null) {
17974
- const message = `Template interpolation "${interpolation.path}" refers to "${interpolation.root}", which is not in scope.`;
17975
- context.report("check-unknown-template-root", message, interpolation.position);
18121
+ function negatedFacts(context, condition) {
18122
+ const facts = /* @__PURE__ */ new Map();
18123
+ if (condition.kind === "binary-expression" && condition.operator === "or") {
18124
+ for (const [name, type] of [...negatedFacts(context, condition.left), ...negatedFacts(context, condition.right)]) {
18125
+ facts.set(name, type);
17976
18126
  }
18127
+ return facts;
17977
18128
  }
17978
- return context.record(expression, STRING_TYPE);
17979
- }
17980
- function checkMultiValueExpression(context, expression, expected = null) {
17981
- switch (expression.kind) {
17982
- case "nil-literal":
17983
- return context.record(expression, NIL_TYPE);
17984
- case "boolean-literal":
17985
- return context.record(expression, createBooleanLiteral(expression.value));
17986
- case "number-literal":
17987
- return context.record(expression, createNumberLiteral(expression.value));
17988
- case "string-literal":
17989
- return context.record(expression, createStringLiteral(expression.value));
17990
- case "template-literal":
17991
- return checkTemplate(context, expression);
17992
- case "vararg-expression":
17993
- return context.record(expression, ANY_TYPE);
17994
- case "identifier": {
17995
- context.references.add(expression.name);
17996
- const symbol = context.binder.lookup(expression.name);
17997
- if (symbol === null) {
17998
- checkGlobalReference(context, expression.name, expression.position);
17999
- }
18000
- return context.record(expression, context.narrowedType(expression.name) ?? symbol?.type ?? ANY_TYPE);
18001
- }
18002
- case "member-expression":
18003
- return checkMember(context, expression);
18004
- case "index-expression": {
18005
- const objectType = checkExpression(context, expression.object);
18006
- const indexType = checkExpression(context, expression.index);
18007
- if (objectType.kind === "map") {
18008
- context.expectAssignable(indexType, objectType.key, expression.index.position, "Key");
18009
- return context.record(expression, objectType.value);
18010
- }
18011
- return context.record(expression, objectType.kind === "array" ? objectType.element : ANY_TYPE);
18012
- }
18013
- case "call-expression":
18014
- return checkCall(context, expression);
18015
- case "new-expression":
18016
- return context.record(expression, checkNewExpression(context, expression));
18017
- case "table-expression":
18018
- return checkTable(context, expression);
18019
- case "function-expression": {
18020
- const type = buildFunctionType(context, expression.parameters, expression.returnAnnotation, contextualFunction(expected));
18021
- checkFunctionBody(context, expression.parameters, expression.returnAnnotation, expression.body, type, null);
18022
- return context.record(expression, type);
18023
- }
18024
- case "binary-expression": {
18025
- const left = checkExpression(context, expression.left);
18026
- const right = checkExpression(context, expression.right);
18027
- return checkBinary(context, expression.operator, left, right, expression);
18129
+ if (condition.kind === "binary-expression" && condition.operator === "==") {
18130
+ const name = nilComparison(condition.left, condition.right);
18131
+ if (name !== null) {
18132
+ present2(context, name, facts);
18133
+ } else {
18134
+ applyDiscriminant(context, condition.left, condition.right, false, facts);
18028
18135
  }
18029
- case "unary-expression":
18030
- return checkUnary(context, expression.operator, checkExpression(context, expression.operand), expression);
18031
- default:
18032
- return context.record(expression, checkExpression(context, expression.expression, expected));
18033
18136
  }
18137
+ if (condition.kind === "binary-expression" && condition.operator === "~=") {
18138
+ applyDiscriminant(context, condition.left, condition.right, true, facts);
18139
+ }
18140
+ if (condition.kind === "unary-expression" && condition.operator === "not" && condition.operand.kind === "identifier") {
18141
+ present2(context, condition.operand.name, facts);
18142
+ }
18143
+ return facts;
18034
18144
  }
18035
- function checkExpression(context, expression, expected = null) {
18036
- return firstValueOf(checkMultiValueExpression(context, expression, expected));
18145
+
18146
+ // ../compiler/src/checker/control-flow.ts
18147
+ var ITERATOR_FUNCTIONS = /* @__PURE__ */ new Set(["pairs", "ipairs"]);
18148
+ function checkBlock(context, body) {
18149
+ context.binder.pushScope();
18150
+ checkStatements(context, body);
18151
+ context.binder.popScope();
18037
18152
  }
18038
- function checkValueList(context, values, expected = []) {
18039
- const types = [];
18040
- values.forEach((value, index) => {
18041
- const type = checkMultiValueExpression(context, value, expected[index] ?? null);
18042
- if (index === values.length - 1) {
18043
- types.push(...valuesOf(type));
18044
- return;
18045
- }
18046
- types.push(firstValueOf(type));
18047
- });
18048
- return types;
18153
+ function checkNarrowedBlock(context, facts, body) {
18154
+ context.pushNarrowing(facts);
18155
+ checkBlock(context, body);
18156
+ context.popNarrowing();
18049
18157
  }
18050
-
18051
- // ../compiler/src/checker/classes.ts
18052
- function fieldType(context, member) {
18053
- const valueType = member.value === null ? null : checkExpression(context, member.value);
18054
- if (member.annotation === null) {
18055
- return valueType ?? ANY_TYPE;
18056
- }
18057
- const declared = context.resolveAnnotation(member.annotation);
18058
- if (valueType !== null && member.value !== null) {
18059
- context.expectAssignable(valueType, declared, member.value.position, `Field "${member.name}"`);
18158
+ function checkIf(context, clauses, alternate) {
18159
+ for (const clause of clauses) {
18160
+ checkExpression(context, clause.condition);
18161
+ checkNarrowedBlock(context, conditionFacts(context, clause.condition), clause.body);
18060
18162
  }
18061
- return declared;
18062
- }
18063
- function checkFieldName(context, member) {
18064
- if (member.name !== "constructor") {
18163
+ if (alternate === null) {
18065
18164
  return;
18066
18165
  }
18067
- const message = 'A "constructor" is the class constructor, not a field. Write it as "constructor = function (...) ... end" or rename the field.';
18068
- context.report("check-invalid-constructor", message, member.position);
18166
+ const [only] = clauses;
18167
+ checkNarrowedBlock(context, clauses.length === 1 && only !== void 0 ? negatedFacts(context, only.condition) : /* @__PURE__ */ new Map(), alternate);
18069
18168
  }
18070
- function syntheticMethodType(context, member, fieldTypes) {
18071
- const fieldType2 = [...fieldTypes].find(([field2]) => field2.position.offset === member.position.offset)?.[1] ?? ANY_TYPE;
18072
- if (member.generated?.kind === "lazy") {
18073
- return createFunction([], fieldType2);
18074
- }
18075
- if (member.generated === void 0) {
18076
- return member.parameters.length === 0 ? createFunction([], fieldType2) : createFunction([fieldType2], VOID_TYPE);
18169
+ function checkNumericFor(context, statement) {
18170
+ const bounds = [statement.start, statement.limit, statement.step].filter((value) => value !== null);
18171
+ for (const bound of bounds) {
18172
+ const type = checkExpression(context, bound);
18173
+ if (!isNumeric(type)) {
18174
+ context.report("check-invalid-operand", `Numeric "for" expects "number" but received "${typeToString(type)}".`, bound.position);
18175
+ }
18077
18176
  }
18078
- return buildFunctionType(context, member.parameters, member.returnAnnotation);
18177
+ context.binder.pushScope();
18178
+ context.binder.declare({ name: statement.variable.name, type: NUMBER_TYPE, isLocal: true, position: statement.variable.position, origin: "local" });
18179
+ checkStatements(context, statement.body);
18180
+ context.binder.popScope();
18079
18181
  }
18080
- function registerMembers(context, info, statement) {
18081
- const fieldTypes = /* @__PURE__ */ new Map();
18082
- for (const member of statement.members) {
18083
- if (member.kind === "class-field") {
18084
- checkFieldName(context, member);
18085
- fieldTypes.set(member, fieldType(context, member));
18086
- }
18182
+ function iteratedTypes(context, iterators) {
18183
+ const [iterator] = iterators;
18184
+ if (iterators.length !== 1 || iterator === void 0 || iterator.kind !== "call-expression" || iterator.callee.kind !== "identifier") {
18185
+ return [];
18087
18186
  }
18088
- const generated = expandClassDecorators(context, statement, fieldTypes);
18089
- for (const member of [...statement.members, ...generated]) {
18090
- const type = member.kind === "class-field" ? fieldTypes.get(member) ?? ANY_TYPE : member.isSynthetic ? syntheticMethodType(context, member, fieldTypes) : buildFunctionType(context, member.parameters, member.returnAnnotation);
18091
- const decorators = member.decorators.map((decorator) => decorator.name);
18092
- info.members.set(member.name, {
18093
- name: member.name,
18094
- type,
18095
- isMethod: member.kind === "class-method",
18096
- position: member.position,
18097
- readOnly: decorators.includes("ReadOnly"),
18098
- deprecated: decorators.includes("Deprecated")
18099
- });
18187
+ const [source] = iterator.args;
18188
+ if (!ITERATOR_FUNCTIONS.has(iterator.callee.name) || source === void 0) {
18189
+ return [];
18100
18190
  }
18101
- return generated;
18191
+ const type = context.typeOf(source);
18192
+ if (type.kind === "map") {
18193
+ return iterator.callee.name === "ipairs" ? [NUMBER_TYPE, type.value] : [type.key, type.value];
18194
+ }
18195
+ return type.kind === "array" ? [NUMBER_TYPE, type.element] : [];
18102
18196
  }
18103
- function declareBuilder(context, info, statement) {
18104
- if (!statement.decorators.some((decorator) => decorator.name === "Builder")) {
18197
+ function checkGenericFor(context, statement) {
18198
+ statement.iterators.forEach((iterator) => checkExpression(context, iterator));
18199
+ const iterated = iteratedTypes(context, statement.iterators);
18200
+ context.binder.pushScope();
18201
+ statement.variables.forEach((variable, index) => {
18202
+ const inferred = iterated[index] ?? ANY_TYPE;
18203
+ const declared = variable.annotation === null ? inferred : context.resolveAnnotation(variable.annotation);
18204
+ context.binder.declare({ name: variable.name, type: declared, isLocal: true, position: variable.position, origin: "local" });
18205
+ });
18206
+ checkStatements(context, statement.body);
18207
+ context.binder.popScope();
18208
+ }
18209
+
18210
+ // ../compiler/src/checker/events.ts
18211
+ function resolveParameters(context, declaration) {
18212
+ const parameters = [];
18213
+ const names = /* @__PURE__ */ new Set();
18214
+ let invalid = false;
18215
+ declaration.parameters.forEach((parameter, index) => {
18216
+ const type = context.resolveAnnotation(parameter.annotation);
18217
+ if (parameter.name.length > 0 && names.has(parameter.name)) {
18218
+ context.report("check-duplicate-event-parameter", `Event "${declaration.name}" declares parameter "${parameter.name}" more than once.`, parameter.position);
18219
+ invalid = true;
18220
+ }
18221
+ if (parameter.isVararg && index !== declaration.parameters.length - 1) {
18222
+ context.report("check-invalid-event-parameter", `Variadic parameter of event "${declaration.name}" must be last.`, parameter.position);
18223
+ invalid = true;
18224
+ }
18225
+ if (parameter.name.length > 0) {
18226
+ names.add(parameter.name);
18227
+ }
18228
+ parameters.push({ name: parameter.name, type, isVariadic: parameter.isVararg, position: parameter.position });
18229
+ });
18230
+ return { parameters, invalid };
18231
+ }
18232
+ function checkEventDeclaration(context, declaration) {
18233
+ if (context.declarations.lookupEvent(declaration.name) !== null) {
18234
+ context.report("check-duplicate-event", `Event "${declaration.name}" is already defined.`, declaration.position);
18105
18235
  return;
18106
18236
  }
18107
- const name = `${statement.name}Builder`;
18108
- const members = /* @__PURE__ */ new Map();
18109
- for (const field2 of statement.members) {
18110
- if (field2.kind === "class-field") {
18111
- const type = info.members.get(field2.name)?.type ?? ANY_TYPE;
18112
- const method = `with${field2.name[0]?.toUpperCase()}${field2.name.slice(1)}`;
18113
- members.set(method, { name: method, type: { kind: "function", parameters: [type], minimumArguments: 1, isVariadic: false, returnType: createNamed(name) }, isMethod: true, position: field2.position });
18114
- }
18237
+ let invalid = declaration.name.length === 0;
18238
+ if (invalid) {
18239
+ context.report("check-invalid-event-name", "An event name cannot be empty.", declaration.position);
18115
18240
  }
18116
- members.set("build", { name: "build", type: { kind: "function", parameters: [], minimumArguments: 0, isVariadic: false, returnType: createNamed(statement.name) }, isMethod: true, position: statement.position });
18117
- context.declarations.declareClass({ name, superClass: null, interfaces: [], members, position: statement.position });
18118
- context.declareModuleGlobal({ name, type: createNamed(name), isLocal: false, position: statement.position });
18119
- }
18120
- function checkMethodBody(context, info, member) {
18121
- const explicitSelf = member.parameters.find((parameter) => parameter.name === "self");
18122
- if (explicitSelf !== void 0) {
18123
- context.report("check-explicit-self-parameter", 'Class methods receive "self" automatically; remove it from the parameter list.', explicitSelf.position);
18241
+ const resolved2 = resolveParameters(context, declaration);
18242
+ invalid = invalid || resolved2.invalid;
18243
+ if (declaration.returnAnnotation !== null && context.resolveAnnotation(declaration.returnAnnotation).kind !== "void") {
18244
+ context.report("check-event-return-type", `Event "${declaration.name}" can only declare a "void" return type.`, declaration.returnAnnotation.position);
18245
+ invalid = true;
18124
18246
  }
18125
- const signature = info.members.get(member.name)?.type;
18126
- if (signature?.kind !== "function") {
18127
- return;
18247
+ if (!invalid) {
18248
+ context.declarations.declareEvent({
18249
+ name: declaration.name,
18250
+ parameters: resolved2.parameters,
18251
+ environment: context.environment,
18252
+ position: declaration.position
18253
+ });
18128
18254
  }
18129
- context.pushClassMethod({ className: info.name, methodName: member.name });
18130
- checkFunctionBody(context, member.parameters, member.returnAnnotation, member.body, signature, createNamed(info.name));
18131
- context.popClassMethod();
18132
18255
  }
18133
- function resolveSuperClass(context, statement) {
18134
- if (statement.superClass === null) {
18135
- return null;
18256
+
18257
+ // ../compiler/src/checker/statements.ts
18258
+ var ORIGIN = { line: 0, column: 0, offset: 0 };
18259
+ function minimumArguments(context, parameters) {
18260
+ const index = parameters.findIndex((parameter) => {
18261
+ const type = context.resolveAnnotation(parameter.annotation);
18262
+ return parameter.isVararg || type.kind === "optional" || type.kind === "any";
18263
+ });
18264
+ return index === -1 ? parameters.length : index;
18265
+ }
18266
+ function buildFunctionType(context, parameters, returnAnnotation, contextual = null) {
18267
+ const isVariadic = parameters.some((parameter) => parameter.isVararg);
18268
+ const types = parameters.filter((parameter) => !parameter.isVararg).map((parameter, index) => parameter.annotation === null ? contextual?.parameters[index] ?? ANY_TYPE : context.resolveAnnotation(parameter.annotation));
18269
+ return createFunction(types, context.resolveAnnotation(returnAnnotation), minimumArguments(context, parameters), isVariadic);
18270
+ }
18271
+ function checkFunctionBody(context, parameters, returnAnnotation, body, signature, selfType) {
18272
+ context.binder.pushScope();
18273
+ context.pushReturnType(returnAnnotation === null ? null : context.resolveAnnotation(returnAnnotation));
18274
+ if (selfType !== null) {
18275
+ context.binder.declare({ name: "self", type: selfType, isLocal: true, position: ORIGIN });
18136
18276
  }
18137
- if (context.declarations.lookupClass(statement.superClass) !== null) {
18138
- if (context.isAmbientClass(statement.superClass)) {
18139
- context.noteExternalReference(statement.superClass, statement.position);
18277
+ let parameterIndex = 0;
18278
+ for (const parameter of parameters) {
18279
+ const type = parameter.isVararg ? ANY_TYPE : signature.parameters[parameterIndex] ?? ANY_TYPE;
18280
+ context.binder.declare({ name: parameter.name, type, isLocal: true, position: parameter.position, origin: "parameter" });
18281
+ if (!parameter.isVararg) {
18282
+ parameterIndex += 1;
18140
18283
  }
18141
- return statement.superClass;
18142
18284
  }
18143
- if (context.mtaClasses !== null && context.mtaClasses.lookupClass(statement.superClass) !== null) {
18144
- const message2 = `Class "${statement.name}" cannot extend native MTA class "${statement.superClass}".`;
18145
- context.report("check-native-class-inheritance", message2, statement.position);
18146
- return null;
18285
+ checkStatements(context, body);
18286
+ const inferred = context.popReturnType();
18287
+ if (returnAnnotation === null) {
18288
+ signature.returnType = inferred;
18147
18289
  }
18148
- const message = `Class "${statement.name}" extends "${statement.superClass}", which is not defined.`;
18149
- context.noteExternalReference(statement.superClass, statement.position);
18150
- context.report("check-unknown-class", message, statement.position);
18151
- return null;
18290
+ context.binder.popScope();
18152
18291
  }
18153
- function checkClassDeclaration(context, statement) {
18154
- if (context.declarations.lookupClass(statement.name) !== null) {
18155
- context.report("check-duplicate-class", `Class "${statement.name}" is already defined.`, statement.position);
18156
- return;
18292
+ function valueAt(values, valueTypes, index) {
18293
+ if (values[index] !== void 0) {
18294
+ return values[index];
18157
18295
  }
18158
- if (context.mtaClasses !== null && context.mtaClasses.lookupClass(statement.name) !== null) {
18159
- context.report("check-duplicate-class", `Class "${statement.name}" is reserved by MTA when OOP is enabled.`, statement.position);
18296
+ return index < valueTypes.length ? values[values.length - 1] : void 0;
18297
+ }
18298
+ function checkLocal(context, statement) {
18299
+ const valueTypes = checkValueList(context, statement.values);
18300
+ statement.declarations.forEach((declaration, index) => {
18301
+ const value = valueAt(statement.values, valueTypes, index);
18302
+ const valueType = valueTypes[index] ?? NIL_TYPE;
18303
+ context.forgetNarrowing(declaration.name);
18304
+ if (declaration.annotation === null) {
18305
+ const inferred = context.allowNil || value === void 0 ? ANY_TYPE : widenInferred(valueType);
18306
+ context.binder.declare({ name: declaration.name, type: inferred, isLocal: true, position: declaration.position, origin: "local" });
18307
+ return;
18308
+ }
18309
+ const declared = context.resolveAnnotation(declaration.annotation);
18310
+ if (value !== void 0) {
18311
+ context.expectAssignable(valueType, declared, value.position, `Variable "${declaration.name}"`);
18312
+ }
18313
+ context.binder.declare({ name: declaration.name, type: declared, isLocal: true, position: declaration.position, origin: "local" });
18314
+ });
18315
+ }
18316
+ function checkCompoundOperand(context, operator, type, position2) {
18317
+ if (operator === "..=") {
18318
+ if (!isConcatenable(type)) {
18319
+ context.report("check-invalid-operand", `Operator "..=" cannot be applied to "${typeToString(type)}".`, position2);
18320
+ }
18160
18321
  return;
18161
18322
  }
18162
- const info = {
18163
- name: statement.name,
18164
- superClass: resolveSuperClass(context, statement),
18165
- interfaces: statement.interfaces,
18166
- members: /* @__PURE__ */ new Map(),
18167
- position: statement.position
18168
- };
18169
- const generated = registerMembers(context, info, statement);
18170
- context.generatedMembers.set(statement, generated);
18171
- context.declarations.declareClass(info);
18172
- context.declareModuleGlobal({ name: info.name, type: createNamed(info.name), isLocal: false, position: statement.position });
18173
- declareBuilder(context, info, statement);
18174
- checkInterfaces(context, info);
18175
- checkOverrides(context, info, statement);
18176
- for (const member of statement.members) {
18177
- if (member.kind === "class-method") {
18178
- checkMethodBody(context, info, member);
18323
+ if (!isNumeric(type)) {
18324
+ context.report("check-invalid-operand", `Operator "${operator}" cannot be applied to "${typeToString(type)}".`, position2);
18325
+ }
18326
+ }
18327
+ function checkAssignment(context, statement) {
18328
+ const valueTypes = checkValueList(context, statement.values);
18329
+ statement.targets.forEach((target, index) => {
18330
+ const targetType = checkExpression(context, target);
18331
+ const valueType = valueTypes[index] ?? NIL_TYPE;
18332
+ const value = valueAt(statement.values, valueTypes, index);
18333
+ if (target.kind === "member-expression") {
18334
+ const object = context.typeOf(target.object);
18335
+ const frame = context.currentClassMethod();
18336
+ if (object.kind === "named") {
18337
+ const member = context.declarations.lookupMember(object.name, target.property);
18338
+ if (member?.readOnly === true && (frame === null || frame.className !== object.name)) {
18339
+ context.report("check-readonly-assignment", `Field "${target.property}" is read-only outside class "${object.name}".`, target.position);
18340
+ }
18341
+ }
18342
+ }
18343
+ if (statement.operator !== "=") {
18344
+ checkCompoundOperand(context, statement.operator, targetType, target.position);
18345
+ if (statement.values.length > 0) {
18346
+ checkCompoundOperand(context, statement.operator, valueType, value?.position ?? target.position);
18347
+ }
18348
+ return;
18349
+ }
18350
+ if (target.kind === "identifier" && context.binder.lookup(target.name) === null) {
18351
+ context.declareModuleGlobal({ name: target.name, type: valueType, isLocal: false, position: target.position });
18352
+ return;
18353
+ }
18354
+ const declared = target.kind === "identifier" ? context.binder.lookup(target.name)?.type ?? targetType : targetType;
18355
+ if (value !== void 0) {
18356
+ context.expectAssignable(valueType, declared, value.position, "Assignment");
18357
+ }
18358
+ if (target.kind === "identifier") {
18359
+ context.forgetNarrowing(target.name);
18360
+ }
18361
+ });
18362
+ }
18363
+ function checkFunctionDeclaration(context, statement) {
18364
+ const type = buildFunctionType(context, statement.parameters, statement.returnAnnotation);
18365
+ if (statement.name.kind === "identifier") {
18366
+ const symbol = { name: statement.name.name, type, isLocal: statement.isLocal, position: statement.position };
18367
+ if (statement.isLocal) {
18368
+ context.binder.declare({ ...symbol, origin: "local" });
18369
+ } else {
18370
+ context.declareModuleGlobal(symbol);
18179
18371
  }
18180
18372
  }
18373
+ context.record(statement.name, type);
18374
+ checkFunctionBody(context, statement.parameters, statement.returnAnnotation, statement.body, type, statement.isMethod ? ANY_TYPE : null);
18181
18375
  }
18182
- function checkInterfaceDeclaration(context, statement) {
18183
- if (context.declarations.lookupInterface(statement.name) !== null) {
18184
- context.report("check-duplicate-interface", `Interface "${statement.name}" is already defined.`, statement.position);
18376
+ function checkReturn(context, statement) {
18377
+ const valueTypes = checkValueList(context, statement.values);
18378
+ const expected = context.currentReturnType();
18379
+ if (expected === null) {
18380
+ context.inferReturnType(createTuple(valueTypes));
18185
18381
  return;
18186
18382
  }
18187
- const members = /* @__PURE__ */ new Map();
18188
- for (const name of new Set(statement.superInterfaces)) {
18189
- const parent = context.declarations.lookupInterface(name);
18190
- if (name === statement.name || context.declarations.interfaceExtends(name, statement.name)) {
18191
- context.report("check-interface-cycle", `Interface "${statement.name}" creates an inheritance cycle through "${name}".`, statement.position);
18192
- } else if (parent === null) {
18193
- context.noteExternalReference(name, statement.position);
18194
- context.report("check-unknown-interface", `Interface "${statement.name}" extends "${name}", which is not defined.`, statement.position);
18195
- } else if (context.isAmbientInterface(name)) {
18196
- context.noteExternalReference(name, statement.position);
18197
- }
18198
- }
18199
- if (new Set(statement.superInterfaces).size !== statement.superInterfaces.length) {
18200
- context.report("check-duplicate-interface-parent", `Interface "${statement.name}" extends the same interface more than once.`, statement.position);
18383
+ if (expected.kind === "any") {
18384
+ return;
18201
18385
  }
18202
- for (const member of statement.members) {
18203
- const type = member.kind === "interface-field" ? context.resolveAnnotation(member.annotation) : buildFunctionType(context, member.parameters, member.returnAnnotation);
18204
- const info = { name: member.name, type, isMethod: member.kind === "interface-method", position: member.position };
18205
- if (members.has(member.name)) {
18206
- context.report("check-duplicate-interface-member", `Interface "${statement.name}" declares member "${member.name}" more than once.`, member.position);
18207
- } else {
18208
- members.set(member.name, info);
18386
+ if (expected.kind === "void") {
18387
+ if (statement.values.length > 0) {
18388
+ context.report("check-return-mismatch", 'This function is declared "void" and cannot return a value.', statement.position);
18209
18389
  }
18390
+ return;
18210
18391
  }
18211
- const inherited = /* @__PURE__ */ new Map();
18212
- for (const parent of statement.superInterfaces) {
18213
- for (const member of context.declarations.collectInterfaceContract(parent)) {
18214
- const existing = members.get(member.name) ?? inherited.get(member.name);
18215
- if (existing !== void 0 && (existing.isMethod !== member.isMethod || typeToString(existing.type) !== typeToString(member.type))) {
18216
- context.report("check-conflicting-interface-member", `Interface "${statement.name}" inherits conflicting declarations of "${member.name}".`, statement.position);
18217
- } else if (existing === void 0) {
18218
- inherited.set(member.name, member);
18219
- }
18392
+ if (expected.kind === "tuple") {
18393
+ if (valueTypes.length !== expected.elements.length) {
18394
+ const message = `This function declares ${expected.elements.length} return values but returns ${valueTypes.length}.`;
18395
+ context.report("check-return-mismatch", message, statement.position);
18396
+ return;
18220
18397
  }
18398
+ valueTypes.forEach((type, index) => {
18399
+ const value2 = statement.values[Math.min(index, statement.values.length - 1)];
18400
+ context.expectAssignable(type, expected.elements[index] ?? ANY_TYPE, value2?.position ?? statement.position, `Return value ${index + 1}`);
18401
+ });
18402
+ return;
18221
18403
  }
18222
- context.declarations.declareInterface({
18223
- name: statement.name,
18224
- superInterfaces: statement.superInterfaces,
18225
- members,
18226
- position: statement.position
18227
- });
18228
- }
18229
- function checkEnumDeclaration(context, statement) {
18230
- if (context.declarations.lookupEnum(statement.name) !== null) {
18231
- context.report("check-duplicate-enum", `Enum "${statement.name}" is already defined.`, statement.position);
18404
+ if (valueTypes.length > 1) {
18405
+ const message = `This function declares one return value of type "${typeToString(expected)}" but returns ${valueTypes.length} values.`;
18406
+ context.report("check-return-mismatch", message, statement.position);
18232
18407
  return;
18233
18408
  }
18234
- const members = statement.members.map((member) => member.name);
18235
- context.declarations.declareEnum({ name: statement.name, members, position: statement.position });
18236
- context.declareModuleGlobal({ name: statement.name, type: createNamed(statement.name), isLocal: false, position: statement.position });
18409
+ const first = valueTypes[0];
18410
+ const value = statement.values[0];
18411
+ if (first === void 0 || value === void 0) {
18412
+ context.expectAssignable(NIL_TYPE, expected, statement.position, "Return value");
18413
+ return;
18414
+ }
18415
+ context.expectAssignable(first, expected, value.position, "Return value");
18237
18416
  }
18238
-
18239
- // ../compiler/src/checker/discriminant.ts
18240
- function literalValue(expression) {
18241
- if (expression.kind === "string-literal") {
18242
- return createStringLiteral(expression.value);
18417
+ function checkDeclareStatement(context, statement) {
18418
+ if (!context.isDeclarationFile) {
18419
+ const message = 'A "declare" statement describes code the compiler does not own and belongs in a ".d.luam" file.';
18420
+ context.report("check-declare-outside-declaration-file", message, statement.position);
18421
+ return;
18243
18422
  }
18244
- if (expression.kind === "boolean-literal") {
18245
- return createBooleanLiteral(expression.value);
18423
+ const type = context.resolveAnnotation(statement.annotation);
18424
+ context.declareModuleGlobal({ name: statement.name, type, isLocal: false, position: statement.position });
18425
+ context.declarations.declareGlobal({ name: statement.name, type, position: statement.position });
18426
+ }
18427
+ function checkStatement(context, statement) {
18428
+ switch (statement.kind) {
18429
+ case "local-statement":
18430
+ return checkLocal(context, statement);
18431
+ case "assignment-statement":
18432
+ return checkAssignment(context, statement);
18433
+ case "call-statement":
18434
+ checkExpression(context, statement.expression);
18435
+ return;
18436
+ case "function-declaration":
18437
+ return checkFunctionDeclaration(context, statement);
18438
+ case "return-statement":
18439
+ return checkReturn(context, statement);
18440
+ case "do-statement":
18441
+ return checkBlock(context, statement.body);
18442
+ case "while-statement":
18443
+ checkExpression(context, statement.condition);
18444
+ return checkBlock(context, statement.body);
18445
+ case "repeat-statement":
18446
+ checkBlock(context, statement.body);
18447
+ checkExpression(context, statement.condition);
18448
+ return;
18449
+ case "if-statement":
18450
+ return checkIf(context, statement.clauses, statement.alternate);
18451
+ case "numeric-for-statement":
18452
+ return checkNumericFor(context, statement);
18453
+ case "generic-for-statement":
18454
+ return checkGenericFor(context, statement);
18455
+ case "type-alias-statement": {
18456
+ const resolved2 = context.resolveAnnotation(statement.annotation);
18457
+ const shape = statement.annotation.kind === "type-object" || statement.annotation.kind === "type-intersection";
18458
+ const named2 = shape ? renameRecord(resolved2, statement.name) : resolved2;
18459
+ context.noteTypeParameters(statement.typeParameters);
18460
+ context.binder.declareAlias(statement.name, named2, statement.typeParameters);
18461
+ return;
18462
+ }
18463
+ case "declare-statement":
18464
+ return checkDeclareStatement(context, statement);
18465
+ case "event-declaration":
18466
+ return checkEventDeclaration(context, statement);
18467
+ case "class-declaration":
18468
+ return checkClassDeclaration(context, statement);
18469
+ case "interface-declaration":
18470
+ return checkInterfaceDeclaration(context, statement);
18471
+ case "enum-declaration":
18472
+ return checkEnumDeclaration(context, statement);
18473
+ default:
18474
+ return;
18246
18475
  }
18247
- return expression.kind === "number-literal" ? createNumberLiteral(expression.value) : null;
18248
18476
  }
18249
- function discriminantTest(left, right) {
18250
- if (left.kind !== "member-expression" || left.object.kind !== "identifier") {
18251
- return null;
18477
+ function checkStatements(context, statements) {
18478
+ let guards = 0;
18479
+ for (const statement of statements) {
18480
+ checkStatement(context, statement);
18481
+ const facts = guardFacts(context, statement);
18482
+ if (facts.size > 0) {
18483
+ context.pushNarrowing(facts);
18484
+ guards += 1;
18485
+ }
18486
+ }
18487
+ while (guards > 0) {
18488
+ context.popNarrowing();
18489
+ guards -= 1;
18252
18490
  }
18253
- const value = literalValue(right);
18254
- return value === null ? null : { name: left.object.name, property: left.property, value };
18255
18491
  }
18256
- function subjectType(context, name) {
18257
- const type = context.narrowedType(name) ?? context.binder.lookup(name)?.type ?? null;
18258
- return type === null ? null : withoutNil(type);
18492
+
18493
+ // ../compiler/src/checker/template.ts
18494
+ function parseInterpolation(segment) {
18495
+ const separator = segment.value.indexOf(":");
18496
+ const path = (separator === -1 ? segment.value : segment.value.slice(0, separator)).trim();
18497
+ const fallback = separator === -1 ? null : segment.value.slice(separator + 1).trim();
18498
+ const root = path.split(".")[0] ?? "";
18499
+ return { raw: segment.value, path, root, fallback, position: segment.position };
18259
18500
  }
18260
- function filterOptions(context, test, keep) {
18261
- const subject = subjectType(context, test.name);
18262
- if (subject === null || subject.kind !== "union") {
18263
- return null;
18264
- }
18265
- const matched = [];
18266
- for (const option of subject.options) {
18267
- const member = optionMemberType(context, option, test.property);
18268
- if (member === null) {
18269
- return null;
18270
- }
18271
- if (isAssignable(test.value, member) === keep) {
18272
- matched.push(option);
18501
+ function collectInterpolations(segments) {
18502
+ return segments.filter((segment) => segment.kind === "interpolation").map(parseInterpolation);
18503
+ }
18504
+ function collectTemplateRoots(segments) {
18505
+ const roots = [];
18506
+ for (const interpolation of collectInterpolations(segments)) {
18507
+ if (interpolation.root.length > 0 && !roots.includes(interpolation.root)) {
18508
+ roots.push(interpolation.root);
18273
18509
  }
18274
18510
  }
18275
- return matched.length === 0 || matched.length === subject.options.length ? null : createUnion(matched);
18511
+ return roots;
18276
18512
  }
18277
- function discriminantFact(context, left, right, keep) {
18278
- const test = discriminantTest(left, right) ?? discriminantTest(right, left);
18279
- if (test === null) {
18280
- return null;
18281
- }
18282
- const narrowed = filterOptions(context, test, keep);
18283
- return narrowed === null ? null : [test.name, narrowed];
18513
+ function templateLiteralText(segments) {
18514
+ return segments.map((segment) => segment.kind === "text" ? segment.value : `\${${segment.value}}`).join("");
18284
18515
  }
18285
18516
 
18286
- // ../compiler/src/checker/narrowing.ts
18287
- var GUARD_FUNCTION = "type";
18288
- var GUARD_TYPES = {
18517
+ // ../compiler/src/checker/expressions.ts
18518
+ var EXTENSION_RESULTS = {
18289
18519
  boolean: BOOLEAN_TYPE,
18290
- function: createFunction([], ANY_TYPE, 0, true),
18291
- nil: NIL_TYPE,
18292
18520
  number: NUMBER_TYPE,
18293
18521
  string: STRING_TYPE,
18294
- table: TABLE_TYPE,
18295
- thread: THREAD_TYPE,
18296
- userdata: USERDATA_TYPE
18522
+ table: TABLE_TYPE
18297
18523
  };
18298
- function declaredType(context, name) {
18299
- return context.binder.lookup(name)?.type ?? null;
18524
+ function isNativeConstruction(context, expression) {
18525
+ const object = expression.object;
18526
+ if (expression.property !== NATIVE_CONSTRUCTOR || object.kind !== "identifier") {
18527
+ return false;
18528
+ }
18529
+ return nativeConstructor(context, object.name) !== null;
18300
18530
  }
18301
- function guardedName(expression) {
18302
- if (expression.kind !== "call-expression" || expression.callee.kind !== "identifier" || expression.callee.name !== GUARD_FUNCTION) {
18303
- return null;
18531
+ function checkMember(context, expression) {
18532
+ if (expression.object.kind === "identifier" && isMtaClassReference(context, expression.object.name)) {
18533
+ context.references.add(expression.object.name);
18534
+ return context.record(expression, resolveMtaStaticMember(context, expression.object.name, expression.property, expression.position)?.type ?? ANY_TYPE);
18535
+ }
18536
+ if (isNativeConstruction(context, expression) && expression.object.kind === "identifier") {
18537
+ const name = expression.object.name;
18538
+ const message = `Construct "${name}" with "new ${name}(...)". The "${name}.new(...)" form is not part of the language.`;
18539
+ context.report("check-native-constructor", message, expression.position);
18540
+ }
18541
+ const library = resolveLibraryMember(context, expression);
18542
+ if (library !== null) {
18543
+ checkExpression(context, expression.object);
18544
+ return context.record(expression, library);
18545
+ }
18546
+ const objectType = checkExpression(context, expression.object);
18547
+ if (objectType.kind === "record") {
18548
+ return context.record(expression, resolveRecordMember(context, objectType, expression));
18549
+ }
18550
+ if (objectType.kind === "union") {
18551
+ const option = resolveUnionMember(context, objectType, expression);
18552
+ if (option !== null) {
18553
+ return context.record(expression, option);
18554
+ }
18304
18555
  }
18305
- const [argument] = expression.args;
18306
- return argument !== void 0 && argument.kind === "identifier" ? argument.name : null;
18556
+ if (objectType.kind === "map" && isAssignable(STRING_TYPE, objectType.key)) {
18557
+ return context.record(expression, objectType.value);
18558
+ }
18559
+ const named2 = objectType.kind === "named" ? resolveNamedMember(context, objectType.name, expression) : null;
18560
+ if (named2 !== null) {
18561
+ return context.record(expression, named2);
18562
+ }
18563
+ const extension = extensionFor(objectType, expression.property);
18564
+ if (extension !== null) {
18565
+ reportExtensionForm(context, expression, extension);
18566
+ }
18567
+ return context.record(expression, extension === null ? ANY_TYPE : EXTENSION_RESULTS[extension.result]);
18307
18568
  }
18308
- function guardFact(left, right) {
18309
- const name = guardedName(left);
18310
- const value = right.kind === "string-literal" ? GUARD_TYPES[right.value] : void 0;
18311
- return name === null || value === void 0 ? null : [name, value];
18569
+ function countArguments(total) {
18570
+ return total === 1 ? "1 argument" : `${total} arguments`;
18312
18571
  }
18313
- function nilComparison(left, right) {
18314
- if (left.kind === "identifier" && right.kind === "nil-literal") {
18315
- return left.name;
18572
+ function checkSignature(context, args, signature, position2) {
18573
+ const argumentTypes = checkValueList(context, args, signature.parameters);
18574
+ if (argumentTypes.length < signature.minimumArguments) {
18575
+ const message = `This call expects at least ${countArguments(signature.minimumArguments)} but received ${argumentTypes.length}.`;
18576
+ context.report("check-argument-count", message, position2);
18316
18577
  }
18317
- return right.kind === "identifier" && left.kind === "nil-literal" ? right.name : null;
18318
- }
18319
- function present2(context, name, facts) {
18320
- const declared = declaredType(context, name);
18321
- if (declared !== null) {
18322
- facts.set(name, withoutNil(declared));
18578
+ if (!signature.isVariadic && argumentTypes.length > signature.parameters.length) {
18579
+ const message = `This call expects at most ${countArguments(signature.parameters.length)} but received ${argumentTypes.length}.`;
18580
+ context.report("check-argument-count", message, position2);
18323
18581
  }
18324
- }
18325
- function mergeAlternatives(left, right, facts) {
18326
- for (const [name, type] of left) {
18327
- const other = right.get(name);
18328
- if (other !== void 0) {
18329
- facts.set(name, createUnion([type, other]));
18582
+ signature.parameters.forEach((parameter, index) => {
18583
+ const argumentType = argumentTypes[index];
18584
+ const argument = args[index];
18585
+ if (argumentType !== void 0 && argument !== void 0) {
18586
+ context.expectAssignable(argumentType, parameter, argument.position, `Argument ${index + 1}`);
18587
+ }
18588
+ });
18589
+ if (signature.isVariadic && signature.variadicType !== void 0) {
18590
+ for (let index = signature.parameters.length; index < argumentTypes.length; index += 1) {
18591
+ const argumentType = argumentTypes[index];
18592
+ const argument = args[Math.min(index, args.length - 1)];
18593
+ if (argumentType !== void 0 && argument !== void 0) {
18594
+ context.expectAssignable(argumentType, signature.variadicType, argument.position, `Argument ${index + 1}`);
18595
+ }
18330
18596
  }
18331
18597
  }
18598
+ return signature.returnType;
18332
18599
  }
18333
- function applyDiscriminant(context, left, right, keep, facts) {
18334
- const fact = discriminantFact(context, left, right, keep);
18335
- if (fact !== null) {
18336
- facts.set(fact[0], fact[1]);
18600
+ function checkArguments(context, expression, calleeType) {
18601
+ if (calleeType.kind !== "function") {
18602
+ checkValueList(context, expression.args);
18603
+ reportNotCallable(context, expression, calleeType);
18604
+ return ANY_TYPE;
18337
18605
  }
18606
+ return checkSignature(context, expression.args, calleeType, expression.position);
18338
18607
  }
18339
- function collectFacts(context, expression, facts) {
18340
- if (expression.kind === "group-expression") {
18341
- collectFacts(context, expression.expression, facts);
18342
- return;
18608
+ function isSuperCall(expression) {
18609
+ return expression.method === null && expression.callee.kind === "identifier" && expression.callee.name === "super";
18610
+ }
18611
+ function isLegacySuperCall(expression) {
18612
+ return expression.method === "super" && expression.callee.kind === "identifier" && expression.callee.name === "self";
18613
+ }
18614
+ function checkMethodCall(context, expression, method, receiver) {
18615
+ if (receiver.kind !== "named") {
18616
+ checkValueList(context, expression.args);
18617
+ return ANY_TYPE;
18343
18618
  }
18344
- if (expression.kind === "identifier") {
18345
- present2(context, expression.name, facts);
18346
- return;
18619
+ const declared = context.declarations.lookupMember(receiver.name, method);
18620
+ if (declared?.type.kind === "function") {
18621
+ if (declared.deprecated === true) {
18622
+ context.warn("check-deprecated-use", `Member "${method}" is deprecated.`, expression.position);
18623
+ }
18624
+ return checkSignature(context, expression.args, declared.type, expression.position);
18347
18625
  }
18348
- if (expression.kind !== "binary-expression") {
18349
- return;
18626
+ if (!isMtaElement(context, receiver.name)) {
18627
+ checkValueList(context, expression.args);
18628
+ return ANY_TYPE;
18350
18629
  }
18351
- if (expression.operator === "and") {
18352
- collectFacts(context, expression.left, facts);
18353
- collectFacts(context, expression.right, facts);
18354
- return;
18630
+ const member = resolveMtaMember(context, receiver.name, method, expression.position);
18631
+ if (member === null || member.type.kind !== "function") {
18632
+ checkValueList(context, expression.args);
18633
+ return ANY_TYPE;
18355
18634
  }
18356
- if (expression.operator === "or") {
18357
- mergeAlternatives(conditionFacts(context, expression.left), conditionFacts(context, expression.right), facts);
18358
- return;
18635
+ return checkSignature(context, expression.args, member.type, expression.position);
18636
+ }
18637
+ function checkCall(context, expression) {
18638
+ if (isLegacySuperCall(expression)) {
18639
+ checkValueList(context, expression.args);
18640
+ context.report("check-invalid-super", 'Call "super(...)" directly instead of "self:super(...)".', expression.position);
18641
+ return context.record(expression, ANY_TYPE);
18359
18642
  }
18360
- if (expression.operator === "==") {
18361
- const guard = guardFact(expression.left, expression.right) ?? guardFact(expression.right, expression.left);
18362
- const missing = nilComparison(expression.left, expression.right);
18363
- if (guard !== null) {
18364
- facts.set(guard[0], guard[1]);
18365
- } else if (missing !== null) {
18366
- facts.set(missing, NIL_TYPE);
18367
- } else {
18368
- applyDiscriminant(context, expression.left, expression.right, true, facts);
18369
- }
18370
- return;
18643
+ if (isSuperCall(expression)) {
18644
+ return context.record(expression, checkSuperCall(context, expression));
18371
18645
  }
18372
- if (expression.operator === "~=") {
18373
- const name = nilComparison(expression.left, expression.right);
18374
- if (name !== null) {
18375
- present2(context, name, facts);
18376
- } else {
18377
- applyDiscriminant(context, expression.left, expression.right, false, facts);
18646
+ if (expression.method === null && expression.callee.kind === "identifier" && isMtaClassReference(context, expression.callee.name)) {
18647
+ context.references.add(expression.callee.name);
18648
+ const constructor = resolveMtaConstructor(context, expression.callee.name, expression.position);
18649
+ if (constructor === null) {
18650
+ checkValueList(context, expression.args);
18651
+ return context.record(expression, ANY_TYPE);
18378
18652
  }
18653
+ return context.record(expression, checkSignature(context, expression.args, constructor, expression.position));
18379
18654
  }
18380
- }
18381
- function conditionFacts(context, condition) {
18382
- const facts = /* @__PURE__ */ new Map();
18383
- collectFacts(context, condition, facts);
18384
- return facts;
18385
- }
18386
- function alwaysExits(body) {
18387
- const last = body[body.length - 1];
18388
- if (last === void 0) {
18389
- return false;
18655
+ if (expression.method === null) {
18656
+ context.calledMembers.add(expression.callee);
18390
18657
  }
18391
- if (last.kind === "return-statement" || last.kind === "break-statement") {
18392
- return true;
18658
+ const calleeType = checkExpression(context, expression.callee);
18659
+ if (expression.method !== null) {
18660
+ return context.record(expression, checkMethodCall(context, expression, expression.method, calleeType));
18393
18661
  }
18394
- if (last.kind !== "if-statement" || last.alternate === null) {
18395
- return false;
18662
+ if (expression.callee.kind === "identifier" && context.binder.isBuiltinReference(expression.callee.name)) {
18663
+ checkEventUsage(context, expression);
18664
+ const specialized = specializeEventCall(context, expression, calleeType);
18665
+ return context.record(expression, checkArguments(context, expression, specialized ?? calleeType));
18396
18666
  }
18397
- return last.clauses.every((clause) => alwaysExits(clause.body)) && alwaysExits(last.alternate);
18667
+ return context.record(expression, checkArguments(context, expression, calleeType));
18398
18668
  }
18399
- function guardFacts(context, statement) {
18400
- const [clause] = statement.kind === "if-statement" ? statement.clauses : [];
18401
- if (statement.kind !== "if-statement" || statement.alternate !== null || statement.clauses.length !== 1 || clause === void 0) {
18402
- return /* @__PURE__ */ new Map();
18669
+ function checkTable(context, expression) {
18670
+ const valueTypes = expression.fields.map((field2) => {
18671
+ if (field2.key !== null) {
18672
+ checkExpression(context, field2.key);
18673
+ }
18674
+ return checkExpression(context, field2.value);
18675
+ });
18676
+ const isRecord = expression.fields.every((field2) => field2.name !== null);
18677
+ if (isRecord) {
18678
+ const members = /* @__PURE__ */ new Map();
18679
+ expression.fields.forEach((field2, index) => {
18680
+ if (field2.name !== null) {
18681
+ members.set(field2.name, valueTypes[index] ?? ANY_TYPE);
18682
+ }
18683
+ });
18684
+ return context.record(expression, createLiteralRecord(members));
18403
18685
  }
18404
- return alwaysExits(clause.body) ? negatedFacts(context, clause.condition) : /* @__PURE__ */ new Map();
18686
+ const isArray = expression.fields.every((field2) => field2.key === null && field2.name === null);
18687
+ if (!isArray) {
18688
+ return context.record(expression, TABLE_TYPE);
18689
+ }
18690
+ return context.record(expression, createArray(createUnion(valueTypes)));
18405
18691
  }
18406
- function negatedFacts(context, condition) {
18407
- const facts = /* @__PURE__ */ new Map();
18408
- if (condition.kind === "binary-expression" && condition.operator === "or") {
18409
- for (const [name, type] of [...negatedFacts(context, condition.left), ...negatedFacts(context, condition.right)]) {
18410
- facts.set(name, type);
18692
+ function checkTemplate(context, expression) {
18693
+ for (const interpolation of collectInterpolations(expression.segments)) {
18694
+ if (interpolation.root.length === 0) {
18695
+ const message = 'Template interpolation is empty. Write "${name}", "${object.field}", or "${name:fallback}".';
18696
+ context.report("check-empty-interpolation", message, interpolation.position);
18697
+ continue;
18698
+ }
18699
+ if (context.binder.lookup(interpolation.root) === null) {
18700
+ const message = `Template interpolation "${interpolation.path}" refers to "${interpolation.root}", which is not in scope.`;
18701
+ context.report("check-unknown-template-root", message, interpolation.position);
18411
18702
  }
18412
- return facts;
18413
18703
  }
18414
- if (condition.kind === "binary-expression" && condition.operator === "==") {
18415
- const name = nilComparison(condition.left, condition.right);
18416
- if (name !== null) {
18417
- present2(context, name, facts);
18418
- } else {
18419
- applyDiscriminant(context, condition.left, condition.right, false, facts);
18704
+ return context.record(expression, STRING_TYPE);
18705
+ }
18706
+ function checkMultiValueExpression(context, expression, expected = null) {
18707
+ switch (expression.kind) {
18708
+ case "nil-literal":
18709
+ return context.record(expression, NIL_TYPE);
18710
+ case "boolean-literal":
18711
+ return context.record(expression, createBooleanLiteral(expression.value));
18712
+ case "number-literal":
18713
+ return context.record(expression, createNumberLiteral(expression.value));
18714
+ case "string-literal":
18715
+ return context.record(expression, createStringLiteral(expression.value));
18716
+ case "template-literal":
18717
+ return checkTemplate(context, expression);
18718
+ case "vararg-expression":
18719
+ return context.record(expression, ANY_TYPE);
18720
+ case "identifier": {
18721
+ context.references.add(expression.name);
18722
+ const symbol = context.binder.lookup(expression.name);
18723
+ if (symbol === null) {
18724
+ checkGlobalReference(context, expression.name, expression.position);
18725
+ }
18726
+ return context.record(expression, context.narrowedType(expression.name) ?? symbol?.type ?? ANY_TYPE);
18727
+ }
18728
+ case "member-expression":
18729
+ return checkMember(context, expression);
18730
+ case "index-expression": {
18731
+ const objectType = checkExpression(context, expression.object);
18732
+ const indexType = checkExpression(context, expression.index);
18733
+ if (objectType.kind === "map") {
18734
+ context.expectAssignable(indexType, objectType.key, expression.index.position, "Key");
18735
+ return context.record(expression, objectType.value);
18736
+ }
18737
+ return context.record(expression, objectType.kind === "array" ? objectType.element : ANY_TYPE);
18738
+ }
18739
+ case "call-expression":
18740
+ return checkCall(context, expression);
18741
+ case "new-expression":
18742
+ return context.record(expression, checkNewExpression(context, expression));
18743
+ case "table-expression":
18744
+ return checkTable(context, expression);
18745
+ case "function-expression": {
18746
+ const type = buildFunctionType(context, expression.parameters, expression.returnAnnotation, contextualFunction(expected));
18747
+ checkFunctionBody(context, expression.parameters, expression.returnAnnotation, expression.body, type, null);
18748
+ return context.record(expression, type);
18420
18749
  }
18750
+ case "binary-expression": {
18751
+ const left = checkExpression(context, expression.left);
18752
+ const right = checkExpression(context, expression.right);
18753
+ return checkBinary(context, expression.operator, left, right, expression);
18754
+ }
18755
+ case "unary-expression":
18756
+ return checkUnary(context, expression.operator, checkExpression(context, expression.operand), expression);
18757
+ default:
18758
+ return context.record(expression, checkExpression(context, expression.expression, expected));
18421
18759
  }
18422
- if (condition.kind === "binary-expression" && condition.operator === "~=") {
18423
- applyDiscriminant(context, condition.left, condition.right, true, facts);
18424
- }
18425
- if (condition.kind === "unary-expression" && condition.operator === "not" && condition.operand.kind === "identifier") {
18426
- present2(context, condition.operand.name, facts);
18427
- }
18428
- return facts;
18429
18760
  }
18430
-
18431
- // ../compiler/src/checker/control-flow.ts
18432
- var ITERATOR_FUNCTIONS = /* @__PURE__ */ new Set(["pairs", "ipairs"]);
18433
- function checkBlock(context, body) {
18434
- context.binder.pushScope();
18435
- checkStatements(context, body);
18436
- context.binder.popScope();
18761
+ function checkExpression(context, expression, expected = null) {
18762
+ return firstValueOf(checkMultiValueExpression(context, expression, expected));
18437
18763
  }
18438
- function checkNarrowedBlock(context, facts, body) {
18439
- context.pushNarrowing(facts);
18440
- checkBlock(context, body);
18441
- context.popNarrowing();
18764
+ function checkValueList(context, values, expected = []) {
18765
+ const types = [];
18766
+ values.forEach((value, index) => {
18767
+ const type = checkMultiValueExpression(context, value, expected[index] ?? null);
18768
+ if (index === values.length - 1) {
18769
+ types.push(...valuesOf(type));
18770
+ return;
18771
+ }
18772
+ types.push(firstValueOf(type));
18773
+ });
18774
+ return types;
18442
18775
  }
18443
- function checkIf(context, clauses, alternate) {
18444
- for (const clause of clauses) {
18445
- checkExpression(context, clause.condition);
18446
- checkNarrowedBlock(context, conditionFacts(context, clause.condition), clause.body);
18776
+
18777
+ // ../compiler/src/checker/classes.ts
18778
+ function fieldType(context, member) {
18779
+ const valueType = member.value === null ? null : checkExpression(context, member.value);
18780
+ if (member.annotation === null) {
18781
+ return valueType === null ? ANY_TYPE : widenInferred(valueType);
18447
18782
  }
18448
- if (alternate === null) {
18449
- return;
18783
+ const declared = context.resolveAnnotation(member.annotation);
18784
+ if (valueType !== null && member.value !== null) {
18785
+ context.expectAssignable(valueType, declared, member.value.position, `Field "${member.name}"`);
18450
18786
  }
18451
- const [only] = clauses;
18452
- checkNarrowedBlock(context, clauses.length === 1 && only !== void 0 ? negatedFacts(context, only.condition) : /* @__PURE__ */ new Map(), alternate);
18787
+ return declared;
18453
18788
  }
18454
- function checkNumericFor(context, statement) {
18455
- const bounds = [statement.start, statement.limit, statement.step].filter((value) => value !== null);
18456
- for (const bound of bounds) {
18457
- const type = checkExpression(context, bound);
18458
- if (!isNumeric(type)) {
18459
- context.report("check-invalid-operand", `Numeric "for" expects "number" but received "${typeToString(type)}".`, bound.position);
18460
- }
18789
+ function checkFieldName(context, member) {
18790
+ if (member.name !== "constructor") {
18791
+ return;
18461
18792
  }
18462
- context.binder.pushScope();
18463
- context.binder.declare({ name: statement.variable.name, type: NUMBER_TYPE, isLocal: true, position: statement.variable.position, origin: "local" });
18464
- checkStatements(context, statement.body);
18465
- context.binder.popScope();
18793
+ const message = 'A "constructor" is the class constructor, not a field. Write it as "constructor = function (...) ... end" or rename the field.';
18794
+ context.report("check-invalid-constructor", message, member.position);
18466
18795
  }
18467
- function iteratedTypes(context, iterators) {
18468
- const [iterator] = iterators;
18469
- if (iterators.length !== 1 || iterator === void 0 || iterator.kind !== "call-expression" || iterator.callee.kind !== "identifier") {
18470
- return [];
18471
- }
18472
- const [source] = iterator.args;
18473
- if (!ITERATOR_FUNCTIONS.has(iterator.callee.name) || source === void 0) {
18474
- return [];
18796
+ function syntheticMethodType(context, member, fieldTypes) {
18797
+ const fieldType2 = [...fieldTypes].find(([field2]) => field2.position.offset === member.position.offset)?.[1] ?? ANY_TYPE;
18798
+ if (member.generated?.kind === "lazy") {
18799
+ return createFunction([], fieldType2);
18475
18800
  }
18476
- const type = context.typeOf(source);
18477
- if (type.kind === "map") {
18478
- return iterator.callee.name === "ipairs" ? [NUMBER_TYPE, type.value] : [type.key, type.value];
18801
+ if (member.generated === void 0) {
18802
+ return member.parameters.length === 0 ? createFunction([], fieldType2) : createFunction([fieldType2], VOID_TYPE);
18479
18803
  }
18480
- return type.kind === "array" ? [NUMBER_TYPE, type.element] : [];
18481
- }
18482
- function checkGenericFor(context, statement) {
18483
- statement.iterators.forEach((iterator) => checkExpression(context, iterator));
18484
- const iterated = iteratedTypes(context, statement.iterators);
18485
- context.binder.pushScope();
18486
- statement.variables.forEach((variable, index) => {
18487
- const inferred = iterated[index] ?? ANY_TYPE;
18488
- const declared = variable.annotation === null ? inferred : context.resolveAnnotation(variable.annotation);
18489
- context.binder.declare({ name: variable.name, type: declared, isLocal: true, position: variable.position, origin: "local" });
18490
- });
18491
- checkStatements(context, statement.body);
18492
- context.binder.popScope();
18804
+ return buildFunctionType(context, member.parameters, member.returnAnnotation);
18493
18805
  }
18494
-
18495
- // ../compiler/src/checker/events.ts
18496
- function resolveParameters(context, declaration) {
18497
- const parameters = [];
18498
- const names = /* @__PURE__ */ new Set();
18499
- let invalid = false;
18500
- declaration.parameters.forEach((parameter, index) => {
18501
- const type = context.resolveAnnotation(parameter.annotation);
18502
- if (parameter.name.length > 0 && names.has(parameter.name)) {
18503
- context.report("check-duplicate-event-parameter", `Event "${declaration.name}" declares parameter "${parameter.name}" more than once.`, parameter.position);
18504
- invalid = true;
18505
- }
18506
- if (parameter.isVararg && index !== declaration.parameters.length - 1) {
18507
- context.report("check-invalid-event-parameter", `Variadic parameter of event "${declaration.name}" must be last.`, parameter.position);
18508
- invalid = true;
18509
- }
18510
- if (parameter.name.length > 0) {
18511
- names.add(parameter.name);
18806
+ function registerMembers(context, info, statement) {
18807
+ const fieldTypes = /* @__PURE__ */ new Map();
18808
+ for (const member of statement.members) {
18809
+ if (member.kind === "class-field") {
18810
+ checkFieldName(context, member);
18811
+ fieldTypes.set(member, fieldType(context, member));
18512
18812
  }
18513
- parameters.push({ name: parameter.name, type, isVariadic: parameter.isVararg, position: parameter.position });
18514
- });
18515
- return { parameters, invalid };
18516
- }
18517
- function checkEventDeclaration(context, declaration) {
18518
- if (context.declarations.lookupEvent(declaration.name) !== null) {
18519
- context.report("check-duplicate-event", `Event "${declaration.name}" is already defined.`, declaration.position);
18520
- return;
18521
- }
18522
- let invalid = declaration.name.length === 0;
18523
- if (invalid) {
18524
- context.report("check-invalid-event-name", "An event name cannot be empty.", declaration.position);
18525
- }
18526
- const resolved2 = resolveParameters(context, declaration);
18527
- invalid = invalid || resolved2.invalid;
18528
- if (declaration.returnAnnotation !== null && context.resolveAnnotation(declaration.returnAnnotation).kind !== "void") {
18529
- context.report("check-event-return-type", `Event "${declaration.name}" can only declare a "void" return type.`, declaration.returnAnnotation.position);
18530
- invalid = true;
18531
18813
  }
18532
- if (!invalid) {
18533
- context.declarations.declareEvent({
18534
- name: declaration.name,
18535
- parameters: resolved2.parameters,
18536
- environment: context.environment,
18537
- position: declaration.position
18814
+ const generated = expandClassDecorators(context, statement, fieldTypes);
18815
+ for (const member of [...statement.members, ...generated]) {
18816
+ const type = member.kind === "class-field" ? fieldTypes.get(member) ?? ANY_TYPE : member.isSynthetic ? syntheticMethodType(context, member, fieldTypes) : buildFunctionType(context, member.parameters, member.returnAnnotation);
18817
+ const decorators = member.decorators.map((decorator) => decorator.name);
18818
+ info.members.set(member.name, {
18819
+ name: member.name,
18820
+ type,
18821
+ isMethod: member.kind === "class-method",
18822
+ position: member.position,
18823
+ readOnly: decorators.includes("ReadOnly"),
18824
+ deprecated: decorators.includes("Deprecated")
18538
18825
  });
18539
18826
  }
18827
+ return generated;
18540
18828
  }
18541
-
18542
- // ../compiler/src/checker/statements.ts
18543
- var ORIGIN = { line: 0, column: 0, offset: 0 };
18544
- function minimumArguments(context, parameters) {
18545
- const index = parameters.findIndex((parameter) => {
18546
- const type = context.resolveAnnotation(parameter.annotation);
18547
- return parameter.isVararg || type.kind === "optional" || type.kind === "any";
18548
- });
18549
- return index === -1 ? parameters.length : index;
18550
- }
18551
- function buildFunctionType(context, parameters, returnAnnotation, contextual = null) {
18552
- const isVariadic = parameters.some((parameter) => parameter.isVararg);
18553
- const types = parameters.filter((parameter) => !parameter.isVararg).map((parameter, index) => parameter.annotation === null ? contextual?.parameters[index] ?? ANY_TYPE : context.resolveAnnotation(parameter.annotation));
18554
- return createFunction(types, context.resolveAnnotation(returnAnnotation), minimumArguments(context, parameters), isVariadic);
18555
- }
18556
- function checkFunctionBody(context, parameters, returnAnnotation, body, signature, selfType) {
18557
- context.binder.pushScope();
18558
- context.pushReturnType(returnAnnotation === null ? null : context.resolveAnnotation(returnAnnotation));
18559
- if (selfType !== null) {
18560
- context.binder.declare({ name: "self", type: selfType, isLocal: true, position: ORIGIN });
18829
+ function declareBuilder(context, info, statement) {
18830
+ if (!statement.decorators.some((decorator) => decorator.name === "Builder")) {
18831
+ return;
18561
18832
  }
18562
- let parameterIndex = 0;
18563
- for (const parameter of parameters) {
18564
- const type = parameter.isVararg ? ANY_TYPE : signature.parameters[parameterIndex] ?? ANY_TYPE;
18565
- context.binder.declare({ name: parameter.name, type, isLocal: true, position: parameter.position, origin: "parameter" });
18566
- if (!parameter.isVararg) {
18567
- parameterIndex += 1;
18833
+ const name = `${statement.name}Builder`;
18834
+ const members = /* @__PURE__ */ new Map();
18835
+ for (const field2 of statement.members) {
18836
+ if (field2.kind === "class-field") {
18837
+ const type = info.members.get(field2.name)?.type ?? ANY_TYPE;
18838
+ const method = `with${field2.name[0]?.toUpperCase()}${field2.name.slice(1)}`;
18839
+ members.set(method, { name: method, type: { kind: "function", parameters: [type], minimumArguments: 1, isVariadic: false, returnType: createNamed(name) }, isMethod: true, position: field2.position });
18568
18840
  }
18569
18841
  }
18570
- checkStatements(context, body);
18571
- const inferred = context.popReturnType();
18572
- if (returnAnnotation === null) {
18573
- signature.returnType = inferred;
18574
- }
18575
- context.binder.popScope();
18842
+ members.set("build", { name: "build", type: { kind: "function", parameters: [], minimumArguments: 0, isVariadic: false, returnType: createNamed(statement.name) }, isMethod: true, position: statement.position });
18843
+ context.declarations.declareClass({ name, superClass: null, interfaces: [], members, position: statement.position });
18844
+ context.declareModuleGlobal({ name, type: createNamed(name), isLocal: false, position: statement.position });
18576
18845
  }
18577
- function valueAt(values, valueTypes, index) {
18578
- if (values[index] !== void 0) {
18579
- return values[index];
18846
+ function checkMethodBody(context, info, member) {
18847
+ const explicitSelf = member.parameters.find((parameter) => parameter.name === "self");
18848
+ if (explicitSelf !== void 0) {
18849
+ context.report("check-explicit-self-parameter", 'Class methods receive "self" automatically; remove it from the parameter list.', explicitSelf.position);
18580
18850
  }
18581
- return index < valueTypes.length ? values[values.length - 1] : void 0;
18851
+ const signature = info.members.get(member.name)?.type;
18852
+ if (signature?.kind !== "function") {
18853
+ return;
18854
+ }
18855
+ context.pushClassMethod({ className: info.name, methodName: member.name });
18856
+ checkFunctionBody(context, member.parameters, member.returnAnnotation, member.body, signature, createNamed(info.name));
18857
+ context.popClassMethod();
18582
18858
  }
18583
- function checkLocal(context, statement) {
18584
- const valueTypes = checkValueList(context, statement.values);
18585
- statement.declarations.forEach((declaration, index) => {
18586
- const value = valueAt(statement.values, valueTypes, index);
18587
- const valueType = valueTypes[index] ?? NIL_TYPE;
18588
- context.forgetNarrowing(declaration.name);
18589
- if (declaration.annotation === null) {
18590
- const inferred = context.allowNil || value === void 0 ? ANY_TYPE : widenInferred(valueType);
18591
- context.binder.declare({ name: declaration.name, type: inferred, isLocal: true, position: declaration.position, origin: "local" });
18592
- return;
18593
- }
18594
- const declared = context.resolveAnnotation(declaration.annotation);
18595
- if (value !== void 0) {
18596
- context.expectAssignable(valueType, declared, value.position, `Variable "${declaration.name}"`);
18859
+ function resolveSuperClass(context, statement) {
18860
+ if (statement.superClass === null) {
18861
+ return null;
18862
+ }
18863
+ if (context.declarations.lookupClass(statement.superClass) !== null) {
18864
+ if (context.isAmbientClass(statement.superClass)) {
18865
+ context.noteExternalReference(statement.superClass, statement.position);
18597
18866
  }
18598
- context.binder.declare({ name: declaration.name, type: declared, isLocal: true, position: declaration.position, origin: "local" });
18599
- });
18867
+ return statement.superClass;
18868
+ }
18869
+ if (context.mtaClasses !== null && context.mtaClasses.lookupClass(statement.superClass) !== null) {
18870
+ const message2 = `Class "${statement.name}" cannot extend native MTA class "${statement.superClass}".`;
18871
+ context.report("check-native-class-inheritance", message2, statement.position);
18872
+ return null;
18873
+ }
18874
+ const message = `Class "${statement.name}" extends "${statement.superClass}", which is not defined.`;
18875
+ context.noteExternalReference(statement.superClass, statement.position);
18876
+ context.report("check-unknown-class", message, statement.position);
18877
+ return null;
18600
18878
  }
18601
- function checkCompoundOperand(context, operator, type, position2) {
18602
- if (operator === "..=") {
18603
- if (!isConcatenable(type)) {
18604
- context.report("check-invalid-operand", `Operator "..=" cannot be applied to "${typeToString(type)}".`, position2);
18605
- }
18606
- return;
18879
+ function declareClassInfo(context, statement) {
18880
+ if (context.declarations.lookupClass(statement.name) !== null) {
18881
+ context.report("check-duplicate-class", `Class "${statement.name}" is already defined.`, statement.position);
18882
+ return null;
18607
18883
  }
18608
- if (!isNumeric(type)) {
18609
- context.report("check-invalid-operand", `Operator "${operator}" cannot be applied to "${typeToString(type)}".`, position2);
18884
+ if (context.mtaClasses !== null && context.mtaClasses.lookupClass(statement.name) !== null) {
18885
+ context.report("check-duplicate-class", `Class "${statement.name}" is reserved by MTA when OOP is enabled.`, statement.position);
18886
+ return null;
18610
18887
  }
18888
+ const info = {
18889
+ name: statement.name,
18890
+ superClass: null,
18891
+ interfaces: statement.interfaces,
18892
+ members: /* @__PURE__ */ new Map(),
18893
+ position: statement.position
18894
+ };
18895
+ context.declarations.declareClass(info);
18896
+ context.declareModuleGlobal({ name: info.name, type: createNamed(info.name), isLocal: false, position: statement.position });
18897
+ return info;
18611
18898
  }
18612
- function checkAssignment(context, statement) {
18613
- const valueTypes = checkValueList(context, statement.values);
18614
- statement.targets.forEach((target, index) => {
18615
- const targetType = checkExpression(context, target);
18616
- const valueType = valueTypes[index] ?? NIL_TYPE;
18617
- const value = valueAt(statement.values, valueTypes, index);
18618
- if (target.kind === "member-expression") {
18619
- const object = context.typeOf(target.object);
18620
- const frame = context.currentClassMethod();
18621
- if (object.kind === "named") {
18622
- const member = context.declarations.lookupMember(object.name, target.property);
18623
- if (member?.readOnly === true && (frame === null || frame.className !== object.name)) {
18624
- context.report("check-readonly-assignment", `Field "${target.property}" is read-only outside class "${object.name}".`, target.position);
18625
- }
18626
- }
18627
- }
18628
- if (statement.operator !== "=") {
18629
- checkCompoundOperand(context, statement.operator, targetType, target.position);
18630
- if (statement.values.length > 0) {
18631
- checkCompoundOperand(context, statement.operator, valueType, value?.position ?? target.position);
18632
- }
18633
- return;
18634
- }
18635
- if (target.kind === "identifier" && context.binder.lookup(target.name) === null) {
18636
- context.declareModuleGlobal({ name: target.name, type: valueType, isLocal: false, position: target.position });
18637
- return;
18638
- }
18639
- const declared = target.kind === "identifier" ? context.binder.lookup(target.name)?.type ?? targetType : targetType;
18640
- if (value !== void 0) {
18641
- context.expectAssignable(valueType, declared, value.position, "Assignment");
18642
- }
18643
- if (target.kind === "identifier") {
18644
- context.forgetNarrowing(target.name);
18645
- }
18646
- });
18899
+ function declareLocalClass(context, statement) {
18900
+ const info = declareClassInfo(context, statement);
18901
+ if (info === null) {
18902
+ return null;
18903
+ }
18904
+ info.superClass = resolveSuperClass(context, statement);
18905
+ return info;
18647
18906
  }
18648
- function checkFunctionDeclaration(context, statement) {
18649
- const type = buildFunctionType(context, statement.parameters, statement.returnAnnotation);
18650
- if (statement.name.kind === "identifier") {
18651
- const symbol = { name: statement.name.name, type, isLocal: statement.isLocal, position: statement.position };
18652
- if (statement.isLocal) {
18653
- context.binder.declare({ ...symbol, origin: "local" });
18654
- } else {
18655
- context.declareModuleGlobal(symbol);
18907
+ function checkClassDeclaration(context, statement) {
18908
+ const info = context.takePredeclaredClass(statement.name) ?? declareLocalClass(context, statement);
18909
+ if (info === null) {
18910
+ return;
18911
+ }
18912
+ context.generatedMembers.set(statement, registerMembers(context, info, statement));
18913
+ declareBuilder(context, info, statement);
18914
+ checkInterfaces(context, info);
18915
+ checkOverrides(context, info, statement);
18916
+ for (const member of statement.members) {
18917
+ if (member.kind === "class-method") {
18918
+ checkMethodBody(context, info, member);
18656
18919
  }
18657
18920
  }
18658
- context.record(statement.name, type);
18659
- checkFunctionBody(context, statement.parameters, statement.returnAnnotation, statement.body, type, statement.isMethod ? ANY_TYPE : null);
18660
18921
  }
18661
- function checkReturn(context, statement) {
18662
- const valueTypes = checkValueList(context, statement.values);
18663
- const expected = context.currentReturnType();
18664
- if (expected === null) {
18665
- context.inferReturnType(createTuple(valueTypes));
18922
+ function checkInterfaceDeclaration(context, statement) {
18923
+ if (context.declarations.lookupInterface(statement.name) !== null) {
18924
+ context.report("check-duplicate-interface", `Interface "${statement.name}" is already defined.`, statement.position);
18666
18925
  return;
18667
18926
  }
18668
- if (expected.kind === "any") {
18669
- return;
18927
+ const members = /* @__PURE__ */ new Map();
18928
+ for (const name of new Set(statement.superInterfaces)) {
18929
+ const parent = context.declarations.lookupInterface(name);
18930
+ if (name === statement.name || context.declarations.interfaceExtends(name, statement.name)) {
18931
+ context.report("check-interface-cycle", `Interface "${statement.name}" creates an inheritance cycle through "${name}".`, statement.position);
18932
+ } else if (parent === null) {
18933
+ context.noteExternalReference(name, statement.position);
18934
+ context.report("check-unknown-interface", `Interface "${statement.name}" extends "${name}", which is not defined.`, statement.position);
18935
+ } else if (context.isAmbientInterface(name)) {
18936
+ context.noteExternalReference(name, statement.position);
18937
+ }
18670
18938
  }
18671
- if (expected.kind === "void") {
18672
- if (statement.values.length > 0) {
18673
- context.report("check-return-mismatch", 'This function is declared "void" and cannot return a value.', statement.position);
18939
+ if (new Set(statement.superInterfaces).size !== statement.superInterfaces.length) {
18940
+ context.report("check-duplicate-interface-parent", `Interface "${statement.name}" extends the same interface more than once.`, statement.position);
18941
+ }
18942
+ for (const member of statement.members) {
18943
+ const type = member.kind === "interface-field" ? context.resolveAnnotation(member.annotation) : buildFunctionType(context, member.parameters, member.returnAnnotation);
18944
+ const info = { name: member.name, type, isMethod: member.kind === "interface-method", position: member.position };
18945
+ if (members.has(member.name)) {
18946
+ context.report("check-duplicate-interface-member", `Interface "${statement.name}" declares member "${member.name}" more than once.`, member.position);
18947
+ } else {
18948
+ members.set(member.name, info);
18674
18949
  }
18675
- return;
18676
18950
  }
18677
- if (expected.kind === "tuple") {
18678
- if (valueTypes.length !== expected.elements.length) {
18679
- const message = `This function declares ${expected.elements.length} return values but returns ${valueTypes.length}.`;
18680
- context.report("check-return-mismatch", message, statement.position);
18681
- return;
18951
+ const inherited = /* @__PURE__ */ new Map();
18952
+ for (const parent of statement.superInterfaces) {
18953
+ for (const member of context.declarations.collectInterfaceContract(parent)) {
18954
+ const existing = members.get(member.name) ?? inherited.get(member.name);
18955
+ if (existing !== void 0 && (existing.isMethod !== member.isMethod || typeToString(existing.type) !== typeToString(member.type))) {
18956
+ context.report("check-conflicting-interface-member", `Interface "${statement.name}" inherits conflicting declarations of "${member.name}".`, statement.position);
18957
+ } else if (existing === void 0) {
18958
+ inherited.set(member.name, member);
18959
+ }
18682
18960
  }
18683
- valueTypes.forEach((type, index) => {
18684
- const value2 = statement.values[Math.min(index, statement.values.length - 1)];
18685
- context.expectAssignable(type, expected.elements[index] ?? ANY_TYPE, value2?.position ?? statement.position, `Return value ${index + 1}`);
18686
- });
18687
- return;
18688
18961
  }
18689
- if (valueTypes.length > 1) {
18690
- const message = `This function declares one return value of type "${typeToString(expected)}" but returns ${valueTypes.length} values.`;
18691
- context.report("check-return-mismatch", message, statement.position);
18962
+ context.declarations.declareInterface({
18963
+ name: statement.name,
18964
+ superInterfaces: statement.superInterfaces,
18965
+ members,
18966
+ position: statement.position
18967
+ });
18968
+ }
18969
+ function checkEnumDeclaration(context, statement) {
18970
+ const existing = context.declarations.lookupEnum(statement.name);
18971
+ const shadowsAmbient = statement.isLocal && existing?.isLocal !== true && context.isAmbientEnum(statement.name);
18972
+ if (existing !== null && !shadowsAmbient) {
18973
+ context.report("check-duplicate-enum", `Enum "${statement.name}" is already defined.`, statement.position);
18692
18974
  return;
18693
18975
  }
18694
- const first = valueTypes[0];
18695
- const value = statement.values[0];
18696
- if (first === void 0 || value === void 0) {
18697
- context.expectAssignable(NIL_TYPE, expected, statement.position, "Return value");
18698
- return;
18976
+ const members = statement.members.map((member) => member.name);
18977
+ context.declarations.declareEnum({ name: statement.name, members, isLocal: statement.isLocal, position: statement.position });
18978
+ if (statement.isLocal) {
18979
+ context.binder.declare({ name: statement.name, type: createNamed(statement.name), isLocal: true, position: statement.position, origin: "local" });
18980
+ } else {
18981
+ context.declareModuleGlobal({ name: statement.name, type: createNamed(statement.name), isLocal: false, position: statement.position });
18699
18982
  }
18700
- context.expectAssignable(first, expected, value.position, "Return value");
18701
18983
  }
18702
- function checkDeclareStatement(context, statement) {
18703
- if (!context.isDeclarationFile) {
18704
- const message = 'A "declare" statement describes code the compiler does not own and belongs in a ".d.luam" file.';
18705
- context.report("check-declare-outside-declaration-file", message, statement.position);
18706
- return;
18984
+
18985
+ // ../compiler/src/checker/predeclaration.ts
18986
+ function declareHeaders(context, body) {
18987
+ const declared = [];
18988
+ for (const statement of body) {
18989
+ if (statement.kind !== "class-declaration") {
18990
+ continue;
18991
+ }
18992
+ const info = declareClassInfo(context, statement);
18993
+ if (info === null) {
18994
+ continue;
18995
+ }
18996
+ context.predeclareClass(info);
18997
+ declared.push({ info, statement });
18707
18998
  }
18708
- const type = context.resolveAnnotation(statement.annotation);
18709
- context.declareModuleGlobal({ name: statement.name, type, isLocal: false, position: statement.position });
18710
- context.declarations.declareGlobal({ name: statement.name, type, position: statement.position });
18999
+ return declared;
18711
19000
  }
18712
- function checkStatement(context, statement) {
18713
- switch (statement.kind) {
18714
- case "local-statement":
18715
- return checkLocal(context, statement);
18716
- case "assignment-statement":
18717
- return checkAssignment(context, statement);
18718
- case "call-statement":
18719
- checkExpression(context, statement.expression);
18720
- return;
18721
- case "function-declaration":
18722
- return checkFunctionDeclaration(context, statement);
18723
- case "return-statement":
18724
- return checkReturn(context, statement);
18725
- case "do-statement":
18726
- return checkBlock(context, statement.body);
18727
- case "while-statement":
18728
- checkExpression(context, statement.condition);
18729
- return checkBlock(context, statement.body);
18730
- case "repeat-statement":
18731
- checkBlock(context, statement.body);
18732
- checkExpression(context, statement.condition);
18733
- return;
18734
- case "if-statement":
18735
- return checkIf(context, statement.clauses, statement.alternate);
18736
- case "numeric-for-statement":
18737
- return checkNumericFor(context, statement);
18738
- case "generic-for-statement":
18739
- return checkGenericFor(context, statement);
18740
- case "type-alias-statement": {
18741
- const resolved2 = context.resolveAnnotation(statement.annotation);
18742
- const shape = statement.annotation.kind === "type-object" || statement.annotation.kind === "type-intersection";
18743
- const named2 = shape ? renameRecord(resolved2, statement.name) : resolved2;
18744
- context.noteTypeParameters(statement.typeParameters);
18745
- context.binder.declareAlias(statement.name, named2, statement.typeParameters);
19001
+ function breakCycle(context, info) {
19002
+ const seen = /* @__PURE__ */ new Set([info.name]);
19003
+ let current = info.superClass === null ? null : context.declarations.lookupClass(info.superClass);
19004
+ while (current !== null) {
19005
+ if (seen.has(current.name)) {
19006
+ context.report("check-class-cycle", `Class "${info.name}" creates an inheritance cycle through "${current.name}".`, info.position);
19007
+ info.superClass = null;
18746
19008
  return;
18747
19009
  }
18748
- case "declare-statement":
18749
- return checkDeclareStatement(context, statement);
18750
- case "event-declaration":
18751
- return checkEventDeclaration(context, statement);
18752
- case "class-declaration":
18753
- return checkClassDeclaration(context, statement);
18754
- case "interface-declaration":
18755
- return checkInterfaceDeclaration(context, statement);
18756
- case "enum-declaration":
18757
- return checkEnumDeclaration(context, statement);
18758
- default:
18759
- return;
19010
+ seen.add(current.name);
19011
+ current = current.superClass === null ? null : context.declarations.lookupClass(current.superClass);
18760
19012
  }
18761
19013
  }
18762
- function checkStatements(context, statements) {
18763
- let guards = 0;
18764
- for (const statement of statements) {
18765
- checkStatement(context, statement);
18766
- const facts = guardFacts(context, statement);
18767
- if (facts.size > 0) {
18768
- context.pushNarrowing(facts);
18769
- guards += 1;
18770
- }
19014
+ function predeclareModule(context, body) {
19015
+ const declared = declareHeaders(context, body);
19016
+ for (const entry of declared) {
19017
+ entry.info.superClass = resolveSuperClass(context, entry.statement);
18771
19018
  }
18772
- while (guards > 0) {
18773
- context.popNarrowing();
18774
- guards -= 1;
19019
+ for (const entry of declared) {
19020
+ breakCycle(context, entry.info);
18775
19021
  }
18776
19022
  }
18777
19023
 
@@ -18835,6 +19081,7 @@ function check(program2, mode, environment = DEFAULT_ENVIRONMENT, options = {})
18835
19081
  context.isDeclarationFile = options.isDeclarationFile === true;
18836
19082
  const structure = context.isDeclarationFile ? checkDeclarationFile(program2.body) : checkJumps(program2.body);
18837
19083
  const directives = collectDirectives(program2.body, { isDeclarationFile: context.isDeclarationFile });
19084
+ predeclareModule(context, program2.body);
18838
19085
  checkStatements(context, program2.body);
18839
19086
  reportUnknownTypes(context);
18840
19087
  reportUnusedSymbols(context, options.noUnusedLocals === true, options.noUnusedParameters === true);
@@ -19263,7 +19510,8 @@ function emitEnumDeclaration(state, statement) {
19263
19510
  }
19264
19511
  requireHelper(state, "class");
19265
19512
  const names = statement.members.map((member) => emitString(member.name));
19266
- return names.length === 0 ? `${statement.name} = enum {}` : `${statement.name} = enum { ${names.join(", ")} }`;
19513
+ const target = statement.isLocal ? `local ${statement.name}` : statement.name;
19514
+ return names.length === 0 ? `${target} = enum {}` : `${target} = enum { ${names.join(", ")} }`;
19267
19515
  }
19268
19516
 
19269
19517
  // ../compiler/src/emitter/loops.ts
@@ -19714,6 +19962,25 @@ function canonicalEdit(input, statement, span) {
19714
19962
  return { start: span.start, end: span.end, replacement: padded, lines: emitted.lines };
19715
19963
  }
19716
19964
 
19965
+ // ../compiler/src/emitter/preserve-decorators.ts
19966
+ var MARKER = new RegExp(String.fromCharCode(0) + "luam:\\d+" + String.fromCharCode(0), "g");
19967
+ function compact(text) {
19968
+ return text.replace(MARKER, "").replace(/\s*\n\s*/g, " ").trim();
19969
+ }
19970
+ function injectedMembersText(input, statement) {
19971
+ const generated = input.generatedMembers.get(statement) ?? [];
19972
+ if (generated.length === 0) {
19973
+ return null;
19974
+ }
19975
+ const state = createEmitState(input.types, input.references, input.generatedMembers);
19976
+ return generated.map((member) => compact(emitMethod(state, statement.name, member))).join(", ");
19977
+ }
19978
+ function builderClassText(input, statement) {
19979
+ const state = createEmitState(input.types, input.references, input.generatedMembers);
19980
+ const builder = emitBuilder(state, statement);
19981
+ return builder === null ? null : compact(builder);
19982
+ }
19983
+
19717
19984
  // ../compiler/src/emitter/preserve-guards.ts
19718
19985
  function isPreservableExpression(expression, types) {
19719
19986
  switch (expression.kind) {
@@ -19834,27 +20101,52 @@ function selfEdit(source, member, span) {
19834
20101
  return { start: parenthesis + 1, end: parenthesis + 1, replacement: "self" };
19835
20102
  }
19836
20103
  function isKept(member) {
19837
- return member.kind === "class-method" || member.value !== null;
20104
+ return !isLazyField(member) && (member.kind === "class-method" || member.value !== null);
19838
20105
  }
19839
- function isLowered(statement, generated, types) {
19840
- if (statement.decorators.length > 0 || generated > 0) {
20106
+ function isLazyField(member) {
20107
+ return member.kind === "class-field" && member.decorators.some((decorator) => decorator.name === "Lazy");
20108
+ }
20109
+ function isLowered(input, statement) {
20110
+ const generated = input.generatedMembers.get(statement)?.length ?? 0;
20111
+ const decorated = statement.decorators.length > 0 || generated > 0 || statement.members.some((member) => member.decorators.length > 0);
20112
+ if (decorated && !input.development) {
19841
20113
  return true;
19842
20114
  }
19843
- return statement.members.some((member) => {
19844
- if (member.decorators.length > 0) {
19845
- return true;
20115
+ return statement.members.some(
20116
+ (member) => member.kind === "class-field" && member.value !== null && !isPreservableExpression(member.value, input.types)
20117
+ );
20118
+ }
20119
+ function decoratorCommentEdits(statement) {
20120
+ const firstByLine = /* @__PURE__ */ new Map();
20121
+ const note = (position2) => {
20122
+ const existing = firstByLine.get(position2.line);
20123
+ if (existing === void 0 || position2.offset < existing) {
20124
+ firstByLine.set(position2.line, position2.offset);
19846
20125
  }
19847
- return member.kind === "class-field" && member.value !== null && !isPreservableExpression(member.value, types);
19848
- });
20126
+ };
20127
+ for (const decorator of statement.decorators) {
20128
+ note(decorator.position);
20129
+ }
20130
+ for (const member of statement.members) {
20131
+ if (!isKept(member)) {
20132
+ continue;
20133
+ }
20134
+ for (const decorator of member.decorators) {
20135
+ note(decorator.position);
20136
+ }
20137
+ }
20138
+ return [...firstByLine.values()].map((offset) => ({ start: offset, end: offset, replacement: "--" }));
19849
20139
  }
19850
- function classEdits(source, statement, generated, types, spans) {
19851
- if (isLowered(statement, generated, types)) {
20140
+ function classEdits(input, statement, spans) {
20141
+ const { source } = input;
20142
+ if (isLowered(input, statement)) {
19852
20143
  return null;
19853
20144
  }
19854
20145
  const header = classHeaderEdit(source, statement);
19855
20146
  if (header === null) {
19856
20147
  return null;
19857
20148
  }
20149
+ const injected = input.development ? injectedMembersText(input, statement) : null;
19858
20150
  const edits = [header];
19859
20151
  const kept = statement.members.filter(isKept);
19860
20152
  const limit = spans.span.end;
@@ -19866,7 +20158,8 @@ function classEdits(source, statement, generated, types, spans) {
19866
20158
  const separator = separatorAt(source, span.end, limit);
19867
20159
  if (!isKept(member)) {
19868
20160
  const end = separator === null ? span.end : separator + 1;
19869
- edits.push({ start: span.start, end, replacement: blankSpan(source.slice(span.start, end)) });
20161
+ const text = source.slice(span.start, end);
20162
+ edits.push({ start: span.start, end, replacement: isLazyField(member) ? longComment(text) : blankSpan(text) });
19870
20163
  continue;
19871
20164
  }
19872
20165
  if (member.kind === "class-method") {
@@ -19876,10 +20169,22 @@ function classEdits(source, statement, generated, types, spans) {
19876
20169
  }
19877
20170
  edits.push(self);
19878
20171
  }
19879
- if (separator === null && member !== kept[kept.length - 1]) {
20172
+ if (separator === null && (member !== kept[kept.length - 1] || injected !== null)) {
19880
20173
  edits.push({ start: span.end, end: span.end, replacement: "," });
19881
20174
  }
19882
20175
  }
20176
+ edits.push(...decoratorCommentEdits(statement));
20177
+ if (injected !== null) {
20178
+ const brace = source.lastIndexOf("}", limit);
20179
+ if (brace <= spans.span.start) {
20180
+ return null;
20181
+ }
20182
+ edits.push({ start: brace, end: brace, replacement: `${injected} ` });
20183
+ }
20184
+ const builder = input.development ? builderClassText(input, statement) : null;
20185
+ if (builder !== null) {
20186
+ edits.push({ start: limit, end: limit, replacement: ` ${builder}` });
20187
+ }
19883
20188
  return edits;
19884
20189
  }
19885
20190
 
@@ -19968,8 +20273,7 @@ function surgery(collector, statement) {
19968
20273
  return edits2 === null ? null : { edits: edits2, wrapsBody: false };
19969
20274
  }
19970
20275
  if (statement.kind === "class-declaration") {
19971
- const generated = input.generatedMembers.get(statement)?.length ?? 0;
19972
- const edits2 = classEdits(input.source, statement, generated, input.types, { span, members: input.spans });
20276
+ const edits2 = classEdits(input, statement, { span, members: input.spans });
19973
20277
  return edits2 === null ? null : { edits: edits2, wrapsBody: false };
19974
20278
  }
19975
20279
  if (loopBody(statement) === null || !isPreservableStatement(statement, input.types)) {