Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions crates/perry-runtime/src/object/field_get_set/get_field_by_name.rs
Original file line number Diff line number Diff line change
Expand Up @@ -852,6 +852,47 @@ pub extern "C" fn js_object_get_field_by_name(
if let Some(v) = result {
return JSValue::from_bits(v.to_bits());
}
// Static DATA fields are INHERITED by subclasses, exactly like
// static methods: `class D {}; D.kind = "x"; class G extends D {}`
// makes `G.kind === "x"` (the class-object proto chain
// `G.__proto__ === D` carries statics). The own-field read above
// only consulted `class_id`; walk the parent class_id chain here
// so an inherited static field (or runtime `Parent.x = …`
// assignment — both live in CLASS_DYNAMIC_PROPS) resolves. Static
// METHODS are handled by `lookup_static_method_in_chain` below;
// this covers the data-field case that was returning `undefined`
// (Auth.js sets `SignInError.kind = "signIn"` and reads it off a
// `CredentialsSignin` subclass to pick the sign-in vs error page).
{
let mut cid = class_id;
let mut depth = 0usize;
while depth < 32 {
match get_parent_class_id(cid) {
Some(p) if p != 0 && p != cid => {
cid = p;
depth += 1;
}
_ => break,
}
if super::super::class_registry::class_is_key_deleted(cid, name) {
// A key deleted on THIS ancestor is not provided by
// it, but a higher ancestor may still define it —
// `delete Mid.foo` must let `Sub.foo` inherit
// `Base.foo`, not resolve to undefined. Skip this
// level and keep walking up (safe: `cid`/`depth`
// advance at the top of every iteration).
continue;
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
let inherited = CLASS_DYNAMIC_PROPS.with(|m| {
m.borrow()
.get(&cid)
.and_then(|props| props.get(name).copied())
});
if let Some(v) = inherited {
return JSValue::from_bits(v.to_bits());
}
}
}
if super::super::class_registry::lookup_static_method_in_chain(class_id, name)
.is_some()
{
Expand Down
32 changes: 32 additions & 0 deletions test-files/test_gap_static_field_inheritance.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// A subclass inherits its parent's static DATA properties — both in-body static
// fields and runtime `Parent.x = …` assignments — via the class-object prototype
// chain (`Sub.__proto__ === Base`). Regression: perry inherited static METHODS
// but returned `undefined` for inherited static data fields, so
// `Sub.tag` / `Sub.kind` read undefined even though `Base` defined them. (Auth.js
// sets `SignInError.kind = "signIn"` and reads it off a `CredentialsSignin`
// subclass to choose the sign-in vs error redirect page.)
class Base {
static inBody = "field";
}
(Base as any).external = "assigned";
class Sub extends Base {}
class Leaf extends Sub {}
console.log("Sub.inBody=" + (Sub as any).inBody);
console.log("Sub.external=" + (Sub as any).external);
console.log("Leaf.inBody=" + (Leaf as any).inBody);
console.log("Leaf.external=" + (Leaf as any).external);
// own field still wins over inherited
(Sub as any).external = "own";
console.log("Sub.own-wins=" + (Sub as any).external);
console.log("Base.unshadowed=" + (Base as any).external);

// A key deleted on an INTERMEDIATE ancestor must not shadow a higher one:
// `delete Mid.foo` should let `Sub.foo` inherit `Base.foo`, not resolve to
// undefined. (The chain walk must `continue` past the deleted level, not
// `break` out of the whole traversal.)
class DBase { static tag = "base"; }
class DMid extends DBase { static tag = "mid"; }
class DSub extends DMid {}
console.log("del-intermediate before:", (DSub as any).tag);
delete (DMid as any).tag;
console.log("del-intermediate after :", (DSub as any).tag);
Loading