lang: Syntax analyzer
This commit is contained in:
parent
512604b85e
commit
de77e29858
|
@ -68,6 +68,7 @@ pub struct Initialize<'info> {
|
|||
pub struct TestOwner<'info> {
|
||||
#[account(owner = misc)]
|
||||
data: AccountInfo<'info>,
|
||||
#[account(address = "asdf")]
|
||||
misc: AccountInfo<'info>,
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
extern crate proc_macro;
|
||||
|
||||
use anchor_syn::analyzer::accounts as accounts_analyzer;
|
||||
use anchor_syn::codegen::accounts as accounts_codegen;
|
||||
use anchor_syn::parser::accounts as accounts_parser;
|
||||
use proc_macro::TokenStream;
|
||||
|
@ -50,11 +51,21 @@ use syn::parse_macro_input;
|
|||
/// | `#[account(state = <target>)]` | On `CpiState` structs | Checks the given state is the canonical state account for the target program. |
|
||||
/// | `#[account(owner = <target>)]` | On `CpiState`, `CpiAccount`, and `AccountInfo` | Checks the account owner matches the target. |
|
||||
/// | `#[account(associated = <target>, with? = <target>, payer? = <target>, space? = "<literal>")]` | On `ProgramAccount` | Creates an associated program account at a program derived address. `associated` is the SOL address to create the account for. `with` is an optional association, for example, a `Mint` account in the SPL token program. `payer` is an optional account to pay for the account creation, defaulting to the `associated` target if none is given. `space` is an optional literal specifying how large the account is, defaulting to the account's serialized `Default::default` size (+ 8 for the account discriminator) if none is given. When creating an associated account, a `rent` `Sysvar` and `system_program` `AccountInfo` must be present in the `Accounts` struct. |
|
||||
/// | `#[account(address = "<literal>")]` | On any type deriving `ToAccountInfo` | Asserts the address of account is the given Rust literal. |
|
||||
/// | `#[account(unsafe)]` | On any type deriving `Accounts` | Opts out of syntax analysis checks. |
|
||||
// TODO: How do we make the markdown render correctly without putting everything
|
||||
// on absurdly long lines?
|
||||
#[proc_macro_derive(Accounts, attributes(account))]
|
||||
pub fn derive_anchor_deserialize(item: TokenStream) -> TokenStream {
|
||||
let strct = parse_macro_input!(item as syn::ItemStruct);
|
||||
let tts = accounts_codegen::generate(accounts_parser::parse(&strct));
|
||||
// Parse the syntax.
|
||||
let unsafe_accounts = accounts_parser::parse(&strct);
|
||||
// Perform syntax analysis.
|
||||
let accounts = match accounts_analyzer::analyze(unsafe_accounts) {
|
||||
Err(err) => panic!("{}", err), // TODO: use compile_error trick.
|
||||
Ok(a) => a,
|
||||
};
|
||||
// Generate the new token stream.
|
||||
let tts = accounts_codegen::generate(accounts);
|
||||
proc_macro::TokenStream::from(tts)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
//! Perform syntax analysis on an accounts struct, providing additional compile
|
||||
//! time guarantees for Anchor programs.
|
||||
|
||||
use crate::{AccountField, AccountsStruct};
|
||||
use thiserror::Error;
|
||||
|
||||
pub fn analyze(accs: AccountsStruct) -> Result<AccountsStruct, AnalyzeError> {
|
||||
CpiOwner::analyze(&accs)?;
|
||||
OwnerRoot::analyze(&accs)?;
|
||||
Ok(accs)
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum AnalyzeError {
|
||||
#[error("Owner not specified on field: {0}")]
|
||||
OwnerNotSpecified(String),
|
||||
}
|
||||
|
||||
trait SyntaxAnalyzer {
|
||||
fn analyze(accs: &AccountsStruct) -> Result<(), AnalyzeError>;
|
||||
}
|
||||
|
||||
// Asserts all cpi accounts have an owner specified.
|
||||
struct CpiOwner;
|
||||
impl SyntaxAnalyzer for CpiOwner {
|
||||
fn analyze(accs: &AccountsStruct) -> Result<(), AnalyzeError> {
|
||||
// TODO
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// Asserts all owners have explicit program ids, or are marked unsafe.
|
||||
struct OwnerRoot;
|
||||
impl SyntaxAnalyzer for OwnerRoot {
|
||||
fn analyze(accs: &AccountsStruct) -> Result<(), AnalyzeError> {
|
||||
// TODO
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
pub mod accounts;
|
|
@ -1,7 +1,8 @@
|
|||
use crate::{
|
||||
AccountField, AccountsStruct, CompositeField, Constraint, ConstraintAssociated,
|
||||
ConstraintBelongsTo, ConstraintExecutable, ConstraintLiteral, ConstraintOwner,
|
||||
ConstraintRentExempt, ConstraintSeeds, ConstraintSigner, ConstraintState, Field, Ty,
|
||||
AccountField, AccountsStruct, CompositeField, Constraint, ConstraintAddress,
|
||||
ConstraintAssociated, ConstraintBelongsTo, ConstraintExecutable, ConstraintLiteral,
|
||||
ConstraintOwner, ConstraintRentExempt, ConstraintSeeds, ConstraintSigner, ConstraintState,
|
||||
ConstraintUnsafe, Field, Ty,
|
||||
};
|
||||
use heck::SnakeCase;
|
||||
use quote::quote;
|
||||
|
@ -380,6 +381,8 @@ pub fn generate_field_constraint(f: &Field, c: &Constraint) -> proc_macro2::Toke
|
|||
Constraint::Executable(c) => generate_constraint_executable(f, c),
|
||||
Constraint::State(c) => generate_constraint_state(f, c),
|
||||
Constraint::Associated(c) => generate_constraint_associated(f, c),
|
||||
Constraint::Address(c) => generate_constraint_address(f, c),
|
||||
Constraint::Unsafe(c) => generate_constraint_unsafe(f, c),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -641,3 +644,17 @@ fn to_seeds_tts(seeds: &[syn::Ident]) -> proc_macro2::TokenStream {
|
|||
}
|
||||
tts
|
||||
}
|
||||
|
||||
pub fn generate_constraint_address(f: &Field, c: &ConstraintAddress) -> proc_macro2::TokenStream {
|
||||
let tokens = &c.tokens;
|
||||
let ident = &f.ident;
|
||||
quote! {
|
||||
if #ident.to_account_info().key != #tokens {
|
||||
return Err(anchor_lang::solana_program::program_error::ProgramError::Custom(1)); // todo: error codes
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn generate_constraint_unsafe(f: &Field, _c: &ConstraintUnsafe) -> proc_macro2::TokenStream {
|
||||
quote! { /* No-op. */}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ use heck::MixedCase;
|
|||
use quote::quote;
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub mod analyzer;
|
||||
pub mod codegen;
|
||||
#[cfg(feature = "hash")]
|
||||
pub mod hash;
|
||||
|
@ -294,6 +295,8 @@ pub enum Constraint {
|
|||
Executable(ConstraintExecutable),
|
||||
State(ConstraintState),
|
||||
Associated(ConstraintAssociated),
|
||||
Address(ConstraintAddress),
|
||||
Unsafe(ConstraintUnsafe),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -338,6 +341,14 @@ pub struct ConstraintAssociated {
|
|||
pub associated_target: proc_macro2::Ident,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ConstraintAddress {
|
||||
pub tokens: proc_macro2::TokenStream,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ConstraintUnsafe {}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Error {
|
||||
pub name: String,
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
use crate::{
|
||||
AccountField, AccountsStruct, CompositeField, Constraint, ConstraintAssociated,
|
||||
ConstraintBelongsTo, ConstraintExecutable, ConstraintLiteral, ConstraintOwner,
|
||||
ConstraintRentExempt, ConstraintSeeds, ConstraintSigner, ConstraintState, CpiAccountTy,
|
||||
CpiStateTy, Field, ProgramAccountTy, ProgramStateTy, SysvarTy, Ty,
|
||||
AccountField, AccountsStruct, CompositeField, Constraint, ConstraintAddress,
|
||||
ConstraintAssociated, ConstraintBelongsTo, ConstraintExecutable, ConstraintLiteral,
|
||||
ConstraintOwner, ConstraintRentExempt, ConstraintSeeds, ConstraintSigner, ConstraintState,
|
||||
ConstraintUnsafe, CpiAccountTy, CpiStateTy, Field, ProgramAccountTy, ProgramStateTy, SysvarTy,
|
||||
Ty,
|
||||
};
|
||||
|
||||
pub fn parse(strct: &syn::ItemStruct) -> AccountsStruct {
|
||||
|
@ -224,6 +225,9 @@ fn parse_constraints(
|
|||
is_signer = true;
|
||||
constraints.push(Constraint::Signer(ConstraintSigner {}));
|
||||
}
|
||||
"unsafe" => {
|
||||
constraints.push(Constraint::Unsafe(ConstraintUnsafe {}));
|
||||
}
|
||||
"seeds" => {
|
||||
match inner_tts.next().unwrap() {
|
||||
proc_macro2::TokenTree::Punct(punct) => {
|
||||
|
@ -369,6 +373,22 @@ fn parse_constraints(
|
|||
_ => panic!("invalid space"),
|
||||
}
|
||||
}
|
||||
"address" => {
|
||||
match inner_tts.next().unwrap() {
|
||||
proc_macro2::TokenTree::Punct(punct) => {
|
||||
assert!(punct.as_char() == '=');
|
||||
punct
|
||||
}
|
||||
_ => panic!("invalid syntax"),
|
||||
};
|
||||
let tokens = match inner_tts.next().unwrap() {
|
||||
proc_macro2::TokenTree::Literal(literal) => {
|
||||
literal.to_string().replace("\"", "").parse().unwrap()
|
||||
}
|
||||
_ => panic!("invalid address syntax"),
|
||||
};
|
||||
constraints.push(Constraint::Address(ConstraintAddress { tokens }));
|
||||
}
|
||||
_ => {
|
||||
panic!("invalid syntax");
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue