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
34 changes: 33 additions & 1 deletion crates/perry-codegen/src/codegen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,19 @@ pub struct CompileOptions {
/// `perry_fn_<source_prefix>__<funcname>`. Built by the CLI driver
/// from each module's `hir.imports` table.
pub import_function_prefixes: std::collections::HashMap<String, String>,
/// Issue #678: for imports that traverse a re-export rename
/// (e.g. `export { default as render } from './render.js'`), maps the
/// consumer-visible name (`render`) to the actual export name in the
/// origin module (`default`). Used by every `perry_fn_<src>__<name>`
/// symbol-construction site so the suffix matches what the origin
/// module actually emits. Absent entries (the common case) mean the
/// name in origin matches the consumer's imported name; callers should
/// treat a missing entry as identity. Without this, `import { Box }
/// from "ink"` lowered to `perry_fn_<components_Box_js>__Box` but the
/// origin module emitted `perry_fn_<components_Box_js>__default` (Box.js
/// has `const Box = ...; export default Box`), and the linker failed
/// with `Undefined symbols: _perry_fn_..._Box`.
pub import_function_origin_names: std::collections::HashMap<String, String>,
/// Issue #680: per-namespace member resolution. Keyed by
/// `(namespace_local_name, member_name)` → `source_prefix`. Used by
/// the namespace-member access lowering paths in `expr.rs` and
Expand Down Expand Up @@ -384,6 +397,10 @@ pub(crate) struct CrossModuleCtx {
pub local_async_funcs: std::collections::HashSet<u32>,
pub type_aliases: std::collections::HashMap<String, perry_types::Type>,
pub imported_func_param_counts: std::collections::HashMap<String, usize>,
/// Issue #678: see `CompileOptions::import_function_origin_names`.
/// Cloned from the same field so codegen helpers reachable via
/// `CrossModuleCtx` can resolve the origin name without an extra arg.
pub import_function_origin_names: std::collections::HashMap<String, String>,
/// Issue #608 — imported function names whose source-side signature
/// has a trailing `...rest` parameter. Used by the cross-module call
/// site in `lower_call.rs` to pack trailing args into a rest array.
Expand Down Expand Up @@ -1183,6 +1200,7 @@ pub fn compile_module(hir: &HirModule, opts: CompileOptions) -> Result<Vec<u8>>
local_async_funcs,
type_aliases: opts.type_aliases,
imported_func_param_counts: opts.imported_func_param_counts,
import_function_origin_names: opts.import_function_origin_names.clone(),
imported_func_has_rest: opts.imported_func_has_rest,
imported_func_return_types: opts.imported_func_return_types,
method_param_counts,
Expand Down Expand Up @@ -2716,7 +2734,15 @@ pub fn compile_module(hir: &HirModule, opts: CompileOptions) -> Result<Vec<u8>>
llmod.add_internal_constant(&global_name, "{ ptr, i32, i32 }", &init);
continue;
}
let target_name = format!("perry_fn_{}__{}", source_prefix, name);
// Issue #678: when a re-export rename routes this name to an
// origin export with a different suffix (`export { default as
// render }`), call into the origin's real symbol — `perry_fn_<
// src>__default`, not `perry_fn_<src>__render`. The local
// wrapper still uses the consumer-visible name so this
// module's own callers can find it.
let origin_suffix =
crate::expr::import_origin_suffix(&cross_module.import_function_origin_names, name);
let target_name = format!("perry_fn_{}__{}", source_prefix, origin_suffix);
// Look up the param count from the import metadata. Fall back
// to 0 if missing — emits a no-arg wrapper, which is wrong
// for nonzero-arity functions but won't break compilation.
Expand Down Expand Up @@ -3146,6 +3172,7 @@ fn compile_function(
methods,
module_globals,
import_function_prefixes,
import_function_origin_names: &cross_module.import_function_origin_names,
closure_captures: HashMap::new(),
current_closure_ptr: None,
enums,
Expand Down Expand Up @@ -3523,6 +3550,7 @@ fn compile_closure(
methods,
module_globals,
import_function_prefixes,
import_function_origin_names: &cross_module.import_function_origin_names,
closure_captures,
current_closure_ptr: Some("%this_closure".to_string()),
enums,
Expand Down Expand Up @@ -3758,6 +3786,7 @@ fn compile_method(
methods,
module_globals,
import_function_prefixes,
import_function_origin_names: &cross_module.import_function_origin_names,
closure_captures: HashMap::new(),
current_closure_ptr: None,
enums,
Expand Down Expand Up @@ -4231,6 +4260,7 @@ fn compile_module_entry(
methods,
module_globals,
import_function_prefixes,
import_function_origin_names: &cross_module.import_function_origin_names,
closure_captures: HashMap::new(),
current_closure_ptr: None,
enums,
Expand Down Expand Up @@ -4585,6 +4615,7 @@ fn compile_module_entry(
methods,
module_globals,
import_function_prefixes,
import_function_origin_names: &cross_module.import_function_origin_names,
closure_captures: HashMap::new(),
current_closure_ptr: None,
enums,
Expand Down Expand Up @@ -5326,6 +5357,7 @@ fn compile_static_method(
methods,
module_globals,
import_function_prefixes,
import_function_origin_names: &cross_module.import_function_origin_names,
closure_captures: HashMap::new(),
current_closure_ptr: None,
enums,
Expand Down
54 changes: 48 additions & 6 deletions crates/perry-codegen/src/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,24 @@ pub(crate) fn nanbox_bigint_inline(blk: &mut LlBlock, ptr_i64: &str) -> String {
blk.bitcast_i64_to_double(&tagged)
}

/// Issue #678: resolve the actual symbol-suffix for an imported name.
///
/// Re-export renames like `export { default as render } from './render.js'`
/// mean the consumer sees `render` while the origin module emits
/// `perry_fn_<origin>__default`. The `import_function_origin_names` map
/// records this rename so every `perry_fn_<src>__<suffix>` construction
/// site can pick the right suffix instead of the consumer-visible name.
///
/// Returns the override when present, falling back to `name` (the common
/// case where origin name == consumer name). Used by every codegen
/// path that builds a `perry_fn_<src>__<name>` extern symbol.
pub(crate) fn import_origin_suffix<'a>(
origin_names: &'a std::collections::HashMap<String, String>,
name: &'a str,
) -> &'a str {
origin_names.get(name).map(String::as_str).unwrap_or(name)
}

/// If `callee` is a `new`-target whose class name is statically
/// known, return that name. Used by the `Expr::NewDynamic` lowering
/// to reroute statically-resolvable shapes to the regular `lower_new`
Expand Down Expand Up @@ -222,6 +240,15 @@ pub(crate) struct FnCtx<'a> {
/// `ExternFuncRef` lowering in `lower_call` to generate scoped
/// cross-module calls.
pub import_function_prefixes: &'a std::collections::HashMap<String, String>,
/// Issue #678: Imported function name → original export name in the
/// origin module. Set when the import traverses a re-export rename
/// (`export { default as render } from './render.js'`). Looked up at
/// every `perry_fn_<source_prefix>__<suffix>` construction site to
/// pick the right suffix. Absent entries (the common case) mean the
/// origin name matches the consumer's imported name; callers should
/// treat a missing entry as identity by calling
/// `import_origin_suffix(import_function_origin_names, name)`.
pub import_function_origin_names: &'a std::collections::HashMap<String, String>,
/// Closure capture map: when lowering inside a closure body, this
/// holds `LocalId → capture_index`. `LocalGet`/`LocalSet`/`Update`
/// of an id in this map routes through the runtime
Expand Down Expand Up @@ -3663,12 +3690,16 @@ pub(crate) fn lower_expr(ctx: &mut FnCtx<'_>, expr: &Expr) -> Result<String> {
// body. The body only runs later when the consumer
// actually calls `HashMap.keySet(self)`, by which time
// both modules have finished `__init`.
// Issue #678: re-export renames mean the suffix in the
// origin module differs from the consumer-visible name.
let origin_suffix =
import_origin_suffix(ctx.import_function_origin_names, property);
if ctx.imported_vars.contains(property) {
let getter = format!("perry_fn_{}__{}", source_prefix, property);
let getter = format!("perry_fn_{}__{}", source_prefix, origin_suffix);
ctx.pending_declares.push((getter.clone(), DOUBLE, vec![]));
return Ok(ctx.block().call(DOUBLE, &getter, &[]));
}
let target_name = format!("perry_fn_{}__{}", source_prefix, property);
let target_name = format!("perry_fn_{}__{}", source_prefix, origin_suffix);
let wrap_name = format!("__perry_wrap_{}", target_name);
let param_count = ctx
.imported_func_param_counts
Expand Down Expand Up @@ -3699,7 +3730,11 @@ pub(crate) fn lower_expr(ctx: &mut FnCtx<'_>, expr: &Expr) -> Result<String> {
// object stored in the module's export global.
if let Expr::ExternFuncRef { name, .. } = object.as_ref() {
if let Some(source_prefix) = ctx.import_function_prefixes.get(name).cloned() {
let getter = format!("perry_fn_{}__{}", source_prefix, name);
// Issue #678: re-export renames mean the suffix in the
// origin module differs from the consumer-visible name.
let origin_suffix =
import_origin_suffix(ctx.import_function_origin_names, name);
let getter = format!("perry_fn_{}__{}", source_prefix, origin_suffix);
ctx.pending_declares.push((getter.clone(), DOUBLE, vec![]));
let obj_val = ctx.block().call(DOUBLE, &getter, &[]);
// Now do property access on the actual object.
Expand Down Expand Up @@ -6060,7 +6095,11 @@ pub(crate) fn lower_expr(ctx: &mut FnCtx<'_>, expr: &Expr) -> Result<String> {
if ctx.namespace_imports.contains(class_name) {
if let Some(source_prefix) = ctx.import_function_prefixes.get(method_name).cloned()
{
let fn_name = format!("perry_fn_{}__{}", source_prefix, method_name);
// Issue #678: namespace member resolved through a re-export
// rename uses the origin name as the symbol suffix.
let origin_suffix =
import_origin_suffix(ctx.import_function_origin_names, method_name);
let fn_name = format!("perry_fn_{}__{}", source_prefix, origin_suffix);
let mut lowered: Vec<String> = Vec::with_capacity(args.len());
for a in args {
lowered.push(lower_expr(ctx, a)?);
Expand Down Expand Up @@ -11164,12 +11203,15 @@ pub(crate) fn lower_expr(ctx: &mut FnCtx<'_>, expr: &Expr) -> Result<String> {
return Ok(double_literal(f64::from_bits(bits)));
}
if let Some(source_prefix) = ctx.import_function_prefixes.get(name).cloned() {
// Issue #678: re-export renames mean the origin's symbol uses
// the *origin* name as the suffix, not the consumer-visible one.
let origin_suffix = import_origin_suffix(ctx.import_function_origin_names, name);
// Imported VARIABLES (exported consts/lets) need to be
// called through their getter to fetch the value, not
// wrapped as closures. Without this, `let v = HONE_VERSION`
// creates a closure wrapper instead of the actual string.
if ctx.imported_vars.contains(name) {
let fname = format!("perry_fn_{}__{}", source_prefix, name);
let fname = format!("perry_fn_{}__{}", source_prefix, origin_suffix);
ctx.pending_declares.push((fname.clone(), DOUBLE, vec![]));
return Ok(ctx.block().call(DOUBLE, &fname, &[]));
}
Expand Down Expand Up @@ -11200,7 +11242,7 @@ pub(crate) fn lower_expr(ctx: &mut FnCtx<'_>, expr: &Expr) -> Result<String> {
// `__perry_extern_closure_<src>__<name>` global is no
// longer referenced and link-time DCE strips it.
// Refs #645 deeper followup / #488 drizzle-sqlite.
let target_name = format!("perry_fn_{}__{}", source_prefix, name);
let target_name = format!("perry_fn_{}__{}", source_prefix, origin_suffix);
let wrap_name = format!("__perry_wrap_{}", target_name);
// Declare the source's wrapper so LLVM accepts the
// `@<wrap_name>` reference. Signature mirrors the
Expand Down
18 changes: 16 additions & 2 deletions crates/perry-codegen/src/lower_call.rs
Original file line number Diff line number Diff line change
Expand Up @@ -560,7 +560,14 @@ pub(crate) fn lower_call(ctx: &mut FnCtx<'_>, callee: &Expr, args: &[Expr]) -> R
.cloned()
.or_else(|| ctx.import_function_prefixes.get(property).cloned())
{
let symbol = format!("perry_fn_{}__{}", source_prefix, property);
// Issue #678: re-exported names (e.g. `export { default as
// render }`) emit `perry_fn_<src>__default` in the origin —
// resolve the actual origin suffix before forming the symbol.
let origin_suffix = crate::expr::import_origin_suffix(
ctx.import_function_origin_names,
property,
);
let symbol = format!("perry_fn_{}__{}", source_prefix, origin_suffix);
if ctx.imported_vars.contains(property) {
// Var-shaped export: fetch closure via zero-arg
// getter, then closure-call with the user args.
Expand Down Expand Up @@ -1077,7 +1084,14 @@ pub(crate) fn lower_call(ctx: &mut FnCtx<'_>, callee: &Expr, args: &[Expr]) -> R
return Ok(ctx.block().call(DOUBLE, name, &arg_slices));
}
};
let fname = format!("perry_fn_{}__{}", source_prefix, name);
// Issue #678: re-export rename (`export { default as render } from
// './render.js'`) means the origin module emits the symbol under
// the *origin* name (`default`), not the consumer-visible name
// (`render`). Look up the actual origin suffix before forming the
// extern.
let origin_suffix =
crate::expr::import_origin_suffix(ctx.import_function_origin_names, name);
let fname = format!("perry_fn_{}__{}", source_prefix, origin_suffix);
// Issue #493 followup: when the imported binding is a VARIABLE
// holding a closure value (e.g. `var mergePath = (b, s, ...r) => …`
// exported from another module), `perry_fn_<src>__<name>` is the
Expand Down
Loading
Loading