Repository: dtolnay/cxx
Pull request #650 (2021-01-03 06:59:24)

Support lifetimes on shared structs
```rust
#[cxx::bridge]
mod ffi {
    pub struct Struct<'a> {
        s: &'a str,
    }
}
```


Patch:
diff --git a/macro/src/derive.rs b/macro/src/derive.rs
index 3e95ea49..2f770951 100644
--- a/macro/src/derive.rs
+++ b/macro/src/derive.rs
@@ -92,14 +92,16 @@ pub fn expand_enum(enm: &Enum, actual_derives: &mut Option<TokenStream>) -> Toke
 
 fn struct_copy(strct: &Struct, span: Span) -> TokenStream {
     let ident = &strct.name.rust;
+    let generics = &strct.generics;
 
     quote_spanned! {span=>
-        impl ::std::marker::Copy for #ident {}
+        impl #generics ::std::marker::Copy for #ident #generics {}
     }
 }
 
 fn struct_clone(strct: &Struct, span: Span) -> TokenStream {
     let ident = &strct.name.rust;
+    let generics = &strct.generics;
 
     let body = if derive::contains(&strct.derives, Trait::Copy) {
         quote!(*self)
@@ -117,7 +119,7 @@ fn struct_clone(strct: &Struct, span: Span) -> TokenStream {
     };
 
     quote_spanned! {span=>
-        impl ::std::clone::Clone for #ident {
+        impl #generics ::std::clone::Clone for #ident #generics {
             fn clone(&self) -> Self {
                 #body
             }
@@ -127,12 +129,13 @@ fn struct_clone(strct: &Struct, span: Span) -> TokenStream {
 
 fn struct_debug(strct: &Struct, span: Span) -> TokenStream {
     let ident = &strct.name.rust;
+    let generics = &strct.generics;
     let struct_name = ident.to_string();
     let fields = strct.fields.iter().map(|field| &field.name.rust);
     let field_names = fields.clone().map(Ident::to_string);
 
     quote_spanned! {span=>
-        impl ::std::fmt::Debug for #ident {
+        impl #generics ::std::fmt::Debug for #ident #generics {
             fn fmt(&self, formatter: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
                 formatter.debug_struct(#struct_name)
                     #(.field(#field_names, &self.#fields))*
@@ -144,10 +147,11 @@ fn struct_debug(strct: &Struct, span: Span) -> TokenStream {
 
 fn struct_default(strct: &Struct, span: Span) -> TokenStream {
     let ident = &strct.name.rust;
+    let generics = &strct.generics;
     let fields = strct.fields.iter().map(|field| &field.name.rust);
 
     quote_spanned! {span=>
-        impl ::std::default::Default for #ident {
+        impl #generics ::std::default::Default for #ident #generics {
             fn default() -> Self {
                 #ident {
                     #(
@@ -161,10 +165,11 @@ fn struct_default(strct: &Struct, span: Span) -> TokenStream {
 
 fn struct_ord(strct: &Struct, span: Span) -> TokenStream {
     let ident = &strct.name.rust;
+    let generics = &strct.generics;
     let fields = strct.fields.iter().map(|field| &field.name.rust);
 
     quote_spanned! {span=>
-        impl ::std::cmp::Ord for #ident {
+        impl #generics ::std::cmp::Ord for #ident #generics {
             fn cmp(&self, other: &Self) -> ::std::cmp::Ordering {
                 #(
                     match ::std::cmp::Ord::cmp(&self.#fields, &other.#fields) {
@@ -180,6 +185,7 @@ fn struct_ord(strct: &Struct, span: Span) -> TokenStream {
 
 fn struct_partial_ord(strct: &Struct, span: Span) -> TokenStream {
     let ident = &strct.name.rust;
+    let generics = &strct.generics;
 
     let body = if derive::contains(&strct.derives, Trait::Ord) {
         quote! {
@@ -199,7 +205,7 @@ fn struct_partial_ord(strct: &Struct, span: Span) -> TokenStream {
     };
 
     quote_spanned! {span=>
-        impl ::std::cmp::PartialOrd for #ident {
+        impl #generics ::std::cmp::PartialOrd for #ident #generics {
             fn partial_cmp(&self, other: &Self) -> ::std::option::Option<::std::cmp::Ordering> {
                 #body
             }
diff --git a/macro/src/expand.rs b/macro/src/expand.rs
index 980ba3f3..11115b81 100644
--- a/macro/src/expand.rs
+++ b/macro/src/expand.rs
@@ -131,6 +131,7 @@ fn expand_struct(strct: &Struct) -> TokenStream {
     let ident = &strct.name.rust;
     let doc = &strct.doc;
     let attrs = &strct.attrs;
+    let generics = &strct.generics;
     let type_id = type_id(&strct.name);
     let fields = strct.fields.iter().map(|field| {
         let doc = &field.doc;
@@ -147,7 +148,7 @@ fn expand_struct(strct: &Struct) -> TokenStream {
     let visibility = strct.visibility;
     let struct_token = strct.struct_token;
     let struct_def = quote_spanned! {span=>
-        #visibility #struct_token #ident {
+        #visibility #struct_token #ident #generics {
             #(#fields,)*
         }
     };
@@ -159,7 +160,7 @@ fn expand_struct(strct: &Struct) -> TokenStream {
         #[repr(C)]
         #struct_def
 
-        unsafe impl ::cxx::ExternType for #ident {
+        unsafe impl #generics ::cxx::ExternType for #ident #generics {
             type Id = #type_id;
             type Kind = ::cxx::kind::Trivial;
         }
diff --git a/syntax/parse.rs b/syntax/parse.rs
index 9d847992..3a7e00f5 100644
--- a/syntax/parse.rs
+++ b/syntax/parse.rs
@@ -73,18 +73,6 @@ fn parse_struct(cx: &mut Errors, mut item: ItemStruct, namespace: &Namespace) ->
         },
     );
 
-    let generics = &item.generics;
-    if !generics.params.is_empty() || generics.where_clause.is_some() {
-        let struct_token = item.struct_token;
-        let ident = &item.ident;
-        let where_clause = &generics.where_clause;
-        let span = quote!(#struct_token #ident #generics #where_clause);
-        return Err(Error::new_spanned(
-            span,
-            "struct with generic parameters is not supported yet",
-        ));
-    }
-
     let named_fields = match item.fields {
         Fields::Named(fields) => fields,
         Fields::Unit => return Err(Error::new_spanned(item, "unit structs are not supported")),
@@ -93,6 +81,46 @@ fn parse_struct(cx: &mut Errors, mut item: ItemStruct, namespace: &Namespace) ->
         }
     };
 
+    let mut lifetimes = Punctuated::new();
+    let mut has_unsupported_generic_param = false;
+    for pair in item.generics.params.into_pairs() {
+        let (param, punct) = pair.into_tuple();
+        match param {
+            GenericParam::Lifetime(param) => {
+                if !param.bounds.is_empty() && !has_unsupported_generic_param {
+                    let msg = "lifetime parameter with bounds is not supported yet";
+                    cx.error(&param, msg);
+                    has_unsupported_generic_param = true;
+                }
+                lifetimes.push_value(param.lifetime);
+                if let Some(punct) = punct {
+                    lifetimes.push_punct(punct);
+                }
+            }
+            GenericParam::Type(param) => {
+                if !has_unsupported_generic_param {
+                    let msg = "struct with generic type parameter is not supported yet";
+                    cx.error(&param, msg);
+                    has_unsupported_generic_param = true;
+                }
+            }
+            GenericParam::Const(param) => {
+                if !has_unsupported_generic_param {
+                    let msg = "struct with const generic parameter is not supported yet";
+                    cx.error(&param, msg);
+                    has_unsupported_generic_param = true;
+                }
+            }
+        }
+    }
+
+    if let Some(where_clause) = &item.generics.where_clause {
+        cx.error(
+            where_clause,
+            "struct with where-clause is not supported yet",
+        );
+    }
+
     let mut fields = Vec::new();
     for field in named_fields.named {
         let ident = field.ident.unwrap();
@@ -131,9 +159,9 @@ fn parse_struct(cx: &mut Errors, mut item: ItemStruct, namespace: &Namespace) ->
     let struct_token = item.struct_token;
     let name = pair(namespace, &item.ident, cxx_name, rust_name);
     let generics = Lifetimes {
-        lt_token: None,
-        lifetimes: Punctuated::new(),
-        gt_token: None,
+        lt_token: item.generics.lt_token,
+        lifetimes,
+        gt_token: item.generics.gt_token,
     };
     let brace_token = named_fields.brace_token;
 


Test patch:
diff --git a/tests/ffi/lib.rs b/tests/ffi/lib.rs
index d07ff45c..be19697e 100644
--- a/tests/ffi/lib.rs
+++ b/tests/ffi/lib.rs
@@ -80,6 +80,11 @@ pub mod ffi {
         a: [i32; 4],
     }
 
+    #[derive(Copy, Clone, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
+    pub struct StructWithLifetime<'a> {
+        s: &'a str,
+    }
+
     unsafe extern "C++" {
         include!("tests/ffi/tests.h");
 
diff --git a/tests/ui/multiple_parse_error.stderr b/tests/ui/multiple_parse_error.stderr
index 7c89129b..f736fd1f 100644
--- a/tests/ui/multiple_parse_error.stderr
+++ b/tests/ui/multiple_parse_error.stderr
@@ -1,8 +1,8 @@
-error: struct with generic parameters is not supported yet
+error: unit structs are not supported
  --> $DIR/multiple_parse_error.rs:3:5
   |
 3 |     struct Monad<T>;
-  |     ^^^^^^^^^^^^^^^
+  |     ^^^^^^^^^^^^^^^^
 
 error: unrecognized ABI, requires either "C++" or "Rust"
  --> $DIR/multiple_parse_error.rs:5:5