Two defects, neither of which is a named method.
const R = class { *[Symbol.iterator]() { yield 1; yield 2; } };
console.log(Object.getOwnPropertyNames(R.prototype).join(","));
// node : constructor
// perry: constructor,@@iterator <-- a SYMBOL key leaking as a STRING key
class D { *[Symbol.iterator]() { yield 1; yield 2; } }
(D.prototype as any)[Symbol.iterator] = function* () { yield 70; yield 71; };
console.log([...new D()].join(","));
// node : 70,71
// perry: 1,2 <-- replacement ignored
An own override on the instance (inst[Symbol.iterator] = ...) does work, so it is the prototype write specifically.
Why this is not #9239
#9239 was closed on 2026-09-04 by 4153caea15 ("preserve fresh prototype replacements"). That commit fixes replacing a named method (C.prototype.m = ...) and its fixture test_gap_9239_fresh_instance_prototype_replacement.ts is exactly that shape. Neither defect above involves a named method, and both reproduce on main@12efed1222.
Entry point
crates/perry-hir/src/lower_decl/helpers.rs::is_special_lowered_well_known and the own-key registration beside it (the "@@iterator" spelling).
Untested hypothesis worth checking in the same pass: that predicate's "iterator" arm requires method.function.is_generator, so a non-generator [Symbol.iterator]() { return ...; } may not be exempted at all and would take the generic computed-member path.
Repro
secret-tests/cases/adversarial/recent/28_iterator_protocol_mutation.ts
Done when
Case 28 matches node.
Two defects, neither of which is a named method.
An own override on the instance (
inst[Symbol.iterator] = ...) does work, so it is the prototype write specifically.Why this is not #9239
#9239 was closed on 2026-09-04 by
4153caea15("preserve fresh prototype replacements"). That commit fixes replacing a named method (C.prototype.m = ...) and its fixturetest_gap_9239_fresh_instance_prototype_replacement.tsis exactly that shape. Neither defect above involves a named method, and both reproduce onmain@12efed1222.Entry point
crates/perry-hir/src/lower_decl/helpers.rs::is_special_lowered_well_knownand the own-key registration beside it (the"@@iterator"spelling).Untested hypothesis worth checking in the same pass: that predicate's
"iterator"arm requiresmethod.function.is_generator, so a non-generator[Symbol.iterator]() { return ...; }may not be exempted at all and would take the generic computed-member path.Repro
secret-tests/cases/adversarial/recent/28_iterator_protocol_mutation.tsDone when
Case 28 matches node.