Allow function pointers with references inside the function signature.
Without this change, because we change the reference to a pointer, the type doesn't line up:
```
error: cannot initialize return object of type 'crubit::t
ype_identity_t<int *(const int *, int *)> *' (aka 'int *(*)(const int *, int *)') with an rvalue of type 'int &(*)(const int &, int *)': type mismatch at 1st parameter
('const int *' vs 'const int &')
```
I was worried this would mean we'd have trouble with `-Wreturn-type-c-linkage`, but surprisingly, it works fine:
https://godbolt.org/z/njabjEfTK
(As you can see, and as the name implies, it also works fine with reference parameters, but presumably there's another warning that fails there.)
I didn't add any other tests because really "does it compile?" is the main thing. This failed before, succeeds now.
PiperOrigin-RevId: 478958712
diff --git a/rs_bindings_from_cc/src_code_gen.rs b/rs_bindings_from_cc/src_code_gen.rs
index 91f25f6..1a9c82e 100644
--- a/rs_bindings_from_cc/src_code_gen.rs
+++ b/rs_bindings_from_cc/src_code_gen.rs
@@ -3103,6 +3103,13 @@
}
fn format_cc_type(ty: &ir::CcType, ir: &IR) -> Result<TokenStream> {
+ // Formatting *both* pointers *and* references as pointers, because:
+ // - Pointers and references have the same representation in the ABI.
+ // - Clang's `-Wreturn-type-c-linkage` warns when using references in C++
+ // function thunks declared as `extern "C"` (see b/238681766).
+ format_cc_type_inner(ty, ir, /* references_ok= */ false)
+}
+fn format_cc_type_inner(ty: &ir::CcType, ir: &IR, references_ok: bool) -> Result<TokenStream> {
let const_fragment = if ty.is_const {
quote! {const}
} else {
@@ -3110,16 +3117,21 @@
};
if let Some(ref name) = ty.name {
match name.as_str() {
- // Formatting *both* pointers *and* references as pointers, because:
- // - Pointers and references have the same representation in the ABI.
- // - Clang's `-Wreturn-type-c-linkage` warns when using references in C++ function
- // thunks declared as `extern "C"` (see b/238681766).
- "*" | "&" | "&&" => {
+ mut name @ ("*" | "&" | "&&") => {
if ty.type_args.len() != 1 {
bail!("Invalid pointer type (need exactly 1 type argument): {:?}", ty);
}
- let nested_type = format_cc_type(&ty.type_args[0], ir)?;
- Ok(quote! {#nested_type * #const_fragment})
+ let nested_type = format_cc_type_inner(&ty.type_args[0], ir, references_ok)?;
+ if !references_ok {
+ name = "*";
+ }
+ let ptr = match name {
+ "*" => quote! {*},
+ "&" => quote! {&},
+ "&&" => quote! {&&},
+ _ => unreachable!(),
+ };
+ Ok(quote! {#nested_type #ptr #const_fragment})
}
cc_type_name => match cc_type_name.strip_prefix("#funcValue ") {
None => {
@@ -3132,10 +3144,14 @@
Some(abi) => match ty.type_args.split_last() {
None => bail!("funcValue type without a return type: {:?}", ty),
Some((ret_type, param_types)) => {
- let ret_type = format_cc_type(ret_type, ir)?;
+ // Function pointer types don't ignore references, but luckily,
+ // `-Wreturn-type-c-linkage` does. So we can just re-enable references now
+ // so that the function type is exactly correct.
+ let ret_type =
+ format_cc_type_inner(ret_type, ir, /* references_ok= */ true)?;
let param_types = param_types
.iter()
- .map(|t| format_cc_type(t, ir))
+ .map(|t| format_cc_type_inner(t, ir, /* references_ok= */ true))
.collect::<Result<Vec<_>>>()?;
let attr = format_cc_call_conv_as_clang_attribute(abi)?;
// `type_identity_t` is used below to avoid having to
diff --git a/rs_bindings_from_cc/test/golden/types.h b/rs_bindings_from_cc/test/golden/types.h
index 881efd9..fbe42cc 100644
--- a/rs_bindings_from_cc/test/golden/types.h
+++ b/rs_bindings_from_cc/test/golden/types.h
@@ -87,4 +87,12 @@
enum Color : unsigned int { kRed, kBlue, kLimeGreen = 4294967295 };
+// Note especially the use of references. If we convert those to pointers,
+// this becomes un-compilable. The syntax here is awful, but this is a function
+// returning a function. In ML-like syntax:
+// FunctionPointerReturningFunction : () -> (const int&, int*) -> int&
+inline int& (*FunctionPointerReturningFunction())(const int&, int*) {
+ return nullptr;
+}
+
#endif // CRUBIT_RS_BINDINGS_FROM_CC_TEST_GOLDEN_TYPES_H_
diff --git a/rs_bindings_from_cc/test/golden/types_rs_api.rs b/rs_bindings_from_cc/test/golden/types_rs_api.rs
index 90f2102..c823bcd 100644
--- a/rs_bindings_from_cc/test/golden/types_rs_api.rs
+++ b/rs_bindings_from_cc/test/golden/types_rs_api.rs
@@ -153,6 +153,16 @@
}
}
+/// Note especially the use of references. If we convert those to pointers,
+/// this becomes un-compilable. The syntax here is awful, but this is a function
+/// returning a function. In ML-like syntax:
+/// FunctionPointerReturningFunction : () -> (const int&, int*) -> int&
+#[inline(always)]
+pub fn FunctionPointerReturningFunction() -> Option<extern "C" fn(*const i32, *mut i32) -> *mut i32>
+{
+ unsafe { crate::detail::__rust_thunk___Z32FunctionPointerReturningFunctionv() }
+}
+
// CRUBIT_RS_BINDINGS_FROM_CC_TEST_GOLDEN_TYPES_H_
#[::ctor::recursively_pinned]
@@ -263,6 +273,8 @@
__param_0: ::ctor::RvalueReference<'b, crate::FieldTypeTestStruct>,
);
pub(crate) fn __rust_thunk___Z21VoidReturningFunctionv();
+ pub(crate) fn __rust_thunk___Z32FunctionPointerReturningFunctionv()
+ -> Option<extern "C" fn(*const i32, *mut i32) -> *mut i32>;
}
}
diff --git a/rs_bindings_from_cc/test/golden/types_rs_api_impl.cc b/rs_bindings_from_cc/test/golden/types_rs_api_impl.cc
index 15f2efb..9003fca 100644
--- a/rs_bindings_from_cc/test/golden/types_rs_api_impl.cc
+++ b/rs_bindings_from_cc/test/golden/types_rs_api_impl.cc
@@ -25,6 +25,10 @@
extern "C" void __rust_thunk___Z21VoidReturningFunctionv() {
VoidReturningFunction();
}
+extern "C" crubit::type_identity_t<int&(int const&, int*)>*
+__rust_thunk___Z32FunctionPointerReturningFunctionv() {
+ return FunctionPointerReturningFunction();
+}
static_assert(sizeof(struct std::integral_constant<bool, false>) == 1);
static_assert(alignof(struct std::integral_constant<bool, false>) == 1);