Skip to content

Commit 5abd092

Browse files
rossngclaude
andcommitted
feat(macro): export generic structs via instantiate()
Adds a `#[wasm_bindgen(instantiate(...))]` attribute that monomorphizes a generic struct into one concrete JS class per listed instantiation, written as `Type<Args> as JsName`: #[wasm_bindgen(instantiate(Pair<f64> as PairF64, Pair<u32> as PairU32))] pub struct Pair<T> { pub first: T, pub second: T } Each instantiation is emitted as an independent `ast::Struct` with the type parameter substituted throughout its fields, producing a distinct exported class with its own ABI conversions and public-field accessors. Because the struct macro is a single expansion that emits both the class and its field accessors, the monomorphized field types cannot diverge from the class they belong to. Scope is deliberately limited to the struct surface: instances cross the boundary via free functions, and public fields are read/written directly from JS. Exporting impl methods/constructors on an instantiation, and propagating TS-level type parameters, are follow-up work. Validation rejects non-type (lifetime/const) parameters, arity mismatches, duplicate or JS-keyword class names, `js_name` combined with `instantiate`, and instantiations that don't name the struct. Tests: - crates/macro/ui-tests/instantiate-struct: compile-fail coverage of every validation path (messages are wasm-bindgen diagnostics, toolchain-stable). - tests/wasm/instantiate: runtime round-trips over two instantiations, field read/write from JS, and class-distinctness (`instanceof`) checks. Also documents the attribute under the guide's on-rust-exports reference and updates the invalid-generics/invalid-items stderr expectations. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 4f17390 commit 5abd092

13 files changed

Lines changed: 564 additions & 44 deletions

File tree

crates/macro-support/src/ast.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -507,6 +507,10 @@ pub struct FunctionArgumentData {
507507
pub struct Struct {
508508
/// The name of the struct in Rust code
509509
pub rust_name: Ident,
510+
/// The concrete generic arguments of a monomorphized instantiation of a
511+
/// generic struct, in turbofish form (e.g. `::<f64>` so the full type is
512+
/// `Tr::<f64>`). `None` for ordinary non-generic structs.
513+
pub rust_generics: Option<syn::AngleBracketedGenericArguments>,
510514
/// The export name of the struct in JS code
511515
pub js_name: String,
512516
/// The namespace-qualified internal name used for wasm symbol generation.
@@ -555,6 +559,9 @@ pub struct StructField {
555559
pub js_name: String,
556560
/// The name of the struct this field is part of
557561
pub struct_name: Ident,
562+
/// The concrete generic arguments of the monomorphized struct this field
563+
/// is part of, in turbofish form. Mirrors [`Struct::rust_generics`].
564+
pub struct_generics: Option<syn::AngleBracketedGenericArguments>,
558565
/// Whether this value is read-only to JS
559566
pub readonly: bool,
560567
/// The type of this field

crates/macro-support/src/codegen.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,11 @@ impl TryToTokens for ast::LinkToModule {
218218

219219
impl ToTokens for ast::Struct {
220220
fn to_tokens(&self, tokens: &mut TokenStream) {
221-
let name = &self.rust_name;
221+
let rust_ident = &self.rust_name;
222+
let rust_generics = &self.rust_generics;
223+
// The full (possibly monomorphized) type, e.g. `Foo` or `Tr::<f64>`.
224+
// Turbofish syntax is valid in both type and expression position.
225+
let name = quote! { #rust_ident #rust_generics };
222226
let name_str = self.qualified_name.to_string();
223227
let name_len = name_str.len() as u32;
224228
let name_chars: Vec<u32> = name_str.chars().map(|c| c as u32).collect();
@@ -548,7 +552,9 @@ impl ToTokens for ast::StructField {
548552
}
549553

550554
let rust_name = &self.rust_name;
551-
let struct_name = &self.struct_name;
555+
let struct_ident = &self.struct_name;
556+
let struct_generics = &self.struct_generics;
557+
let struct_name = quote! { #struct_ident #struct_generics };
552558
let ty = &self.ty;
553559
let getter = &self.getter;
554560
let setter = &self.setter;

crates/macro-support/src/lib.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,8 @@ pub fn expand_struct_marker(item: TokenStream) -> Result<TokenStream, Diagnostic
228228
let mut s: syn::ItemStruct = syn::parse2(item)?;
229229

230230
let mut program = ast::Program::default();
231-
program.structs.push((&mut s).convert(&program)?);
231+
let structs = (&mut s).convert(&program)?;
232+
program.structs.extend(structs);
232233

233234
let mut tokens = proc_macro2::TokenStream::new();
234235
program.try_to_tokens(&mut tokens)?;

0 commit comments

Comments
 (0)