redscript-mc 1.1.0 → 1.2.1

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 (83) hide show
  1. package/CHANGELOG.md +59 -0
  2. package/README.md +53 -10
  3. package/README.zh.md +53 -10
  4. package/dist/__tests__/cli.test.js +138 -0
  5. package/dist/__tests__/codegen.test.js +25 -0
  6. package/dist/__tests__/dce.test.d.ts +1 -0
  7. package/dist/__tests__/dce.test.js +137 -0
  8. package/dist/__tests__/e2e.test.js +190 -12
  9. package/dist/__tests__/lexer.test.js +31 -4
  10. package/dist/__tests__/lowering.test.js +172 -9
  11. package/dist/__tests__/mc-integration.test.js +145 -51
  12. package/dist/__tests__/mc-syntax.test.js +12 -0
  13. package/dist/__tests__/optimizer-advanced.test.js +3 -3
  14. package/dist/__tests__/parser.test.js +90 -0
  15. package/dist/__tests__/runtime.test.js +21 -8
  16. package/dist/__tests__/typechecker.test.js +188 -0
  17. package/dist/ast/types.d.ts +42 -3
  18. package/dist/cli.js +15 -10
  19. package/dist/codegen/mcfunction/index.js +30 -1
  20. package/dist/codegen/structure/index.d.ts +4 -1
  21. package/dist/codegen/structure/index.js +29 -2
  22. package/dist/compile.d.ts +11 -0
  23. package/dist/compile.js +40 -6
  24. package/dist/events/types.d.ts +35 -0
  25. package/dist/events/types.js +59 -0
  26. package/dist/index.d.ts +1 -0
  27. package/dist/index.js +7 -3
  28. package/dist/ir/types.d.ts +4 -0
  29. package/dist/lexer/index.d.ts +2 -1
  30. package/dist/lexer/index.js +91 -1
  31. package/dist/lowering/index.d.ts +32 -1
  32. package/dist/lowering/index.js +476 -16
  33. package/dist/optimizer/dce.d.ts +23 -0
  34. package/dist/optimizer/dce.js +591 -0
  35. package/dist/parser/index.d.ts +4 -0
  36. package/dist/parser/index.js +160 -26
  37. package/dist/typechecker/index.d.ts +19 -0
  38. package/dist/typechecker/index.js +392 -17
  39. package/docs/ARCHITECTURE.zh.md +1088 -0
  40. package/docs/ENTITY_TYPE_SYSTEM.md +242 -0
  41. package/editors/vscode/.vscodeignore +3 -0
  42. package/editors/vscode/CHANGELOG.md +9 -0
  43. package/editors/vscode/icon.png +0 -0
  44. package/editors/vscode/out/extension.js +1144 -72
  45. package/editors/vscode/package-lock.json +2 -2
  46. package/editors/vscode/package.json +1 -1
  47. package/editors/vscode/syntaxes/redscript.tmLanguage.json +6 -2
  48. package/examples/spiral.mcrs +79 -0
  49. package/logo.png +0 -0
  50. package/package.json +1 -1
  51. package/src/__tests__/cli.test.ts +166 -0
  52. package/src/__tests__/codegen.test.ts +27 -0
  53. package/src/__tests__/dce.test.ts +129 -0
  54. package/src/__tests__/e2e.test.ts +201 -12
  55. package/src/__tests__/fixtures/event-test.mcrs +13 -0
  56. package/src/__tests__/fixtures/impl-test.mcrs +46 -0
  57. package/src/__tests__/fixtures/interval-test.mcrs +11 -0
  58. package/src/__tests__/fixtures/is-check-test.mcrs +20 -0
  59. package/src/__tests__/fixtures/timeout-test.mcrs +7 -0
  60. package/src/__tests__/lexer.test.ts +35 -4
  61. package/src/__tests__/lowering.test.ts +187 -9
  62. package/src/__tests__/mc-integration.test.ts +166 -51
  63. package/src/__tests__/mc-syntax.test.ts +14 -0
  64. package/src/__tests__/optimizer-advanced.test.ts +3 -3
  65. package/src/__tests__/parser.test.ts +102 -5
  66. package/src/__tests__/runtime.test.ts +24 -8
  67. package/src/__tests__/typechecker.test.ts +204 -0
  68. package/src/ast/types.ts +39 -2
  69. package/src/cli.ts +24 -10
  70. package/src/codegen/mcfunction/index.ts +31 -1
  71. package/src/codegen/structure/index.ts +40 -2
  72. package/src/compile.ts +59 -7
  73. package/src/events/types.ts +69 -0
  74. package/src/index.ts +9 -4
  75. package/src/ir/types.ts +4 -0
  76. package/src/lexer/index.ts +105 -2
  77. package/src/lowering/index.ts +566 -18
  78. package/src/optimizer/dce.ts +618 -0
  79. package/src/parser/index.ts +187 -29
  80. package/src/stdlib/README.md +34 -4
  81. package/src/stdlib/tags.mcrs +951 -0
  82. package/src/stdlib/timer.mcrs +54 -33
  83. package/src/typechecker/index.ts +469 -18
@@ -8,18 +8,99 @@
8
8
  Object.defineProperty(exports, "__esModule", { value: true });
9
9
  exports.TypeChecker = void 0;
10
10
  const diagnostics_1 = require("../diagnostics");
11
+ const types_1 = require("../events/types");
12
+ // Entity type hierarchy for subtype checking
13
+ const ENTITY_HIERARCHY = {
14
+ 'entity': null,
15
+ 'Player': 'entity',
16
+ 'Mob': 'entity',
17
+ 'HostileMob': 'Mob',
18
+ 'PassiveMob': 'Mob',
19
+ 'Zombie': 'HostileMob',
20
+ 'Skeleton': 'HostileMob',
21
+ 'Creeper': 'HostileMob',
22
+ 'Spider': 'HostileMob',
23
+ 'Enderman': 'HostileMob',
24
+ 'Pig': 'PassiveMob',
25
+ 'Cow': 'PassiveMob',
26
+ 'Sheep': 'PassiveMob',
27
+ 'Chicken': 'PassiveMob',
28
+ 'Villager': 'PassiveMob',
29
+ 'ArmorStand': 'entity',
30
+ 'Item': 'entity',
31
+ 'Arrow': 'entity',
32
+ };
33
+ // Map Minecraft type names to entity types
34
+ const MC_TYPE_TO_ENTITY = {
35
+ 'zombie': 'Zombie',
36
+ 'minecraft:zombie': 'Zombie',
37
+ 'skeleton': 'Skeleton',
38
+ 'minecraft:skeleton': 'Skeleton',
39
+ 'creeper': 'Creeper',
40
+ 'minecraft:creeper': 'Creeper',
41
+ 'spider': 'Spider',
42
+ 'minecraft:spider': 'Spider',
43
+ 'enderman': 'Enderman',
44
+ 'minecraft:enderman': 'Enderman',
45
+ 'pig': 'Pig',
46
+ 'minecraft:pig': 'Pig',
47
+ 'cow': 'Cow',
48
+ 'minecraft:cow': 'Cow',
49
+ 'sheep': 'Sheep',
50
+ 'minecraft:sheep': 'Sheep',
51
+ 'chicken': 'Chicken',
52
+ 'minecraft:chicken': 'Chicken',
53
+ 'villager': 'Villager',
54
+ 'minecraft:villager': 'Villager',
55
+ 'armor_stand': 'ArmorStand',
56
+ 'minecraft:armor_stand': 'ArmorStand',
57
+ 'item': 'Item',
58
+ 'minecraft:item': 'Item',
59
+ 'arrow': 'Arrow',
60
+ 'minecraft:arrow': 'Arrow',
61
+ };
62
+ const VOID_TYPE = { kind: 'named', name: 'void' };
63
+ const INT_TYPE = { kind: 'named', name: 'int' };
64
+ const STRING_TYPE = { kind: 'named', name: 'string' };
65
+ const FORMAT_STRING_TYPE = { kind: 'named', name: 'format_string' };
66
+ const BUILTIN_SIGNATURES = {
67
+ setTimeout: {
68
+ params: [INT_TYPE, { kind: 'function_type', params: [], return: VOID_TYPE }],
69
+ return: VOID_TYPE,
70
+ },
71
+ setInterval: {
72
+ params: [INT_TYPE, { kind: 'function_type', params: [], return: VOID_TYPE }],
73
+ return: INT_TYPE,
74
+ },
75
+ clearInterval: {
76
+ params: [INT_TYPE],
77
+ return: VOID_TYPE,
78
+ },
79
+ };
11
80
  // ---------------------------------------------------------------------------
12
81
  // Type Checker
13
82
  // ---------------------------------------------------------------------------
14
83
  class TypeChecker {
15
84
  constructor(source, filePath) {
16
85
  this.functions = new Map();
86
+ this.implMethods = new Map();
17
87
  this.structs = new Map();
18
88
  this.enums = new Map();
19
89
  this.consts = new Map();
20
90
  this.currentFn = null;
21
91
  this.currentReturnType = null;
22
92
  this.scope = new Map();
93
+ // Stack for tracking @s type in different contexts
94
+ this.selfTypeStack = ['entity'];
95
+ this.richTextBuiltins = new Map([
96
+ ['say', { messageIndex: 0 }],
97
+ ['announce', { messageIndex: 0 }],
98
+ ['tell', { messageIndex: 1 }],
99
+ ['tellraw', { messageIndex: 1 }],
100
+ ['title', { messageIndex: 1 }],
101
+ ['actionbar', { messageIndex: 1 }],
102
+ ['subtitle', { messageIndex: 1 }],
103
+ ]);
23
104
  this.collector = new diagnostics_1.DiagnosticCollector(source, filePath);
24
105
  }
25
106
  getNodeLocation(node) {
@@ -41,6 +122,26 @@ class TypeChecker {
41
122
  for (const fn of program.declarations) {
42
123
  this.functions.set(fn.name, fn);
43
124
  }
125
+ for (const implBlock of program.implBlocks ?? []) {
126
+ let methods = this.implMethods.get(implBlock.typeName);
127
+ if (!methods) {
128
+ methods = new Map();
129
+ this.implMethods.set(implBlock.typeName, methods);
130
+ }
131
+ for (const method of implBlock.methods) {
132
+ const selfIndex = method.params.findIndex(param => param.name === 'self');
133
+ if (selfIndex > 0) {
134
+ this.report(`Method '${method.name}' must declare 'self' as the first parameter`, method.params[selfIndex]);
135
+ }
136
+ if (selfIndex === 0) {
137
+ const selfType = this.normalizeType(method.params[0].type);
138
+ if (selfType.kind !== 'struct' || selfType.name !== implBlock.typeName) {
139
+ this.report(`Method '${method.name}' has invalid 'self' type`, method.params[0]);
140
+ }
141
+ }
142
+ methods.set(method.name, method);
143
+ }
144
+ }
44
145
  for (const struct of program.structs ?? []) {
45
146
  const fields = new Map();
46
147
  for (const field of struct.fields) {
@@ -67,6 +168,11 @@ class TypeChecker {
67
168
  for (const fn of program.declarations) {
68
169
  this.checkFunction(fn);
69
170
  }
171
+ for (const implBlock of program.implBlocks ?? []) {
172
+ for (const method of implBlock.methods) {
173
+ this.checkFunction(method);
174
+ }
175
+ }
70
176
  return this.collector.getErrors();
71
177
  }
72
178
  checkFunction(fn) {
@@ -74,6 +180,7 @@ class TypeChecker {
74
180
  this.currentReturnType = this.normalizeType(fn.returnType);
75
181
  this.scope = new Map();
76
182
  let seenDefault = false;
183
+ this.checkFunctionDecorators(fn);
77
184
  for (const [name, type] of this.consts.entries()) {
78
185
  this.scope.set(name, { type, mutable: false });
79
186
  }
@@ -98,6 +205,37 @@ class TypeChecker {
98
205
  this.currentFn = null;
99
206
  this.currentReturnType = null;
100
207
  }
208
+ checkFunctionDecorators(fn) {
209
+ const eventDecorators = fn.decorators.filter(decorator => decorator.name === 'on');
210
+ if (eventDecorators.length === 0) {
211
+ return;
212
+ }
213
+ if (eventDecorators.length > 1) {
214
+ this.report(`Function '${fn.name}' cannot have multiple @on decorators`, fn);
215
+ return;
216
+ }
217
+ const eventType = eventDecorators[0].args?.eventType;
218
+ if (!eventType) {
219
+ this.report(`Function '${fn.name}' is missing an event type in @on(...)`, fn);
220
+ return;
221
+ }
222
+ if (!(0, types_1.isEventTypeName)(eventType)) {
223
+ this.report(`Unknown event type '${eventType}'`, fn);
224
+ return;
225
+ }
226
+ const expectedParams = (0, types_1.getEventParamSpecs)(eventType);
227
+ if (fn.params.length !== expectedParams.length) {
228
+ this.report(`Event handler '${fn.name}' for ${eventType} must declare ${expectedParams.length} parameter(s), got ${fn.params.length}`, fn);
229
+ return;
230
+ }
231
+ for (let i = 0; i < expectedParams.length; i++) {
232
+ const actual = this.normalizeType(fn.params[i].type);
233
+ const expected = this.normalizeType(expectedParams[i].type);
234
+ if (!this.typesMatch(expected, actual)) {
235
+ this.report(`Event handler '${fn.name}' parameter ${i + 1} must be ${this.typeToString(expected)}, got ${this.typeToString(actual)}`, fn.params[i]);
236
+ }
237
+ }
238
+ }
101
239
  checkBlock(stmts) {
102
240
  for (const stmt of stmts) {
103
241
  this.checkStmt(stmt);
@@ -113,9 +251,7 @@ class TypeChecker {
113
251
  break;
114
252
  case 'if':
115
253
  this.checkExpr(stmt.cond);
116
- this.checkBlock(stmt.then);
117
- if (stmt.else_)
118
- this.checkBlock(stmt.else_);
254
+ this.checkIfBranches(stmt);
119
255
  break;
120
256
  case 'while':
121
257
  this.checkExpr(stmt.cond);
@@ -131,7 +267,16 @@ class TypeChecker {
131
267
  case 'foreach':
132
268
  this.checkExpr(stmt.iterable);
133
269
  if (stmt.iterable.kind === 'selector') {
134
- this.scope.set(stmt.binding, { type: { kind: 'named', name: 'void' }, mutable: true }); // Entity marker
270
+ // Infer entity type from selector (access .sel for the EntitySelector)
271
+ const entityType = this.inferEntityTypeFromSelector(stmt.iterable.sel);
272
+ this.scope.set(stmt.binding, {
273
+ type: { kind: 'entity', entityType },
274
+ mutable: false // Entity bindings are not reassignable
275
+ });
276
+ // Push self type context for @s inside the loop
277
+ this.pushSelfType(entityType);
278
+ this.checkBlock(stmt.body);
279
+ this.popSelfType();
135
280
  }
136
281
  else {
137
282
  const iterableType = this.inferType(stmt.iterable);
@@ -141,8 +286,8 @@ class TypeChecker {
141
286
  else {
142
287
  this.scope.set(stmt.binding, { type: { kind: 'named', name: 'void' }, mutable: true });
143
288
  }
289
+ this.checkBlock(stmt.body);
144
290
  }
145
- this.checkBlock(stmt.body);
146
291
  break;
147
292
  case 'match':
148
293
  this.checkExpr(stmt.expr);
@@ -156,15 +301,41 @@ class TypeChecker {
156
301
  this.checkBlock(arm.body);
157
302
  }
158
303
  break;
159
- case 'as_block':
304
+ case 'as_block': {
305
+ // as block changes @s to the selector's entity type
306
+ const entityType = this.inferEntityTypeFromSelector(stmt.selector);
307
+ this.pushSelfType(entityType);
308
+ this.checkBlock(stmt.body);
309
+ this.popSelfType();
310
+ break;
311
+ }
160
312
  case 'at_block':
313
+ // at block doesn't change @s type, only position
161
314
  this.checkBlock(stmt.body);
162
315
  break;
163
- case 'as_at':
316
+ case 'as_at': {
317
+ // as @x at @y - @s becomes the as selector's type
318
+ const entityType = this.inferEntityTypeFromSelector(stmt.as_sel);
319
+ this.pushSelfType(entityType);
164
320
  this.checkBlock(stmt.body);
321
+ this.popSelfType();
165
322
  break;
323
+ }
166
324
  case 'execute':
325
+ // execute with subcommands - check for 'as' subcommands
326
+ for (const sub of stmt.subcommands) {
327
+ if (sub.kind === 'as' && sub.selector) {
328
+ const entityType = this.inferEntityTypeFromSelector(sub.selector);
329
+ this.pushSelfType(entityType);
330
+ }
331
+ }
167
332
  this.checkBlock(stmt.body);
333
+ // Pop for each 'as' subcommand
334
+ for (const sub of stmt.subcommands) {
335
+ if (sub.kind === 'as') {
336
+ this.popSelfType();
337
+ }
338
+ }
168
339
  break;
169
340
  case 'expr':
170
341
  this.checkExpr(stmt.expr);
@@ -224,10 +395,21 @@ class TypeChecker {
224
395
  case 'member':
225
396
  this.checkMemberExpr(expr);
226
397
  break;
398
+ case 'static_call':
399
+ this.checkStaticCallExpr(expr);
400
+ break;
227
401
  case 'binary':
228
402
  this.checkExpr(expr.left);
229
403
  this.checkExpr(expr.right);
230
404
  break;
405
+ case 'is_check': {
406
+ this.checkExpr(expr.expr);
407
+ const checkedType = this.inferType(expr.expr);
408
+ if (checkedType.kind !== 'entity') {
409
+ this.report(`'is' checks require an entity expression, got ${this.typeToString(checkedType)}`, expr.expr);
410
+ }
411
+ break;
412
+ }
231
413
  case 'unary':
232
414
  this.checkExpr(expr.operand);
233
415
  break;
@@ -264,6 +446,18 @@ class TypeChecker {
264
446
  }
265
447
  }
266
448
  break;
449
+ case 'f_string':
450
+ for (const part of expr.parts) {
451
+ if (part.kind !== 'expr') {
452
+ continue;
453
+ }
454
+ this.checkExpr(part.expr);
455
+ const partType = this.inferType(part.expr);
456
+ if (!(partType.kind === 'named' && (partType.name === 'int' || partType.name === 'string' || partType.name === 'format_string'))) {
457
+ this.report(`f-string placeholder must be int or string, got ${this.typeToString(partType)}`, part.expr);
458
+ }
459
+ }
460
+ break;
267
461
  case 'array_lit':
268
462
  for (const elem of expr.elements) {
269
463
  this.checkExpr(elem);
@@ -274,11 +468,6 @@ class TypeChecker {
274
468
  break;
275
469
  case 'blockpos':
276
470
  break;
277
- case 'static_call':
278
- for (const arg of expr.args) {
279
- this.checkExpr(arg);
280
- }
281
- break;
282
471
  // Literals don't need checking
283
472
  case 'int_lit':
284
473
  case 'float_lit':
@@ -298,6 +487,16 @@ class TypeChecker {
298
487
  if (expr.fn === 'tp' || expr.fn === 'tp_to') {
299
488
  this.checkTpCall(expr);
300
489
  }
490
+ const richTextBuiltin = this.richTextBuiltins.get(expr.fn);
491
+ if (richTextBuiltin) {
492
+ this.checkRichTextBuiltinCall(expr, richTextBuiltin.messageIndex);
493
+ return;
494
+ }
495
+ const builtin = BUILTIN_SIGNATURES[expr.fn];
496
+ if (builtin) {
497
+ this.checkFunctionCallArgs(expr.args, builtin.params, expr.fn, expr);
498
+ return;
499
+ }
301
500
  // Check if function exists and arg count matches
302
501
  const fn = this.functions.get(expr.fn);
303
502
  if (fn) {
@@ -325,11 +524,30 @@ class TypeChecker {
325
524
  this.checkFunctionCallArgs(expr.args, varType.params, expr.fn, expr);
326
525
  return;
327
526
  }
527
+ const implMethod = this.resolveInstanceMethod(expr);
528
+ if (implMethod) {
529
+ this.checkFunctionCallArgs(expr.args, implMethod.params.map(param => this.normalizeType(param.type)), implMethod.name, expr);
530
+ return;
531
+ }
328
532
  for (const arg of expr.args) {
329
533
  this.checkExpr(arg);
330
534
  }
331
535
  // Built-in functions are not checked for arg count
332
536
  }
537
+ checkRichTextBuiltinCall(expr, messageIndex) {
538
+ for (let i = 0; i < expr.args.length; i++) {
539
+ this.checkExpr(expr.args[i], i === messageIndex ? undefined : STRING_TYPE);
540
+ }
541
+ const message = expr.args[messageIndex];
542
+ if (!message) {
543
+ return;
544
+ }
545
+ const messageType = this.inferType(message);
546
+ if (messageType.kind !== 'named' ||
547
+ (messageType.name !== 'string' && messageType.name !== 'format_string')) {
548
+ this.report(`Argument ${messageIndex + 1} of '${expr.fn}' expects string or format_string, got ${this.typeToString(messageType)}`, message);
549
+ }
550
+ }
333
551
  checkInvokeExpr(expr) {
334
552
  this.checkExpr(expr.callee);
335
553
  const calleeType = this.inferType(expr.callee);
@@ -412,6 +630,21 @@ class TypeChecker {
412
630
  }
413
631
  }
414
632
  }
633
+ checkStaticCallExpr(expr) {
634
+ const method = this.implMethods.get(expr.type)?.get(expr.method);
635
+ if (!method) {
636
+ this.report(`Type '${expr.type}' has no static method '${expr.method}'`, expr);
637
+ for (const arg of expr.args) {
638
+ this.checkExpr(arg);
639
+ }
640
+ return;
641
+ }
642
+ if (method.params[0]?.name === 'self') {
643
+ this.report(`Method '${expr.type}::${expr.method}' is an instance method`, expr);
644
+ return;
645
+ }
646
+ this.checkFunctionCallArgs(expr.args, method.params.map(param => this.normalizeType(param.type)), `${expr.type}::${expr.method}`, expr);
647
+ }
415
648
  checkLambdaExpr(expr, expectedType) {
416
649
  const normalizedExpected = expectedType ? this.normalizeType(expectedType) : undefined;
417
650
  const expectedFnType = normalizedExpected?.kind === 'function_type' ? normalizedExpected : undefined;
@@ -447,6 +680,37 @@ class TypeChecker {
447
680
  this.scope = outerScope;
448
681
  this.currentReturnType = outerReturnType;
449
682
  }
683
+ checkIfBranches(stmt) {
684
+ const narrowed = this.getThenBranchNarrowing(stmt.cond);
685
+ if (narrowed) {
686
+ const thenScope = new Map(this.scope);
687
+ thenScope.set(narrowed.name, { type: narrowed.type, mutable: narrowed.mutable });
688
+ const outerScope = this.scope;
689
+ this.scope = thenScope;
690
+ this.checkBlock(stmt.then);
691
+ this.scope = outerScope;
692
+ }
693
+ else {
694
+ this.checkBlock(stmt.then);
695
+ }
696
+ if (stmt.else_) {
697
+ this.checkBlock(stmt.else_);
698
+ }
699
+ }
700
+ getThenBranchNarrowing(cond) {
701
+ if (cond.kind !== 'is_check' || cond.expr.kind !== 'ident') {
702
+ return null;
703
+ }
704
+ const symbol = this.scope.get(cond.expr.name);
705
+ if (!symbol || symbol.type.kind !== 'entity') {
706
+ return null;
707
+ }
708
+ return {
709
+ name: cond.expr.name,
710
+ type: { kind: 'entity', entityType: cond.entityType },
711
+ mutable: symbol.mutable,
712
+ };
713
+ }
450
714
  inferType(expr, expectedType) {
451
715
  switch (expr.kind) {
452
716
  case 'int_lit':
@@ -473,13 +737,24 @@ class TypeChecker {
473
737
  }
474
738
  }
475
739
  return { kind: 'named', name: 'string' };
740
+ case 'f_string':
741
+ for (const part of expr.parts) {
742
+ if (part.kind === 'expr') {
743
+ this.checkExpr(part.expr);
744
+ }
745
+ }
746
+ return FORMAT_STRING_TYPE;
476
747
  case 'blockpos':
477
748
  return { kind: 'named', name: 'BlockPos' };
478
749
  case 'ident':
479
750
  return this.scope.get(expr.name)?.type ?? { kind: 'named', name: 'void' };
480
751
  case 'call': {
752
+ const builtin = BUILTIN_SIGNATURES[expr.fn];
753
+ if (builtin) {
754
+ return builtin.return;
755
+ }
481
756
  if (expr.fn === '__array_push') {
482
- return { kind: 'named', name: 'void' };
757
+ return VOID_TYPE;
483
758
  }
484
759
  if (expr.fn === '__array_pop') {
485
760
  const target = expr.args[0];
@@ -488,20 +763,28 @@ class TypeChecker {
488
763
  if (targetType?.kind === 'array')
489
764
  return targetType.elem;
490
765
  }
491
- return { kind: 'named', name: 'int' };
766
+ return INT_TYPE;
492
767
  }
493
768
  if (expr.fn === 'bossbar_get_value') {
494
- return { kind: 'named', name: 'int' };
769
+ return INT_TYPE;
495
770
  }
496
771
  if (expr.fn === 'random_sequence') {
497
- return { kind: 'named', name: 'void' };
772
+ return VOID_TYPE;
498
773
  }
499
774
  const varType = this.scope.get(expr.fn)?.type;
500
775
  if (varType?.kind === 'function_type') {
501
776
  return varType.return;
502
777
  }
778
+ const implMethod = this.resolveInstanceMethod(expr);
779
+ if (implMethod) {
780
+ return this.normalizeType(implMethod.returnType);
781
+ }
503
782
  const fn = this.functions.get(expr.fn);
504
- return fn?.returnType ?? { kind: 'named', name: 'int' };
783
+ return fn?.returnType ?? INT_TYPE;
784
+ }
785
+ case 'static_call': {
786
+ const method = this.implMethods.get(expr.type)?.get(expr.method);
787
+ return method ? this.normalizeType(method.returnType) : { kind: 'named', name: 'void' };
505
788
  }
506
789
  case 'invoke': {
507
790
  const calleeType = this.inferType(expr.callee);
@@ -532,6 +815,8 @@ class TypeChecker {
532
815
  return { kind: 'named', name: 'bool' };
533
816
  }
534
817
  return this.inferType(expr.left);
818
+ case 'is_check':
819
+ return { kind: 'named', name: 'bool' };
535
820
  case 'unary':
536
821
  if (expr.op === '!')
537
822
  return { kind: 'named', name: 'bool' };
@@ -541,6 +826,14 @@ class TypeChecker {
541
826
  return { kind: 'array', elem: this.inferType(expr.elements[0]) };
542
827
  }
543
828
  return { kind: 'array', elem: { kind: 'named', name: 'int' } };
829
+ case 'struct_lit':
830
+ if (expectedType) {
831
+ const normalized = this.normalizeType(expectedType);
832
+ if (normalized.kind === 'struct') {
833
+ return normalized;
834
+ }
835
+ }
836
+ return { kind: 'named', name: 'void' };
544
837
  case 'lambda':
545
838
  return this.inferLambdaType(expr, expectedType && this.normalizeType(expectedType).kind === 'function_type'
546
839
  ? this.normalizeType(expectedType)
@@ -569,6 +862,68 @@ class TypeChecker {
569
862
  }
570
863
  return { kind: 'function_type', params, return: returnType };
571
864
  }
865
+ // ---------------------------------------------------------------------------
866
+ // Entity Type Helpers
867
+ // ---------------------------------------------------------------------------
868
+ /** Infer entity type from a selector */
869
+ inferEntityTypeFromSelector(selector) {
870
+ // @a, @p, @r always return Player
871
+ if (selector.kind === '@a' || selector.kind === '@p' || selector.kind === '@r') {
872
+ return 'Player';
873
+ }
874
+ // @e or @s with type= filter
875
+ if (selector.filters?.type) {
876
+ const mcType = selector.filters.type.toLowerCase();
877
+ return MC_TYPE_TO_ENTITY[mcType] ?? 'entity';
878
+ }
879
+ // @s uses current context
880
+ if (selector.kind === '@s') {
881
+ return this.selfTypeStack[this.selfTypeStack.length - 1];
882
+ }
883
+ // Default to entity
884
+ return 'entity';
885
+ }
886
+ resolveInstanceMethod(expr) {
887
+ const receiver = expr.args[0];
888
+ if (!receiver) {
889
+ return null;
890
+ }
891
+ const receiverType = this.inferType(receiver);
892
+ if (receiverType.kind !== 'struct') {
893
+ return null;
894
+ }
895
+ const method = this.implMethods.get(receiverType.name)?.get(expr.fn);
896
+ if (!method || method.params[0]?.name !== 'self') {
897
+ return null;
898
+ }
899
+ return method;
900
+ }
901
+ /** Check if childType is a subtype of parentType */
902
+ isEntitySubtype(childType, parentType) {
903
+ if (childType === parentType)
904
+ return true;
905
+ let current = childType;
906
+ while (current !== null) {
907
+ if (current === parentType)
908
+ return true;
909
+ current = ENTITY_HIERARCHY[current];
910
+ }
911
+ return false;
912
+ }
913
+ /** Push a new self type context */
914
+ pushSelfType(entityType) {
915
+ this.selfTypeStack.push(entityType);
916
+ }
917
+ /** Pop self type context */
918
+ popSelfType() {
919
+ if (this.selfTypeStack.length > 1) {
920
+ this.selfTypeStack.pop();
921
+ }
922
+ }
923
+ /** Get current @s type */
924
+ getCurrentSelfType() {
925
+ return this.selfTypeStack[this.selfTypeStack.length - 1];
926
+ }
572
927
  typesMatch(expected, actual) {
573
928
  if (expected.kind !== actual.kind)
574
929
  return false;
@@ -592,6 +947,14 @@ class TypeChecker {
592
947
  expected.params.every((param, index) => this.typesMatch(param, actual.params[index])) &&
593
948
  this.typesMatch(expected.return, actual.return);
594
949
  }
950
+ // Entity type matching with subtype support
951
+ if (expected.kind === 'entity' && actual.kind === 'entity') {
952
+ return this.isEntitySubtype(actual.entityType, expected.entityType);
953
+ }
954
+ // Selector matches any entity type
955
+ if (expected.kind === 'selector' && actual.kind === 'entity') {
956
+ return true;
957
+ }
595
958
  return false;
596
959
  }
597
960
  typeToString(type) {
@@ -606,6 +969,12 @@ class TypeChecker {
606
969
  return type.name;
607
970
  case 'function_type':
608
971
  return `(${type.params.map(param => this.typeToString(param)).join(', ')}) -> ${this.typeToString(type.return)}`;
972
+ case 'entity':
973
+ return type.entityType;
974
+ case 'selector':
975
+ return 'selector';
976
+ default:
977
+ return 'unknown';
609
978
  }
610
979
  }
611
980
  normalizeType(type) {
@@ -622,6 +991,12 @@ class TypeChecker {
622
991
  if ((type.kind === 'struct' || type.kind === 'enum') && this.enums.has(type.name)) {
623
992
  return { kind: 'enum', name: type.name };
624
993
  }
994
+ if (type.kind === 'struct' && type.name in ENTITY_HIERARCHY) {
995
+ return { kind: 'entity', entityType: type.name };
996
+ }
997
+ if (type.kind === 'named' && type.name in ENTITY_HIERARCHY) {
998
+ return { kind: 'entity', entityType: type.name };
999
+ }
625
1000
  return type;
626
1001
  }
627
1002
  }