Repository: wasm-bindgen/wasm-bindgen (Rust)

Issue:
Extern type in struct not possible
### Describe the Bug

Currently, it's not possible that the type of a struct field is an external JS type.
As far as I can tell, it's not a missing feature but a bug. See Additional Context for the reasons that make me believe that.

### Steps to Reproduce

Example code:
```rust
#[wasm_bindgen]
extern "C" {
    type Foo;
}

#[wasm_bindgen]
struct Bar {
    #[wasm_bindgen(getter_with_clone)]
    pub foo: Foo,
}
```

### Expected Behavior

It should work without a compiler error.

### Actual Behavior

```
error[E0308]: mismatched types
  --> src/lib.rs:18:1
   |
18 | #[wasm_bindgen]
   | ^^^^^^^^^^^^^^^
   | |
   | expected struct `Foo`, found struct `JsValue`
   | arguments to this function are incorrect
   |
note: associated function defined here
  --> /Users/abdul/.cargo/registry/src/github.com-1ecc6299db9ec823/wasm-bindgen-0.2.84/src/convert/traits.rs:17:8
   |
17 |     fn into_abi(self) -> Self::Abi;
   |        ^^^^^^^^
   = note: this error originates in the attribute macro `wasm_bindgen` (in Nightly builds, run with -Z macro-backtrace for more info)
help: try wrapping the expression in `Foo`
   |
18 | Foo { obj: #[wasm_bindgen] }
   | ++++++++++                 +
```

### Additional Context

[These lines in codegen.rs](https://github.com/rustwasm/wasm-bindgen/blob/0.2.84/crates/backend/src/codegen.rs#L334-L335) generate the following code in this case:
```rust
let val = (*js).borrow().foo.clone();
<Foo as IntoWasmAbi>::into_abi(val)
```
The issue is that the code assumes that the type of the cloned value is `Foo`. But actually, it's `JsValue`. I don't know why, though.
So one way to fix this issue is to modify the generator into generating `<JsValue as IntoWasmAbi>` instead of `<Foo as IntoWasmAbi>`.


Discussion: The problem is that `Foo` needs to implement `Clone` to be used with `#[wasm_bindgen(getter_with_clone)]`. You can fix this by adding `#[derive(Clone)]` to the declaration of `Foo`:

```rust
#[wasm_bindgen]
extern "C" {
    #[derive(Clone)]
    type Foo;
}
```

The error message you got was incredibly unhelpful, though, so I've opened #3295 to improve it.



Fix (patch):
diff --git a/crates/backend/src/ast.rs b/crates/backend/src/ast.rs
index 064e0137713..7dc728fd356 100644
--- a/crates/backend/src/ast.rs
+++ b/crates/backend/src/ast.rs
@@ -349,8 +349,12 @@ pub struct StructField {
     pub comments: Vec<String>,
     /// Whether to generate a typescript definition for this field
     pub generate_typescript: bool,
-    /// Whether to use .clone() in the auto-generated getter for this field
-    pub getter_with_clone: bool,
+    /// The span of the `#[wasm_bindgen(getter_with_clone)]` attribute applied
+    /// to this field, if any.
+    ///
+    /// If this is `Some`, the auto-generated getter for this field must clone
+    /// the field instead of copying it.
+    pub getter_with_clone: Option<Span>,
 }
 
 /// Information about an Enum being exported
diff --git a/crates/backend/src/codegen.rs b/crates/backend/src/codegen.rs
index e2d57f43373..34cf3a32472 100644
--- a/crates/backend/src/codegen.rs
+++ b/crates/backend/src/codegen.rs
@@ -3,9 +3,11 @@ use crate::encode;
 use crate::Diagnostic;
 use once_cell::sync::Lazy;
 use proc_macro2::{Ident, Literal, Span, TokenStream};
+use quote::quote_spanned;
 use quote::{quote, ToTokens};
 use std::collections::{HashMap, HashSet};
 use std::sync::Mutex;
+use syn::spanned::Spanned;
 use wasm_bindgen_shared as shared;
 
 /// A trait for converting AST structs into Tokens and adding them to a TokenStream,
@@ -302,18 +304,17 @@ impl ToTokens for ast::StructField {
         let getter = &self.getter;
         let setter = &self.setter;
 
-        let maybe_assert_copy = if self.getter_with_clone {
+        let maybe_assert_copy = if self.getter_with_clone.is_some() {
             quote! {}
         } else {
             quote! { assert_copy::<#ty>() }
         };
         let maybe_assert_copy = respan(maybe_assert_copy, ty);
 
-        let maybe_clone = if self.getter_with_clone {
-            quote! { .clone() }
-        } else {
-            quote! {}
-        };
+        let mut val = quote_spanned!(self.rust_name.span()=> (*js).borrow().#rust_name);
+        if let Some(span) = self.getter_with_clone {
+            val = quote_spanned!(span=> <#ty as Clone>::clone(&#val) );
+        }
 
         (quote! {
             #[automatically_derived]
@@ -331,7 +332,7 @@ impl ToTokens for ast::StructField {
 
                     let js = js as *mut WasmRefCell<#struct_name>;
                     assert_not_null(js);
-                    let val = (*js).borrow().#rust_name#maybe_clone;
+                    let val = #val;
                     <#ty as IntoWasmAbi>::into_abi(val)
                 }
             };
diff --git a/crates/macro-support/src/parser.rs b/crates/macro-support/src/parser.rs
index 94786143d44..148cc55f9cb 100644
--- a/crates/macro-support/src/parser.rs
+++ b/crates/macro-support/src/parser.rs
@@ -409,7 +409,7 @@ impl<'a> ConvertToAst<BindgenAttrs> for &'a mut syn::ItemStruct {
             .map(|s| s.0.to_string())
             .unwrap_or(self.ident.to_string());
         let is_inspectable = attrs.inspectable().is_some();
-        let getter_with_clone = attrs.getter_with_clone().is_some();
+        let getter_with_clone = attrs.getter_with_clone();
         for (i, field) in self.fields.iter_mut().enumerate() {
             match field.vis {
                 syn::Visibility::Public(..) => {}
@@ -445,7 +445,7 @@ impl<'a> ConvertToAst<BindgenAttrs> for &'a mut syn::ItemStruct {
                 setter: Ident::new(&setter, Span::call_site()),
                 comments,
                 generate_typescript: attrs.skip_typescript().is_none(),
-                getter_with_clone: getter_with_clone || attrs.getter_with_clone().is_some(),
+                getter_with_clone: attrs.getter_with_clone().or(getter_with_clone).copied(),
             });
             attrs.check_used();
         }