Keyboard shortcuts

Press or to navigate between chapters

Press ? to show this help

Press Esc to hide this help

Build a proc-macro expansion

Build an expansion by parsing each matching attribute, substituting its subject type, preserving every call in order, and appending a completion marker only when the parsed chain contains one.

Expand one attribute

For each attribute:

  1. Parse the arguments with attr.parse_args::<AttributeChain>()?.
  2. Substitute the field type into chain.root_path().
  3. Quote each call’s method, optional turbofish, and arguments.
  4. Quote chain.completion_marker() after the calls when it exists.
  5. Place the result after an application-owned constructor that returns the desired receiver type.
use attribute_dsl::{AttributeChain, substitute_infer_in_path};
use proc_macro2::TokenStream;
use quote::quote;
use syn::{Attribute, Type};

fn expand_attribute(
    attr: &Attribute,
    field_ty: &Type,
    field_name: &str,
) -> syn::Result<TokenStream> {
    let chain = attr.parse_args::<AttributeChain>()?;
    let root = substitute_infer_in_path(chain.root_path(), field_ty);

    let calls = chain.calls().iter().map(|call| {
        let method = call.method();
        let turbofish = call.turbofish();
        let args = call.args();
        quote! { .#method #turbofish (#(#args),*) }
    });
    let completion = chain
        .completion_marker()
        .map(|marker| quote! { .#marker })
        .unwrap_or_default();

    Ok(quote! {
        #root::builder_for(#field_name) #(#calls)* #completion
    })
}

The consumer owns builder_for, its arguments, and the final generated item. Keeping construction outside the parser lets multiple macro crates share the chain grammar while producing different domain-specific code.

Preserve completion typing

A complete chain has no marker, so its normal expansion ends after the final call. For trailing-dot input, the marker must follow the same constructor and calls so rust-analyzer sees the real receiver type at the cursor. Disable probe parsing when the expansion cannot maintain that invariant.

Report and test failures

Return parsing and semantic failures as syn::Error values. Use syn::Error::new_spanned for consumer-owned checks so the diagnostic points to the relevant root, method, or argument.

Test at least:

  • a root-only chain;
  • ordered calls with arguments and turbofish syntax;
  • inferred and explicit subject types;
  • rejected non-path roots; and
  • trailing-dot input when completion probes are enabled.

The repository’s examples/derive_field_attrs.rs shows the same workflow in a complete executable derive-style example.