Skip to content
Closed
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
1 change: 1 addition & 0 deletions changelog.d/11008-dynamic-function-names.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fixed `.name` on named functions and classes returned from runtime-generated `new Function` sources, including sources assembled from strings.
6 changes: 6 additions & 0 deletions crates/perry-runtime/src/dyn_eval/interp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -470,6 +470,9 @@ pub(crate) fn make_function_value(
ctx.wasm_allowed,
);
let closure_idx = root_push(closure);
let closure_ptr = crate::value::js_nanbox_get_pointer(root_get(closure_idx))
as *mut crate::closure::ClosureHeader;
crate::object::set_bound_native_closure_name(closure_ptr, &fn_name);
env::define(root_get(name_env_idx), &fn_name, root_get(closure_idx));
let closure = root_get(closure_idx);
roots_truncate(name_env_idx);
Expand Down Expand Up @@ -1199,6 +1202,9 @@ pub(crate) fn eval_class_expr(ctx: &Ctx, class_expr: &ast::ClassExpr, env_idx: u
let ctor_idx = root_push(ctor_closure);

if let Some(name) = &name {
let ctor_ptr = crate::value::js_nanbox_get_pointer(root_get(ctor_idx))
as *mut crate::closure::ClosureHeader;
crate::object::set_bound_native_closure_name(ctor_ptr, name);
env::define(root_get(body_env_idx), name, root_get(ctor_idx));
}

Expand Down
15 changes: 15 additions & 0 deletions crates/perry-runtime/src/dyn_eval/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,21 @@ fn named_function_expression_recursion_and_expando() {
assert_eq!(as_num(calls), 1.0);
}

#[test]
fn interpreted_functions_and_classes_keep_their_declared_names() {
for (source, expected) in [
("return function Named() {};", "Named"),
("function Declared() {} return Declared;", "Declared"),
("return class Widget {};", "Widget"),
] {
let factory = dyn_fn(&[source]);
let value_idx = root_push(call(factory, &[]));
let name = bridge::get_member(root_get(value_idx), "name");
assert_eq!(as_str(name), expected, "source: {source}");
roots_truncate(value_idx);
}
}

#[test]
fn closures_capture_interpreter_scope() {
let f = dyn_fn(&[r#"
Expand Down
57 changes: 57 additions & 0 deletions crates/perry/tests/issue_10676_dynamic_function_names.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
//! Runtime-built Function sources keep their parsed function and class names.

use std::path::PathBuf;
use std::process::Command;

#[test]
fn dynamic_function_and_class_names_are_visible() {
let dir = tempfile::tempdir().expect("tempdir");
let entry = dir.path().join("main.ts");
let binary = dir.path().join("main_bin");
std::fs::write(
&entry,
r#"
const literal: any = new Function("return function Literal() {}")();
const functionParts = ["return function ", "Named", "() {}"];
const dynamic: any = new Function(functionParts.join(""))();
const classParts = ["return class ", "Widget", " {}"];
const klass: any = new Function(classParts.join(""))();
console.log("FUNCTION", literal.name, dynamic.name);
console.log("CLASS", klass.name);
console.log("CONSTRUCTOR", new Function("return 1").name);
"#,
)
.expect("write fixture");

let compiler = PathBuf::from(env!("CARGO_BIN_EXE_perry"));
let compile = Command::new(compiler)
.current_dir(dir.path())
.arg("compile")
.arg(&entry)
.arg("-o")
.arg(&binary)
.arg("--no-cache")
.output()
.expect("compile fixture");
assert!(
compile.status.success(),
"compile failed\nstdout:\n{}\nstderr:\n{}",
String::from_utf8_lossy(&compile.stdout),
String::from_utf8_lossy(&compile.stderr)
);

let run = Command::new(binary)
.current_dir(dir.path())
.output()
.expect("run fixture");
assert!(
run.status.success(),
"fixture failed\nstdout:\n{}\nstderr:\n{}",
String::from_utf8_lossy(&run.stdout),
String::from_utf8_lossy(&run.stderr)
);
assert_eq!(
String::from_utf8_lossy(&run.stdout),
"FUNCTION Literal Named\nCLASS Widget\nCONSTRUCTOR anonymous\n"
);
}
Loading