Skip to content

Add resolvable directive system for extensible type and value directives#84

Merged
lorisleiva merged 1 commit intomainfrom
loris/plugin-directives
Mar 18, 2026
Merged

Add resolvable directive system for extensible type and value directives#84
lorisleiva merged 1 commit intomainfrom
loris/plugin-directives

Conversation

@lorisleiva
Copy link
Copy Markdown
Member

This PR introduces a system that allows Codama plugins to contribute custom type and value directives inside #[codama(...)] attributes using a namespaced prefix::name(...) syntax.

Context

Today, all type and value directives inside #[codama(...)] are hardcoded. If someone wants a shorthand like ata(...) for Associated Token Account PDAs, it must be added to the core codama-attributes crate. This PR makes these directive slots extensible so that plugins can provide their own.

Syntax

Any directive with exactly two path segments (e.g., token::ata(...)) is treated as a resolvable directive. Single-segment paths (e.g., payer, number(u8)) continue to be parsed as built-in directives with hard errors on unrecognized names.

// Built-in (parsed eagerly as before):
#[codama(default_value = payer)]
#[codama(type = number(u8, le))]

// Resolvable (deferred to a plugin for resolution):
#[codama(default_value = token::ata(account("owner"), account("mint")))]
#[codama(type = foo::custom_type)]

// Composable (multiple plugins in one attribute):
#[codama(default_value = token::ata(account("escrow"), token::address, account("mint")))]

How it works

Directive structs now store Resolvable<T> instead of bare node types in their type/value slots. Resolvable<T> is either Resolved(T) (a concrete node) or Unresolved(ResolvableDirective) (namespace + name + raw meta, waiting for a plugin to resolve it). Before any lifecycle hooks fire, the framework runs a ResolveDirectivesVisitor that walks the korok tree and delegates each Unresolved entry to the DirectiveResolver (built from all installed plugins). Plugins implement resolve_type_directive and/or resolve_value_directive on the KorokPlugin trait to claim directives matching their namespace. Nested resolution across plugins is supported — a plugin can call the resolver for directives belonging to other plugins while parsing its own arguments.

Changes

  • codama-syn-helpers: Meta, PathValue, PathList now implement Clone and PartialEq.
  • codama-errors: New CodamaError::UnresolvedDirective variant.
  • codama-attributes: New Resolvable<T> enum and ResolvableDirective struct. TypeDirective, DefaultValueDirective, AccountDirective, FieldDirective, ArgumentDirective, and SeedDirective now store Resolvable<T> in their type/value slots. SeedDirectiveType::Defined replaced by Variable and Constant variants. Directive structs that previously stored inline nodes (AccountDirective, FieldDirective, ArgumentDirective) now store individual fields with to_*_node() methods for constructing the node after resolution.
  • codama-korok-plugins: New DirectiveResolver trait, CompositeDirectiveResolver implementation, and ResolveDirectivesVisitor. KorokPlugin gains resolve_type_directive and resolve_value_directive methods. resolve_plugins() automatically runs directive resolution before on_initialized.
  • codama-korok-visitors: All visitors that consume directives now propagate CodamaResult errors from try_resolved() instead of silently skipping unresolved entries.

This PR introduces a system that allows Codama plugins to contribute custom type and value directives inside `#[codama(...)]` attributes using a namespaced `prefix::name(...)` syntax.

Today, all type and value directives inside `#[codama(...)]` are hardcoded. If someone wants a shorthand like `ata(...)` for Associated Token Account PDAs, it must be added to the core `codama-attributes` crate. This PR makes these directive slots extensible so that plugins can provide their own.

Any directive with exactly two path segments (e.g., `token::ata(...)`) is treated as a resolvable directive. Single-segment paths (e.g., `payer`, `number(u8)`) continue to be parsed as built-in directives with hard errors on unrecognized names.

```rust
// Built-in (parsed eagerly as before):

// Resolvable (deferred to a plugin for resolution):

// Composable (multiple plugins in one attribute):
```

Directive structs now store `Resolvable<T>` instead of bare node types in their type/value slots. `Resolvable<T>` is either `Resolved(T)` (a concrete node) or `Unresolved(ResolvableDirective)` (namespace + name + raw meta, waiting for a plugin to resolve it). Before any lifecycle hooks fire, the framework runs a `ResolveDirectivesVisitor` that walks the korok tree and delegates each `Unresolved` entry to the `DirectiveResolver` (built from all installed plugins). Plugins implement `resolve_type_directive` and/or `resolve_value_directive` on the `KorokPlugin` trait to claim directives matching their namespace. Nested resolution across plugins is supported — a plugin can call the resolver for directives belonging to other plugins while parsing its own arguments.

- **`codama-syn-helpers`**: `Meta`, `PathValue`, `PathList` now implement `Clone` and `PartialEq`.
- **`codama-errors`**: New `CodamaError::UnresolvedDirective` variant.
- **`codama-attributes`**: New `Resolvable<T>` enum and `ResolvableDirective` struct. `TypeDirective`, `DefaultValueDirective`, `AccountDirective`, `FieldDirective`, `ArgumentDirective`, and `SeedDirective` now store `Resolvable<T>` in their type/value slots. `SeedDirectiveType::Defined` replaced by `Variable` and `Constant` variants. Directive structs that previously stored inline nodes (`AccountDirective`, `FieldDirective`, `ArgumentDirective`) now store individual fields with `to_*_node()` methods for constructing the node after resolution.
- **`codama-korok-plugins`**: New `DirectiveResolver` trait, `CompositeDirectiveResolver` implementation, and `ResolveDirectivesVisitor`. `KorokPlugin` gains `resolve_type_directive` and `resolve_value_directive` methods. `resolve_plugins()` automatically runs directive resolution before `on_initialized`.
- **`codama-korok-visitors`**: All visitors that consume directives now propagate `CodamaResult` errors from `try_resolved()` instead of silently skipping unresolved entries.
@lorisleiva lorisleiva marked this pull request as ready for review March 17, 2026 22:37
Copy link
Copy Markdown
Contributor

@amilz amilz left a comment

Choose a reason for hiding this comment

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

Very cool! Thank you!

@lorisleiva lorisleiva merged commit 6c9327d into main Mar 18, 2026
2 checks passed
@amilz amilz mentioned this pull request Mar 20, 2026
4 tasks
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