Skip to content
Open
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
13 changes: 13 additions & 0 deletions crates/perry-api-manifest/src/entries.rs
Original file line number Diff line number Diff line change
Expand Up @@ -519,6 +519,12 @@ pub static API_MANIFEST: &[ApiEntry] = &[
method("async_hooks", "enterWith", true, None),
method("async_hooks", "exit", true, None),
method("async_hooks", "disable", true, None),
// AsyncResource — Nest's `@nestjs/core` request-scoped DI uses
// this to bind a callback to a synthetic async resource. The
// stub in `node:async_hooks` JS module satisfies callers that
// only need the `runInAsyncScope` shape.
class("async_hooks", "AsyncResource"),
class("async_hooks", "AsyncLocalStorage"),
method("decimal.js", "plus", true, None),
method("decimal.js", "minus", true, None),
method("decimal.js", "times", true, None),
Expand Down Expand Up @@ -1654,6 +1660,13 @@ pub static API_MANIFEST: &[ApiEntry] = &[
method("util", "isDeepStrictEqual", false, None),
class("util", "TextEncoder"),
class("util", "TextDecoder"),
// util.types — Node's runtime type-introspection namespace. Required
// for `@nestjs/core` / rxjs internal dispatch (PR #754 fixture). The
// backing object lives in the `node:util` stub in
// perry-jsruntime/src/modules.rs and answers every is* probe with
// `false` (a safe default — no Perry value type matches Node's
// privileged BoxedPrimitive/Proxy/external introspection cases).
property("util", "types"),
// --- stream (Web Streams API + Node stream classes — see
// perry-stdlib/src/streams.rs and perry-ext-streams) ---
class("stream", "Readable"),
Expand Down
2 changes: 2 additions & 0 deletions crates/perry-codegen-arkts/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8494,6 +8494,7 @@ mod tests {
name: "item".to_string(),
ty: perry_types::Type::Any,
default: None,
decorators: Vec::new(),
is_rest: false,
};
let inner_text = nmc("Text", vec![Expr::LocalGet(42)]);
Expand Down Expand Up @@ -9027,6 +9028,7 @@ mod tests {
name: "item".to_string(),
ty: perry_types::Type::Any,
default: None,
decorators: Vec::new(),
is_rest: false,
};
let inner_text = nmc("Text", vec![Expr::LocalGet(99)]);
Expand Down
1 change: 1 addition & 0 deletions crates/perry-codegen-arkts/tests/phase2_full_app_smoke.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ fn param(id: LocalId, name: &str) -> Param {
name: name.to_string(),
ty: Type::Any,
default: None,
decorators: Vec::new(),
is_rest: false,
}
}
Expand Down
76 changes: 59 additions & 17 deletions crates/perry-codegen/src/codegen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -685,6 +685,7 @@ pub fn compile_module(hir: &HirModule, opts: CompileOptions) -> Result<Vec<u8>>
init: None,
is_private: false,
is_readonly: false,
decorators: Vec::new(),
})
.collect(),
constructor: None,
Expand All @@ -711,6 +712,7 @@ pub fn compile_module(hir: &HirModule, opts: CompileOptions) -> Result<Vec<u8>>
setters: Vec::new(),
static_fields: Vec::new(),
static_methods: Vec::new(),
decorators: Vec::new(),
is_exported: false,
aliases: Vec::new(),
};
Expand Down Expand Up @@ -1832,9 +1834,18 @@ pub fn compile_module(hir: &HirModule, opts: CompileOptions) -> Result<Vec<u8>>
// Names are scoped by module prefix to avoid cross-module collisions.
let mut func_names: HashMap<u32, String> = HashMap::new();
let mut func_signatures: HashMap<u32, (usize, bool, bool)> = HashMap::new();
let mut func_synthetic_arguments: std::collections::HashSet<u32> =
std::collections::HashSet::new();
for f in &hir.functions {
func_names.insert(f.id, scoped_fn_name(&module_prefix, &f.name));
let has_rest = f.params.iter().any(|p| p.is_rest);
if f.params
.last()
.map(|p| p.is_rest && p.name == "arguments")
.unwrap_or(false)
{
func_synthetic_arguments.insert(f.id);
}
let returns_number = matches!(
f.return_type,
perry_types::Type::Number | perry_types::Type::Int32
Expand Down Expand Up @@ -2101,6 +2112,7 @@ pub fn compile_module(hir: &HirModule, opts: CompileOptions) -> Result<Vec<u8>>
&static_field_globals,
&class_ids,
&func_signatures,
&func_synthetic_arguments,
&module_boxed_vars,
&closure_rest_params,
&cross_module,
Expand Down Expand Up @@ -2214,6 +2226,7 @@ pub fn compile_module(hir: &HirModule, opts: CompileOptions) -> Result<Vec<u8>>
&static_field_globals,
&class_ids,
&func_signatures,
&func_synthetic_arguments,
&module_prefix,
&module_boxed_vars,
&module_local_types,
Expand Down Expand Up @@ -2244,6 +2257,7 @@ pub fn compile_module(hir: &HirModule, opts: CompileOptions) -> Result<Vec<u8>>
&static_field_globals,
&class_ids,
&func_signatures,
&func_synthetic_arguments,
&module_boxed_vars,
&closure_rest_params,
&cross_module,
Expand Down Expand Up @@ -2271,6 +2285,7 @@ pub fn compile_module(hir: &HirModule, opts: CompileOptions) -> Result<Vec<u8>>
&static_field_globals,
&class_ids,
&func_signatures,
&func_synthetic_arguments,
&module_boxed_vars,
&closure_rest_params,
&cross_module,
Expand All @@ -2295,6 +2310,7 @@ pub fn compile_module(hir: &HirModule, opts: CompileOptions) -> Result<Vec<u8>>
&static_field_globals,
&class_ids,
&func_signatures,
&func_synthetic_arguments,
&module_boxed_vars,
&closure_rest_params,
&cross_module,
Expand Down Expand Up @@ -2351,6 +2367,7 @@ pub fn compile_module(hir: &HirModule, opts: CompileOptions) -> Result<Vec<u8>>
name: format!("__forward_arg{}", i),
ty: perry_types::Type::Any,
default: None,
decorators: Vec::new(),
is_rest: false,
});
}
Expand All @@ -2368,6 +2385,7 @@ pub fn compile_module(hir: &HirModule, opts: CompileOptions) -> Result<Vec<u8>>
name: format!("__forward_arg{}", i),
ty: perry_types::Type::Any,
default: None,
decorators: Vec::new(),
is_rest: false,
});
}
Expand Down Expand Up @@ -2414,6 +2432,7 @@ pub fn compile_module(hir: &HirModule, opts: CompileOptions) -> Result<Vec<u8>>
&static_field_globals,
&class_ids,
&func_signatures,
&func_synthetic_arguments,
&module_boxed_vars,
&closure_rest_params,
&cross_module,
Expand All @@ -2438,6 +2457,7 @@ pub fn compile_module(hir: &HirModule, opts: CompileOptions) -> Result<Vec<u8>>
&static_field_globals,
&class_ids,
&func_signatures,
&func_synthetic_arguments,
&module_prefix,
&module_boxed_vars,
&closure_rest_params,
Expand Down Expand Up @@ -2734,6 +2754,7 @@ pub fn compile_module(hir: &HirModule, opts: CompileOptions) -> Result<Vec<u8>>
&static_field_globals,
&class_ids,
&func_signatures,
&func_synthetic_arguments,
&module_prefix,
opts.is_entry_module,
&opts.non_entry_module_prefixes,
Expand Down Expand Up @@ -2897,6 +2918,7 @@ fn compile_function(
static_field_globals: &HashMap<(String, String), String>,
class_ids: &HashMap<String, u32>,
func_signatures: &HashMap<u32, (usize, bool, bool)>,
func_synthetic_arguments: &std::collections::HashSet<u32>,
module_boxed_vars: &std::collections::HashSet<u32>,
closure_rest_params: &HashMap<u32, usize>,
cross_module: &CrossModuleCtx,
Expand Down Expand Up @@ -3048,6 +3070,7 @@ fn compile_function(
class_keys_globals: &cross_module.class_keys_globals,
imported_class_ctors: &cross_module.imported_class_ctors,
func_signatures,
func_synthetic_arguments,
boxed_vars,
prealloc_boxes: std::collections::HashSet::new(),
closure_rest_params,
Expand Down Expand Up @@ -3216,6 +3239,7 @@ fn compile_closure(
static_field_globals: &HashMap<(String, String), String>,
class_ids: &HashMap<String, u32>,
func_signatures: &HashMap<u32, (usize, bool, bool)>,
func_synthetic_arguments: &std::collections::HashSet<u32>,
module_prefix: &str,
module_boxed_vars: &std::collections::HashSet<u32>,
module_local_types: &HashMap<u32, perry_types::Type>,
Expand Down Expand Up @@ -3429,6 +3453,7 @@ fn compile_closure(
class_keys_globals: &cross_module.class_keys_globals,
imported_class_ctors: &cross_module.imported_class_ctors,
func_signatures,
func_synthetic_arguments,
boxed_vars: closure_boxed_vars,
prealloc_boxes: std::collections::HashSet::new(),
closure_rest_params,
Expand Down Expand Up @@ -3542,6 +3567,7 @@ fn compile_method(
static_field_globals: &HashMap<(String, String), String>,
class_ids: &HashMap<String, u32>,
func_signatures: &HashMap<u32, (usize, bool, bool)>,
func_synthetic_arguments: &std::collections::HashSet<u32>,
module_boxed_vars: &std::collections::HashSet<u32>,
closure_rest_params: &HashMap<u32, usize>,
cross_module: &CrossModuleCtx,
Expand Down Expand Up @@ -3655,6 +3681,7 @@ fn compile_method(
class_keys_globals: &cross_module.class_keys_globals,
imported_class_ctors: &cross_module.imported_class_ctors,
func_signatures,
func_synthetic_arguments,
boxed_vars: method_boxed_vars,
prealloc_boxes: std::collections::HashSet::new(),
closure_rest_params,
Expand Down Expand Up @@ -3920,6 +3947,7 @@ fn compile_module_entry(
static_field_globals: &HashMap<(String, String), String>,
class_ids: &HashMap<String, u32>,
func_signatures: &HashMap<u32, (usize, bool, bool)>,
func_synthetic_arguments: &std::collections::HashSet<u32>,
module_prefix: &str,
is_entry: bool,
non_entry_module_prefixes: &[String],
Expand Down Expand Up @@ -4095,6 +4123,7 @@ fn compile_module_entry(
class_keys_globals: &cross_module.class_keys_globals,
imported_class_ctors: &cross_module.imported_class_ctors,
func_signatures,
func_synthetic_arguments,
boxed_vars: main_boxed_vars,
prealloc_boxes: std::collections::HashSet::new(),
closure_rest_params: closure_rest_params,
Expand Down Expand Up @@ -4375,6 +4404,7 @@ fn compile_module_entry(
class_keys_globals: &cross_module.class_keys_globals,
imported_class_ctors: &cross_module.imported_class_ctors,
func_signatures,
func_synthetic_arguments,
boxed_vars: init_boxed_vars,
prealloc_boxes: std::collections::HashSet::new(),
closure_rest_params: closure_rest_params,
Expand Down Expand Up @@ -4718,18 +4748,20 @@ fn emit_string_pool(
if *class_name != class.name {
continue;
}
// Imported class stubs carry id == 0 (they're typed-name
// placeholders for cross-module dispatch; the defining module's init
// registers their methods). Skip them here so we don't re-emit the
// registration. Previously this filter was `method.body.is_empty()`;
// the id check is equivalent for stubs and also catches getter/setter
// and property-decorator init that legitimately has an empty body.
if class.id == 0 {
continue;
}
let cid = match class_ids.get(class_name) {
Some(&c) if c != 0 => c,
_ => continue,
};
for method in &class.methods {
// Skip imported class stubs: their `body` is empty
// (they're just typed-name placeholders for cross-module
// dispatch). The defining module's init registers them.
// Local methods always have non-empty bodies.
if method.body.is_empty() {
continue;
}
let llvm_name = format!(
"perry_method_{}__{}__{}",
module_prefix,
Expand Down Expand Up @@ -4821,16 +4853,20 @@ fn emit_string_pool(
if *class_name != class.name {
continue;
}
// Imported class stubs carry id == 0 (they're typed-name
// placeholders for cross-module dispatch; the defining module's init
// registers their methods). Skip them here so we don't re-emit the
// registration. Previously this filter was `method.body.is_empty()`;
// the id check is equivalent for stubs and also catches getter/setter
// and property-decorator init that legitimately has an empty body.
if class.id == 0 {
continue;
}
let cid = match class_ids.get(class_name).copied() {
Some(c) if c != 0 => c,
_ => continue,
};
for (prop, getter_fn) in &class.getters {
// Skip imported class stubs: their `body` is empty (the
// defining module's init registers them).
if getter_fn.body.is_empty() {
continue;
}
// The local-emit path at codegen.rs:1858 prepends `__get_`
// to the HIR-assigned getter name (`get_<prop>`), giving
// the LLVM symbol `perry_method_<modprefix>__<class>__<sanitize(__get_get_<prop>)>`.
Expand Down Expand Up @@ -4882,16 +4918,20 @@ fn emit_string_pool(
if *class_name != class.name {
continue;
}
// Imported class stubs carry id == 0 (they're typed-name
// placeholders for cross-module dispatch; the defining module's init
// registers their methods). Skip them here so we don't re-emit the
// registration. Previously this filter was `method.body.is_empty()`;
// the id check is equivalent for stubs and also catches getter/setter
// and property-decorator init that legitimately has an empty body.
if class.id == 0 {
continue;
}
let cid = match class_ids.get(class_name).copied() {
Some(c) if c != 0 => c,
_ => continue,
};
for (prop, setter_fn) in &class.setters {
// Skip imported class stubs (their body is empty — the defining
// module's init registers them).
if setter_fn.body.is_empty() {
continue;
}
let inner = format!("__set_{}", setter_fn.name);
let llvm_name = format!(
"perry_method_{}__{}__{}",
Expand Down Expand Up @@ -5004,6 +5044,7 @@ fn compile_static_method(
static_field_globals: &HashMap<(String, String), String>,
class_ids: &HashMap<String, u32>,
func_signatures: &HashMap<u32, (usize, bool, bool)>,
func_synthetic_arguments: &std::collections::HashSet<u32>,
module_prefix: &str,
module_boxed_vars: &std::collections::HashSet<u32>,
closure_rest_params: &HashMap<u32, usize>,
Expand Down Expand Up @@ -5104,6 +5145,7 @@ fn compile_static_method(
class_keys_globals: &cross_module.class_keys_globals,
imported_class_ctors: &cross_module.imported_class_ctors,
func_signatures,
func_synthetic_arguments,
boxed_vars: static_boxed_vars,
prealloc_boxes: std::collections::HashSet::new(),
closure_rest_params,
Expand Down
57 changes: 57 additions & 0 deletions crates/perry-codegen/src/collectors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -860,6 +860,63 @@ pub(crate) fn collect_ref_ids_in_expr(e: &perry_hir::Expr, out: &mut HashSet<u32
walk(a, out);
walk(b, out);
}
Expr::ReflectDefineMetadata {
key,
value,
target,
property_key,
} => {
walk(key, out);
walk(value, out);
walk(target, out);
if let Some(property_key) = property_key {
walk(property_key, out);
}
}
Expr::ReflectGetMetadata {
key,
target,
property_key,
}
| Expr::ReflectGetOwnMetadata {
key,
target,
property_key,
}
| Expr::ReflectHasMetadata {
key,
target,
property_key,
}
| Expr::ReflectHasOwnMetadata {
key,
target,
property_key,
}
| Expr::ReflectDeleteMetadata {
key,
target,
property_key,
} => {
walk(key, out);
walk(target, out);
if let Some(property_key) = property_key {
walk(property_key, out);
}
}
Expr::ReflectGetMetadataKeys {
target,
property_key,
}
| Expr::ReflectGetOwnMetadataKeys {
target,
property_key,
} => {
walk(target, out);
if let Some(property_key) = property_key {
walk(property_key, out);
}
}
Expr::Closure { body, captures, .. } => {
// Closure literals don't introduce captures into the outer
// scope, but their explicit captures + body references may
Expand Down
Loading