You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Two specializations of one generic class are distinct constructor objects, and an instance's .constructor is not the class the user wrote.
Repro
classGen<T>{v: T|undefined;}consta=newGen<number>();constb=newGen<string>();constc=newGen();// never specializedconsole.log(a.constructor===Gen,b.constructor===Gen,c.constructor===Gen);console.log(a.constructor===b.constructor);console.log(Object.getPrototypeOf(a)===Gen.prototype,Object.getPrototypeOf(a)===Object.getPrototypeOf(b));
new Gen<number>() is monomorphized into a separate class Gen$num with its own class id (monomorph::mangle::generate_specialized_name), and the instance carries that id. TypeScript erases type arguments, so at runtime there is exactly one Gen — the specializations are an implementation detail that is leaking through every id-keyed identity surface.
constructor / prototype IDENTITY — this issue. Not addressed by either, because both fixed a lookup (a chain walk, a name table) rather than the object identity itself.
Worth noting that #7756 makes this one harder to see, not easier: after it, a.constructor.name === b.constructor.name === "Gen" while a.constructor !== b.constructor. Two things that print identically and compare unequal is a worse failure mode for a user to debug than an obviously-wrong name, so this is the natural follow-up to schedule rather than leave implicit.
Already inconsistent today
Object.getPrototypeOf(a) === Gen.prototype is true while a.constructor === Gen is false — so the prototype edge and the constructor edge already disagree with each other about whether the specialization is Gen. Whatever the fix is, that pair should come out consistent.
Shape of a fix (unverified)
The specialization exists to monomorphize method bodies; nothing about that requires a distinct constructor object or prototype. Candidates, in rough order of blast radius:
Stop specializing classes whose type arguments do not change any lowering decision (the erased-generic majority), so no second class exists to diverge. driver.rs already declines to specialize when a type argument is still an unresolved type var (the rxjs Observable<R> case) — this would extend that judgment.
Option 2 also removes the code-size cost of specializing classes that monomorphization does not benefit.
Not urgent
Every shape here is reflective/identity code, not a miscompile: instanceof, field access, method dispatch and .name are all correct. Filing so the last face of the leak is tracked rather than rediscovered.
Two specializations of one generic class are distinct constructor objects, and an instance's
.constructoris not the class the user wrote.Repro
Why
new Gen<number>()is monomorphized into a separate classGen$numwith its own class id (monomorph::mangle::generate_specialized_name), and the instance carries that id. TypeScript erases type arguments, so at runtime there is exactly oneGen— the specializations are an implementation detail that is leaking through every id-keyed identity surface.This is the third face of the same leak:
instanceof— fixed ininstanceofa Map/Set SUBCLASS is false (m instanceof MyMap); only the native base edge survives #7575 / PR fix(instanceof): #7575 — a monomorphized generic class is an instance of the generic it came from #7631, by recordingClass::specialized_fromand teaching the chain walk a specialization→generic edge.constructor.name— fixed in constructor.name of a generic-class instance reports the mangled specialization (Gen$num), not Gen #7632 / PR fix(monomorph): a specialized generic class reports the generic's name, not Gen$num (#7632) #7756, by registering the origin's display name against the specialization's class id.Worth noting that #7756 makes this one harder to see, not easier: after it,
a.constructor.name === b.constructor.name === "Gen"whilea.constructor !== b.constructor. Two things that print identically and compare unequal is a worse failure mode for a user to debug than an obviously-wrong name, so this is the natural follow-up to schedule rather than leave implicit.Already inconsistent today
Object.getPrototypeOf(a) === Gen.prototypeistruewhilea.constructor === Genisfalse— so the prototype edge and the constructor edge already disagree with each other about whether the specialization isGen. Whatever the fix is, that pair should come out consistent.Shape of a fix (unverified)
The specialization exists to monomorphize method bodies; nothing about that requires a distinct constructor object or prototype. Candidates, in rough order of blast radius:
specialized_fromas an identity alias for the reflective surfaces, the wayinstanceofa Map/Set SUBCLASS is false (m instanceof MyMap); only the native base edge survives #7575 already treats it forinstanceof.driver.rsalready declines to specialize when a type argument is still an unresolved type var (the rxjsObservable<R>case) — this would extend that judgment.Option 2 also removes the code-size cost of specializing classes that monomorphization does not benefit.
Not urgent
Every shape here is reflective/identity code, not a miscompile:
instanceof, field access, method dispatch and.nameare all correct. Filing so the last face of the leak is tracked rather than rediscovered.