From 84f9b959a9f7bc1129e0e1537aab8aff1a515780 Mon Sep 17 00:00:00 2001 From: Lucian Popescu Date: Wed, 7 Oct 2026 20:40:41 +0100 Subject: [PATCH 1/2] Translate reference to function pointer --- cpp2rust/converter/converter.cpp | 9 ++++++ .../converter/models/converter_refcount.cpp | 4 +++ tests/unit/fn_ref.cpp | 32 +++++++++++++++++++ 3 files changed, 45 insertions(+) create mode 100644 tests/unit/fn_ref.cpp diff --git a/cpp2rust/converter/converter.cpp b/cpp2rust/converter/converter.cpp index 779cfdc0b..9fdd9a259 100644 --- a/cpp2rust/converter/converter.cpp +++ b/cpp2rust/converter/converter.cpp @@ -274,6 +274,9 @@ bool Converter::VisitIncompleteArrayType(clang::IncompleteArrayType *type) { bool Converter::VisitReferenceType(clang::ReferenceType *type) { auto pointee_type = type->getPointeeType(); + if (pointee_type->isFunctionType()) { + return Convert(ctx_.getPointerType(pointee_type)); + } StrCat(pointee_type.isConstQualified() ? "*const" : "*mut"); return Convert(pointee_type); } @@ -4299,6 +4302,12 @@ std::string Converter::GetUnsafeTypeAsString(clang::QualType qual_type) const { } void Converter::ConvertVarInit(clang::QualType qual_type, clang::Expr *expr) { + if (qual_type->isFunctionReferenceType()) { + PushExprKind push(*this, ExprKind::AddrOf); + PushInitType init_type(*this, qual_type); + Convert(expr); + return; + } if (qual_type->isReferenceType() && !IsReferenceType(expr)) { if (llvm::isa(expr->IgnoreImpCasts())) { StrCat(EmitMaterializedTempBinding(qual_type, expr)); diff --git a/cpp2rust/converter/models/converter_refcount.cpp b/cpp2rust/converter/models/converter_refcount.cpp index fe2909e39..87129e5e6 100644 --- a/cpp2rust/converter/models/converter_refcount.cpp +++ b/cpp2rust/converter/models/converter_refcount.cpp @@ -286,6 +286,10 @@ bool ConverterRefCount::VisitIncompleteArrayType( bool ConverterRefCount::VisitReferenceType(clang::ReferenceType *type) { auto pointee_type = type->getPointeeType(); + if (pointee_type->isFunctionType()) { + PushConversionKind push(*this, ConversionKind::Unboxed); + return Convert(ctx_.getPointerType(pointee_type)); + } if (pointee_type->isArrayType()) { // A reference to an array decays straight to a pointer to its first // element, the same way a by-value array parameter would, instead of diff --git a/tests/unit/fn_ref.cpp b/tests/unit/fn_ref.cpp new file mode 100644 index 000000000..22cf8ac7e --- /dev/null +++ b/tests/unit/fn_ref.cpp @@ -0,0 +1,32 @@ +#include + +static int square(int x) { return x * x; } + +static int twice(int x) { return 2 * x; } + +static int call_ref(int (&f)(int), int x) { return f(x); } + +template static int call_deduced(F &f, int x) { return f(x); } + +template static int call_forwarded(F &&f, int x) { return f(x); } + +struct Holder { + int (&f)(int); + int run(int x) const { return f(x); } +}; + +int main() { + assert(call_ref(square, 3) == 9); + assert(call_ref(twice, 3) == 6); + + int (&r)(int) = square; + assert(r(4) == 16); + assert(call_ref(r, 5) == 25); + + assert(call_deduced(twice, 7) == 14); + assert(call_forwarded(square, 6) == 36); + + Holder h{twice}; + assert(h.run(8) == 16); + return 0; +} From b91ca5b73e7af05f8fad2adfa312a254921704f8 Mon Sep 17 00:00:00 2001 From: Lucian Popescu Date: Wed, 7 Oct 2026 22:54:14 +0100 Subject: [PATCH 2/2] Update tests --- tests/unit/out/refcount/fn_ref.rs | 57 +++++++++++++++++++++++++++++++ tests/unit/out/unsafe/fn_ref.rs | 52 ++++++++++++++++++++++++++++ 2 files changed, 109 insertions(+) create mode 100644 tests/unit/out/refcount/fn_ref.rs create mode 100644 tests/unit/out/unsafe/fn_ref.rs diff --git a/tests/unit/out/refcount/fn_ref.rs b/tests/unit/out/refcount/fn_ref.rs new file mode 100644 index 000000000..70b1f00b9 --- /dev/null +++ b/tests/unit/out/refcount/fn_ref.rs @@ -0,0 +1,57 @@ +extern crate libcc2rs; +use libcc2rs::*; +use std::cell::RefCell; +use std::collections::BTreeMap; +use std::io::prelude::*; +use std::io::{Read, Seek, Write}; +use std::os::fd::AsFd; +use std::rc::{Rc, Weak}; +pub fn square_0(mut x: i32) -> i32 { + return (x * x); +} +pub fn twice_1(mut x: i32) -> i32 { + return (2 * x); +} +pub fn call_ref_2(f: FnPtr i32>, mut x: i32) -> i32 { + return ({ f.call(x) }); +} +pub fn call_deduced_3(f: FnPtr i32>, mut x: i32) -> i32 { + return ({ f.call(x) }); +} +pub fn call_forwarded_4(f: FnPtr i32>, mut x: i32) -> i32 { + return ({ f.call(x) }); +} +#[derive(Clone, Record, ByteRepr, VaArg, FnPtrArg, Default)] +#[byte_size(8)] +pub struct Holder { + #[offset(0)] + #[byte_size(8)] + pub f: FnPtr i32>, +} +pub fn main() { + __cpp2rust_init_globals(); + std::process::exit(main_0()); +} +fn main_0() -> i32 { + assert!((({ call_ref_2(FnPtr:: i32>::new(square_0), 3,) }) == 9)); + assert!((({ call_ref_2(FnPtr:: i32>::new(twice_1), 3,) }) == 6)); + let r: FnPtr i32> = FnPtr:: i32>::new(square_0); + assert!((({ r.call(4,) }) == 16)); + assert!((({ call_ref_2((r).clone(), 5,) }) == 25)); + assert!((({ call_deduced_3(FnPtr:: i32>::new(twice_1), 7,) }) == 14)); + assert!((({ call_forwarded_4(FnPtr:: i32>::new(square_0), 6,) }) == 36)); + let h: Value = Rc::new(RefCell::new(Holder { + f: FnPtr:: i32>::new(twice_1), + })); + assert!((({ HolderImpl::run(&h.as_pointer(), 8,) }) == 16)); + return 0; +} +pub trait HolderImpl { + fn run(&self, x: i32) -> i32; +} +impl HolderImpl for Ptr { + fn run(&self, mut x: i32) -> i32 { + return ({ (*self).with(|__s| __s.f.clone()).call(x) }); + } +} +pub fn __cpp2rust_init_globals() {} diff --git a/tests/unit/out/unsafe/fn_ref.rs b/tests/unit/out/unsafe/fn_ref.rs new file mode 100644 index 000000000..9f591019f --- /dev/null +++ b/tests/unit/out/unsafe/fn_ref.rs @@ -0,0 +1,52 @@ +extern crate libc; +use libc::*; +extern crate libcc2rs; +use libcc2rs::*; +use std::collections::BTreeMap; +use std::io::{Read, Seek, Write}; +use std::os::fd::{AsFd, FromRawFd, IntoRawFd}; +use std::rc::Rc; +pub unsafe fn square_0(mut x: i32) -> i32 { + return ((x) * (x)); +} +pub unsafe fn twice_1(mut x: i32) -> i32 { + return ((2) * (x)); +} +pub unsafe fn call_ref_2(f: Option i32>, mut x: i32) -> i32 { + return (unsafe { (f).unwrap()(x) }); +} +pub unsafe fn call_deduced_3(f: Option i32>, mut x: i32) -> i32 { + return (unsafe { (f).unwrap()(x) }); +} +pub unsafe fn call_forwarded_4(f: Option i32>, mut x: i32) -> i32 { + return (unsafe { (f).unwrap()(x) }); +} +#[repr(C)] +#[derive(Copy, Clone, VaArg, FnPtrArg, Default)] +pub struct Holder { + pub f: Option i32>, +} +impl Holder { + pub unsafe fn run(&self, mut x: i32) -> i32 { + return (unsafe { (self.f).unwrap()(x) }); + } +} +pub fn main() { + unsafe { + __cpp2rust_init_globals(); + std::process::exit(main_0() as i32); + } +} +unsafe fn main_0() -> i32 { + assert!(((unsafe { call_ref_2(Some(square_0), 3,) }) == (9))); + assert!(((unsafe { call_ref_2(Some(twice_1), 3,) }) == (6))); + let r: Option i32> = Some(square_0); + assert!(((unsafe { (r).unwrap()(4,) }) == (16))); + assert!(((unsafe { call_ref_2(r, 5,) }) == (25))); + assert!(((unsafe { call_deduced_3(Some(twice_1), 7,) }) == (14))); + assert!(((unsafe { call_forwarded_4(Some(square_0), 6,) }) == (36))); + let mut h: Holder = Holder { f: Some(twice_1) }; + assert!(((unsafe { Holder::run(&h, 8,) }) == (16))); + return 0; +} +pub unsafe fn __cpp2rust_init_globals() {}