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
14 changes: 14 additions & 0 deletions crates/perry-hir/src/lower/expr_call/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,20 @@ fn lower_call_inner(ctx: &mut LoweringContext, call: &ast::CallExpr) -> Result<E
args,
});
}
// `super['getThis']()` in a CLASS method with a string-
// literal key is a super METHOD CALL — route it through
// SuperMethodCall (the ident-form path) so it binds the
// current `this` as receiver. Without this it fell through
// to a generic property-get-then-call that lost the
// receiver binding (test262 super/prop-expr-cls-ref-this).
if let ast::Expr::Lit(ast::Lit::Str(s)) = computed.expr.as_ref() {
if let Some(method) = s.value.as_str() {
return Ok(Expr::SuperMethodCall {
method: method.to_string(),
args,
});
}
}
}
}
}
Expand Down
15 changes: 14 additions & 1 deletion crates/perry-hir/src/lower/expr_misc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,14 +82,27 @@ pub(super) fn lower_super_prop(
}
}
ast::SuperProp::Computed(computed) => {
let index = Box::new(lower_expr(ctx, &computed.expr)?);
if let Some(home_id) = ctx.object_super_home_stack.last().copied() {
let index = Box::new(lower_expr(ctx, &computed.expr)?);
Ok(Expr::ObjectSuperPropertyGet {
home: Box::new(Expr::LocalGet(home_id)),
key: index,
receiver: Box::new(Expr::This),
})
} else if let Some(key) = match computed.expr.as_ref() {
ast::Expr::Lit(ast::Lit::Str(s)) => s.value.as_str().map(|s| s.to_string()),
_ => None,
} {
// `super['fromA']` in a CLASS method with a string-literal key:
// route through the same parent-prototype-chain lookup as the
// ident form `super.fromA` (Expr::SuperPropertyGet). The previous
// `this[index]` fallback read the property off the CHILD instance,
// shadowing the parent value (test262
// super/prop-expr-cls-val{,-from-arrow}). A truly dynamic computed
// key (not a literal) still falls back below.
Ok(Expr::SuperPropertyGet { property: key })
} else {
let index = Box::new(lower_expr(ctx, &computed.expr)?);
Ok(Expr::IndexGet {
object: Box::new(Expr::This),
index,
Expand Down
10 changes: 10 additions & 0 deletions crates/perry-hir/src/lower/lower_expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -672,6 +672,16 @@ pub(crate) fn lower_expr(ctx: &mut LoweringContext, expr: &ast::Expr) -> Result<
let ty_expr = match bin.right.as_ref() {
ast::Expr::Ident(ident) => {
let name = ident.sym.as_ref();
// `x instanceof undefined`: `undefined` is the primitive
// value, never a class name. Codegen would resolve `ty =
// "undefined"` to class_id 0 and silently return `false`;
// ECMAScript requires evaluating the RHS and throwing a
// TypeError because it is not an object (test262
// instanceof/S11.8.6_A3 #4). Lower it to the undefined
// value so it routes through `js_instanceof_dynamic`.
if name == "undefined" {
Some(Box::new(Expr::Undefined))
} else
// A local holding a class ref (drizzle's `is(value, type)`),
// OR a top-level ES5 function constructor (`function Foo(){…}`
// used as `x instanceof Foo`). The latter has no class entry,
Expand Down
18 changes: 18 additions & 0 deletions crates/perry-runtime/src/collection_iter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,24 @@ pub(crate) fn is_iterable(value: f64) -> bool {
if crate::map::is_registered_map(addr) || crate::set::is_registered_set(addr) {
return true;
}
// A built-in iterator object — an array/map/set/string/buffer/iterator-
// helper iterator — IS already an iterator and is therefore iterable
// (`[Symbol.iterator]()` returns itself). Mirrors the equivalent
// `is_builtin_iterator_class_id` arm in `js_get_iterator`.
if crate::array::is_builtin_iterator_class_id(addr) {
return true;
}
// A generator object (`g()` from `function* g(){}`) is lowered by Perry to a
// plain object with own closure-valued `next`/`return`/`throw` methods and
// NO `[Symbol.iterator]` symbol property, so the generic check below misses
// it. Generators ARE iterable (`[Symbol.iterator]()` returns themselves), so
// without this `js_array_like_to_array` — call / `new` / super spread of a
// generator (`f(...g())`, `new C(...g())`) and `new Map/Set(g())` — fell
// through to the array-reinterpret garbage path. `js_get_iterator` returns
// the generator unchanged (its own `.next()` drives the state machine).
if crate::object::js_util_types_is_generator_object(value).to_bits() == crate::value::TAG_TRUE {
return true;
}
// Generic object: iterable iff it exposes a callable `[Symbol.iterator]`.
if !crate::object::is_valid_obj_ptr(raw as *const u8) {
return false;
Expand Down
12 changes: 11 additions & 1 deletion crates/perry-runtime/src/object/property_key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,17 @@ pub unsafe extern "C" fn js_super_accessor_get(
}
}
}
let proto = crate::object::class_prototype_object(parent_class_id);
// Prefer the *declared* prototype object (stable heap identity). A dynamic
// write `Parent.prototype.foo = v` lands on that object, whereas the older
// overloaded `CLASS_PROTOTYPE_OBJECTS` table may hold a distinct synthetic
// prototype that never sees such writes — so reading through it returned
// `undefined` for data properties added to a parent prototype after the
// class declaration (test262 super/prop-{dot,expr}-cls-val). Falls back to
// the older table for synthetic-prototype sources that lack a decl entry.
let mut proto = crate::object::class_decl_prototype_object(parent_class_id);
if proto.is_null() {
proto = crate::object::class_prototype_object(parent_class_id);
}
if !proto.is_null() {
let target = crate::value::js_nanbox_pointer(proto as i64);
return js_object_get_property_key(target, key);
Expand Down
Loading