Skip to content

declared-number class field holding a string: o.x + 1 coerces to NaN (Node concatenates); any-laundered add evaporates #7773

Description

@proggeramlug

Symptom

A declared-number class field that actually holds a string (Perry does not enforce annotations) diverges from Node when read in an arithmetic +:

class C { x: number; constructor(x: number) { this.x = x; } }
function g(o: C): void { (o as any).x = "s"; }
function f(): string {
  const o = new C(1);
  g(o);
  return `${o.x + 1}`;
}
console.log(f());

Node (26.5.1, strip-types): s1 (string concat — the runtime value decides).
Perry: NaN (the Add was classified numeric from the DECLARED field type, so the class-field get lowers in number context and its fallback arm hard-coerces via js_number_coerce).

Second shape — the add EVAPORATES through an any local

  const v: any = o.x;
  return `${v + 1}`;

Node: s1. Perry: s — the + 1 is dropped entirely, not even coerced (smells like the #7590 discard_expr_value family or an any-add lowering hole).

Attribution

Both reproduce with PERRY_PTR_SHAPE_LOCALS=0, no arrays, no element groups, on a build of main + #7770 (the #7770 collector change is not involved; its A/B arms behave identically here). The escape (g(o)) is required — a non-escaping o gets scalar-replaced and prints s1 correctly, which is why the trivial form never showed this.

Where to look

expr/property_get/helpers.rs::lower_raw_f64_class_field_get_for_number_context is only entered when the surrounding context was already classified numeric; the misclassification is upstream in the Add lowering (type_analysis::is_numeric_expr trusting the declared field type for an aliased receiver). The guarded diamond's fallback arm then coerces unconditionally.

Found while validating #7770 (its gap test deliberately avoids + on poisoned fields and points here).

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugConfirmed defect or regression

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions