redscript-mc 1.0.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (136) hide show
  1. package/.github/ISSUE_TEMPLATE/bug_report.yml +72 -0
  2. package/.github/ISSUE_TEMPLATE/feature_request.yml +57 -0
  3. package/.github/PULL_REQUEST_TEMPLATE.md +17 -25
  4. package/CHANGELOG.md +112 -0
  5. package/CONTRIBUTING.md +140 -0
  6. package/README.md +28 -19
  7. package/README.zh.md +28 -19
  8. package/dist/__tests__/cli.test.js +148 -10
  9. package/dist/__tests__/codegen.test.js +26 -1
  10. package/dist/__tests__/diagnostics.test.js +5 -5
  11. package/dist/__tests__/e2e.test.js +336 -17
  12. package/dist/__tests__/formatter.test.d.ts +1 -0
  13. package/dist/__tests__/formatter.test.js +40 -0
  14. package/dist/__tests__/lexer.test.js +12 -2
  15. package/dist/__tests__/lowering.test.js +200 -12
  16. package/dist/__tests__/mc-integration.test.js +370 -31
  17. package/dist/__tests__/mc-syntax.test.js +3 -3
  18. package/dist/__tests__/nbt.test.js +2 -2
  19. package/dist/__tests__/optimizer-advanced.test.js +5 -5
  20. package/dist/__tests__/parser.test.js +80 -0
  21. package/dist/__tests__/runtime.test.js +9 -9
  22. package/dist/__tests__/typechecker.test.js +158 -0
  23. package/dist/ast/types.d.ts +40 -3
  24. package/dist/cli.js +25 -7
  25. package/dist/codegen/mcfunction/index.d.ts +1 -1
  26. package/dist/codegen/mcfunction/index.js +38 -3
  27. package/dist/codegen/structure/index.js +32 -1
  28. package/dist/compile.d.ts +10 -0
  29. package/dist/compile.js +36 -5
  30. package/dist/events/types.d.ts +35 -0
  31. package/dist/events/types.js +59 -0
  32. package/dist/formatter/index.d.ts +1 -0
  33. package/dist/formatter/index.js +26 -0
  34. package/dist/index.js +3 -2
  35. package/dist/ir/builder.d.ts +2 -1
  36. package/dist/ir/types.d.ts +11 -2
  37. package/dist/ir/types.js +1 -1
  38. package/dist/lexer/index.d.ts +1 -1
  39. package/dist/lexer/index.js +2 -0
  40. package/dist/lowering/index.d.ts +34 -1
  41. package/dist/lowering/index.js +622 -23
  42. package/dist/mc-test/runner.d.ts +2 -2
  43. package/dist/mc-test/runner.js +3 -3
  44. package/dist/mc-test/setup.js +2 -2
  45. package/dist/parser/index.d.ts +4 -0
  46. package/dist/parser/index.js +153 -16
  47. package/dist/typechecker/index.d.ts +17 -0
  48. package/dist/typechecker/index.js +343 -17
  49. package/docs/COMPILATION_STATS.md +24 -24
  50. package/docs/ENTITY_TYPE_SYSTEM.md +242 -0
  51. package/docs/IMPLEMENTATION_GUIDE.md +1 -1
  52. package/docs/STRUCTURE_TARGET.md +1 -1
  53. package/editors/vscode/.vscodeignore +1 -0
  54. package/editors/vscode/CHANGELOG.md +9 -0
  55. package/editors/vscode/icons/mcrs.svg +7 -0
  56. package/editors/vscode/icons/redscript-icons.json +10 -0
  57. package/editors/vscode/out/extension.js +1295 -80
  58. package/editors/vscode/package-lock.json +2 -2
  59. package/editors/vscode/package.json +10 -3
  60. package/editors/vscode/src/hover.ts +55 -2
  61. package/editors/vscode/src/symbols.ts +42 -0
  62. package/package.json +1 -1
  63. package/src/__tests__/cli.test.ts +176 -10
  64. package/src/__tests__/codegen.test.ts +28 -1
  65. package/src/__tests__/diagnostics.test.ts +5 -5
  66. package/src/__tests__/e2e.test.ts +335 -17
  67. package/src/__tests__/fixtures/event-test.mcrs +13 -0
  68. package/src/__tests__/fixtures/impl-test.mcrs +46 -0
  69. package/src/__tests__/fixtures/interval-test.mcrs +11 -0
  70. package/src/__tests__/fixtures/is-check-test.mcrs +20 -0
  71. package/src/__tests__/fixtures/timeout-test.mcrs +7 -0
  72. package/src/__tests__/lexer.test.ts +14 -2
  73. package/src/__tests__/lowering.test.ts +226 -12
  74. package/src/__tests__/mc-integration.test.ts +421 -31
  75. package/src/__tests__/mc-syntax.test.ts +3 -3
  76. package/src/__tests__/nbt.test.ts +2 -2
  77. package/src/__tests__/optimizer-advanced.test.ts +5 -5
  78. package/src/__tests__/parser.test.ts +91 -5
  79. package/src/__tests__/runtime.test.ts +9 -9
  80. package/src/__tests__/typechecker.test.ts +171 -0
  81. package/src/ast/types.ts +44 -3
  82. package/src/cli.ts +10 -10
  83. package/src/codegen/mcfunction/index.ts +40 -3
  84. package/src/codegen/structure/index.ts +35 -1
  85. package/src/compile.ts +54 -6
  86. package/src/events/types.ts +69 -0
  87. package/src/examples/capture_the_flag.mcrs +208 -0
  88. package/src/examples/{counter.rs → counter.mcrs} +1 -1
  89. package/src/examples/hunger_games.mcrs +301 -0
  90. package/src/examples/new_features_demo.mcrs +193 -0
  91. package/src/examples/parkour_race.mcrs +233 -0
  92. package/src/examples/rpg.mcrs +13 -0
  93. package/src/examples/{shop.rs → shop.mcrs} +1 -1
  94. package/src/examples/{showcase_game.rs → showcase_game.mcrs} +3 -3
  95. package/src/examples/{turret.rs → turret.mcrs} +1 -1
  96. package/src/examples/zombie_survival.mcrs +314 -0
  97. package/src/index.ts +4 -3
  98. package/src/ir/builder.ts +3 -1
  99. package/src/ir/types.ts +12 -2
  100. package/src/lexer/index.ts +3 -1
  101. package/src/lowering/index.ts +684 -24
  102. package/src/mc-test/runner.ts +3 -3
  103. package/src/mc-test/setup.ts +2 -2
  104. package/src/parser/index.ts +170 -19
  105. package/src/stdlib/README.md +178 -140
  106. package/src/stdlib/bossbar.mcrs +68 -0
  107. package/src/stdlib/{cooldown.rs → cooldown.mcrs} +1 -1
  108. package/src/stdlib/effects.mcrs +64 -0
  109. package/src/stdlib/interactions.mcrs +195 -0
  110. package/src/stdlib/inventory.mcrs +38 -0
  111. package/src/stdlib/mobs.mcrs +99 -0
  112. package/src/stdlib/particles.mcrs +52 -0
  113. package/src/stdlib/sets.mcrs +20 -0
  114. package/src/stdlib/spawn.mcrs +41 -0
  115. package/src/stdlib/tags.mcrs +951 -0
  116. package/src/stdlib/teams.mcrs +68 -0
  117. package/src/stdlib/timer.mcrs +72 -0
  118. package/src/stdlib/world.mcrs +92 -0
  119. package/src/typechecker/index.ts +404 -18
  120. package/src/examples/rpg.rs +0 -13
  121. package/src/stdlib/mobs.rs +0 -99
  122. package/src/stdlib/timer.rs +0 -51
  123. /package/src/examples/{arena.rs → arena.mcrs} +0 -0
  124. /package/src/examples/{pvp_arena.rs → pvp_arena.mcrs} +0 -0
  125. /package/src/examples/{quiz.rs → quiz.mcrs} +0 -0
  126. /package/src/examples/{stdlib_demo.rs → stdlib_demo.mcrs} +0 -0
  127. /package/src/examples/{world_manager.rs → world_manager.mcrs} +0 -0
  128. /package/src/stdlib/{combat.rs → combat.mcrs} +0 -0
  129. /package/src/stdlib/{math.rs → math.mcrs} +0 -0
  130. /package/src/stdlib/{player.rs → player.mcrs} +0 -0
  131. /package/src/stdlib/{strings.rs → strings.mcrs} +0 -0
  132. /package/src/templates/{combat.rs → combat.mcrs} +0 -0
  133. /package/src/templates/{economy.rs → economy.mcrs} +0 -0
  134. /package/src/templates/{mini-game-framework.rs → mini-game-framework.mcrs} +0 -0
  135. /package/src/templates/{quest.rs → quest.mcrs} +0 -0
  136. /package/src/test_programs/{zombie_game.rs → zombie_game.mcrs} +0 -0
@@ -8,18 +8,88 @@
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 BUILTIN_SIGNATURES = {
65
+ setTimeout: {
66
+ params: [INT_TYPE, { kind: 'function_type', params: [], return: VOID_TYPE }],
67
+ return: VOID_TYPE,
68
+ },
69
+ setInterval: {
70
+ params: [INT_TYPE, { kind: 'function_type', params: [], return: VOID_TYPE }],
71
+ return: INT_TYPE,
72
+ },
73
+ clearInterval: {
74
+ params: [INT_TYPE],
75
+ return: VOID_TYPE,
76
+ },
77
+ };
11
78
  // ---------------------------------------------------------------------------
12
79
  // Type Checker
13
80
  // ---------------------------------------------------------------------------
14
81
  class TypeChecker {
15
82
  constructor(source, filePath) {
16
83
  this.functions = new Map();
84
+ this.implMethods = new Map();
17
85
  this.structs = new Map();
18
86
  this.enums = new Map();
19
87
  this.consts = new Map();
20
88
  this.currentFn = null;
21
89
  this.currentReturnType = null;
22
90
  this.scope = new Map();
91
+ // Stack for tracking @s type in different contexts
92
+ this.selfTypeStack = ['entity'];
23
93
  this.collector = new diagnostics_1.DiagnosticCollector(source, filePath);
24
94
  }
25
95
  getNodeLocation(node) {
@@ -41,6 +111,26 @@ class TypeChecker {
41
111
  for (const fn of program.declarations) {
42
112
  this.functions.set(fn.name, fn);
43
113
  }
114
+ for (const implBlock of program.implBlocks ?? []) {
115
+ let methods = this.implMethods.get(implBlock.typeName);
116
+ if (!methods) {
117
+ methods = new Map();
118
+ this.implMethods.set(implBlock.typeName, methods);
119
+ }
120
+ for (const method of implBlock.methods) {
121
+ const selfIndex = method.params.findIndex(param => param.name === 'self');
122
+ if (selfIndex > 0) {
123
+ this.report(`Method '${method.name}' must declare 'self' as the first parameter`, method.params[selfIndex]);
124
+ }
125
+ if (selfIndex === 0) {
126
+ const selfType = this.normalizeType(method.params[0].type);
127
+ if (selfType.kind !== 'struct' || selfType.name !== implBlock.typeName) {
128
+ this.report(`Method '${method.name}' has invalid 'self' type`, method.params[0]);
129
+ }
130
+ }
131
+ methods.set(method.name, method);
132
+ }
133
+ }
44
134
  for (const struct of program.structs ?? []) {
45
135
  const fields = new Map();
46
136
  for (const field of struct.fields) {
@@ -67,6 +157,11 @@ class TypeChecker {
67
157
  for (const fn of program.declarations) {
68
158
  this.checkFunction(fn);
69
159
  }
160
+ for (const implBlock of program.implBlocks ?? []) {
161
+ for (const method of implBlock.methods) {
162
+ this.checkFunction(method);
163
+ }
164
+ }
70
165
  return this.collector.getErrors();
71
166
  }
72
167
  checkFunction(fn) {
@@ -74,6 +169,7 @@ class TypeChecker {
74
169
  this.currentReturnType = this.normalizeType(fn.returnType);
75
170
  this.scope = new Map();
76
171
  let seenDefault = false;
172
+ this.checkFunctionDecorators(fn);
77
173
  for (const [name, type] of this.consts.entries()) {
78
174
  this.scope.set(name, { type, mutable: false });
79
175
  }
@@ -98,6 +194,37 @@ class TypeChecker {
98
194
  this.currentFn = null;
99
195
  this.currentReturnType = null;
100
196
  }
197
+ checkFunctionDecorators(fn) {
198
+ const eventDecorators = fn.decorators.filter(decorator => decorator.name === 'on');
199
+ if (eventDecorators.length === 0) {
200
+ return;
201
+ }
202
+ if (eventDecorators.length > 1) {
203
+ this.report(`Function '${fn.name}' cannot have multiple @on decorators`, fn);
204
+ return;
205
+ }
206
+ const eventType = eventDecorators[0].args?.eventType;
207
+ if (!eventType) {
208
+ this.report(`Function '${fn.name}' is missing an event type in @on(...)`, fn);
209
+ return;
210
+ }
211
+ if (!(0, types_1.isEventTypeName)(eventType)) {
212
+ this.report(`Unknown event type '${eventType}'`, fn);
213
+ return;
214
+ }
215
+ const expectedParams = (0, types_1.getEventParamSpecs)(eventType);
216
+ if (fn.params.length !== expectedParams.length) {
217
+ this.report(`Event handler '${fn.name}' for ${eventType} must declare ${expectedParams.length} parameter(s), got ${fn.params.length}`, fn);
218
+ return;
219
+ }
220
+ for (let i = 0; i < expectedParams.length; i++) {
221
+ const actual = this.normalizeType(fn.params[i].type);
222
+ const expected = this.normalizeType(expectedParams[i].type);
223
+ if (!this.typesMatch(expected, actual)) {
224
+ this.report(`Event handler '${fn.name}' parameter ${i + 1} must be ${this.typeToString(expected)}, got ${this.typeToString(actual)}`, fn.params[i]);
225
+ }
226
+ }
227
+ }
101
228
  checkBlock(stmts) {
102
229
  for (const stmt of stmts) {
103
230
  this.checkStmt(stmt);
@@ -113,9 +240,7 @@ class TypeChecker {
113
240
  break;
114
241
  case 'if':
115
242
  this.checkExpr(stmt.cond);
116
- this.checkBlock(stmt.then);
117
- if (stmt.else_)
118
- this.checkBlock(stmt.else_);
243
+ this.checkIfBranches(stmt);
119
244
  break;
120
245
  case 'while':
121
246
  this.checkExpr(stmt.cond);
@@ -131,7 +256,16 @@ class TypeChecker {
131
256
  case 'foreach':
132
257
  this.checkExpr(stmt.iterable);
133
258
  if (stmt.iterable.kind === 'selector') {
134
- this.scope.set(stmt.binding, { type: { kind: 'named', name: 'void' }, mutable: true }); // Entity marker
259
+ // Infer entity type from selector (access .sel for the EntitySelector)
260
+ const entityType = this.inferEntityTypeFromSelector(stmt.iterable.sel);
261
+ this.scope.set(stmt.binding, {
262
+ type: { kind: 'entity', entityType },
263
+ mutable: false // Entity bindings are not reassignable
264
+ });
265
+ // Push self type context for @s inside the loop
266
+ this.pushSelfType(entityType);
267
+ this.checkBlock(stmt.body);
268
+ this.popSelfType();
135
269
  }
136
270
  else {
137
271
  const iterableType = this.inferType(stmt.iterable);
@@ -141,8 +275,8 @@ class TypeChecker {
141
275
  else {
142
276
  this.scope.set(stmt.binding, { type: { kind: 'named', name: 'void' }, mutable: true });
143
277
  }
278
+ this.checkBlock(stmt.body);
144
279
  }
145
- this.checkBlock(stmt.body);
146
280
  break;
147
281
  case 'match':
148
282
  this.checkExpr(stmt.expr);
@@ -156,15 +290,41 @@ class TypeChecker {
156
290
  this.checkBlock(arm.body);
157
291
  }
158
292
  break;
159
- case 'as_block':
293
+ case 'as_block': {
294
+ // as block changes @s to the selector's entity type
295
+ const entityType = this.inferEntityTypeFromSelector(stmt.selector);
296
+ this.pushSelfType(entityType);
297
+ this.checkBlock(stmt.body);
298
+ this.popSelfType();
299
+ break;
300
+ }
160
301
  case 'at_block':
302
+ // at block doesn't change @s type, only position
161
303
  this.checkBlock(stmt.body);
162
304
  break;
163
- case 'as_at':
305
+ case 'as_at': {
306
+ // as @x at @y - @s becomes the as selector's type
307
+ const entityType = this.inferEntityTypeFromSelector(stmt.as_sel);
308
+ this.pushSelfType(entityType);
164
309
  this.checkBlock(stmt.body);
310
+ this.popSelfType();
165
311
  break;
312
+ }
166
313
  case 'execute':
314
+ // execute with subcommands - check for 'as' subcommands
315
+ for (const sub of stmt.subcommands) {
316
+ if (sub.kind === 'as' && sub.selector) {
317
+ const entityType = this.inferEntityTypeFromSelector(sub.selector);
318
+ this.pushSelfType(entityType);
319
+ }
320
+ }
167
321
  this.checkBlock(stmt.body);
322
+ // Pop for each 'as' subcommand
323
+ for (const sub of stmt.subcommands) {
324
+ if (sub.kind === 'as') {
325
+ this.popSelfType();
326
+ }
327
+ }
168
328
  break;
169
329
  case 'expr':
170
330
  this.checkExpr(stmt.expr);
@@ -224,10 +384,21 @@ class TypeChecker {
224
384
  case 'member':
225
385
  this.checkMemberExpr(expr);
226
386
  break;
387
+ case 'static_call':
388
+ this.checkStaticCallExpr(expr);
389
+ break;
227
390
  case 'binary':
228
391
  this.checkExpr(expr.left);
229
392
  this.checkExpr(expr.right);
230
393
  break;
394
+ case 'is_check': {
395
+ this.checkExpr(expr.expr);
396
+ const checkedType = this.inferType(expr.expr);
397
+ if (checkedType.kind !== 'entity') {
398
+ this.report(`'is' checks require an entity expression, got ${this.typeToString(checkedType)}`, expr.expr);
399
+ }
400
+ break;
401
+ }
231
402
  case 'unary':
232
403
  this.checkExpr(expr.operand);
233
404
  break;
@@ -274,11 +445,6 @@ class TypeChecker {
274
445
  break;
275
446
  case 'blockpos':
276
447
  break;
277
- case 'static_call':
278
- for (const arg of expr.args) {
279
- this.checkExpr(arg);
280
- }
281
- break;
282
448
  // Literals don't need checking
283
449
  case 'int_lit':
284
450
  case 'float_lit':
@@ -298,6 +464,11 @@ class TypeChecker {
298
464
  if (expr.fn === 'tp' || expr.fn === 'tp_to') {
299
465
  this.checkTpCall(expr);
300
466
  }
467
+ const builtin = BUILTIN_SIGNATURES[expr.fn];
468
+ if (builtin) {
469
+ this.checkFunctionCallArgs(expr.args, builtin.params, expr.fn, expr);
470
+ return;
471
+ }
301
472
  // Check if function exists and arg count matches
302
473
  const fn = this.functions.get(expr.fn);
303
474
  if (fn) {
@@ -325,6 +496,11 @@ class TypeChecker {
325
496
  this.checkFunctionCallArgs(expr.args, varType.params, expr.fn, expr);
326
497
  return;
327
498
  }
499
+ const implMethod = this.resolveInstanceMethod(expr);
500
+ if (implMethod) {
501
+ this.checkFunctionCallArgs(expr.args, implMethod.params.map(param => this.normalizeType(param.type)), implMethod.name, expr);
502
+ return;
503
+ }
328
504
  for (const arg of expr.args) {
329
505
  this.checkExpr(arg);
330
506
  }
@@ -412,6 +588,21 @@ class TypeChecker {
412
588
  }
413
589
  }
414
590
  }
591
+ checkStaticCallExpr(expr) {
592
+ const method = this.implMethods.get(expr.type)?.get(expr.method);
593
+ if (!method) {
594
+ this.report(`Type '${expr.type}' has no static method '${expr.method}'`, expr);
595
+ for (const arg of expr.args) {
596
+ this.checkExpr(arg);
597
+ }
598
+ return;
599
+ }
600
+ if (method.params[0]?.name === 'self') {
601
+ this.report(`Method '${expr.type}::${expr.method}' is an instance method`, expr);
602
+ return;
603
+ }
604
+ this.checkFunctionCallArgs(expr.args, method.params.map(param => this.normalizeType(param.type)), `${expr.type}::${expr.method}`, expr);
605
+ }
415
606
  checkLambdaExpr(expr, expectedType) {
416
607
  const normalizedExpected = expectedType ? this.normalizeType(expectedType) : undefined;
417
608
  const expectedFnType = normalizedExpected?.kind === 'function_type' ? normalizedExpected : undefined;
@@ -447,6 +638,37 @@ class TypeChecker {
447
638
  this.scope = outerScope;
448
639
  this.currentReturnType = outerReturnType;
449
640
  }
641
+ checkIfBranches(stmt) {
642
+ const narrowed = this.getThenBranchNarrowing(stmt.cond);
643
+ if (narrowed) {
644
+ const thenScope = new Map(this.scope);
645
+ thenScope.set(narrowed.name, { type: narrowed.type, mutable: narrowed.mutable });
646
+ const outerScope = this.scope;
647
+ this.scope = thenScope;
648
+ this.checkBlock(stmt.then);
649
+ this.scope = outerScope;
650
+ }
651
+ else {
652
+ this.checkBlock(stmt.then);
653
+ }
654
+ if (stmt.else_) {
655
+ this.checkBlock(stmt.else_);
656
+ }
657
+ }
658
+ getThenBranchNarrowing(cond) {
659
+ if (cond.kind !== 'is_check' || cond.expr.kind !== 'ident') {
660
+ return null;
661
+ }
662
+ const symbol = this.scope.get(cond.expr.name);
663
+ if (!symbol || symbol.type.kind !== 'entity') {
664
+ return null;
665
+ }
666
+ return {
667
+ name: cond.expr.name,
668
+ type: { kind: 'entity', entityType: cond.entityType },
669
+ mutable: symbol.mutable,
670
+ };
671
+ }
450
672
  inferType(expr, expectedType) {
451
673
  switch (expr.kind) {
452
674
  case 'int_lit':
@@ -478,8 +700,12 @@ class TypeChecker {
478
700
  case 'ident':
479
701
  return this.scope.get(expr.name)?.type ?? { kind: 'named', name: 'void' };
480
702
  case 'call': {
703
+ const builtin = BUILTIN_SIGNATURES[expr.fn];
704
+ if (builtin) {
705
+ return builtin.return;
706
+ }
481
707
  if (expr.fn === '__array_push') {
482
- return { kind: 'named', name: 'void' };
708
+ return VOID_TYPE;
483
709
  }
484
710
  if (expr.fn === '__array_pop') {
485
711
  const target = expr.args[0];
@@ -488,20 +714,28 @@ class TypeChecker {
488
714
  if (targetType?.kind === 'array')
489
715
  return targetType.elem;
490
716
  }
491
- return { kind: 'named', name: 'int' };
717
+ return INT_TYPE;
492
718
  }
493
719
  if (expr.fn === 'bossbar_get_value') {
494
- return { kind: 'named', name: 'int' };
720
+ return INT_TYPE;
495
721
  }
496
722
  if (expr.fn === 'random_sequence') {
497
- return { kind: 'named', name: 'void' };
723
+ return VOID_TYPE;
498
724
  }
499
725
  const varType = this.scope.get(expr.fn)?.type;
500
726
  if (varType?.kind === 'function_type') {
501
727
  return varType.return;
502
728
  }
729
+ const implMethod = this.resolveInstanceMethod(expr);
730
+ if (implMethod) {
731
+ return this.normalizeType(implMethod.returnType);
732
+ }
503
733
  const fn = this.functions.get(expr.fn);
504
- return fn?.returnType ?? { kind: 'named', name: 'int' };
734
+ return fn?.returnType ?? INT_TYPE;
735
+ }
736
+ case 'static_call': {
737
+ const method = this.implMethods.get(expr.type)?.get(expr.method);
738
+ return method ? this.normalizeType(method.returnType) : { kind: 'named', name: 'void' };
505
739
  }
506
740
  case 'invoke': {
507
741
  const calleeType = this.inferType(expr.callee);
@@ -532,6 +766,8 @@ class TypeChecker {
532
766
  return { kind: 'named', name: 'bool' };
533
767
  }
534
768
  return this.inferType(expr.left);
769
+ case 'is_check':
770
+ return { kind: 'named', name: 'bool' };
535
771
  case 'unary':
536
772
  if (expr.op === '!')
537
773
  return { kind: 'named', name: 'bool' };
@@ -541,6 +777,14 @@ class TypeChecker {
541
777
  return { kind: 'array', elem: this.inferType(expr.elements[0]) };
542
778
  }
543
779
  return { kind: 'array', elem: { kind: 'named', name: 'int' } };
780
+ case 'struct_lit':
781
+ if (expectedType) {
782
+ const normalized = this.normalizeType(expectedType);
783
+ if (normalized.kind === 'struct') {
784
+ return normalized;
785
+ }
786
+ }
787
+ return { kind: 'named', name: 'void' };
544
788
  case 'lambda':
545
789
  return this.inferLambdaType(expr, expectedType && this.normalizeType(expectedType).kind === 'function_type'
546
790
  ? this.normalizeType(expectedType)
@@ -569,6 +813,68 @@ class TypeChecker {
569
813
  }
570
814
  return { kind: 'function_type', params, return: returnType };
571
815
  }
816
+ // ---------------------------------------------------------------------------
817
+ // Entity Type Helpers
818
+ // ---------------------------------------------------------------------------
819
+ /** Infer entity type from a selector */
820
+ inferEntityTypeFromSelector(selector) {
821
+ // @a, @p, @r always return Player
822
+ if (selector.kind === '@a' || selector.kind === '@p' || selector.kind === '@r') {
823
+ return 'Player';
824
+ }
825
+ // @e or @s with type= filter
826
+ if (selector.filters?.type) {
827
+ const mcType = selector.filters.type.toLowerCase();
828
+ return MC_TYPE_TO_ENTITY[mcType] ?? 'entity';
829
+ }
830
+ // @s uses current context
831
+ if (selector.kind === '@s') {
832
+ return this.selfTypeStack[this.selfTypeStack.length - 1];
833
+ }
834
+ // Default to entity
835
+ return 'entity';
836
+ }
837
+ resolveInstanceMethod(expr) {
838
+ const receiver = expr.args[0];
839
+ if (!receiver) {
840
+ return null;
841
+ }
842
+ const receiverType = this.inferType(receiver);
843
+ if (receiverType.kind !== 'struct') {
844
+ return null;
845
+ }
846
+ const method = this.implMethods.get(receiverType.name)?.get(expr.fn);
847
+ if (!method || method.params[0]?.name !== 'self') {
848
+ return null;
849
+ }
850
+ return method;
851
+ }
852
+ /** Check if childType is a subtype of parentType */
853
+ isEntitySubtype(childType, parentType) {
854
+ if (childType === parentType)
855
+ return true;
856
+ let current = childType;
857
+ while (current !== null) {
858
+ if (current === parentType)
859
+ return true;
860
+ current = ENTITY_HIERARCHY[current];
861
+ }
862
+ return false;
863
+ }
864
+ /** Push a new self type context */
865
+ pushSelfType(entityType) {
866
+ this.selfTypeStack.push(entityType);
867
+ }
868
+ /** Pop self type context */
869
+ popSelfType() {
870
+ if (this.selfTypeStack.length > 1) {
871
+ this.selfTypeStack.pop();
872
+ }
873
+ }
874
+ /** Get current @s type */
875
+ getCurrentSelfType() {
876
+ return this.selfTypeStack[this.selfTypeStack.length - 1];
877
+ }
572
878
  typesMatch(expected, actual) {
573
879
  if (expected.kind !== actual.kind)
574
880
  return false;
@@ -592,6 +898,14 @@ class TypeChecker {
592
898
  expected.params.every((param, index) => this.typesMatch(param, actual.params[index])) &&
593
899
  this.typesMatch(expected.return, actual.return);
594
900
  }
901
+ // Entity type matching with subtype support
902
+ if (expected.kind === 'entity' && actual.kind === 'entity') {
903
+ return this.isEntitySubtype(actual.entityType, expected.entityType);
904
+ }
905
+ // Selector matches any entity type
906
+ if (expected.kind === 'selector' && actual.kind === 'entity') {
907
+ return true;
908
+ }
595
909
  return false;
596
910
  }
597
911
  typeToString(type) {
@@ -606,6 +920,12 @@ class TypeChecker {
606
920
  return type.name;
607
921
  case 'function_type':
608
922
  return `(${type.params.map(param => this.typeToString(param)).join(', ')}) -> ${this.typeToString(type.return)}`;
923
+ case 'entity':
924
+ return type.entityType;
925
+ case 'selector':
926
+ return 'selector';
927
+ default:
928
+ return 'unknown';
609
929
  }
610
930
  }
611
931
  normalizeType(type) {
@@ -622,6 +942,12 @@ class TypeChecker {
622
942
  if ((type.kind === 'struct' || type.kind === 'enum') && this.enums.has(type.name)) {
623
943
  return { kind: 'enum', name: type.name };
624
944
  }
945
+ if (type.kind === 'struct' && type.name in ENTITY_HIERARCHY) {
946
+ return { kind: 'entity', entityType: type.name };
947
+ }
948
+ if (type.kind === 'named' && type.name in ENTITY_HIERARCHY) {
949
+ return { kind: 'entity', entityType: type.name };
950
+ }
625
951
  return type;
626
952
  }
627
953
  }
@@ -3,8 +3,8 @@
3
3
  Generated on 2026-03-12 with:
4
4
 
5
5
  ```bash
6
- for example in src/examples/*.rs; do
7
- name=$(basename "$example" .rs)
6
+ for example in src/examples/*.mcrs; do
7
+ name=$(basename "$example" .mcrs)
8
8
  TS_NODE_TRANSPILE_ONLY=1 npx ts-node src/cli.ts compile "$example" --stats -o /tmp/eval/$name 2>&1
9
9
  done
10
10
  ```
@@ -13,20 +13,20 @@ done
13
13
 
14
14
  | Example | Functions | Commands Before | Commands After | Savings |
15
15
  |:--|--:|--:|--:|--:|
16
- | `arena.rs` | 4 | 208 | 52 | 75% |
17
- | `counter.rs` | 1 | 76 | 19 | 75% |
18
- | `pvp_arena.rs` | 15 | 780 | 195 | 75% |
19
- | `quiz.rs` | 7 | 564 | 141 | 75% |
20
- | `rpg.rs` | 10 | 548 | 137 | 75% |
21
- | `shop.rs` | 2 | 160 | 40 | 75% |
22
- | `showcase_game.rs` | 89 | 3360 | 840 | 75% |
23
- | `stdlib_demo.rs` | 15 | 1104 | 276 | 75% |
24
- | `turret.rs` | 5 | 104 | 26 | 75% |
25
- | `world_manager.rs` | 3 | 80 | 20 | 75% |
16
+ | `arena.mcrs` | 4 | 208 | 52 | 75% |
17
+ | `counter.mcrs` | 1 | 76 | 19 | 75% |
18
+ | `pvp_arena.mcrs` | 15 | 780 | 195 | 75% |
19
+ | `quiz.mcrs` | 7 | 564 | 141 | 75% |
20
+ | `rpg.mcrs` | 10 | 548 | 137 | 75% |
21
+ | `shop.mcrs` | 2 | 160 | 40 | 75% |
22
+ | `showcase_game.mcrs` | 89 | 3360 | 840 | 75% |
23
+ | `stdlib_demo.mcrs` | 15 | 1104 | 276 | 75% |
24
+ | `turret.mcrs` | 5 | 104 | 26 | 75% |
25
+ | `world_manager.mcrs` | 3 | 80 | 20 | 75% |
26
26
 
27
27
  ## Per-example Optimizer Stats
28
28
 
29
- ### `arena.rs`
29
+ ### `arena.mcrs`
30
30
 
31
31
  - Functions: `4`
32
32
  - Commands: `208 -> 52`
@@ -37,7 +37,7 @@ done
37
37
  - Dead code removed: `2`
38
38
  - Constant folds: `0`
39
39
 
40
- ### `counter.rs`
40
+ ### `counter.mcrs`
41
41
 
42
42
  - Functions: `1`
43
43
  - Commands: `76 -> 19`
@@ -48,7 +48,7 @@ done
48
48
  - Dead code removed: `0`
49
49
  - Constant folds: `0`
50
50
 
51
- ### `pvp_arena.rs`
51
+ ### `pvp_arena.mcrs`
52
52
 
53
53
  - Functions: `15`
54
54
  - Commands: `780 -> 195`
@@ -59,7 +59,7 @@ done
59
59
  - Dead code removed: `4`
60
60
  - Constant folds: `0`
61
61
 
62
- ### `quiz.rs`
62
+ ### `quiz.mcrs`
63
63
 
64
64
  - Functions: `7`
65
65
  - Commands: `564 -> 141`
@@ -70,7 +70,7 @@ done
70
70
  - Dead code removed: `0`
71
71
  - Constant folds: `0`
72
72
 
73
- ### `rpg.rs`
73
+ ### `rpg.mcrs`
74
74
 
75
75
  - Functions: `10`
76
76
  - Commands: `548 -> 137`
@@ -81,7 +81,7 @@ done
81
81
  - Dead code removed: `3`
82
82
  - Constant folds: `1`
83
83
 
84
- ### `shop.rs`
84
+ ### `shop.mcrs`
85
85
 
86
86
  - Functions: `2`
87
87
  - Commands: `160 -> 40`
@@ -92,7 +92,7 @@ done
92
92
  - Dead code removed: `0`
93
93
  - Constant folds: `0`
94
94
 
95
- ### `showcase_game.rs`
95
+ ### `showcase_game.mcrs`
96
96
 
97
97
  - Functions: `89`
98
98
  - Commands: `3360 -> 840`
@@ -103,7 +103,7 @@ done
103
103
  - Dead code removed: `20`
104
104
  - Constant folds: `1`
105
105
 
106
- ### `stdlib_demo.rs`
106
+ ### `stdlib_demo.mcrs`
107
107
 
108
108
  - Functions: `15`
109
109
  - Commands: `1104 -> 276`
@@ -114,7 +114,7 @@ done
114
114
  - Dead code removed: `11`
115
115
  - Constant folds: `0`
116
116
 
117
- ### `turret.rs`
117
+ ### `turret.mcrs`
118
118
 
119
119
  - Functions: `5`
120
120
  - Commands: `104 -> 26`
@@ -125,7 +125,7 @@ done
125
125
  - Dead code removed: `0`
126
126
  - Constant folds: `0`
127
127
 
128
- ### `world_manager.rs`
128
+ ### `world_manager.mcrs`
129
129
 
130
130
  - Functions: `3`
131
131
  - Commands: `80 -> 20`
@@ -138,5 +138,5 @@ done
138
138
 
139
139
  ## Warnings Observed During Compilation
140
140
 
141
- - `showcase_game.rs`: warnings inherited from imported stdlib helpers using quoted selectors in `player.rs`, plus one auto-qualification warning for unnamespaced `zombie`.
142
- - `turret.rs`: auto-qualification warnings for unnamespaced `armor_stand` and `zombie`.
141
+ - `showcase_game.mcrs`: warnings inherited from imported stdlib helpers using quoted selectors in `player.mcrs`, plus one auto-qualification warning for unnamespaced `zombie`.
142
+ - `turret.mcrs`: auto-qualification warnings for unnamespaced `armor_stand` and `zombie`.