feat(macro): export generic structs via instantiate()#2
Open
rossng wants to merge 5 commits into
Open
Conversation
Preparation for exporting monomorphized generic structs: add an optional `rust_generics` slot to `ast::Struct` (and the mirroring `struct_generics` on `ast::StructField`) holding the concrete generic arguments in turbofish form, and emit them in codegen so the generated type is `Foo #generics` rather than the bare ident. No behavior change: every construction site sets these to `None`, so the emitted code for ordinary non-generic structs is byte-for-byte identical. The next commit populates the slot from a new `instantiate(...)` attribute.
Adds `#[wasm_bindgen(instantiate(...))]`, which 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 }
The attribute is parsed into a list of `Instantiation`s; each is validated and
turned into an `InstantiationTarget` carrying the JS name, the concrete
turbofish arguments, and a `T -> f64` (plus `Self`) substitution map. The
struct conversion now returns one `ast::Struct` per target, substituting the
type parameter throughout every field type and populating the `rust_generics`
slot added in the previous commit. Each instantiation becomes an independent
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 limited to the struct surface: instances cross the boundary via free
functions, and public fields are read/written directly from JS. 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.
Adds a trybuild ui-test exercising every rejection path of the struct `instantiate(...)` attribute: a non-generic struct, duplicate JS names, an arity mismatch, a const generic parameter, `js_name` combined with `instantiate`, an instantiation naming a different type, and a JS-keyword class name. All assertions are wasm-bindgen's own diagnostics, so the snapshots are stable across compiler versions.
Adds a `tests/wasm` case that builds a real Wasm module from a single `Pair<T>` monomorphized into `PairF64` and `PairI32`. It round-trips each instantiation through JS, reads and writes the public fields from JS, and uses `instanceof` to confirm the two instantiations are genuinely distinct classes.
Adds a guide reference page under on-rust-exports describing `instantiate(...)` — the syntax, the generated classes, producing/consuming instances via free functions, and the validation rules — and links it from the summary.
5abd092 to
c82467b
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds
#[wasm_bindgen(instantiate(...))]for exporting generic structs to JavaScript. This is the first of three incremental PRs; it covers the struct surface only, with impl methods/constructors and TypeScript-level type parameters to follow.What this does
instantiate(...)monomorphizes a generic struct into one concrete JS class per listed instantiation, written asType<Args> as JsName:Each instantiation is emitted as an independent
ast::Structwith 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
Instances cross the boundary via free functions; public fields are read and written directly from JS. Exporting impl methods/constructors on an instantiation and propagating TypeScript type parameters are deliberately out of scope here and handled in follow-up PRs.
Validation rejects:
js_namecombined withinstantiateTests & docs
crates/macro/ui-tests/instantiate-struct— compile-fail coverage of every validation path. The messages are wasm-bindgen diagnostics, so the snapshots are toolchain-stable.tests/wasm/instantiate— runtime round-trips over two instantiations, field read/write from JS, and class-distinctness (instanceof) checks.invalid-generics/invalid-itemsstderr expectations.cargo fmtapplied.A
crates/cli/tests/reference/case was intentionally not included: the harness compares.watexactly, and that output is toolchain-specific. It should be added by regenerating (BLESS=1) in CI's environment so the committed.watmatches.Verification
macro-support builds with no warnings; 21 macro-support unit tests pass; the full workspace builds; the ui-cases produce clean located errors; the runtime tests pass; changed files are
rustfmt-clean.🤖 Generated with Claude Code