How does quote::quote_spanned! differ from quote! for preserving span information in procedural macros?

quote! generates token streams with synthetic spans that point to the macro invocation site, while quote_spanned! lets you explicitly control span information so error messages, warnings, and IDE features point to specific locations in the user's source code. In procedural macros, spans determine where compiler diagnostics appear and how identifiers resolve across hygiene boundaries. quote_spanned! is essential when you need errors to point to specific tokens from the input rather than the macro definition, or when generating code where specific parts should be attributed to specific source locations for proper error reporting.

Basic quote! Usage

use proc_macro2::TokenStream;
use quote::quote;
 
fn generate_getter(field_name: &syn::Ident) -> TokenStream {
    // quote! generates tokens with call-site spans
    // Error messages will point to where this macro is invoked
    quote! {
        pub fn #field_name(&self) -> &str {
            &self.#field_name
        }
    }
}
 
// If the generated code has an error, it points to:
// - The macro invocation site (not helpful for user)
// - Not the field definition in user's code

quote! uses synthetic spans based on where the macro is invoked.

The Span Problem in Procedural Macros

use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, DeriveInput, Data, Fields};
 
#[proc_macro_derive(Builder)]
pub fn derive_builder(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);
    
    // With plain quote!, errors point here, not at user's code
    let name = &input.ident;
    let expanded = quote! {
        impl #name {
            fn validate(&self) -> Result<(), &'static str> {
                // If this code has an error, user sees error at macro site
                // Not at their struct definition
                Err("not implemented")
            }
        }
    };
    
    expanded.into()
}

Errors from generated code point to macro invocation, not relevant user code.

quote_spanned! for Precise Error Locations

use proc_macro2::Span;
use proc_macro::TokenStream;
use quote::quote_spanned;
use syn::{parse_macro_input, DeriveInput, Data, Fields, Field};
 
#[proc_macro_derive(Builder)]
pub fn derive_builder(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);
    
    let name = &input.ident;
    
    // Get span from the struct definition
    let struct_span = name.span();
    
    // Use quote_spanned! to set error location
    let check_code = if let Data::Struct(data) = &input.data {
        match &data.fields {
            Fields::Named(fields) => {
                fields.named.iter().map(|field| {
                    let field_name = field.ident.as_ref().unwrap();
                    let field_span = field_name.span();
                    
                    // Error for this field points to the field definition
                    quote_spanned! {field_span=>
                        if self.#field_name.is_none() {
                            return Err(concat!("Missing field: ", stringify!(#field_name)));
                        }
                    }
                }).collect::<proc_macro2::TokenStream>()
            }
            _ => quote! {}
        }
    } else {
        quote! {}
    };
    
    let expanded = quote! {
        impl #name {
            fn validate(&self) -> Result<(), &'static str> {
                #check_code
                Ok(())
            }
        }
    };
    
    expanded.into()
}

quote_spanned! makes errors point to specific fields in user's code.

How Spans Affect Error Messages

use proc_macro2::TokenStream;
use quote::{quote, quote_spanned};
 
fn demonstrate_span_difference() -> TokenStream {
    // Imagine this is in a derive macro
    
    // Using quote!: error points to macro invocation
    let code1 = quote! {
        nonexistent_function();  // Error points to macro call site
    };
    
    // Using quote_spanned!: error points to specific span
    // In real code, this span comes from input tokens
    use proc_macro2::Span;
    let specific_span = Span::call_site();  // In practice, use input.span()
    
    let code2 = quote_spanned! {specific_span=>
        nonexistent_function();  // Error points to specific_span location
    };
    
    code1
}

Spans determine where error messages appear in the user's source.

Span Propagation from Input Tokens

use proc_macro::TokenStream;
use quote::{quote, quote_spanned};
use syn::{parse_macro_input, DeriveInput, Data, Fields};
 
#[proc_macro_derive(Validate)]
pub fn derive_validate(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);
    let name = &input.ident;
    
    let validations = if let Data::Struct(data) = &input.data {
        if let Fields::Named(fields) = &data.fields {
            fields.named.iter().map(|field| {
                let field_name = field.ident.as_ref().unwrap();
                let field_ty = &field.ty;
                
                // Use the field's span for error messages
                // This makes errors point to the field in user's code
                let field_span = field_name.span();
                
                quote_spanned! {field_span=>
                    // Errors in this block point to field_name's location
                    #field_name: <#field_ty>::validate(&self.#field_name)?;
                }
            }).collect::<proc_macro2::TokenStream>()
        } else {
            quote! {}
        }
    } else {
        quote! {}
    };
    
    let expanded = quote! {
        impl Validate for #name {
            fn validate(&self) -> Result<(), Box<dyn std::error::Error>> {
                #validations
                Ok(())
            }
        }
    };
    
    expanded.into()
}
 
trait Validate {
    fn validate(&self) -> Result<(), Box<dyn std::error::Error>>;
}

Each field's validation error points to that field's definition.

Error Pointing to Struct Definition

use proc_macro::TokenStream;
use quote::quote_spanned;
use syn::{parse_macro_input, DeriveInput, Ident};
 
#[proc_macro_derive(Constructor)]
pub fn derive_constructor(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);
    
    // Span from the struct's name
    let struct_span = input.ident.span();
    
    // Generate compile_error! pointing to the struct
    let error_code = quote_spanned! {struct_span=>
        compile_error!("This struct cannot derive Constructor");
    };
    
    // When this error fires, it points to the struct name
    error_code.into()
}
 
// User's code:
// #[derive(Constructor)]
// struct MyStruct { ... }
// Error: "This struct cannot derive Constructor" points to "MyStruct"

compile_error! with proper span shows errors at meaningful locations.

Spanned Method Generation

use proc_macro::TokenStream;
use quote::quote_spanned;
use syn::{parse_macro_input, DeriveInput, Data, Fields, Field, Type, Ident};
 
#[proc_macro_derive(Getters)]
pub fn derive_getters(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);
    let struct_name = &input.ident;
    
    let getters = if let Data::Struct(data) = &input.data {
        if let Fields::Named(fields) = &data.fields {
            fields.named.iter().map(|field| {
                let field_name = field.ident.as_ref().unwrap();
                let field_ty = &field.ty;
                
                // Method name uses field's span
                // This affects IDE support and error messages
                let method_span = field_name.span();
                
                quote_spanned! {method_span=>
                    pub fn #field_name(&self) -> &#field_ty {
                        &self.#field_name
                    }
                }
            }).collect::<proc_macro2::TokenStream>()
        } else {
            quote! {}
        }
    } else {
        quote! {}
    };
    
    let expanded = quote! {
        impl #struct_name {
            #getters
        }
    };
    
    expanded.into()
}

Getter methods inherit spans from their corresponding fields.

Comparison: quote! vs quote_spanned!

use proc_macro2::Span;
use quote::{quote, quote_spanned};
 
fn compare_approaches(input_ident: syn::Ident) -> proc_macro2::TokenStream {
    // quote! - all spans point to call site
    let with_quote = quote! {
        let val = #input_ident;  // Error here points to macro call site
    };
    
    // quote_spanned! - spans controlled explicitly
    let input_span = input_ident.span();
    let with_spanned = quote_spanned! {input_span=>
        let val = #input_ident;  // Error here points to input_ident's definition
    };
    
    // Practical difference:
    // - quote!: "error at line 5 (where macro was invoked)"
    // - quote_spanned!: "error at line 12 (where the identifier was defined)"
    
    with_quote
}

The difference is where error messages point in the source.

Multi-Token Spanning

use proc_macro::TokenStream;
use quote::quote_spanned;
use syn::{parse_macro_input, DeriveInput, Data, Fields};
 
#[proc_macro_derive(Builder)]
pub fn derive_builder(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);
    let name = &input.ident;
    
    // Span the entire generated method with the struct's span
    let struct_span = name.span();
    
    let builder_methods = if let Data::Struct(data) = &input.data {
        if let Fields::Named(fields) = &data.fields {
            fields.named.iter().map(|field| {
                let field_name = field.ident.as_ref().unwrap();
                let field_ty = &field.ty;
                
                // Each field gets its own span
                let field_span = field_name.span();
                
                quote_spanned! {field_span=>
                    pub fn #field_name(mut self, value: #field_ty) -> Self {
                        self.#field_name = Some(value);
                        self
                    }
                }
            }).collect::<proc_macro2::TokenStream>()
        } else {
            quote! {}
        }
    } else {
        quote! {}
    };
    
    // The impl block itself can have a different span
    let expanded = quote_spanned! {struct_span=>
        impl Builder for #name {
            type Builder = #name Builder;
            
            fn builder() -> Self::Builder {
                #name Builder::default()
            }
        }
    };
    
    expanded.into()
}
 
trait Builder {
    type Builder;
    fn builder() -> Self::Builder;
}

Different parts of generated code can have different spans.

Hygiene and Spans

use proc_macro2::Span;
use quote::{quote, quote_spanned};
 
// Spans affect identifier hygiene
fn hygiene_example() {
    // quote! creates identifiers at call_site
    // These are "unhygienic" - they can shadow user variables
    let code1 = quote! {
        let internal_var = 42;  // May shadow user's internal_var
    };
    
    // quote_spanned! with mixed_site creates hygienic identifiers
    // These won't interfere with user variables
    let span = Span::mixed_site();
    let code2 = quote_spanned! {span=>
        let internal_var = 42;  // Won't shadow user variables
    };
    
    // Span::call_site() - visible to user, can shadow
    // Span::mixed_site() - hygienic, won't shadow user vars
    // Span::def_site() - defined at macro definition site
}

Spans control hygiene—whether generated identifiers can shadow user identifiers.

Practical Pattern: Proper Error Attribution

use proc_macro::TokenStream;
use quote::quote_spanned;
use syn::{parse_macro_input, DeriveInput, Data, Fields, Error};
 
#[proc_macro_derive(ValidateFields)]
pub fn derive_validate_fields(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);
    let name = &input.ident;
    
    // Generate compile-time validation
    let validations = if let Data::Struct(data) = &input.data {
        if let Fields::Named(fields) = &data.fields {
            fields.named.iter().map(|field| {
                let field_name = field.ident.as_ref().unwrap();
                let field_ty = &field.ty;
                let field_span = field_name.span();
                
                // Check if field type implements Validate
                // Error points to field if validation fails
                quote_spanned! {field_span=>
                    <#field_ty as Validate>::validate(&self.#field_name)
                        .map_err(|e| {
                            // Error context includes field name
                            e.context(stringify!(#field_name))
                        })?;
                }
            }).collect::<proc_macro2::TokenStream>()
        } else {
            quote! {}
        }
    } else {
        // Emit compile error pointing to struct
        let err = Error::new_spanned(&input, "Only structs with named fields supported");
        return err.to_compile_error().into();
    };
    
    let expanded = quote! {
        impl ValidateFields for #name {
            fn validate_fields(&self) -> Result<(), ValidationError> {
                #validations
                Ok(())
            }
        }
    };
    
    expanded.into()
}
 
trait Validate {
    fn validate(&self) -> Result<(), ValidationError>;
    fn context(self, ctx: &'static str) -> ValidationError;
}
 
struct ValidationError;
impl ValidationError {
    fn context(self, _ctx: &'static str) -> Self { self }
}

Errors from generated code point to the fields they relate to.

Debugging with Spans

use proc_macro2::Span;
use quote::quote_spanned;
use syn::Ident;
 
fn debug_spans() {
    // When developing procedural macros, understanding spans helps debugging
    
    // call_site() - Where the macro is invoked
    let call_span = Span::call_site();
    let code1 = quote_spanned! {call_span=>
        println!("This error points to macro invocation");
    };
    
    // def_site() - Where the macro is defined (less useful)
    let def_span = Span::def_site();
    let code2 = quote_spanned! {def_span=>
        println!("This error points to macro definition");
    };
    
    // mixed_site() - Hybrid hygiene
    let mixed_span = Span::mixed_site();
    let code3 = quote_spanned! {mixed_span=>
        println!("Hygienic identifiers");
    };
    
    // In practice: use input spans for user-attributed errors
    // input_ident.span() - Points to where identifier appears in user code
}

Different span types serve different purposes.

Real-World Example: Error Attribution in derive Macros

use proc_macro::TokenStream;
use quote::quote_spanned;
use syn::{parse_macro_input, DeriveInput, Data, Fields, Type, Error};
 
#[proc_macro_derive(Serialize)]
pub fn derive_serialize(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);
    let name = &input.ident;
    
    // Validate all fields implement Serialize
    let fields = match &input.data {
        Data::Struct(data) => &data.fields,
        _ => {
            let err = Error::new_spanned(&input, "Only structs supported");
            return err.to_compile_error().into();
        }
    };
    
    let field_serializers = fields.iter().map(|field| {
        let field_name = field.ident.as_ref();
        let field_ty = &field.ty;
        
        // Get the span of the field type for error attribution
        let ty_span = field_ty.span();
        
        // If field type doesn't implement Serialize,
        // error points to the type, not the macro
        match field_name {
            Some(name) => {
                let field_span = name.span();
                quote_spanned! {field_span=>
                    #name: <#field_ty as Serialize>::serialize(&self.#name)?,
                }
            }
            None => {
                // Tuple struct field - use type span
                quote_spanned! {ty_span=>
                    <#field_ty as Serialize>::serialize(&self.0)?,
                }
            }
        }
    });
    
    let expanded = quote! {
        impl Serialize for #name {
            fn serialize(&self) -> Result<String, SerializeError> {
                // ...
                Ok(String::new())
            }
        }
    };
    
    expanded.into()
}
 
trait Serialize {
    fn serialize(&self) -> Result<String, SerializeError>;
}
struct SerializeError;

Type errors in generated code point to the field types in user's struct.

When to Use Each

use proc_macro2::TokenStream;
use quote::{quote, quote_spanned};
 
fn when_to_use_which() {
    // Use quote! when:
    // - Generated code errors should point to macro call site
    // - You're generating helper code internal to the macro
    // - Spans don't matter for the generated code
    
    let internal_code = quote! {
        fn internal_helper() { /* ... */ }
    };
    
    // Use quote_spanned! when:
    // - Errors should point to specific user code locations
    // - You're generating code based on user input
    // - IDE support should show proper hover/goto
    // - Hygiene matters for generated identifiers
    
    // In derive macros:
    // - Use input token spans for validation errors
    // - Use field spans for field-specific generated code
    // - Use struct span for struct-wide generated code
}

Choose based on where you want errors to point.

Synthesis

Quick reference:

use quote::{quote, quote_spanned};
use proc_macro2::Span;
 
// quote!: All spans point to call site (macro invocation)
let code1 = quote! {
    fn generated() { /* error points to macro call site */ }
};
 
// quote_spanned!: Explicit span control
let user_span = some_input_token.span();
let code2 = quote_spanned! {user_span=>
    fn generated() { /* error points to user_span location */ }
};
 
// Common span sources:
// - input.ident.span() - struct/enum name
// - field.ident.span() - field name
// - field.ty.span() - field type
// - Span::call_site() - macro invocation location
// - Span::mixed_site() - hygienic identifiers
 
// Use quote_spanned! for:
// - Derive macros where errors should point to fields
// - Attribute macros with input token attribution
// - Generated code that should integrate with user's IDE

Key insight: The difference between quote! and quote_spanned! is fundamentally about error attribution and IDE support. quote! generates token streams where all locations resolve to the macro call site—errors appear at the #[derive(...)] attribute. quote_spanned! lets you preserve span information from input tokens so that errors, warnings, and IDE features point to specific locations in the user's source code. In derive macros, this means using each input token's span for generated code related to that token: field spans for field-specific methods, type spans for type errors, struct spans for struct-wide code. This dramatically improves the developer experience because diagnostics appear where they're meaningful rather than at an opaque macro invocation.