Loading pageā¦
Rust walkthroughs
Loading pageā¦
quote::quote_spanned! preserve span information for better compiler error messages?quote_spanned! generates tokens with a specific span attached, so compiler errors point to user code rather than generated code. When a proc macro generates tokens using regular quote!, those tokens carry a synthetic span pointing to the macro invocation site or nowhere meaningful. quote_spanned! lets you attach a span from the input token stream to the output tokens, so if the generated code has a type error or other issue, the error message highlights the relevant source location from the user's code. This is essential for proc macros that generate trait implementations, methods, or any code that might fail to compileāthe difference is between "error in generated code at line 1" and "error at the field you marked with #[derive]".
use proc_macro2::Span;
use quote::quote;
// A span represents a location in source code
// It includes the file, line, column, and extent of a token
fn demonstrate_spans() {
// Every token has a span
let token: proc_macro2::TokenStream = quote!(let x = 42;);
// Spans are used by the compiler for:
// 1. Error messages - "error at line 5, column 10"
// 2. IDE features - go to definition, hover info
// 3. Macro expansion traces
// When proc macros generate code, those tokens need spans
// Bad spans = confusing error messages
}Spans track source locations for error reporting and IDE features.
// In a proc macro crate:
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, DeriveInput};
#[proc_macro_derive(ExampleDerive)]
pub fn example_derive(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
// Regular quote! generates tokens with call_site spans
// These point to the macro invocation, not the actual source
let expanded = quote! {
// If this code has errors, the error points to the derive macro
// invocation, not to any specific part of the user's type
impl ExampleDerive for #input {
fn example_method(&self) -> i32 {
self.nonexistent_field // Error here points to wrong place
}
}
};
TokenStream::from(expanded)
}Regular quote! gives all generated tokens the same span, pointing to the macro call site.
use proc_macro::TokenStream;
use quote::{quote, quote_spanned};
use syn::{parse_macro_input, DeriveInput, Data, Fields, Ident};
#[proc_macro_derive(GetField)]
pub fn get_field_derive(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let name = &input.ident;
// Get the first field's ident with its span
let field_info = match &input.data {
Data::Struct(data) => {
match &data.fields {
Fields::Named(fields) => {
fields.named.iter().next().map(|f| {
let field_name = f.ident.as_ref().unwrap();
let field_ty = &f.ty;
// Store the field's span for error reporting
(field_name.clone(), field_ty.clone(), field_name.span())
})
}
_ => None
}
}
_ => None
};
let expanded = if let Some((field_name, field_ty, field_span)) = field_info {
// Use quote_spanned! to attach the field's span to generated code
// If there's an error accessing this field, it points to the field
quote! {
impl GetField for #name {
type FieldType = #field_ty;
fn get_field(&self) -> &Self::FieldType {
// The span for this expression comes from the field
quote_spanned!(field_span=> &self.#field_name)
}
}
}
} else {
quote! {}
};
TokenStream::from(expanded)
}quote_spanned! attaches a specific span to generated code for precise error locations.
// Imagine we have a derive macro that generates a method
// WITHOUT quote_spanned!:
// User's code:
#[derive(ExampleDerive)]
struct MyStruct {
name: String,
count: i32,
}
// If the generated code has an error, they see:
// error[E0609]: no field `some_field` on type `MyStruct`
// --> src/main.rs:1:10
// |
// 1 | #[derive(ExampleDerive)]
// | ^^^^^^^^^^^^^ field not found in `MyStruct`
//
// This points to the derive attribute, not the problematic field!
// WITH quote_spanned!:
// The error would show:
// error[E0609]: no field `some_field` on type `MyStruct`
// --> src/main.rs:3:5
// |
// 3 | count: i32,
// | ^^^^^ field not found in `MyStruct`
//
// Now it points to the specific field that caused the issuequote_spanned! makes errors point to relevant user code, not the macro invocation.
use proc_macro::TokenStream;
use proc_macro2::Span;
use quote::{quote, quote_spanned};
use syn::{parse_macro_input, DeriveInput, Ident};
#[proc_macro_derive(Builder)]
pub fn builder_derive(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let struct_name = &input.ident;
let builder_name = Ident::new(
&format!("{}Builder", struct_name),
struct_name.span() // Use struct's span for builder name
);
let fields = match &input.data {
syn::Data::Struct(data) => {
data.fields.iter().map(|field| {
let field_name = field.ident.as_ref().unwrap();
let field_ty = &field.ty;
let field_span = field_name.span();
// Each generated method gets the field's span
let getter = quote_spanned! {field_span=>
pub fn #field_name(mut self, value: #field_ty) -> Self {
self.#field_name = Some(value);
self
}
};
getter
}).collect::<Vec<_>>()
}
_ => vec![]
};
let expanded = quote! {
pub struct #builder_name {
#(#fields)*
}
impl #builder_name {
#(#fields)*
}
};
TokenStream::from(expanded)
}Extract spans from input tokens and use them in generated code.
use proc_macro2::Span;
use quote::{quote, quote_spanned};
fn span_hierarchy() {
// Different span sources have different behaviors:
// 1. call_site() - Where the macro was invoked
let call_site_span = Span::call_site();
// 2. mixed_site() - Hybrid behavior ( hygiene)
let mixed_site_span = Span::mixed_site();
// 3. From input tokens - The actual source location
// This is what you extract from parsed input
// When generating code:
// Using call_site - errors point to macro invocation
let with_call_site = quote_spanned! {call_site_span=>
let x = nonexistent; // Error at #[derive(MyMacro)]
};
// Using input token span - errors point to user code
// let with_input_span = quote_spanned! {field.span()=> ...};
}Different span sources affect where error messages point.
use proc_macro::TokenStream;
use proc_macro2::Span;
use quote::{quote, quote_spanned, format_ident};
use syn::{parse_macro_input, DeriveInput, Data, Fields, Meta, Lit};
#[proc_macro_derive(Validate, attributes(validate))]
pub fn validate_derive(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let struct_name = &input.ident;
let validations = match &input.data {
Data::Struct(data) => {
data.fields.iter().map(|field| {
let field_name = field.ident.as_ref().unwrap();
let field_span = field_name.span();
// Check for validate attributes
let mut min_value: Option<i64> = None;
let mut max_value: Option<i64> = None;
for attr in &field.attrs {
if attr.path.is_ident("validate") {
// Parse validation attributes
// ...
}
}
// Generate validation code with proper spans
let mut validation_code = quote! {};
if let Some(min) = min_value {
// Use quote_spanned so error points to the field
validation_code = quote! {
#validation_code
if self.#field_name < #min {
return Err(format!(
"Field {} must be at least {}",
stringify!(#field_name),
#min
));
}
};
}
// Wrap with field span for better error location
quote_spanned! {field_span=>
#validation_code
}
}).collect::<Vec<_>>()
}
_ => vec![]
};
let expanded = quote! {
impl Validate for #struct_name {
fn validate(&self) -> Result<(), String> {
#(#validations)*
Ok(())
}
}
};
TokenStream::from(expanded)
}Generate validation code where errors point to the specific field with validation attributes.
use proc_macro::TokenStream;
use proc_macro2::Span;
use quote::{quote, quote_spanned, compile_error};
use syn::{parse_macro_input, DeriveInput, Data, Fields};
#[proc_macro_derive(OnlyStructs)]
pub fn only_structs_derive(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
match &input.data {
Data::Struct(data) => {
// Generate struct code
let fields: Vec<_> = data.fields.iter().map(|field| {
let field_name = field.ident.as_ref().unwrap();
let field_span = field_name.span();
// Use quote_spanned for field-specific code
quote_spanned! {field_span=>
println!("Field: {}", stringify!(#field_name));
}
}).collect();
let expanded = quote! {
#(#fields)*
};
TokenStream::from(expanded)
}
Data::Enum(_) => {
// Report error at the enum keyword, not at the derive
// Find the span of "enum" in the input
let enum_span = input.ident.span(); // Approximation
let error = quote_spanned! {enum_span=>
compile_error!("OnlyStructs can only be derived for structs");
};
TokenStream::from(error)
}
Data::Union(_) => {
let error = quote! {
compile_error!("OnlyStructs can only be derived for structs");
};
TokenStream::from(error)
}
}
}Use compile_error! with appropriate spans to show errors at the right location.
use proc_macro::TokenStream;
use proc_macro2::Span;
use quote::{quote, format_ident};
use syn::{parse_macro_input, DeriveInput, Ident};
#[proc_macro_derive(StateMachine)]
pub fn state_machine_derive(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let struct_name = &input.ident;
let struct_span = struct_name.span();
// Create helper types that "belong" to the original struct
// Use the struct's span so errors point to the struct definition
let state_enum = format_ident!("{}State", struct_name, span = struct_span);
let event_enum = format_ident!("{}Event", struct_name, span = struct_span);
let error_type = format_ident!("{}Error", struct_name, span = struct_span);
let expanded = quote! {
// These generated types have the struct's span
// If there's an issue, error points to original struct
enum #state_enum {
Initial,
Running,
Finished,
}
enum #event_enum {
Start,
Stop,
}
struct #error_type {
message: String,
}
impl #struct_name {
fn transition(&mut self, event: #event_enum) -> Result<#state_enum, #error_type> {
// Implementation
Ok(#state_enum::Initial)
}
}
};
TokenStream::from(expanded)
}Generated type names can inherit spans from their source type.
use proc_macro::TokenStream;
use proc_macro2::Span;
use quote::{quote, quote_spanned};
use syn::{parse_macro_input, DeriveInput, Data, Ident};
#[proc_macro_derive(Getters)]
pub fn getters_derive(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let struct_name = &input.ident;
let getters = match &input.data {
Data::Struct(data) => {
data.fields.iter().map(|field| {
let field_name = field.ident.as_ref().unwrap();
let field_ty = &field.ty;
let field_span = field_name.span();
// Create getter method name
let method_name = field_name.clone();
// Generate getter with field's span
// If there's a type mismatch, error points to the field
quote_spanned! {field_span=>
pub fn #method_name(&self) -> &#field_ty {
&self.#field_name
}
}
}).collect::<Vec<_>>()
}
_ => vec![]
};
let expanded = quote! {
impl #struct_name {
#(#getters)*
}
};
TokenStream::from(expanded)
}
// Usage:
// #[derive(Getters)]
// struct User {
// name: String,
// age: u32,
// }
//
// If there's an issue with the age getter,
// the error points to the `age` field, not the #[derive(Getters)]Getter methods generated with field spans produce field-specific error messages.
use proc_macro::TokenStream;
use proc_macro2::Span;
use quote::{quote, quote_spanned};
use syn::{parse_macro_input, DeriveInput, Data, Fields, Type};
#[proc_macro_derive(Compare)]
pub fn compare_derive(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let struct_name = &input.ident;
// Generate comparison code that uses spans from both fields
let comparisons = match &input.data {
Data::Struct(data) => {
let fields: Vec<_> = data.fields.iter().collect();
fields.windows(2).map(|pair| {
let field1 = &pair[0];
let field2 = &pair[1];
let name1 = field1.ident.as_ref().unwrap();
let name2 = field2.ident.as_ref().unwrap();
let span1 = name1.span();
let span2 = name2.span();
// Generate comparison with appropriate spans
quote! {
if self.#name1 < other.#name1 {
return std::cmp::Ordering::Less;
}
if self.#name1 > other.#name1 {
return std::cmp::Ordering::Greater;
}
}
}).collect::<Vec<_>>()
}
_ => vec![]
};
let expanded = quote! {
impl std::cmp::Ord for #struct_name {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
#(#comparisons)*
std::cmp::Ordering::Equal
}
}
impl std::cmp::PartialOrd for #struct_name {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl std::cmp::PartialEq for #struct_name {
fn eq(&self, other: &Self) -> bool {
self.cmp(other) == std::cmp::Ordering::Equal
}
}
impl std::cmp::Eq for #struct_name {}
};
TokenStream::from(expanded)
}When generating code that involves multiple fields, choose the most relevant span.
Span sources:
| Source | Behavior | Use Case |
|--------|----------|----------|
| Span::call_site() | Points to macro invocation | Default for quote! |
| Span::mixed_site() | Hybrid hygiene | Advanced hygiene needs |
| Input token span | Points to user code | Field/method-specific errors |
quote! vs quote_spanned!:
// quote! - all tokens get call_site span
let code = quote! {
self.field.method() // Error points to #[derive]
};
// quote_spanned! - tokens get specific span
let field_span = field.ident.span();
let code = quote_spanned! {field_span=>
self.field.method() // Error points to field
};Common pattern:
// For each field/variant from input
for field in &fields {
let field_span = field.ident.span();
let field_name = &field.ident;
// Generate code with that field's span
let generated = quote_spanned! {field_span=>
// Any error here points to the field
some_expression_involving(#field_name)
};
}When to use quote_spanned!:
Key insight: quote_spanned! bridges the gap between macro-generated code and the original source. Without it, proc macros produce a black box where errors emerge from generated code at artificial locations. By threading spans from input tokens through to generated code, errors appear at meaningful locations in user codeāthe field that has the wrong type, the attribute with invalid syntax, the method that doesn't exist. This isn't just about convenience; it's about making proc macros feel like native language features. When a user sees an error pointing to their own code rather than an opaque macro expansion, they can fix it quickly. The pattern is straightforward: extract spans from parsed input tokens, and use quote_spanned! (or format_ident! with span = ) when generating code that might fail. The resulting error messages transform the macro from a mysterious code generator into a transparent, helpful tool that guides users to exactly where problems exist.