Skip to content

Commit 0f32682

Browse files
committed
fix: Replace quote! with quote_spanned! in #[program] macro
Replaces all uses of quote!{} with quote_spanned! in the program macro code generation to ensure generated code corresponds to correct source code locations. This enables the Rust compiler to report error messages at the actual problematic code instead of the macro invocation. Changes: - Applied quote_spanned! to all 9 codegen files - Added spanned variants of helper functions - Proper span extraction at instruction, program, and module levels Addresses issue #4015.
1 parent a1f0743 commit 0f32682

File tree

9 files changed

+99
-49
lines changed

9 files changed

+99
-49
lines changed

lang/syn/src/codegen/program/accounts.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
use crate::Program;
22
use heck::SnakeCase;
3-
use quote::quote;
3+
use quote::quote_spanned;
4+
use syn::spanned::Spanned;
45

56
pub fn generate(program: &Program) -> proc_macro2::TokenStream {
7+
let program_span = program.program_mod.span();
68
let mut accounts = std::collections::HashMap::new();
79

810
// Go through instruction accounts.
@@ -21,7 +23,7 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
2123
.iter()
2224
.map(|(macro_name, cfgs)| {
2325
let macro_name: proc_macro2::TokenStream = macro_name.parse().unwrap();
24-
quote! {
26+
quote_spanned! { program_span =>
2527
#(#cfgs)*
2628
pub use crate::#macro_name::*;
2729
}
@@ -31,7 +33,7 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
3133
// TODO: calculate the account size and add it as a constant field to
3234
// each struct here. This is convenient for Rust clients.
3335

34-
quote! {
36+
quote_spanned! { program_span =>
3537
/// An Anchor generated module, providing a set of structs
3638
/// mirroring the structs deriving `Accounts`, where each field is
3739
/// a `Pubkey`. This is useful for specifying accounts for a client.

lang/syn/src/codegen/program/common.rs

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use crate::IxArg;
22
use heck::CamelCase;
3-
use quote::quote;
3+
use quote::quote_spanned;
44

55
// Namespace for calculating instruction sighash signatures for any instruction
66
// not affecting program state.
@@ -24,15 +24,23 @@ pub fn gen_discriminator(namespace: &str, name: impl ToString) -> proc_macro2::T
2424
}
2525

2626
pub fn generate_ix_variant(name: &str, args: &[IxArg]) -> proc_macro2::TokenStream {
27+
generate_ix_variant_spanned(name, args, proc_macro2::Span::call_site())
28+
}
29+
30+
pub fn generate_ix_variant_spanned(
31+
name: &str,
32+
args: &[IxArg],
33+
span: proc_macro2::Span,
34+
) -> proc_macro2::TokenStream {
2735
let ix_arg_names: Vec<&syn::Ident> = args.iter().map(|arg| &arg.name).collect();
28-
let ix_name_camel = generate_ix_variant_name(name);
36+
let ix_name_camel = generate_ix_variant_name_spanned(name, span);
2937

3038
if args.is_empty() {
31-
quote! {
39+
quote_spanned! { span =>
3240
#ix_name_camel
3341
}
3442
} else {
35-
quote! {
43+
quote_spanned! { span =>
3644
#ix_name_camel {
3745
#(#ix_arg_names),*
3846
}
@@ -44,3 +52,12 @@ pub fn generate_ix_variant_name(name: &str) -> proc_macro2::TokenStream {
4452
let n = name.to_camel_case();
4553
n.parse().unwrap()
4654
}
55+
56+
pub fn generate_ix_variant_name_spanned(
57+
name: &str,
58+
span: proc_macro2::Span,
59+
) -> proc_macro2::TokenStream {
60+
let n = name.to_camel_case();
61+
let ident = proc_macro2::Ident::new(&n, span);
62+
quote_spanned! { span => #ident }
63+
}

lang/syn/src/codegen/program/cpi.rs

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
1-
use crate::codegen::program::common::{generate_ix_variant, generate_ix_variant_name};
1+
use crate::codegen::program::common::{
2+
generate_ix_variant_name_spanned, generate_ix_variant_spanned,
3+
};
24
use crate::Program;
35
use heck::SnakeCase;
4-
use quote::{quote, ToTokens};
6+
use quote::{quote_spanned, ToTokens};
7+
#[allow(unused_imports)]
8+
use syn::spanned::Spanned;
59

610
pub fn generate(program: &Program) -> proc_macro2::TokenStream {
11+
let program_span = program.program_mod.span();
712
// Generate cpi methods for global methods.
813
let global_cpi_methods: Vec<proc_macro2::TokenStream> = program
914
.ixs
@@ -13,24 +18,25 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
1318
let cpi_method = {
1419
let name = &ix.raw_method.sig.ident;
1520
let name_str = name.to_string();
16-
let ix_variant = generate_ix_variant(&name_str, &ix.args);
21+
let ix_span = ix.raw_method.span();
22+
let ix_variant = generate_ix_variant_spanned(&name_str, &ix.args, ix_span);
1723
let method_name = &ix.ident;
1824
let args: Vec<&syn::PatType> = ix.args.iter().map(|arg| &arg.raw_arg).collect();
1925
let discriminator = {
20-
let name = generate_ix_variant_name(&name_str);
21-
quote! { <instruction::#name as anchor_lang::Discriminator>::DISCRIMINATOR }
26+
let name = generate_ix_variant_name_spanned(&name_str, ix_span);
27+
quote_spanned! { ix_span => <instruction::#name as anchor_lang::Discriminator>::DISCRIMINATOR }
2228
};
2329
let ret_type = &ix.returns.ty.to_token_stream();
2430
let ix_cfgs = &ix.cfgs;
2531
let (method_ret, maybe_return) = match ret_type.to_string().as_str() {
26-
"()" => (quote! {anchor_lang::Result<()> }, quote! { Ok(()) }),
32+
"()" => (quote_spanned! { ix_span => anchor_lang::Result<()> }, quote_spanned! { ix_span => Ok(()) }),
2733
_ => (
28-
quote! { anchor_lang::Result<crate::cpi::Return::<#ret_type>> },
29-
quote! { Ok(crate::cpi::Return::<#ret_type> { phantom: crate::cpi::PhantomData }) }
34+
quote_spanned! { ix_span => anchor_lang::Result<crate::cpi::Return::<#ret_type>> },
35+
quote_spanned! { ix_span => Ok(crate::cpi::Return::<#ret_type> { phantom: crate::cpi::PhantomData }) }
3036
)
3137
};
3238

33-
quote! {
39+
quote_spanned! { ix_span =>
3440
#(#ix_cfgs)*
3541
pub fn #method_name<'a, 'b, 'c, 'info>(
3642
ctx: anchor_lang::context::CpiContext<'a, 'b, 'c, 'info, #accounts_ident<'info>>,
@@ -69,7 +75,7 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
6975

7076
let accounts = generate_accounts(program);
7177

72-
quote! {
78+
quote_spanned! { program_span =>
7379
#[cfg(feature = "cpi")]
7480
pub mod cpi {
7581
use super::*;
@@ -95,6 +101,7 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
95101
}
96102

97103
pub fn generate_accounts(program: &Program) -> proc_macro2::TokenStream {
104+
let program_span = program.program_mod.span();
98105
let mut accounts = std::collections::HashMap::new();
99106

100107
// Go through instruction accounts.
@@ -114,14 +121,14 @@ pub fn generate_accounts(program: &Program) -> proc_macro2::TokenStream {
114121
.iter()
115122
.map(|(macro_name, cfgs)| {
116123
let macro_name: proc_macro2::TokenStream = macro_name.parse().unwrap();
117-
quote! {
124+
quote_spanned! { program_span =>
118125
#(#cfgs)*
119126
pub use crate::#macro_name::*;
120127
}
121128
})
122129
.collect();
123130

124-
quote! {
131+
quote_spanned! { program_span =>
125132
/// An Anchor generated module, providing a set of structs
126133
/// mirroring the structs deriving `Accounts`, where each field is
127134
/// an `AccountInfo`. This is useful for CPI.

lang/syn/src/codegen/program/dispatch.rs

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use crate::Program;
22
use heck::CamelCase;
3-
use quote::quote;
3+
use quote::quote_spanned;
4+
use syn::spanned::Spanned;
45

56
pub fn generate(program: &Program) -> proc_macro2::TokenStream {
67
// Dispatch all global instructions.
@@ -11,10 +12,12 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
1112
.to_camel_case()
1213
.parse()
1314
.expect("Failed to parse ix method name in camel as `TokenStream`");
14-
let discriminator = quote! { instruction::#ix_name_camel::DISCRIMINATOR };
15+
let ix_span = ix.raw_method.span();
16+
let discriminator =
17+
quote_spanned! { ix_span => instruction::#ix_name_camel::DISCRIMINATOR };
1518
let ix_cfgs = &ix.cfgs;
1619

17-
quote! {
20+
quote_spanned! { ix_span =>
1821
#(#ix_cfgs)*
1922
if data.starts_with(#discriminator) {
2023
return __private::__global::#ix_method_name(
@@ -27,9 +30,10 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
2730
});
2831

2932
// Generate the event-cpi instruction handler based on whether the `event-cpi` feature is enabled.
33+
let program_span = program.program_mod.span();
3034
let event_cpi_handler = {
3135
#[cfg(feature = "event-cpi")]
32-
quote! {
36+
quote_spanned! { program_span =>
3337
// `event-cpi` feature is enabled, dispatch self-cpi instruction
3438
__private::__events::__event_dispatch(
3539
program_id,
@@ -38,7 +42,7 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
3842
)
3943
}
4044
#[cfg(not(feature = "event-cpi"))]
41-
quote! {
45+
quote_spanned! { program_span =>
4246
// `event-cpi` feature is not enabled
4347
Err(anchor_lang::error::ErrorCode::EventInstructionStub.into())
4448
}
@@ -50,17 +54,20 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
5054
.map(|fallback_fn| {
5155
let program_name = &program.name;
5256
let fn_name = &fallback_fn.raw_method.sig.ident;
53-
quote! {
57+
let fallback_span = fallback_fn.raw_method.span();
58+
quote_spanned! { fallback_span =>
5459
#program_name::#fn_name(program_id, accounts, data)
5560
}
5661
})
5762
.unwrap_or_else(|| {
58-
quote! {
63+
let program_span = program.program_mod.span();
64+
quote_spanned! { program_span =>
5965
Err(anchor_lang::error::ErrorCode::InstructionFallbackNotFound.into())
6066
}
6167
});
6268

63-
quote! {
69+
let program_span = program.program_mod.span();
70+
quote_spanned! { program_span =>
6471
/// Performs method dispatch.
6572
///
6673
/// Each instruction's discriminator is checked until the given instruction data starts with

lang/syn/src/codegen/program/entry.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
use crate::Program;
22
use heck::CamelCase;
3-
use quote::quote;
3+
use quote::quote_spanned;
4+
use syn::spanned::Spanned;
45

56
pub fn generate(program: &Program) -> proc_macro2::TokenStream {
67
let name: proc_macro2::TokenStream = program.name.to_string().to_camel_case().parse().unwrap();
7-
quote! {
8+
let span = program.program_mod.span();
9+
quote_spanned! { span =>
810
#[cfg(not(feature = "no-entrypoint"))]
911
anchor_lang::solana_program::entrypoint!(entry);
1012
/// The Anchor codegen exposes a programming model where a user defines

lang/syn/src/codegen/program/handlers.rs

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
use crate::codegen::program::common::*;
22
use crate::program_codegen::idl::idl_accounts_and_functions;
33
use crate::Program;
4-
use quote::{quote, ToTokens};
4+
use quote::{quote_spanned, ToTokens};
5+
use syn::spanned::Spanned;
56

67
// Generate non-inlined wrappers for each instruction handler, since Solana's
78
// BPF max stack size can't handle reasonable sized dispatch trees without doing
@@ -11,8 +12,9 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
1112
// A constant token stream that stores the accounts and functions, required to live
1213
// inside the target program in order to get the program ID.
1314
let idl_accounts_and_functions = idl_accounts_and_functions();
15+
let program_span = program.program_mod.span();
1416
let non_inlined_idl: proc_macro2::TokenStream = {
15-
quote! {
17+
quote_spanned! { program_span =>
1618
// Entry for all IDL related instructions. Use the "no-idl" feature
1719
// to eliminate this code, for example, if one wants to make the
1820
// IDL no longer mutable or if one doesn't want to store the IDL
@@ -99,21 +101,23 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
99101
let ix_arg_names: Vec<&syn::Ident> = ix.args.iter().map(|arg| &arg.name).collect();
100102
let ix_method_name = &ix.raw_method.sig.ident;
101103
let ix_method_name_str = ix_method_name.to_string();
104+
let ix_span = ix.raw_method.span();
102105
let ix_name = generate_ix_variant_name(&ix_method_name_str);
103-
let variant_arm = generate_ix_variant(&ix_method_name_str, &ix.args);
106+
let variant_arm = generate_ix_variant_spanned(&ix_method_name_str, &ix.args, ix_span);
104107
let ix_name_log = format!("Instruction: {ix_name}");
105108
let anchor = &ix.anchor_ident;
106109
let ret_type = &ix.returns.ty.to_token_stream();
107110
let cfgs = &ix.cfgs;
111+
let ix_span = ix.raw_method.span();
108112
let maybe_set_return_data = match ret_type.to_string().as_str() {
109-
"()" => quote! {},
110-
_ => quote! {
113+
"()" => quote_spanned! { ix_span => {} },
114+
_ => quote_spanned! { ix_span => {
111115
let mut return_data = Vec::with_capacity(256);
112116
result.serialize(&mut return_data).unwrap();
113117
anchor_lang::solana_program::program::set_return_data(&return_data);
114-
},
118+
}},
115119
};
116-
quote! {
120+
quote_spanned! { ix_span =>
117121
#(#cfgs)*
118122
#[inline(never)]
119123
pub fn #ix_method_name<'info>(
@@ -165,7 +169,7 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
165169
})
166170
.collect();
167171

168-
quote! {
172+
quote_spanned! { program_span =>
169173
/// Create a private module to not clutter the program's namespace.
170174
/// Defines an entrypoint for each individual instruction handler
171175
/// wrapper.
@@ -197,8 +201,9 @@ fn generate_event_cpi_mod() -> proc_macro2::TokenStream {
197201
{
198202
let authority = crate::parser::accounts::event_cpi::EventAuthority::get();
199203
let authority_name = authority.name;
204+
let span = proc_macro2::Span::call_site();
200205

201-
quote! {
206+
quote_spanned! { span =>
202207
/// __events mod defines handler for self-cpi based event logging
203208
pub mod __events {
204209
use super::*;
@@ -231,5 +236,8 @@ fn generate_event_cpi_mod() -> proc_macro2::TokenStream {
231236
}
232237
}
233238
#[cfg(not(feature = "event-cpi"))]
234-
quote! {}
239+
{
240+
let span = proc_macro2::Span::call_site();
241+
quote_spanned! { span => }
242+
}
235243
}

lang/syn/src/codegen/program/idl.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
use quote::quote;
1+
use quote::quote_spanned;
22

33
pub fn idl_accounts_and_functions() -> proc_macro2::TokenStream {
4-
quote! {
4+
let span = proc_macro2::Span::call_site();
5+
quote_spanned! { span =>
56
use anchor_lang::idl::ERASED_AUTHORITY;
67

78
#[account("internal")]

lang/syn/src/codegen/program/instruction.rs

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@ use crate::codegen::program::common::*;
22
use crate::parser;
33
use crate::Program;
44
use heck::CamelCase;
5-
use quote::quote;
5+
use quote::quote_spanned;
6+
use syn::spanned::Spanned;
67

78
pub fn generate(program: &Program) -> proc_macro2::TokenStream {
9+
let program_span = program.program_mod.span();
810
let variants: Vec<proc_macro2::TokenStream> = program
911
.ixs
1012
.iter()
@@ -33,8 +35,9 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
3335
_ => gen_discriminator(SIGHASH_GLOBAL_NAMESPACE, name),
3436
},
3537
};
38+
let ix_span = ix.raw_method.span();
3639

37-
quote! {
40+
quote_spanned! { ix_span =>
3841
#(#ix_cfgs)*
3942
impl anchor_lang::Discriminator for #ix_name_camel {
4043
const DISCRIMINATOR: &'static [u8] = #discriminator;
@@ -50,8 +53,9 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
5053
}
5154
};
5255
// If no args, output a "unit" variant instead of a struct variant.
56+
let ix_span = ix.raw_method.span();
5357
if ix.args.is_empty() {
54-
quote! {
58+
quote_spanned! { ix_span =>
5559
#(#ix_cfgs)*
5660
/// Instruction.
5761
#[derive(AnchorSerialize, AnchorDeserialize)]
@@ -60,7 +64,7 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
6064
#impls
6165
}
6266
} else {
63-
quote! {
67+
quote_spanned! { ix_span =>
6468
#(#ix_cfgs)*
6569
/// Instruction.
6670
#[derive(AnchorSerialize, AnchorDeserialize)]
@@ -74,7 +78,7 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
7478
})
7579
.collect();
7680

77-
quote! {
81+
quote_spanned! { program_span =>
7882
/// An Anchor generated module containing the program's set of
7983
/// instructions, where each method handler in the `#[program]` mod is
8084
/// associated with a struct defining the input arguments to the

0 commit comments

Comments
 (0)