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:
- Parse the arguments with
attr.parse_args::<AttributeChain>()?. - Substitute the field type into
chain.root_path(). - Quote each call’s method, optional turbofish, and arguments.
- Quote
chain.completion_marker()after the calls when it exists. - 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.