blob: 13ebc19c230276956c5de35ee532986ce6989aa3 [file]
// Part of the Crubit project, under the Apache License v2.0 with LLVM
// Exceptions. See /LICENSE for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
use crate::{
does_type_implement_trait, ensure_ty_is_pointer_like, format_cc_ident,
format_param_types_for_cc, format_ret_ty_for_cc, is_bridged_type, is_c_abi_compatible_by_value,
liberate_and_deanonymize_late_bound_regions, BridgedType, BridgedTypeConversionInfo,
FullyQualifiedName, RsSnippet,
};
use arc_anyhow::{Context, Result};
use code_gen_utils::escape_non_identifier_chars;
use code_gen_utils::make_rs_ident;
use code_gen_utils::CcConstQualifier;
use crubit_abi_type::CrubitAbiTypeToRustTokens;
use database::code_snippet::{CcPrerequisites, CcSnippet, ExternCDecl};
use database::{AdtCoreBindings, BindingsGenerator, SugaredTy};
use error_report::{anyhow, bail, ensure};
use itertools::Itertools;
use proc_macro2::{Ident, TokenStream};
use query_compiler::post_analysis_typing_env;
use quote::format_ident;
use quote::quote;
use rustc_middle::ty::{self, Ty, TyCtxt};
use rustc_span::def_id::DefId;
use rustc_span::symbol::{kw, Symbol};
use rustc_type_ir::inherent::Region;
use std::collections::{BTreeSet, HashMap};
use std::ops::AddAssign;
/// Returns a C ABI-compatible C type to pass a tuple, or `None` if `possibly_tuple_ty` is not a
/// tuple.
///
/// Tuples are passed via a pointer to an array of `void*` where
/// each pointer points to the corresponding element of the tuple.
fn tuple_c_abi_c_type(possibly_tuple_ty: ty::Ty) -> Option<TokenStream> {
let ty::TyKind::Tuple(_) = possibly_tuple_ty.kind() else { return None };
// Sized array types are sadly not usable by-pointer in C++.
Some(quote! { void** })
}
/// Returns a C ABI-compatible Rust type to pass a tuple, or `None` if `possibly_tuple_ty` is not a
/// tuple.
///
/// Tuples are passed via a pointer to an array of `*const c_void` where
/// each pointer points to the corresponding element of the tuple.
fn tuple_c_abi_rs_type(possibly_tuple_ty: ty::Ty) -> Option<TokenStream> {
let ty::TyKind::Tuple(tuple_tys) = possibly_tuple_ty.kind() else { return None };
let num_elements = tuple_tys.len();
Some(quote! { *const [*const core::ffi::c_void; #num_elements] })
}
/// Formats a C++ declaration of a C-ABI-compatible-function wrapper around a Rust function.
pub fn generate_thunk_decl<'tcx>(
db: &dyn BindingsGenerator<'tcx>,
sig_mid: &ty::FnSig<'tcx>,
sig_hir: Option<&rustc_hir::FnDecl<'tcx>>,
thunk_name: &Ident,
has_self_param: bool,
) -> Result<CcSnippet> {
let mut prereqs = CcPrerequisites::default();
let main_api_ret_type = format_ret_ty_for_cc(db, sig_mid, sig_hir)?.into_tokens(&mut prereqs);
let mut thunk_params = {
let cpp_types = format_param_types_for_cc(db, sig_mid, sig_hir, has_self_param)?;
sig_mid
.inputs()
.iter()
.zip(cpp_types.into_iter())
.map(|(&ty, cpp_type)| -> Result<TokenStream> {
let cpp_type = cpp_type.into_tokens(&mut prereqs);
if is_bridged_type(db, ty)?.is_some() {
match code_gen_utils::is_cpp_pointer_type(cpp_type.clone()) {
Some(CcConstQualifier::Mut) | Some(CcConstQualifier::Const) => {
Ok(quote! { #cpp_type })
}
None => Ok(quote! { #cpp_type* }),
}
} else if is_c_abi_compatible_by_value(ty) {
Ok(quote! { #cpp_type })
} else if let Some(tuple_abi) = tuple_c_abi_c_type(ty) {
Ok(tuple_abi)
} else if let Some(adt_def) = ty.ty_adt_def() {
let core = db.generate_adt_core(adt_def.did())?;
db.generate_move_ctor_and_assignment_operator(core).map_err(|_| {
anyhow!("Can't pass a type by value without a move constructor")
})?;
Ok(quote! { #cpp_type* })
} else {
bail!("Unknown type")
}
})
.collect::<Result<Vec<_>>>()?
};
// Types which are not C-ABI compatible by-value are returned via out-pointer parameters.
let thunk_ret_type: TokenStream;
if is_c_abi_compatible_by_value(sig_mid.output()) {
thunk_ret_type = main_api_ret_type;
} else if let Some(tuple_abi) = tuple_c_abi_c_type(sig_mid.output()) {
thunk_ret_type = quote! { void };
thunk_params.push(quote! { #tuple_abi __ret_ptr });
} else if let Some(BridgedType::Composable(_)) = is_bridged_type(db, sig_mid.output())? {
thunk_ret_type = quote! { void };
thunk_params.push(quote! { unsigned char * __ret_ptr });
} else {
thunk_ret_type = quote! { void };
thunk_params.push(quote! { #main_api_ret_type* __ret_ptr });
};
let mut attributes = vec![];
// Attribute: noreturn
let rs_return_type = SugaredTy::fn_output(sig_mid, sig_hir);
if *rs_return_type.mid().kind() == ty::TyKind::Never {
attributes.push(quote! {[[noreturn]]});
}
Ok(CcSnippet {
prereqs,
tokens: quote! {
namespace __crubit_internal {
extern "C" #(#attributes)* #thunk_ret_type #thunk_name ( #( #thunk_params ),* );
}
},
})
}
/// Creates Rust code to convert a bridged type from a C ABI type to a Rust type.
///
/// Expects an exising local of type `cpp_type` named `local_name` and shadows it
/// with a local of type `ty` named `local_name`.
fn convert_bridged_type_from_c_abi_to_rust<'tcx>(
db: &dyn BindingsGenerator<'tcx>,
ty: ty::Ty<'tcx>,
bridged_type: &BridgedType,
local_name: &Ident,
extern_c_decls: &mut BTreeSet<ExternCDecl>,
) -> Result<TokenStream> {
let rs_type = db
.format_ty_for_rs(ty)
.with_context(|| format!("Error handling parameter `{local_name}`"))?;
let temp_name = format_ident!("__crubit_temp");
match bridged_type {
BridgedType::Legacy { conversion_info, .. } => {
let convert = match conversion_info {
BridgedTypeConversionInfo::PointerLikeTransmute => quote! {
#temp_name.write(::core::mem::transmute(#local_name));
},
BridgedTypeConversionInfo::ExternCFuncConverters {
cpp_to_rust_converter, ..
} => {
let cpp_to_rust_converter_ident = add_extern_c_decl(
extern_c_decls,
ExternCDeclKind::CppToRustConverter,
*cpp_to_rust_converter,
);
quote! {
#cpp_to_rust_converter_ident(#local_name,#temp_name.as_mut_ptr() as *mut core::ffi::c_void);
}
}
};
Ok(quote! {
let #local_name = {
let mut #temp_name = ::core::mem::MaybeUninit::<#rs_type>::uninit();
#convert
#temp_name.assume_init()
};
})
}
BridgedType::Composable(composable) => {
let crubit_abi_type = CrubitAbiTypeToRustTokens(&composable.crubit_abi_type);
// SAFETY: The buffer is the correct size, as determined by Crubit.
Ok(quote! {
let #local_name = unsafe { ::bridge_rust::internal::decode::<#crubit_abi_type>(#local_name) };
})
}
}
}
/// Converts a local named `local_name` from its C ABI-compatible type
/// `*const [*const core::ffi::c_void; <tuple_tys.len()>]` to a tuple of Rust types.
fn convert_tuple_from_c_abi_to_rust<'tcx>(
db: &dyn BindingsGenerator<'tcx>,
tuple_tys: &[ty::Ty<'tcx>],
local_name: &Ident,
extern_c_decls: &mut BTreeSet<ExternCDecl>,
) -> Result<TokenStream> {
let mut read_elements = Vec::with_capacity(tuple_tys.len());
for (i, element_type) in tuple_tys.iter().copied().enumerate() {
let element_c_abi_type = c_abi_for_param_type(db, element_type)?;
let element_local_name = format_ident!("{local_name}_{i}");
let from_c_abi_to_rust = convert_value_from_c_abi_to_rust(
db,
element_type,
&element_local_name,
extern_c_decls,
)?;
read_elements.push(quote! { {
let #element_local_name: #element_c_abi_type = ((*#local_name)[#i] as *const #element_c_abi_type).read();
#from_c_abi_to_rust
#element_local_name
} });
}
Ok(quote! {
let #local_name = (#(#read_elements,)*);
})
}
/// Returns code to convert a local named `local_name` from its C ABI-compatible type to its Rust
/// type.
fn convert_value_from_c_abi_to_rust<'tcx>(
db: &dyn BindingsGenerator<'tcx>,
ty: ty::Ty<'tcx>,
local_name: &Ident,
extern_c_decls: &mut BTreeSet<ExternCDecl>,
) -> Result<TokenStream> {
if let Some(bridged) = is_bridged_type(db, ty)? {
return convert_bridged_type_from_c_abi_to_rust(
db,
ty,
&bridged,
local_name,
extern_c_decls,
);
}
if is_c_abi_compatible_by_value(ty) {
return Ok(quote! {});
}
if let ty::TyKind::Tuple(tuple_tys) = ty.kind() {
return convert_tuple_from_c_abi_to_rust(db, tuple_tys, local_name, extern_c_decls);
}
// Non-C-ABI-compatible-by-value types are passed by
// `&mut MaybeUninit<T>` reference, so we need to read out the value.
Ok(quote! { let #local_name = #local_name.assume_init_read(); })
}
fn c_abi_for_param_type<'tcx>(
db: &dyn BindingsGenerator<'tcx>,
ty: ty::Ty<'tcx>,
) -> Result<TokenStream> {
if let Some(bridged) = is_bridged_type(db, ty)? {
match bridged {
BridgedType::Legacy { .. } => Ok(quote! { *const core::ffi::c_void }),
BridgedType::Composable(_) => Ok(quote! { *const core::ffi::c_uchar }),
}
} else if is_c_abi_compatible_by_value(ty) {
let rs_type = db.format_ty_for_rs(ty)?;
Ok(quote! { #rs_type })
} else if let Some(tuple_abi) = tuple_c_abi_rs_type(ty) {
Ok(quote! { #tuple_abi })
} else {
let rs_type = db.format_ty_for_rs(ty)?;
// `'static` is used to erase all lifetime parameters since C++ doesn't understand
// lifetime constraints.
Ok(quote! { &'static mut ::core::mem::MaybeUninit<#rs_type> })
}
}
#[rustversion::before(2025-03-19)]
pub(crate) fn ident_or_opt_ident(i: &rustc_span::Ident) -> Option<&rustc_span::Ident> {
Some(i)
}
#[rustversion::since(2025-03-19)]
pub(crate) fn ident_or_opt_ident(i: &Option<rustc_span::Ident>) -> Option<&rustc_span::Ident> {
i.as_ref()
}
/// Returns an iterator which yields arbitrary unique names for the parameters
/// of the function identified by `fn_def_id`.
pub fn thunk_param_names(
tcx: ty::TyCtxt<'_>,
fn_def_id: DefId,
) -> impl Iterator<Item = Ident> + '_ {
tcx.fn_arg_idents(fn_def_id).iter().enumerate().map(|(i, ident)| {
let Some(ident) = ident_or_opt_ident(ident) else {
return format_ident!("__param_{i}");
};
// TODO(jeanpierreda): Deduplicate the logic after the next rustc rollout.
if ident.name == kw::Underscore || ident.name.is_empty() {
format_ident!("__param_{i}")
} else if ident.name == kw::SelfLower {
format_ident!("__self")
} else {
make_rs_ident(ident.as_str())
}
})
}
enum ExternCDeclKind {
/// The function is a Rust to C++ converter.
RustToCppConverter,
/// The function is a C++ to Rust converter.
CppToRustConverter,
}
fn add_extern_c_decl(
extern_c_decls: &mut BTreeSet<ExternCDecl>,
kind: ExternCDeclKind,
symbol: Symbol,
) -> Ident {
let converter_ident = make_rs_ident(symbol.as_str());
let decl = match kind {
ExternCDeclKind::RustToCppConverter => {
quote! {
fn #converter_ident(
rs_in: *const core::ffi::c_void,
cpp_out: *mut core::ffi::c_void);
}
}
ExternCDeclKind::CppToRustConverter => {
quote! {
fn #converter_ident(
cpp_in: *const core::ffi::c_void,
rs_out: *mut core::ffi::c_void);
}
}
};
extern_c_decls.insert(ExternCDecl { symbol, decl });
converter_ident
}
/// Writes a Rust value out into the memory pointed to a `*mut c_void` pointed to by `c_ptr`.
fn write_rs_value_to_c_abi_ptr<'tcx>(
db: &dyn BindingsGenerator<'tcx>,
rs_value: &Ident,
c_ptr: &Ident,
rs_type: ty::Ty<'tcx>,
extern_c_decls: &mut BTreeSet<ExternCDecl>,
) -> Result<TokenStream> {
let write_directly = || -> Result<TokenStream> {
let rs_type_tokens = db.format_ty_for_rs(rs_type)?;
Ok(quote! { (#c_ptr as *mut #rs_type_tokens).write(#rs_value); })
};
Ok(if let Some(bridged_type) = is_bridged_type(db, rs_type)? {
match bridged_type {
BridgedType::Legacy { conversion_info, .. } => match conversion_info {
BridgedTypeConversionInfo::PointerLikeTransmute => {
ensure_ty_is_pointer_like(db, rs_type)?;
write_directly()?
}
BridgedTypeConversionInfo::ExternCFuncConverters {
rust_to_cpp_converter, ..
} => {
let rust_to_cpp_converter_ident = add_extern_c_decl(
extern_c_decls,
ExternCDeclKind::RustToCppConverter,
rust_to_cpp_converter,
);
quote! {
#rust_to_cpp_converter_ident(
std::ptr::from_ref(&#rs_value) as *const core::ffi::c_void,
#c_ptr);
}
}
},
BridgedType::Composable(composable) => {
let crubit_abi_type = CrubitAbiTypeToRustTokens(&composable.crubit_abi_type);
quote! {
// SAFETY: TODO(okabayashi)
unsafe {
::bridge_rust::internal::encode::<#crubit_abi_type>(
// TODO(okabayashi): This ptr case can be removed once tuple bridging is supported,
// as it only is required in the tuple recursive case.
#c_ptr as *mut core::ffi::c_uchar,
#rs_value,
);
}
}
}
}
} else if is_c_abi_compatible_by_value(rs_type) {
write_directly()?
} else if let ty::TyKind::Tuple(tuple_tys) = rs_type.kind() {
let num_elements = tuple_tys.len();
let rs_element_names =
(0..num_elements).map(|i| format_ident!("{rs_value}_{i}")).collect_vec();
let ptr_member_names =
(0..num_elements).map(|i| format_ident!("{c_ptr}_{i}")).collect_vec();
let unpack = quote! {
let (#(#rs_element_names,)*) = #rs_value;
let [#(#ptr_member_names),*] = *(#c_ptr as *mut [*mut core::ffi::c_void; #num_elements]);
};
let write_elements = (0..num_elements)
.map(|i| {
write_rs_value_to_c_abi_ptr(
db,
&rs_element_names[i],
&ptr_member_names[i],
tuple_tys[i],
extern_c_decls,
)
})
.collect::<Result<TokenStream>>()?;
quote! {
#unpack
#write_elements
}
} else if rs_type.ty_adt_def().is_some() {
write_directly()?
} else {
bail!("Attempted to write out unknown type from Rust to C")
})
}
fn replace_all_regions_with_static<'tcx, T>(tcx: TyCtxt<'tcx>, value: T) -> T
where
T: ty::TypeFoldable<TyCtxt<'tcx>>,
{
struct Staticifier<'tcx> {
tcx: TyCtxt<'tcx>,
static_region: ty::Region<'tcx>,
}
impl<'tcx> ty::TypeFolder<TyCtxt<'tcx>> for Staticifier<'tcx> {
fn cx(&self) -> TyCtxt<'tcx> {
self.tcx
}
fn fold_region(&mut self, _: ty::Region<'tcx>) -> ty::Region<'tcx> {
self.static_region
}
}
value.fold_with(&mut Staticifier { tcx, static_region: ty::Region::new_static(tcx) })
}
/// Formats a thunk implementation in Rust that provides an `extern "C"` ABI for
/// calling a Rust function identified by `fn_def_id`. `generate_thunk_impl`
/// may panic if `fn_def_id` doesn't identify a function.
///
/// `fully_qualified_fn_name` specifies how the thunk can identify the function
/// to call. Examples of valid arguments:
/// - `::crate_name::some_module::free_function`
/// - `::crate_name::some_module::SomeStruct::method`
/// - `<::crate_name::some_module::SomeStruct as
/// ::core::default::Default>::default`
pub fn generate_thunk_impl<'tcx>(
db: &dyn BindingsGenerator<'tcx>,
fn_def_id: DefId,
sig: &ty::FnSig<'tcx>,
thunk_name: &str,
fully_qualified_fn_name: TokenStream,
) -> Result<RsSnippet> {
let tcx = db.tcx();
// We replace all regions with `'static`. C++ doesn't understand region constraints, so our FFI
// thunk cannot be dependent upon a particular choice of lifetime parameters. Using `'static`
// everywhere is the easiest way to allow the thunk to compile regardless of the specific
// relationship between the lifetime parameters.
let sig = replace_all_regions_with_static(tcx, *sig);
let param_names_and_types: Vec<(Ident, Ty)> = {
let param_names = thunk_param_names(tcx, fn_def_id);
let param_types = sig.inputs().iter().copied();
param_names.zip(param_types).collect_vec()
};
let mut thunk_params = param_names_and_types
.iter()
.map(|(param_name, ty)| {
let c_abi_type = c_abi_for_param_type(db, *ty)
.with_context(|| format!("Error handling parameter `{param_name}`"))?;
Ok(quote! { #param_name: #c_abi_type })
})
.collect::<Result<Vec<TokenStream>>>()?;
let mut extern_c_decls = BTreeSet::new();
// Convert all parameters from their C ABI types to their Rust types.
let fn_args_conversions = param_names_and_types
.iter()
.map(|(param_name, ty)| {
convert_value_from_c_abi_to_rust(db, *ty, param_name, &mut extern_c_decls)
})
.collect::<Result<Vec<TokenStream>>>()?;
let fn_args: Vec<Ident> =
param_names_and_types.into_iter().map(|(rs_name, _ty)| rs_name).collect();
let output_is_bridged = is_bridged_type(db, sig.output())?;
let thunk_return_type;
let thunk_return_expression;
if output_is_bridged.is_none() && is_c_abi_compatible_by_value(sig.output()) {
// The output is not bridged and is C ABI compatible by-value, so we can just return
// the result directly, and no out-param is needed.
thunk_return_type = db.format_ty_for_rs(sig.output())?;
thunk_return_expression = quote! {
#fully_qualified_fn_name( #( #fn_args ),* )
};
} else {
let return_ptr_ident = format_ident!("__ret_ptr");
let rs_return_value_ident = format_ident!("__rs_return_value");
thunk_return_type = quote! { () };
let return_ptr_type = if let Some(BridgedType::Composable(_)) = output_is_bridged {
// Composable bridging writes its Crubit ABI form in an unsigned char array.
quote! { *mut core::ffi::c_uchar }
} else {
quote! { *mut core::ffi::c_void }
};
thunk_params.push(quote! {
#return_ptr_ident: #return_ptr_type
});
let write_return_value = write_rs_value_to_c_abi_ptr(
db,
&rs_return_value_ident,
&return_ptr_ident,
sig.output(),
&mut extern_c_decls,
)?;
thunk_return_expression = quote! {
let #rs_return_value_ident = #fully_qualified_fn_name( #( #fn_args ),* );
#write_return_value
};
}
let thunk_name = make_rs_ident(thunk_name);
Ok(RsSnippet {
tokens: quote! {
#[unsafe(no_mangle)]
unsafe extern "C" fn #thunk_name (
#( #thunk_params ),*
) -> #thunk_return_type { unsafe {
#(#fn_args_conversions)*
#thunk_return_expression
} }
},
extern_c_decls,
})
}
/// Returns `Ok(())` if no thunk is required.
/// Otherwise returns an error the describes why the thunk is needed.
pub fn is_thunk_required(sig: &ty::FnSig) -> Result<()> {
match sig.abi {
// "C" ABI is okay: since https://rust-lang.github.io/rfcs/2945-c-unwind-abi.html has been
// accepted, a Rust panic that "escapes" a "C" ABI function is a defined crash. See
// https://doc.rust-lang.org/nomicon/ffi.html#ffi-and-unwinding.
rustc_abi::ExternAbi::C { unwind: false } => (),
// This requires a thunk if the calling C++ frames use `-fno-exceptions`, as it is
// UB. However, we leave this to the caller: if you use `extern "C-unwind"`, we assume you
// know what you are doing and do not block you from integrating with exception-enabled C++.
rustc_abi::ExternAbi::C { unwind: true } => (),
// All other ABIs trigger thunk generation. This covers Rust ABI functions, but also
// ABIs that theoretically are understood both by C++ and Rust (e.g. see
// `format_cc_call_conv_as_clang_attribute` in `rs_bindings_from_cc/src_code_gen.rs`).
_ => bail!("Any calling convention other than `extern \"C\"` requires a thunk"),
};
ensure!(is_c_abi_compatible_by_value(sig.output()), "Return type requires a thunk");
for (i, param_ty) in sig.inputs().iter().enumerate() {
ensure!(is_c_abi_compatible_by_value(*param_ty), "Type of parameter #{i} requires a thunk");
}
Ok(())
}
pub struct TraitThunks {
pub method_name_to_cc_thunk_name: HashMap<Symbol, Ident>,
pub cc_thunk_decls: CcSnippet,
pub rs_thunk_impls: RsSnippet,
}
pub fn generate_trait_thunks<'tcx>(
db: &dyn BindingsGenerator<'tcx>,
trait_id: DefId,
// We do not support other generic args, yet.
type_args: &[Ty<'tcx>],
adt: &AdtCoreBindings<'tcx>,
) -> Result<TraitThunks> {
let tcx = db.tcx();
assert!(tcx.is_trait(trait_id));
let self_ty = adt.self_ty;
let is_drop_trait = Some(trait_id) == tcx.lang_items().drop_trait();
if is_drop_trait {
// To support "drop glue" we don't require that `self_ty` directly implements
// the `Drop` trait. Instead we require the caller to check
// `needs_drop`.
assert!(self_ty.needs_drop(tcx, post_analysis_typing_env(tcx, adt.def_id)));
} else if !does_type_implement_trait(
tcx,
self_ty,
trait_id,
type_args.iter().copied().map(ty::GenericArg::from),
) {
let trait_name = tcx.item_name(trait_id);
bail!("`{self_ty}` doesn't implement the `{trait_name}` trait");
}
let mut method_name_to_cc_thunk_name = HashMap::new();
let mut cc_thunk_decls = CcSnippet::default();
let mut rs_thunk_impls = RsSnippet::default();
let methods = tcx
.associated_items(trait_id)
.in_definition_order()
.filter(|item| matches!(item.kind, ty::AssocKind::Fn { .. }));
for method in methods {
let substs = {
let generics = tcx.generics_of(method.def_id);
if generics.own_params.iter().any(|p| p.kind.is_ty_or_const()) {
// Note that lifetime-generic methods are ok:
// * they are handled by `generate_thunk_decl` and `generate_thunk_impl`
// * the lifetimes are erased by `ty::Instance::mono` and *seem* to be erased by
// `ty::Instance::new`
panic!(
"So far callers of `generate_trait_thunks` didn't need traits with \
methods that are type-generic or const-generic"
);
}
assert!(generics.has_self);
tcx.mk_args_trait(self_ty, type_args.iter().copied().map(ty::GenericArg::from))
};
let thunk_name = {
if db.no_thunk_name_mangling() {
let print_types = type_args.iter().map(|ty| format!("{}", ty)).collect_vec();
let method_name = if print_types.is_empty() {
escape_non_identifier_chars(method.name().as_str())
} else {
escape_non_identifier_chars(&format!(
"{}_{}",
method.name().as_str(),
print_types.join("_")
))
};
format!("__crubit_thunk_{}", method_name)
} else {
#[rustversion::since(2025-05-06)]
let instance = ty::Instance::new_raw(method.def_id, substs);
#[rustversion::before(2025-05-06)]
let instance = ty::Instance::new(method.def_id, substs);
let symbol = tcx.symbol_name(instance);
format!(
"__crubit_thunk_{}_{}",
tcx.crate_hash(db.source_crate_num()).to_hex(),
&escape_non_identifier_chars(symbol.name)
)
}
};
let sig_mid = liberate_and_deanonymize_late_bound_regions(
tcx,
tcx.fn_sig(method.def_id).instantiate(tcx, substs),
method.def_id,
);
// TODO(b/254096006): Preserve the HIR here, if possible?
// Cannot in general (e.g. blanket impl from another crate), but should be able
// to for traits defined or implemented in the current crate.
let sig_hir = None;
let thunk_name_cc_ident = format_cc_ident(db, &thunk_name)?;
cc_thunk_decls.add_assign(generate_thunk_decl(
db,
&sig_mid,
sig_hir,
&thunk_name_cc_ident,
/*has_self_param=*/ true,
)?);
method_name_to_cc_thunk_name.insert(method.name(), thunk_name_cc_ident);
rs_thunk_impls += {
let struct_name = &adt.rs_fully_qualified_name;
if is_drop_trait {
// Manually formatting (instead of depending on `generate_thunk_impl`)
// to avoid https://doc.rust-lang.org/error_codes/E0040.html
let thunk_name = make_rs_ident(&thunk_name);
RsSnippet::new(quote! {
#[unsafe(no_mangle)]
extern "C" fn #thunk_name(
__self: &'static mut ::core::mem::MaybeUninit<#struct_name>
) {
unsafe { __self.assume_init_drop() };
}
})
} else {
let fully_qualified_fn_name = {
let fully_qualified_trait_name =
FullyQualifiedName::new(db, trait_id).format_for_rs();
let method_name = make_rs_ident(method.name().as_str());
let args = type_args
.iter()
.map(|ty| {
let static_ty = replace_all_regions_with_static(tcx, *ty);
// Check our type has no variables.
assert!(
!static_ty.flags().contains(
ty::TypeFlags::HAS_PARAM
| ty::TypeFlags::HAS_INFER
| ty::TypeFlags::HAS_PLACEHOLDER
| ty::TypeFlags::HAS_FREE_REGIONS
),
"Generic types are not supported in trait impls yet."
);
db.format_ty_for_rs(static_ty)
.expect("We've replaced all types with static")
})
.collect_vec();
let generics = if args.is_empty() {
quote! {}
} else {
quote! { < #( #args ),* > }
};
quote! { <#struct_name as #fully_qualified_trait_name #generics >::#method_name }
};
generate_thunk_impl(
db,
method.def_id,
&sig_mid,
&thunk_name,
fully_qualified_fn_name,
)?
}
};
}
Ok(TraitThunks { method_name_to_cc_thunk_name, cc_thunk_decls, rs_thunk_impls })
}