What is the difference between quote::quote_spanned! and quote! for preserving span information in diagnostics?
quote! uses the call site span for all generated tokens, while quote_spanned! assigns a specified span to the generated code, enabling precise error message locations that point to the original source code rather than the macro definition site. This distinction is critical for proc-macro authors who want to provide helpful compiler error messages that guide users to the actual problematic code in their source files.
The Role of Spans in Procedural Macros
use proc_macro2::Span;
fn span_basics() {
// A Span represents a location in source code
// It includes:
// - File path
// - Line and column (start and end)
// - Information for error reporting
// Every token in source code has an associated span
// When macros generate code, spans determine:
// 1. Where error messages point
// 2. How IDE features work (go to definition, etc.)
// 3. How rustfmt handles the code
}Spans are metadata attached to tokens that tell the compiler where they originated, affecting error messages and IDE features.
Basic quote! Behavior
use quote::quote;
fn quote_basics() {
// quote! generates TokenStream with call_site spans
let tokens = quote! {
fn hello() -> i32 {
42
}
};
// All tokens in the output have spans pointing to:
// - The macro call site (where quote! was invoked)
// - NOT the definition of 'fn', 'i32', etc.
// If an error occurs in generated code:
// - Error points to the macro invocation
// - Not helpful for understanding what went wrong
}quote! assigns the call site span to all generated tokens, meaning errors point to where quote! was called, not where problematic input originated.
The Problem with quote! Spans
use quote::quote;
use proc_macro::TokenStream;
// Imagine this proc-macro in a library
#[proc_macro_attribute]
pub fn make_getter(_attr: TokenStream, item: TokenStream) -> TokenStream {
let input = syn::parse_macro_input!(item as syn::ItemStruct);
let name = &input.ident;
// Problem: All spans point here (call site)
let generated = quote! {
impl #name {
fn get_value(&self) -> &String {
&self.value // If this errors, points to proc-macro!
}
}
};
// If user's struct doesn't have 'value' field:
// Error points to proc-macro definition
// User sees error in library code, not their code
TokenStream::from(generated)
}When generated code has errors, the default spans point to the macro definition, confusing users.
Understanding Call Site vs Mixed Site
use quote::quote;
fn span_locations() {
// In a proc-macro:
// Call site: Where the macro is invoked (user's code)
// let span = proc_macro2::Span::call_site();
// Points to: user_code.rs line 10 (where macro was called)
// Mixed site: Where specific tokens originated
// If user passes `field_name`, it has its own span
// Points to: user_code.rs line 12 (where field_name was defined)
// quote! uses call_site for everything:
let field_name = syn::Ident::new("my_field", proc_macro2::Span::call_site());
let tokens = quote! {
#field_name // Uses field_name's span (mixed)
};
// But literal tokens like `fn`, `->` get call_site span
// quote_spanned! uses specified span for everything:
}Call site is where the macro was invoked; mixed site preserves original token locations.
quote_spanned! Syntax and Behavior
use quote::quote_spanned;
use proc_macro2::Span;
fn quote_spanned_syntax() {
let span = Span::call_site(); // In real use, this comes from input tokens
// quote_spanned!(span => tokens)
// All generated tokens get the specified span
let tokens = quote_spanned!(span =>
fn hello() -> i32 {
42
}
);
// Now 'fn', 'hello', '->', 'i32', '42' all have span
// If any error occurs, it points to span's location
}quote_spanned! takes a span expression and applies it to all generated tokens.
Preserving Input Spans for Better Errors
use quote::{quote, quote_spanned};
use proc_macro::TokenStream;
use syn::{parse_macro_input, DeriveInput};
#[proc_macro_derive(MyTrait)]
pub fn derive_my_trait(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let name = &input.ident;
// name has span from user's code
// BAD: Errors point to macro definition
let bad = quote! {
impl MyTrait for #name {
fn do_thing(&self) -> Result<(), Error> {
Err(Error::new()) // If Error undefined, confusing error
}
}
};
// GOOD: Errors point to user's type name location
let good = quote_spanned! {name.span() =>
impl MyTrait for #name {
fn do_thing(&self) -> Result<(), Error> {
Err(Error::new())
}
}
};
TokenStream::from(good)
}Using the input token's span makes errors point to the user's code location.
Field Access Span Example
use quote::{quote, quote_spanned};
use proc_macro::TokenStream;
use syn::{parse_macro_input, DeriveInput, Data, Fields};
#[proc_macro_derive(Getter)]
pub fn derive_getter(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let name = &input.ident;
let getter_methods = match &input.data {
Data::Struct(data) => match &data.fields {
Fields::Named(fields) => {
fields.named.iter().map(|field| {
let field_name = field.ident.as_ref().unwrap();
let field_ty = &field.ty;
// Use field's span for generated method
// If field doesn't exist, error points to field definition
quote_spanned! {field.span() =>
pub fn #field_name(&self) -> &#field_ty {
&self.#field_name
}
}
}).collect::<proc_macro2::TokenStream>()
}
_ => quote! {}
},
_ => quote! {}
};
let output = quote! {
impl #name {
#getter_methods
}
};
TokenStream::from(output)
}Each field's getter method gets that field's span, making errors specific to each field.
Comparing Error Messages
use quote::{quote, quote_spanned};
use proc_macro2::Span;
fn error_comparison() {
// Scenario: Generated code references undefined type
let user_type: syn::Ident = syn::Ident::new("MyStruct", Span::call_site());
// Using quote!
let bad_code = quote! {
impl UndefinedTrait for #user_type {} // UndefinedTrait doesn't exist
};
// Error: "cannot find trait UndefinedTrait in this scope"
// Location: Points to proc-macro definition (unhelpful!)
// Using quote_spanned!
let good_code = quote_spanned! {user_type.span() =>
impl UndefinedTrait for #user_type {} // UndefinedTrait doesn't exist
};
// Error: "cannot find trait UndefinedTrait in this scope"
// Location: Points to where MyStruct was defined (better!)
// User sees: "Here's where I used MyStruct that the macro expanded"
}The span determines where the compiler points in error messages.
Span Propagation for Token Trees
use quote::quote_spanned;
use syn::Ident;
use proc_macro2::Span;
fn span_propagation() {
let span = Span::call_site();
// All tokens in quote_spanned! get the specified span:
// - Keywords (fn, impl, pub)
// - Punctuation ({, }, ->)
// - Literals (42, "string")
// - Interpolated values keep their own spans
let name = Ident::new("my_function", span);
let tokens = quote_spanned! {span =>
fn #name() -> i32 {
42
}
};
// 'fn', '()', '->', 'i32', '{', '}', '42' all have `span`
// #name has name's span (which is also `span` here)
// If an error in 'i32' type resolution:
// Points to `span` location
}All generated tokens receive the specified span, while interpolated values keep their original spans.
Mixed Spans Within quote_spanned!
use quote::quote_spanned;
use syn::Ident;
use proc_macro2::Span;
fn mixed_spans() {
let error_span = Span::call_site(); // From user input
let type_name = Ident::new("String", Span::call_site()); // Built-in
// Different tokens can have different spans
let tokens = quote_spanned! {error_span =>
fn example() -> #type_name {
String::new()
}
};
// 'fn', 'example', '->' have error_span
// #type_name has type_name's span (may differ)
// 'String::new()' has error_span (it's generated, not interpolated)
// When mixing spans:
// - Use quote_spanned for structural tokens
// - Interpolated values keep their spans
}Interpolated values (#var) retain their original spans even inside quote_spanned!.
Practical Derive Macro Example
use quote::{quote, quote_spanned};
use proc_macro::TokenStream;
use syn::{parse_macro_input, DeriveInput, Data, Fields, Result, Error};
#[proc_macro_derive(Builder)]
pub fn derive_builder(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
// Early error with proper span
if !matches!(&input.data, Data::Struct(_)) {
let error = Error::new_spanned(
&input,
"Builder derive only works on structs"
);
return error.to_compile_error().into();
}
let name = &input.ident;
let builder_name = Ident::new(
&format!("{}Builder", name),
name.span() // Use original name's span
);
let fields = match &input.data {
Data::Struct(data) => match &data.fields {
Fields::Named(fields) => &fields.named,
_ => {
let error = Error::new(
proc_macro2::Span::call_site(),
"Only named fields supported"
);
return error.to_compile_error().into();
}
},
_ => unreachable!(),
};
let setter_methods: proc_macro2::TokenStream = fields.iter().map(|field| {
let field_name = field.ident.as_ref().unwrap();
let field_ty = &field.ty;
// Each setter gets the field's span
// Errors in setter usage point to field definition
quote_spanned! {field.span() =>
pub fn #field_name(&mut self, value: #field_ty) -> &mut Self {
self.#field_name = Some(value);
self
}
}
}).collect();
let field_names: Vec<_> = fields.iter()
.map(|f| f.ident.as_ref().unwrap())
.collect();
let output = quote! {
pub struct #builder_name {
#(#field_names: Option<#fields>),*
}
impl #builder_name {
#setter_methods
}
};
TokenStream::from(output)
}Using field spans for setter methods makes errors specific to each field.
Diagnostic Messages with Spans
use quote::quote_spanned;
use proc_macro2::Span;
use syn::Error;
fn diagnostic_errors() {
// When generating error messages:
// Use the span of the problematic input
let problematic_ident: syn::Ident = syn::Ident::new("bad_name", Span::call_site());
// Create error pointing to user's code
let error = Error::new(
problematic_ident.span(),
"this identifier is invalid"
);
// Generate error token stream
let error_tokens = error.to_compile_error();
// This produces a compile_error! macro call
// With the error message pointing to problematic_ident
}Using proper spans in errors makes compiler messages point to the user's code, not the macro.
Spanning Different Parts Differently
use quote::quote_spanned;
use syn::Ident;
use proc_macro2::Span;
fn multiple_spans() {
let struct_span = Span::call_site();
let impl_span = Span::call_site(); // Different span in practice
let type_name = Ident::new("MyType", struct_span);
// Different spans for different generated sections
let impl_block = quote_spanned! {impl_span =>
impl SomeTrait for #type_name {
fn trait_method() {}
}
};
let struct_block = quote_spanned! {struct_span =>
struct #type_name {
field: i32,
}
};
// Errors in impl block point to impl_span
// Errors in struct block point to struct_span
// Users see errors at relevant locations
}Different code sections can have different spans for context-specific error reporting.
When to Use Each Macro
use quote::{quote, quote_spanned};
fn choosing_macro() {
// Use quote! when:
// - Generated code is self-contained
// - Errors in generated code are truly macro bugs
// - No user input spans to preserve
let helper_function = quote! {
fn internal_helper() -> i32 {
42
}
};
// Use quote_spanned! when:
// - Generated code references user-defined items
// - Errors might occur in generated code
// - Want errors to point to user's input location
let user_item: syn::Ident = syn::Ident::new("UserStruct", proc_macro2::Span::call_site());
let user_impl = quote_spanned! {user_item.span() =>
impl SomeTrait for #user_item {
fn do_thing(&self) {}
}
};
}Choose quote! for internal code and quote_spanned! for user-facing generated code.
Complete Example: Validating Macro Input
use quote::{quote, quote_spanned};
use proc_macro::TokenStream;
use syn::{parse_macro_input, DeriveInput, Data, Fields, Result, Error, Ident};
#[proc_macro_attribute]
pub fn validated(
_attr: TokenStream,
item: TokenStream
) -> TokenStream {
let input = parse_macro_input!(item as DeriveInput);
// Validate and use spans for errors
match validate_input(&input) {
Ok(()) => {
let name = &input.ident;
let output = quote_spanned! {name.span() =>
#input
impl #name {
fn validated(&self) -> bool {
true
}
}
};
TokenStream::from(output)
}
Err(err) => err.to_compile_error().into()
}
}
fn validate_input(input: &DeriveInput) -> Result<()> {
match &input.data {
Data::Struct(data) => {
if let Fields::Named(fields) = &data.fields {
for field in &fields.named {
if let Some(ident) = &field.ident {
// Check field name conventions
let name = ident.to_string();
if name.starts_with('_') {
return Err(Error::new(
field.span(),
"fields starting with _ are not allowed"
));
}
}
}
}
Ok(())
}
Data::Enum(_) => Err(Error::new_spanned(
input,
"validated only works on structs"
)),
Data::Union(_) => Err(Error::new_spanned(
input,
"validated only works on structs, not unions"
)),
}
}Validation errors use proper spans to guide users to problematic code locations.
Summary Table
fn summary_table() {
// | Aspect | quote! | quote_spanned! |
// |--------|--------|----------------|
// | Span source | Call site | Specified span |
// | Error location | Macro definition | Specified location |
// | Use case | Internal helpers | User-facing code |
// | Interpolated values | Keep their spans | Keep their spans |
// | Use quote! | Use quote_spanned! |
// |------------|---------------------|
// | Internal utility functions | Trait impls for user types |
// | Generated helpers | Generated methods for fields |
// | Unlikely to error | Code that might error |
// | No user input span available | Have user input spans |
}Synthesis
Quick reference:
use quote::{quote, quote_spanned};
fn quick_reference() {
// quote! - use for internal generated code
let internal = quote! {
fn helper() -> i32 { 42 }
};
// quote_spanned! - use for user-facing generated code
let user_type: syn::Ident = /* from user input */;
let user_code = quote_spanned! {user_type.span() =>
impl MyTrait for #user_type {
fn do_it(&self) {}
}
};
// Key difference:
// - quote! errors point to macro definition
// - quote_spanned! errors point to user's code
}Key insight: quote! assigns the call site span to all generated tokens, making errors point to where the macro was invoked, while quote_spanned! allows specifying exactly which span to use, enabling errors to point to meaningful locations in the user's source code. This distinction is essential for proc-macro ergonomics: when a macro generates code that references undefined items or has type mismatches, users need to see errors at their code locations, not the macro library's code. The quote_spanned! macro bridges this gap by propagating input spans through generated code, so if a field my_field in the user's struct causes an issue in generated accessor methods, the error points to where my_field was defined, not where the derive macro's implementation lives. Interpolated values (like #name) always preserve their spans regardless of which macro is used, so the key difference is in the literal tokens (keywords, punctuation, literals) that make up the macro's template. Use quote! for self-contained generated code where errors truly indicate macro bugs, and quote_spanned! for generated code that interacts with user-defined items where errors should guide users to their own code.
