@thigasdevelopment/luam 0.19.4 → 0.19.6

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 (3) hide show
  1. package/README.md +11 -5
  2. package/luam.mjs +366 -202
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -309,11 +309,17 @@ pnpm test
309
309
  pnpm build
310
310
  ```
311
311
 
312
- Branch from `develop`, add a fixture and a snapshot for new language behaviour,
313
- and run `pnpm typecheck && pnpm test` before opening a pull request. House style:
314
- TypeScript only, strict, no `any`, no comments inside code, 4-space indentation,
315
- single quotes, kebab-case file names, path aliases instead of `../` imports, no
316
- barrel files, everything in English.
312
+ Fork, open the pull request against `develop`, add a fixture and a snapshot for
313
+ new language behaviour, and run `pnpm typecheck && pnpm test` before sending it.
314
+ House style: TypeScript only, strict, no `any`, no comments inside code, 4-space
315
+ indentation, single quotes, kebab-case file names, path aliases instead of `../`
316
+ imports, no barrel files, everything in English.
317
+
318
+ [CONTRIBUTING.md](CONTRIBUTING.md) is the full contract — the Node and Lua 5.1
319
+ requirements, the command that reproduces each required check, and what happens
320
+ to a pull request from a fork. Conduct is in
321
+ [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md). A vulnerability is reported privately
322
+ through [SECURITY.md](SECURITY.md), never as an issue.
317
323
 
318
324
  The manual lives in [`docs/`](docs/) — `pnpm docs:dev` to preview,
319
325
  `pnpm docs:verify` before pushing. English is the source locale and pt-BR is
package/luam.mjs CHANGED
@@ -4008,6 +4008,188 @@ function parseNamedAnnotation(stream, declaration) {
4008
4008
  return { kind: "type-optional", element, position: optional.position };
4009
4009
  }
4010
4010
 
4011
+ // ../compiler/src/parser/token-stream.ts
4012
+ var ParserError = class extends Error {
4013
+ diagnostic;
4014
+ constructor(diagnostic) {
4015
+ super(diagnostic.message);
4016
+ this.name = "ParserError";
4017
+ this.diagnostic = diagnostic;
4018
+ }
4019
+ };
4020
+ var TokenStream = class {
4021
+ diagnostics = [];
4022
+ tokens;
4023
+ index = 0;
4024
+ erased = [];
4025
+ spans = /* @__PURE__ */ new Map();
4026
+ constructor(tokens) {
4027
+ this.tokens = tokens;
4028
+ }
4029
+ peek(offset = 0) {
4030
+ const token = this.tokens[Math.min(this.index + offset, this.tokens.length - 1)];
4031
+ return token;
4032
+ }
4033
+ current() {
4034
+ return this.peek();
4035
+ }
4036
+ isEof() {
4037
+ return this.current().kind === "eof";
4038
+ }
4039
+ next() {
4040
+ const token = this.current();
4041
+ if (!this.isEof()) {
4042
+ this.index += 1;
4043
+ }
4044
+ return token;
4045
+ }
4046
+ checkpoint() {
4047
+ return this.index;
4048
+ }
4049
+ speculate() {
4050
+ return { index: this.index, erasures: this.erased.length, diagnostics: this.diagnostics.length };
4051
+ }
4052
+ rewind(point) {
4053
+ this.index = point.index;
4054
+ this.erased.length = point.erasures;
4055
+ this.diagnostics.length = point.diagnostics;
4056
+ }
4057
+ eraseFrom(checkpoint, kind = "annotation") {
4058
+ const first = this.tokens[checkpoint];
4059
+ const last = this.tokens[this.index - 1];
4060
+ if (first !== void 0 && last !== void 0 && first.kind !== "eof") {
4061
+ this.erased.push({ start: first.position.offset, end: last.end.offset, kind });
4062
+ }
4063
+ }
4064
+ eraseToCurrent(checkpoint) {
4065
+ const first = this.tokens[checkpoint];
4066
+ if (first !== void 0 && first.kind !== "eof") {
4067
+ this.erased.push({ start: first.position.offset, end: this.current().position.offset, kind: "annotation" });
4068
+ }
4069
+ }
4070
+ extendDeclarationErasure(end) {
4071
+ const last = this.erased[this.erased.length - 1];
4072
+ if (last !== void 0 && last.kind === "declaration" && last.end <= end) {
4073
+ last.end = end;
4074
+ }
4075
+ }
4076
+ erasures() {
4077
+ return [...this.erased];
4078
+ }
4079
+ recordSpan(node, checkpoint) {
4080
+ const span = this.sourceSpanFrom(checkpoint);
4081
+ if (span !== null) {
4082
+ this.spans.set(node, span);
4083
+ }
4084
+ }
4085
+ nodeSpans() {
4086
+ return this.spans;
4087
+ }
4088
+ sourceSpanFrom(checkpoint) {
4089
+ const first = this.tokens[checkpoint];
4090
+ const last = this.tokens[this.index - 1];
4091
+ return first === void 0 || last === void 0 || first.kind === "eof" ? null : { start: first.position.offset, end: last.end.offset };
4092
+ }
4093
+ check(kind, value) {
4094
+ const token = this.current();
4095
+ return token.kind === kind && (value === void 0 || token.value === value);
4096
+ }
4097
+ checkAhead(offset, kind, value) {
4098
+ const token = this.peek(offset);
4099
+ return token.kind === kind && (value === void 0 || token.value === value);
4100
+ }
4101
+ match(kind, value) {
4102
+ if (!this.check(kind, value)) {
4103
+ return false;
4104
+ }
4105
+ this.next();
4106
+ return true;
4107
+ }
4108
+ expect(kind, value) {
4109
+ if (this.check(kind, value)) {
4110
+ return this.next();
4111
+ }
4112
+ const expected = value === void 0 ? kind : `"${value}"`;
4113
+ throw this.error(`Expected ${expected} but found "${this.describeCurrent()}".`, "parse-unexpected-token");
4114
+ }
4115
+ checkName(offset = 0) {
4116
+ const token = this.peek(offset);
4117
+ return token.kind === "identifier" || token.kind === "keyword" && isLuamKeyword(token.value);
4118
+ }
4119
+ expectName() {
4120
+ if (this.checkName()) {
4121
+ return this.next();
4122
+ }
4123
+ throw this.error(`Expected a name but found "${this.describeCurrent()}".`, "parse-unexpected-token");
4124
+ }
4125
+ describeCurrent() {
4126
+ const token = this.current();
4127
+ return token.kind === "eof" ? "<end of file>" : token.value;
4128
+ }
4129
+ error(message, code = "parse-error") {
4130
+ const token = this.current();
4131
+ return new ParserError(createDiagnostic("parser", code, message, token.position, "error", token.end));
4132
+ }
4133
+ errorAt(message, code, position2, end) {
4134
+ return new ParserError(createDiagnostic("parser", code, message, position2, "error", end));
4135
+ }
4136
+ report(code, message, position2) {
4137
+ this.diagnostics.push(createDiagnostic("parser", code, message, position2));
4138
+ }
4139
+ };
4140
+
4141
+ // ../compiler/src/parser/type-parameters.ts
4142
+ function emptyTypeParameters() {
4143
+ return { names: [], constraints: [] };
4144
+ }
4145
+ function parseTypeParameters(stream) {
4146
+ const list = emptyTypeParameters();
4147
+ if (!stream.check("operator", "<")) {
4148
+ return list;
4149
+ }
4150
+ const checkpoint = stream.checkpoint();
4151
+ stream.next();
4152
+ do {
4153
+ list.names.push(stream.expect("identifier").value);
4154
+ list.constraints.push(stream.match("keyword", "extends") ? parseTypeAnnotation(stream) : null);
4155
+ } while (stream.match("punctuation", ","));
4156
+ stream.expect("operator", ">");
4157
+ stream.eraseFrom(checkpoint);
4158
+ return list;
4159
+ }
4160
+ function parseTypeArguments(stream) {
4161
+ const args = [];
4162
+ if (!stream.check("operator", "<")) {
4163
+ return args;
4164
+ }
4165
+ const checkpoint = stream.checkpoint();
4166
+ stream.next();
4167
+ do {
4168
+ args.push(parseTypeAnnotation(stream));
4169
+ } while (stream.match("punctuation", ","));
4170
+ stream.expect("operator", ">");
4171
+ stream.eraseFrom(checkpoint);
4172
+ return args;
4173
+ }
4174
+ function parseCallTypeArguments(stream) {
4175
+ if (!stream.check("operator", "<")) {
4176
+ return [];
4177
+ }
4178
+ const point = stream.speculate();
4179
+ try {
4180
+ const args = parseTypeArguments(stream);
4181
+ if (args.length > 0 && stream.check("punctuation", "(")) {
4182
+ return args;
4183
+ }
4184
+ } catch (error) {
4185
+ if (!(error instanceof ParserError)) {
4186
+ throw error;
4187
+ }
4188
+ }
4189
+ stream.rewind(point);
4190
+ return [];
4191
+ }
4192
+
4011
4193
  // ../compiler/src/parser/function-expression.ts
4012
4194
  function parseParameter(stream) {
4013
4195
  const token = stream.current();
@@ -4038,11 +4220,20 @@ function parseParameters(stream) {
4038
4220
  }
4039
4221
  function parseFunctionExpression(stream) {
4040
4222
  const position2 = stream.expect("keyword", "function").position;
4223
+ const typeParameters = parseTypeParameters(stream);
4041
4224
  const parameters = parseParameters(stream);
4042
4225
  const returnAnnotation = parseOptionalAnnotation(stream);
4043
4226
  const body = parseBlock(stream, ["end"]);
4044
4227
  stream.expect("keyword", "end");
4045
- return { kind: "function-expression", parameters, returnAnnotation, body, position: position2 };
4228
+ return {
4229
+ kind: "function-expression",
4230
+ typeParameters: typeParameters.names,
4231
+ typeConstraints: typeParameters.constraints,
4232
+ parameters,
4233
+ returnAnnotation,
4234
+ body,
4235
+ position: position2
4236
+ };
4046
4237
  }
4047
4238
  function parseFunctionName(stream) {
4048
4239
  const token = stream.expect("identifier");
@@ -4060,11 +4251,25 @@ function parseFunctionName(stream) {
4060
4251
  function parseFunctionDeclaration(stream, isLocal, isExported = false, isHttpExport = false) {
4061
4252
  const position2 = stream.expect("keyword", "function").position;
4062
4253
  const { name, isMethod } = parseFunctionName(stream);
4254
+ const typeParameters = parseTypeParameters(stream);
4063
4255
  const parameters = parseParameters(stream);
4064
4256
  const returnAnnotation = parseOptionalAnnotation(stream);
4065
4257
  const body = parseBlock(stream, ["end"]);
4066
4258
  stream.expect("keyword", "end");
4067
- return { kind: "function-declaration", name, isLocal, isExported, isHttpExport, isMethod, parameters, returnAnnotation, body, position: position2 };
4259
+ return {
4260
+ kind: "function-declaration",
4261
+ name,
4262
+ isLocal,
4263
+ isExported,
4264
+ isHttpExport,
4265
+ isMethod,
4266
+ typeParameters: typeParameters.names,
4267
+ typeConstraints: typeParameters.constraints,
4268
+ parameters,
4269
+ returnAnnotation,
4270
+ body,
4271
+ position: position2
4272
+ };
4068
4273
  }
4069
4274
 
4070
4275
  // ../compiler/src/parser/precedence.ts
@@ -4159,7 +4364,7 @@ function parsePrimary(stream) {
4159
4364
  if (token.kind === "keyword" && token.value === "new" && stream.checkAhead(1, "identifier")) {
4160
4365
  stream.next();
4161
4366
  const className = stream.next().value;
4162
- const typeArguments = parseNewTypeArguments(stream);
4367
+ const typeArguments = parseTypeArguments(stream);
4163
4368
  return { kind: "new-expression", className, typeArguments, args: parseArguments(stream), position: token.position };
4164
4369
  }
4165
4370
  if (token.kind === "keyword" && CALLABLE_KEYWORDS.has(token.value) && stream.checkAhead(1, "punctuation", "(")) {
@@ -4192,20 +4397,6 @@ function parsePrimary(stream) {
4192
4397
  }
4193
4398
  throw stream.error(`Unexpected "${stream.describeCurrent()}" in expression.`, "parse-unexpected-token");
4194
4399
  }
4195
- function parseNewTypeArguments(stream) {
4196
- const args = [];
4197
- if (!stream.check("operator", "<")) {
4198
- return args;
4199
- }
4200
- const checkpoint = stream.checkpoint();
4201
- stream.next();
4202
- do {
4203
- args.push(parseTypeAnnotation(stream));
4204
- } while (stream.match("punctuation", ","));
4205
- stream.expect("operator", ">");
4206
- stream.eraseFrom(checkpoint);
4207
- return args;
4208
- }
4209
4400
  function parseArguments(stream) {
4210
4401
  if (stream.check("string") || stream.check("template") || stream.check("punctuation", "{")) {
4211
4402
  return [parsePrimary(stream)];
@@ -4244,11 +4435,13 @@ function parseSuffixed(stream) {
4244
4435
  if (token.kind === "punctuation" && token.value === ":" && stream.checkName(1)) {
4245
4436
  stream.next();
4246
4437
  const method = stream.next().value;
4247
- expression = { kind: "call-expression", callee: expression, method, args: parseArguments(stream), position: token.position };
4438
+ const typeArguments2 = parseCallTypeArguments(stream);
4439
+ expression = { kind: "call-expression", callee: expression, method, typeArguments: typeArguments2, args: parseArguments(stream), position: token.position };
4248
4440
  continue;
4249
4441
  }
4250
- if (isCallStart(stream)) {
4251
- expression = { kind: "call-expression", callee: expression, method: null, args: parseArguments(stream), position: token.position };
4442
+ const typeArguments = parseCallTypeArguments(stream);
4443
+ if (typeArguments.length > 0 || isCallStart(stream)) {
4444
+ expression = { kind: "call-expression", callee: expression, method: null, typeArguments, args: parseArguments(stream), position: token.position };
4252
4445
  continue;
4253
4446
  }
4254
4447
  return expression;
@@ -4367,128 +4560,6 @@ function recoverInBlock(stream, error, terminators = BRACE_TERMINATORS) {
4367
4560
  }
4368
4561
  }
4369
4562
 
4370
- // ../compiler/src/parser/token-stream.ts
4371
- var ParserError = class extends Error {
4372
- diagnostic;
4373
- constructor(diagnostic) {
4374
- super(diagnostic.message);
4375
- this.name = "ParserError";
4376
- this.diagnostic = diagnostic;
4377
- }
4378
- };
4379
- var TokenStream = class {
4380
- diagnostics = [];
4381
- tokens;
4382
- index = 0;
4383
- erased = [];
4384
- spans = /* @__PURE__ */ new Map();
4385
- constructor(tokens) {
4386
- this.tokens = tokens;
4387
- }
4388
- peek(offset = 0) {
4389
- const token = this.tokens[Math.min(this.index + offset, this.tokens.length - 1)];
4390
- return token;
4391
- }
4392
- current() {
4393
- return this.peek();
4394
- }
4395
- isEof() {
4396
- return this.current().kind === "eof";
4397
- }
4398
- next() {
4399
- const token = this.current();
4400
- if (!this.isEof()) {
4401
- this.index += 1;
4402
- }
4403
- return token;
4404
- }
4405
- checkpoint() {
4406
- return this.index;
4407
- }
4408
- eraseFrom(checkpoint, kind = "annotation") {
4409
- const first = this.tokens[checkpoint];
4410
- const last = this.tokens[this.index - 1];
4411
- if (first !== void 0 && last !== void 0 && first.kind !== "eof") {
4412
- this.erased.push({ start: first.position.offset, end: last.end.offset, kind });
4413
- }
4414
- }
4415
- eraseToCurrent(checkpoint) {
4416
- const first = this.tokens[checkpoint];
4417
- if (first !== void 0 && first.kind !== "eof") {
4418
- this.erased.push({ start: first.position.offset, end: this.current().position.offset, kind: "annotation" });
4419
- }
4420
- }
4421
- extendDeclarationErasure(end) {
4422
- const last = this.erased[this.erased.length - 1];
4423
- if (last !== void 0 && last.kind === "declaration" && last.end <= end) {
4424
- last.end = end;
4425
- }
4426
- }
4427
- erasures() {
4428
- return [...this.erased];
4429
- }
4430
- recordSpan(node, checkpoint) {
4431
- const span = this.sourceSpanFrom(checkpoint);
4432
- if (span !== null) {
4433
- this.spans.set(node, span);
4434
- }
4435
- }
4436
- nodeSpans() {
4437
- return this.spans;
4438
- }
4439
- sourceSpanFrom(checkpoint) {
4440
- const first = this.tokens[checkpoint];
4441
- const last = this.tokens[this.index - 1];
4442
- return first === void 0 || last === void 0 || first.kind === "eof" ? null : { start: first.position.offset, end: last.end.offset };
4443
- }
4444
- check(kind, value) {
4445
- const token = this.current();
4446
- return token.kind === kind && (value === void 0 || token.value === value);
4447
- }
4448
- checkAhead(offset, kind, value) {
4449
- const token = this.peek(offset);
4450
- return token.kind === kind && (value === void 0 || token.value === value);
4451
- }
4452
- match(kind, value) {
4453
- if (!this.check(kind, value)) {
4454
- return false;
4455
- }
4456
- this.next();
4457
- return true;
4458
- }
4459
- expect(kind, value) {
4460
- if (this.check(kind, value)) {
4461
- return this.next();
4462
- }
4463
- const expected = value === void 0 ? kind : `"${value}"`;
4464
- throw this.error(`Expected ${expected} but found "${this.describeCurrent()}".`, "parse-unexpected-token");
4465
- }
4466
- checkName(offset = 0) {
4467
- const token = this.peek(offset);
4468
- return token.kind === "identifier" || token.kind === "keyword" && isLuamKeyword(token.value);
4469
- }
4470
- expectName() {
4471
- if (this.checkName()) {
4472
- return this.next();
4473
- }
4474
- throw this.error(`Expected a name but found "${this.describeCurrent()}".`, "parse-unexpected-token");
4475
- }
4476
- describeCurrent() {
4477
- const token = this.current();
4478
- return token.kind === "eof" ? "<end of file>" : token.value;
4479
- }
4480
- error(message, code = "parse-error") {
4481
- const token = this.current();
4482
- return new ParserError(createDiagnostic("parser", code, message, token.position, "error", token.end));
4483
- }
4484
- errorAt(message, code, position2, end) {
4485
- return new ParserError(createDiagnostic("parser", code, message, position2, "error", end));
4486
- }
4487
- report(code, message, position2) {
4488
- this.diagnostics.push(createDiagnostic("parser", code, message, position2));
4489
- }
4490
- };
4491
-
4492
4563
  // ../compiler/src/parser/declarations.ts
4493
4564
  var DECLARATION_NAMES = /* @__PURE__ */ new Set(["class", "interface", "enum"]);
4494
4565
  var CLASS_MODIFIERS = /* @__PURE__ */ new Set(["extends", "implements"]);
@@ -4573,6 +4644,8 @@ function parseClassMethod(stream, token, decorators, isStatic) {
4573
4644
  isConstructor: token.value === "constructor",
4574
4645
  isSynthetic: false,
4575
4646
  isStatic,
4647
+ typeParameters: expression.typeParameters,
4648
+ typeConstraints: expression.typeConstraints,
4576
4649
  parameters: expression.parameters,
4577
4650
  returnAnnotation: expression.returnAnnotation,
4578
4651
  body: expression.body,
@@ -4590,6 +4663,8 @@ function parseBraceClassMethod(stream, token, decorators, isStatic) {
4590
4663
  isConstructor: token.value === "constructor",
4591
4664
  isSynthetic: false,
4592
4665
  isStatic,
4666
+ typeParameters: [],
4667
+ typeConstraints: [],
4593
4668
  parameters,
4594
4669
  returnAnnotation,
4595
4670
  body,
@@ -4638,35 +4713,6 @@ function parseClassMember(stream) {
4638
4713
  expectClassFieldBoundary(stream, token);
4639
4714
  return { kind: "class-field", name: token.value, annotation, value, decorators, isStatic, position: token.position };
4640
4715
  }
4641
- function parseTypeParameters(stream) {
4642
- const list = { names: [], constraints: [] };
4643
- if (!stream.check("operator", "<")) {
4644
- return list;
4645
- }
4646
- const checkpoint = stream.checkpoint();
4647
- stream.next();
4648
- do {
4649
- list.names.push(stream.expect("identifier").value);
4650
- list.constraints.push(stream.match("keyword", "extends") ? parseTypeAnnotation(stream) : null);
4651
- } while (stream.match("punctuation", ","));
4652
- stream.expect("operator", ">");
4653
- stream.eraseFrom(checkpoint);
4654
- return list;
4655
- }
4656
- function parseTypeArguments(stream) {
4657
- const args = [];
4658
- if (!stream.check("operator", "<")) {
4659
- return args;
4660
- }
4661
- const checkpoint = stream.checkpoint();
4662
- stream.next();
4663
- do {
4664
- args.push(parseTypeAnnotation(stream));
4665
- } while (stream.match("punctuation", ","));
4666
- stream.expect("operator", ">");
4667
- stream.eraseFrom(checkpoint);
4668
- return args;
4669
- }
4670
4716
  function parseClassModifiers(stream, declaration) {
4671
4717
  while (stream.check("keyword") && CLASS_MODIFIERS.has(stream.current().value)) {
4672
4718
  if (stream.next().value === "extends") {
@@ -5305,10 +5351,12 @@ function typeToString(type) {
5305
5351
  }
5306
5352
  if (type.kind === "function") {
5307
5353
  const parameters = type.parameters.map(typeToString);
5354
+ const names = type.typeParameters ?? [];
5308
5355
  if (type.isVariadic) {
5309
5356
  parameters.push(type.variadicType === void 0 ? "..." : `...: ${typeToString(type.variadicType)}`);
5310
5357
  }
5311
- return `fun(${parameters.join(", ")}): ${typeToString(type.returnType)}`;
5358
+ const generic = names.length === 0 ? "" : `<${names.join(", ")}>`;
5359
+ return `fun${generic}(${parameters.join(", ")}): ${typeToString(type.returnType)}`;
5312
5360
  }
5313
5361
  return type.kind;
5314
5362
  }
@@ -6762,7 +6810,7 @@ import { tmpdir } from "node:os";
6762
6810
  import { join as join2 } from "node:path";
6763
6811
 
6764
6812
  // src/cli/version.ts
6765
- var VERSION = true ? "0.19.4" : "0.0.0-dev";
6813
+ var VERSION = true ? "0.19.6" : "0.0.0-dev";
6766
6814
  var PROGRAM_NAME = "luam";
6767
6815
  var PROGRAM_DESCRIPTION = "luam \u2014 the Luam compiler for Multi Theft Auto resources.";
6768
6816
 
@@ -8780,6 +8828,13 @@ function assignmentFact(narrowed, declared) {
8780
8828
  }
8781
8829
 
8782
8830
  // ../compiler/src/checker/type-substitution.ts
8831
+ function shadowed(substitutions, names) {
8832
+ const remaining = new Map(substitutions);
8833
+ for (const name of names) {
8834
+ remaining.delete(name);
8835
+ }
8836
+ return remaining;
8837
+ }
8783
8838
  function substituteType(type, substitutions) {
8784
8839
  if (type.kind === "named") {
8785
8840
  const args = type.typeArguments ?? [];
@@ -8801,10 +8856,17 @@ function substituteType(type, substitutions) {
8801
8856
  return createUnion(type.options.map((option) => substituteType(option, substitutions)));
8802
8857
  }
8803
8858
  if (type.kind === "function") {
8804
- const parameters = type.parameters.map((parameter) => substituteType(parameter, substitutions));
8805
- const returnType = substituteType(type.returnType, substitutions);
8806
- const variadicType = type.variadicType === void 0 ? void 0 : substituteType(type.variadicType, substitutions);
8807
- return createFunction(parameters, returnType, type.minimumArguments, type.isVariadic, type.parameterNames, variadicType);
8859
+ const own = type.typeParameters ?? [];
8860
+ const outer = own.length === 0 ? substitutions : shadowed(substitutions, own);
8861
+ const parameters = type.parameters.map((parameter) => substituteType(parameter, outer));
8862
+ const returnType = substituteType(type.returnType, outer);
8863
+ const variadicType = type.variadicType === void 0 ? void 0 : substituteType(type.variadicType, outer);
8864
+ const substituted = createFunction(parameters, returnType, type.minimumArguments, type.isVariadic, type.parameterNames, variadicType);
8865
+ if (own.length > 0) {
8866
+ substituted.typeParameters = [...own];
8867
+ substituted.typeConstraints = (type.typeConstraints ?? []).map((constraint) => constraint === null ? null : substituteType(constraint, outer));
8868
+ }
8869
+ return substituted;
8808
8870
  }
8809
8871
  if (type.kind === "tuple") {
8810
8872
  return createTuple(type.elements.map((element) => substituteType(element, substitutions)));
@@ -8921,21 +8983,24 @@ function satisfies(context, argument, constraint) {
8921
8983
  }
8922
8984
  return isAssignable(argument, constraint, { allowNil: false });
8923
8985
  }
8924
- function checkTypeConstraints(context, name, typeArguments, position2) {
8925
- const info = context.declarations.lookupClass(name);
8926
- if (info === null) {
8927
- return;
8928
- }
8929
- info.typeConstraints.forEach((constraint, index) => {
8986
+ function reportConstraintViolations(context, owner, typeParameters, typeConstraints, typeArguments, position2) {
8987
+ typeConstraints.forEach((constraint, index) => {
8930
8988
  const argument = typeArguments[index];
8931
8989
  if (constraint === null || argument === void 0 || argument.kind === "any" || satisfies(context, argument, constraint)) {
8932
8990
  return;
8933
8991
  }
8934
- const parameter = info.typeParameters[index] ?? "T";
8935
- const message = `Type argument "${typeToString(argument)}" does not satisfy "${parameter} extends ${typeToString(constraint)}" on class "${name}".`;
8992
+ const parameter = typeParameters[index] ?? "T";
8993
+ const message = `Type argument "${typeToString(argument)}" does not satisfy "${parameter} extends ${typeToString(constraint)}" on ${owner}.`;
8936
8994
  context.report("check-generic-constraint", message, position2);
8937
8995
  });
8938
8996
  }
8997
+ function checkTypeConstraints(context, name, typeArguments, position2) {
8998
+ const info = context.declarations.lookupClass(name);
8999
+ if (info === null) {
9000
+ return;
9001
+ }
9002
+ reportConstraintViolations(context, `class "${name}"`, info.typeParameters, info.typeConstraints, typeArguments, position2);
9003
+ }
8939
9004
 
8940
9005
  // ../mta-types/src/lua-standard.ts
8941
9006
  var LUA_GLOBALS = {
@@ -15027,6 +15092,8 @@ function accessorMethod(field2, decorator, name) {
15027
15092
  isConstructor: false,
15028
15093
  isSynthetic: true,
15029
15094
  isStatic: false,
15095
+ typeParameters: [],
15096
+ typeConstraints: [],
15030
15097
  parameters: decorator === "Getter" ? [] : [value],
15031
15098
  returnAnnotation: decorator === "Getter" ? field2.annotation : voidAnnotation,
15032
15099
  body,
@@ -15035,7 +15102,7 @@ function accessorMethod(field2, decorator, name) {
15035
15102
  };
15036
15103
  }
15037
15104
  function generatedMethod(field2, name, parameters, returnAnnotation, kind, fields) {
15038
- return { kind: "class-method", name, isConstructor: false, isSynthetic: true, isStatic: false, parameters, returnAnnotation, body: [], decorators: [], generated: { kind, fields }, position: field2.position };
15105
+ return { kind: "class-method", name, isConstructor: false, isSynthetic: true, isStatic: false, typeParameters: [], typeConstraints: [], parameters, returnAnnotation, body: [], decorators: [], generated: { kind, fields }, position: field2.position };
15039
15106
  }
15040
15107
  function typeName(name, node) {
15041
15108
  return { kind: "type-name", name, typeArguments: [], position: node.position };
@@ -15073,6 +15140,8 @@ function staticMethod(statement, name, returnAnnotation, kind, descriptor) {
15073
15140
  isConstructor: false,
15074
15141
  isSynthetic: true,
15075
15142
  isStatic: true,
15143
+ typeParameters: [],
15144
+ typeConstraints: [],
15076
15145
  parameters: [value],
15077
15146
  returnAnnotation,
15078
15147
  body: [],
@@ -18570,6 +18639,33 @@ function specializeEventCall(context, expression, signature) {
18570
18639
  return candidate === null ? null : specialize(signature, candidate);
18571
18640
  }
18572
18641
 
18642
+ // ../compiler/src/checker/generic-call.ts
18643
+ function countArguments(total) {
18644
+ return total === 1 ? "1 type argument" : `${total} type arguments`;
18645
+ }
18646
+ function substituteSignature(signature, substitutions) {
18647
+ const parameters = signature.parameters.map((parameter) => substituteType(parameter, substitutions));
18648
+ const returnType = substituteType(signature.returnType, substitutions);
18649
+ const variadicType = signature.variadicType === void 0 ? void 0 : substituteType(signature.variadicType, substitutions);
18650
+ return createFunction(parameters, returnType, signature.minimumArguments, signature.isVariadic, signature.parameterNames, variadicType);
18651
+ }
18652
+ function specializeCall(context, owner, signature, argumentTypes, typeArguments, position2) {
18653
+ const names = signature.typeParameters ?? [];
18654
+ if (names.length === 0) {
18655
+ if (typeArguments.length > 0) {
18656
+ context.report("check-generic-arity", `${owner} does not accept type arguments.`, position2);
18657
+ }
18658
+ return signature;
18659
+ }
18660
+ const explicit = typeArguments.map((argument) => context.resolveAnnotation(argument));
18661
+ if (typeArguments.length > 0 && explicit.length !== names.length) {
18662
+ context.report("check-generic-arity", `${owner} expects ${countArguments(names.length)} but received ${explicit.length}.`, position2);
18663
+ }
18664
+ const resolved2 = typeArguments.length > 0 ? names.map((unused, index) => explicit[index] ?? ANY_TYPE) : inferTypeArguments(names, signature.parameters, argumentTypes);
18665
+ reportConstraintViolations(context, owner, names, signature.typeConstraints ?? [], resolved2, position2);
18666
+ return substituteSignature(signature, new Map(names.map((name, index) => [name, resolved2[index] ?? ANY_TYPE])));
18667
+ }
18668
+
18573
18669
  // ../compiler/src/checker/method-receiver.ts
18574
18670
  var SELF_PARAMETER = "self";
18575
18671
  function resolveNonNominalMethod(receiver, method) {
@@ -19383,15 +19479,48 @@ function checkLoopBody(context, body, facts) {
19383
19479
  checkBlock(context, body);
19384
19480
  context.setFlow(entry);
19385
19481
  }
19482
+ function isTruthyLiteral(condition) {
19483
+ if (condition.kind === "group-expression") {
19484
+ return isTruthyLiteral(condition.expression);
19485
+ }
19486
+ const literal2 = condition.kind === "number-literal" || condition.kind === "string-literal" || condition.kind === "template-literal";
19487
+ return literal2 || condition.kind === "boolean-literal" && condition.value;
19488
+ }
19489
+ function isFalsyLiteral(condition) {
19490
+ if (condition.kind === "group-expression") {
19491
+ return isFalsyLiteral(condition.expression);
19492
+ }
19493
+ return condition.kind === "nil-literal" || condition.kind === "boolean-literal" && !condition.value;
19494
+ }
19495
+ function hasBreak(body) {
19496
+ return body.some((statement) => {
19497
+ if (statement.kind === "break-statement") {
19498
+ return true;
19499
+ }
19500
+ if (statement.kind === "do-statement") {
19501
+ return hasBreak(statement.body);
19502
+ }
19503
+ if (statement.kind === "if-statement") {
19504
+ return statement.clauses.some((clause) => hasBreak(clause.body)) || statement.alternate !== null && hasBreak(statement.alternate);
19505
+ }
19506
+ return false;
19507
+ });
19508
+ }
19386
19509
  function checkWhile(context, statement) {
19387
19510
  forgetAssignedPaths(context, statement.body);
19388
19511
  checkExpression(context, statement.condition);
19389
19512
  checkLoopBody(context, statement.body, conditionFacts(context, statement.condition));
19390
19513
  context.applyFlowFacts(negatedFacts(context, statement.condition));
19514
+ if (isTruthyLiteral(statement.condition) && !hasBreak(statement.body)) {
19515
+ context.markUnreachable();
19516
+ }
19391
19517
  }
19392
19518
  function checkRepeat(context, statement) {
19393
19519
  checkLoopBody(context, statement.body, /* @__PURE__ */ new Map());
19394
19520
  checkExpression(context, statement.condition);
19521
+ if (isFalsyLiteral(statement.condition) && !hasBreak(statement.body)) {
19522
+ context.markUnreachable();
19523
+ }
19395
19524
  }
19396
19525
  function checkNumericFor(context, statement) {
19397
19526
  const bounds = [statement.start, statement.limit, statement.step].filter((value) => value !== null);
@@ -19505,7 +19634,31 @@ function buildFunctionType(context, parameters, returnAnnotation, contextual = n
19505
19634
  const names = named2.map((parameter) => parameter.name);
19506
19635
  return createFunction(types, context.resolveAnnotation(returnAnnotation), minimumArguments(context, parameters), isVariadic, names);
19507
19636
  }
19508
- function checkFunctionBody(context, parameters, returnAnnotation, body, signature, selfType2) {
19637
+ function applyTypeParameters(context, type, names, constraints) {
19638
+ if (names.length === 0) {
19639
+ return type;
19640
+ }
19641
+ context.noteTypeParameters(names);
19642
+ type.typeParameters = [...names];
19643
+ type.typeConstraints = names.map((unused, index) => {
19644
+ const constraint = constraints[index];
19645
+ return constraint === void 0 || constraint === null ? null : context.resolveAnnotation(constraint);
19646
+ });
19647
+ return type;
19648
+ }
19649
+ var FALLTHROUGH_KINDS = /* @__PURE__ */ new Set(["any", "void", "nil", "optional", "unknown"]);
19650
+ function toleratesFallthrough(declared) {
19651
+ if (FALLTHROUGH_KINDS.has(declared.kind)) {
19652
+ return true;
19653
+ }
19654
+ return declared.kind === "union" && declared.options.some((option) => option.kind === "nil");
19655
+ }
19656
+ function reportMissingReturn(context, declared, position2) {
19657
+ const name = typeToString(declared);
19658
+ const repair = declared.kind === "tuple" ? "Add a return on every path." : `Add a return on every path, or declare "${name}?".`;
19659
+ context.report("check-missing-return", `This function declares "${name}" but can end without returning a value. ${repair}`, position2);
19660
+ }
19661
+ function checkFunctionBody(context, parameters, returnAnnotation, body, signature, selfType2, position2 = ORIGIN) {
19509
19662
  forgetAssignedPaths(context, body);
19510
19663
  const entry = context.flowState;
19511
19664
  context.setFlow(cloneFlow(entry));
@@ -19523,6 +19676,10 @@ function checkFunctionBody(context, parameters, returnAnnotation, body, signatur
19523
19676
  }
19524
19677
  }
19525
19678
  checkStatements(context, body);
19679
+ const declared = context.currentReturnType();
19680
+ if (declared !== null && context.flowState.reachable && !toleratesFallthrough(declared)) {
19681
+ reportMissingReturn(context, declared, position2);
19682
+ }
19526
19683
  const inferred = context.popReturnType();
19527
19684
  if (returnAnnotation === null) {
19528
19685
  signature.returnType = inferred;
@@ -19616,7 +19773,8 @@ function checkAssignment(context, statement) {
19616
19773
  });
19617
19774
  }
19618
19775
  function checkFunctionDeclaration(context, statement) {
19619
- const type = buildFunctionType(context, statement.parameters, statement.returnAnnotation);
19776
+ const built = buildFunctionType(context, statement.parameters, statement.returnAnnotation);
19777
+ const type = applyTypeParameters(context, built, statement.typeParameters, statement.typeConstraints);
19620
19778
  if (statement.name.kind === "identifier") {
19621
19779
  const symbol = { name: statement.name.name, type, isLocal: statement.isLocal, position: statement.position };
19622
19780
  if (statement.isLocal) {
@@ -19626,7 +19784,7 @@ function checkFunctionDeclaration(context, statement) {
19626
19784
  }
19627
19785
  }
19628
19786
  context.record(statement.name, type);
19629
- checkFunctionBody(context, statement.parameters, statement.returnAnnotation, statement.body, type, statement.isMethod ? ANY_TYPE : null);
19787
+ checkFunctionBody(context, statement.parameters, statement.returnAnnotation, statement.body, type, statement.isMethod ? ANY_TYPE : null, statement.position);
19630
19788
  }
19631
19789
  function checkReturn(context, statement) {
19632
19790
  const valueTypes = checkValueList(context, statement.values);
@@ -19819,17 +19977,18 @@ function checkMember(context, expression) {
19819
19977
  }
19820
19978
  return context.record(expression, extension === null ? ANY_TYPE : EXTENSION_RESULTS[extension.result]);
19821
19979
  }
19822
- function countArguments(total) {
19980
+ function countArguments2(total) {
19823
19981
  return total === 1 ? "1 argument" : `${total} arguments`;
19824
19982
  }
19825
- function checkSignature(context, args, signature, position2) {
19826
- const argumentTypes = checkValueList(context, args, signature.parameters);
19983
+ function checkSignature(context, args, declared, position2, owner = "This call", typeArguments = []) {
19984
+ const argumentTypes = checkValueList(context, args, declared.parameters);
19985
+ const signature = specializeCall(context, owner, declared, argumentTypes, typeArguments, position2);
19827
19986
  if (argumentTypes.length < signature.minimumArguments) {
19828
- const message = `This call expects at least ${countArguments(signature.minimumArguments)} but received ${argumentTypes.length}.`;
19987
+ const message = `This call expects at least ${countArguments2(signature.minimumArguments)} but received ${argumentTypes.length}.`;
19829
19988
  context.report("check-argument-count", message, position2);
19830
19989
  }
19831
19990
  if (!signature.isVariadic && argumentTypes.length > signature.parameters.length) {
19832
- const message = `This call expects at most ${countArguments(signature.parameters.length)} but received ${argumentTypes.length}.`;
19991
+ const message = `This call expects at most ${countArguments2(signature.parameters.length)} but received ${argumentTypes.length}.`;
19833
19992
  context.report("check-argument-count", message, position2);
19834
19993
  }
19835
19994
  signature.parameters.forEach((parameter, index) => {
@@ -19850,13 +20009,17 @@ function checkSignature(context, args, signature, position2) {
19850
20009
  }
19851
20010
  return signature.returnType;
19852
20011
  }
20012
+ function callOwner(expression) {
20013
+ const name = expression.method ?? (expression.callee.kind === "identifier" ? expression.callee.name : null);
20014
+ return name === null ? "This call" : `Function "${name}"`;
20015
+ }
19853
20016
  function checkArguments(context, expression, calleeType) {
19854
20017
  if (calleeType.kind !== "function") {
19855
20018
  checkValueList(context, expression.args);
19856
20019
  reportNotCallable(context, expression, calleeType);
19857
20020
  return ANY_TYPE;
19858
20021
  }
19859
- return checkSignature(context, expression.args, calleeType, expression.position);
20022
+ return checkSignature(context, expression.args, calleeType, expression.position, callOwner(expression), expression.typeArguments);
19860
20023
  }
19861
20024
  function isSuperCall(expression) {
19862
20025
  return expression.method === null && expression.callee.kind === "identifier" && expression.callee.name === "super";
@@ -19871,14 +20034,14 @@ function checkMethodCall(context, expression, method, receiver) {
19871
20034
  checkValueList(context, expression.args);
19872
20035
  return ANY_TYPE;
19873
20036
  }
19874
- return checkSignature(context, expression.args, withoutSelfParameter(signature), expression.position);
20037
+ return checkSignature(context, expression.args, withoutSelfParameter(signature), expression.position, callOwner(expression), expression.typeArguments);
19875
20038
  }
19876
20039
  const declared = memberOf(context, receiver, method);
19877
20040
  if (declared?.type.kind === "function") {
19878
20041
  if (declared.deprecated === true) {
19879
20042
  context.warn("check-deprecated-use", `Member "${method}" is deprecated.`, expression.position);
19880
20043
  }
19881
- return checkSignature(context, expression.args, declared.type, expression.position);
20044
+ return checkSignature(context, expression.args, declared.type, expression.position, callOwner(expression), expression.typeArguments);
19882
20045
  }
19883
20046
  if (!isMtaElement(context, receiver.name)) {
19884
20047
  checkValueList(context, expression.args);
@@ -20019,8 +20182,9 @@ function checkMultiValueExpression(context, expression, expected = null) {
20019
20182
  case "table-expression":
20020
20183
  return checkTable(context, expression);
20021
20184
  case "function-expression": {
20022
- const type = buildFunctionType(context, expression.parameters, expression.returnAnnotation, contextualFunction(expected));
20023
- checkFunctionBody(context, expression.parameters, expression.returnAnnotation, expression.body, type, null);
20185
+ const built = buildFunctionType(context, expression.parameters, expression.returnAnnotation, contextualFunction(expected));
20186
+ const type = applyTypeParameters(context, built, expression.typeParameters, expression.typeConstraints);
20187
+ checkFunctionBody(context, expression.parameters, expression.returnAnnotation, expression.body, type, null, expression.position);
20024
20188
  return context.record(expression, type);
20025
20189
  }
20026
20190
  case "binary-expression": {
@@ -20109,7 +20273,7 @@ function registerMembers(context, info, statement) {
20109
20273
  }
20110
20274
  const generated = expandClassDecorators(context, statement, fieldTypes);
20111
20275
  for (const member of [...statement.members, ...generated]) {
20112
- const type = member.kind === "class-field" ? fieldTypes.get(member) ?? ANY_TYPE : member.isSynthetic ? syntheticMethodType(context, member, fieldTypes) : buildFunctionType(context, member.parameters, member.returnAnnotation);
20276
+ const type = member.kind === "class-field" ? fieldTypes.get(member) ?? ANY_TYPE : member.isSynthetic ? syntheticMethodType(context, member, fieldTypes) : applyTypeParameters(context, buildFunctionType(context, member.parameters, member.returnAnnotation), member.typeParameters, member.typeConstraints);
20113
20277
  const decorators = member.decorators.map((decorator) => decorator.name);
20114
20278
  if (member.kind === "class-method" && isMetamethodMember(member)) {
20115
20279
  checkMetamethod(context, info.name, member, type);
@@ -20169,11 +20333,11 @@ function checkMethodBody(context, info, member) {
20169
20333
  return;
20170
20334
  }
20171
20335
  if (member.isStatic) {
20172
- checkFunctionBody(context, member.parameters, member.returnAnnotation, member.body, signature, null);
20336
+ checkFunctionBody(context, member.parameters, member.returnAnnotation, member.body, signature, null, member.position);
20173
20337
  return;
20174
20338
  }
20175
20339
  context.pushClassMethod({ className: info.name, methodName: member.name });
20176
- checkFunctionBody(context, member.parameters, member.returnAnnotation, member.body, signature, selfType(info));
20340
+ checkFunctionBody(context, member.parameters, member.returnAnnotation, member.body, signature, selfType(info), member.position);
20177
20341
  context.popClassMethod();
20178
20342
  }
20179
20343
  function resolveSuperClass(context, statement) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thigasdevelopment/luam",
3
- "version": "0.19.4",
3
+ "version": "0.19.6",
4
4
  "description": "The Luam compiler for Multi Theft Auto resources.",
5
5
  "keywords": [
6
6
  "mta",