@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.
- package/luam.mjs +482 -19
- 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
|
|
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(
|
|
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
|
|
5016
|
-
return
|
|
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.
|
|
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
|
-
|
|
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 === "
|
|
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
|
-
|
|
13022
|
+
checkNarrowedBlock(context, conditionFacts(context, clause.condition), clause.body);
|
|
12616
13023
|
}
|
|
12617
|
-
if (alternate
|
|
12618
|
-
|
|
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
|
-
|
|
12638
|
-
const
|
|
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,
|
|
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
|
|
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,
|