@thigasdevelopment/luam 0.9.0 → 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 +199 -5
  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
  }
@@ -5987,7 +5999,7 @@ import { tmpdir } from "node:os";
5987
5999
  import { join as join2 } from "node:path";
5988
6000
 
5989
6001
  // src/cli/version.ts
5990
- var VERSION = true ? "0.9.0" : "0.0.0-dev";
6002
+ var VERSION = true ? "0.10.0" : "0.0.0-dev";
5991
6003
  var PROGRAM_NAME = "luam";
5992
6004
  var PROGRAM_DESCRIPTION = "luam \u2014 the Luam compiler for Multi Theft Auto resources.";
5993
6005
 
@@ -11021,6 +11033,41 @@ function mtaMember(className, member) {
11021
11033
  return mtaClassRegistry().lookupMember(className, member);
11022
11034
  }
11023
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
+
11024
11071
  // ../compiler/src/checker/type-substitution.ts
11025
11072
  function substituteType(type, substitutions) {
11026
11073
  if (type.kind === "named") {
@@ -11085,6 +11132,8 @@ var CheckContext = class {
11085
11132
  references = /* @__PURE__ */ new Set();
11086
11133
  declaredGlobals = /* @__PURE__ */ new Map();
11087
11134
  externalReferences = /* @__PURE__ */ new Map();
11135
+ unknownTypes = /* @__PURE__ */ new Map();
11136
+ typeParameters = /* @__PURE__ */ new Set();
11088
11137
  generatedMembers = /* @__PURE__ */ new Map();
11089
11138
  mode;
11090
11139
  environment;
@@ -11145,6 +11194,12 @@ var CheckContext = class {
11145
11194
  }
11146
11195
  }
11147
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) {
11148
11203
  if (this.mode === "nocheck") {
11149
11204
  return;
11150
11205
  }
@@ -11153,7 +11208,20 @@ var CheckContext = class {
11153
11208
  return;
11154
11209
  }
11155
11210
  this.reported.add(key);
11156
- this.diagnostics.push(createDiagnostic("checker", code, message, position2));
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
+ }
11157
11225
  }
11158
11226
  record(expression, type) {
11159
11227
  this.types.set(expression, type);
@@ -11212,6 +11280,10 @@ var CheckContext = class {
11212
11280
  if (annotation.kind === "type-union") {
11213
11281
  return createUnion(annotation.options.map((option) => this.resolveAnnotation(option)));
11214
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
+ }
11215
11287
  if (annotation.kind === "type-string-literal") {
11216
11288
  return createStringLiteral(annotation.value);
11217
11289
  }
@@ -11293,6 +11365,7 @@ var CheckContext = class {
11293
11365
  }
11294
11366
  const alias = this.binder.lookupAlias(name);
11295
11367
  if (alias === null) {
11368
+ this.noteUnknownType(name, position2);
11296
11369
  return createNamed(name);
11297
11370
  }
11298
11371
  if (typeArguments.length !== alias.parameters.length) {
@@ -12304,6 +12377,47 @@ function templateLiteralText(segments) {
12304
12377
  return segments.map((segment) => segment.kind === "text" ? segment.value : `\${${segment.value}}`).join("");
12305
12378
  }
12306
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
+
12307
12421
  // ../compiler/src/checker/expressions.ts
12308
12422
  var EXTENSION_RESULTS = {
12309
12423
  boolean: BOOLEAN_TYPE,
@@ -12346,6 +12460,12 @@ function checkMember(context, expression) {
12346
12460
  if (objectType.kind === "record") {
12347
12461
  return context.record(expression, resolveRecordMember(context, objectType, expression));
12348
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
+ }
12349
12469
  if (objectType.kind === "map" && isAssignable(STRING_TYPE, objectType.key)) {
12350
12470
  return context.record(expression, objectType.value);
12351
12471
  }
@@ -12702,6 +12822,43 @@ function checkEnumDeclaration(context, statement) {
12702
12822
  context.declareModuleGlobal({ name: statement.name, type: createNamed(statement.name), isLocal: false, position: statement.position });
12703
12823
  }
12704
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
+
12705
12862
  // ../compiler/src/checker/narrowing.ts
12706
12863
  var GUARD_FUNCTION = "type";
12707
12864
  var GUARD_TYPES = {
@@ -12749,6 +12906,12 @@ function mergeAlternatives(left, right, facts) {
12749
12906
  }
12750
12907
  }
12751
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
+ }
12752
12915
  function collectFacts(context, expression, facts) {
12753
12916
  if (expression.kind === "group-expression") {
12754
12917
  collectFacts(context, expression.expression, facts);
@@ -12777,6 +12940,8 @@ function collectFacts(context, expression, facts) {
12777
12940
  facts.set(guard[0], guard[1]);
12778
12941
  } else if (missing !== null) {
12779
12942
  facts.set(missing, NIL_TYPE);
12943
+ } else {
12944
+ applyDiscriminant(context, expression.left, expression.right, true, facts);
12780
12945
  }
12781
12946
  return;
12782
12947
  }
@@ -12784,6 +12949,8 @@ function collectFacts(context, expression, facts) {
12784
12949
  const name = nilComparison(expression.left, expression.right);
12785
12950
  if (name !== null) {
12786
12951
  present2(context, name, facts);
12952
+ } else {
12953
+ applyDiscriminant(context, expression.left, expression.right, false, facts);
12787
12954
  }
12788
12955
  }
12789
12956
  }
@@ -12824,8 +12991,13 @@ function negatedFacts(context, condition) {
12824
12991
  const name = nilComparison(condition.left, condition.right);
12825
12992
  if (name !== null) {
12826
12993
  present2(context, name, facts);
12994
+ } else {
12995
+ applyDiscriminant(context, condition.left, condition.right, false, facts);
12827
12996
  }
12828
12997
  }
12998
+ if (condition.kind === "binary-expression" && condition.operator === "~=") {
12999
+ applyDiscriminant(context, condition.left, condition.right, true, facts);
13000
+ }
12829
13001
  if (condition.kind === "unary-expression" && condition.operator === "not" && condition.operand.kind === "identifier") {
12830
13002
  present2(context, condition.operand.name, facts);
12831
13003
  }
@@ -13082,7 +13254,9 @@ function checkStatement(context, statement) {
13082
13254
  return checkGenericFor(context, statement);
13083
13255
  case "type-alias-statement": {
13084
13256
  const resolved2 = context.resolveAnnotation(statement.annotation);
13085
- 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);
13086
13260
  context.binder.declareAlias(statement.name, named2, statement.typeParameters);
13087
13261
  return;
13088
13262
  }
@@ -13114,6 +13288,25 @@ function checkStatements(context, statements) {
13114
13288
  }
13115
13289
  }
13116
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
+ }
13307
+ }
13308
+ }
13309
+
13117
13310
  // ../compiler/src/checker/checker.ts
13118
13311
  function externalReferences(context) {
13119
13312
  const external = /* @__PURE__ */ new Map();
@@ -13132,6 +13325,7 @@ function check(program2, mode, environment = DEFAULT_ENVIRONMENT, options = {})
13132
13325
  const structure = context.isDeclarationFile ? checkDeclarationFile(program2.body) : checkJumps(program2.body);
13133
13326
  const directives = collectDirectives(program2.body, { isDeclarationFile: context.isDeclarationFile });
13134
13327
  checkStatements(context, program2.body);
13328
+ reportUnknownTypes(context);
13135
13329
  return {
13136
13330
  diagnostics: sortDiagnostics([...structure, ...directives.diagnostics, ...context.diagnostics]),
13137
13331
  types: context.types,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thigasdevelopment/luam",
3
- "version": "0.9.0",
3
+ "version": "0.10.0",
4
4
  "description": "The Luam compiler for Multi Theft Auto resources.",
5
5
  "keywords": [
6
6
  "mta",