diff --git a/crates/perry-codegen/src/runtime_decls/objects.rs b/crates/perry-codegen/src/runtime_decls/objects.rs index 7e2a52b387..fdda4f3f3c 100644 --- a/crates/perry-codegen/src/runtime_decls/objects.rs +++ b/crates/perry-codegen/src/runtime_decls/objects.rs @@ -53,7 +53,11 @@ pub fn declare_phase_b_objects(module: &mut LlModule) { module.declare_function("js_object_set_unboxed_f64_field", VOID, &[I64, I32, DOUBLE]); module.declare_function("js_object_get_unboxed_f64_field", DOUBLE, &[I64, I32]); module.declare_function("js_object_set_field_by_name", VOID, &[I64, I64, DOUBLE]); - module.declare_function("js_object_set_field_by_name_nonenum", VOID, &[I64, I64, DOUBLE]); + module.declare_function( + "js_object_set_field_by_name_nonenum", + VOID, + &[I64, I64, DOUBLE], + ); module.declare_function("js_with_has_binding", I32, &[DOUBLE, I64]); module.declare_function("js_with_get_binding", DOUBLE, &[DOUBLE, I64]); module.declare_function("js_with_set_binding", DOUBLE, &[DOUBLE, I64, DOUBLE, I32]); diff --git a/crates/perry-hir/src/lower_decl/body_stmt.rs b/crates/perry-hir/src/lower_decl/body_stmt.rs index 66ac92dd75..210307821b 100644 --- a/crates/perry-hir/src/lower_decl/body_stmt.rs +++ b/crates/perry-hir/src/lower_decl/body_stmt.rs @@ -18,231 +18,17 @@ use super::class_computed::{ use super::helpers::{async_iterator_method_call, is_filehandle_readlines_for_await_target}; use super::*; +mod detect; mod for_await; mod nested_fn_decl; -use for_await::lower_runtime_for_await_iterator_body; - -fn unwrap_stream_expr(mut expr: &ast::Expr) -> &ast::Expr { - loop { - expr = match expr { - ast::Expr::TsAs(ts_as) => &ts_as.expr, - ast::Expr::TsNonNull(non_null) => &non_null.expr, - ast::Expr::TsConstAssertion(assertion) => &assertion.expr, - ast::Expr::TsTypeAssertion(assertion) => &assertion.expr, - ast::Expr::Paren(paren) => &paren.expr, - _ => break, - }; - } - expr -} - -fn web_readable_stream_values_receiver(expr: &ast::Expr) -> Option<&ast::Expr> { - let ast::Expr::Call(call) = unwrap_stream_expr(expr) else { - return None; - }; - let ast::Callee::Expr(callee_expr) = &call.callee else { - return None; - }; - let ast::Expr::Member(member) = callee_expr.as_ref() else { - return None; - }; - if !matches!(&member.prop, ast::MemberProp::Ident(prop) if prop.sym.as_ref() == "values") { - return None; - } - Some(member.obj.as_ref()) -} - -fn is_web_readable_stream_expr(ctx: &LoweringContext, expr: &ast::Expr) -> bool { - match unwrap_stream_expr(expr) { - ast::Expr::Ident(ident) => { - let name = ident.sym.as_ref(); - matches!( - ctx.lookup_native_instance(name), - Some((_, "ReadableStream")) - ) || matches!( - ctx.lookup_local_type(name), - Some(Type::Named(n)) if n == "ReadableStream" - ) - } - ast::Expr::New(new_expr) => matches!( - new_expr.callee.as_ref(), - ast::Expr::Ident(callee) if callee.sym.as_ref() == "ReadableStream" - ), - _ => false, - } -} - -fn strip_for_of_expr_wrappers(mut expr: &ast::Expr) -> &ast::Expr { - loop { - expr = match expr { - ast::Expr::TsAs(x) => &x.expr, - ast::Expr::TsNonNull(x) => &x.expr, - ast::Expr::TsConstAssertion(x) => &x.expr, - ast::Expr::Paren(x) => &x.expr, - _ => return expr, - }; - } -} - -fn is_node_readable_class_ref(expr: &ast::Expr) -> bool { - match strip_for_of_expr_wrappers(expr) { - ast::Expr::Ident(ident) => ident.sym.as_ref() == "Readable", - ast::Expr::Member(member) => { - matches!(&member.prop, ast::MemberProp::Ident(prop) if prop.sym.as_ref() == "Readable") - } - _ => false, - } -} - -fn is_node_readable_static_factory(expr: &ast::Expr) -> bool { - let ast::Expr::Call(call) = strip_for_of_expr_wrappers(expr) else { - return false; - }; - let ast::Callee::Expr(callee) = &call.callee else { - return false; - }; - let ast::Expr::Member(member) = strip_for_of_expr_wrappers(callee.as_ref()) else { - return false; - }; - let ast::MemberProp::Ident(prop) = &member.prop else { - return false; - }; - matches!(prop.sym.as_ref(), "from" | "of") && is_node_readable_class_ref(&member.obj) -} - -fn is_node_readable_expr(ctx: &LoweringContext, expr: &ast::Expr) -> bool { - is_node_readable_static_factory(expr) - || is_node_readable_helper_chain(ctx, expr) - || matches!( - crate::lower_types::infer_type_from_expr(strip_for_of_expr_wrappers(expr), ctx), - Type::Named(name) if name == "Readable" - ) -} - -fn is_node_readable_helper_chain(ctx: &LoweringContext, expr: &ast::Expr) -> bool { - let ast::Expr::Call(call) = strip_for_of_expr_wrappers(expr) else { - return false; - }; - let ast::Callee::Expr(callee) = &call.callee else { - return false; - }; - let ast::Expr::Member(member) = strip_for_of_expr_wrappers(callee.as_ref()) else { - return false; - }; - let ast::MemberProp::Ident(prop) = &member.prop else { - return false; - }; - match prop.sym.as_ref() { - "from" | "of" => is_node_readable_class_ref(&member.obj), - "map" | "filter" | "flatMap" | "take" | "drop" | "compose" => { - is_node_readable_expr(ctx, &member.obj) - } - _ => false, - } -} - -/// `for await (const line of rl)` where `rl = readline.createInterface(...)`. -/// Async-function-body counterpart of the same check in `lower/stmt_loops.rs`. -fn is_readline_interface_for_await_target(ctx: &LoweringContext, expr: &ast::Expr) -> bool { - matches!( - strip_for_of_expr_wrappers(expr), - ast::Expr::Ident(ident) - if matches!( - ctx.lookup_native_instance(ident.sym.as_ref()), - Some(("readline", "Interface")) - ) - ) -} - -fn is_fs_dir_type(ty: Type) -> bool { - matches!(ty, Type::Named(name) if name == "Dir" || name == "fs.Dir") -} - -fn is_fs_dir_for_await_target(ctx: &LoweringContext, expr: &ast::Expr) -> bool { - let expr = strip_for_of_expr_wrappers(expr); - if is_fs_dir_type(crate::lower_types::infer_type_from_expr(expr, ctx)) { - return true; - } - - let ast::Expr::Call(call) = expr else { - return false; - }; - let ast::Callee::Expr(callee) = &call.callee else { - return false; - }; - let ast::Expr::Member(member) = strip_for_of_expr_wrappers(callee.as_ref()) else { - return false; - }; - if !matches!(&member.prop, ast::MemberProp::Ident(prop) if prop.sym.as_ref() == "entries") { - return false; - } - is_fs_dir_type(crate::lower_types::infer_type_from_expr( - strip_for_of_expr_wrappers(&member.obj), - ctx, - )) -} - -fn iterator_return_call(iter_id: LocalId, needs_await: bool) -> Expr { - let call = Expr::Call { - callee: Box::new(Expr::PropertyGet { - object: Box::new(Expr::LocalGet(iter_id)), - property: "return".to_string(), - }), - args: vec![], - type_args: vec![], - }; - if needs_await { - Expr::Await(Box::new(call)) - } else { - call - } -} +use detect::{ + insert_iterator_return_before_abrupts, is_fs_dir_for_await_target, is_node_readable_expr, + is_readline_interface_for_await_target, is_web_readable_stream_expr, + web_readable_stream_values_receiver, +}; -fn insert_iterator_return_before_abrupts( - stmts: &mut Vec, - iter_id: LocalId, - needs_await: bool, -) { - let mut rewritten = Vec::with_capacity(stmts.len()); - for stmt in stmts.drain(..) { - match stmt { - Stmt::Break => { - rewritten.push(Stmt::Expr(iterator_return_call(iter_id, needs_await))); - rewritten.push(Stmt::Break); - } - Stmt::LabeledBreak(label) => { - rewritten.push(Stmt::Expr(iterator_return_call(iter_id, needs_await))); - rewritten.push(Stmt::LabeledBreak(label)); - } - Stmt::Return(value) => { - rewritten.push(Stmt::Expr(iterator_return_call(iter_id, needs_await))); - rewritten.push(Stmt::Return(value)); - } - Stmt::Throw(expr) => { - rewritten.push(Stmt::Expr(iterator_return_call(iter_id, needs_await))); - rewritten.push(Stmt::Throw(expr)); - } - Stmt::If { - condition, - mut then_branch, - mut else_branch, - } => { - insert_iterator_return_before_abrupts(&mut then_branch, iter_id, needs_await); - if let Some(else_stmts) = else_branch.as_mut() { - insert_iterator_return_before_abrupts(else_stmts, iter_id, needs_await); - } - rewritten.push(Stmt::If { - condition, - then_branch, - else_branch, - }); - } - other => rewritten.push(other), - } - } - *stmts = rewritten; -} +use for_await::lower_runtime_for_await_iterator_body; pub fn lower_body_stmt(ctx: &mut LoweringContext, stmt: &ast::Stmt) -> Result> { let mut result = Vec::new(); diff --git a/crates/perry-hir/src/lower_decl/body_stmt/detect.rs b/crates/perry-hir/src/lower_decl/body_stmt/detect.rs new file mode 100644 index 0000000000..8e13d94edc --- /dev/null +++ b/crates/perry-hir/src/lower_decl/body_stmt/detect.rs @@ -0,0 +1,234 @@ +//! for-await/for-of TARGET DETECTION helpers: predicates that decide +//! whether a `for await (…)` / `for (… of …)` head expression is a web +//! ReadableStream, a Node Readable, a readline interface, or an fs.Dir +//! handle (each gets a specialized lowering in `lower_body_stmt`), plus +//! the shared `iterator_return_call` / `insert_iterator_return_before_abrupts` +//! IteratorClose machinery. Split out of `body_stmt.rs` for the 2000-line +//! file-size gate; the twins of these helpers for the `lower/stmt_loops.rs` +//! duplicate lowering path live there (see #4786). + +use super::*; + +pub(super) fn unwrap_stream_expr(mut expr: &ast::Expr) -> &ast::Expr { + loop { + expr = match expr { + ast::Expr::TsAs(ts_as) => &ts_as.expr, + ast::Expr::TsNonNull(non_null) => &non_null.expr, + ast::Expr::TsConstAssertion(assertion) => &assertion.expr, + ast::Expr::TsTypeAssertion(assertion) => &assertion.expr, + ast::Expr::Paren(paren) => &paren.expr, + _ => break, + }; + } + expr +} + +pub(super) fn web_readable_stream_values_receiver(expr: &ast::Expr) -> Option<&ast::Expr> { + let ast::Expr::Call(call) = unwrap_stream_expr(expr) else { + return None; + }; + let ast::Callee::Expr(callee_expr) = &call.callee else { + return None; + }; + let ast::Expr::Member(member) = callee_expr.as_ref() else { + return None; + }; + if !matches!(&member.prop, ast::MemberProp::Ident(prop) if prop.sym.as_ref() == "values") { + return None; + } + Some(member.obj.as_ref()) +} + +pub(super) fn is_web_readable_stream_expr(ctx: &LoweringContext, expr: &ast::Expr) -> bool { + match unwrap_stream_expr(expr) { + ast::Expr::Ident(ident) => { + let name = ident.sym.as_ref(); + matches!( + ctx.lookup_native_instance(name), + Some((_, "ReadableStream")) + ) || matches!( + ctx.lookup_local_type(name), + Some(Type::Named(n)) if n == "ReadableStream" + ) + } + ast::Expr::New(new_expr) => matches!( + new_expr.callee.as_ref(), + ast::Expr::Ident(callee) if callee.sym.as_ref() == "ReadableStream" + ), + _ => false, + } +} + +pub(super) fn strip_for_of_expr_wrappers(mut expr: &ast::Expr) -> &ast::Expr { + loop { + expr = match expr { + ast::Expr::TsAs(x) => &x.expr, + ast::Expr::TsNonNull(x) => &x.expr, + ast::Expr::TsConstAssertion(x) => &x.expr, + ast::Expr::Paren(x) => &x.expr, + _ => return expr, + }; + } +} + +pub(super) fn is_node_readable_class_ref(expr: &ast::Expr) -> bool { + match strip_for_of_expr_wrappers(expr) { + ast::Expr::Ident(ident) => ident.sym.as_ref() == "Readable", + ast::Expr::Member(member) => { + matches!(&member.prop, ast::MemberProp::Ident(prop) if prop.sym.as_ref() == "Readable") + } + _ => false, + } +} + +pub(super) fn is_node_readable_static_factory(expr: &ast::Expr) -> bool { + let ast::Expr::Call(call) = strip_for_of_expr_wrappers(expr) else { + return false; + }; + let ast::Callee::Expr(callee) = &call.callee else { + return false; + }; + let ast::Expr::Member(member) = strip_for_of_expr_wrappers(callee.as_ref()) else { + return false; + }; + let ast::MemberProp::Ident(prop) = &member.prop else { + return false; + }; + matches!(prop.sym.as_ref(), "from" | "of") && is_node_readable_class_ref(&member.obj) +} + +pub(super) fn is_node_readable_expr(ctx: &LoweringContext, expr: &ast::Expr) -> bool { + is_node_readable_static_factory(expr) + || is_node_readable_helper_chain(ctx, expr) + || matches!( + crate::lower_types::infer_type_from_expr(strip_for_of_expr_wrappers(expr), ctx), + Type::Named(name) if name == "Readable" + ) +} + +pub(super) fn is_node_readable_helper_chain(ctx: &LoweringContext, expr: &ast::Expr) -> bool { + let ast::Expr::Call(call) = strip_for_of_expr_wrappers(expr) else { + return false; + }; + let ast::Callee::Expr(callee) = &call.callee else { + return false; + }; + let ast::Expr::Member(member) = strip_for_of_expr_wrappers(callee.as_ref()) else { + return false; + }; + let ast::MemberProp::Ident(prop) = &member.prop else { + return false; + }; + match prop.sym.as_ref() { + "from" | "of" => is_node_readable_class_ref(&member.obj), + "map" | "filter" | "flatMap" | "take" | "drop" | "compose" => { + is_node_readable_expr(ctx, &member.obj) + } + _ => false, + } +} + +/// `for await (const line of rl)` where `rl = readline.createInterface(...)`. +/// Async-function-body counterpart of the same check in `lower/stmt_loops.rs`. +pub(super) fn is_readline_interface_for_await_target( + ctx: &LoweringContext, + expr: &ast::Expr, +) -> bool { + matches!( + strip_for_of_expr_wrappers(expr), + ast::Expr::Ident(ident) + if matches!( + ctx.lookup_native_instance(ident.sym.as_ref()), + Some(("readline", "Interface")) + ) + ) +} + +pub(super) fn is_fs_dir_type(ty: Type) -> bool { + matches!(ty, Type::Named(name) if name == "Dir" || name == "fs.Dir") +} + +pub(super) fn is_fs_dir_for_await_target(ctx: &LoweringContext, expr: &ast::Expr) -> bool { + let expr = strip_for_of_expr_wrappers(expr); + if is_fs_dir_type(crate::lower_types::infer_type_from_expr(expr, ctx)) { + return true; + } + + let ast::Expr::Call(call) = expr else { + return false; + }; + let ast::Callee::Expr(callee) = &call.callee else { + return false; + }; + let ast::Expr::Member(member) = strip_for_of_expr_wrappers(callee.as_ref()) else { + return false; + }; + if !matches!(&member.prop, ast::MemberProp::Ident(prop) if prop.sym.as_ref() == "entries") { + return false; + } + is_fs_dir_type(crate::lower_types::infer_type_from_expr( + strip_for_of_expr_wrappers(&member.obj), + ctx, + )) +} + +pub(super) fn iterator_return_call(iter_id: LocalId, needs_await: bool) -> Expr { + let call = Expr::Call { + callee: Box::new(Expr::PropertyGet { + object: Box::new(Expr::LocalGet(iter_id)), + property: "return".to_string(), + }), + args: vec![], + type_args: vec![], + }; + if needs_await { + Expr::Await(Box::new(call)) + } else { + call + } +} + +pub(super) fn insert_iterator_return_before_abrupts( + stmts: &mut Vec, + iter_id: LocalId, + needs_await: bool, +) { + let mut rewritten = Vec::with_capacity(stmts.len()); + for stmt in stmts.drain(..) { + match stmt { + Stmt::Break => { + rewritten.push(Stmt::Expr(iterator_return_call(iter_id, needs_await))); + rewritten.push(Stmt::Break); + } + Stmt::LabeledBreak(label) => { + rewritten.push(Stmt::Expr(iterator_return_call(iter_id, needs_await))); + rewritten.push(Stmt::LabeledBreak(label)); + } + Stmt::Return(value) => { + rewritten.push(Stmt::Expr(iterator_return_call(iter_id, needs_await))); + rewritten.push(Stmt::Return(value)); + } + Stmt::Throw(expr) => { + rewritten.push(Stmt::Expr(iterator_return_call(iter_id, needs_await))); + rewritten.push(Stmt::Throw(expr)); + } + Stmt::If { + condition, + mut then_branch, + mut else_branch, + } => { + insert_iterator_return_before_abrupts(&mut then_branch, iter_id, needs_await); + if let Some(else_stmts) = else_branch.as_mut() { + insert_iterator_return_before_abrupts(else_stmts, iter_id, needs_await); + } + rewritten.push(Stmt::If { + condition, + then_branch, + else_branch, + }); + } + other => rewritten.push(other), + } + } + *stmts = rewritten; +} diff --git a/crates/perry-runtime/src/closure/mod.rs b/crates/perry-runtime/src/closure/mod.rs index efa129de3d..085e9fb57c 100644 --- a/crates/perry-runtime/src/closure/mod.rs +++ b/crates/perry-runtime/src/closure/mod.rs @@ -26,16 +26,16 @@ pub use alloc::{ pub use registry::{ build_rest_array, closure_arity, closure_is_arrow, closure_is_bound_method, closure_length, - dispatch_rest_bundled, - dispatch_with_arity, is_registered_arrow_function, is_registered_async_function, - is_registered_async_generator_function, is_registered_generator_function, - js_register_closure_arity, js_register_closure_arrow_function, - js_register_closure_async_function, js_register_closure_async_generator_function, - js_register_closure_generator_function, js_register_closure_length, js_register_closure_rest, - js_register_closure_rest_and_arguments, js_register_closure_synthetic_arguments, - lookup_closure_arity, lookup_closure_length, lookup_closure_rest, lookup_closure_rest_full, - real_capture_count, resolve_strategy, DispatchStrategy, BOUND_FUNCTION_FUNC_PTR, - BOUND_METHOD_FUNC_PTR, CAPTURES_THIS_FLAG, CLOSURE_MAGIC, + dispatch_rest_bundled, dispatch_with_arity, is_registered_arrow_function, + is_registered_async_function, is_registered_async_generator_function, + is_registered_generator_function, js_register_closure_arity, + js_register_closure_arrow_function, js_register_closure_async_function, + js_register_closure_async_generator_function, js_register_closure_generator_function, + js_register_closure_length, js_register_closure_rest, js_register_closure_rest_and_arguments, + js_register_closure_synthetic_arguments, lookup_closure_arity, lookup_closure_length, + lookup_closure_rest, lookup_closure_rest_full, real_capture_count, resolve_strategy, + DispatchStrategy, BOUND_FUNCTION_FUNC_PTR, BOUND_METHOD_FUNC_PTR, CAPTURES_THIS_FLAG, + CLOSURE_MAGIC, }; pub use dispatch::{ diff --git a/crates/perry-runtime/src/object/class_registry.rs b/crates/perry-runtime/src/object/class_registry.rs index 8dcbac4f2d..afc66befd6 100644 --- a/crates/perry-runtime/src/object/class_registry.rs +++ b/crates/perry-runtime/src/object/class_registry.rs @@ -4709,13 +4709,15 @@ pub(crate) unsafe fn call_static_method( a(args_ptr, args_len, 2), a(args_ptr, args_len, 3), ), - 5 => (std::mem::transmute:: f64>(func_ptr))( - a(args_ptr, args_len, 0), - a(args_ptr, args_len, 1), - a(args_ptr, args_len, 2), - a(args_ptr, args_len, 3), - a(args_ptr, args_len, 4), - ), + 5 => { + (std::mem::transmute:: f64>(func_ptr))( + a(args_ptr, args_len, 0), + a(args_ptr, args_len, 1), + a(args_ptr, args_len, 2), + a(args_ptr, args_len, 3), + a(args_ptr, args_len, 4), + ) + } 6 => (std::mem::transmute:: f64>( func_ptr, ))( @@ -4726,18 +4728,19 @@ pub(crate) unsafe fn call_static_method( a(args_ptr, args_len, 4), a(args_ptr, args_len, 5), ), - 7 => (std::mem::transmute::< - usize, - extern "C" fn(f64, f64, f64, f64, f64, f64, f64) -> f64, - >(func_ptr))( - a(args_ptr, args_len, 0), - a(args_ptr, args_len, 1), - a(args_ptr, args_len, 2), - a(args_ptr, args_len, 3), - a(args_ptr, args_len, 4), - a(args_ptr, args_len, 5), - a(args_ptr, args_len, 6), - ), + 7 => { + (std::mem::transmute:: f64>( + func_ptr, + ))( + a(args_ptr, args_len, 0), + a(args_ptr, args_len, 1), + a(args_ptr, args_len, 2), + a(args_ptr, args_len, 3), + a(args_ptr, args_len, 4), + a(args_ptr, args_len, 5), + a(args_ptr, args_len, 6), + ) + } _ => (std::mem::transmute::< usize, extern "C" fn(f64, f64, f64, f64, f64, f64, f64, f64) -> f64, diff --git a/crates/perry-runtime/src/object/descriptors.rs b/crates/perry-runtime/src/object/descriptors.rs index dba4d72cdd..297116f27d 100644 --- a/crates/perry-runtime/src/object/descriptors.rs +++ b/crates/perry-runtime/src/object/descriptors.rs @@ -223,8 +223,7 @@ pub extern "C" fn js_object_get_own_property_descriptor(obj_value: f64, key_valu // MakeConstructor). Only the constructor ref carries it — the // prototype ref's own `prototype` lookup falls through. // (Test262 definition/prototype-property.) - if method_name == "prototype" - && super::class_prototype_ref_id(obj_value).is_none() + if method_name == "prototype" && super::class_prototype_ref_id(obj_value).is_none() { let proto = super::native_module::class_prototype_ref_value(class_id); return build_data_descriptor(proto, false, false, false);