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: