Loading pageâŚ
Rust walkthroughs
Loading pageâŚ
quote::quote_spanned for preserving span information in procedural macros?quote::quote_spanned generates tokens with specific span information attached, allowing procedural macros to produce code where error messages and warnings point to meaningful locations in the original source rather than the macro invocation site. When quote! generates tokens, it assigns them a default spanâtypically the macro call siteâwhich makes compiler errors point to the macro invocation rather than the specific input causing the problem. quote_spanned! lets macro authors attach spans from parsed input to generated output, ensuring that type errors, borrow checker messages, and other diagnostics reference the correct locations in user code.
use proc_macro2::Span;
fn span_basics() {
// A Span represents a location in source code
// - File name (or synthetic identifier)
// - Line and column for start and end
// Spans determine where error messages point
// They also affect hygiene (identifier resolution)
// Real spans come from parsed source code
// Synthetic spans are created by macros
}Spans track source locations for error messages and identifier resolution.
use quote::quote;
use proc_macro2::TokenStream;
fn basic_quote() {
// quote! generates tokens with default spans
// All generated code points to the macro call site
let generated: TokenStream = quote! {
let x = 42;
let y = x + 1;
};
// Any error in this code would point to the macro invocation
// Not helpful for debugging!
}quote! creates tokens with spans pointing to where the macro was invoked.
// Imagine a derive macro that generates this:
fn generated_code() {
// If this has the default span from quote!
let result = some_function_that_doesnt_exist();
// Error points to the derive macro invocation:
// "cannot find function `some_function_that_doesnt_exist`"
// The error points to #[derive(MyMacro)], not helpful!
}
// What we want:
// If we use the span from the input field name
// Error points to the actual field in user's code
// Much more helpful for debugging!Default spans make errors point to macro invocation, not the actual problem location.
use quote::quote_spanned;
use proc_macro2::Span;
fn basic_quote_spanned() {
let span = Span::call_site();
// quote_spanned! takes a span as the first argument
// All tokens in the block get that span
let generated = quote_spanned! {span=>
let x = 42;
let y = x + 1;
};
// Errors in this code will point to `span`
// Still call_site in this case, but can be any span
}quote_spanned! assigns the specified span to all generated tokens.
use quote::{quote, quote_spanned};
use syn::{parse_quote, Ident};
fn with_input_span() {
// Get an identifier from parsed input
let field_name: Ident = parse_quote!(my_field);
// Use the field's span for generated code
let span = field_name.span();
// Generated code pointing to field_name's location
let generated = quote_spanned! {span=>
#field_name
};
// If there's an error with this identifier,
// it will point to where `my_field` appeared in input
}Extracting spans from input tokens allows errors to point to user code.
use quote::{quote, quote_spanned};
use syn::{Data, DeriveInput, Fields, Ident};
// A simple derive macro that generates a getter method
fn generate_getter(input: &DeriveInput) -> proc_macro2::TokenStream {
let struct_name = &input.ident;
let fields = match &input.data {
Data::Struct(data) => &data.fields,
_ => return quote! {}.into(),
};
let mut getters = Vec::new();
for field in fields.iter() {
let field_name = field.ident.as_ref().unwrap();
let field_ty = &field.ty;
let span = field_name.span();
// Use quote_spanned! with field_name's span
// So errors point to the field definition
let getter = quote_spanned! {span=>
pub fn #field_name(&self) -> &#field_ty {
&self.#field_name
}
};
getters.push(getter);
}
quote! {
impl #struct_name {
#(#getters)*
}
}
}Each field's getter uses that field's span for accurate error location.
use quote::{quote, quote_spanned};
use syn::Ident;
fn error_comparison() {
let field_name: Ident = syn::parse_quote!(invalid_type_field);
let span = field_name.span();
// Without quote_spanned:
let bad = quote! {
let value: NonExistentType = #field_name;
};
// Error: "cannot find type `NonExistentType`"
// Points to: macro invocation site (not helpful)
// With quote_spanned:
let good = quote_spanned! {span=>
let value: NonExistentType = #field_name;
};
// Error: "cannot find type `NonExistentType`"
// Points to: where `invalid_type_field` was defined (helpful!)
}quote_spanned! ensures errors reference meaningful locations in user code.
use quote::{quote, quote_spanned};
use syn::{Ident, Type};
fn generate_method(field_name: &Ident, field_type: &Type) -> proc_macro2::TokenStream {
let span = field_name.span();
// Generate setter with proper span
let setter_name = syn::Ident::new(
&format!("set_{}", field_name),
span // Use field's span for generated identifier too
);
quote_spanned! {span=>
pub fn #setter_name(&mut self, value: #field_type) {
self.#field_name = value;
}
}
}
fn generate_all_methods(fields: &[(Ident, Type)]) -> proc_macro2::TokenStream {
let methods: Vec<_> = fields
.iter()
.map(|(name, ty)| generate_method(name, ty))
.collect();
quote! {
#(#methods)*
}
}Generated identifiers also use input spans for consistent error reporting.
use quote::{quote, quote_spanned};
use syn::{Ident, Type};
fn multi_span_example(field_name: &Ident, field_type: &Type) -> proc_macro2::TokenStream {
// Different parts of generated code may need different spans
let name_span = field_name.span();
let type_span = field_type.span();
// Use the most relevant span for each generated piece
// Field name errors should point to field name
// Type errors should point to type
quote! {
// Use name_span for field-related errors
let #field_name = Default::default();
// Use type_span for type-related errors
let _: #field_type = #field_name;
}
// For combined operations, use the primary span
quote_spanned! {name_span=>
pub fn get(#field_name: &#field_type) -> &#field_type {
#field_name
}
}
}Choose the most relevant span for each generated code segment.
use proc_macro2::Span;
fn span_information() {
let span = Span::call_site();
// Spans carry location information:
// - Start line and column
// - End line and column
// - Source file (if available)
// In procedural macros:
// - call_site(): Where the macro was invoked
// - mixed_site(): Hybrid hygiene location
// - def_site(): Where the macro is defined (proc_macro only)
// From parsed input: actual source location
}
// In a derive macro:
fn derive_example(input: &syn::DeriveInput) {
// input.ident.span() -> where the struct name appears
// input.span() -> the entire struct definition
// Use appropriate span for generated code
}Different span sources provide different location information.
use quote::{quote, quote_spanned};
use proc_macro2::Span;
fn hygiene_example() {
// Spans affect identifier hygiene
// Affects how names resolve
let local_var: syn::Ident = syn::parse_quote!(x);
// Without proper span, generated identifier might not
// resolve correctly in the macro caller's context
// Using the input span helps maintain hygiene
let span = local_var.span();
quote_spanned! {span=>
let #local_var = 42; // Uses the same hygiene as input
};
// This ensures the identifier resolves correctly
// in the caller's scope, not the macro's scope
}Spans affect identifier resolutionâproper spans maintain correct hygiene.
use quote::{quote, quote_spanned};
use syn::{DeriveInput, Data, Fields, Member};
fn field_access_example(input: &DeriveInput) -> proc_macro2::TokenStream {
let name = &input.ident;
let fields = match &input.data {
Data::Struct(data) => &data.fields,
_ => return quote! {},
};
let mut field_inits = Vec::new();
for (idx, field) in fields.iter().enumerate() {
let span = field.span();
// For named fields
if let Some(ident) = &field.ident {
// Use field's span for the entire initialization
field_inits.push(quote_spanned! {span=>
#ident: Default::default()
});
} else {
// For unnamed fields (tuple struct)
let idx = syn::Index::from(idx);
field_inits.push(quote_spanned! {span=>
#idx: Default::default()
});
}
}
quote! {
#name {
#(#field_inits),*
}
}
}Using field spans ensures errors point to field definitions.
use quote::{quote, quote_spanned};
use syn::{Ident, Variant};
fn match_arm_generation(variants: &[Variant]) -> proc_macro2::TokenStream {
let arms: Vec<_> = variants
.iter()
.map(|variant| {
let variant_name = &variant.ident;
let span = variant_name.span();
// Each match arm uses its variant's span
quote_spanned! {span=>
Self::#variant_name => {
println!("Variant: {}", stringify!(#variant_name));
}
}
})
.collect();
quote! {
match self {
#(#arms)*
}
}
}Each match arm inherits the span of its corresponding enum variant.
use quote::{quote, quote_spanned};
use syn::{Expr, Member};
fn method_call_span(receiver: &Expr, method_name: &syn::Ident) -> proc_macro2::TokenStream {
let span = method_name.span();
// Use method name's span for the call expression
quote_spanned! {span=>
#receiver.#method_name()
}
// If method doesn't exist, error points to method name
// Not the receiver expression
}Assigning spans to method calls targets error messages appropriately.
use quote::{quote, quote_spanned};
use syn::Ident;
fn comparison() {
let field: Ident = syn::parse_quote!(my_field);
let span = field.span();
// quote! - all tokens have call_site span
let bad = quote! {
let #field: InvalidType = Default::default();
};
// Error points to macro invocation
// quote_spanned! - tokens have input span
let good = quote_spanned! {span=>
let #field: InvalidType = Default::default();
};
// Error points to field location in user code
// quote_spanned! is more work but produces better errors
// Use it when span quality matters for diagnostics
}quote! is simpler but quote_spanned! produces better error messages.
use quote::{quote, quote_spanned};
use syn::{Ident, Type};
fn nested_example(field_name: &Ident, field_type: &Type) -> proc_macro2::TokenStream {
let span = field_name.span();
// Outer quote_spanned for overall structure
quote_spanned! {span=>
{
// Inner quote for simple code
let value: #field_type = Default::default();
// Inner quote_spanned for specific error locations
#field_name
}
}
}Nested spans compose correctlyâuse the most specific span for each part.
use quote::{quote, quote_spanned};
use syn::{DeriveInput, Data, Fields, Ident, Type};
fn derive_builder(input: &DeriveInput) -> proc_macro2::TokenStream {
let struct_name = &input.ident;
let builder_name = syn::Ident::new(
&format!("{}Builder", struct_name),
input.ident.span()
);
let fields = match &input.data {
Data::Struct(data) => &data.fields,
_ => return quote! { compile_error!("Only structs supported"); },
};
let builder_fields: Vec<_> = fields.iter().map(|field| {
let name = field.ident.as_ref().unwrap();
let ty = &field.ty;
quote! { #name: Option<#ty> }
}).collect();
let setter_methods: Vec<_> = fields.iter().map(|field| {
let name = field.ident.as_ref().unwrap();
let ty = &field.ty;
let span = name.span();
// Each setter method uses field's span
// So errors point to field definition
quote_spanned! {span=>
pub fn #name(mut self, value: #ty) -> Self {
self.#name = Some(value);
self
}
}
}).collect();
let field_names: Vec<_> = fields.iter()
.map(|f| f.ident.as_ref().unwrap())
.collect();
quote! {
pub struct #builder_name {
#(#builder_fields),*
}
impl #builder_name {
pub fn new() -> Self {
Self {
#(#field_names: None),*
}
}
#(#setter_methods)*
pub fn build(self) -> #struct_name {
#struct_name {
#(#field_names: self.#field_names.expect("field not set")),*
}
}
}
}
}Builder derive uses field spans for setter methods to improve error messages.
use quote::{quote, quote_spanned};
use syn::{DeriveInput, Meta, NestedMeta, Lit};
fn derive_with_validation(input: &DeriveInput) -> proc_macro2::TokenStream {
let struct_name = &input.ident;
let span = input.span();
// Parse validation attributes
let mut validations = Vec::new();
for attr in &input.attrs {
if attr.path.is_ident("validate") {
let meta = attr.parse_meta().unwrap();
if let Meta::List(list) = meta {
for nested in list.nested {
if let NestedMeta::Lit(Lit::Str(s)) = nested {
let validation = s.value();
let validation_span = s.span();
// Use attribute's span for validation code
validations.push(quote_spanned! {validation_span=>
if !(#validation) {
return Err("validation failed");
}
});
}
}
}
}
}
quote_spanned! {span=>
impl #struct_name {
pub fn validate(&self) -> Result<(), &'static str> {
#(#validations)*
Ok(())
}
}
}
}Validation derive uses attribute spans for accurate error reporting.
use proc_macro2::Span;
use quote::quote_spanned;
fn span_stability() {
// Spans are stable within a single macro expansion
// The same span produces the same error location
let span = Span::call_site();
// All tokens generated with this span point to the same location
quote_spanned! {span=>
let x = 1;
let y = 2;
let z = x + y; // All errors point to call_site
};
// Different spans point to different locations
let span1 = Span::call_site();
let span2 = Span::call_site();
// These are technically different spans (different calls)
// but point to the same location
}Spans are stable within macro expansion and consistently point to their source.
use syn::spanned::Spanned;
use quote::quote_spanned;
use syn::{DeriveInput, Field};
fn spanned_trait(input: &DeriveInput) {
// Everything parsed by syn implements Spanned
// Provides .span() method for any syntax element
let span = input.span(); // Entire struct definition
// More specific spans:
// input.ident.span() - struct name location
// input.attrs[0].span() - first attribute
// input.data - variant/field spans
// Use the most specific span possible
}
fn field_spans(field: &Field) {
// Field has multiple relevant spans:
let field_span = field.span(); // Entire field
let name_span = field.ident.as_ref().map(|i| i.span()); // Field name
let type_span = field.ty.span(); // Field type
// Use appropriate span for each error type:
// - Name errors -> name_span
// - Type errors -> type_span
// - General errors -> field_span
}The Spanned trait provides span access for all parsed syntax elements.
use quote::{quote, quote_spanned};
fn when_to_use() {
// Use quote! when:
// - Generated code is internal/hidden
// - Error messages don't matter
// - Quick prototyping
// Use quote_spanned! when:
// - Generated code produces user-facing errors
// - Type checking happens in generated code
// - You want errors pointing to specific input
// Examples where quote_spanned! helps:
// - Derive macros (errors should point to struct/field)
// - Attribute macros (errors should point to attribute)
// - Function-like macros (errors should point to input)
// Examples where quote! is fine:
// - Internal helper functions
// - Implementation details
// - Generated trait implementations without user-visible errors
}Use quote_spanned! for user-facing code; quote! is fine for internal helpers.
Span sources:
| Source | Span Type | Points To |
|--------|-----------|-----------|
| Span::call_site() | Call site | Macro invocation |
| Span::mixed_site() | Mixed | Hybrid hygiene |
| Span::def_site() | Definition | Macro definition |
| input.span() | Parsed | Actual source location |
| field.ident.span() | Parsed | Field name location |
Span impact on errors:
| Method | Error Location | Quality |
|--------|----------------|---------|
| quote! | Macro call site | Poor (generic) |
| quote_spanned! | Specified span | Good (specific) |
When to use each:
| Scenario | Method |
|----------|--------|
| Internal helper code | quote! |
| User-facing errors | quote_spanned! |
| Type-checking code | quote_spanned! |
| Generated identifiers | quote_spanned! with input span |
| Debug output | quote! |
Key insight: quote_spanned! bridges the gap between macro-generated code and meaningful error messages by attaching spans from parsed input to generated output. Without it, all errors in generated code point to the macro invocationâfrustrating for users trying to debug. With it, type errors, borrow checker messages, and other diagnostics point to the specific field, type, or expression in the user's source that caused the problem. This is particularly important for derive macros where the generated code contains type annotations and field accesses that the compiler checks. The span from each parsed elementâidentifiers, types, entire fieldsâshould be used for the corresponding generated code. The syn::spanned::Spanned trait makes extracting spans from any parsed element straightforward, enabling fine-grained control over error locations. The extra complexity of quote_spanned! pays off in significantly better developer experience when macros produce compiler errors.