Translate C++ constructors with a single T argument into `impl From<T>`.

PiperOrigin-RevId: 419711182
diff --git a/rs_bindings_from_cc/ir.rs b/rs_bindings_from_cc/ir.rs
index 8466b29..56fafe2 100644
--- a/rs_bindings_from_cc/ir.rs
+++ b/rs_bindings_from_cc/ir.rs
@@ -99,6 +99,13 @@
     pub fn is_void(&self) -> bool {
         self.name.as_deref() == Some("void")
     }
+
+    pub fn is_const_ref_to(&self, record: &Record) -> bool {
+        self.name.as_deref() == Some("&")
+            && self.type_args.first().map_or(false, |type_arg| {
+                type_arg.is_const && type_arg.name.is_none() && type_arg.decl_id == Some(record.id)
+            })
+    }
 }
 
 pub trait TypeWithDeclId {
diff --git a/rs_bindings_from_cc/ir_from_cc_test.rs b/rs_bindings_from_cc/ir_from_cc_test.rs
index c87a1f8..780d65e 100644
--- a/rs_bindings_from_cc/ir_from_cc_test.rs
+++ b/rs_bindings_from_cc/ir_from_cc_test.rs
@@ -565,6 +565,23 @@
 }
 
 #[test]
+fn test_copy_constructor_properties() {
+    let ir = ir_from_cc(
+        "struct SomeStruct {
+            SomeStruct() = delete;
+            SomeStruct(const SomeStruct&);
+            SomeStruct(const SomeStruct&&) = delete;
+            ~SomeStruct() = delete;
+        };",
+    )
+    .unwrap();
+    let func = ir.functions().next().unwrap();
+    let rec = ir.records().next().unwrap();
+    assert_eq!(2, func.params.len());
+    assert!(func.params[1].type_.cc_type.is_const_ref_to(rec));
+}
+
+#[test]
 fn test_unsupported_items_are_emitted() -> Result<()> {
     // We will have to rewrite this test to use something else that is unsupported
     // once we start importing structs from namespaces.
diff --git a/rs_bindings_from_cc/src_code_gen.rs b/rs_bindings_from_cc/src_code_gen.rs
index 91f8dfa..0bb047c 100644
--- a/rs_bindings_from_cc/src_code_gen.rs
+++ b/rs_bindings_from_cc/src_code_gen.rs
@@ -245,30 +245,57 @@
         }
 
         UnqualifiedIdentifier::Constructor => {
+            // TODO(lukasza): Also allow mapping constructors to inherent static methods
+            // (e.g. if named via a bindings-generator-recognized C++
+            // attribute).
             let record = record.ok_or_else(|| anyhow!("Constructors must be member functions."))?;
             if !record.is_trivial_abi {
                 return empty_result;
             }
-            let type_name = make_ident(&record.identifier.identifier);
-            match func.params.len() {
+            let (trait_name, method_name) = match func.params.len() {
                 0 => bail!("Constructor should have at least 1 parameter (__this)"),
-                1 => quote! {
-                    #doc_comment
-                    impl Default for #type_name {
-                        #[inline(always)]
-                        fn default() -> Self {
-                            let mut tmp = std::mem::MaybeUninit::<Self>::uninit();
-                            unsafe {
-                                crate::detail::#thunk_ident(tmp.as_mut_ptr());
-                                tmp.assume_init()
-                            }
+                1 => (quote! { Default }, quote! { default }),
+                2 => {
+                    let param_type = &func.params[1].type_;
+                    if param_type.cc_type.is_const_ref_to(record) {
+                        // TODO(b/200066396): Map copy constructor to `impl Clone`.
+                        // TODO(lukasza): Do something smart with move constructor.
+                        return empty_result;
+                    } else {
+                        let quoted_param_type = &param_types[1];
+                        (quote! { From< #quoted_param_type > }, quote! { from })
+                    }
+                }
+                _ => {
+                    // TODO(b/200066396): Map other constructors to something.
+                    return empty_result;
+                }
+            };
+            // Skip the first parameter in the public function definition. C++ constructors
+            // (and the thunk) take `__this` as the first parameter, but Rust
+            // translation returns a `Self` instead (in Clone, Default, and From
+            // traits, as well as in static methods).
+            let (param_idents, param_types) = (
+                // TODO(lukasza): We should also trim `generic_params` so that
+                // 1) the generated Rust code is easier to read and 2) to avoid
+                // running into unused lifetime parameters warning (see also
+                // https://github.com/rust-lang/rust/issues/41960).
+                param_idents.iter().skip(1).collect_vec(),
+                param_types.iter().skip(1).collect_vec(),
+            );
+
+            let struct_name = make_ident(&record.identifier.identifier);
+            quote! {
+                #doc_comment
+                impl #trait_name for #struct_name {
+                    #[inline(always)]
+                    fn #method_name #generic_params( #( #param_idents: #param_types ),* ) -> Self {
+                        let mut tmp = std::mem::MaybeUninit::<Self>::uninit();
+                        unsafe {
+                            crate::detail::#thunk_ident(tmp.as_mut_ptr() #( , #param_idents )* );
+                            tmp.assume_init()
                         }
                     }
-                },
-                _ => {
-                    // TODO(b/208946210): Map some of these constructors to the From trait.
-                    // TODO(b/200066396): Map other constructors (to the Clone trait?).
-                    return empty_result;
                 }
             }
         }
diff --git a/rs_bindings_from_cc/test/golden/doc_comment.h b/rs_bindings_from_cc/test/golden/doc_comment.h
index 5172931..11b655c 100644
--- a/rs_bindings_from_cc/test/golden/doc_comment.h
+++ b/rs_bindings_from_cc/test/golden/doc_comment.h
@@ -9,13 +9,18 @@
 ///
 ///  * with three slashes
 struct DocCommentSlashes final {
-  /// The default constructor
+  /// The default constructor which will get translated into
+  /// `impl Default for DocCommentSlashes`.
   DocCommentSlashes();
 
-  /// A static method
+  /// A conversion constructor which will get translated into
+  /// `impl From<int> for DocCommentSlashes`.
+  explicit DocCommentSlashes(int);
+
+  /// A static method.
   static int static_method();
 
-  /// A field
+  /// A field.
   int i;
 };
 
diff --git a/rs_bindings_from_cc/test/golden/doc_comment_rs_api.rs b/rs_bindings_from_cc/test/golden/doc_comment_rs_api.rs
index f22ca57..25c7f67 100644
--- a/rs_bindings_from_cc/test/golden/doc_comment_rs_api.rs
+++ b/rs_bindings_from_cc/test/golden/doc_comment_rs_api.rs
@@ -13,7 +13,7 @@
 #[derive(Clone, Copy)]
 #[repr(C)]
 pub struct DocCommentSlashes {
-    /// A field
+    /// A field.
     pub i: i32,
 }
 
@@ -21,7 +21,8 @@
 // Error while generating bindings for item 'DocCommentSlashes::DocCommentSlashes':
 // Nested classes are not supported yet
 
-/// The default constructor
+/// The default constructor which will get translated into
+/// `impl Default for DocCommentSlashes`.
 impl Default for DocCommentSlashes {
     #[inline(always)]
     fn default() -> Self {
@@ -33,8 +34,21 @@
     }
 }
 
+/// A conversion constructor which will get translated into
+/// `impl From<int> for DocCommentSlashes`.
+impl From<i32> for DocCommentSlashes {
+    #[inline(always)]
+    fn from(__param_0: i32) -> Self {
+        let mut tmp = std::mem::MaybeUninit::<Self>::uninit();
+        unsafe {
+            crate::detail::__rust_thunk___ZN17DocCommentSlashesC1Ei(tmp.as_mut_ptr(), __param_0);
+            tmp.assume_init()
+        }
+    }
+}
+
 impl DocCommentSlashes {
-    /// A static method
+    /// A static method.
     #[inline(always)]
     pub fn static_method() -> i32 {
         unsafe { crate::detail::__rust_thunk___ZN17DocCommentSlashes13static_methodEv() }
@@ -59,7 +73,7 @@
     pub i: i32,
 }
 
-// rs_bindings_from_cc/test/golden/doc_comment.h;l=21
+// rs_bindings_from_cc/test/golden/doc_comment.h;l=26
 // Error while generating bindings for item 'DocCommentBang::DocCommentBang':
 // Nested classes are not supported yet
 
@@ -74,11 +88,11 @@
     }
 }
 
-// rs_bindings_from_cc/test/golden/doc_comment.h;l=21
+// rs_bindings_from_cc/test/golden/doc_comment.h;l=26
 // Error while generating bindings for item 'DocCommentBang::DocCommentBang':
 // Parameter type 'struct DocCommentBang &&' is not supported
 
-// rs_bindings_from_cc/test/golden/doc_comment.h;l=21
+// rs_bindings_from_cc/test/golden/doc_comment.h;l=26
 // Error while generating bindings for item 'DocCommentBang::operator=':
 // Parameter type 'struct DocCommentBang &&' is not supported
 
@@ -92,7 +106,7 @@
     pub i: i32,
 }
 
-// rs_bindings_from_cc/test/golden/doc_comment.h;l=29
+// rs_bindings_from_cc/test/golden/doc_comment.h;l=34
 // Error while generating bindings for item 'MultilineCommentTwoStars::MultilineCommentTwoStars':
 // Nested classes are not supported yet
 
@@ -107,11 +121,11 @@
     }
 }
 
-// rs_bindings_from_cc/test/golden/doc_comment.h;l=29
+// rs_bindings_from_cc/test/golden/doc_comment.h;l=34
 // Error while generating bindings for item 'MultilineCommentTwoStars::MultilineCommentTwoStars':
 // Parameter type 'struct MultilineCommentTwoStars &&' is not supported
 
-// rs_bindings_from_cc/test/golden/doc_comment.h;l=29
+// rs_bindings_from_cc/test/golden/doc_comment.h;l=34
 // Error while generating bindings for item 'MultilineCommentTwoStars::operator=':
 // Parameter type 'struct MultilineCommentTwoStars &&' is not supported
 
@@ -125,7 +139,7 @@
     pub i: i32,
 }
 
-// rs_bindings_from_cc/test/golden/doc_comment.h;l=37
+// rs_bindings_from_cc/test/golden/doc_comment.h;l=42
 // Error while generating bindings for item 'LineComment::LineComment':
 // Nested classes are not supported yet
 
@@ -140,11 +154,11 @@
     }
 }
 
-// rs_bindings_from_cc/test/golden/doc_comment.h;l=37
+// rs_bindings_from_cc/test/golden/doc_comment.h;l=42
 // Error while generating bindings for item 'LineComment::LineComment':
 // Parameter type 'struct LineComment &&' is not supported
 
-// rs_bindings_from_cc/test/golden/doc_comment.h;l=37
+// rs_bindings_from_cc/test/golden/doc_comment.h;l=42
 // Error while generating bindings for item 'LineComment::operator=':
 // Parameter type 'struct LineComment &&' is not supported
 
@@ -158,7 +172,7 @@
     pub i: i32,
 }
 
-// rs_bindings_from_cc/test/golden/doc_comment.h;l=45
+// rs_bindings_from_cc/test/golden/doc_comment.h;l=50
 // Error while generating bindings for item 'MultilineOneStar::MultilineOneStar':
 // Nested classes are not supported yet
 
@@ -173,11 +187,11 @@
     }
 }
 
-// rs_bindings_from_cc/test/golden/doc_comment.h;l=45
+// rs_bindings_from_cc/test/golden/doc_comment.h;l=50
 // Error while generating bindings for item 'MultilineOneStar::MultilineOneStar':
 // Parameter type 'struct MultilineOneStar &&' is not supported
 
-// rs_bindings_from_cc/test/golden/doc_comment.h;l=45
+// rs_bindings_from_cc/test/golden/doc_comment.h;l=50
 // Error while generating bindings for item 'MultilineOneStar::operator=':
 // Parameter type 'struct MultilineOneStar &&' is not supported
 
@@ -194,6 +208,11 @@
     extern "C" {
         #[link_name = "_ZN17DocCommentSlashesC1Ev"]
         pub(crate) fn __rust_thunk___ZN17DocCommentSlashesC1Ev(__this: *mut DocCommentSlashes);
+        #[link_name = "_ZN17DocCommentSlashesC1Ei"]
+        pub(crate) fn __rust_thunk___ZN17DocCommentSlashesC1Ei(
+            __this: *mut DocCommentSlashes,
+            __param_0: i32,
+        );
         #[link_name = "_ZN17DocCommentSlashes13static_methodEv"]
         pub(crate) fn __rust_thunk___ZN17DocCommentSlashes13static_methodEv() -> i32;
         pub(crate) fn __rust_thunk___ZN14DocCommentBangC1Ev(__this: *mut DocCommentBang);
diff --git a/rs_bindings_from_cc/test/golden/elided_lifetimes_rs_api.rs b/rs_bindings_from_cc/test/golden/elided_lifetimes_rs_api.rs
index a2446fe..b961447 100644
--- a/rs_bindings_from_cc/test/golden/elided_lifetimes_rs_api.rs
+++ b/rs_bindings_from_cc/test/golden/elided_lifetimes_rs_api.rs
@@ -32,7 +32,7 @@
 
 impl Default for S {
     #[inline(always)]
-    fn default() -> Self {
+    fn default<'a>() -> Self {
         let mut tmp = std::mem::MaybeUninit::<Self>::uninit();
         unsafe {
             crate::detail::__rust_thunk___ZN1SC1Ev(tmp.as_mut_ptr());
diff --git a/rs_bindings_from_cc/test/struct/constructors/constructors.cc b/rs_bindings_from_cc/test/struct/constructors/constructors.cc
index 37f5b09..2f6e730 100644
--- a/rs_bindings_from_cc/test/struct/constructors/constructors.cc
+++ b/rs_bindings_from_cc/test/struct/constructors/constructors.cc
@@ -4,9 +4,12 @@
 
 #include "rs_bindings_from_cc/test/struct/constructors/constructors.h"
 
-StructWithUserProvidedConstructor::StructWithUserProvidedConstructor()
+StructWithUserProvidedConstructors::StructWithUserProvidedConstructors()
     : int_field(42) {}
 
+StructWithUserProvidedConstructors::StructWithUserProvidedConstructors(int i)
+    : int_field(i) {}
+
 StructWithPrivateConstructor::StructWithPrivateConstructor() : int_field(42) {}
 
 NonTrivialStructWithConstructors::NonTrivialStructWithConstructors()
diff --git a/rs_bindings_from_cc/test/struct/constructors/constructors.h b/rs_bindings_from_cc/test/struct/constructors/constructors.h
index ec26972..74ab11d 100644
--- a/rs_bindings_from_cc/test/struct/constructors/constructors.h
+++ b/rs_bindings_from_cc/test/struct/constructors/constructors.h
@@ -5,14 +5,20 @@
 #ifndef CRUBIT_RS_BINDINGS_FROM_CC_TEST_STRUCT_CONSTRUCTORS_CONSTRUCTORS_H_
 #define CRUBIT_RS_BINDINGS_FROM_CC_TEST_STRUCT_CONSTRUCTORS_CONSTRUCTORS_H_
 
-struct StructWithUserProvidedConstructor final {
-  StructWithUserProvidedConstructor();
-  // TODO(lukasza): Add a copy constructor (to be mapped to Clone?).
-  // TODO(b/208946210): Add a "conversion" constructor (to be mapped to From).
+struct StructWithUserProvidedConstructors final {
+  // `impl Default for StructWithUserProvidedConstructors { ... }`.
+  StructWithUserProvidedConstructors();
+
+  // TODO(lukasza): Add a copy constructor (to be mapped to Clone?)
+
+  // `impl From<int> for StructWithUserProvidedConstructors { ... }`.
+  explicit StructWithUserProvidedConstructors(int);
 
   int int_field;
 };
 
+// TODO(lukasza): StructWithInlinedConstructors (to force thunk generation).
+
 struct StructWithDeletedConstructor final {
   StructWithDeletedConstructor() = delete;
 
diff --git a/rs_bindings_from_cc/test/struct/constructors/test.rs b/rs_bindings_from_cc/test/struct/constructors/test.rs
index b4b981c..bcc572a 100644
--- a/rs_bindings_from_cc/test/struct/constructors/test.rs
+++ b/rs_bindings_from_cc/test/struct/constructors/test.rs
@@ -11,10 +11,14 @@
 
     #[test]
     fn test_user_provided_constructors() {
-        assert_impl_all!(StructWithUserProvidedConstructor: Default);
+        assert_impl_all!(StructWithUserProvidedConstructors: From<i32>);
+        assert_impl_all!(StructWithUserProvidedConstructors: Default);
 
-        let s: StructWithUserProvidedConstructor = Default::default();
+        let s: StructWithUserProvidedConstructors = Default::default();
         assert_eq!(42, s.int_field);
+
+        let i: StructWithUserProvidedConstructors = 123.into();
+        assert_eq!(123, i.int_field);
     }
 
     #[test]