Skip to content

this handle in class has problem #313

Description

@codehz

What happened

See below code comments

What you expected

It should match node/bun's behavior

Minimal reproduction

// Perry bug minimal reproduction
//
// ROOT CAUSE: When a class method calls a stored arrow function via `this.fn(x)`,
// perry passes the method's `this` (the holder object) to the arrow function,
// instead of using the arrow function's lexically captured `this`.
//
// This causes two symptoms:
//
// SYMPTOM 1 (wrong value / NaN):
//   `const self = this` inside the arrow function's defining class captures
//   an incorrect object (the Store instance, not the Holder instance).
//   `self.v` becomes undefined, and `x + self.v` becomes NaN.
//
// SYMPTOM 2 (segfault/crash):
//   Direct `this.v` access inside the arrow, when called with Store as `this`,
//   uses the native field offset of Holder.v on a Store object -> memory violation.
//
// Both symptoms disappear when the closure is called directly (not via a method).
//
// bun runs both correctly; perry native exhibits these bugs.

// ─── SYMPTOM 1: NaN (const self = this) ───────────────────────────────────────

class Store1 {
  fn: (x: number) => void;
  constructor(f: (x: number) => void) {
    this.fn = f;
  }
  call(x: number) {
    this.fn(x);
  } // calls stored fn via method (triggers bug)
}

class Holder1 {
  v = 10;
  s: Store1;
  constructor() {
    const self = this;
    // bun:   self.v = 10
    // perry: self.v = undefined  (perry assigns Store1 instance as 'self')
    console.log("[Symptom1] self.v in ctor:", self.v);
    this.s = new Store1((x) => {
      // bun:   result = 15
      // perry: result = NaN  (self.v is undefined because self = Store1 instance)
      console.log("[Symptom1] result:", x + self.v);
    });
  }
}

const h1 = new Holder1();
h1.s.call(5);
// Expected: self.v in ctor: 10 / result: 15
// Perry:    self.v in ctor: undefined / result: NaN

console.log("---");

// ─── SYMPTOM 2: Crash (direct this.v access) ──────────────────────────────────

class Store2 {
  fn: (x: number) => void;
  constructor(f: (x: number) => void) {
    this.fn = f;
  }
  call(x: number) {
    this.fn(x);
  }
}

class Holder2 {
  v = 10;
  s: Store2;
  constructor() {
    // Arrow uses 'this.v' directly (lexical this = Holder2 instance)
    this.s = new Store2((x) => {
      // bun:   result = 15 (this = Holder2, this.v = 10)
      // perry: SEGFAULT   (this = Store2 instance, accessing Holder2.v offset on Store2 -> crash)
      console.log("[Symptom2] result:", x + this.v);
    });
  }
}

const h2 = new Holder2();
h2.s.call(5);
// Expected: result: 15
// Perry:    segfault (signal 11 / core dump)

Command you ran:

perry test.ts && ./test.ts

Environment

  • Perry version: perry 0.5.402
  • Host OS: ArchLinux
  • Target: native
  • Rust toolchain (if building from source): rustc 1.95.0 (59807616e 2026-04-14)
  • Installed via: from source

Diagnostic output

output:

[Symptom1] self.v in ctor: undefined
[Symptom1] result: NaN
---
(crashed)

bun:

[Symptom1] self.v in ctor: 10
[Symptom1] result: 15
---
[Symptom2] result: 15
Perry Doctor

Environment Checks
──────────────────
  ✓ perry version: 0.5.386
  ✓ update status: up to date
  ✓ clang (LLVM codegen): clang version 22.1.3
  ✓ system linker (cc): cc (GCC) 15.2.1 20260209
  ⚠ runtime library: not found - run: cargo build --release -p perry-runtime
  ⚠ project config (perry.toml): not found - run: perry init

All critical checks passed with some warnings.

Anything else

Found in my ecs library's demo

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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