Skip to content

Function-expression implicit return leaves numeric sentinel in return slot instead of undefined #776

@proggeramlug

Description

@proggeramlug

Background

While addressing PR #754 review item 2b (throw on class decorator that returns a replacement class), the natural check would have been:

if ret !== undefined { throw TypeError(...) }

The PR author had to fall back to:

if typeof ret === "function" { throw TypeError(...) }

…with this inline comment in crates/perry-hir/src/lower.rs:

Check typeof ret === "function" rather than ret !== undefined: Perry's lowering for a function expression with no explicit return currently leaves a numeric sentinel in the return slot rather than the NaN-boxed undefined value, so !== undefined would false-positive on side-effect-only decorators.

So bare @Injectable (function expression with no return) returns some non-undefined value — a numeric sentinel — instead of the NaN-boxed undefined that strict JS semantics require.

Why this matters

  1. It's masking a wider correctness bug. The decorator workaround only catches the typeof === "function" case. A decorator that returns a primitive or plain object would currently be silently discarded; once this is fixed, the decorator check can be tightened to the cleaner !== undefined shape and catch all replacement-attempt patterns.
  2. Any user code that does if (someFn() !== undefined) against a function with no explicit return is silently wrong. That's a fundamental JS semantic and the workaround comment suggests Perry currently violates it for a specific lowering path.

Repro shape (suggested — needs verification)

function bare() { /* no return */ }
const r = bare();
console.log(r === undefined);          // node: true
console.log(typeof r);                  // node: "undefined"
console.log(r);                         // node: undefined

Worth confirming whether this reproduces standalone or only on the specific decorator-invocation lowering path that the PR #754 author hit.

Suggested fix

Audit Perry's function-expression epilogue lowering in crates/perry-codegen/ (probably the Stmt::Return insertion when the function body falls off the end without an explicit return) and ensure the implicit return slot is the NaN-boxed TAG_UNDEFINED value (0x7FFC_0000_0000_0001) rather than whatever numeric sentinel is currently leaking through.

Once fixed:

  • Tighten the append_decorator_invocations_inner check from typeof ret === "function" to ret !== undefined so non-function class-decorator returns also throw.
  • Remove the workaround comment.

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions