Skip to content

single-field generic class with numeric type arg is scalar-replaced to its raw-f64 field — method call on the instance throws (number).method #6040

Closed

Description

@proggeramlug

A single-field generic class monomorphized with a numeric type argument is scalar-replaced down to its raw-f64 field, so the instance itself becomes the field value — and any method call on it throws.

Repro

class SimpleContainer<T> {
    private _value: T;
    constructor(initial: T) { this._value = initial; }
    get(): T { return this._value; }
    set(value: T): void { this._value = value; }
}
const c = new SimpleContainer<number>(0);
c.set(42);
console.log(c.get());          // node: 42

Perry: TypeError: (number).get is not a function.

Mechanism

  • The class monomorphizes to SimpleContainer$num with a raw_f64 typed shape (@perry_typed_shape_raw_f64_mask_*) because its single field _value: T = number.
  • Scalar replacement then collapses the instance to that single raw-f64 field, so the local c holds the number (0/42), not an object pointer. c.get() dispatches on a number → (number).get is not a function.

Scope / not-affected

Fixed independently (generic-class method dispatch was also resolving to the number fast path when the base name wasn't in ctx.classes after monomorphization — receiver_class_name now tries the specialized base$mangled name):

  • SimpleContainer<string> — works (hello).
  • Multi<number> with a second field — works (7 99): scalar replacement only collapses the SINGLE-field shape.

So this issue is specifically scalar replacement eligibility: an object whose methods are called (get/set/...) must not be scalar-replaced away — the object has to exist for method dispatch. The raw-f64 single-field collapse needs to be gated on "no method calls / no escaping use of the instance as an object."

Bisected origin: the #5466 representation-lowering / typed-shape line (ce0117a60); surfaced by test-files/test_edge_interfaces.ts (the SimpleContainer<number> block) and the #5917 generic_class triage ((string).getValue was the dispatch half, now fixed).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions