feat: variants, options, enum lift, and interface-scoped name collisions#197
Merged
Conversation
… collisions
Extends gravity codegen to cover the canonical-ABI shapes needed for richer
WIT worlds: variants with mixed unit and payload cases, `option<T>` in any
position, enum lifting, and interface-scoped type-name collisions.
**Variant codegen**
* `Variant` resolves to a sealed Go marker interface (e.g.
`type SensitiveInfoEntity interface { isSensitiveInfoEntity() }`) and
one Go type per case implements it.
* WIT shorthand `case-name(case-name)` (where the payload record shares
the case's name) uses *direct-record dispatch*: the existing record
type satisfies the marker interface directly, so callers construct
`MyRecord{...}` rather than `Wrapper{Value: MyRecord{...}}`.
* All other cases (unit cases like `email`, primitive-payload cases like
`custom(string)`, list-payload cases like `allow(list<entity>)`) get a
dedicated `{VariantName}{CaseName}` wrapper struct with an optional
`Value` field.
* When every case in a variant is direct-record, `VariantLower` emits
the compact `switch variantPayload := value.(type)` form; mixed
variants use a temp binder plus per-arm `variantPayload := caseN.Value`
unwrapping.
* Top-level variant parameters in method signatures resolve to
`interface{}` (via the new `resolve_param_type` helper) so existing
callers passing variants through `any`-typed plumbing keep compiling.
Records, list elements, and return positions remain strongly typed.
**Option representation**
* `option<T>` is now `*T` in Go: `nil` for none, `&v` for some. A single
pointer composes uniformly in every position — params, returns, record
fields, list elements — where the prior `(T, bool)` comma-ok shape
couldn't.
* `OptionLift`/`OptionLower` rewritten for pointer semantics; no
`reflect` dependency.
**Enum lifting**
* `EnumLift` mirrors the existing `EnumLower`, reading the discriminant
and producing the named Go constant.
**Interface-scoped type collisions**
* New `qualified_type_name` resolves interface-owned WIT types to
`{interface-name}-{type-name}` only when their bare name would
collide with another concrete type in the world (skipping `Type`
aliases so a world-level `use foo.{x}` doesn't trigger
qualification). Single-instance names stay flat.
* Follows alias chains via `wit_bindgen_core::dealias` so world-level
re-exports resolve to the canonical interface-scoped Go name.
**Direction-aware module accessor**
* Lift/lower instructions now use a per-direction `module_handle`
(`i.module` for exports, `mod` for imports), fixing previously
broken codegen for option/list/string lowering invoked from
import-side host wrappers.
* Several store instructions (`I32Store`, `LengthStore`, `PointerStore`,
`F32Store`, `F64Store`, plus the literal-byte arm of `I32Store8`)
collapse their Direction match into a single body that uses
`$module_handle`.
**Examples and regressions**
* New `examples/variants/` covering mixed unit/payload cases, the
direct-record shorthand, `list<variant>` payloads, variants nested in
records, and `option<u32>` record fields.
* `examples/regressions/` extended with:
* Two interfaces in the same world defining `enum validator-response`
(cross-interface enum collision).
* An import callback returning `option<string>` (exercises
import-side option lowering through `mod.Memory()` /
`mod.ExportedFunction("cabi_realloc")`).
* `examples/basic/basic_test.go` updated for the new `*T` option shape.
**Tests**
* `cargo test` — 34 pass.
* `cargo test --test cli` — 8 snapshots green.
* `cd examples && go test ./...` — basic, iface-method-returns-string,
instructions, records, regressions, variants all pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
davidmytton
approved these changes
May 26, 2026
davidmytton
left a comment
Contributor
There was a problem hiding this comment.
Looks good. There's a bit of followup needed to get this over to generating the final Wasm for the Go SDK, but this is the major Gravity work done.
Contributor
Author
|
This should be all of the changes to Gravity, but I needed this piece first so that I could do clean PRs for the SDK and monorepo work. Thanks for the review! |
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.
Summary
Extends gravity codegen to cover canonical-ABI shapes that previously
weren't supported:
variant entity { email, phone-number, custom(string) })option<T>in every position (params, returns, record fields, list elements)same-named types in the same world)
on the import-side host wrapper path
Variant codegen
Each WIT
variantresolves to a sealed Go marker interface plus one Gotype per case:
WIT shorthand
case-name(case-name)(where the payload record sharesthe case's name) uses direct-record dispatch: the existing record
satisfies the marker interface directly, so callers write
MyRecord{...}rather than
Wrapper{Value: MyRecord{...}}. This preserves the naturalshape for the common pattern:
variant config { allow(allow), deny(deny), }Other shapes (unit cases, primitive payloads, list payloads) use a
dedicated
{VariantName}{CaseName}wrapper struct with an optionalValuefield.VariantLowercollapses the switch binder when every case isdirect-record:
Top-level variant parameters in method signatures resolve to
interface{}(via a newresolve_param_typehelper). The markerinterface is still emitted as an opt-in type for callers who want
compile-time safety, but binding method signatures stay flexible. Records,
list elements, and return positions are still strongly typed.
Option representation
option<T>is now*Tin Go:nilfor none,&vfor some. A singlepointer composes uniformly in every position where the prior
(T, bool)comma-ok shape couldn't (record fields, list elements, callback returns).
OptionLift/OptionLowerrewritten for pointer semantics.Enum lifting
EnumLiftmirrors the existingEnumLower, reading the discriminant andproducing the named Go constant.
Interface-scoped type collisions
WIT permits two interfaces in the same world to declare types of the same
name (e.g. both
email-validator-overridesandverify-botdeclaring anenum validator-response). Without qualification the generated Goredeclared the same type twice.
New
qualified_type_nameprefixes interface-owned types with the owninginterface's name only when the bare name would collide with another
concrete type in the world:
enum algorithm-result(only one)AlgorithmResultenum validator-response(two interfaces)EmailValidatorOverridesValidatorResponse/VerifyBotValidatorResponseFollows alias chains via
wit_bindgen_core::dealiasso world-levelre-exports (
use foo.{x}) resolve to the canonical interface-scoped Goname rather than emitting a duplicate definition.
Direction-aware module accessor
Lift/lower instructions now use a per-direction
module_handle—i.modulefor exports,modfor imports — so option/list/stringlowering invoked from import-side host wrappers no longer references an
undefined
i. Several store instructions (I32Store,LengthStore,PointerStore,F32Store,F64Store, plus the literal-byte arm ofI32Store8) collapse their Direction match into a single body that uses$module_handle.New example coverage
examples/variants/— mixed unit/payload cases, direct-recordshorthand,
list<variant>payloads, variants nested in records, andoption<u32>record fields. Theentityvariant deliberately mirrorsthe shapes that initially exposed each gap.
examples/regressions/extended with:enum validator-response(cross-interface enum collision).option<string>(exercises import-side option lowering through
mod.Memory()/mod.ExportedFunction("cabi_realloc")).examples/basic/basic_test.goupdated for the new*Toption shape.Test plan
cargo test— 34 unit tests passcargo test --test cli— 8 snapshot tests passcd examples && go test ./...— basic, iface-method-returns-string,instructions, records, regressions, variants all pass
🤖 Generated with Claude Code