Zip the HIR and MIR together, and use it to expose the `ffi` aliases.
See[]
(I would suggest not approving without at least in principle approving that doc.)
## What?
The core of this CL is that, while recursing through the `ty::Ty`, we also recurse through the corresponding `hir::Ty` if we have one.
We then get to see aliases, as long as they haven't been "lost" because we lost them in the HIR. (This can happen for at least two reasons: because the HIR is in another crate (not sure about the boundaries of this?), and because the HIR had an opaque barrier, such as an inferred type, a generic type, or an associated type).
## Why?
This has the following benefits:
* The direct impetus is that it allows us to map `c_char` to `char`, even though it's actually a `u8` or `i8`. Rust has two 8-bit types, but the Rust standard library uses three. This provides a _relatively_ intuitive behavior. See[]
* In a future CL, zipping this will allow us to create bindings for aliases to non-public types. For instance, consider the following:
https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=725fa6d6140ab9894b6796c71d84abf0
```rust
pub trait MyTrait {
type Type;
}
const _ : () = {
pub struct Hidden;
impl MyTrait for i32 {
type Type = Hidden;
}
};
pub type X = <i32 as MyTrait>::Type;
```
We may need to see aliases to find the public instance of this, otherwise we are lost. The `rustc_middle::ty::Ty`, however, has no useful name attached to it so far as I know.
---
This is not perfect. For `c_char`, ideally we'd use a newtype, not an alias. The HIR will get lost in many predictable places:
1. When the entity is in another crate (?). For example, a trait defined in another crate. This also might be why `pub type MyChar = core::ffi::c_char` doesn't work, see TODO -- have not yet investigated, and we'd need to fully outline the limitations of this approach before the bug is marked fixed.
2. When this is an _instantiation_. For a function `fn foo<T>()`, `foo::<i8>` and `foo::<c_char>` are the _same function_, and HIR is lost here.
IMO, we will want to do this regardless of whether we can broadly embrace the use of newtypes, simply because users would _expect_ c_char etc. to work. Walking the HIR like this forces it to work in a predictable set of places: it works wherever it _can_ work, and doesn't work wherever it can't. Easy enough.
(See[]
PiperOrigin-RevId: 659732898
Change-Id: I700b771caa57ca792ceb656ca3a4a4be9499a1d9
diff --git a/cc_bindings_from_rs/bindings.rs b/cc_bindings_from_rs/bindings.rs
index d0f0eb3..658e9d4 100644
--- a/cc_bindings_from_rs/bindings.rs
+++ b/cc_bindings_from_rs/bindings.rs
@@ -24,7 +24,7 @@
use quote::{format_ident, quote, ToTokens};
use rustc_attr::find_deprecation;
use rustc_hir::def::{DefKind, Res};
-use rustc_hir::{AssocItemKind, Item, ItemKind, Node, Safety, UseKind, UsePath};
+use rustc_hir::{AssocItemKind, HirId, Item, ItemKind, Node, Safety, UseKind, UsePath};
use rustc_infer::infer::TyCtxtInferExt;
use rustc_middle::dep_graph::DepContext;
use rustc_middle::mir::Mutability;
@@ -78,7 +78,7 @@
fn format_ty_for_cc(
&self,
- ty: Ty<'tcx>,
+ ty: SugaredTy<'tcx>,
location: TypeLocation,
) -> Result<CcSnippet>;
@@ -346,6 +346,51 @@
}
}
+mod sugared_ty {
+ use super::*;
+ /// A Ty, optionally attached to its `hir::Ty` counterpart, if any.
+ ///
+ /// The rustc_hir::Ty is used only for detecting type aliases (or other
+ /// optional sugar), unrelated to the actual concrete type. It
+ /// necessarily disappears if, for instance, the type is plugged in from
+ /// a generic. There's no way to tell, in the bindings for
+ /// Vec<c_char>::len(), that `T` came from the type alias
+ /// `c_char`, instead of a plain `i8` or `u8`.
+ #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
+ pub(super) struct SugaredTy<'tcx> {
+ mid: Ty<'tcx>,
+ /// The HirId of the corresponding HirTy. We store it as a HirId so that
+ /// it's hashable.
+ hir_id: Option<HirId>,
+ }
+
+ impl<'tcx> std::fmt::Display for SugaredTy<'tcx> {
+ fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
+ std::fmt::Display::fmt(&self.mid, f)
+ }
+ }
+
+ impl<'tcx> SugaredTy<'tcx> {
+ pub fn new(mid: Ty<'tcx>, hir: Option<&rustc_hir::Ty<'tcx>>) -> Self {
+ Self { mid, hir_id: hir.map(|hir| hir.hir_id) }
+ }
+
+ /// Returns the rustc_middle::Ty this represents.
+ pub fn mid(&self) -> Ty<'tcx> {
+ self.mid
+ }
+
+ /// Returns the rustc_hir::Ty this represents, if any.
+ pub fn hir(&self, db: &dyn BindingsGenerator<'tcx>) -> Option<&'tcx rustc_hir::Ty<'tcx>> {
+ let hir_id = self.hir_id?;
+ let hir_ty = db.tcx().hir_node(hir_id).expect_ty();
+ debug_assert_eq!(hir_ty.hir_id, hir_id);
+ Some(hir_ty)
+ }
+ }
+}
+use sugared_ty::SugaredTy;
+
/// Whether functions using `extern "C"` ABI can safely handle values of type
/// `ty` (e.g. when passing by value arguments or return values of such type).
fn is_c_abi_compatible_by_value(ty: Ty) -> bool {
@@ -432,7 +477,7 @@
fn format_pointer_or_reference_ty_for_cc<'tcx>(
db: &dyn BindingsGenerator<'tcx>,
- pointee: Ty<'tcx>,
+ pointee: SugaredTy<'tcx>,
mutability: rustc_middle::mir::Mutability,
pointer_sigil: TokenStream,
) -> Result<CcSnippet> {
@@ -441,7 +486,7 @@
Mutability::Mut => quote! {},
Mutability::Not => quote! { const },
};
- if pointee.is_c_void(tcx) {
+ if pointee.mid().is_c_void(tcx) {
return Ok(CcSnippet { tokens: quote! { #const_qualifier void* }, ..Default::default() });
}
let CcSnippet { tokens, mut prereqs } = db.format_ty_for_cc(pointee, TypeLocation::Other)?;
@@ -453,7 +498,7 @@
/// spelled in a C++ declaration of a function parameter or field.
fn format_ty_for_cc<'tcx>(
db: &dyn BindingsGenerator<'tcx>,
- ty: Ty<'tcx>,
+ ty: SugaredTy<'tcx>,
location: TypeLocation,
) -> Result<CcSnippet> {
let tcx = db.tcx();
@@ -463,7 +508,12 @@
fn keyword(tokens: TokenStream) -> CcSnippet {
CcSnippet::new(tokens)
}
- Ok(match ty.kind() {
+
+ if let Some(alias) = format_core_alias_for_cc(db, ty) {
+ return Ok(alias);
+ }
+
+ Ok(match ty.mid().kind() {
ty::TyKind::Never => match location {
TypeLocation::FnReturn => keyword(quote! { void }),
_ => {
@@ -509,7 +559,7 @@
// `rust_builtin_type_abi_assumptions.md` - we assume that Rust's `char` has the
// same ABI as `u32`.
let layout = tcx
- .layout_of(ty::ParamEnv::empty().and(ty))
+ .layout_of(ty::ParamEnv::empty().and(ty.mid()))
.expect("`layout_of` is expected to succeed for the builtin `char` type")
.layout;
assert_eq!(4, layout.align().abi.bytes());
@@ -596,14 +646,27 @@
CcSnippet { tokens: FullyQualifiedName::new(tcx, def_id).format_for_cc()?, prereqs }
}
- ty::TyKind::RawPtr(pointee_ty, mutbl) => {
- format_pointer_or_reference_ty_for_cc(db, *pointee_ty, *mutbl, quote! { * })
- .with_context(|| {
- format!("Failed to format the pointee of the pointer type `{ty}`")
- })?
+ ty::TyKind::RawPtr(pointee_mid, mutbl) => {
+ let mut pointee_hir = None;
+ if let Some(hir) = ty.hir(db) {
+ if let rustc_hir::TyKind::Ptr(mut_p) = hir.kind {
+ pointee_hir = Some(mut_p.ty);
+ }
+ }
+ let pointee = SugaredTy::new(*pointee_mid, pointee_hir);
+ format_pointer_or_reference_ty_for_cc(db, pointee, *mutbl, quote! { * }).with_context(
+ || format!("Failed to format the pointee of the pointer type `{ty}`"),
+ )?
}
- ty::TyKind::Ref(region, referent_ty, mutability) => {
+ ty::TyKind::Ref(region, referent_mid, mutability) => {
+ let mut referent_hir = None;
+ if let Some(hir) = ty.hir(db) {
+ if let rustc_hir::TyKind::Ref(_, mut_p, ..) = &hir.kind {
+ referent_hir = Some(mut_p.ty);
+ }
+ }
+ let referent = SugaredTy::new(*referent_mid, referent_hir);
match location {
TypeLocation::FnReturn | TypeLocation::FnParam => (),
TypeLocation::Other => bail!(
@@ -612,15 +675,10 @@
),
};
let lifetime = format_region_as_cc_lifetime(region);
- format_pointer_or_reference_ty_for_cc(
- db,
- *referent_ty,
- *mutability,
- quote! { & #lifetime },
- )
- .with_context(|| {
- format!("Failed to format the referent of the reference type `{ty}`")
- })?
+ format_pointer_or_reference_ty_for_cc(db, referent, *mutability, quote! { & #lifetime })
+ .with_context(|| {
+ format!("Failed to format the referent of the reference type `{ty}`")
+ })?
}
ty::TyKind::FnPtr(sig) => {
@@ -648,8 +706,15 @@
let mut prereqs = CcPrerequisites::default();
prereqs.includes.insert(db.support_header("internal/cxx20_backports.h"));
- let ret_type = format_ret_ty_for_cc(db, &sig)?.into_tokens(&mut prereqs);
- let param_types = format_param_types_for_cc(db, &sig)?
+
+ let mut sig_hir = None;
+ if let Some(hir) = ty.hir(db) {
+ if let rustc_hir::TyKind::BareFn(bare_fn) = &hir.kind {
+ sig_hir = Some(bare_fn.decl);
+ }
+ }
+ let ret_type = format_ret_ty_for_cc(db, &sig, sig_hir)?.into_tokens(&mut prereqs);
+ let param_types = format_param_types_for_cc(db, &sig, sig_hir)?
.into_iter()
.map(|snippet| snippet.into_tokens(&mut prereqs));
let tokens = quote! {
@@ -670,23 +735,95 @@
})
}
+/// Returns `Some(CcSnippet)` if `ty` is a special-cased alias type from
+/// `core::ffi` (AKA `std::ffi`).
+///
+/// TODO(b/283258442): Also handle `libc` aliases.
+fn format_core_alias_for_cc<'tcx>(
+ db: &dyn BindingsGenerator<'tcx>,
+ ty: SugaredTy<'tcx>,
+) -> Option<CcSnippet> {
+ let tcx = db.tcx();
+ let hir_ty = ty.hir(db)?;
+ let rustc_hir::TyKind::Path(rustc_hir::QPath::Resolved(None, path)) = &hir_ty.kind else {
+ return None;
+ };
+ let rustc_hir::def::Res::Def(rustc_hir::def::DefKind::TyAlias, alias_def_id) = &path.res else {
+ return None;
+ };
+ let def_path = tcx.def_path(*alias_def_id);
+
+ // Note: the `std::ffi` aliases are still originally defined in `core::ffi`, so
+ // we only need to check for a crate name of `core` here.
+ if tcx.crate_name(def_path.krate) != sym::core {
+ return None;
+ };
+ let [module, item] = def_path.data.as_slice() else {
+ return None;
+ };
+ if module.data != rustc_hir::definitions::DefPathData::TypeNs(sym::ffi) {
+ return None;
+ };
+ let rustc_hir::definitions::DefPathData::TypeNs(item) = item.data else {
+ return None;
+ };
+ let cpp_type = match item.as_str() {
+ "c_char" => quote! { char},
+ "c_schar" => quote! { signed char},
+ "c_uchar" => quote! { unsigned char},
+ "c_short" => quote! { short},
+ "c_ushort" => quote! { unsigned short},
+ "c_int" => quote! { int},
+ "c_uint" => quote! { unsigned int},
+ "c_long" => quote! { long},
+ "c_ulong" => quote! { unsigned long},
+ "c_longlong" => quote! { long long},
+ "c_ulonglong" => quote! { unsigned long long},
+ _ => return None,
+ };
+ Some(CcSnippet::new(cpp_type))
+}
+
+/// Returns the C++ return type.
+///
+/// `sig_hir` is the optional HIR `FnDecl`, if available. This is used to
+/// retrieve alias information.
fn format_ret_ty_for_cc<'tcx>(
db: &dyn BindingsGenerator<'tcx>,
- sig: &ty::FnSig<'tcx>,
+ sig_mid: &ty::FnSig<'tcx>,
+ sig_hir: Option<&rustc_hir::FnDecl<'tcx>>,
) -> Result<CcSnippet> {
- db.format_ty_for_cc(sig.output(), TypeLocation::FnReturn)
+ let hir = sig_hir.and_then(|sig_hir| match sig_hir.output {
+ rustc_hir::FnRetTy::Return(hir_ty) => Some(hir_ty),
+ _ => None,
+ });
+ db.format_ty_for_cc(SugaredTy::new(sig_mid.output(), hir), TypeLocation::FnReturn)
.context("Error formatting function return type")
}
+/// Returns the C++ parameter types.
+///
+/// `sig_hir` is the optional HIR FnSig, if available. This is used to retrieve
+/// alias information.
fn format_param_types_for_cc<'tcx>(
db: &dyn BindingsGenerator<'tcx>,
- sig: &ty::FnSig<'tcx>,
+ sig_mid: &ty::FnSig<'tcx>,
+ sig_hir: Option<&rustc_hir::FnDecl<'tcx>>,
) -> Result<Vec<CcSnippet>> {
- sig.inputs()
+ if let Some(sig_hir) = sig_hir {
+ assert_eq!(
+ sig_mid.inputs().len(),
+ sig_hir.inputs.len(),
+ "internal error: MIR and HIR function signatures do not line up"
+ );
+ }
+ sig_mid
+ .inputs()
.iter()
.enumerate()
- .map(|(i, &ty)| {
- db.format_ty_for_cc(ty, TypeLocation::FnParam)
+ .map(|(i, &mid)| {
+ let hir = sig_hir.map(|sig_hir| &sig_hir.inputs[i]);
+ db.format_ty_for_cc(SugaredTy::new(mid, hir), TypeLocation::FnParam)
.with_context(|| format!("Error handling parameter #{i}"))
})
.collect()
@@ -820,10 +957,22 @@
tcx.instantiate_bound_regions_uncached(sig, region_f)
}
-fn get_fn_sig(tcx: TyCtxt, fn_def_id: LocalDefId) -> ty::FnSig {
- let fn_def_id = fn_def_id.to_def_id(); // LocalDefId => DefId
- let sig = tcx.fn_sig(fn_def_id).instantiate_identity();
- liberate_and_deanonymize_late_bound_regions(tcx, sig, fn_def_id)
+/// 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.
+fn get_fn_sig(tcx: TyCtxt, local_def_id: LocalDefId) -> (ty::FnSig, &rustc_hir::FnDecl) {
+ let def_id = local_def_id.to_def_id();
+ let sig_mid = liberate_and_deanonymize_late_bound_regions(
+ tcx,
+ tcx.fn_sig(def_id).instantiate_identity(),
+ def_id,
+ );
+ let sig_hir = tcx.hir_node_by_def_id(local_def_id).fn_sig().unwrap();
+ (sig_mid, sig_hir.decl)
}
/// Formats a C++ function declaration of a thunk that wraps a Rust function
@@ -832,17 +981,19 @@
fn format_thunk_decl<'tcx>(
db: &dyn BindingsGenerator<'tcx>,
fn_def_id: DefId,
- sig: &ty::FnSig<'tcx>,
+ sig_mid: &ty::FnSig<'tcx>,
+ sig_hir: Option<&rustc_hir::FnDecl<'tcx>>,
thunk_name: &TokenStream,
) -> Result<CcSnippet> {
let tcx = db.tcx();
let mut prereqs = CcPrerequisites::default();
- let main_api_ret_type = format_ret_ty_for_cc(db, sig)?.into_tokens(&mut prereqs);
+ 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)?;
- sig.inputs()
+ let cpp_types = format_param_types_for_cc(db, sig_mid, sig_hir)?;
+ sig_mid
+ .inputs()
.iter()
.zip(cpp_types.into_iter())
.map(|(&ty, cpp_type)| -> Result<TokenStream> {
@@ -864,7 +1015,7 @@
};
let thunk_ret_type: TokenStream;
- if is_c_abi_compatible_by_value(sig.output()) {
+ if is_c_abi_compatible_by_value(sig_mid.output()) {
thunk_ret_type = main_api_ret_type;
} else {
thunk_ret_type = quote! { void };
@@ -1167,7 +1318,9 @@
})
}
DefKind::Struct | DefKind::Enum => {
- let use_type = tcx.type_of(def_id).instantiate_identity();
+ // This points directly to a type definition, not an alias or compound data
+ // type, so we can drop the hir type.
+ let use_type = SugaredTy::new(tcx.type_of(def_id).instantiate_identity(), None);
create_type_alias(db, using_name, use_type)
}
_ => bail!(
@@ -1183,14 +1336,18 @@
) -> Result<ApiSnippets> {
let tcx = db.tcx();
let def_id: DefId = local_def_id.to_def_id();
- let alias_type = tcx.type_of(def_id).instantiate_identity();
+ let Item { kind: ItemKind::TyAlias(hir_ty, ..), .. } = tcx.hir().expect_item(local_def_id)
+ else {
+ panic!("called format_type_alias on a non-type-alias");
+ };
+ let alias_type = SugaredTy::new(tcx.type_of(def_id).instantiate_identity(), Some(*hir_ty));
create_type_alias(db, tcx.item_name(def_id).as_str(), alias_type)
}
fn create_type_alias<'tcx>(
db: &dyn BindingsGenerator<'tcx>,
alias_name: &str,
- alias_type: Ty<'tcx>,
+ alias_type: SugaredTy<'tcx>,
) -> Result<ApiSnippets> {
let cc_bindings = format_ty_for_cc(db, alias_type, TypeLocation::Other)?;
let mut main_api_prereqs = CcPrerequisites::default();
@@ -1220,10 +1377,10 @@
"Generic functions are not supported yet (b/259749023)"
);
- let sig = get_fn_sig(tcx, local_def_id);
- check_fn_sig(&sig)?;
+ let (sig_mid, sig_hir) = get_fn_sig(tcx, local_def_id);
+ check_fn_sig(&sig_mid)?;
// TODO(b/262904507): Don't require thunks for mangled extern "C" functions.
- let needs_thunk = is_thunk_required(&sig).is_err()
+ let needs_thunk = is_thunk_required(&sig_mid).is_err()
|| (tcx.get_attr(def_id, rustc_span::symbol::sym::no_mangle).is_none()
&& tcx.get_attr(def_id, rustc_span::symbol::sym::export_name).is_none());
let thunk_name = {
@@ -1249,7 +1406,8 @@
.context("Error formatting function name")?;
let mut main_api_prereqs = CcPrerequisites::default();
- let main_api_ret_type = format_ret_ty_for_cc(db, &sig)?.into_tokens(&mut main_api_prereqs);
+ let main_api_ret_type =
+ format_ret_ty_for_cc(db, &sig_mid, Some(sig_hir))?.into_tokens(&mut main_api_prereqs);
struct Param<'tcx> {
cc_name: TokenStream,
@@ -1258,10 +1416,10 @@
}
let params = {
let names = tcx.fn_arg_names(def_id).iter();
- let cpp_types = format_param_types_for_cc(db, &sig)?;
+ let cpp_types = format_param_types_for_cc(db, &sig_mid, Some(sig_hir))?;
names
.enumerate()
- .zip(sig.inputs().iter())
+ .zip(sig_mid.inputs().iter())
.zip(cpp_types)
.map(|(((i, name), &ty), cpp_type)| {
let cc_name = format_cc_ident(name.as_str())
@@ -1404,8 +1562,8 @@
};
let mut prereqs = main_api_prereqs;
- let thunk_decl =
- format_thunk_decl(db, def_id, &sig, &thunk_name)?.into_tokens(&mut prereqs);
+ let thunk_decl = format_thunk_decl(db, def_id, &sig_mid, Some(sig_hir), &thunk_name)?
+ .into_tokens(&mut prereqs);
let mut thunk_args = params
.iter()
@@ -1425,12 +1583,12 @@
})
.collect_vec();
let impl_body: TokenStream;
- if is_c_abi_compatible_by_value(sig.output()) {
+ if is_c_abi_compatible_by_value(sig_mid.output()) {
impl_body = quote! {
return __crubit_internal :: #thunk_name( #( #thunk_args ),* );
};
} else {
- if let Some(adt_def) = sig.output().ty_adt_def() {
+ if let Some(adt_def) = sig_mid.output().ty_adt_def() {
let core = db.format_adt_core(adt_def.did())?;
db.format_move_ctor_and_assignment_operator(core).map_err(|_| {
anyhow!("Can't pass the return type by value without a move constructor")
@@ -1470,7 +1628,7 @@
quote! { #struct_name :: #fn_name }
}
};
- format_thunk_impl(tcx, def_id, &sig, &thunk_name, fully_qualified_fn_name)?
+ format_thunk_impl(tcx, def_id, &sig_mid, &thunk_name, fully_qualified_fn_name)?
};
Ok(ApiSnippets { main_api, cc_details, rs_details })
}
@@ -1698,6 +1856,18 @@
attributes: vec![],
}]
} else {
+ let rustc_hir::Node::Item(item) = tcx.hir_node_by_def_id(core.def_id.expect_local()) else {
+ panic!("internal error: def_id referring to an ADT was not a HIR Item.");
+ };
+ let variants = match item.kind {
+ rustc_hir::ItemKind::Struct(variants, _) => variants,
+ rustc_hir::ItemKind::Union(variants, _) => variants,
+ _ => panic!(
+ "internal error: def_id referring to a non-enum ADT was not a struct or union."
+ ),
+ };
+ let hir_fields: Vec<_> = variants.fields().iter().sorted_by_key(|f| f.span).collect();
+
let mut fields = core
.self_ty
.ty_adt_def()
@@ -1706,12 +1876,16 @@
.sorted_by_key(|f| tcx.def_span(f.did))
.enumerate()
.map(|(index, field_def)| {
- let field_ty = field_def.ty(tcx, substs_ref);
- let size = get_layout(tcx, field_ty).map(|layout| layout.size().bytes());
+ // *Not* using zip, in order to crash on length mismatch.
+ let hir_field =
+ hir_fields.get(index).expect("HIR ADT had fewer fields than rustc_middle");
+ assert!(field_def.did == hir_field.def_id.to_def_id());
+ let ty = SugaredTy::new(field_def.ty(tcx, substs_ref), Some(hir_field.ty));
+ let size = get_layout(tcx, ty.mid()).map(|layout| layout.size().bytes());
let type_info = size.and_then(|size| {
Ok(FieldTypeInfo {
size,
- cpp_type: db.format_ty_for_cc(field_ty, TypeLocation::Other)?,
+ cpp_type: db.format_ty_for_cc(ty, TypeLocation::Other)?,
})
});
let name = field_def.ident(tcx);
@@ -2049,12 +2223,19 @@
};
method_name_to_cc_thunk_name.insert(method.name, format_cc_ident(&thunk_name)?);
- let sig = tcx.fn_sig(method.def_id).instantiate(tcx, substs);
- let sig = liberate_and_deanonymize_late_bound_regions(tcx, sig, method.def_id);
+ 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;
cc_thunk_decls.add_assign({
let thunk_name = format_cc_ident(&thunk_name)?;
- format_thunk_decl(db, method.def_id, &sig, &thunk_name)?
+ format_thunk_decl(db, method.def_id, &sig_mid, sig_hir, &thunk_name)?
});
rs_thunk_impls.extend({
@@ -2078,7 +2259,13 @@
let method_name = make_rs_ident(method.name.as_str());
quote! { <#struct_name as #fully_qualified_trait_name>::#method_name }
};
- format_thunk_impl(tcx, method.def_id, &sig, &thunk_name, fully_qualified_fn_name)?
+ format_thunk_impl(
+ tcx,
+ method.def_id,
+ &sig_mid,
+ &thunk_name,
+ fully_qualified_fn_name,
+ )?
}
});
}
@@ -7224,6 +7411,28 @@
("bool", ("bool", "", "", "")),
("f32", ("float", "", "", "")),
("f64", ("double", "", "", "")),
+ // The ffi aliases are special-cased to refer to the C++ fundamental integer types,
+ // if the type alias information is not lost (e.g. from generics).
+ ("std::ffi::c_char", ("char", "", "", "")),
+ ("::std::ffi::c_char", ("char", "", "", "")),
+ ("core::ffi::c_char", ("char", "", "", "")),
+ ("::core::ffi::c_char", ("char", "", "", "")),
+ ("std::ffi::c_uchar", ("unsigned char", "", "", "")),
+ ("std::ffi::c_longlong", ("long long", "", "", "")),
+ ("c_char", ("char", "", "", "")),
+ // Simple pointers/references do not lose the type alias information.
+ ("*const std::ffi::c_uchar", ("unsigned char const *", "", "", "")),
+ (
+ "&'static std::ffi::c_uchar",
+ (
+ "unsigned char const & [[clang :: annotate_type (\"lifetime\" , \"static\")]]",
+ "",
+ "",
+ "",
+ ),
+ ),
+ // Generics lose type alias information.
+ ("Identity<std::ffi::c_longlong>", ("std::int64_t", "<cstdint>", "", "")),
("i8", ("std::int8_t", "<cstdint>", "", "")),
("i16", ("std::int16_t", "<cstdint>", "", "")),
("i32", ("std::int32_t", "<cstdint>", "", "")),
@@ -7325,6 +7534,13 @@
pub struct OriginallyCcStruct {
pub x: i32
}
+
+ #[allow(unused)]
+ type Identity<T> = T;
+
+ pub use core::ffi::c_char;
+ // TODO(b/283258442): Correctly handle something like this:
+ // pub type MyChar = core::ffi::c_char;
};
test_ty(
TypeLocation::FnParam,
@@ -7598,7 +7814,7 @@
}
};
test_ty(TypeLocation::FnParam, &testcases, preamble, |desc, tcx, ty, expected_tokens| {
- let actual_tokens = format_ty_for_rs(tcx, ty).unwrap().to_string();
+ let actual_tokens = format_ty_for_rs(tcx, ty.mid()).unwrap().to_string();
let expected_tokens = expected_tokens.parse::<TokenStream>().unwrap().to_string();
assert_eq!(actual_tokens, expected_tokens, "{desc}");
});
@@ -7640,7 +7856,7 @@
let preamble = quote! {};
test_ty(TypeLocation::FnParam, &testcases, preamble, |desc, tcx, ty, expected_err| {
let anyhow_err =
- format_ty_for_rs(tcx, ty).expect_err(&format!("Expecting error for: {desc}"));
+ format_ty_for_rs(tcx, ty.mid()).expect_err(&format!("Expecting error for: {desc}"));
let actual_err = format!("{anyhow_err:#}");
assert_eq!(&actual_err, *expected_err, "{desc}");
});
@@ -8531,7 +8747,7 @@
TestFn: for<'tcx> Fn(
/* testcase_description: */ &str,
TyCtxt<'tcx>,
- Ty<'tcx>,
+ SugaredTy<'tcx>,
&Expectation,
) + Sync,
Expectation: Sync,
@@ -8554,11 +8770,20 @@
input.to_string()
};
run_compiler_for_testing(input, |tcx| {
- let def_id = find_def_id_by_name(tcx, "test_function");
- let sig = get_fn_sig(tcx, def_id);
+ let (sig_mid, sig_hir) = get_fn_sig(tcx, find_def_id_by_name(tcx, "test_function"));
let ty = match type_location {
- TypeLocation::FnReturn => sig.output(),
- TypeLocation::FnParam => sig.inputs()[0],
+ TypeLocation::FnReturn => {
+ let rustc_hir::FnRetTy::Return(ty_hir) = sig_hir.output else {
+ unreachable!(
+ "HIR return type should be fully specified, got: {:?}",
+ sig_hir.output
+ );
+ };
+ SugaredTy::new(sig_mid.output(), Some(ty_hir))
+ }
+ TypeLocation::FnParam => {
+ SugaredTy::new(sig_mid.inputs()[0], Some(&sig_hir.inputs[0]))
+ }
TypeLocation::Other => unimplemented!(),
};
test_fn(&desc, tcx, ty, expected);
diff --git a/cc_bindings_from_rs/test/primitive_types/primitive_types.rs b/cc_bindings_from_rs/test/primitive_types/primitive_types.rs
index 3989165..27ed60c 100644
--- a/cc_bindings_from_rs/test/primitive_types/primitive_types.rs
+++ b/cc_bindings_from_rs/test/primitive_types/primitive_types.rs
@@ -29,6 +29,7 @@
}
extern "C" fn i8_func(_: i8) {}
+extern "C" fn c_char_func(_: core::ffi::c_char) {}
pub mod return_types {
use core::ffi;
@@ -41,6 +42,39 @@
core::ptr::null()
}
+ pub fn c_char() -> ffi::c_char {
+ 0
+ }
+ pub fn c_schar() -> ffi::c_schar {
+ 0
+ }
+ pub fn c_uchar() -> ffi::c_uchar {
+ 0
+ }
+ pub fn c_short() -> ffi::c_short {
+ 0
+ }
+ pub fn c_ushort() -> ffi::c_ushort {
+ 0
+ }
+ pub fn c_int() -> ffi::c_int {
+ 0
+ }
+ pub fn c_uint() -> ffi::c_uint {
+ 0
+ }
+ pub fn c_long() -> ffi::c_long {
+ 0
+ }
+ pub fn c_ulong() -> ffi::c_ulong {
+ 0
+ }
+ pub fn c_longlong() -> ffi::c_longlong {
+ 0
+ }
+ pub fn c_ulonglong() -> ffi::c_ulonglong {
+ 0
+ }
pub fn c_float() -> ffi::c_float {
0.0
}
@@ -88,6 +122,10 @@
pub fn i8_func() -> extern "C" fn(i8) {
crate::i8_func
}
+
+ pub fn c_char_func() -> extern "C" fn(ffi::c_char) {
+ crate::c_char_func
+ }
}
pub mod field_types {
@@ -96,6 +134,17 @@
pub c_void_mut_ptr: *mut ffi::c_void,
pub c_void_const_ptr: *const ffi::c_void,
+ pub c_char: ffi::c_char,
+ pub c_schar: ffi::c_schar,
+ pub c_uchar: ffi::c_uchar,
+ pub c_short: ffi::c_short,
+ pub c_ushort: ffi::c_ushort,
+ pub c_int: ffi::c_int,
+ pub c_uint: ffi::c_uint,
+ pub c_long: ffi::c_long,
+ pub c_ulong: ffi::c_ulong,
+ pub c_longlong: ffi::c_longlong,
+ pub c_ulonglong: ffi::c_ulonglong,
pub c_float: ffi::c_float,
pub c_double: ffi::c_double,
@@ -113,5 +162,6 @@
pub f64: f64,
pub i8_func: extern "C" fn(i8),
+ pub c_char_func: extern "C" fn(ffi::c_char),
}
}
diff --git a/cc_bindings_from_rs/test/primitive_types/primitive_types_test.cc b/cc_bindings_from_rs/test/primitive_types/primitive_types_test.cc
index 954178c..72e3298 100644
--- a/cc_bindings_from_rs/test/primitive_types/primitive_types_test.cc
+++ b/cc_bindings_from_rs/test/primitive_types/primitive_types_test.cc
@@ -53,6 +53,41 @@
static_assert(
std::is_same_v<decltype(types::c_void_const_ptr()), const void*>);
EXPECT_EQ(types::c_void_const_ptr(), nullptr);
+
+ static_assert(std::is_same_v<decltype(types::c_char()), char>);
+ EXPECT_EQ(types::c_char(), 0);
+
+ static_assert(std::is_same_v<decltype(types::c_schar()), signed char>);
+ EXPECT_EQ(types::c_schar(), 0);
+
+ static_assert(std::is_same_v<decltype(types::c_uchar()), unsigned char>);
+ EXPECT_EQ(types::c_uchar(), 0);
+
+ static_assert(std::is_same_v<decltype(types::c_short()), short>);
+ EXPECT_EQ(types::c_short(), 0);
+
+ static_assert(std::is_same_v<decltype(types::c_ushort()), unsigned short>);
+ EXPECT_EQ(types::c_ushort(), 0);
+
+ static_assert(std::is_same_v<decltype(types::c_int()), int>);
+ EXPECT_EQ(types::c_int(), 0);
+
+ static_assert(std::is_same_v<decltype(types::c_uint()), unsigned int>);
+ EXPECT_EQ(types::c_uint(), 0);
+
+ static_assert(std::is_same_v<decltype(types::c_long()), long>);
+ EXPECT_EQ(types::c_long(), 0);
+
+ static_assert(std::is_same_v<decltype(types::c_ulong()), unsigned long>);
+ EXPECT_EQ(types::c_ulong(), 0);
+
+ static_assert(std::is_same_v<decltype(types::c_longlong()), long long>);
+ EXPECT_EQ(types::c_longlong(), 0);
+
+ static_assert(
+ std::is_same_v<decltype(types::c_ulonglong()), unsigned long long>);
+ EXPECT_EQ(types::c_ulonglong(), 0);
+
static_assert(std::is_same_v<decltype(types::c_float()), float>);
EXPECT_EQ(types::c_float(), 0);
@@ -97,6 +132,8 @@
static_assert(std::is_same_v<decltype(types::i8_func()),
std::type_identity_t<void(int8_t)>&>);
+ static_assert(std::is_same_v<decltype(types::c_char_func()),
+ std::type_identity_t<void(char)>&>);
}
TEST(PrimitiveTypesTest, FieldTypes) {
@@ -105,6 +142,18 @@
static_assert(std::is_same_v<decltype(Types::c_void_mut_ptr), void*>);
static_assert(std::is_same_v<decltype(Types::c_void_const_ptr), const void*>);
+ static_assert(std::is_same_v<decltype(Types::c_char), char>);
+ static_assert(std::is_same_v<decltype(Types::c_schar), signed char>);
+ static_assert(std::is_same_v<decltype(Types::c_uchar), unsigned char>);
+ static_assert(std::is_same_v<decltype(Types::c_short), short>);
+ static_assert(std::is_same_v<decltype(Types::c_ushort), unsigned short>);
+ static_assert(std::is_same_v<decltype(Types::c_int), int>);
+ static_assert(std::is_same_v<decltype(Types::c_uint), unsigned int>);
+ static_assert(std::is_same_v<decltype(Types::c_long), long>);
+ static_assert(std::is_same_v<decltype(Types::c_ulong), unsigned long>);
+ static_assert(std::is_same_v<decltype(Types::c_longlong), long long>);
+ static_assert(
+ std::is_same_v<decltype(Types::c_ulonglong), unsigned long long>);
static_assert(std::is_same_v<decltype(Types::c_float), float>);
static_assert(std::is_same_v<decltype(Types::c_double), double>);
@@ -123,6 +172,9 @@
static_assert(std::is_same_v<decltype(Types::i8_func),
std::type_identity_t<void(int8_t)>*>);
+
+ static_assert(std::is_same_v<decltype(Types::c_char_func),
+ std::type_identity_t<void(char)>*>);
}
} // namespace
} // namespace crubit
diff --git a/docs/cpp/fundamental_types.md b/docs/cpp/fundamental_types.md
index 46e64bf..0b30b45 100644
--- a/docs/cpp/fundamental_types.md
+++ b/docs/cpp/fundamental_types.md
@@ -13,36 +13,22 @@
Crubit, then `int32_t` in C++ becomes `i32` in Rust. Vice versa, if you call a
Rust interface from C++ using Crubit, `i32` in Rust becomes `int32_t` in C++.
-C++ | Rust
------------ | -------------------------------------------------------
-`void` | `()` as a return type, `::core::ffi::c_void` otherwise.
-`int8_t` | `i8`
-`int16_t` | `i16`
-`int32_t` | `i32`
-`int64_t` | `i64`
-`intptr_t` | `isize`
-`uint8_t` | `u8`
-`uint16_t` | `u16`
-`uint32_t` | `u32`
-`uint64_t` | `u64`
-`uintptr_t` | `usize`
-`bool` | `bool`
-`double` | `f64`
-`float` | `f32`
-
-## One-way map of C++ into Rust types
-
-The C++ types below are mapped one-way into the corresponding Rust types. For
-example `size_t` maps to `usize`, but `usize` maps to `uintptr_t`.
-
-TODO(b/283258442): `::core::ffi::*` should eventually be a bidirectional mapping
-
C++ | Rust
--------------------- | -----------------------------
-`ptrdiff_t` | `isize`
-`size_t` | `usize`
-`char16_t` | `u16`
-`char32_t` | `u32` [^char32_t]
+-------------------- | -------------------------------------------------------
+`void` | `()` as a return type, `::core::ffi::c_void` otherwise.
+`int8_t` | `i8`
+`int16_t` | `i16`
+`int32_t` | `i32`
+`int64_t` | `i64`
+`intptr_t` | `isize`
+`uint8_t` | `u8`
+`uint16_t` | `u16`
+`uint32_t` | `u32`
+`uint64_t` | `u64`
+`uintptr_t` | `usize`
+`bool` | `bool`
+`double` | `f64`
+`float` | `f32`
`char` | `::core::ffi::c_char` [^char]
`signed char` | `::core::ffi::c_schar`
`unsigned char` | `::core::ffi::c_uchar`
@@ -55,6 +41,18 @@
`long long` | `::core::ffi::c_longlong`
`unsigned long long` | `::core::ffi::c_ulonglong`
+## One-way map of C++ into Rust types
+
+The C++ types below are mapped one-way into the corresponding Rust types. For
+example `size_t` maps to `usize`, but `usize` maps to `uintptr_t`.
+
+C++ | Rust
+----------- | -----------------
+`ptrdiff_t` | `isize`
+`size_t` | `usize`
+`char16_t` | `u16`
+`char32_t` | `u32` [^char32_t]
+
## Unsupported types
Bindings for the following types are not supported at this point: