Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 11 additions & 4 deletions lang/syn/src/codegen/program/accounts.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
use crate::Program;
use heck::SnakeCase;
use quote::quote;
use quote::{quote, quote_spanned};
use syn::spanned::Spanned;

pub fn generate(program: &Program) -> proc_macro2::TokenStream {
let mut accounts = std::collections::HashMap::new();
let mut ix_spans = std::collections::HashMap::new();

// Go through instruction accounts.
for ix in &program.ixs {
Expand All @@ -13,15 +15,20 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
"__client_accounts_{}",
anchor_ident.to_string().to_snake_case()
);
ix_spans.insert(macro_name.clone(), ix.raw_method.span());
accounts.insert(macro_name, ix.cfgs.as_slice());
}

// Build the tokens from all accounts
let account_structs: Vec<proc_macro2::TokenStream> = accounts
.iter()
.map(|(macro_name, cfgs)| {
let macro_name: proc_macro2::TokenStream = macro_name.parse().unwrap();
quote! {
.map(|(macro_name_str, cfgs)| {
let macro_name: proc_macro2::TokenStream = macro_name_str.parse().unwrap();
let ix_span = ix_spans
.get(macro_name_str)
.copied()
.unwrap_or_else(proc_macro2::Span::call_site);
quote_spanned! { ix_span =>
#(#cfgs)*
pub use crate::#macro_name::*;
}
Expand Down
28 changes: 20 additions & 8 deletions lang/syn/src/codegen/program/common.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::IxArg;
use anyhow::Result;
use heck::CamelCase;
use quote::quote;
use quote::{quote, quote_spanned};

// Namespace for calculating instruction sighash signatures for any instruction
// not affecting program state.
Expand All @@ -24,24 +24,36 @@ pub fn gen_discriminator(namespace: &str, name: impl ToString) -> proc_macro2::T
format!("&{discriminator:?}").parse().unwrap()
}

pub fn generate_ix_variant(name: &str, args: &[IxArg]) -> Result<proc_macro2::TokenStream> {
pub fn generate_ix_variant_spanned(
name: &str,
args: &[IxArg],
span: proc_macro2::Span,
) -> proc_macro2::TokenStream {
let ix_arg_names: Vec<&syn::Ident> = args.iter().map(|arg| &arg.name).collect();
let ix_name_camel = generate_ix_variant_name(name)?;
let ix_name_camel = generate_ix_variant_name_spanned(name, span);

let variant = if args.is_empty() {
quote! {
if args.is_empty() {
quote_spanned! { span =>
#ix_name_camel
}
} else {
quote! {
quote_spanned! { span =>
#ix_name_camel {
#(#ix_arg_names),*
}
}
};
Ok(variant)
}
}

pub fn generate_ix_variant_name(name: &str) -> Result<syn::Ident> {
Ok(syn::parse_str(&name.to_camel_case())?)
}

pub fn generate_ix_variant_name_spanned(
name: &str,
span: proc_macro2::Span,
) -> proc_macro2::TokenStream {
let n = name.to_camel_case();
let ident = proc_macro2::Ident::new(&n, span);
quote! { #ident }
}
30 changes: 13 additions & 17 deletions lang/syn/src/codegen/program/cpi.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
use crate::codegen::program::common::{generate_ix_variant, generate_ix_variant_name};
use crate::codegen::program::common::{
generate_ix_variant_name_spanned, generate_ix_variant_spanned,
};
use crate::Program;
use heck::SnakeCase;
use quote::{quote, ToTokens};
use quote::{quote, quote_spanned, ToTokens};
use syn::spanned::Spanned;

pub fn generate(program: &Program) -> proc_macro2::TokenStream {
// Generate cpi methods for global methods.
Expand All @@ -13,35 +16,28 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
let cpi_method = {
let name = &ix.raw_method.sig.ident;
let name_str = name.to_string();
let ix_variant = match generate_ix_variant(&name_str, &ix.args) {
Ok(v) => v,
Err(e) => {
let err = e.to_string();
return quote! { compile_error!(concat!("error generating ix variant: `", #err, "`")) };
}
};
let ix_span = ix.raw_method.span();
let ix_variant = generate_ix_variant_spanned(&name_str, &ix.args, ix_span);
let method_name = &ix.ident;
let args: Vec<&syn::PatType> = ix.args.iter().map(|arg| &arg.raw_arg).collect();
let discriminator = match generate_ix_variant_name(&name_str) {
Ok(name) => quote! { <instruction::#name as anchor_lang::Discriminator>::DISCRIMINATOR },
Err(e) => {
let err = e.to_string();
return quote! { compile_error!(concat!("error generating ix variant name: `", #err, "`")) };
}
let discriminator = {
let name = generate_ix_variant_name_spanned(&name_str, ix_span);
quote_spanned! { ix_span => <instruction::#name as anchor_lang::Discriminator>::DISCRIMINATOR }
};
let ret_type = &ix.returns.ty.to_token_stream();
let ix_cfgs = &ix.cfgs;
let (method_ret, maybe_return) = match ret_type.to_string().as_str() {
"()" => (quote! {anchor_lang::Result<()> }, quote! { Ok(()) }),
"()" => (quote! { anchor_lang::Result<()> }, quote! { Ok(()) }),
_ => (
quote! { anchor_lang::Result<crate::cpi::Return::<#ret_type>> },
quote! { Ok(crate::cpi::Return::<#ret_type> { phantom: crate::cpi::PhantomData }) }
)
};
let spanned_method_name = quote_spanned! { ix_span => #method_name };

quote! {
#(#ix_cfgs)*
pub fn #method_name<'a, 'b, 'c, 'info>(
pub fn #spanned_method_name<'a, 'b, 'c, 'info>(
ctx: anchor_lang::context::CpiContext<'a, 'b, 'c, 'info, #accounts_ident<'info>>,
#(#args),*
) -> #method_ret {
Expand Down
11 changes: 8 additions & 3 deletions lang/syn/src/codegen/program/dispatch.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use crate::Program;
use heck::CamelCase;
use quote::quote;
use quote::{quote, quote_spanned};
use syn::spanned::Spanned;

pub fn generate(program: &Program) -> proc_macro2::TokenStream {
// Dispatch all global instructions.
Expand All @@ -11,13 +12,15 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
.to_camel_case()
.parse()
.expect("Failed to parse ix method name in camel as `TokenStream`");
let ix_span = ix.raw_method.span();
let discriminator = quote! { instruction::#ix_name_camel::DISCRIMINATOR };
let spanned_method_name = quote_spanned! { ix_span => #ix_method_name };
let ix_cfgs = &ix.cfgs;

quote! {
#(#ix_cfgs)*
if data.starts_with(#discriminator) {
return __private::__global::#ix_method_name(
return __private::__global::#spanned_method_name(
program_id,
accounts,
&data[#discriminator.len()..],
Expand Down Expand Up @@ -50,8 +53,10 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
.map(|fallback_fn| {
let program_name = &program.name;
let fn_name = &fallback_fn.raw_method.sig.ident;
let fallback_span = fallback_fn.raw_method.span();
let spanned_fn_name = quote_spanned! { fallback_span => #fn_name };
quote! {
#program_name::#fn_name(program_id, accounts, data)
#program_name::#spanned_fn_name(program_id, accounts, data)
}
})
.unwrap_or_else(|| {
Expand Down
79 changes: 9 additions & 70 deletions lang/syn/src/codegen/program/handlers.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use crate::codegen::program::common::*;
use crate::Program;
use quote::{quote, ToTokens};
use quote::{quote, quote_spanned, ToTokens};
use syn::spanned::Spanned;

// Generate non-inlined wrappers for each instruction handler, since Solana's
// BPF max stack size can't handle reasonable sized dispatch trees without doing
Expand All @@ -17,25 +18,15 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
let ix_arg_names: Vec<&syn::Ident> = ix.args.iter().map(|arg| &arg.name).collect();
let ix_method_name = &ix.raw_method.sig.ident;
let ix_method_name_str = ix_method_name.to_string();
let ix_name = match generate_ix_variant_name(&ix_method_name_str) {
Ok(name) => quote! { #name },
Err(e) => {
let err = e.to_string();
return quote! { compile_error!(concat!("error generating ix variant name: `", #err, "`")) };
}
};
let variant_arm = match generate_ix_variant(&ix_method_name_str, &ix.args) {
Ok(v) => v,
Err(e) => {
let err = e.to_string();
return quote! { compile_error!(concat!("error generating ix variant arm: `", #err, "`")) };
}
};

let ix_span = ix.raw_method.span();
let ix_name = generate_ix_variant_name(&ix_method_name_str)
.expect("Failed to parse ix method name as camelCase identifier");
let variant_arm = generate_ix_variant_spanned(&ix_method_name_str, &ix.args, ix_span);
let ix_name_log = format!("Instruction: {ix_name}");
let anchor = &ix.anchor_ident;
let ret_type = &ix.returns.ty.to_token_stream();
let cfgs = &ix.cfgs;
let ix_span = ix.raw_method.span();
let maybe_set_return_data = match ret_type.to_string().as_str() {
"()" => quote! {},
_ => quote! {
Expand All @@ -44,70 +35,18 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
anchor_lang::solana_program::program::set_return_data(&return_data);
},
};

let actual_param_count = ix.args.len();
let ix_name_str = ix_method_name.to_string();
let accounts_type_str = anchor.to_string();

// Build clear error messages
let count_error_msg = format!(
"#[instruction(...)] on Account `{}<'_>` expects MORE args, the ix `{}(...)` has only {} args.",
accounts_type_str,
ix_name_str,
actual_param_count,
);

// Generate type validation calls for each argument
let type_validations: Vec<proc_macro2::TokenStream> = ix.args
.iter()
.enumerate()
.map(|(idx, arg)| {
let arg_ty = &arg.raw_arg.ty;
let method_name = syn::Ident::new(
&format!("__anchor_validate_ix_arg_type_{}", idx),
proc_macro2::Span::call_site(),
);
quote! {
// Type validation for argument #idx
if #anchor::__ANCHOR_IX_PARAM_COUNT > #idx {
#[allow(unreachable_code)]
if false {
// This code is never executed but is type-checked at compile time
let __type_check_arg: #arg_ty = panic!();
#anchor::#method_name(&__type_check_arg);
}
}
}
})
.collect();

let param_validation = quote! {
const _: () = {
const EXPECTED_COUNT: usize = #anchor::__ANCHOR_IX_PARAM_COUNT;
const HANDLER_PARAM_COUNT: usize = #actual_param_count;

// Count validation
if EXPECTED_COUNT > HANDLER_PARAM_COUNT {
panic!(#count_error_msg);
}
};

// Type validations
#(#type_validations)*
};

let spanned_fn_name = quote_spanned! { ix_span => #ix_method_name };
quote! {
#(#cfgs)*
#[inline(never)]
pub fn #ix_method_name<'info>(
pub fn #spanned_fn_name<'info>(
__program_id: &Pubkey,
__accounts: &'info[AccountInfo<'info>],
__ix_data: &[u8],
) -> anchor_lang::Result<()> {
#[cfg(not(feature = "no-log-ix-name"))]
anchor_lang::prelude::msg!(#ix_name_log);

#param_validation
// Deserialize data.
let ix = instruction::#ix_name::deserialize(&mut &__ix_data[..])
.map_err(|_| anchor_lang::error::ErrorCode::InstructionDidNotDeserialize)?;
Expand Down
15 changes: 10 additions & 5 deletions lang/syn/src/codegen/program/instruction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use crate::parser;
use crate::Program;
use heck::CamelCase;
use quote::{quote, quote_spanned};
use syn::spanned::Spanned;

pub fn generate(program: &Program) -> proc_macro2::TokenStream {
let variants: Vec<proc_macro2::TokenStream> = program
Expand Down Expand Up @@ -36,29 +37,33 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
_ => gen_discriminator(SIGHASH_GLOBAL_NAMESPACE, name),
},
};
let ix_span = ix.raw_method.span();
let spanned_name = quote_spanned! { ix_span => #ix_name_camel };

quote! {
#(#ix_cfgs)*
impl anchor_lang::Discriminator for #ix_name_camel {
impl anchor_lang::Discriminator for #spanned_name {
const DISCRIMINATOR: &'static [u8] = #discriminator;
}
#(#ix_cfgs)*
impl anchor_lang::InstructionData for #ix_name_camel {}
impl anchor_lang::InstructionData for #spanned_name {}
#(#ix_cfgs)*
impl anchor_lang::Owner for #ix_name_camel {
impl anchor_lang::Owner for #spanned_name {
fn owner() -> Pubkey {
ID
}
}
}
};
// If no args, output a "unit" variant instead of a struct variant.
let ix_span = ix.raw_method.span();
let spanned_name = quote_spanned! { ix_span => #ix_name_camel };
if ix.args.is_empty() {
quote! {
#(#ix_cfgs)*
/// Instruction.
#[derive(AnchorSerialize, AnchorDeserialize)]
pub struct #ix_name_camel;
pub struct #spanned_name;

#impls
}
Expand All @@ -67,7 +72,7 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
#(#ix_cfgs)*
/// Instruction.
#[derive(AnchorSerialize, AnchorDeserialize)]
pub struct #ix_name_camel {
pub struct #spanned_name {
#(#raw_args),*
}

Expand Down
Loading
Loading