Skip to main content

Crate zyn

Crate zyn 

Source
Expand description

zyn

A proc macro framework with templates, composable elements, and built-in diagnostics.

cargo add zyn

§Table of Contents


§Templates

The zyn! macro is the core of zyn. Write token output as if it were source code, with {{ }} interpolation and @ control flow directives.

Interpolation — any quote::ToTokens value:

let name = zyn::format_ident!("hello_world");
zyn::zyn!(fn {{ name }}() {})
// → fn hello_world() {}

Pipes — transform values inline:

zyn::zyn!(fn {{ name | pascal }}() {})
// name = "hello_world" → fn HelloWorld() {}

Control flow:

zyn::zyn!(
    @if (is_pub) { pub }
    @for (field in fields.named.iter()) {
        fn {{ field.ident }}(&self) -> &{{ field.ty }} {
            &self.{{ field.ident }}
        }
    }
)

Full template syntax:

SyntaxPurpose
{{ expr }}Interpolate any quote::ToTokens value
{{ expr | pipe }}Transform value through a pipe before inserting
@if (cond) { ... }Conditional token emission
@else { ... }Else branch
@else if (cond) { ... }Else-if branch
@for (x in iter) { ... }Loop over an iterator
@for (N) { ... }Repeat N times
@match (expr) { pat => { ... } }Pattern-based emission
@element_name(prop = val)Invoke a #[element] component

See zyn! for the full syntax reference.

Templates are fully type-checked at compile time — errors appear inline, just like regular Rust code.

Compile-time type safety


§Elements

Elements are reusable template components defined with #[zyn::element]. They encapsulate a fragment of token output and accept typed props.

Define an element:

#[zyn::element]
fn getter(name: zyn::syn::Ident, ty: zyn::syn::Type) -> zyn::TokenStream {
    zyn::zyn! {
        pub fn {{ name | snake | ident:"get_{}" }}(&self) -> &{{ ty }} {
            &self.{{ name }}
        }
    }
}

Invoke it inside any template with @:

zyn::zyn! {
    impl {{ ident }} {
        @for (field in fields.named.iter()) {
            @getter(name = field.ident.clone().unwrap(), ty = field.ty.clone())
        }
    }
}

Elements can also receive extractors — values resolved automatically from proc macro input — by marking a param with #[zyn(input)]:

#[zyn::derive]
fn my_getters(
    #[zyn(input)] ident: zyn::Extract<zyn::syn::Ident>,
    #[zyn(input)] fields: zyn::Fields<zyn::syn::FieldsNamed>,
) -> zyn::TokenStream {
    zyn::zyn! {
        impl {{ ident }} {
            @for (field in fields.named.iter()) {
                pub fn {{ field.ident | snake | ident:"get_{}" }}(&self) -> &{{ field.ty }} {
                    &self.{{ field.ident }}
                }
            }
        }
    }
}
// Applied to: struct User { first_name: String, age: u32 }
// Generates:
// impl User {
//     pub fn get_first_name(&self) -> &String { &self.first_name }
//     pub fn get_age(&self) -> &u32 { &self.age }
// }

See element and derive for the full reference.


§Pipes

Pipes transform interpolated values: {{ expr | pipe }}. They chain left to right:

zyn::zyn!(fn {{ name | snake | ident:"get_{}" }}() {})
// name = "HelloWorld" → fn get_hello_world() {}

Built-in pipes:

PipeInput exampleOutput
snakeHelloWorldhello_world
pascalhello_worldHelloWorld
camelhello_worldhelloWorld
screamingHelloWorldHELLO_WORLD
kebabHelloWorld"hello-world"
upperhelloHELLO
lowerHELLOhello
strhello"hello"
trim__foo__foo
pluraluserusers
singularusersuser
ident:"pattern_{}"hellopattern_hello (ident)
fmt:"pattern_{}"hello"pattern_hello" (string)

All built-in pipes are in the pipes module and re-exported by prelude.

Custom pipes via #[zyn::pipe]:

#[zyn::pipe]
fn shout(input: String) -> zyn::syn::Ident {
    zyn::syn::Ident::new(&format!("{}_BANG", input.to_uppercase()), zyn::Span::call_site())
}

zyn::zyn!(fn {{ name | shout }}() {})
// name = "hello" → fn HELLO_BANG() {}

See pipe and the Pipe trait for the full reference.


§Attributes

zyn provides two tools for attribute handling: a derive macro for typed parsing and a proc macro attribute for writing attribute macros.

Typed attribute structs via #[derive(Attribute)]:

#[derive(zyn::Attribute)]
#[zyn("builder")]
struct BuilderConfig {
    #[zyn(default)]
    skip: bool,
    #[zyn(default = "build".to_string())]
    method: String,
}
// users write: #[builder(skip)] or #[builder(method = "create")]

The derive generates from_args, FromArg, and FromInput implementations, as well as a human-readable about() string for error messages.

Auto-suggest — when a user misspells an argument name, zyn automatically suggests the closest known field. No extra setup required:

error: unknown argument `skiip`
  --> src/main.rs:5:12
   |
5  | #[builder(skiip)]
   |           ^^^^^
   |
   = help: did you mean `skip`?

Suggestions are offered when the edit distance is ≤ 3 characters. Distant or completely unknown keys produce only the “unknown argument” error without a suggestion.

Attribute proc macros via #[zyn::attribute]:

#[zyn::attribute]
fn my_attr(#[zyn(input)] item: zyn::syn::ItemFn, args: zyn::Args) -> zyn::TokenStream {
    // args: parsed key=value arguments from the attribute invocation
    zyn::zyn!({ { item } })
}

See Attribute and attribute for the full reference.


§Testing

§Assertions

zyn! returns Output — test both tokens and diagnostics directly:

#[test]
fn generates_function() {
    let input: zyn::Input = zyn::parse!("struct Foo;" => zyn::syn::DeriveInput).unwrap().into();
    let output = zyn::zyn!(fn hello() {});
    let expected = zyn::quote::quote!(fn hello() {});
    zyn::assert_tokens!(output, expected);
}

Diagnostic assertions check error messages from error!, warn!, bail!:

#[zyn::element]
fn validated(name: zyn::syn::Ident) -> zyn::TokenStream {
    if name == "forbidden" {
        bail!("reserved identifier `{}`", name);
    }
    zyn::zyn!(fn {{ name }}() {})
}

#[test]
fn rejects_forbidden_name() {
    let input: zyn::Input = zyn::parse!("struct Foo;" => zyn::syn::DeriveInput).unwrap().into();
    let output = zyn::zyn!(@validated(name = zyn::format_ident!("forbidden")));
    zyn::assert_diagnostic_error!(output, "reserved identifier");
    zyn::assert_tokens_empty!(output);
}

#[test]
fn accepts_valid_name() {
    let input: zyn::Input = zyn::parse!("struct Foo;" => zyn::syn::DeriveInput).unwrap().into();
    let output = zyn::zyn!(@validated(name = zyn::format_ident!("hello")));
    zyn::assert_tokens_contain!(output, "fn hello");
}
MacroPurpose
assert_tokens!Compare two token streams
assert_tokens_empty!Assert no tokens produced
assert_tokens_contain!Check for substring in output
assert_diagnostic_error!Assert error diagnostic with message
assert_diagnostic_warning!Assert warning diagnostic
assert_diagnostic_note!Assert note diagnostic
assert_diagnostic_help!Assert help diagnostic
assert_compile_error!Alias for assert_diagnostic_error!

With the pretty feature:

MacroPurpose
assert_tokens_pretty!Compare using prettyplease-formatted output
assert_tokens_contain_pretty!Substring check on pretty-printed output

§Debugging

Add debug (or debug = "pretty") to any zyn attribute macro and set ZYN_DEBUG to inspect generated code at compile time:

#[zyn::element(debug)]
fn greeting(name: zyn::syn::Ident) -> zyn::TokenStream {
    zyn::zyn!(fn {{ name }}() {})
}
ZYN_DEBUG="*" cargo build
note: zyn::element ─── Greeting

      struct Greeting { pub name : zyn :: syn :: Ident , } impl :: zyn :: Render
      for Greeting { fn render (& self , input : & :: zyn :: Input) -> :: zyn ::
      proc_macro2 :: TokenStream { ... } }

With the pretty feature, use debug = "pretty" for formatted output:

#[zyn::element(debug = "pretty")]
fn greeting(name: zyn::syn::Ident) -> zyn::TokenStream {
    zyn::zyn!(fn {{ name }}() {})
}
note: zyn::element ─── Greeting

      struct Greeting {
          pub name: zyn::syn::Ident,
      }
      impl ::zyn::Render for Greeting {
          fn render(&self, input: &::zyn::Input) -> ::zyn::Output { ... }
      }

All macros support debug: #[zyn::element], #[zyn::pipe], #[zyn::derive], #[zyn::attribute].

ZYN_DEBUG accepts comma-separated *-wildcard patterns matched against the generated PascalCase type name:

ZYN_DEBUG="Greeting" cargo build     # exact match
ZYN_DEBUG="Greet*" cargo build       # prefix wildcard
ZYN_DEBUG="*Element" cargo build     # suffix wildcard
ZYN_DEBUG="Greeting,Shout" cargo build  # multiple patterns

See the [test] module for the full assertion macro reference and the debug module for programmatic access via DebugExt.


§Features

FeatureDefaultDescription
deriveAll proc macro attributes: element, pipe, derive, attribute, and Attribute
extExtension traits for common syn AST types (AttrExt, FieldExt, TypeExt, etc.) — see ext
prettyPretty-printed token output in debug mode — see debug
diagnosticsError accumulation — collect multiple errors before aborting — see mark

§ext

The ext module adds ergonomic extension traits for navigating syn AST types.

zyn = { features = ["ext"] }
use zyn::ext::{AttrExt, TypeExt};

// check and read attribute arguments
if attr.is("serde") {
    let rename: Option<_> = attr.get("rename"); // → Some(Meta::NameValue)
    let skip: bool = attr.exists("skip");
}

// inspect field types
if field.is_option() {
    let inner = field.inner_type().unwrap();
}

See the ext module for all available traits.

§pretty

The pretty feature enables pretty-printed output in debug mode, formatting generated token streams as readable Rust source code via prettyplease.

zyn = { features = ["pretty"] }

Enable debug output per-element with the debug or debug = "pretty" argument, then set ZYN_DEBUG="*" at build time:

#[zyn::element(debug = "pretty")]
fn my_element(name: zyn::syn::Ident) -> zyn::TokenStream {
    zyn::zyn!(struct {{ name }} {})
}
ZYN_DEBUG="*" cargo build
note: zyn::element ─── my_element

    struct MyElement {
        pub name: zyn::syn::Ident,
    }
    impl ::zyn::Render for MyElement {
        fn render(&self, input: &::zyn::Input) -> ::zyn::Output {
            ...
        }
    }

An element annotated with debug — the argument is inert until ZYN_DEBUG is set.

Element with debug arg

Raw debug output shown as an inline compiler diagnostic in the editor.

Raw debug output — inline diagnostic

The same raw output surfaced in the Problems panel for easy navigation.

Raw debug output — Problems panel

Pretty-printed debug output — formatted with prettyplease for readable, indented Rust code.

Pretty debug output

See the debug module for programmatic access via DebugExt.

§diagnostics

The diagnostics feature enables error accumulation — collecting multiple compiler errors before aborting, so users see all problems at once instead of one at a time.

zyn = { features = ["diagnostics"] }

Inside any #[zyn::element], #[zyn::derive], or #[zyn::attribute] body, use the built-in diagnostic macros directly — no setup required:

#[zyn::element]
fn my_element(name: zyn::syn::Ident) -> zyn::TokenStream {
    if name == "forbidden" {
        bail!("reserved identifier `{}`", name);
    }

    if name.to_string().starts_with('_') {
        warn!("identifiers starting with `_` are conventionally unused");
    }

    zyn::zyn!(fn {{ name }}() {})
}
MacroLevelBehaviour
error!(msg)erroraccumulates, does not stop execution
warn!(msg)warningaccumulates, does not stop execution
note!(msg)noteaccumulates, does not stop execution
help!(msg)helpaccumulates, does not stop execution
bail!(msg)erroraccumulates and immediately returns

All accumulated diagnostics are emitted together at the end of the element or macro body, so users see every error at once instead of fixing them one by one.

error: reserved identifier `forbidden`
 --> src/main.rs:3:1

error: reserved identifier `forbidden`
 --> src/main.rs:7:1

See the mark module for the lower-level diagnostic builder API.

Modules§

ast
Template AST node types. Template AST node types.
case
Case conversion utilities. Case conversion functions and macros.
debug
Debug formatting and printing for template expansions. Debug formatting utilities for macro expansions.
ext
Extension traits for common syn AST types. Extension traits for common syn AST types.
extract
Extractors for resolving values from proc macro input. Extractors for resolving typed values from proc macro input.
ident
Internal identifier generation for template expansion. Internal identifier generation for template expansion.
mark
Types and implementations for marking spans. Diagnostic construction utilities.
meta
Attribute argument parsing types. Attribute argument parsing types.
path
Dot-separated path type for navigating nested syn metadata. Dot-separated path type for navigating nested syn metadata.
pipes
Built-in pipe types for template value transforms. Built-in pipe transforms for template value interpolation.
prelude
The zyn prelude. Re-exports all built-in pipes, core traits, and proc macros.
proc_macro2
githubcrates-iodocs-rs
quote
githubcrates-iodocs-rs
syn
githubcrates-iodocs-rs
test
Test utilities for the zyn proc macro framework.

Macros§

assert_compile_error
Asserts that an Output has an error-level diagnostic containing the given message.
assert_diagnostic
Asserts that an Output contains a diagnostic at the given level whose Display output contains the expected substring.
assert_diagnostic_error
Asserts that an Output contains an error diagnostic with the given message.
assert_diagnostic_help
Asserts that an Output contains a help diagnostic with the given message.
assert_diagnostic_note
Asserts that an Output contains a note diagnostic with the given message.
assert_diagnostic_warning
Asserts that an Output contains a warning diagnostic with the given message.
assert_tokens
Asserts that two token streams produce identical output.
assert_tokens_contain
Asserts that the token output contains the given substring.
assert_tokens_contain_pretty
Asserts that the pretty-printed token output contains the given substring.
assert_tokens_empty
Asserts that a token stream or Output produces no tokens.
assert_tokens_pretty
Asserts that two token streams produce identical output when pretty-printed.
camel
Converts a string or ident to camelCase.
format_ident
Formatting macro for constructing Idents.
kebab
Converts a string or ident to kebab-case.
parse
Parses tokens or string literals into a type. Wraps syn::parse_str and syn::parse2.
parse_input
Parses a proc_macro::TokenStream in a proc macro entry point. Wraps syn::parse_macro_input!.
pascal
Converts a string or ident to PascalCase.
screaming
Converts a string or ident to SCREAMING_SNAKE_CASE.
snake
Converts a string or ident to snake_case.
zyn
Expands a zyn template into a [proc_macro2::TokenStream].

Structs§

Args
A parsed list of attribute arguments.
Attr
Element extractor that resolves a #[derive(Attribute)] struct from the input’s attributes.
Data
Element extractor that pulls the syn::Data from a derive input.
Diagnostic
A compiler diagnostic (error, warning, note, or help message).
Extract
Generic extractor wrapper — delegates to T::from_input.
Fields
Element extractor that pulls struct fields from the input.
Output
The result of rendering an element or expanding a template.
OutputBuilder
Builder for constructing Output instances.
Span
A region of source code, along with macro expansion information.
Template
A parsed template AST. Created by parsing template syntax via syn::parse2::<Template>(tokens).
TokenStream
An abstract stream of tokens, or more concretely a sequence of token trees.
Variants
Element extractor that pulls enum variants from the input.

Enums§

Arg
A single parsed attribute argument.
Input
The proc macro input context. Wraps either a DeriveInput or an Item.

Traits§

Expand
Internal trait for AST node expansion. Not part of the public API.
FromArg
Converts a single Arg into a typed value.
FromData
Converts syn::Data into a specific data representation.
FromFields
Converts syn::Fields into a specific fields representation.
FromInput
Extracts a value from the macro input context.
Pipe
Implemented by #[zyn::pipe] types. Transforms a value in a pipe chain.
Render
Implemented by #[zyn::element] types. Renders the element with the given Input context.
ToTokens
Types that can be interpolated inside a quote! invocation.

Functions§

closest_match
Returns the closest matching string from haystack if the edit distance is at most 3. Used for “did you mean?” suggestions in error messages.
levenshtein
Computes the Levenshtein edit distance between two strings.

Type Aliases§

Result
A specialized Result type for zyn diagnostics.

Attribute Macros§

attribute
Defines an attribute macro entry point that auto-parses the annotated item into typed inputs.
derive
Defines a derive macro entry point that auto-parses DeriveInput into typed inputs.
element
Defines a reusable template component generating a struct that implements Render.
pipe
Defines a custom pipe transform used inside {{ expr | pipe }} interpolations.

Derive Macros§

Attribute
Derives typed attribute parsing from #[attr(...)] key-value syntax.