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
3 changes: 3 additions & 0 deletions transpiler/src/codegen/emit_expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20863,6 +20863,9 @@ impl CodeGen {
.iter()
.enumerate()
.map(|(idx, arg)| {
if let Some(array_lvalue) = self.try_emit_fixed_array_local_borrow_call_arg(arg) {
return array_lvalue;
}
let declared_arg_expected_ty =
self.lookup_function_arg_expected_type(call.func.as_ref(), idx);
if self
Expand Down
37 changes: 37 additions & 0 deletions transpiler/src/codegen/emit_stmt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3020,6 +3020,12 @@ impl CodeGen {
pub(super) fn emit_local(&mut self, local: &syn::Local) {
let pat = &local.pat;
self.register_local_binding_pattern(pat);
// Preserve repeat-expression emission as `rusty::array_repeat(...)`,
// but retain its unambiguous fixed-array type for later call-argument
// lowering. This must be recorded after emitting the initializer: using
// it as the initializer's expected type selects the typed std::array
// materialization path instead.
let deferred_fixed_array_repeat_ty = self.fixed_array_repeat_local_type(local);
// `let PAT = EXPR else { DIVERGE };` — the diverge block was silently
// DROPPED (the None path aborted through an unguarded unwrap) and the
// matched-path bindings dangled (auto&& over an unwrap() prvalue).
Expand Down Expand Up @@ -5678,6 +5684,37 @@ impl CodeGen {
}
}
}
if let Some(ty) = deferred_fixed_array_repeat_ty {
self.update_local_binding_type_for_pattern(pat, ty);
}
}

/// Infer the source-level fixed array type of a simple local initialized by
/// `[seed; len]`. This is deliberately narrower than general initializer
/// inference: it exists so a later `&local` / `&mut local` can preserve the
/// C++ `std::array` lvalue ABI without changing repeat-expression emission.
fn fixed_array_repeat_local_type(&self, local: &syn::Local) -> Option<syn::Type> {
let syn::Pat::Ident(pat_ident) = &local.pat else {
return None;
};
if pat_ident.subpat.is_some() {
return None;
}
let init = local.init.as_ref()?;
let syn::Expr::Repeat(repeat) = self.peel_paren_group_expr(&init.expr) else {
return None;
};
let elem_ty = self
.infer_simple_expr_type(&repeat.expr)
.or_else(|| self.infer_local_binding_type_from_initializer(&repeat.expr))?;
let len = &repeat.len;
syn::parse2::<syn::Type>(quote::quote!([#elem_ty; #len])).ok()
}

fn update_local_binding_type_for_pattern(&mut self, pat: &syn::Pat, ty: syn::Type) {
if let syn::Pat::Ident(pat_ident) = pat {
self.update_local_binding_type(pat_ident.ident.to_string(), ty);
}
}

pub(super) fn emit_if_expr_to_string(
Expand Down
37 changes: 37 additions & 0 deletions transpiler/src/codegen/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13171,6 +13171,40 @@ impl CodeGen {
}
}

/// C++ represents Rust fixed arrays as `std::array`, whose references bind
/// directly to the array lvalue. Do not route a borrow of a local fixed
/// array through the generic pointer/span lowering: that loses compatibility
/// with cross-module `&[T; N]` parameters, which retain their
/// `std::array<T, N>&` ABI.
///
/// Restrict this to stable *local* paths. Fields, indexes, raw pointers,
/// literals, and other rvalues retain their existing borrow lowering.
fn try_emit_fixed_array_local_borrow_call_arg(&self, arg: &syn::Expr) -> Option<String> {
let syn::Expr::Reference(reference) = self.peel_paren_group_expr(arg) else {
return None;
};
if !self.is_stable_reference_lvalue_expr(&reference.expr) {
return None;
}
let syn::Expr::Path(path) = self.peel_paren_group_expr(&reference.expr) else {
return None;
};
if path.qself.is_some() || path.path.segments.len() != 1 {
return None;
}
let local_name = path.path.segments[0].ident.to_string();
let local_ty = self
.lookup_local_binding_type(&local_name)
.or_else(|| self.infer_simple_expr_type(&reference.expr));
if !local_ty
.as_ref()
.is_some_and(|ty| self.type_is_fixed_array_like(ty))
{
return None;
}
Some(self.emit_expr_to_string_with_expected(&reference.expr, None))
}

fn emit_deserializer_call_arg(&self, expr: &syn::Expr) -> String {
if let syn::Expr::Reference(reference) = self.peel_paren_group_expr(expr)
&& reference.mutability.is_some()
Expand Down Expand Up @@ -13225,6 +13259,9 @@ impl CodeGen {
if let Some(wrapped) = self.try_emit_interface_traits_dyn_ref_coercion(arg, expected_ty) {
return wrapped;
}
if let Some(array_lvalue) = self.try_emit_fixed_array_local_borrow_call_arg(arg) {
return array_lvalue;
}

let arg_is_closure = matches!(self.peel_paren_group_expr(arg), syn::Expr::Closure(_));
let suppress_placeholder_expected_for_closure = arg_is_closure
Expand Down
76 changes: 76 additions & 0 deletions transpiler/tests/e2e_basic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,82 @@ fn test_crate_mode_basic() {
assert!(stdout.contains("2 files transpiled"));
}

// Regression fixture for srpc.wire's cross-module fixed-array borrow ABI.
//
// A cross-module callee exposes `&[u8; N]` as `const std::array<...>&`; both
// shared and mutable borrows of an array local must therefore lower to the bare
// lvalue. The same-module case does not cover this because its callers are tests.
#[test]
fn test_crate_mode_cross_module_fixed_array_borrow_abi() {
let dir = tempfile::tempdir().unwrap();
let src_dir = dir.path().join("src");
std::fs::create_dir(&src_dir).unwrap();

std::fs::write(
dir.path().join("Cargo.toml"),
"[package]\nname = \"srpc_wire_array_borrow\"\nversion = \"0.1.0\"\nedition = \"2021\"\n\n[lib]\nname = \"srpc_wire_array_borrow\"\n",
)
.unwrap();
std::fs::write(src_dir.join("lib.rs"), "pub mod varint;\npub mod serde;\n").unwrap();
std::fs::write(
src_dir.join("varint.rs"),
"pub fn load32(buf: &[u8; 9]) -> i32 { buf[0] as i32 }\n\
pub fn dump32(val: i32, buf: &mut [u8; 9]) -> usize { buf[0] = val as u8; 1 }\n",
)
.unwrap();
std::fs::write(
src_dir.join("serde.rs"),
"use super::varint;\n\
pub fn ser(v: i32) -> usize {\n\
let mut b = [0u8; 9];\n\
let n = varint::dump32(v, &mut b);\n\
let _ = varint::load32(&b);\n\
n\n\
}\n",
)
.unwrap();

let out_dir = dir.path().join("cpp_out");
let output = transpiler_bin()
.arg("--crate")
.arg(dir.path().join("Cargo.toml").to_str().unwrap())
.arg("--output-dir")
.arg(&out_dir)
.output()
.expect("failed to transpile crate fixture");
assert!(
output.status.success(),
"stderr: {}",
String::from_utf8_lossy(&output.stderr)
);

let varint_cpp = std::fs::read_to_string(out_dir.join("srpc_wire_array_borrow.varint.cppm"))
.unwrap();
let serde_cpp = std::fs::read_to_string(out_dir.join("srpc_wire_array_borrow.serde.cppm"))
.unwrap();

assert!(
varint_cpp.contains("const std::array<uint8_t, 9>& buf"),
"unexpected varint ABI:\n{varint_cpp}"
);
assert!(
varint_cpp.contains("std::array<uint8_t, 9>& buf"),
"unexpected varint ABI:\n{varint_cpp}"
);
assert!(
serde_cpp.contains("varint::dump32(") && serde_cpp.contains(", b)"),
"mutable array borrow must pass its bare lvalue:\n{serde_cpp}"
);
assert!(
serde_cpp.contains("varint::load32(b)"),
"shared array borrow must pass its bare lvalue:\n{serde_cpp}"
);
assert!(
!serde_cpp.contains("rusty::as_slice(b)"),
"array borrow must not become a span when the callee ABI is std::array&:\n{serde_cpp}"
);
}

#[test]
fn test_crate_mode_missing_cargo_toml() {
let dir = tempfile::tempdir().unwrap();
Expand Down