Skip to content

Generate Rust enum shells from spec literalUnions#101

Merged
lorisleiva merged 1 commit into
mainfrom
loris/spec-gen-literal-unions
Jun 10, 2026
Merged

Generate Rust enum shells from spec literalUnions#101
lorisleiva merged 1 commit into
mainfrom
loris/spec-gen-literal-unions

Conversation

@lorisleiva

Copy link
Copy Markdown
Member

This PR teaches the generator to emit Rust enum shells from spec literalUnion TypeExprs, parallel to the enumeration support added in the previous PR. The sole literalUnion in v1 is isSigner: [true, false, "either"], referenced by instructionAccountNode.isSigner and instructionRemainingAccountsNode.isSigner (two value-identical inline copies); both consumers collapse to a single generated type. Because literalUnion is anonymous and inline in the spec — there's no name registry, unlike enumerations — the type name is derived from the referencing attribute (pascalCase('isSigner')IsSigner). Distinct value-sets become distinct types; the same value-set referenced by two differently-named attributes throws as ambiguous.

The renderer uses a deliberately narrower derive set than enumPage does: Debug, PartialEq, Eq, Clone, Copy only — no Serialize/Deserialize, no Default, no rename_all. A literalUnion's wire form is heterogeneous (real JSON booleans for the boolean variants, a string for the "either" case) and can't be expressed via serde's declarative attributes; the bespoke Serialize/Deserialize visitor pair, the From<bool> conversion, and the Default choice all stay hand-written in a companion file. This is the same generated-shell / hand-written-impls split established by Number and re-used for the enumeration Default impls in the previous PR.

Reconciling the Rust crate required one breaking rename: IsAccountSigner → IsSigner, since the generator derives the type name from the attribute name. ~45 occurrences across codama-nodes (2 node files + shared/) and codama-attributes (the FromMeta impl + account_directive tests). The hand-written shared/is_account_signer.rs (104 lines, enum + impls + tests) becomes shared/is_signer.rs (impls + tests only — 122 lines, +2 tests covering From<bool> and default_is_false that weren't previously covered). One small wiring note: attributeBodyLine.ts resolves the literalUnion field type itself (because getTypeExprFragment has no access to the attribute name), while typeExpr.ts's literalUnion case continues to throw — only nested-position literalUnions would reach it, and those don't occur in v1.

Workspace-wide: 1006 → 1008 cargo tests passing (+2 from the new hand-written tests), 119 → 130 JS tests passing (+11 from the new discovery + renderer + integration tests), fmt and clippy stay clean, pnpm generate round-trips deterministically. The override surface (3 maps, FIELD_TYPE_OVERRIDES with 1 entry) is unchanged.

lorisleiva commented Jun 10, 2026

Copy link
Copy Markdown
Member Author

Merge activity

  • Jun 10, 2:13 PM UTC: A user started a stack merge that includes this pull request via Graphite.
  • Jun 10, 2:31 PM UTC: Graphite rebased this pull request as part of a merge.
  • Jun 10, 2:31 PM UTC: @lorisleiva merged this pull request with Graphite.

@lorisleiva lorisleiva changed the base branch from loris/spec-gen-enumerations to graphite-base/101 June 10, 2026 14:28
@lorisleiva lorisleiva changed the base branch from graphite-base/101 to main June 10, 2026 14:29
This PR teaches the generator to emit Rust `enum` shells from spec `literalUnion` TypeExprs, parallel to the enumeration support added in the previous PR. The sole `literalUnion` in v1 is `isSigner: [true, false, "either"]`, referenced by `instructionAccountNode.isSigner` and `instructionRemainingAccountsNode.isSigner` (two value-identical inline copies); both consumers collapse to a single generated type. Because `literalUnion` is anonymous and inline in the spec — there's no name registry, unlike `enumerations` — the type name is derived from the referencing attribute (`pascalCase('isSigner')` → `IsSigner`). Distinct value-sets become distinct types; the same value-set referenced by two differently-named attributes throws as ambiguous.

The renderer uses a deliberately narrower derive set than `enumPage` does: `Debug, PartialEq, Eq, Clone, Copy` only — no `Serialize`/`Deserialize`, no `Default`, no `rename_all`. A `literalUnion`'s wire form is heterogeneous (real JSON booleans for the boolean variants, a string for the `"either"` case) and can't be expressed via serde's declarative attributes; the bespoke `Serialize`/`Deserialize` visitor pair, the `From<bool>` conversion, and the `Default` choice all stay hand-written in a companion file. This is the same generated-shell / hand-written-impls split established by `Number` and re-used for the enumeration `Default` impls in the previous PR.

Reconciling the Rust crate required one breaking rename: `IsAccountSigner → IsSigner`, since the generator derives the type name from the attribute name. ~45 occurrences across `codama-nodes` (2 node files + `shared/`) and `codama-attributes` (the `FromMeta` impl + `account_directive` tests). The hand-written `shared/is_account_signer.rs` (104 lines, enum + impls + tests) becomes `shared/is_signer.rs` (impls + tests only — 122 lines, +2 tests covering `From<bool>` and `default_is_false` that weren't previously covered). One small wiring note: `attributeBodyLine.ts` resolves the literalUnion field type itself (because `getTypeExprFragment` has no access to the attribute name), while `typeExpr.ts`'s `literalUnion` case continues to throw — only nested-position literalUnions would reach it, and those don't occur in v1.

Workspace-wide: 1006 → 1008 cargo tests passing (+2 from the new hand-written tests), 119 → 130 JS tests passing (+11 from the new discovery + renderer + integration tests), fmt and clippy stay clean, `pnpm generate` round-trips deterministically. The override surface (3 maps, `FIELD_TYPE_OVERRIDES` with 1 entry) is unchanged.
@lorisleiva lorisleiva force-pushed the loris/spec-gen-literal-unions branch from 9e97b64 to 67a832b Compare June 10, 2026 14:30
@lorisleiva lorisleiva merged commit e1412be into main Jun 10, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant