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
3 changes: 3 additions & 0 deletions changelog.d/11009-cjs-function-names.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
### Fixed

- Preserve the declared `.name` of nested function constructors exported from CommonJS modules, including values built by a factory and inspected through `constructor.name`.
5 changes: 5 additions & 0 deletions crates/perry-hir/src/lower_decl/body_stmt/nested_fn_decl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,11 @@ pub(super) fn lower_nested_fn_decl(
// tag has to be applied here too.
ctx.function_valued_locals.insert(local_id);

// Nested declarations become inline closures rather than entries in
// `module.functions`. Preserve the declared name for function.name;
// codegen cannot infer it from a Let inside another closure's body.
ctx.closure_display_names.insert(func_id, func_name.clone());

let closure = Expr::Closure {
func_id,
params,
Expand Down
66 changes: 66 additions & 0 deletions crates/perry/tests/issue_10702_cjs_function_name.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
//! A CJS default export preserves the name of its function constructor.

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

#[test]
fn imported_cjs_function_constructor_keeps_its_name() {
let dir = tempfile::tempdir().expect("tempdir");
let entry = dir.path().join("main.ts");
let library = dir.path().join("lib.cjs");
let binary = dir.path().join("main_bin");
std::fs::write(
&library,
r#"
function clone() {
function Ledger(value) { this.value = value; }
Ledger.prototype = { constructor: Ledger, getValue() { return this.value; } };
return Ledger;
}
module.exports = clone();
"#,
)
.expect("write CJS library");
std::fs::write(
&entry,
r#"
import Ledger from "./lib.cjs";
const value = new Ledger(5);
console.log("NAMES", Ledger.name, value.constructor.name);
console.log("VALUE", value instanceof Ledger, value.getValue());
"#,
)
.expect("write entry");

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),
"NAMES Ledger Ledger\nVALUE true 5\n"
);
}
Loading