@thigasdevelopment/luam 0.8.2 → 0.9.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 +284 -15
  2. package/package.json +1 -1
package/luam.mjs CHANGED
@@ -4774,6 +4774,9 @@ function createArray(element) {
4774
4774
  function createNamed(name) {
4775
4775
  return { kind: "named", name };
4776
4776
  }
4777
+ function createMap(key, value) {
4778
+ return { kind: "map", key, value };
4779
+ }
4777
4780
  function createStringLiteral(value) {
4778
4781
  return { kind: "string-literal", value };
4779
4782
  }
@@ -4850,6 +4853,9 @@ function typeToString(type) {
4850
4853
  if (type.kind === "array") {
4851
4854
  return `${typeToString(type.element)}[]`;
4852
4855
  }
4856
+ if (type.kind === "map") {
4857
+ return `table<${typeToString(type.key)}, ${typeToString(type.value)}>`;
4858
+ }
4853
4859
  if (type.kind === "optional") {
4854
4860
  return `${typeToString(type.element)}?`;
4855
4861
  }
@@ -4874,17 +4880,38 @@ function typeToString(type) {
4874
4880
  }
4875
4881
  return type.kind;
4876
4882
  }
4883
+ function withoutNil(type) {
4884
+ if (type.kind === "optional") {
4885
+ return type.element;
4886
+ }
4887
+ if (type.kind === "union") {
4888
+ return createUnion(type.options.filter((option) => option.kind !== "nil"));
4889
+ }
4890
+ return type;
4891
+ }
4892
+ function acceptsNil(type) {
4893
+ if (type.kind === "nil" || type.kind === "optional" || type.kind === "any" || type.kind === "unknown") {
4894
+ return true;
4895
+ }
4896
+ return type.kind === "union" && type.options.some((option) => option.kind === "nil");
4897
+ }
4877
4898
  function isTableLike(type) {
4878
- return type.kind === "table" || type.kind === "array";
4899
+ return type.kind === "table" || type.kind === "array" || type.kind === "map";
4879
4900
  }
4880
4901
  function isBooleanType(type) {
4881
4902
  const resolved2 = type?.kind === "optional" ? type.element : type;
4882
4903
  return resolved2?.kind === "boolean";
4883
4904
  }
4884
4905
  function isNumeric(type) {
4906
+ if (type.kind === "union") {
4907
+ return type.options.every(isNumeric);
4908
+ }
4885
4909
  return type.kind === "number" || type.kind === "any" || type.kind === "unknown" || type.kind === "named";
4886
4910
  }
4887
4911
  function isConcatenable(type) {
4912
+ if (type.kind === "union") {
4913
+ return type.options.every(isConcatenable);
4914
+ }
4888
4915
  return type.kind === "string" || type.kind === "string-literal" || isNumeric(type);
4889
4916
  }
4890
4917
  function isFunctionAssignable(source, target, options) {
@@ -4901,6 +4928,18 @@ function isFunctionAssignable(source, target, options) {
4901
4928
  }
4902
4929
  return target.returnType.kind === "void" || isAssignable(source.returnType, target.returnType, options);
4903
4930
  }
4931
+ function isMapAssignable(source, target, options) {
4932
+ if (source.kind === "table") {
4933
+ return true;
4934
+ }
4935
+ if (source.kind === "array") {
4936
+ return isAssignable(NUMBER_TYPE, target.key, options) && isAssignable(source.element, target.value, options);
4937
+ }
4938
+ if (source.kind === "record") {
4939
+ return isAssignable(STRING_TYPE, target.key, options) && [...source.members.values()].every((member) => isAssignable(member, target.value, options));
4940
+ }
4941
+ return source.kind === "map" && isAssignable(source.key, target.key, options) && isAssignable(source.value, target.value, options);
4942
+ }
4904
4943
  function isRecordAssignable(source, target, options) {
4905
4944
  for (const [name, member] of target.members) {
4906
4945
  const value = source.members.get(name);
@@ -4953,6 +4992,9 @@ function isAssignable(source, target, options = { allowNil: false }) {
4953
4992
  if (target.kind === "array") {
4954
4993
  return source.kind === "table" || source.kind === "array" && isAssignable(source.element, target.element, options);
4955
4994
  }
4995
+ if (target.kind === "map") {
4996
+ return isMapAssignable(source, target, options);
4997
+ }
4956
4998
  if (source.kind === "function" && target.kind === "function") {
4957
4999
  return isFunctionAssignable(source, target, options);
4958
5000
  }
@@ -5012,8 +5054,8 @@ function stringValue(value) {
5012
5054
  return { value, type, truthy: type, falsy: null };
5013
5055
  }
5014
5056
  function unionOf(options) {
5015
- const present2 = options.filter((option) => option !== null);
5016
- return present2.length === 0 ? NIL_TYPE : createUnion([...present2]);
5057
+ const present3 = options.filter((option) => option !== null);
5058
+ return present3.length === 0 ? NIL_TYPE : createUnion([...present3]);
5017
5059
  }
5018
5060
 
5019
5061
  // ../runtime/src/helpers.ts
@@ -5945,7 +5987,7 @@ import { tmpdir } from "node:os";
5945
5987
  import { join as join2 } from "node:path";
5946
5988
 
5947
5989
  // src/cli/version.ts
5948
- var VERSION = true ? "0.8.2" : "0.0.0-dev";
5990
+ var VERSION = true ? "0.9.0" : "0.0.0-dev";
5949
5991
  var PROGRAM_NAME = "luam";
5950
5992
  var PROGRAM_DESCRIPTION = "luam \u2014 the Luam compiler for Multi Theft Auto resources.";
5951
5993
 
@@ -10987,6 +11029,9 @@ function substituteType(type, substitutions) {
10987
11029
  if (type.kind === "array") {
10988
11030
  return createArray(substituteType(type.element, substitutions));
10989
11031
  }
11032
+ if (type.kind === "map") {
11033
+ return createMap(substituteType(type.key, substitutions), substituteType(type.value, substitutions));
11034
+ }
10990
11035
  if (type.kind === "optional") {
10991
11036
  return createOptional(substituteType(type.element, substitutions));
10992
11037
  }
@@ -11021,6 +11066,8 @@ var BUILTIN_TYPES = {
11021
11066
  userdata: USERDATA_TYPE,
11022
11067
  void: VOID_TYPE
11023
11068
  };
11069
+ var TABLE_ANNOTATION = "table";
11070
+ var MAP_ARGUMENTS = 2;
11024
11071
  var PROJECT_POSITION = { line: 0, column: 0, offset: 0 };
11025
11072
  function nilHint(source, target, mode) {
11026
11073
  if (mode !== "strict" || source.kind !== "nil" || target.kind === "nil" || target.kind === "optional") {
@@ -11032,6 +11079,8 @@ var CheckContext = class {
11032
11079
  binder = new Binder();
11033
11080
  declarations = new DeclarationRegistry();
11034
11081
  diagnostics = [];
11082
+ reported = /* @__PURE__ */ new Set();
11083
+ narrowings = [];
11035
11084
  types = /* @__PURE__ */ new Map();
11036
11085
  references = /* @__PURE__ */ new Set();
11037
11086
  declaredGlobals = /* @__PURE__ */ new Map();
@@ -11064,6 +11113,26 @@ var CheckContext = class {
11064
11113
  this.binder.declareGlobal(symbol);
11065
11114
  this.declaredGlobals.set(symbol.name, symbol.position);
11066
11115
  }
11116
+ pushNarrowing(facts) {
11117
+ this.narrowings.push(new Map(facts));
11118
+ }
11119
+ popNarrowing() {
11120
+ this.narrowings.pop();
11121
+ }
11122
+ forgetNarrowing(name) {
11123
+ for (const frame of this.narrowings) {
11124
+ frame.delete(name);
11125
+ }
11126
+ }
11127
+ narrowedType(name) {
11128
+ for (let index = this.narrowings.length - 1; index >= 0; index -= 1) {
11129
+ const found = this.narrowings[index]?.get(name);
11130
+ if (found !== void 0) {
11131
+ return found;
11132
+ }
11133
+ }
11134
+ return null;
11135
+ }
11067
11136
  isAmbientClass(name) {
11068
11137
  return this.ambientClasses.has(name);
11069
11138
  }
@@ -11079,6 +11148,11 @@ var CheckContext = class {
11079
11148
  if (this.mode === "nocheck") {
11080
11149
  return;
11081
11150
  }
11151
+ const key = `${code}|${position2.offset}|${message}`;
11152
+ if (this.reported.has(key)) {
11153
+ return;
11154
+ }
11155
+ this.reported.add(key);
11082
11156
  this.diagnostics.push(createDiagnostic("checker", code, message, position2));
11083
11157
  }
11084
11158
  record(expression, type) {
@@ -11194,9 +11268,24 @@ var CheckContext = class {
11194
11268
  this.binder.declareGlobal({ name: info.name, type: info.type, isLocal: false, position: info.position });
11195
11269
  }
11196
11270
  }
11271
+ resolveMapAnnotation(typeArguments, position2) {
11272
+ const [key, value] = typeArguments;
11273
+ if (key === void 0 || value === void 0 || typeArguments.length !== MAP_ARGUMENTS) {
11274
+ const message = `Type "${TABLE_ANNOTATION}" expects a key type and a value type but received ${typeArguments.length}.`;
11275
+ this.report("check-generic-arity", message, position2);
11276
+ for (const argument of typeArguments) {
11277
+ this.resolveAnnotation(argument);
11278
+ }
11279
+ return TABLE_TYPE;
11280
+ }
11281
+ return createMap(this.resolveAnnotation(key), this.resolveAnnotation(value));
11282
+ }
11197
11283
  resolveNamedAnnotation(name, typeArguments, position2) {
11198
11284
  const builtin = BUILTIN_TYPES[name];
11199
11285
  if (builtin !== void 0) {
11286
+ if (name === TABLE_ANNOTATION && typeArguments.length > 0) {
11287
+ return this.resolveMapAnnotation(typeArguments, position2);
11288
+ }
11200
11289
  if (typeArguments.length > 0) {
11201
11290
  this.report("check-generic-arity", `Type "${name}" does not accept type arguments.`, position2);
11202
11291
  }
@@ -12154,8 +12243,11 @@ function reportInvalidOperand(context, operator, operand, expression) {
12154
12243
  context.report("check-invalid-operand", `Operator "${operator}" cannot be applied to "${typeToString(operand)}".`, expression.position);
12155
12244
  }
12156
12245
  function checkBinary(context, operator, left, right, expression) {
12157
- if (operator === "and" || operator === "or") {
12158
- return context.record(expression, createUnion([left, right]));
12246
+ if (operator === "or") {
12247
+ return context.record(expression, createUnion([withoutNil(left), right]));
12248
+ }
12249
+ if (operator === "and") {
12250
+ return context.record(expression, acceptsNil(left) ? createUnion([right, NIL_TYPE]) : right);
12159
12251
  }
12160
12252
  if (operator === "==" || operator === "~=" || COMPARISON.has(operator)) {
12161
12253
  return context.record(expression, BOOLEAN_TYPE);
@@ -12254,6 +12346,9 @@ function checkMember(context, expression) {
12254
12346
  if (objectType.kind === "record") {
12255
12347
  return context.record(expression, resolveRecordMember(context, objectType, expression));
12256
12348
  }
12349
+ if (objectType.kind === "map" && isAssignable(STRING_TYPE, objectType.key)) {
12350
+ return context.record(expression, objectType.value);
12351
+ }
12257
12352
  const named2 = objectType.kind === "named" ? resolveNamedMember(context, objectType.name, expression) : null;
12258
12353
  if (named2 !== null) {
12259
12354
  return context.record(expression, named2);
@@ -12374,13 +12469,17 @@ function checkMultiValueExpression(context, expression) {
12374
12469
  if (symbol === null) {
12375
12470
  checkGlobalReference(context, expression.name, expression.position);
12376
12471
  }
12377
- return context.record(expression, symbol?.type ?? ANY_TYPE);
12472
+ return context.record(expression, context.narrowedType(expression.name) ?? symbol?.type ?? ANY_TYPE);
12378
12473
  }
12379
12474
  case "member-expression":
12380
12475
  return checkMember(context, expression);
12381
12476
  case "index-expression": {
12382
12477
  const objectType = checkExpression(context, expression.object);
12383
- checkExpression(context, expression.index);
12478
+ const indexType = checkExpression(context, expression.index);
12479
+ if (objectType.kind === "map") {
12480
+ context.expectAssignable(indexType, objectType.key, expression.index.position, "Key");
12481
+ return context.record(expression, objectType.value);
12482
+ }
12384
12483
  return context.record(expression, objectType.kind === "array" ? objectType.element : ANY_TYPE);
12385
12484
  }
12386
12485
  case "call-expression":
@@ -12603,20 +12702,158 @@ function checkEnumDeclaration(context, statement) {
12603
12702
  context.declareModuleGlobal({ name: statement.name, type: createNamed(statement.name), isLocal: false, position: statement.position });
12604
12703
  }
12605
12704
 
12705
+ // ../compiler/src/checker/narrowing.ts
12706
+ var GUARD_FUNCTION = "type";
12707
+ var GUARD_TYPES = {
12708
+ boolean: BOOLEAN_TYPE,
12709
+ function: createFunction([], ANY_TYPE, 0, true),
12710
+ nil: NIL_TYPE,
12711
+ number: NUMBER_TYPE,
12712
+ string: STRING_TYPE,
12713
+ table: TABLE_TYPE,
12714
+ thread: THREAD_TYPE,
12715
+ userdata: USERDATA_TYPE
12716
+ };
12717
+ function declaredType(context, name) {
12718
+ return context.binder.lookup(name)?.type ?? null;
12719
+ }
12720
+ function guardedName(expression) {
12721
+ if (expression.kind !== "call-expression" || expression.callee.kind !== "identifier" || expression.callee.name !== GUARD_FUNCTION) {
12722
+ return null;
12723
+ }
12724
+ const [argument] = expression.args;
12725
+ return argument !== void 0 && argument.kind === "identifier" ? argument.name : null;
12726
+ }
12727
+ function guardFact(left, right) {
12728
+ const name = guardedName(left);
12729
+ const value = right.kind === "string-literal" ? GUARD_TYPES[right.value] : void 0;
12730
+ return name === null || value === void 0 ? null : [name, value];
12731
+ }
12732
+ function nilComparison(left, right) {
12733
+ if (left.kind === "identifier" && right.kind === "nil-literal") {
12734
+ return left.name;
12735
+ }
12736
+ return right.kind === "identifier" && left.kind === "nil-literal" ? right.name : null;
12737
+ }
12738
+ function present2(context, name, facts) {
12739
+ const declared = declaredType(context, name);
12740
+ if (declared !== null) {
12741
+ facts.set(name, withoutNil(declared));
12742
+ }
12743
+ }
12744
+ function mergeAlternatives(left, right, facts) {
12745
+ for (const [name, type] of left) {
12746
+ const other = right.get(name);
12747
+ if (other !== void 0) {
12748
+ facts.set(name, createUnion([type, other]));
12749
+ }
12750
+ }
12751
+ }
12752
+ function collectFacts(context, expression, facts) {
12753
+ if (expression.kind === "group-expression") {
12754
+ collectFacts(context, expression.expression, facts);
12755
+ return;
12756
+ }
12757
+ if (expression.kind === "identifier") {
12758
+ present2(context, expression.name, facts);
12759
+ return;
12760
+ }
12761
+ if (expression.kind !== "binary-expression") {
12762
+ return;
12763
+ }
12764
+ if (expression.operator === "and") {
12765
+ collectFacts(context, expression.left, facts);
12766
+ collectFacts(context, expression.right, facts);
12767
+ return;
12768
+ }
12769
+ if (expression.operator === "or") {
12770
+ mergeAlternatives(conditionFacts(context, expression.left), conditionFacts(context, expression.right), facts);
12771
+ return;
12772
+ }
12773
+ if (expression.operator === "==") {
12774
+ const guard = guardFact(expression.left, expression.right) ?? guardFact(expression.right, expression.left);
12775
+ const missing = nilComparison(expression.left, expression.right);
12776
+ if (guard !== null) {
12777
+ facts.set(guard[0], guard[1]);
12778
+ } else if (missing !== null) {
12779
+ facts.set(missing, NIL_TYPE);
12780
+ }
12781
+ return;
12782
+ }
12783
+ if (expression.operator === "~=") {
12784
+ const name = nilComparison(expression.left, expression.right);
12785
+ if (name !== null) {
12786
+ present2(context, name, facts);
12787
+ }
12788
+ }
12789
+ }
12790
+ function conditionFacts(context, condition) {
12791
+ const facts = /* @__PURE__ */ new Map();
12792
+ collectFacts(context, condition, facts);
12793
+ return facts;
12794
+ }
12795
+ function alwaysExits(body) {
12796
+ const last = body[body.length - 1];
12797
+ if (last === void 0) {
12798
+ return false;
12799
+ }
12800
+ if (last.kind === "return-statement" || last.kind === "break-statement") {
12801
+ return true;
12802
+ }
12803
+ if (last.kind !== "if-statement" || last.alternate === null) {
12804
+ return false;
12805
+ }
12806
+ return last.clauses.every((clause) => alwaysExits(clause.body)) && alwaysExits(last.alternate);
12807
+ }
12808
+ function guardFacts(context, statement) {
12809
+ const [clause] = statement.kind === "if-statement" ? statement.clauses : [];
12810
+ if (statement.kind !== "if-statement" || statement.alternate !== null || statement.clauses.length !== 1 || clause === void 0) {
12811
+ return /* @__PURE__ */ new Map();
12812
+ }
12813
+ return alwaysExits(clause.body) ? negatedFacts(context, clause.condition) : /* @__PURE__ */ new Map();
12814
+ }
12815
+ function negatedFacts(context, condition) {
12816
+ const facts = /* @__PURE__ */ new Map();
12817
+ if (condition.kind === "binary-expression" && condition.operator === "or") {
12818
+ for (const [name, type] of [...negatedFacts(context, condition.left), ...negatedFacts(context, condition.right)]) {
12819
+ facts.set(name, type);
12820
+ }
12821
+ return facts;
12822
+ }
12823
+ if (condition.kind === "binary-expression" && condition.operator === "==") {
12824
+ const name = nilComparison(condition.left, condition.right);
12825
+ if (name !== null) {
12826
+ present2(context, name, facts);
12827
+ }
12828
+ }
12829
+ if (condition.kind === "unary-expression" && condition.operator === "not" && condition.operand.kind === "identifier") {
12830
+ present2(context, condition.operand.name, facts);
12831
+ }
12832
+ return facts;
12833
+ }
12834
+
12606
12835
  // ../compiler/src/checker/control-flow.ts
12836
+ var ITERATOR_FUNCTIONS = /* @__PURE__ */ new Set(["pairs", "ipairs"]);
12607
12837
  function checkBlock(context, body) {
12608
12838
  context.binder.pushScope();
12609
12839
  checkStatements(context, body);
12610
12840
  context.binder.popScope();
12611
12841
  }
12842
+ function checkNarrowedBlock(context, facts, body) {
12843
+ context.pushNarrowing(facts);
12844
+ checkBlock(context, body);
12845
+ context.popNarrowing();
12846
+ }
12612
12847
  function checkIf(context, clauses, alternate) {
12613
12848
  for (const clause of clauses) {
12614
12849
  checkExpression(context, clause.condition);
12615
- checkBlock(context, clause.body);
12850
+ checkNarrowedBlock(context, conditionFacts(context, clause.condition), clause.body);
12616
12851
  }
12617
- if (alternate !== null) {
12618
- checkBlock(context, alternate);
12852
+ if (alternate === null) {
12853
+ return;
12619
12854
  }
12855
+ const [only] = clauses;
12856
+ checkNarrowedBlock(context, clauses.length === 1 && only !== void 0 ? negatedFacts(context, only.condition) : /* @__PURE__ */ new Map(), alternate);
12620
12857
  }
12621
12858
  function checkNumericFor(context, statement) {
12622
12859
  const bounds = [statement.start, statement.limit, statement.step].filter((value) => value !== null);
@@ -12631,13 +12868,30 @@ function checkNumericFor(context, statement) {
12631
12868
  checkStatements(context, statement.body);
12632
12869
  context.binder.popScope();
12633
12870
  }
12871
+ function iteratedTypes(context, iterators) {
12872
+ const [iterator] = iterators;
12873
+ if (iterators.length !== 1 || iterator === void 0 || iterator.kind !== "call-expression" || iterator.callee.kind !== "identifier") {
12874
+ return [];
12875
+ }
12876
+ const [source] = iterator.args;
12877
+ if (!ITERATOR_FUNCTIONS.has(iterator.callee.name) || source === void 0) {
12878
+ return [];
12879
+ }
12880
+ const type = context.typeOf(source);
12881
+ if (type.kind === "map") {
12882
+ return iterator.callee.name === "ipairs" ? [NUMBER_TYPE, type.value] : [type.key, type.value];
12883
+ }
12884
+ return type.kind === "array" ? [NUMBER_TYPE, type.element] : [];
12885
+ }
12634
12886
  function checkGenericFor(context, statement) {
12635
12887
  statement.iterators.forEach((iterator) => checkExpression(context, iterator));
12888
+ const iterated = iteratedTypes(context, statement.iterators);
12636
12889
  context.binder.pushScope();
12637
- for (const variable of statement.variables) {
12638
- const declared = variable.annotation === null ? ANY_TYPE : context.resolveAnnotation(variable.annotation);
12890
+ statement.variables.forEach((variable, index) => {
12891
+ const inferred = iterated[index] ?? ANY_TYPE;
12892
+ const declared = variable.annotation === null ? inferred : context.resolveAnnotation(variable.annotation);
12639
12893
  context.binder.declare({ name: variable.name, type: declared, isLocal: true, position: variable.position });
12640
- }
12894
+ });
12641
12895
  checkStatements(context, statement.body);
12642
12896
  context.binder.popScope();
12643
12897
  }
@@ -12684,6 +12938,7 @@ function checkLocal(context, statement) {
12684
12938
  statement.declarations.forEach((declaration, index) => {
12685
12939
  const value = valueAt(statement.values, valueTypes, index);
12686
12940
  const valueType = valueTypes[index] ?? NIL_TYPE;
12941
+ context.forgetNarrowing(declaration.name);
12687
12942
  if (declaration.annotation === null) {
12688
12943
  const inferred = context.allowNil || value === void 0 ? ANY_TYPE : widenLiteral(valueType);
12689
12944
  context.binder.declare({ name: declaration.name, type: inferred, isLocal: true, position: declaration.position });
@@ -12724,8 +12979,12 @@ function checkAssignment(context, statement) {
12724
12979
  context.declareModuleGlobal({ name: target.name, type: valueType, isLocal: false, position: target.position });
12725
12980
  return;
12726
12981
  }
12982
+ const declared = target.kind === "identifier" ? context.binder.lookup(target.name)?.type ?? targetType : targetType;
12727
12983
  if (value !== void 0) {
12728
- context.expectAssignable(valueType, targetType, value.position, "Assignment");
12984
+ context.expectAssignable(valueType, declared, value.position, "Assignment");
12985
+ }
12986
+ if (target.kind === "identifier") {
12987
+ context.forgetNarrowing(target.name);
12729
12988
  }
12730
12989
  });
12731
12990
  }
@@ -12840,8 +13099,18 @@ function checkStatement(context, statement) {
12840
13099
  }
12841
13100
  }
12842
13101
  function checkStatements(context, statements) {
13102
+ let guards = 0;
12843
13103
  for (const statement of statements) {
12844
13104
  checkStatement(context, statement);
13105
+ const facts = guardFacts(context, statement);
13106
+ if (facts.size > 0) {
13107
+ context.pushNarrowing(facts);
13108
+ guards += 1;
13109
+ }
13110
+ }
13111
+ while (guards > 0) {
13112
+ context.popNarrowing();
13113
+ guards -= 1;
12845
13114
  }
12846
13115
  }
12847
13116
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thigasdevelopment/luam",
3
- "version": "0.8.2",
3
+ "version": "0.9.0",
4
4
  "description": "The Luam compiler for Multi Theft Auto resources.",
5
5
  "keywords": [
6
6
  "mta",