Repository navigation
for...of on Map obtained via local alias of class field yields incorrect keys / zero iterations #305
Copy link
Copy link
Closed
Labels
bugConfirmed defect or regressionConfirmed defect or regression
Description
What happened
as title
What you expected
it should works as usual
Minimal reproduction
// ============================================================
// BUG 2: for...of on Map obtained via local alias of class field
// yields incorrect keys / zero iterations
// ============================================================
// When a class-field Map<BrandedType, T> is assigned to a local variable
// and iterated, Perry uses a wrong iterator/key representation.
//
// Impact in @codehz/ecs:
// In CommandBuffer.execute():
// const entityCommands = this.entityCommands; // class field Map<EntityId, Command[]>
// for (const cmd of currentCommands) {
// entityCommands.set(cmd.entityId, [cmd]); // key appears as 0 not entity ID
// }
// for (const [entityId, commands] of entityCommands) { // yields 0 entries
// this.executeEntityCommands(entityId, commands); // never called!
// }
// Because executeEntityCommands is never called, world.sync() is a no-op,
// and world.has(child, relation) returns false.
class Example {
private map: Map<number, string> = new Map();
runViaLocal(key: number): void {
const m = this.map; // local alias – BUG triggers here
m.set(key, "hello");
const keys = Array.from(m.keys());
let iterCount = 0;
for (const [k, v] of m) {
iterCount++;
}
console.log("=== via local alias ===");
console.log(`map.size: ${m.size}`); // expected 1
console.log(`keys: ${JSON.stringify(keys)}`); // expected [1025]
console.log(`for..of count: ${iterCount}`); // expected 1
console.log(`bug (wrong keys): ${JSON.stringify(keys) !== "[1025]"}`);
console.log(`bug (no iters): ${iterCount !== 1}`);
this.map.clear();
}
runViaDirect(key: number): void {
this.map.set(key, "hello");
const keys = Array.from(this.map.keys());
let iterCount = 0;
for (const [k, v] of this.map) {
iterCount++;
}
console.log("\n=== via this.map directly ===");
console.log(`map.size: ${this.map.size}`);
console.log(`keys: ${JSON.stringify(keys)}`);
console.log(`for..of count: ${iterCount}`);
console.log(`bug (wrong keys): ${JSON.stringify(keys) !== "[1025]"}`);
console.log(`bug (no iters): ${iterCount !== 1}`);
this.map.clear();
}
}
const ex = new Example();
ex.runViaLocal(1025);
ex.runViaDirect(1025);Command you ran:
perry test.ts && ./testEnvironment
- Perry version: 0.5.390
- Host OS: ArchLinux
- Target: native
- Rust toolchain (if building from source): rustc 1.95.0 (59807616e 2026-04-14)
- Installed via: from source
Diagnostic output
Perry Doctor
Environment Checks
──────────────────
✓ perry version: 0.5.390
✓ update status: up to date
✓ clang (LLVM codegen): clang version 22.1.3
✓ system linker (cc): cc (GCC) 15.2.1 20260209
✓ runtime library: /home/codehz/Projects/perry/target/release/libperry_runtime.a
⚠ project config (perry.toml): not found - run: perry init
All critical checks passed with some warnings.
Anything else
found in my ecs's demo
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
bugConfirmed defect or regressionConfirmed defect or regression
Activity
fix: closes #304 + #305 — array.length=N truncate + class-field local…
proggeramlug commentedon Apr 30, 2026
Fixed in v0.5.397.
const m = this.map; for-of mnow iterates correctly because thethis.<field>shape is now resolved through theclass_field_typesregistry (originally landed in v0.5.388 for #302).The same fix transparently covers
const s = this.items(Set field) andconst a = this.xs(number[] field) — any local alias of a typed class field now inherits the correct generic / array element type so for-of and method dispatch take the right fast path.Regression test:
test-files/test_issue_305_local_alias_class_field_map.ts(4 cases — Map local alias, Map this.x direct comparison, Set local alias, Array local alias).fix(hir): closes #310 — traverse `export * as X from ...` namespace r…
codehz commentedon Apr 30, 2026
new repro in 0.5.415