Support bindings for `pub extern "C" fn foo() { ... }`.

This CL adds support for generating bindings for very simple Rust
functions:
- `extern "C"` is required (i.e. we don't have to worry about generating
  Rust thunks just yet).
- Only the unit / `void` / `()` return type is supported
- No parameters are supported.

Still, after this CL it should be possible to start working on
end-to-end tests that actually verify that the generated bindings
build+link without any issues.

PiperOrigin-RevId: 481745367
diff --git a/cc_bindings_from_rs/bindings.rs b/cc_bindings_from_rs/bindings.rs
index e924338..6d8b964 100644
--- a/cc_bindings_from_rs/bindings.rs
+++ b/cc_bindings_from_rs/bindings.rs
@@ -2,16 +2,18 @@
 // Exceptions. See /LICENSE for license information.
 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
 
-use anyhow::{bail, Result};
+use anyhow::{anyhow, bail, Context, Result};
 use code_gen_utils::format_cc_ident;
 use proc_macro2::TokenStream;
 use quote::quote;
+use rustc_hir::{Item, ItemKind, Node, Unsafety};
 use rustc_interface::Queries;
 use rustc_middle::dep_graph::DepContext;
 use rustc_middle::middle::exported_symbols::ExportedSymbol;
-use rustc_middle::ty::TyCtxt;
+use rustc_middle::ty::{Ty, TyCtxt};
 use rustc_span::def_id::{LocalDefId, LOCAL_CRATE};
-use std::fmt::Display;
+use rustc_span::symbol::Ident;
+use rustc_target::spec::abi::Abi;
 
 pub struct GeneratedBindings {
     pub h_body: TokenStream,
@@ -60,21 +62,115 @@
     Ok(query_context.peek_mut().enter(f))
 }
 
-fn format_def(_tcx: TyCtxt, _def_id: LocalDefId) -> Result<TokenStream> {
-    bail!("Nothing works yet!")
+fn format_ty(ty: Ty) -> Result<TokenStream> {
+    if ty.is_unit() {
+        Ok(quote! { void })
+    } else {
+        bail!("The following Rust type is not supported yet: {}", ty)
+    }
 }
 
+/// Formats a function with the given `def_id` and `fn_name`.
+///
+/// Will panic if `def_id` is invalid or doesn't identify a function.
+fn format_fn(tcx: TyCtxt, def_id: LocalDefId, fn_name: &Ident) -> Result<TokenStream> {
+    let sig = tcx
+        .fn_sig(def_id.to_def_id())
+        .no_bound_vars()
+        .expect("Caller (e.g. `format_def`) should verify no unbound generic vars");
+
+    if sig.c_variadic {
+        // TODO(b/254097223): Add support for variadic functions.
+        bail!("C variadic functions are not supported (b/254097223)");
+    }
+    if sig.inputs().len() != 0 {
+        // TODO(lukasza): Add support for function parameters.
+        bail!("Function parameters are not supported yet");
+    }
+
+    match sig.unsafety {
+        Unsafety::Normal => (),
+        Unsafety::Unsafe => {
+            // TODO(b/254095482): Figure out how to handle `unsafe` functions.
+            bail!("Bindings for `unsafe` functions are not fully designed yet (b/254095482)");
+        }
+    }
+
+    let need_thunk = match sig.abi {
+        // Before https://rust-lang.github.io/rfcs/2945-c-unwind-abi.html a Rust panic that
+        // "escapes" a "C" ABI function leads to Undefined Behavior.  This is unfortunate,
+        // but Crubit's `panics_and_exceptions.md` documents that `-Cpanic=abort` is the
+        // only supported configuration.
+        //
+        // After https://rust-lang.github.io/rfcs/2945-c-unwind-abi.html a Rust panic that
+        // tries to "escape" a "C" ABI function will terminate the program.  This is okay.
+        Abi::C { unwind: false } => false,
+
+        // After https://rust-lang.github.io/rfcs/2945-c-unwind-abi.html a new "C-unwind" ABI
+        // may be used by Rust functions that want to safely propagate Rust panics through
+        // frames that may belong to another language.
+        Abi::C { unwind: true } => false,
+
+        // In all other cases, C++ needs to call into a Rust thunk that wraps the original function
+        // in a "C" ABI.
+        _ => true,
+    };
+    if need_thunk {
+        // TODO(b/254097223): Add support for Rust thunks.
+        bail!(
+            "Functions that require Rust thunks (e.g. non-`extern \"C\"`) are not supported yet \
+               (b/254097223)"
+        );
+    }
+
+    let ret_type = format_ty(sig.output()).context("Error formatting function return type")?;
+    let fn_name = format_cc_ident(fn_name.as_str()).context("Error formatting function name")?;
+
+    Ok(quote! {
+        extern "C" #ret_type #fn_name ();
+    })
+}
+
+/// Formats a Rust item idenfied by `def_id`.
+///
+/// Will panic if `def_id` is invalid (i.e. doesn't identify a Rust node or
+/// item).
+fn format_def(tcx: TyCtxt, def_id: LocalDefId) -> Result<TokenStream> {
+    match tcx.hir().get_by_def_id(def_id) {
+        Node::Item(item) => match item {
+            Item { ident, kind: ItemKind::Fn(_hir_fn_sig, generics, _body), .. } => {
+                if generics.params.len() == 0 {
+                    format_fn(tcx, def_id, &ident)
+                } else {
+                    bail!(
+                        "Generic functions (lifetime-generic or type-generic) are not supported yet"
+                    )
+                }
+            }
+            Item { kind, .. } => bail!("Unsupported rustc_hir::hir::ItemKind: {}", kind.descr()),
+        },
+        _unsupported_node => bail!("Unsupported rustc_hir::hir::Node"),
+    }
+}
+
+/// Formats a C++ comment explaining why no bindings have been generated for
+/// `local_def_id`.
 fn format_unsupported_def(
     tcx: TyCtxt,
     local_def_id: LocalDefId,
-    err_msg: impl Display,
+    err: anyhow::Error,
 ) -> TokenStream {
     let span = tcx.sess().source_map().span_to_embeddable_string(tcx.def_span(local_def_id));
     let name = tcx.def_path_str(local_def_id.to_def_id());
-    let msg = format!("Error while generating bindings for `{name}` defined at {span}: {err_msg}");
+
+    // https://docs.rs/anyhow/latest/anyhow/struct.Error.html#display-representations
+    // says: To print causes as well [...], use the alternate selector “{:#}”.
+    let msg = format!("Error generating bindings for `{name}` defined at {span}: {err:#}");
     quote! { __NEWLINE__ __NEWLINE__ __COMMENT__ #msg __NEWLINE__ }
 }
 
+/// Formats all public items from the Rust crate being compiled (aka the
+/// `LOCAL_CRATE`).
 fn format_crate(tcx: TyCtxt) -> Result<TokenStream> {
     let crate_name = format_cc_ident(tcx.crate_name(LOCAL_CRATE).as_str())?;
 
@@ -82,7 +178,8 @@
     // entry point for finding Rust definitions that need to be wrapping in C++
     // bindings.  For example, it _seems_ that things like `type` aliases or
     // `struct`s (without an `impl`) won't be visible to a linker and therefore
-    // won't have exported symbols.
+    // won't have exported symbols.  Additionally, walking Rust's modules top-down
+    // might result in easier translation into C++ namespaces.
     let snippets =
         tcx.exported_symbols(LOCAL_CRATE).iter().filter_map(move |(symbol, _)| match symbol {
             ExportedSymbol::NonGeneric(def_id) => {
@@ -107,7 +204,7 @@
                 // crate.  One specific example (covered via `async fn` in one of the tests) is
                 // `DefId(2:14250 ~ core[ef75]::future::from_generator)`.
                 def_id.as_local().map(|local_id| {
-                    format_unsupported_def(tcx, local_id, "Generics are not supported yet.")
+                    format_unsupported_def(tcx, local_id, anyhow!("Generics are not supported yet."))
                 })
             }
             ExportedSymbol::DropGlue(_) | ExportedSymbol::NoDefId(_) => None,
@@ -229,15 +326,10 @@
                 }
             "#;
         test_generated_bindings(test_src, |bindings| {
-            // TODO(lukasza): Fix test expectations once this becomes supported (in early Q4
-            // 2022).
-            let expected_comment_txt = "Error while generating bindings for `public_function` \
-                                        defined at <crubit_unittests.rs>:2:17: 2:52: \
-                                        Nothing works yet!";
             assert_cc_matches!(
                 bindings.h_body,
                 quote! {
-                    __COMMENT__ #expected_comment_txt
+                    extern "C" void public_function();
                 }
             );
         });
@@ -285,18 +377,19 @@
         // (in `format_crate` and `format_unsupported_def`).
         // - This test covers only a single example of an unsupported item.  Additional
         //   coverage is provided by `test_format_def_unsupported_...` tests.
-        // - This test somewhat arbitrarily chooses an example of an unsupported item
-        //   (i.e. if `async fn` becomes supported by `cc_bindings_from_rs` in the
-        //   future, then the test will have to be modified to use another `test_src`
-        //   input).
+        // - This test somewhat arbitrarily chooses an example of an unsupported item,
+        //   trying to pick one that 1) will never be supported (b/254104998 has some extra
+        //   notes about APIs named after reserved C++ keywords) and 2) tests that the
+        //   full error chain is included in the message.
         let test_src = r#"
-                pub async fn public_function() {}
+                pub extern "C" fn reinterpret_cast() {}
             "#;
-
         test_generated_bindings(test_src, |bindings| {
-            let expected_comment_txt = "Error while generating bindings for `public_function` \
-                                        defined at <crubit_unittests.rs>:2:17: 2:47: \
-                                        Nothing works yet!";
+            let expected_comment_txt = "Error generating bindings for `reinterpret_cast` \
+                 defined at <crubit_unittests.rs>:2:17: 2:53: \
+                 Error formatting function name: \
+                 `reinterpret_cast` is a C++ reserved keyword \
+                 and can't be used as a C++ identifier";
             assert_cc_matches!(
                 bindings.h_body,
                 quote! {
@@ -307,26 +400,248 @@
     }
 
     #[test]
-    fn test_format_def_unsupported_fn_extern_c_no_params_no_return_type() {
+    fn test_format_def_fn_extern_c_no_params_no_return_type() {
         let test_src = r#"
                 pub extern "C" fn public_function() {}
             "#;
         test_format_def(test_src, "public_function", |result| {
-            // TODO(lukasza): Fix test expectations once this becomes supported (in early Q4
-            // 2022).
-            let err = result.expect_err("Test expects an error here").to_string();
-            assert_eq!(err, "Nothing works yet!");
+            assert_cc_matches!(
+                result.expect("Test expects success here"),
+                quote! {
+                    extern "C" void public_function();
+                }
+            );
+        });
+    }
+
+    #[test]
+    fn test_format_def_fn_extern_c_no_params_unit_return_type() {
+        // This test is very similar to the
+        // `test_format_def_fn_extern_c_no_params_no_return_type` above, except
+        // that the return type is explicitly spelled out.  There is no difference in
+        // `ty::FnSig` so our code behaves exactly the same, but the test has been
+        // planned based on earlier, hir-focused approach and having this extra
+        // test coverage shouldn't hurt. (`hir::FnSig` and `hir::FnRetTy` _do_
+        // see a difference between the two tests).
+        let test_src = r#"
+                pub extern "C" fn explicit_unit_return_type() -> () {}
+            "#;
+        test_format_def(test_src, "explicit_unit_return_type", |result| {
+            assert_cc_matches!(
+                result.expect("Test expects success here"),
+                quote! {
+                    extern "C" void explicit_unit_return_type();
+                }
+            );
+        });
+    }
+
+    #[test]
+    fn test_format_def_unsupported_fn_unsafe() {
+        // This tests how bindings for an `unsafe fn` are generated.
+        let test_src = r#"
+                pub unsafe extern "C" fn foo() {}
+            "#;
+        test_format_def(test_src, "foo", |result| {
+            let err = result.expect_err("Test expects an error here");
+            assert_eq!(
+                err,
+                "Bindings for `unsafe` functions \
+                             are not fully designed yet (b/254095482)"
+            );
+        });
+    }
+
+    #[test]
+    fn test_format_def_fn_const() {
+        // This tests how bindings for an `const fn` are generated.
+        //
+        // Right now the `const` qualifier is ignored, but one can imagine that in the
+        // (very) long-term future such functions (including their bodies) could
+        // be translated into C++ `consteval` functions.
+        let test_src = r#"
+                pub const fn foo(i: i32) -> i32 { i * 42 }
+            "#;
+        test_format_def(test_src, "foo", |result| {
+            // TODO(lukasza): Update test expectations below once `const fn` example from
+            // the testcase doesn't just error out (and is instead supported as
+            // a non-`consteval` binding).
+            // TODO(b/254095787): Update test expectations below once `const fn` from Rust
+            // is translated into a `consteval` C++ function.
+            let err = result.expect_err("Test expects an error here");
+            assert_eq!(err, "Function parameters are not supported yet",);
+        });
+    }
+
+    #[test]
+    fn test_format_def_fn_with_c_unwind_abi() {
+        // See also https://rust-lang.github.io/rfcs/2945-c-unwind-abi.html
+        let test_src = r#"
+                #![feature(c_unwind)]
+                pub extern "C-unwind" fn may_throw() {}
+            "#;
+        test_format_def(test_src, "may_throw", |result| {
+            assert_cc_matches!(
+                result.expect("Test expects success here"),
+                quote! {
+                    extern "C" void may_throw();
+                }
+            );
+        });
+    }
+
+    #[test]
+    fn test_format_def_fn_with_type_aliased_return_type() {
+        // Type aliases disappear at the `rustc_middle::ty::Ty` level and therefore in
+        // the short-term the generated bindings also ignore type aliases.
+        //
+        // TODO(b/254096006): Consider preserving `type` aliases when generating
+        // bindings.
+        let test_src = r#"
+                type MyTypeAlias = ();
+
+                pub extern "C" fn type_aliased_return() -> MyTypeAlias {}
+            "#;
+        test_format_def(test_src, "type_aliased_return", |result| {
+            assert_cc_matches!(
+                result.expect("Test expects success here"),
+                quote! {
+                    extern "C" void type_aliased_return();
+                }
+            );
+        });
+    }
+
+    #[test]
+    fn test_format_def_unsupported_fn_name_is_reserved_cpp_keyword() {
+        let test_src = r#"
+                pub extern "C" fn reinterpret_cast() -> () {}
+            "#;
+        test_format_def(test_src, "reinterpret_cast", |result| {
+            let err = result.expect_err("Test expects an error here");
+            assert_eq!(
+                err,
+                "Error formatting function name: \
+                       `reinterpret_cast` is a C++ reserved keyword \
+                       and can't be used as a C++ identifier"
+            );
+        });
+    }
+
+    #[test]
+    fn test_format_def_unsupported_fn_ret_type() {
+        let test_src = r#"
+                pub extern "C" fn foo() -> *const i32 { std::ptr::null() }
+            "#;
+        test_format_def(test_src, "foo", |result| {
+            let err = result.expect_err("Test expects an error here");
+            assert_eq!(
+                err,
+                "Error formatting function return type: \
+                       The following Rust type is not supported yet: *const i32"
+            );
+        });
+    }
+
+    #[test]
+    fn test_format_def_unsupported_fn_with_late_bound_lifetimes() {
+        let test_src = r#"
+                pub fn foo(arg: &i32) -> &i32 { arg }
+
+                // Lifetime inference translates the above into:
+                //     pub fn foo<'a>(arg: &'a i32) -> &'a i32 { ... }
+                // leaving 'a lifetime late-bound (it is bound with a lifetime
+                // taken from each of the callsites).  In other words, we can't
+                // just call `no_bound_vars` on this `FnSig`'s `Binder`.
+            "#;
+        test_format_def(test_src, "foo", |result| {
+            let err = result.expect_err("Test expects an error here");
+            assert_eq!(
+                err,
+                "Generic functions (lifetime-generic or type-generic) are not supported yet"
+            );
+        });
+    }
+
+    #[test]
+    fn test_format_def_unsupported_generic_fn() {
+        let test_src = r#"
+                use std::default::Default;
+                use std::fmt::Display;
+                pub fn generic_function<T: Default + Display>() {
+                    println!("{}", T::default());
+                }
+            "#;
+        test_format_def(test_src, "generic_function", |result| {
+            let err = result.expect_err("Test expects an error here");
+            assert_eq!(
+                err,
+                "Generic functions (lifetime-generic or type-generic) are not supported yet"
+            );
         });
     }
 
     #[test]
     fn test_format_def_unsupported_fn_async() {
         let test_src = r#"
-                pub async fn public_function() {}
+                pub async fn async_function() {}
             "#;
-        test_format_def(test_src, "public_function", |result| {
-            let err = result.expect_err("Test expects an error here").to_string();
-            assert_eq!(err, "Nothing works yet!");
+        test_format_def(test_src, "async_function", |result| {
+            let err = result.expect_err("Test expects an error here");
+            assert_eq!(
+                err,
+                "Functions that require Rust thunks (e.g. non-`extern \"C\"`) \
+                 are not supported yet (b/254097223)"
+            );
+        });
+    }
+
+    #[test]
+    fn test_format_def_unsupported_fn_non_c_abi() {
+        let test_src = r#"
+                pub fn default_rust_abi_function() {}
+            "#;
+        test_format_def(test_src, "default_rust_abi_function", |result| {
+            let err = result.expect_err("Test expects an error here");
+            assert_eq!(
+                err,
+                "Functions that require Rust thunks \
+                       (e.g. non-`extern \"C\"`) are not supported yet (b/254097223)"
+            );
+        })
+    }
+
+    #[test]
+    fn test_format_def_unsupported_fn_variadic() {
+        let test_src = r#"
+                #![feature(c_variadic)]
+                pub unsafe extern "C" fn variadic_function(_fmt: *const u8, ...) {}
+            "#;
+        test_format_def(test_src, "variadic_function", |result| {
+            let err = result.expect_err("Test expects an error here");
+            assert_eq!(err, "C variadic functions are not supported (b/254097223)");
+        });
+    }
+
+    #[test]
+    fn test_format_def_unsupported_fn_params() {
+        let test_src = r#"
+                pub unsafe extern "C" fn fn_with_params(_i: i32) {}
+            "#;
+        test_format_def(test_src, "fn_with_params", |result| {
+            let err = result.expect_err("Test expects an error here");
+            assert_eq!(err, "Function parameters are not supported yet");
+        });
+    }
+
+    #[test]
+    fn test_format_def_unsupported_hir_item_kind() {
+        let test_src = r#"
+                pub struct SomeStruct(i32);
+            "#;
+        test_format_def(test_src, "SomeStruct", |result| {
+            let err = result.expect_err("Test expects an error here");
+            assert_eq!(err, "Unsupported rustc_hir::hir::ItemKind: struct");
         });
     }
 
@@ -337,12 +652,18 @@
     /// result from `format_def`.)
     fn test_format_def<F, T>(source: &str, name: &str, test_function: F) -> T
     where
-        F: FnOnce(Result<TokenStream>) -> T + Send,
+        F: FnOnce(Result<TokenStream, String>) -> T + Send,
         T: Send,
     {
         run_compiler(source, |tcx| {
             let def_id = find_def_id_by_name(tcx, name);
-            test_function(format_def(tcx, def_id))
+            let result = format_def(tcx, def_id);
+
+            // https://docs.rs/anyhow/latest/anyhow/struct.Error.html#display-representations says:
+            // To print causes as well [...], use the alternate selector “{:#}”.
+            let result = result.map_err(|anyhow_err| format!("{anyhow_err:#}"));
+
+            test_function(result)
         })
     }
 
diff --git a/cc_bindings_from_rs/cc_bindings_from_rs.rs b/cc_bindings_from_rs/cc_bindings_from_rs.rs
index 4af0be0..ac13968 100644
--- a/cc_bindings_from_rs/cc_bindings_from_rs.rs
+++ b/cc_bindings_from_rs/cc_bindings_from_rs.rs
@@ -16,6 +16,7 @@
 extern crate rustc_middle;
 extern crate rustc_session;
 extern crate rustc_span;
+extern crate rustc_target;
 
 // TODO(lukasza): Make `bindings` and `cmdline` separate crates (once we move to
 // Bazel).  This hasn't been done that yet, because:
@@ -240,7 +241,7 @@
     #[derive(Debug)]
     struct TestResult {
         h_path: PathBuf,
-        rs_input_path: PathBuf,
+        // TODO(b/254097223): Cover out_rs_impl_path once Rust thunks are generated.
     }
 
     impl TestArgs {
@@ -297,7 +298,7 @@
             let rs_input_path = self.tempdir.path().join("test_crate.rs");
             std::fs::write(
                 &rs_input_path,
-                r#" pub fn public_function() {
+                r#" pub extern "C" fn public_function() {
                         private_function()
                     }
 
@@ -320,7 +321,7 @@
 
             run_with_cmdline_args(&args)?;
 
-            Ok(TestResult { h_path, rs_input_path })
+            Ok(TestResult { h_path })
         }
     }
 
@@ -331,22 +332,16 @@
 
         assert!(test_result.h_path.exists());
         let h_body = std::fs::read_to_string(&test_result.h_path)?;
-        let rs_input_path = test_result.rs_input_path.display().to_string();
         assert_eq!(
             h_body,
-            format!(
 r#"// Automatically @generated C++ bindings for the following Rust crate:
 // test_crate
 
 #pragma once
 
-namespace test_crate {{
-
-// Error while generating bindings for `public_function` defined at
-// {rs_input_path}:1:2: 1:26: Nothing works yet!
-
-}}"#
-            )
+namespace test_crate {
+extern "C" void public_function();
+}"#
         );
         Ok(())
     }
diff --git a/common/code_gen_utils.rs b/common/code_gen_utils.rs
index 2031372..59e7361 100644
--- a/common/code_gen_utils.rs
+++ b/common/code_gen_utils.rs
@@ -20,8 +20,7 @@
     // an error is returned when `ident` is a C++ reserved keyword.
     ensure!(
         !RESERVED_CC_KEYWORDS.contains(ident),
-        "`{}` is a C++ reserved keyword and \
-        can't be used as a C++ identifier in the generated bindings",
+        "`{}` is a C++ reserved keyword and can't be used as a C++ identifier",
         ident
     );