blob: bd0587162a415552bf30cf5954989bebe1e95253 [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::format_type::{format_cc_ident, has_elided_region, region_is_elided};
use crate::generate_doc_comment;
use crate::generate_function_thunk::{
generate_thunk_decl, generate_thunk_impl, ident_or_opt_ident, is_thunk_required,
};
use crate::{
format_param_types_for_cc, format_region_as_cc_lifetime, format_ret_ty_for_cc,
generate_deprecated_tag, is_bridged_type, is_c_abi_compatible_by_value,
liberate_and_deanonymize_late_bound_regions, BridgedType, CcType, FullyQualifiedName,
RsSnippet,
};
use arc_anyhow::{Context, Result};
use code_gen_utils::{
escape_non_identifier_chars, expect_format_cc_ident, format_cc_type_name, make_rs_ident,
CcInclude,
};
use crubit_abi_type::CrubitAbiTypeToCppTokens;
use database::code_snippet::{ApiSnippets, CcPrerequisites, CcSnippet};
use database::BindingsGenerator;
use database::{SugaredTy, TypeLocation};
use error_report::{anyhow, bail, ensure};
use itertools::Itertools;
use proc_macro2::{Ident, Literal, TokenStream};
use query_compiler::{is_copy, post_analysis_typing_env};
use quote::quote;
use rustc_hir::attrs::AttributeKind;
use rustc_hir::{self as hir, def::DefKind};
use rustc_middle::mir::Mutability;
use rustc_middle::ty::{self, Ty, TyCtxt};
use rustc_span::def_id::DefId;
use rustc_span::symbol::Symbol;
use std::collections::BTreeSet;
#[derive(Debug, Eq, PartialEq)]
enum FunctionKind {
/// Free function (i.e. not a method).
Free,
/// Non-method associated function (i.e. the first parameter is not named `self`).
AssociatedFn,
/// Instance method taking `self` by value (i.e. `self: Self`).
MethodTakingSelfByValue,
/// Instance method taking `self` by reference (i.e. `&self` or `&mut
/// self`).
MethodTakingSelfByRef,
}
impl FunctionKind {
fn has_self_param(&self) -> bool {
match self {
FunctionKind::MethodTakingSelfByValue | FunctionKind::MethodTakingSelfByRef => true,
FunctionKind::Free | FunctionKind::AssociatedFn => false,
}
}
}
fn thunk_name(
db: &dyn BindingsGenerator,
def_id: DefId,
export_name: Option<Symbol>,
needs_thunk: bool,
) -> String {
let tcx = db.tcx();
let symbol_name = if db.no_thunk_name_mangling() {
if let Some(export_name) = export_name {
export_name.to_string()
} else {
FullyQualifiedName::new(db, def_id)
.rs_name
.expect("Functions are assumed to always have a name")
.to_string()
}
} else {
// Call to `mono` is ok - `generics_of` have been checked above.
let instance = ty::Instance::mono(tcx, def_id);
tcx.symbol_name(instance).name.to_string()
};
let target_path_mangled_hash = if db.no_thunk_name_mangling() {
"".to_string()
} else {
format!("{}_", tcx.crate_hash(db.source_crate_num()).to_hex())
};
if needs_thunk {
format!(
"__crubit_thunk_{}{}",
target_path_mangled_hash,
&escape_non_identifier_chars(&symbol_name)
)
} else {
symbol_name
}
}
/// Returns a vector of identifiers `{prefix}_{i}` for `i` in `[0, n)`.
fn ident_for_each(prefix: &str, n: usize) -> Vec<Ident> {
(0..n).map(|i| expect_format_cc_ident(&format!("{prefix}_{i}"))).collect()
}
/// Converts a C++ value to a C-ABI-compatible type.
///
/// * `db` - the bindings generator
/// * `cc_ident` - the name of the C++ lvalue.
/// * `ty` - the Rust type of the parameter
/// * `post_analysis_typing_env` - the typing environment, used to determine if the type is
/// trivially destructible (no drop glue).
/// * `includes` - an output parameter used to store the set of C++ includes required
/// * `statements` - an output parameter used to store the C++ statements performing the conversion
///
/// Returns a `TokenStream` containing an expression that evaluates to the
/// C-ABI-compatible version of the type.
fn cc_param_to_c_abi<'tcx>(
db: &dyn BindingsGenerator<'tcx>,
cc_ident: Ident,
ty: SugaredTy<'tcx>,
post_analysis_typing_env: ty::TypingEnv<'tcx>,
includes: &mut BTreeSet<CcInclude>,
statements: &mut TokenStream,
) -> Result<TokenStream> {
Ok(if let Some(bridged_type) = is_bridged_type(db, ty.mid())? {
match bridged_type {
BridgedType::Legacy { cpp_type, .. } => {
if let CcType::Pointer { .. } = cpp_type {
quote! { #cc_ident }
} else {
quote! { & #cc_ident }
}
}
BridgedType::Composable(composable) => {
// We're the library, and are about to send it over to Rust.
// We need to encode it into a buffer, then send that over.
includes.insert(db.support_header("bridge.h"));
let crubit_abi_type = CrubitAbiTypeToCppTokens(&composable.crubit_abi_type);
let buffer_name = expect_format_cc_ident(&format!("{cc_ident}_buffer"));
// Create a buffer, encode it into the buffer, and then make the buffer be
// what we sent to Rust across the C ABI.
statements.extend(quote! {
unsigned char #buffer_name[#crubit_abi_type::kSize];
::crubit::internal::Encode<#crubit_abi_type>(#buffer_name, #cc_ident);
});
quote! { #buffer_name }
}
}
} else if is_c_abi_compatible_by_value(ty.mid()) {
quote! { #cc_ident }
} else if let Some(tuple_tys) = ty.as_tuple(db) {
let n = tuple_tys.len();
let c_abi_names = ident_for_each(&format!("{cc_ident}_cabi"), n);
// Create a statement defining a local for the C ABI representation of each tuple element.
// This is necessary in order to ensure that we have a non-temporary value to point to
// in the `result_name` array below.
//
// Locals of unknown type use `auto&&` in order to avoid changing the type of the
// expression.
for (i, c_abi_name) in c_abi_names.iter().enumerate() {
let tuple_element_name = expect_format_cc_ident(&format!("{cc_ident}_{i}"));
// Needed to avoid `proc_macro2` interpolating `1usize` instead of `1`.
let i_literal = Literal::usize_unsuffixed(i);
statements.extend(quote! {
auto&& #tuple_element_name = std::get<#i_literal>(#cc_ident);
});
let converted_value = cc_param_to_c_abi(
db,
tuple_element_name.clone(),
tuple_tys.index(i),
post_analysis_typing_env,
includes,
statements,
)?;
if matches!(tuple_tys.index(i).mid().kind(), ty::TyKind::Tuple(_)) {
// Elements which are arrays must be referenced again in order
// to properly convert them to pointers.
//
// Note that `converted_value` here is a `result_name` array lvalue,
// never a temporary, so it's fine to take its address in the RHS.
statements.extend(quote! {
auto* #c_abi_name = &#converted_value;
});
} else {
statements.extend(quote! {
auto&& #c_abi_name = #converted_value;
});
}
}
let result_name = expect_format_cc_ident(&format!("{cc_ident}_cabi"));
statements.extend(quote! {
void* #result_name[] = { #(&#c_abi_names),* };
});
quote! {
#result_name
}
} else if !ty.mid().needs_drop(db.tcx(), post_analysis_typing_env) {
// As an optimization, if the type is trivially destructible, we don't
// need to move it to a new NoDestructor location. We can directly copy the
// bytes.
quote! { & #cc_ident }
} else {
// The implementation will copy the bytes, we just need to leave the variable
// behind in a valid moved-from state.
// TODO(jeanpierreda): Ideally, the Rust code should C++-move instead of memcpy,
// allowing us to avoid one extra memcpy: we could move it directly into its
// target location, instead of moving to a temporary that we memcpy to its
// target location.
includes.insert(db.support_header("internal/slot.h"));
let slot_name = &expect_format_cc_ident(&format!("{cc_ident}_slot"));
statements.extend(quote! {
crubit::Slot #slot_name((std::move(#cc_ident)));
});
quote! { #slot_name.Get() }
})
}
struct ReturnConversion {
/// The name of a variable holding a pointer to storage of the C-ABI-compatible version of
/// the return type.
storage_name: Ident,
/// An expression that unpacks the return value from the storage location.
unpack_expr: TokenStream,
}
fn format_ty_for_cc_amending_prereqs<'tcx>(
db: &dyn BindingsGenerator<'tcx>,
ty: SugaredTy<'tcx>,
prereqs: &mut CcPrerequisites,
) -> Result<TokenStream> {
let CcSnippet { tokens: cc_type, prereqs: ty_prereqs } =
db.format_ty_for_cc(ty, TypeLocation::Other)?;
*prereqs += ty_prereqs;
Ok(cc_type)
}
fn cc_return_value_from_c_abi<'tcx>(
db: &dyn BindingsGenerator<'tcx>,
ident: Ident,
ty: SugaredTy<'tcx>,
prereqs: &mut CcPrerequisites,
storage_statements: &mut TokenStream,
recursive: bool,
) -> Result<ReturnConversion> {
let storage_name = &expect_format_cc_ident(&format!("__{ident}_storage"));
if let Some(bridged_type) = is_bridged_type(db, ty.mid())? {
match bridged_type {
BridgedType::Legacy { cpp_type, .. } => {
let cpp_type = format_cc_type_name(cpp_type.as_ref())?;
// Below, we use a union to allocate uninitialized memory that fits cpp_type.
// The union prevents the type from being default constructed. It's
// the responsibility of the thunk to properly initialize the
// memory. In the union's destructor we use std::destroy_at to call
// the cpp_type's destructor after the value has been moved on return.
let union_type = expect_format_cc_ident(&format!("__{ident}_crubit_return_union"));
let local_name = expect_format_cc_ident(&format!("__{ident}_ret_val_holder"));
storage_statements.extend(quote! {
union #union_type {
constexpr #union_type() {}
~#union_type() { std::destroy_at(&this->val); }
#cpp_type val;
} #local_name;
auto* #storage_name = &#local_name.val;
});
Ok(ReturnConversion {
storage_name: storage_name.clone(),
unpack_expr: quote! {
std::move(#local_name.val)
},
})
}
BridgedType::Composable(composable) => {
// make the buffer space for it, then decode the buffer into a value
// ::crubit::internal::Decode<T>(buffer);
prereqs.includes.insert(db.support_header("bridge.h"));
let crubit_abi_type = CrubitAbiTypeToCppTokens(&composable.crubit_abi_type);
storage_statements.extend(quote! {
unsigned char #storage_name[#crubit_abi_type::kSize];
});
Ok(ReturnConversion {
storage_name: storage_name.clone(),
unpack_expr: quote! {
::crubit::internal::Decode<#crubit_abi_type>(#storage_name)
},
})
}
}
} else if is_c_abi_compatible_by_value(ty.mid()) {
let cc_type = &format_ty_for_cc_amending_prereqs(db, ty, prereqs)?;
let local_name = &expect_format_cc_ident(&format!("__{ident}_ret_val_holder"));
storage_statements.extend(quote! {
#cc_type #local_name;
#cc_type* #storage_name = &#local_name;
});
Ok(ReturnConversion {
storage_name: storage_name.clone(),
unpack_expr: quote! { *#storage_name },
})
} else if let Some(tuple_tys) = ty.as_tuple(db) {
let n = tuple_tys.len();
let mut storage_names = Vec::with_capacity(n);
let mut unpack_exprs = Vec::with_capacity(n);
for i in 0..n {
let tuple_element_ident = expect_format_cc_ident(&format!("{ident}_{i}"));
let ReturnConversion {
storage_name: element_storage_name,
unpack_expr: element_unpack_expr,
} = cc_return_value_from_c_abi(
db,
tuple_element_ident,
tuple_tys.index(i),
prereqs,
storage_statements,
/*recursive=*/ true,
)?;
storage_names.push(element_storage_name);
unpack_exprs.push(element_unpack_expr);
}
storage_statements.extend(quote! {
void* #storage_name[] = { #(#storage_names),* };
});
Ok(ReturnConversion {
storage_name: storage_name.clone(),
unpack_expr: quote! { std::make_tuple(#(#unpack_exprs),*) },
})
} else {
if recursive {
if let Some(adt_def) = ty.mid().ty_adt_def() {
let core = db.generate_adt_core(adt_def.did())?;
// Note: the error here is an ApiSnippets which is not propagated.
db.generate_move_ctor_and_assignment_operator(core).map_err(|_| {
anyhow!("Can't return a type by value inside a compound data type without a move constructor")
})?;
}
}
let local_name = expect_format_cc_ident(&format!("__{ident}_ret_val_holder"));
let cc_type = format_ty_for_cc_amending_prereqs(db, ty, prereqs)?;
storage_statements.extend(quote! {
crubit::Slot<#cc_type> #local_name;
auto* #storage_name = #local_name.Get();
});
prereqs.includes.insert(CcInclude::utility()); // for `std::move`
prereqs.includes.insert(db.support_header("internal/slot.h"));
Ok(ReturnConversion {
storage_name: storage_name.clone(),
unpack_expr: quote! {
std::move(#local_name).AssumeInitAndTakeValue()
},
})
}
}
/// Returns the kind of a function (free, method, etc.) or an error if the self type is unsupported.
fn function_kind<'tcx>(
tcx: TyCtxt<'tcx>,
def_id: DefId,
self_ty: Option<Ty<'tcx>>,
param_types: &[Ty<'tcx>],
) -> Result<FunctionKind> {
match tcx.def_kind(def_id) {
DefKind::Fn => return Ok(FunctionKind::Free),
DefKind::AssocFn => {}
other => panic!("Unexpected HIR node kind: {other:?}"),
}
if !tcx.associated_item(def_id).is_method() {
return Ok(FunctionKind::AssociatedFn);
}
let self_ty = self_ty.expect("ImplItem => non-None `self_ty`");
if param_types[0] == self_ty {
return Ok(FunctionKind::MethodTakingSelfByValue);
}
match param_types[0].kind() {
ty::TyKind::Ref(_, referent_ty, _) if *referent_ty == self_ty => {
Ok(FunctionKind::MethodTakingSelfByRef)
}
_ => bail!("Unsupported `self` type `{}`", param_types[0]),
}
}
fn self_ty_of_method<'tcx>(tcx: TyCtxt<'tcx>, def_id: DefId) -> Option<Ty<'tcx>> {
#[rustversion::before(2025-07-29)]
let impl_id = tcx.impl_of_method(def_id)?;
#[rustversion::since(2025-07-29)]
let impl_id = tcx.impl_of_assoc(def_id)?;
#[rustversion::since(2025-10-17)]
assert!(!tcx.impl_is_of_trait(impl_id), "Trait methods should be filtered by caller");
#[rustversion::before(2025-10-17)]
assert!(tcx.impl_trait_ref(impl_id).is_none(), "Trait methods should be filtered by caller");
Some(tcx.type_of(impl_id).instantiate_identity())
}
fn export_name_and_no_mangle_attrs_of<'tcx>(
tcx: TyCtxt<'tcx>,
def_id: DefId,
) -> (Option<Symbol>, bool) {
let mut export_name: Option<Symbol> = None;
let mut no_mangle = false;
for attr in tcx.get_all_attrs(def_id) {
match attr {
hir::Attribute::Parsed(AttributeKind::ExportName { name, .. }) => {
export_name = Some(*name);
}
hir::Attribute::Parsed(AttributeKind::NoMangle(..)) => {
no_mangle = true;
}
_ => {}
}
}
(export_name, no_mangle)
}
pub(crate) struct MustUseAttr {
pub reason: Option<Symbol>,
}
pub(crate) fn must_use_attr_of<'tcx>(tcx: TyCtxt<'tcx>, def_id: DefId) -> Option<MustUseAttr> {
for attr in tcx.get_all_attrs(def_id) {
if let hir::Attribute::Parsed(AttributeKind::MustUse { reason, .. }) = attr {
return Some(MustUseAttr { reason: *reason });
}
}
None
}
pub(crate) struct Param<'tcx> {
pub(crate) cc_name: Ident,
pub(crate) cpp_type: TokenStream,
pub(crate) ty: SugaredTy<'tcx>,
}
fn can_shared_refs_to_ty_alias_mut_refs<'tcx>(tcx: TyCtxt<'tcx>, target_ty: Ty<'tcx>) -> bool {
let is_zero_sized =
query_compiler::get_layout(tcx, target_ty).map(|layout| layout.is_zst()).unwrap_or(false);
if is_zero_sized {
return true;
}
// Shared references to types which contain `UnsafeCell` may alias with mutable references.
if !target_ty.is_freeze(tcx, ty::TypingEnv::fully_monomorphized()) {
return true;
}
false
}
#[derive(Default)]
struct RefsToCheckForAliasing<'a, 'tcx> {
mutable: Vec<&'a Param<'tcx>>,
shared: Vec<&'a Param<'tcx>>,
}
/// Returns function parameters which need to be checked for possible illegal mutable aliasing.
///
/// Rust does not allow mutable references to alias with other references (shared or mutable).
/// C++ does not have this requirement, so we insert checks in the generated bindings to ensure that
/// this requirement is not violated.
fn refs_to_check_for_aliasing<'tcx, 'a>(
db: &dyn BindingsGenerator<'tcx>,
params: &'a [Param<'tcx>],
) -> Option<RefsToCheckForAliasing<'a, 'tcx>> {
let tcx = db.tcx();
let mut refs = RefsToCheckForAliasing::default();
// TODO: b/351876244 - Apply this check to public reference fields of ADTs, not just top-level
// reference function parameters.
//
// TODO: b/351876244 - Apply this check to reference-like types such as
// `cpp_std::string_view` and `absl::Span`.
for param in params {
if let ty::TyKind::Ref(_region, target_ty, mutability) = param.ty.mid().kind() {
if mutability.is_mut() {
refs.mutable.push(param);
} else if !can_shared_refs_to_ty_alias_mut_refs(tcx, *target_ty) {
refs.shared.push(param);
}
}
}
if refs.mutable.is_empty() || (refs.shared.len() + refs.mutable.len() < 2) {
return None;
}
Some(refs)
}
/// Generates the wrapping code to call a thunk and return its result.
/// This can be checking parameter invariants or creating a slot to pass as an output pointer.
pub(crate) fn generate_thunk_call<'tcx>(
db: &dyn BindingsGenerator<'tcx>,
def_id: DefId,
thunk_name: Ident,
rs_return_type: SugaredTy<'tcx>,
takes_self_by_copy: bool,
has_self_param: bool,
params: &[Param<'tcx>],
) -> Result<CcSnippet> {
let mut prereqs = CcPrerequisites::default();
let mut tokens = TokenStream::new();
let mut thunk_args = params
.iter()
.enumerate()
.map(|(i, Param { cc_name, ty, .. })| {
if i == 0 && has_self_param {
if takes_self_by_copy {
// Self-by-copy methods are `const` qualified. The Rust thunk does not
// accept a const pointer, but we can just const_cast since underlying C++
// object is not modified: Rust copies the object before passing it into
// the by-value method.
tokens.extend(quote! {
auto& #cc_name = const_cast<
std::remove_cvref_t<decltype(*this)>&>(*this);
});
} else {
tokens.extend(quote! { auto&& #cc_name = *this; });
}
}
let tcx = db.tcx();
cc_param_to_c_abi(
db,
cc_name.clone(),
*ty,
post_analysis_typing_env(tcx, def_id),
&mut prereqs.includes,
&mut tokens,
)
})
.collect::<Result<Vec<TokenStream>>>()?;
if let Some(refs_to_check) = refs_to_check_for_aliasing(db, params) {
let mut_cpp_tys = refs_to_check.mutable.iter().map(|param| &param.cpp_type);
let mut_cpp_names = refs_to_check.mutable.iter().map(|param| &param.cc_name);
let shared_cpp_tys = refs_to_check.shared.iter().map(|param| &param.cpp_type);
let shared_cpp_names = refs_to_check.shared.iter().map(|param| &param.cc_name);
prereqs.includes.insert(db.support_header("internal/check_no_mutable_aliasing.h"));
tokens.extend(quote! {
__NEWLINE__
crubit::internal::CheckNoMutableAliasing(
crubit::internal::AsMutPtrDatas<#( #mut_cpp_tys ),*>( #( #mut_cpp_names ),* ),
crubit::internal::AsPtrDatas<#( #shared_cpp_tys ),*>(
#( #shared_cpp_names ),* )
);
__NEWLINE__
});
}
let return_body = if is_bridged_type(db, rs_return_type.mid())?.is_none()
&& is_c_abi_compatible_by_value(rs_return_type.mid())
{
// C++ compilers can emit diagnostics if a function marked [[noreturn]] looks like it
// might return. In this scenario, we just call the (also [[noreturn]]) thunk.
let return_expr = if rs_return_type.is_never() {
quote! {}
} else {
quote! {return}
};
quote! {
#return_expr __crubit_internal::#thunk_name(#( #thunk_args ),*);
}
} else {
let ReturnConversion { storage_name, unpack_expr } = cc_return_value_from_c_abi(
db,
expect_format_cc_ident("return_value"),
rs_return_type,
&mut prereqs,
&mut tokens,
/*recursive=*/ false,
)?;
thunk_args.push(quote! { #storage_name });
// We don't have to worry about the [[noreturn]] situation described above because all
// [[noreturn]] functions will take that branch.
quote! {
__crubit_internal::#thunk_name(#( #thunk_args ),*);
return #unpack_expr;
}
};
tokens.extend(return_body);
Ok(CcSnippet { prereqs, tokens })
}
/// Implementation of `BindingsGenerator::generate_function`.
pub fn generate_function(db: &dyn BindingsGenerator<'_>, def_id: DefId) -> Result<ApiSnippets> {
let tcx = db.tcx();
ensure!(
!query_compiler::has_non_lifetime_generics(tcx, def_id),
"Generic functions are not supported yet (b/259749023)"
);
let (sig_mid, sig_hir) = get_fn_sig(tcx, def_id);
check_fn_sig(&sig_mid)?;
let self_ty = self_ty_of_method(tcx, def_id);
let function_kind = function_kind(tcx, def_id, self_ty, sig_mid.inputs())?;
// TODO(b/262904507): Don't require thunks for mangled extern "C" functions.
let (export_name, has_no_mangle) = export_name_and_no_mangle_attrs_of(tcx, def_id);
let has_export_name = export_name.is_some();
let needs_thunk = is_thunk_required(&sig_mid).is_err() || (!has_no_mangle && !has_export_name);
let thunk_name = thunk_name(db, def_id, export_name, needs_thunk);
let fully_qualified_fn_name = FullyQualifiedName::new(db, def_id);
let unqualified_rust_fn_name =
fully_qualified_fn_name.rs_name.expect("Functions are assumed to always have a name");
let main_api_fn_name = format_cc_ident(db, fully_qualified_fn_name.cpp_name.unwrap().as_str())
.context("Error formatting function name")?;
let mut main_api_prereqs = CcPrerequisites::default();
let main_api_ret_type =
format_ret_ty_for_cc(db, &sig_mid, sig_hir)?.into_tokens(&mut main_api_prereqs);
let params = {
let names = tcx.fn_arg_idents(def_id).iter();
let cpp_types =
format_param_types_for_cc(db, &sig_mid, sig_hir, function_kind.has_self_param())?;
names
.enumerate()
.zip(SugaredTy::fn_inputs(&sig_mid, sig_hir))
.zip(cpp_types)
.map(|(((i, name), ty), cpp_type)| {
// TODO(jeanpierreda): deduplicate this with thunk_param_names.
let mut cc_name = None;
if let Some(ident) = ident_or_opt_ident(name) {
if ident.name.as_str() != "_" {
if let Ok(name) = format_cc_ident(db, ident.name.as_str()) {
cc_name = Some(name);
}
}
}
let cc_name = if let Some(cc_name) = cc_name {
cc_name
} else {
expect_format_cc_ident(&format!("__param_{i}"))
};
let cpp_type = cpp_type.into_tokens(&mut main_api_prereqs);
Param { cc_name, cpp_type, ty }
})
.collect_vec()
};
let mut takes_self_by_copy = false;
let method_qualifiers = match function_kind {
FunctionKind::Free | FunctionKind::AssociatedFn => quote! {},
FunctionKind::MethodTakingSelfByValue => {
let self_ty = params[0].ty.mid();
if is_copy(tcx, def_id, self_ty) {
takes_self_by_copy = true;
quote! { const }
} else {
quote! { && }
}
}
FunctionKind::MethodTakingSelfByRef => match params[0].ty.mid().kind() {
ty::TyKind::Ref(region, _, mutability) => {
let tcx = db.tcx();
// Ref-qualify if the lifetime of `&self` is a named lifetime or if the elided
// lifetime appears in the return type.
// See <internal link> for more details on the motivation.
let ref_qualifier = if !region_is_elided(tcx, *region)
|| has_elided_region(tcx, sig_mid.output())
{
let lifetime_annotation = format_region_as_cc_lifetime(tcx, region);
quote! { & #lifetime_annotation }
} else {
quote! {}
};
let mutability = match mutability {
Mutability::Mut => quote! {},
Mutability::Not => quote! { const },
};
quote! { #mutability #ref_qualifier }
}
_ => panic!("Expecting TyKind::Ref for MethodKind...Self...Ref"),
},
};
fn has_non_lifetime_substs(substs: &[ty::GenericArg]) -> bool {
substs.iter().any(|subst| subst.as_region().is_none())
}
let struct_name = match self_ty {
Some(ty) => match ty.kind() {
ty::TyKind::Adt(adt, substs) => {
assert!(!has_non_lifetime_substs(substs), "Callers should filter out generics");
Some(FullyQualifiedName::new(db, adt.did()))
}
_ => panic!("Non-ADT `impl`s should be filtered by caller"),
},
None => None,
};
let needs_definition = unqualified_rust_fn_name.as_str() != thunk_name;
let main_api_params = params
.iter()
.skip(if function_kind.has_self_param() { 1 } else { 0 })
.map(|Param { cc_name, cpp_type, .. }| quote! { #cpp_type #cc_name })
.collect_vec();
let rs_return_type = SugaredTy::fn_output(&sig_mid, sig_hir);
let main_api = {
let doc_comment = {
let doc_comment = generate_doc_comment(db, def_id);
quote! { __NEWLINE__ #doc_comment }
};
let mut prereqs = main_api_prereqs.clone();
prereqs.move_defs_to_fwd_decls();
let static_ = if function_kind == FunctionKind::AssociatedFn {
quote! { static }
} else {
quote! {}
};
let extern_c = if !needs_definition {
quote! { extern "C" }
} else {
quote! {}
};
let mut attributes = vec![];
// Attribute: must_use
if let Some(must_use_attr) = must_use_attr_of(tcx, def_id) {
match must_use_attr.reason {
None => attributes.push(quote! {[[nodiscard]]}),
Some(symbol) => {
let message = symbol.as_str();
attributes.push(quote! {[[nodiscard(#message)]]});
}
};
}
// Attribute: deprecated
if let Some(cc_deprecated_tag) = generate_deprecated_tag(tcx, def_id) {
attributes.push(cc_deprecated_tag);
}
// Also check the impl block to which this function belongs (if there is one).
// Note: parent_def_id can be Some(...) even if the function is not inside an
// impl block.
if let Some(parent_def_id) = tcx.opt_parent(def_id) {
if let Some(cc_deprecated_tag) = generate_deprecated_tag(tcx, parent_def_id) {
attributes.push(cc_deprecated_tag);
}
}
// Attribute: noreturn
if rs_return_type.is_never() {
attributes.push(quote! {[[noreturn]]});
}
CcSnippet {
prereqs,
tokens: quote! {
__NEWLINE__
#doc_comment
#extern_c #(#attributes)* #static_
#main_api_ret_type #main_api_fn_name (
#( #main_api_params ),*
) #method_qualifiers;
__NEWLINE__
},
}
};
let cc_details = if !needs_definition {
CcSnippet::default()
} else {
let thunk_name = format_cc_ident(db, &thunk_name).context("Error formatting thunk name")?;
let struct_name = match struct_name.as_ref() {
None => quote! {},
Some(fully_qualified_name) => {
let name = fully_qualified_name.cpp_name.expect("Structs always have a name");
let name = format_cc_ident(db, name.as_str()).expect(
"Caller of generate_function should verify struct via generate_adt_core",
);
quote! { #name :: }
}
};
let mut prereqs = main_api_prereqs;
let thunk_decl = generate_thunk_decl(
db,
&sig_mid,
sig_hir,
&thunk_name,
function_kind.has_self_param(),
)?
.into_tokens(&mut prereqs);
let impl_body = generate_thunk_call(
db,
def_id,
thunk_name,
rs_return_type,
takes_self_by_copy,
function_kind.has_self_param(),
&params,
)?
.into_tokens(&mut prereqs);
CcSnippet {
prereqs,
tokens: quote! {
__NEWLINE__
#thunk_decl
inline #main_api_ret_type #struct_name #main_api_fn_name (
#( #main_api_params ),* ) #method_qualifiers {
#impl_body
}
__NEWLINE__
},
}
};
let rs_details = if !needs_thunk {
RsSnippet::default()
} else {
let fully_qualified_fn_name = match struct_name.as_ref() {
None => fully_qualified_fn_name.format_for_rs(),
Some(struct_name) => {
let fn_name = make_rs_ident(unqualified_rust_fn_name.as_str());
let struct_name = struct_name.format_for_rs();
quote! { #struct_name :: #fn_name }
}
};
generate_thunk_impl(db, def_id, &sig_mid, &thunk_name, fully_qualified_fn_name)?
};
Ok(ApiSnippets { main_api, cc_details, rs_details })
}
pub fn check_fn_sig(sig: &ty::FnSig) -> Result<()> {
if sig.c_variadic {
// TODO(b/254097223): Add support for variadic functions.
bail!("C variadic functions are not supported (b/254097223)");
}
Ok(())
}
/// Returns the rustc_middle and rustc_hir function signatures.
///
/// In the case of rustc_hir, this returns the `FnDecl`, not the
/// `rustc_hir::FnSig`, because the `FnDecl` type is used for both function
/// pointers and actual functions. This makes it a more useful vocabulary type.
/// `FnDecl` does drop information, but that information is already on the
/// rustc_middle `FnSig`, so there is no loss.
pub fn get_fn_sig<'tcx>(
tcx: TyCtxt<'tcx>,
def_id: DefId,
) -> (ty::FnSig<'tcx>, Option<&'tcx rustc_hir::FnDecl<'tcx>>) {
let sig_mid = liberate_and_deanonymize_late_bound_regions(
tcx,
tcx.fn_sig(def_id).instantiate_identity(),
def_id,
);
let hir_decl = def_id
.as_local()
.map(|local_def_id| tcx.hir_node_by_def_id(local_def_id).fn_sig().unwrap().decl);
(sig_mid, hir_decl)
}