Skip to content

[2.x] Adapt structure visitors to Codama v2 - #1175

Merged
lorisleiva merged 1 commit into
mainfrom
09-24-adapt_structure_visitors_to_codama_v2
Sep 30, 2026
Merged

lorisleiva merged 1 commit into
mainfrom
09-24-adapt_structure_visitors_to_codama_v2

Conversation

@lorisleiva

@lorisleiva lorisleiva commented Sep 24, 2026 •

Copy link
Copy Markdown
Member

This PR adapts the structure visitors of @codama/visitors to the Codama v2 node model: the visitors that flatten, unwrap, transform or deduplicate types, plus the account, struct-default and PDA helpers. The update* visitors and the remaining instruction visitors are adapted separately, so the package does not fully type-check yet.

Renamed exports

Before After
flattenInstructionDataArgumentsVisitor flattenInstructionDataVisitor
unwrapInstructionArgsDefinedTypesVisitor unwrapInstructionDataDefinedTypesVisitor
createSubInstructionsFromEnumArgsVisitor createSubInstructionsFromEnumDataVisitor
DefinedTypeHistogram keys directlyAsInstructionArgs / inInstructionArgs directlyAsInstructionData / inInstructionData
flattenInstructionArguments / FlattenInstructionArgumentsConfig removed, use flattenStruct / FlattenStructOptions
CODAMA_ERROR__VISITORS__INSTRUCTION_ENUM_ARGUMENT_NOT_FOUND (context argumentName) CODAMA_ERROR__VISITORS__INSTRUCTION_ENUM_DATA_FIELD_NOT_FOUND (context fieldName), same code 1200009

CODAMA_ERROR__UNEXPECTED_NESTED_NODE_KIND is marked as deprecated since nested type node wrappers no longer exist. A new CODAMA_ERROR__VISITORS__CANNOT_FLATTEN_STRUCT_WITH_PLUGINS error (1200014) is added.

Behaviour

  • Exact identifiers. User-supplied names are matched exactly rather than camelCased, consistent with NodeSelector. Conflict checks (flattenStruct, addPdasVisitor) compare camelCase forms, following the spec's casing-collision rule.
  • Transforms are preserved. Visitors that rebuild type nodes keep their transforms and plugins, and inlining a definedTypeLinkNode layers the link's own transforms on top of the inlined type. flattenStruct no longer inlines structs that carry transforms, since that would change their wire format, and throws the new CODAMA_ERROR__VISITORS__CANNOT_FLATTEN_STRUCT_WITH_PLUGINS error when a struct to inline carries plugins, since they would be lost.
  • Instruction data. Visitors work on instructionNode.data. A "direct" use in the histogram is now an instruction's data itself or the type of one of its top-level data fields, so unwrapInstructionDataDefinedTypesVisitor also inlines data: definedTypeLinkNode(...).
  • Enum variants. unwrapTupleEnumWithSingleStructVisitor turns data: tupleTypeNode([struct]) into data: struct, keeping the tuple's transforms and the variant's discriminator.
  • Sub-instructions. createSubInstructionsFromEnumDataVisitor supports linked data and linked enums, and names sub-instructions ${instruction}_${variant} without changing their casing. The discriminator field (${instruction}_${variant}_discriminator) uses the enum's size and the variant's discriminator rather than a hard-coded u8 index, and only the enum field is flattened.
  • Bytes. transformU8ArraysToBytesVisitor emits bytesTypeNode with a fixedSizeTransformNode, only for plain u8 items, keeping the array's transforms outside the fixed size.
  • Linked data is rejected by setAccountDiscriminatorFromFieldVisitor and skipped by flattenInstructionDataVisitor, since changing it would affect every user of the defined type. transformDefinedTypesIntoAccountsVisitor accepts any type, not just structs.

Bug fixes

  • unwrapDefinedTypesVisitor left links inside PDAs, events and constants dangling.
  • Inlining a type from another program left its inner links pointing at the wrong program; they are now qualified with the original program.
  • unwrapTupleEnumWithSingleStructVisitor looked defined types up by the wrong histogram key, removing types that were still used, and mixed up same-named types across programs.
  • getDefinedTypeHistogramVisitor keyed cross-program links under the enclosing program.
  • deduplicateIdenticalDefinedTypesVisitor now repoints links to removed copies at the kept one, and no longer merges types whose unqualified links resolve to different types.
  • createSubInstructionsFromEnumDataVisitor copied the parent's sub-instructions into every new sub-instruction; it now also reports unresolved links as CODAMA_ERROR__LINKED_NODE_NOT_FOUND.
  • addPdasVisitor never matched programs with non-camelCase identifiers and dropped programId and plugins.
  • Renaming enum variants dropped their discriminator and display.

Tests

Twelve new test files cover the visitors that had none, including regression tests for each fix above, and the existing tests are ported to v2 nodes. The README is updated accordingly.

@changeset-bot

changeset-bot Bot commented Sep 24, 2026 •

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: be4576d

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@lorisleiva

Copy link
Copy Markdown
Member Author

@trevor-cortex

@trevor-cortex trevor-cortex left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary

Ports the structure visitors of @codama/visitors to the v2 node model: instructionNode.data replaces arguments, enumVariantTypeNode.data replaces the three variant kinds, wrapper nodes become transforms, and identifiers are matched exactly. Along the way it extracts inlineDefinedType / qualifyDefinedTypeLinks as a shared helper (used by four visitors), fixes a good batch of latent bugs (dangling links in PDAs/events/constants, cross-program link resolution, wrong histogram keys, dedupe not repointing links, dropped PDA programId/plugins, dropped variant discriminator/display), and adds twelve new test files.

I read every changed file and cross-checked the factory signatures the code relies on (structTypeNode(fields, { ...node }), enumVariantTypeNode(id, { ...variant }), addTypeNodeTransforms, fieldDiscriminatorNode(path, { offset }), bytesTypeNode({ transforms, plugins }), EnumTypeNode.size being required, PdaNodeInput). They all line up. The variant.discriminator ?? variantIndex fallback matches the spec's "index of the variant, starting at 0" rule.

Things worth knowing (not blockers)

  • getDefinedTypeHistogramVisitor now counts every link in total, including those in instruction account default values, PDA seeds and constants. In v1, visitInstruction only walked arguments/extraArguments, so those uses were invisible. Downstream, unwrapInstructionDataDefinedTypesVisitor will now inline fewer types than before in IDLs where a type is used both as instruction data and, say, in an instructionAccountNode.defaultValue. That is the correct outcome (v1 would have inlined and removed the type, leaving the default value's link dangling), but it is a behaviour change anyone comparing v1/v2 output should expect.
  • Sub-instruction naming is now ${instruction}_${variant} verbatim (e.g. transferTokens_V1) instead of camelCased. Valid per the identifier grammar and consistent with the exact-identifier policy, but it produces mixed-style identifiers when the inputs use different conventions; renderers will re-case anyway.
  • flattenStruct conflict detection is now camelCase-based, so max_supply + maxSupply throws where v1 silently accepted. Correct per the spec's collision rule; just noting it since flattenInstructionDataVisitor runs on every instruction in the Anchor default pipeline.
  • deduplicateIdenticalDefinedTypesVisitor: the "unqualified links must be deduplicated across the same programs" fixpoint plus the repointing pass is the right design. I walked the P0/P1/P2 cases (referenced type present in a superset of programs, kept copy in an earlier program than the referrer) and repointing handles them.
  • unwrapDefinedTypesVisitor with '*' on a self-referential defined type would still recurse forever — pre-existing, not introduced here.
  • Changeset: none in this PR. If the 2.x branch already carries the seeded major changeset for all public packages that's fine; otherwise a changeset for the renamed exports / behaviour changes would be expected per CONTRIBUTING.md.

Notes for subsequent reviewers

  • createSubInstructionsFromEnumDataVisitor is the most logic-dense file: the double flattenStruct at the end relies on the first pass inlining the { discriminator, payload } wrapper only when the enum has no transforms, and the second pass inlining the payload struct only when it has none. The test "keeps the discriminator and payload grouped when the enum has transforms" covers the no-op path; worth eyeballing.
  • getLinkUsage (histogram) derives "direct" from path shape: […, instruction, link] or […, instruction, struct, field, link] with field.type === link. That excludes a link used as a top-level field's defaultValue, which is intended.
  • The as StructTypeNode cast in unwrapTupleEnumWithSingleStructVisitor is safe because addTypeNodeTransforms preserves kind.

A few small inline nits below.

Comment thread packages/visitors/src/createSubInstructionsFromEnumDataVisitor.ts Outdated
Comment thread packages/visitors/src/createSubInstructionsFromEnumDataVisitor.ts
Comment thread packages/visitors/src/flattenStructVisitor.ts
Comment thread packages/visitors/src/addPdasVisitor.ts Outdated
Comment thread packages/visitors/test/addPdasVisitor.test.ts Outdated
@lorisleiva
lorisleiva force-pushed the 09-24-adapt_structure_visitors_to_codama_v2 branch from 5febf52 to 68455c3 Compare September 24, 2026 19:21

@trevor-cortex trevor-cortex left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-review

Re-fetched the files touched since my last pass (errors/*, addPdasVisitor, createSubInstructionsFromEnumDataVisitor, flattenStructVisitor, and their tests). Everything I raised has been addressed:

  • resolveType now goes through linkables.getPathOrThrow, so an unresolvable data/field link surfaces as LINKED_NODE_NOT_FOUND instead of the misleading "field not found" — covered by a new test. The docblock now lists UNEXPECTED_NODE_KIND for non-struct data too.
  • Payload field carries plugins: field.plugins again, with a test.
  • flattenStruct throws the new CANNOT_FLATTEN_STRUCT_WITH_PLUGINS (1200014) rather than silently dropping plugins, with context/message/imports wired up in @codama/errors (StructFieldTypeNode was already imported in context.ts), and both the throwing path and the "not selected → no throw" path are tested.
  • addPdasVisitor uses a Set of camelCase keys.
  • Test import order fixed.

Still approved.

One thing to keep in mind (not blocking)

The transforms/plugins asymmetry in flattenStruct — transforms skip the field, plugins throw — is a deliberate choice and defensible (transforms have a clear "leave it alone" fallback; plugins don't). But it does make two '*' callers stricter than before:

  • flattenInstructionDataVisitor runs on every instruction in the Anchor default pipeline. Any nested struct that ends up with plugins (e.g. from a future Anchor visitor or a user pre-pass) will now make the whole pipeline throw with no per-field opt-out.
  • The second flattenStruct in createSubInstructionsFromEnumDataVisitor inlines the variant's struct payload. A variant with data: structTypeNode([...], { plugins }) will now throw there, and the user has no knob to keep it grouped instead.

If that's the intended contract ("plugins on a struct you asked to flatten is a user error, fix your input"), it's fine as-is. If you'd rather these pipeline visitors degrade gracefully, an option like { onPlugins: 'throw' | 'skip' } on FlattenStructOptions (defaulting to 'throw') would give the default pipelines a way to opt into skipping. Can be a follow-up if it ever bites.

@lorisleiva
lorisleiva force-pushed the 09-24-move_casing_helpers_to_codama_fragments branch 2 times, most recently from 4354390 to fc393d4 Compare September 30, 2026 14:07
@lorisleiva
lorisleiva force-pushed the 09-24-adapt_structure_visitors_to_codama_v2 branch 2 times, most recently from 82d679f to e48e185 Compare September 30, 2026 14:08
@lorisleiva
lorisleiva force-pushed the 09-24-move_casing_helpers_to_codama_fragments branch 2 times, most recently from 42578fd to 7d45051 Compare September 30, 2026 14:09
@lorisleiva
lorisleiva force-pushed the 09-24-adapt_structure_visitors_to_codama_v2 branch 2 times, most recently from 66f05b8 to c4f8eac Compare September 30, 2026 14:11
@lorisleiva
lorisleiva force-pushed the 09-24-move_casing_helpers_to_codama_fragments branch from 7d45051 to be3fca0 Compare September 30, 2026 14:11
@lorisleiva
lorisleiva force-pushed the 09-24-adapt_structure_visitors_to_codama_v2 branch from c4f8eac to ae160c2 Compare September 30, 2026 14:12
@lorisleiva
lorisleiva force-pushed the 09-24-move_casing_helpers_to_codama_fragments branch from be3fca0 to 26fcebe Compare September 30, 2026 14:12
Base automatically changed from 09-24-move_casing_helpers_to_codama_fragments to main September 30, 2026 14:13
@lorisleiva
lorisleiva force-pushed the 09-24-adapt_structure_visitors_to_codama_v2 branch 2 times, most recently from ae160c2 to be4576d Compare September 30, 2026 14:13
@lorisleiva
lorisleiva marked this pull request as ready for review September 30, 2026 14:14
@lorisleiva
lorisleiva merged commit 4ce5c5a into main Sep 30, 2026
4 of 9 checks passed
@lorisleiva
lorisleiva deleted the 09-24-adapt_structure_visitors_to_codama_v2 branch September 30, 2026 14:14
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.

2 participants