[bindings] Un-special-case returning an associated type
authorMatt Corallo <git@bluematt.me>
Wed, 25 Nov 2020 19:46:21 +0000 (14:46 -0500)
committerMatt Corallo <git@bluematt.me>
Mon, 4 Jan 2021 17:40:40 +0000 (12:40 -0500)
In the case that we return an associated type to C (ie when
implementing a trait which returns an associated type, we had to
convert the Rust-returned concrete Rust type to the C trait struct),
we had code to manually create the neccessary trait struct at the
return site.

This was special-cased in the method-body-writing function instead
of letting the type conversion logic handle it. As a result, we are
unable to do the same conversion when it appears in a different
context, for example inside of a generic like
`Result<Self::AssocType, ErrorType>`.

To solve this, we do the actual work in a
`impl From<nativeType> for CTraitStruct` implementation and then
call `into()` from within the type conversion logic.

c-bindings-gen/src/blocks.rs
c-bindings-gen/src/main.rs
c-bindings-gen/src/types.rs

index 606131a679d011df6a5bc92580802aa53959ce8a..6ba829537ad3bbe2f7d3b6919a18f9a5ed1eabff 100644 (file)
@@ -274,34 +274,12 @@ pub fn write_method_call_params<W: std::io::Write>(w: &mut W, sig: &syn::Signatu
                syn::ReturnType::Type(_, rtype) => {
                        write!(w, ";\n\t{}", extra_indent).unwrap();
 
+                       let self_segs_iter = first_seg_self(&*rtype);
                        if to_c && first_seg_self(&*rtype).is_some() {
                                // Assume rather blindly that we're returning an associated trait from a C fn call to a Rust trait object.
                                write!(w, "ret").unwrap();
-                       } else if !to_c && first_seg_self(&*rtype).is_some() {
-                               if let Some(mut remaining_path) = first_seg_self(&*rtype) {
-                                       if let Some(associated_seg) = get_single_remaining_path_seg(&mut remaining_path) {
-                                               // Build a fake path with only associated_seg and resolve it:
-                                               let mut segments = syn::punctuated::Punctuated::new();
-                                               segments.push(syn::PathSegment {
-                                                       ident: associated_seg.clone(), arguments: syn::PathArguments::None });
-                                               let (_, real_path) = generics.unwrap().maybe_resolve_path(&syn::Path {
-                                                       leading_colon: None, segments }).unwrap();
-
-                                               assert_eq!(real_path.segments.len(), 1);
-                                               let real_ident = &real_path.segments.iter().next().unwrap().ident;
-                                               if let Some(t) = types.crate_types.traits.get(&types.maybe_resolve_ident(&real_ident).unwrap()) {
-                                                       // We're returning an associated trait from a Rust fn call to a C trait
-                                                       // object.
-                                                       writeln!(w, "let mut rust_obj = {} {{ inner: Box::into_raw(Box::new(ret)), is_owned: true }};", this_type).unwrap();
-                                                       writeln!(w, "\t{}let mut ret = {}_as_{}(&rust_obj);", extra_indent, this_type, t.ident).unwrap();
-                                                       writeln!(w, "\t{}// We want to free rust_obj when ret gets drop()'d, not rust_obj, so wipe rust_obj's pointer and set ret's free() fn", extra_indent).unwrap();
-                                                       writeln!(w, "\t{}rust_obj.inner = std::ptr::null_mut();", extra_indent).unwrap();
-                                                       writeln!(w, "\t{}ret.free = Some({}_free_void);", extra_indent, this_type).unwrap();
-                                                       writeln!(w, "\t{}ret", extra_indent).unwrap();
-                                                       return;
-                                               }
-                                       }
-                               }
+                       } else if !to_c && self_segs_iter.is_some() && self_segs_iter.unwrap().next().is_none() {
+                               // If we're returning "Self" (and not "Self::X"), just do it manually
                                write!(w, "{} {{ inner: Box::into_raw(Box::new(ret)), is_owned: true }}", this_type).unwrap();
                        } else if to_c {
                                let new_var = types.write_from_c_conversion_new_var(w, &syn::Ident::new("ret", Span::call_site()), rtype, generics);
index 6c1c8a246b30096df058814bb5965caa38ce0b4c..7917c36d841256cc2c4cec5f5dba74cdc5fa1d4c 100644 (file)
@@ -600,6 +600,21 @@ fn writeln_impl<W: std::io::Write>(w: &mut W, i: &syn::ItemImpl, types: &mut Typ
                                                        ExportStatus::Export => {},
                                                        ExportStatus::NoExport|ExportStatus::TestOnly => return,
                                                }
+
+                                               // For cases where we have a concrete native object which implements a
+                                               // trait and need to return the C-mapped version of the trait, provide a
+                                               // From<> implementation which does all the work to ensure free is handled
+                                               // properly. This way we can call this method from deep in the
+                                               // type-conversion logic without actually knowing the concrete native type.
+                                               writeln!(w, "impl From<native{}> for crate::{} {{", ident, full_trait_path).unwrap();
+                                               writeln!(w, "\tfn from(obj: native{}) -> Self {{", ident).unwrap();
+                                               writeln!(w, "\t\tlet mut rust_obj = {} {{ inner: Box::into_raw(Box::new(obj)), is_owned: true }};", ident).unwrap();
+                                               writeln!(w, "\t\tlet mut ret = {}_as_{}(&rust_obj);", ident, trait_obj.ident).unwrap();
+                                               writeln!(w, "\t\t// We want to free rust_obj when ret gets drop()'d, not rust_obj, so wipe rust_obj's pointer and set ret's free() fn").unwrap();
+                                               writeln!(w, "\t\trust_obj.inner = std::ptr::null_mut();").unwrap();
+                                               writeln!(w, "\t\tret.free = Some({}_free_void);", ident).unwrap();
+                                               writeln!(w, "\t\tret\n\t}}\n}}").unwrap();
+
                                                write!(w, "#[no_mangle]\npub extern \"C\" fn {}_as_{}(this_arg: *const {}) -> crate::{} {{\n", ident, trait_obj.ident, ident, full_trait_path).unwrap();
                                                writeln!(w, "\tcrate::{} {{", full_trait_path).unwrap();
                                                writeln!(w, "\t\tthis_arg: unsafe {{ (*this_arg).inner as *mut c_void }},").unwrap();
index 935e572ca4aaeeead2f87b6c2932b1676891c9a7..ef38b080405080744bcbcd73e11465077e2662ef 100644 (file)
@@ -1242,17 +1242,16 @@ impl<'a, 'c: 'a> TypeResolver<'a, 'c> {
                                        decl_lookup(w, &DeclType::StructImported, &resolved_path, is_ref, is_mut);
                                } else if self.crate_types.mirrored_enums.get(&resolved_path).is_some() {
                                        decl_lookup(w, &DeclType::MirroredEnum, &resolved_path, is_ref, is_mut);
+                               } else if let Some(t) = self.crate_types.traits.get(&resolved_path) {
+                                       decl_lookup(w, &DeclType::Trait(t), &resolved_path, is_ref, is_mut);
                                } else if let Some(ident) = single_ident_generic_path_to_ident(&p.path) {
-                                       if let Some(t) = self.crate_types.traits.get(&resolved_path) {
-                                               decl_lookup(w, &DeclType::Trait(t), &resolved_path, is_ref, is_mut);
-                                               return;
-                                       } else if let Some(_) = self.imports.get(ident) {
+                                       if let Some(_) = self.imports.get(ident) {
                                                // crate_types lookup has to have succeeded:
                                                panic!("Failed to print inline conversion for {}", ident);
                                        } else if let Some(decl_type) = self.declared.get(ident) {
                                                decl_lookup(w, decl_type, &self.maybe_resolve_ident(ident).unwrap(), is_ref, is_mut);
                                        } else { unimplemented!(); }
-                               }
+                               } else { unimplemented!(); }
                        },
                        syn::Type::Array(a) => {
                                // We assume all arrays contain only [int_literal; X]s.
@@ -1335,6 +1334,7 @@ impl<'a, 'c: 'a> TypeResolver<'a, 'c> {
                                                DeclType::EnumIgnored|DeclType::StructImported if !is_ref =>
                                                        write!(w, "crate::{} {{ inner: Box::into_raw(Box::new(", decl_path).unwrap(),
                                                DeclType::Trait(_) if is_ref => write!(w, "&").unwrap(),
+                                               DeclType::Trait(_) if !is_ref => {},
                                                _ => panic!("{:?}", decl_path),
                                        }
                                });
@@ -1357,6 +1357,13 @@ impl<'a, 'c: 'a> TypeResolver<'a, 'c> {
                                                write!(w, ", is_owned: true }}").unwrap(),
                                        DeclType::EnumIgnored|DeclType::StructImported if !is_ref => write!(w, ")), is_owned: true }}").unwrap(),
                                        DeclType::Trait(_) if is_ref => {},
+                                       DeclType::Trait(_) => {
+                                               // This is used when we're converting a concrete Rust type into a C trait
+                                               // for use when a Rust trait method returns an associated type.
+                                               // Because all of our C traits implement From<RustTypesImplementingTraits>
+                                               // we can just call .into() here and be done.
+                                               write!(w, ".into()").unwrap()
+                                       },
                                        _ => unimplemented!(),
                                });
        }