@thigasdevelopment/luam 0.8.2 → 0.10.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.
Files changed (2) hide show
  1. package/luam.mjs +482 -19
  2. package/package.json +1 -1
package/luam.mjs CHANGED
@@ -3534,6 +3534,7 @@ var SYMBOLS = [
3534
3534
  ">",
3535
3535
  "=",
3536
3536
  "|",
3537
+ "&",
3537
3538
  "?",
3538
3539
  "@",
3539
3540
  "(",
@@ -3809,14 +3810,25 @@ function parsePostfixType(stream) {
3809
3810
  return annotation;
3810
3811
  }
3811
3812
  }
3812
- function parseTypeAnnotation(stream) {
3813
+ function parseIntersectionType(stream) {
3813
3814
  const first = parsePostfixType(stream);
3815
+ if (!stream.check("operator", "&")) {
3816
+ return first;
3817
+ }
3818
+ const parts = [first];
3819
+ while (stream.match("operator", "&")) {
3820
+ parts.push(parsePostfixType(stream));
3821
+ }
3822
+ return { kind: "type-intersection", parts, position: first.position };
3823
+ }
3824
+ function parseTypeAnnotation(stream) {
3825
+ const first = parseIntersectionType(stream);
3814
3826
  if (!stream.check("operator", "|")) {
3815
3827
  return first;
3816
3828
  }
3817
3829
  const options = [first];
3818
3830
  while (stream.match("operator", "|")) {
3819
- options.push(parsePostfixType(stream));
3831
+ options.push(parseIntersectionType(stream));
3820
3832
  }
3821
3833
  return { kind: "type-union", options, position: first.position };
3822
3834
  }
@@ -4774,6 +4786,9 @@ function createArray(element) {
4774
4786
  function createNamed(name) {
4775
4787
  return { kind: "named", name };
4776
4788
  }
4789
+ function createMap(key, value) {
4790
+ return { kind: "map", key, value };
4791
+ }
4777
4792
  function createStringLiteral(value) {
4778
4793
  return { kind: "string-literal", value };
4779
4794
  }
@@ -4850,6 +4865,9 @@ function typeToString(type) {
4850
4865
  if (type.kind === "array") {
4851
4866
  return `${typeToString(type.element)}[]`;
4852
4867
  }
4868
+ if (type.kind === "map") {
4869
+ return `table<${typeToString(type.key)}, ${typeToString(type.value)}>`;
4870
+ }
4853
4871
  if (type.kind === "optional") {
4854
4872
  return `${typeToString(type.element)}?`;
4855
4873
  }
@@ -4874,17 +4892,38 @@ function typeToString(type) {
4874
4892
  }
4875
4893
  return type.kind;
4876
4894
  }
4895
+ function withoutNil(type) {
4896
+ if (type.kind === "optional") {
4897
+ return type.element;
4898
+ }
4899
+ if (type.kind === "union") {
4900
+ return createUnion(type.options.filter((option) => option.kind !== "nil"));
4901
+ }
4902
+ return type;
4903
+ }
4904
+ function acceptsNil(type) {
4905
+ if (type.kind === "nil" || type.kind === "optional" || type.kind === "any" || type.kind === "unknown") {
4906
+ return true;
4907
+ }
4908
+ return type.kind === "union" && type.options.some((option) => option.kind === "nil");
4909
+ }
4877
4910
  function isTableLike(type) {
4878
- return type.kind === "table" || type.kind === "array";
4911
+ return type.kind === "table" || type.kind === "array" || type.kind === "map";
4879
4912
  }
4880
4913
  function isBooleanType(type) {
4881
4914
  const resolved2 = type?.kind === "optional" ? type.element : type;
4882
4915
  return resolved2?.kind === "boolean";
4883
4916
  }
4884
4917
  function isNumeric(type) {
4918
+ if (type.kind === "union") {
4919
+ return type.options.every(isNumeric);
4920
+ }
4885
4921
  return type.kind === "number" || type.kind === "any" || type.kind === "unknown" || type.kind === "named";
4886
4922
  }
4887
4923
  function isConcatenable(type) {
4924
+ if (type.kind === "union") {
4925
+ return type.options.every(isConcatenable);
4926
+ }
4888
4927
  return type.kind === "string" || type.kind === "string-literal" || isNumeric(type);
4889
4928
  }
4890
4929
  function isFunctionAssignable(source, target, options) {
@@ -4901,6 +4940,18 @@ function isFunctionAssignable(source, target, options) {
4901
4940
  }
4902
4941
  return target.returnType.kind === "void" || isAssignable(source.returnType, target.returnType, options);
4903
4942
  }
4943
+ function isMapAssignable(source, target, options) {
4944
+ if (source.kind === "table") {
4945
+ return true;
4946
+ }
4947
+ if (source.kind === "array") {
4948
+ return isAssignable(NUMBER_TYPE, target.key, options) && isAssignable(source.element, target.value, options);
4949
+ }
4950
+ if (source.kind === "record") {
4951
+ return isAssignable(STRING_TYPE, target.key, options) && [...source.members.values()].every((member) => isAssignable(member, target.value, options));
4952
+ }
4953
+ return source.kind === "map" && isAssignable(source.key, target.key, options) && isAssignable(source.value, target.value, options);
4954
+ }
4904
4955
  function isRecordAssignable(source, target, options) {
4905
4956
  for (const [name, member] of target.members) {
4906
4957
  const value = source.members.get(name);
@@ -4953,6 +5004,9 @@ function isAssignable(source, target, options = { allowNil: false }) {
4953
5004
  if (target.kind === "array") {
4954
5005
  return source.kind === "table" || source.kind === "array" && isAssignable(source.element, target.element, options);
4955
5006
  }
5007
+ if (target.kind === "map") {
5008
+ return isMapAssignable(source, target, options);
5009
+ }
4956
5010
  if (source.kind === "function" && target.kind === "function") {
4957
5011
  return isFunctionAssignable(source, target, options);
4958
5012
  }
@@ -5012,8 +5066,8 @@ function stringValue(value) {
5012
5066
  return { value, type, truthy: type, falsy: null };
5013
5067
  }
5014
5068
  function unionOf(options) {
5015
- const present2 = options.filter((option) => option !== null);
5016
- return present2.length === 0 ? NIL_TYPE : createUnion([...present2]);
5069
+ const present3 = options.filter((option) => option !== null);
5070
+ return present3.length === 0 ? NIL_TYPE : createUnion([...present3]);
5017
5071
  }
5018
5072
 
5019
5073
  // ../runtime/src/helpers.ts
@@ -5945,7 +5999,7 @@ import { tmpdir } from "node:os";
5945
5999
  import { join as join2 } from "node:path";
5946
6000
 
5947
6001
  // src/cli/version.ts
5948
- var VERSION = true ? "0.8.2" : "0.0.0-dev";
6002
+ var VERSION = true ? "0.10.0" : "0.0.0-dev";
5949
6003
  var PROGRAM_NAME = "luam";
5950
6004
  var PROGRAM_DESCRIPTION = "luam \u2014 the Luam compiler for Multi Theft Auto resources.";
5951
6005
 
@@ -10979,6 +11033,41 @@ function mtaMember(className, member) {
10979
11033
  return mtaClassRegistry().lookupMember(className, member);
10980
11034
  }
10981
11035
 
11036
+ // ../compiler/src/checker/type-intersection.ts
11037
+ function namedMembers(context, name) {
11038
+ if (context.declarations.lookupInterface(name) === null && context.declarations.lookupClass(name) === null) {
11039
+ return null;
11040
+ }
11041
+ return new Map(context.declarations.collectMembers(name).map((member) => [member.name, member.type]));
11042
+ }
11043
+ function partMembers(context, part) {
11044
+ if (part.kind === "record") {
11045
+ return part.members;
11046
+ }
11047
+ return part.kind === "named" ? namedMembers(context, part.name) : null;
11048
+ }
11049
+ function mergeIntersection(context, parts, position2) {
11050
+ const members = /* @__PURE__ */ new Map();
11051
+ for (const part of parts) {
11052
+ const collected = partMembers(context, part);
11053
+ if (collected === null) {
11054
+ const message = `"${typeToString(part)}" is not an object type, so it cannot take part in an intersection.`;
11055
+ context.report("check-invalid-intersection", message, position2);
11056
+ continue;
11057
+ }
11058
+ for (const [name, type] of collected) {
11059
+ const existing = members.get(name);
11060
+ if (existing !== void 0 && typeToString(existing) !== typeToString(type)) {
11061
+ const message = `Intersection declares "${name}" as both "${typeToString(existing)}" and "${typeToString(type)}".`;
11062
+ context.report("check-conflicting-intersection-member", message, position2);
11063
+ continue;
11064
+ }
11065
+ members.set(name, type);
11066
+ }
11067
+ }
11068
+ return createObjectType(members);
11069
+ }
11070
+
10982
11071
  // ../compiler/src/checker/type-substitution.ts
10983
11072
  function substituteType(type, substitutions) {
10984
11073
  if (type.kind === "named") {
@@ -10987,6 +11076,9 @@ function substituteType(type, substitutions) {
10987
11076
  if (type.kind === "array") {
10988
11077
  return createArray(substituteType(type.element, substitutions));
10989
11078
  }
11079
+ if (type.kind === "map") {
11080
+ return createMap(substituteType(type.key, substitutions), substituteType(type.value, substitutions));
11081
+ }
10990
11082
  if (type.kind === "optional") {
10991
11083
  return createOptional(substituteType(type.element, substitutions));
10992
11084
  }
@@ -11021,6 +11113,8 @@ var BUILTIN_TYPES = {
11021
11113
  userdata: USERDATA_TYPE,
11022
11114
  void: VOID_TYPE
11023
11115
  };
11116
+ var TABLE_ANNOTATION = "table";
11117
+ var MAP_ARGUMENTS = 2;
11024
11118
  var PROJECT_POSITION = { line: 0, column: 0, offset: 0 };
11025
11119
  function nilHint(source, target, mode) {
11026
11120
  if (mode !== "strict" || source.kind !== "nil" || target.kind === "nil" || target.kind === "optional") {
@@ -11032,10 +11126,14 @@ var CheckContext = class {
11032
11126
  binder = new Binder();
11033
11127
  declarations = new DeclarationRegistry();
11034
11128
  diagnostics = [];
11129
+ reported = /* @__PURE__ */ new Set();
11130
+ narrowings = [];
11035
11131
  types = /* @__PURE__ */ new Map();
11036
11132
  references = /* @__PURE__ */ new Set();
11037
11133
  declaredGlobals = /* @__PURE__ */ new Map();
11038
11134
  externalReferences = /* @__PURE__ */ new Map();
11135
+ unknownTypes = /* @__PURE__ */ new Map();
11136
+ typeParameters = /* @__PURE__ */ new Set();
11039
11137
  generatedMembers = /* @__PURE__ */ new Map();
11040
11138
  mode;
11041
11139
  environment;
@@ -11064,6 +11162,26 @@ var CheckContext = class {
11064
11162
  this.binder.declareGlobal(symbol);
11065
11163
  this.declaredGlobals.set(symbol.name, symbol.position);
11066
11164
  }
11165
+ pushNarrowing(facts) {
11166
+ this.narrowings.push(new Map(facts));
11167
+ }
11168
+ popNarrowing() {
11169
+ this.narrowings.pop();
11170
+ }
11171
+ forgetNarrowing(name) {
11172
+ for (const frame of this.narrowings) {
11173
+ frame.delete(name);
11174
+ }
11175
+ }
11176
+ narrowedType(name) {
11177
+ for (let index = this.narrowings.length - 1; index >= 0; index -= 1) {
11178
+ const found = this.narrowings[index]?.get(name);
11179
+ if (found !== void 0) {
11180
+ return found;
11181
+ }
11182
+ }
11183
+ return null;
11184
+ }
11067
11185
  isAmbientClass(name) {
11068
11186
  return this.ambientClasses.has(name);
11069
11187
  }
@@ -11076,10 +11194,34 @@ var CheckContext = class {
11076
11194
  }
11077
11195
  }
11078
11196
  report(code, message, position2) {
11197
+ this.push(code, message, position2, "error");
11198
+ }
11199
+ warn(code, message, position2) {
11200
+ this.push(code, message, position2, "warning");
11201
+ }
11202
+ push(code, message, position2, severity) {
11079
11203
  if (this.mode === "nocheck") {
11080
11204
  return;
11081
11205
  }
11082
- this.diagnostics.push(createDiagnostic("checker", code, message, position2));
11206
+ const key = `${code}|${position2.offset}|${message}`;
11207
+ if (this.reported.has(key)) {
11208
+ return;
11209
+ }
11210
+ this.reported.add(key);
11211
+ this.diagnostics.push(createDiagnostic("checker", code, message, position2, severity));
11212
+ }
11213
+ noteTypeParameters(names) {
11214
+ for (const name of names) {
11215
+ this.typeParameters.add(name);
11216
+ }
11217
+ }
11218
+ isTypeParameter(name) {
11219
+ return this.typeParameters.has(name);
11220
+ }
11221
+ noteUnknownType(name, position2) {
11222
+ if (!this.unknownTypes.has(name)) {
11223
+ this.unknownTypes.set(name, position2);
11224
+ }
11083
11225
  }
11084
11226
  record(expression, type) {
11085
11227
  this.types.set(expression, type);
@@ -11138,6 +11280,10 @@ var CheckContext = class {
11138
11280
  if (annotation.kind === "type-union") {
11139
11281
  return createUnion(annotation.options.map((option) => this.resolveAnnotation(option)));
11140
11282
  }
11283
+ if (annotation.kind === "type-intersection") {
11284
+ const parts = annotation.parts.map((part) => this.resolveAnnotation(part));
11285
+ return mergeIntersection(this, parts, annotation.position);
11286
+ }
11141
11287
  if (annotation.kind === "type-string-literal") {
11142
11288
  return createStringLiteral(annotation.value);
11143
11289
  }
@@ -11194,9 +11340,24 @@ var CheckContext = class {
11194
11340
  this.binder.declareGlobal({ name: info.name, type: info.type, isLocal: false, position: info.position });
11195
11341
  }
11196
11342
  }
11343
+ resolveMapAnnotation(typeArguments, position2) {
11344
+ const [key, value] = typeArguments;
11345
+ if (key === void 0 || value === void 0 || typeArguments.length !== MAP_ARGUMENTS) {
11346
+ const message = `Type "${TABLE_ANNOTATION}" expects a key type and a value type but received ${typeArguments.length}.`;
11347
+ this.report("check-generic-arity", message, position2);
11348
+ for (const argument of typeArguments) {
11349
+ this.resolveAnnotation(argument);
11350
+ }
11351
+ return TABLE_TYPE;
11352
+ }
11353
+ return createMap(this.resolveAnnotation(key), this.resolveAnnotation(value));
11354
+ }
11197
11355
  resolveNamedAnnotation(name, typeArguments, position2) {
11198
11356
  const builtin = BUILTIN_TYPES[name];
11199
11357
  if (builtin !== void 0) {
11358
+ if (name === TABLE_ANNOTATION && typeArguments.length > 0) {
11359
+ return this.resolveMapAnnotation(typeArguments, position2);
11360
+ }
11200
11361
  if (typeArguments.length > 0) {
11201
11362
  this.report("check-generic-arity", `Type "${name}" does not accept type arguments.`, position2);
11202
11363
  }
@@ -11204,6 +11365,7 @@ var CheckContext = class {
11204
11365
  }
11205
11366
  const alias = this.binder.lookupAlias(name);
11206
11367
  if (alias === null) {
11368
+ this.noteUnknownType(name, position2);
11207
11369
  return createNamed(name);
11208
11370
  }
11209
11371
  if (typeArguments.length !== alias.parameters.length) {
@@ -12154,8 +12316,11 @@ function reportInvalidOperand(context, operator, operand, expression) {
12154
12316
  context.report("check-invalid-operand", `Operator "${operator}" cannot be applied to "${typeToString(operand)}".`, expression.position);
12155
12317
  }
12156
12318
  function checkBinary(context, operator, left, right, expression) {
12157
- if (operator === "and" || operator === "or") {
12158
- return context.record(expression, createUnion([left, right]));
12319
+ if (operator === "or") {
12320
+ return context.record(expression, createUnion([withoutNil(left), right]));
12321
+ }
12322
+ if (operator === "and") {
12323
+ return context.record(expression, acceptsNil(left) ? createUnion([right, NIL_TYPE]) : right);
12159
12324
  }
12160
12325
  if (operator === "==" || operator === "~=" || COMPARISON.has(operator)) {
12161
12326
  return context.record(expression, BOOLEAN_TYPE);
@@ -12212,6 +12377,47 @@ function templateLiteralText(segments) {
12212
12377
  return segments.map((segment) => segment.kind === "text" ? segment.value : `\${${segment.value}}`).join("");
12213
12378
  }
12214
12379
 
12380
+ // ../compiler/src/checker/union-members.ts
12381
+ function isShapeOption(context, option) {
12382
+ if (option.kind === "record") {
12383
+ return true;
12384
+ }
12385
+ if (option.kind !== "named") {
12386
+ return false;
12387
+ }
12388
+ return context.declarations.lookupInterface(option.name) !== null || context.declarations.lookupClass(option.name) !== null;
12389
+ }
12390
+ function memberOf(context, option, property) {
12391
+ if (option.kind === "record") {
12392
+ return option.members.get(property) ?? null;
12393
+ }
12394
+ return option.kind === "named" ? context.declarations.lookupMember(option.name, property)?.type ?? null : null;
12395
+ }
12396
+ function optionMemberType(context, option, property) {
12397
+ return isShapeOption(context, option) ? memberOf(context, option, property) : null;
12398
+ }
12399
+ function resolveUnionMember(context, type, expression) {
12400
+ if (!type.options.every((option) => isShapeOption(context, option))) {
12401
+ return null;
12402
+ }
12403
+ const resolved2 = [];
12404
+ const missing = [];
12405
+ for (const option of type.options) {
12406
+ const member = memberOf(context, option, expression.property);
12407
+ if (member === null) {
12408
+ missing.push(`"${typeToString(option)}"`);
12409
+ } else {
12410
+ resolved2.push(member);
12411
+ }
12412
+ }
12413
+ if (missing.length === 0) {
12414
+ return createUnion(resolved2);
12415
+ }
12416
+ const message = `"${expression.property}" is not a key of every member of "${typeToString(type)}". It is missing from ${missing.join(", ")}.`;
12417
+ context.report("check-unknown-union-key", message, expression.position);
12418
+ return ANY_TYPE;
12419
+ }
12420
+
12215
12421
  // ../compiler/src/checker/expressions.ts
12216
12422
  var EXTENSION_RESULTS = {
12217
12423
  boolean: BOOLEAN_TYPE,
@@ -12254,6 +12460,15 @@ function checkMember(context, expression) {
12254
12460
  if (objectType.kind === "record") {
12255
12461
  return context.record(expression, resolveRecordMember(context, objectType, expression));
12256
12462
  }
12463
+ if (objectType.kind === "union") {
12464
+ const option = resolveUnionMember(context, objectType, expression);
12465
+ if (option !== null) {
12466
+ return context.record(expression, option);
12467
+ }
12468
+ }
12469
+ if (objectType.kind === "map" && isAssignable(STRING_TYPE, objectType.key)) {
12470
+ return context.record(expression, objectType.value);
12471
+ }
12257
12472
  const named2 = objectType.kind === "named" ? resolveNamedMember(context, objectType.name, expression) : null;
12258
12473
  if (named2 !== null) {
12259
12474
  return context.record(expression, named2);
@@ -12374,13 +12589,17 @@ function checkMultiValueExpression(context, expression) {
12374
12589
  if (symbol === null) {
12375
12590
  checkGlobalReference(context, expression.name, expression.position);
12376
12591
  }
12377
- return context.record(expression, symbol?.type ?? ANY_TYPE);
12592
+ return context.record(expression, context.narrowedType(expression.name) ?? symbol?.type ?? ANY_TYPE);
12378
12593
  }
12379
12594
  case "member-expression":
12380
12595
  return checkMember(context, expression);
12381
12596
  case "index-expression": {
12382
12597
  const objectType = checkExpression(context, expression.object);
12383
- checkExpression(context, expression.index);
12598
+ const indexType = checkExpression(context, expression.index);
12599
+ if (objectType.kind === "map") {
12600
+ context.expectAssignable(indexType, objectType.key, expression.index.position, "Key");
12601
+ return context.record(expression, objectType.value);
12602
+ }
12384
12603
  return context.record(expression, objectType.kind === "array" ? objectType.element : ANY_TYPE);
12385
12604
  }
12386
12605
  case "call-expression":
@@ -12603,20 +12822,210 @@ function checkEnumDeclaration(context, statement) {
12603
12822
  context.declareModuleGlobal({ name: statement.name, type: createNamed(statement.name), isLocal: false, position: statement.position });
12604
12823
  }
12605
12824
 
12825
+ // ../compiler/src/checker/discriminant.ts
12826
+ function discriminantTest(left, right) {
12827
+ if (left.kind !== "member-expression" || left.object.kind !== "identifier" || right.kind !== "string-literal") {
12828
+ return null;
12829
+ }
12830
+ return { name: left.object.name, property: left.property, value: createStringLiteral(right.value) };
12831
+ }
12832
+ function subjectType(context, name) {
12833
+ const type = context.narrowedType(name) ?? context.binder.lookup(name)?.type ?? null;
12834
+ return type === null ? null : withoutNil(type);
12835
+ }
12836
+ function filterOptions(context, test, keep) {
12837
+ const subject = subjectType(context, test.name);
12838
+ if (subject === null || subject.kind !== "union") {
12839
+ return null;
12840
+ }
12841
+ const matched = [];
12842
+ for (const option of subject.options) {
12843
+ const member = optionMemberType(context, option, test.property);
12844
+ if (member === null) {
12845
+ return null;
12846
+ }
12847
+ if (isAssignable(test.value, member) === keep) {
12848
+ matched.push(option);
12849
+ }
12850
+ }
12851
+ return matched.length === 0 || matched.length === subject.options.length ? null : createUnion(matched);
12852
+ }
12853
+ function discriminantFact(context, left, right, keep) {
12854
+ const test = discriminantTest(left, right) ?? discriminantTest(right, left);
12855
+ if (test === null) {
12856
+ return null;
12857
+ }
12858
+ const narrowed = filterOptions(context, test, keep);
12859
+ return narrowed === null ? null : [test.name, narrowed];
12860
+ }
12861
+
12862
+ // ../compiler/src/checker/narrowing.ts
12863
+ var GUARD_FUNCTION = "type";
12864
+ var GUARD_TYPES = {
12865
+ boolean: BOOLEAN_TYPE,
12866
+ function: createFunction([], ANY_TYPE, 0, true),
12867
+ nil: NIL_TYPE,
12868
+ number: NUMBER_TYPE,
12869
+ string: STRING_TYPE,
12870
+ table: TABLE_TYPE,
12871
+ thread: THREAD_TYPE,
12872
+ userdata: USERDATA_TYPE
12873
+ };
12874
+ function declaredType(context, name) {
12875
+ return context.binder.lookup(name)?.type ?? null;
12876
+ }
12877
+ function guardedName(expression) {
12878
+ if (expression.kind !== "call-expression" || expression.callee.kind !== "identifier" || expression.callee.name !== GUARD_FUNCTION) {
12879
+ return null;
12880
+ }
12881
+ const [argument] = expression.args;
12882
+ return argument !== void 0 && argument.kind === "identifier" ? argument.name : null;
12883
+ }
12884
+ function guardFact(left, right) {
12885
+ const name = guardedName(left);
12886
+ const value = right.kind === "string-literal" ? GUARD_TYPES[right.value] : void 0;
12887
+ return name === null || value === void 0 ? null : [name, value];
12888
+ }
12889
+ function nilComparison(left, right) {
12890
+ if (left.kind === "identifier" && right.kind === "nil-literal") {
12891
+ return left.name;
12892
+ }
12893
+ return right.kind === "identifier" && left.kind === "nil-literal" ? right.name : null;
12894
+ }
12895
+ function present2(context, name, facts) {
12896
+ const declared = declaredType(context, name);
12897
+ if (declared !== null) {
12898
+ facts.set(name, withoutNil(declared));
12899
+ }
12900
+ }
12901
+ function mergeAlternatives(left, right, facts) {
12902
+ for (const [name, type] of left) {
12903
+ const other = right.get(name);
12904
+ if (other !== void 0) {
12905
+ facts.set(name, createUnion([type, other]));
12906
+ }
12907
+ }
12908
+ }
12909
+ function applyDiscriminant(context, left, right, keep, facts) {
12910
+ const fact = discriminantFact(context, left, right, keep);
12911
+ if (fact !== null) {
12912
+ facts.set(fact[0], fact[1]);
12913
+ }
12914
+ }
12915
+ function collectFacts(context, expression, facts) {
12916
+ if (expression.kind === "group-expression") {
12917
+ collectFacts(context, expression.expression, facts);
12918
+ return;
12919
+ }
12920
+ if (expression.kind === "identifier") {
12921
+ present2(context, expression.name, facts);
12922
+ return;
12923
+ }
12924
+ if (expression.kind !== "binary-expression") {
12925
+ return;
12926
+ }
12927
+ if (expression.operator === "and") {
12928
+ collectFacts(context, expression.left, facts);
12929
+ collectFacts(context, expression.right, facts);
12930
+ return;
12931
+ }
12932
+ if (expression.operator === "or") {
12933
+ mergeAlternatives(conditionFacts(context, expression.left), conditionFacts(context, expression.right), facts);
12934
+ return;
12935
+ }
12936
+ if (expression.operator === "==") {
12937
+ const guard = guardFact(expression.left, expression.right) ?? guardFact(expression.right, expression.left);
12938
+ const missing = nilComparison(expression.left, expression.right);
12939
+ if (guard !== null) {
12940
+ facts.set(guard[0], guard[1]);
12941
+ } else if (missing !== null) {
12942
+ facts.set(missing, NIL_TYPE);
12943
+ } else {
12944
+ applyDiscriminant(context, expression.left, expression.right, true, facts);
12945
+ }
12946
+ return;
12947
+ }
12948
+ if (expression.operator === "~=") {
12949
+ const name = nilComparison(expression.left, expression.right);
12950
+ if (name !== null) {
12951
+ present2(context, name, facts);
12952
+ } else {
12953
+ applyDiscriminant(context, expression.left, expression.right, false, facts);
12954
+ }
12955
+ }
12956
+ }
12957
+ function conditionFacts(context, condition) {
12958
+ const facts = /* @__PURE__ */ new Map();
12959
+ collectFacts(context, condition, facts);
12960
+ return facts;
12961
+ }
12962
+ function alwaysExits(body) {
12963
+ const last = body[body.length - 1];
12964
+ if (last === void 0) {
12965
+ return false;
12966
+ }
12967
+ if (last.kind === "return-statement" || last.kind === "break-statement") {
12968
+ return true;
12969
+ }
12970
+ if (last.kind !== "if-statement" || last.alternate === null) {
12971
+ return false;
12972
+ }
12973
+ return last.clauses.every((clause) => alwaysExits(clause.body)) && alwaysExits(last.alternate);
12974
+ }
12975
+ function guardFacts(context, statement) {
12976
+ const [clause] = statement.kind === "if-statement" ? statement.clauses : [];
12977
+ if (statement.kind !== "if-statement" || statement.alternate !== null || statement.clauses.length !== 1 || clause === void 0) {
12978
+ return /* @__PURE__ */ new Map();
12979
+ }
12980
+ return alwaysExits(clause.body) ? negatedFacts(context, clause.condition) : /* @__PURE__ */ new Map();
12981
+ }
12982
+ function negatedFacts(context, condition) {
12983
+ const facts = /* @__PURE__ */ new Map();
12984
+ if (condition.kind === "binary-expression" && condition.operator === "or") {
12985
+ for (const [name, type] of [...negatedFacts(context, condition.left), ...negatedFacts(context, condition.right)]) {
12986
+ facts.set(name, type);
12987
+ }
12988
+ return facts;
12989
+ }
12990
+ if (condition.kind === "binary-expression" && condition.operator === "==") {
12991
+ const name = nilComparison(condition.left, condition.right);
12992
+ if (name !== null) {
12993
+ present2(context, name, facts);
12994
+ } else {
12995
+ applyDiscriminant(context, condition.left, condition.right, false, facts);
12996
+ }
12997
+ }
12998
+ if (condition.kind === "binary-expression" && condition.operator === "~=") {
12999
+ applyDiscriminant(context, condition.left, condition.right, true, facts);
13000
+ }
13001
+ if (condition.kind === "unary-expression" && condition.operator === "not" && condition.operand.kind === "identifier") {
13002
+ present2(context, condition.operand.name, facts);
13003
+ }
13004
+ return facts;
13005
+ }
13006
+
12606
13007
  // ../compiler/src/checker/control-flow.ts
13008
+ var ITERATOR_FUNCTIONS = /* @__PURE__ */ new Set(["pairs", "ipairs"]);
12607
13009
  function checkBlock(context, body) {
12608
13010
  context.binder.pushScope();
12609
13011
  checkStatements(context, body);
12610
13012
  context.binder.popScope();
12611
13013
  }
13014
+ function checkNarrowedBlock(context, facts, body) {
13015
+ context.pushNarrowing(facts);
13016
+ checkBlock(context, body);
13017
+ context.popNarrowing();
13018
+ }
12612
13019
  function checkIf(context, clauses, alternate) {
12613
13020
  for (const clause of clauses) {
12614
13021
  checkExpression(context, clause.condition);
12615
- checkBlock(context, clause.body);
13022
+ checkNarrowedBlock(context, conditionFacts(context, clause.condition), clause.body);
12616
13023
  }
12617
- if (alternate !== null) {
12618
- checkBlock(context, alternate);
13024
+ if (alternate === null) {
13025
+ return;
12619
13026
  }
13027
+ const [only] = clauses;
13028
+ checkNarrowedBlock(context, clauses.length === 1 && only !== void 0 ? negatedFacts(context, only.condition) : /* @__PURE__ */ new Map(), alternate);
12620
13029
  }
12621
13030
  function checkNumericFor(context, statement) {
12622
13031
  const bounds = [statement.start, statement.limit, statement.step].filter((value) => value !== null);
@@ -12631,13 +13040,30 @@ function checkNumericFor(context, statement) {
12631
13040
  checkStatements(context, statement.body);
12632
13041
  context.binder.popScope();
12633
13042
  }
13043
+ function iteratedTypes(context, iterators) {
13044
+ const [iterator] = iterators;
13045
+ if (iterators.length !== 1 || iterator === void 0 || iterator.kind !== "call-expression" || iterator.callee.kind !== "identifier") {
13046
+ return [];
13047
+ }
13048
+ const [source] = iterator.args;
13049
+ if (!ITERATOR_FUNCTIONS.has(iterator.callee.name) || source === void 0) {
13050
+ return [];
13051
+ }
13052
+ const type = context.typeOf(source);
13053
+ if (type.kind === "map") {
13054
+ return iterator.callee.name === "ipairs" ? [NUMBER_TYPE, type.value] : [type.key, type.value];
13055
+ }
13056
+ return type.kind === "array" ? [NUMBER_TYPE, type.element] : [];
13057
+ }
12634
13058
  function checkGenericFor(context, statement) {
12635
13059
  statement.iterators.forEach((iterator) => checkExpression(context, iterator));
13060
+ const iterated = iteratedTypes(context, statement.iterators);
12636
13061
  context.binder.pushScope();
12637
- for (const variable of statement.variables) {
12638
- const declared = variable.annotation === null ? ANY_TYPE : context.resolveAnnotation(variable.annotation);
13062
+ statement.variables.forEach((variable, index) => {
13063
+ const inferred = iterated[index] ?? ANY_TYPE;
13064
+ const declared = variable.annotation === null ? inferred : context.resolveAnnotation(variable.annotation);
12639
13065
  context.binder.declare({ name: variable.name, type: declared, isLocal: true, position: variable.position });
12640
- }
13066
+ });
12641
13067
  checkStatements(context, statement.body);
12642
13068
  context.binder.popScope();
12643
13069
  }
@@ -12684,6 +13110,7 @@ function checkLocal(context, statement) {
12684
13110
  statement.declarations.forEach((declaration, index) => {
12685
13111
  const value = valueAt(statement.values, valueTypes, index);
12686
13112
  const valueType = valueTypes[index] ?? NIL_TYPE;
13113
+ context.forgetNarrowing(declaration.name);
12687
13114
  if (declaration.annotation === null) {
12688
13115
  const inferred = context.allowNil || value === void 0 ? ANY_TYPE : widenLiteral(valueType);
12689
13116
  context.binder.declare({ name: declaration.name, type: inferred, isLocal: true, position: declaration.position });
@@ -12724,8 +13151,12 @@ function checkAssignment(context, statement) {
12724
13151
  context.declareModuleGlobal({ name: target.name, type: valueType, isLocal: false, position: target.position });
12725
13152
  return;
12726
13153
  }
13154
+ const declared = target.kind === "identifier" ? context.binder.lookup(target.name)?.type ?? targetType : targetType;
12727
13155
  if (value !== void 0) {
12728
- context.expectAssignable(valueType, targetType, value.position, "Assignment");
13156
+ context.expectAssignable(valueType, declared, value.position, "Assignment");
13157
+ }
13158
+ if (target.kind === "identifier") {
13159
+ context.forgetNarrowing(target.name);
12729
13160
  }
12730
13161
  });
12731
13162
  }
@@ -12823,7 +13254,9 @@ function checkStatement(context, statement) {
12823
13254
  return checkGenericFor(context, statement);
12824
13255
  case "type-alias-statement": {
12825
13256
  const resolved2 = context.resolveAnnotation(statement.annotation);
12826
- const named2 = statement.annotation.kind === "type-object" ? renameRecord(resolved2, statement.name) : resolved2;
13257
+ const shape = statement.annotation.kind === "type-object" || statement.annotation.kind === "type-intersection";
13258
+ const named2 = shape ? renameRecord(resolved2, statement.name) : resolved2;
13259
+ context.noteTypeParameters(statement.typeParameters);
12827
13260
  context.binder.declareAlias(statement.name, named2, statement.typeParameters);
12828
13261
  return;
12829
13262
  }
@@ -12840,8 +13273,37 @@ function checkStatement(context, statement) {
12840
13273
  }
12841
13274
  }
12842
13275
  function checkStatements(context, statements) {
13276
+ let guards = 0;
12843
13277
  for (const statement of statements) {
12844
13278
  checkStatement(context, statement);
13279
+ const facts = guardFacts(context, statement);
13280
+ if (facts.size > 0) {
13281
+ context.pushNarrowing(facts);
13282
+ guards += 1;
13283
+ }
13284
+ }
13285
+ while (guards > 0) {
13286
+ context.popNarrowing();
13287
+ guards -= 1;
13288
+ }
13289
+ }
13290
+
13291
+ // ../compiler/src/checker/type-names.ts
13292
+ function isDeclaredType(context, name) {
13293
+ const declarations = context.declarations;
13294
+ return declarations.lookupClass(name) !== null || declarations.lookupInterface(name) !== null || declarations.lookupEnum(name) !== null;
13295
+ }
13296
+ function isKnownTypeName(context, name) {
13297
+ if (context.binder.lookupAlias(name) !== null || context.isTypeParameter(name)) {
13298
+ return true;
13299
+ }
13300
+ return isDeclaredType(context, name) || isMtaClass(name);
13301
+ }
13302
+ function reportUnknownTypes(context) {
13303
+ for (const [name, position2] of context.unknownTypes) {
13304
+ if (!isKnownTypeName(context, name)) {
13305
+ context.warn("check-unknown-type", `Type "${name}" is not defined.`, position2);
13306
+ }
12845
13307
  }
12846
13308
  }
12847
13309
 
@@ -12863,6 +13325,7 @@ function check(program2, mode, environment = DEFAULT_ENVIRONMENT, options = {})
12863
13325
  const structure = context.isDeclarationFile ? checkDeclarationFile(program2.body) : checkJumps(program2.body);
12864
13326
  const directives = collectDirectives(program2.body, { isDeclarationFile: context.isDeclarationFile });
12865
13327
  checkStatements(context, program2.body);
13328
+ reportUnknownTypes(context);
12866
13329
  return {
12867
13330
  diagnostics: sortDiagnostics([...structure, ...directives.diagnostics, ...context.diagnostics]),
12868
13331
  types: context.types,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thigasdevelopment/luam",
3
- "version": "0.8.2",
3
+ "version": "0.10.0",
4
4
  "description": "The Luam compiler for Multi Theft Auto resources.",
5
5
  "keywords": [
6
6
  "mta",