Przeglądaj źródła

lang/syn: Parse entire crate for IDL (#517)

suscd 4 lat temu
rodzic
commit
f34d0730db

+ 1 - 0
CHANGELOG.md

@@ -18,6 +18,7 @@ incremented for features.
 * lang: Adds `require` macro for specifying assertions that return error codes on failure ([#483](https://github.com/project-serum/anchor/pull/483)).
 * lang: Allow one to specify arbitrary programs as the owner when creating PDA ([#483](https://github.com/project-serum/anchor/pull/483)).
 * lang: A new `bump` keyword is added to the accounts constraints, which is used to add an optional bump seed to the end of a `seeds` array. When used in conjunction with *both* `init` and `seeds`, then the program executes `find_program_address` to assert that the given bump is the canonical bump ([#483](https://github.com/project-serum/anchor/pull/483)).
+* lang: IDLs are now parsed from the entire crate ([#517](https://github.com/project-serum/anchor/pull/517)).
 
 ### Fixes
 

+ 36 - 0
examples/misc/programs/misc/src/account.rs

@@ -0,0 +1,36 @@
+use anchor_lang::prelude::*;
+
+#[associated]
+#[derive(Default)]
+pub struct TestData {
+    pub data: u64,
+}
+
+#[account]
+pub struct Data {
+    pub udata: u128,
+    pub idata: i128,
+}
+
+#[account]
+#[derive(Default)]
+pub struct DataU16 {
+    pub data: u16,
+}
+
+#[account]
+pub struct DataI8 {
+    pub data: i8,
+}
+
+#[account]
+pub struct DataI16 {
+    pub data: i16,
+}
+
+#[account(zero_copy)]
+#[derive(Default)]
+pub struct DataZeroCopy {
+    pub data: u16,
+    pub bump: u8,
+}

+ 172 - 0
examples/misc/programs/misc/src/context.rs

@@ -0,0 +1,172 @@
+use crate::account::*;
+use crate::misc::MyState;
+use anchor_lang::prelude::*;
+use anchor_spl::token::{Mint, TokenAccount};
+use misc2::misc2::MyState as Misc2State;
+
+#[derive(Accounts)]
+#[instruction(nonce: u8)]
+pub struct TestTokenSeedsInit<'info> {
+    #[account(
+        init,
+        token = mint,
+        authority = authority,
+        seeds = [b"my-token-seed".as_ref(), &[nonce]],
+        payer = authority,
+        space = TokenAccount::LEN,
+    )]
+    pub my_pda: CpiAccount<'info, TokenAccount>,
+    pub mint: CpiAccount<'info, Mint>,
+    pub authority: AccountInfo<'info>,
+    pub system_program: AccountInfo<'info>,
+    pub rent: Sysvar<'info, Rent>,
+    pub token_program: AccountInfo<'info>,
+}
+
+#[derive(Accounts)]
+#[instruction(nonce: u8)]
+pub struct TestInstructionConstraint<'info> {
+    #[account(seeds = [b"my-seed", my_account.key.as_ref(), &[nonce]])]
+    pub my_pda: AccountInfo<'info>,
+    pub my_account: AccountInfo<'info>,
+}
+
+#[derive(Accounts)]
+#[instruction(domain: String, seed: Vec<u8>, bump: u8)]
+pub struct TestPdaInit<'info> {
+    #[account(
+        init,
+        seeds = [b"my-seed", domain.as_bytes(), foo.key.as_ref(), &seed, &[bump]],
+        payer = my_payer,
+    )]
+    pub my_pda: ProgramAccount<'info, DataU16>,
+    pub my_payer: AccountInfo<'info>,
+    pub foo: AccountInfo<'info>,
+    pub rent: Sysvar<'info, Rent>,
+    pub system_program: AccountInfo<'info>,
+}
+
+#[derive(Accounts)]
+#[instruction(bump: u8)]
+pub struct TestPdaInitZeroCopy<'info> {
+    #[account(init, seeds = [b"my-seed".as_ref(), &[bump]], payer = my_payer)]
+    pub my_pda: Loader<'info, DataZeroCopy>,
+    pub my_payer: AccountInfo<'info>,
+    pub rent: Sysvar<'info, Rent>,
+    pub system_program: AccountInfo<'info>,
+}
+
+#[derive(Accounts)]
+pub struct TestPdaMutZeroCopy<'info> {
+    #[account(mut, seeds = [b"my-seed".as_ref(), &[my_pda.load()?.bump]])]
+    pub my_pda: Loader<'info, DataZeroCopy>,
+    pub my_payer: AccountInfo<'info>,
+}
+
+#[derive(Accounts)]
+pub struct Ctor {}
+
+#[derive(Accounts)]
+pub struct RemainingAccounts {}
+
+#[derive(Accounts)]
+pub struct Initialize<'info> {
+    #[account(init)]
+    pub data: ProgramAccount<'info, Data>,
+    pub rent: Sysvar<'info, Rent>,
+}
+
+#[derive(Accounts)]
+pub struct TestOwner<'info> {
+    #[account(owner = misc)]
+    pub data: AccountInfo<'info>,
+    pub misc: AccountInfo<'info>,
+}
+
+#[derive(Accounts)]
+pub struct TestExecutable<'info> {
+    #[account(executable)]
+    pub program: AccountInfo<'info>,
+}
+
+#[derive(Accounts)]
+pub struct TestStateCpi<'info> {
+    #[account(signer)]
+    pub authority: AccountInfo<'info>,
+    #[account(mut, state = misc2_program)]
+    pub cpi_state: CpiState<'info, Misc2State>,
+    #[account(executable)]
+    pub misc2_program: AccountInfo<'info>,
+}
+
+#[derive(Accounts)]
+pub struct TestClose<'info> {
+    #[account(mut, close = sol_dest)]
+    pub data: ProgramAccount<'info, Data>,
+    sol_dest: AccountInfo<'info>,
+}
+
+// `my_account` is the associated token account being created.
+// `authority` must be a `mut` and `signer` since it will pay for the creation
+// of the associated token account. `state` is used as an association, i.e., one
+// can *optionally* identify targets to be used as seeds for the program
+// derived address by using `with` (and it doesn't have to be a state account).
+// For example, the SPL token program uses a `Mint` account. Lastly,
+// `rent` and `system_program` are *required* by convention, since the
+// accounts are needed when creating the associated program address within
+// the program.
+#[derive(Accounts)]
+pub struct TestInitAssociatedAccount<'info> {
+    #[account(init, associated = authority, with = state, with = data, with = b"my-seed")]
+    pub my_account: ProgramAccount<'info, TestData>,
+    #[account(mut, signer)]
+    pub authority: AccountInfo<'info>,
+    pub state: ProgramState<'info, MyState>,
+    pub data: ProgramAccount<'info, Data>,
+    pub rent: Sysvar<'info, Rent>,
+    pub system_program: AccountInfo<'info>,
+}
+
+#[derive(Accounts)]
+pub struct TestAssociatedAccount<'info> {
+    #[account(mut, associated = authority, with = state, with = data, with = b"my-seed")]
+    pub my_account: ProgramAccount<'info, TestData>,
+    #[account(mut, signer)]
+    pub authority: AccountInfo<'info>,
+    pub state: ProgramState<'info, MyState>,
+    pub data: ProgramAccount<'info, Data>,
+}
+
+#[derive(Accounts)]
+pub struct TestU16<'info> {
+    #[account(init)]
+    pub my_account: ProgramAccount<'info, DataU16>,
+    pub rent: Sysvar<'info, Rent>,
+}
+
+#[derive(Accounts)]
+pub struct TestI16<'info> {
+    #[account(init)]
+    pub data: ProgramAccount<'info, DataI16>,
+    pub rent: Sysvar<'info, Rent>,
+}
+
+#[derive(Accounts)]
+pub struct TestSimulate {}
+
+#[derive(Accounts)]
+pub struct TestSimulateAssociatedAccount<'info> {
+    #[account(init, associated = authority)]
+    pub my_account: ProgramAccount<'info, TestData>,
+    #[account(mut, signer)]
+    pub authority: AccountInfo<'info>,
+    pub rent: Sysvar<'info, Rent>,
+    pub system_program: AccountInfo<'info>,
+}
+
+#[derive(Accounts)]
+pub struct TestI8<'info> {
+    #[account(init)]
+    pub data: ProgramAccount<'info, DataI8>,
+    pub rent: Sysvar<'info, Rent>,
+}

+ 21 - 0
examples/misc/programs/misc/src/event.rs

@@ -0,0 +1,21 @@
+use anchor_lang::prelude::*;
+
+#[event]
+pub struct E1 {
+    pub data: u32,
+}
+
+#[event]
+pub struct E2 {
+    pub data: u32,
+}
+
+#[event]
+pub struct E3 {
+    pub data: u32,
+}
+
+#[event]
+pub struct E4 {
+    pub data: Pubkey,
+}

+ 6 - 224
examples/misc/programs/misc/src/lib.rs

@@ -2,10 +2,14 @@
 //! It's not too instructive/coherent by itself, so please see other examples.
 
 use anchor_lang::prelude::*;
-use anchor_spl::token::{Mint, TokenAccount};
-use misc2::misc2::MyState as Misc2State;
+use context::*;
+use event::*;
 use misc2::Auth;
 
+mod account;
+mod context;
+mod event;
+
 #[program]
 pub mod misc {
     use super::*;
@@ -151,225 +155,3 @@ pub mod misc {
         Err(ProgramError::Custom(1234))
     }
 }
-
-#[derive(Accounts)]
-#[instruction(nonce: u8)]
-pub struct TestTokenSeedsInit<'info> {
-    #[account(
-        init,
-        token = mint,
-        authority = authority,
-        seeds = [b"my-token-seed".as_ref(), &[nonce]],
-        payer = authority,
-        space = TokenAccount::LEN,
-    )]
-    pub my_pda: CpiAccount<'info, TokenAccount>,
-    pub mint: CpiAccount<'info, Mint>,
-    pub authority: AccountInfo<'info>,
-    pub system_program: AccountInfo<'info>,
-    pub rent: Sysvar<'info, Rent>,
-    pub token_program: AccountInfo<'info>,
-}
-
-#[derive(Accounts)]
-#[instruction(nonce: u8)]
-pub struct TestInstructionConstraint<'info> {
-    #[account(seeds = [b"my-seed", my_account.key.as_ref(), &[nonce]])]
-    pub my_pda: AccountInfo<'info>,
-    pub my_account: AccountInfo<'info>,
-}
-
-#[derive(Accounts)]
-#[instruction(domain: String, seed: Vec<u8>, bump: u8)]
-pub struct TestPdaInit<'info> {
-    #[account(
-        init,
-        seeds = [b"my-seed", domain.as_bytes(), foo.key.as_ref(), &seed, &[bump]],
-        payer = my_payer,
-    )]
-    my_pda: ProgramAccount<'info, DataU16>,
-    my_payer: AccountInfo<'info>,
-    foo: AccountInfo<'info>,
-    rent: Sysvar<'info, Rent>,
-    system_program: AccountInfo<'info>,
-}
-
-#[derive(Accounts)]
-#[instruction(bump: u8)]
-pub struct TestPdaInitZeroCopy<'info> {
-    #[account(init, seeds = [b"my-seed".as_ref(), &[bump]], payer = my_payer)]
-    my_pda: Loader<'info, DataZeroCopy>,
-    my_payer: AccountInfo<'info>,
-    rent: Sysvar<'info, Rent>,
-    system_program: AccountInfo<'info>,
-}
-
-#[derive(Accounts)]
-pub struct TestPdaMutZeroCopy<'info> {
-    #[account(mut, seeds = [b"my-seed".as_ref(), &[my_pda.load()?.bump]])]
-    my_pda: Loader<'info, DataZeroCopy>,
-    my_payer: AccountInfo<'info>,
-}
-
-#[derive(Accounts)]
-pub struct Ctor {}
-
-#[derive(Accounts)]
-pub struct RemainingAccounts {}
-
-#[derive(Accounts)]
-pub struct Initialize<'info> {
-    #[account(init)]
-    data: ProgramAccount<'info, Data>,
-    rent: Sysvar<'info, Rent>,
-}
-
-#[derive(Accounts)]
-pub struct TestOwner<'info> {
-    #[account(owner = misc)]
-    data: AccountInfo<'info>,
-    misc: AccountInfo<'info>,
-}
-
-#[derive(Accounts)]
-pub struct TestExecutable<'info> {
-    #[account(executable)]
-    program: AccountInfo<'info>,
-}
-
-#[derive(Accounts)]
-pub struct TestStateCpi<'info> {
-    #[account(signer)]
-    authority: AccountInfo<'info>,
-    #[account(mut, state = misc2_program)]
-    cpi_state: CpiState<'info, Misc2State>,
-    #[account(executable)]
-    misc2_program: AccountInfo<'info>,
-}
-
-#[derive(Accounts)]
-pub struct TestClose<'info> {
-    #[account(mut, close = sol_dest)]
-    data: ProgramAccount<'info, Data>,
-    sol_dest: AccountInfo<'info>,
-}
-
-// `my_account` is the associated token account being created.
-// `authority` must be a `mut` and `signer` since it will pay for the creation
-// of the associated token account. `state` is used as an association, i.e., one
-// can *optionally* identify targets to be used as seeds for the program
-// derived address by using `with` (and it doesn't have to be a state account).
-// For example, the SPL token program uses a `Mint` account. Lastly,
-// `rent` and `system_program` are *required* by convention, since the
-// accounts are needed when creating the associated program address within
-// the program.
-#[derive(Accounts)]
-pub struct TestInitAssociatedAccount<'info> {
-    #[account(init, associated = authority, with = state, with = data, with = b"my-seed")]
-    my_account: ProgramAccount<'info, TestData>,
-    #[account(mut, signer)]
-    authority: AccountInfo<'info>,
-    state: ProgramState<'info, MyState>,
-    data: ProgramAccount<'info, Data>,
-    rent: Sysvar<'info, Rent>,
-    system_program: AccountInfo<'info>,
-}
-
-#[derive(Accounts)]
-pub struct TestAssociatedAccount<'info> {
-    #[account(mut, associated = authority, with = state, with = data, with = b"my-seed")]
-    my_account: ProgramAccount<'info, TestData>,
-    #[account(mut, signer)]
-    authority: AccountInfo<'info>,
-    state: ProgramState<'info, MyState>,
-    data: ProgramAccount<'info, Data>,
-}
-
-#[derive(Accounts)]
-pub struct TestU16<'info> {
-    #[account(init)]
-    my_account: ProgramAccount<'info, DataU16>,
-    rent: Sysvar<'info, Rent>,
-}
-
-#[derive(Accounts)]
-pub struct TestI16<'info> {
-    #[account(init)]
-    data: ProgramAccount<'info, DataI16>,
-    rent: Sysvar<'info, Rent>,
-}
-
-#[derive(Accounts)]
-pub struct TestSimulate {}
-
-#[derive(Accounts)]
-pub struct TestSimulateAssociatedAccount<'info> {
-    #[account(init, associated = authority)]
-    my_account: ProgramAccount<'info, TestData>,
-    #[account(mut, signer)]
-    authority: AccountInfo<'info>,
-    rent: Sysvar<'info, Rent>,
-    system_program: AccountInfo<'info>,
-}
-
-#[derive(Accounts)]
-pub struct TestI8<'info> {
-    #[account(init)]
-    data: ProgramAccount<'info, DataI8>,
-    rent: Sysvar<'info, Rent>,
-}
-
-#[associated]
-#[derive(Default)]
-pub struct TestData {
-    data: u64,
-}
-
-#[account]
-pub struct Data {
-    udata: u128,
-    idata: i128,
-}
-
-#[account]
-#[derive(Default)]
-pub struct DataU16 {
-    data: u16,
-}
-
-#[account]
-pub struct DataI8 {
-    data: i8,
-}
-
-#[account]
-pub struct DataI16 {
-    data: i16,
-}
-
-#[account(zero_copy)]
-#[derive(Default)]
-pub struct DataZeroCopy {
-    data: u16,
-    bump: u8,
-}
-
-#[event]
-pub struct E1 {
-    data: u32,
-}
-
-#[event]
-pub struct E2 {
-    data: u32,
-}
-
-#[event]
-pub struct E3 {
-    data: u32,
-}
-
-#[event]
-pub struct E4 {
-    data: Pubkey,
-}

+ 145 - 156
lang/syn/src/idl/file.rs

@@ -1,12 +1,11 @@
 use crate::idl::*;
+use crate::parser::context::CrateContext;
 use crate::parser::{self, accounts, error, program};
 use crate::{AccountField, AccountsStruct, StateIx};
 use anyhow::Result;
 use heck::MixedCase;
 use quote::ToTokens;
 use std::collections::{HashMap, HashSet};
-use std::fs::File;
-use std::io::Read;
 use std::path::Path;
 
 const DERIVE_NAME: &str = "Accounts";
@@ -15,16 +14,11 @@ const ERROR_CODE_OFFSET: u32 = 300;
 
 // Parse an entire interface file.
 pub fn parse(filename: impl AsRef<Path>) -> Result<Idl> {
-    let mut file = File::open(&filename)?;
+    let ctx = CrateContext::parse(filename)?;
 
-    let mut src = String::new();
-    file.read_to_string(&mut src).expect("Unable to read file");
+    let p = program::parse(parse_program_mod(&ctx))?;
 
-    let f = syn::parse_file(&src).expect("Unable to parse file");
-
-    let p = program::parse(parse_program_mod(&f))?;
-
-    let accs = parse_account_derives(&f);
+    let accs = parse_account_derives(&ctx);
 
     let state = match p.state {
         None => None,
@@ -129,7 +123,7 @@ pub fn parse(filename: impl AsRef<Path>) -> Result<Idl> {
             }
         },
     };
-    let error = parse_error_enum(&f).map(|mut e| error::parse(&mut e, None));
+    let error = parse_error_enum(&ctx).map(|mut e| error::parse(&mut e, None));
     let error_codes = error.as_ref().map(|e| {
         e.codes
             .iter()
@@ -169,7 +163,7 @@ pub fn parse(filename: impl AsRef<Path>) -> Result<Idl> {
         })
         .collect::<Vec<_>>();
 
-    let events = parse_events(&f)
+    let events = parse_events(&ctx)
         .iter()
         .map(|e: &&syn::ItemStruct| {
             let fields = match &e.fields {
@@ -202,9 +196,9 @@ pub fn parse(filename: impl AsRef<Path>) -> Result<Idl> {
     // All user defined types.
     let mut accounts = vec![];
     let mut types = vec![];
-    let ty_defs = parse_ty_defs(&f)?;
+    let ty_defs = parse_ty_defs(&ctx)?;
 
-    let account_structs = parse_accounts(&f);
+    let account_structs = parse_accounts(&ctx);
     let account_names: HashSet<String> = account_structs
         .iter()
         .map(|a| a.ident.to_string())
@@ -242,10 +236,10 @@ pub fn parse(filename: impl AsRef<Path>) -> Result<Idl> {
 }
 
 // Parse the main program mod.
-fn parse_program_mod(f: &syn::File) -> syn::ItemMod {
-    let mods = f
-        .items
-        .iter()
+fn parse_program_mod(ctx: &CrateContext) -> syn::ItemMod {
+    let root = ctx.root_module();
+    let mods = root
+        .items()
         .filter_map(|i| match i {
             syn::Item::Mod(item_mod) => {
                 let mod_count = item_mod
@@ -267,173 +261,168 @@ fn parse_program_mod(f: &syn::File) -> syn::ItemMod {
     mods[0].clone()
 }
 
-fn parse_error_enum(f: &syn::File) -> Option<syn::ItemEnum> {
-    f.items
-        .iter()
-        .filter_map(|i| match i {
-            syn::Item::Enum(item_enum) => {
-                let attrs_count = item_enum
-                    .attrs
-                    .iter()
-                    .filter(|attr| {
-                        let segment = attr.path.segments.last().unwrap();
-                        segment.ident == "error"
-                    })
-                    .count();
-                match attrs_count {
-                    0 => None,
-                    1 => Some(item_enum),
-                    _ => panic!("Invalid syntax: one error attribute allowed"),
-                }
+fn parse_error_enum(ctx: &CrateContext) -> Option<syn::ItemEnum> {
+    ctx.enums()
+        .filter_map(|item_enum| {
+            let attrs_count = item_enum
+                .attrs
+                .iter()
+                .filter(|attr| {
+                    let segment = attr.path.segments.last().unwrap();
+                    segment.ident == "error"
+                })
+                .count();
+            match attrs_count {
+                0 => None,
+                1 => Some(item_enum),
+                _ => panic!("Invalid syntax: one error attribute allowed"),
             }
-            _ => None,
         })
         .next()
         .cloned()
 }
 
-fn parse_events(f: &syn::File) -> Vec<&syn::ItemStruct> {
-    f.items
-        .iter()
-        .filter_map(|i| match i {
-            syn::Item::Struct(item_strct) => {
-                let attrs_count = item_strct
-                    .attrs
-                    .iter()
-                    .filter(|attr| {
-                        let segment = attr.path.segments.last().unwrap();
-                        segment.ident == "event"
-                    })
-                    .count();
-                match attrs_count {
-                    0 => None,
-                    1 => Some(item_strct),
-                    _ => panic!("Invalid syntax: one event attribute allowed"),
-                }
+fn parse_events(ctx: &CrateContext) -> Vec<&syn::ItemStruct> {
+    ctx.structs()
+        .filter_map(|item_strct| {
+            let attrs_count = item_strct
+                .attrs
+                .iter()
+                .filter(|attr| {
+                    let segment = attr.path.segments.last().unwrap();
+                    segment.ident == "event"
+                })
+                .count();
+            match attrs_count {
+                0 => None,
+                1 => Some(item_strct),
+                _ => panic!("Invalid syntax: one event attribute allowed"),
             }
-            _ => None,
         })
         .collect()
 }
 
-fn parse_accounts(f: &syn::File) -> Vec<&syn::ItemStruct> {
-    f.items
-        .iter()
-        .filter_map(|i| match i {
-            syn::Item::Struct(item_strct) => {
-                let attrs_count = item_strct
-                    .attrs
-                    .iter()
-                    .filter(|attr| {
-                        let segment = attr.path.segments.last().unwrap();
-                        segment.ident == "account" || segment.ident == "associated"
-                    })
-                    .count();
-                match attrs_count {
-                    0 => None,
-                    1 => Some(item_strct),
-                    _ => panic!("Invalid syntax: one event attribute allowed"),
-                }
+fn parse_accounts(ctx: &CrateContext) -> Vec<&syn::ItemStruct> {
+    ctx.structs()
+        .filter_map(|item_strct| {
+            let attrs_count = item_strct
+                .attrs
+                .iter()
+                .filter(|attr| {
+                    let segment = attr.path.segments.last().unwrap();
+                    segment.ident == "account" || segment.ident == "associated"
+                })
+                .count();
+            match attrs_count {
+                0 => None,
+                1 => Some(item_strct),
+                _ => panic!("Invalid syntax: one event attribute allowed"),
             }
-            _ => None,
         })
         .collect()
 }
 
 // Parse all structs implementing the `Accounts` trait.
-fn parse_account_derives(f: &syn::File) -> HashMap<String, AccountsStruct> {
-    f.items
-        .iter()
-        .filter_map(|i| match i {
-            syn::Item::Struct(i_strct) => {
-                for attr in &i_strct.attrs {
-                    if attr.tokens.to_string().contains(DERIVE_NAME) {
-                        let strct = accounts::parse(i_strct).expect("Code not parseable");
-                        return Some((strct.ident.to_string(), strct));
-                    }
+fn parse_account_derives(ctx: &CrateContext) -> HashMap<String, AccountsStruct> {
+    // TODO: parse manual implementations. Currently we only look
+    //       for derives.
+    ctx.structs()
+        .filter_map(|i_strct| {
+            for attr in &i_strct.attrs {
+                if attr.tokens.to_string().contains(DERIVE_NAME) {
+                    let strct = accounts::parse(i_strct).expect("Code not parseable");
+                    return Some((strct.ident.to_string(), strct));
                 }
-                None
             }
-            // TODO: parse manual implementations. Currently we only look
-            //       for derives.
-            _ => None,
+            None
         })
         .collect()
 }
 
 // Parse all user defined types in the file.
-fn parse_ty_defs(f: &syn::File) -> Result<Vec<IdlTypeDefinition>> {
-    f.items
-        .iter()
-        .filter_map(|i| match i {
-            syn::Item::Struct(item_strct) => {
-                for attr in &item_strct.attrs {
-                    if attr.tokens.to_string().contains(DERIVE_NAME) {
-                        return None;
-                    }
-                }
-                if let syn::Visibility::Public(_) = &item_strct.vis {
-                    let name = item_strct.ident.to_string();
-                    let fields = match &item_strct.fields {
-                        syn::Fields::Named(fields) => fields
-                            .named
-                            .iter()
-                            .map(|f: &syn::Field| {
-                                let mut tts = proc_macro2::TokenStream::new();
-                                f.ty.to_tokens(&mut tts);
-                                Ok(IdlField {
-                                    name: f.ident.as_ref().unwrap().to_string().to_mixed_case(),
-                                    ty: tts.to_string().parse()?,
-                                })
-                            })
-                            .collect::<Result<Vec<IdlField>>>(),
-                        _ => panic!("Only named structs are allowed."),
-                    };
+fn parse_ty_defs(ctx: &CrateContext) -> Result<Vec<IdlTypeDefinition>> {
+    ctx.structs()
+        .filter_map(|item_strct| {
+            // Only take serializable types
+            let serializable = item_strct.attrs.iter().any(|attr| {
+                let attr_string = attr.tokens.to_string();
+                let attr_name = attr.path.segments.last().unwrap().ident.to_string();
+                let attr_serializable = ["account", "associated", "event", "zero_copy"];
 
-                    return Some(fields.map(|fields| IdlTypeDefinition {
-                        name,
-                        ty: IdlTypeDefinitionTy::Struct { fields },
-                    }));
-                }
-                None
+                let derived_serializable = attr_name == "derive"
+                    && attr_string.contains("AnchorSerialize")
+                    && attr_string.contains("AnchorDeserialize");
+
+                attr_serializable.iter().any(|a| *a == attr_name) || derived_serializable
+            });
+
+            if !serializable {
+                return None;
+            }
+
+            // Only take public types
+            match &item_strct.vis {
+                syn::Visibility::Public(_) => (),
+                _ => return None,
             }
-            syn::Item::Enum(enm) => {
-                let name = enm.ident.to_string();
-                let variants = enm
-                    .variants
+
+            let name = item_strct.ident.to_string();
+            let fields = match &item_strct.fields {
+                syn::Fields::Named(fields) => fields
+                    .named
                     .iter()
-                    .map(|variant: &syn::Variant| {
-                        let name = variant.ident.to_string();
-                        let fields = match &variant.fields {
-                            syn::Fields::Unit => None,
-                            syn::Fields::Unnamed(fields) => {
-                                let fields: Vec<IdlType> =
-                                    fields.unnamed.iter().map(to_idl_type).collect();
-                                Some(EnumFields::Tuple(fields))
-                            }
-                            syn::Fields::Named(fields) => {
-                                let fields: Vec<IdlField> = fields
-                                    .named
-                                    .iter()
-                                    .map(|f: &syn::Field| {
-                                        let name = f.ident.as_ref().unwrap().to_string();
-                                        let ty = to_idl_type(f);
-                                        IdlField { name, ty }
-                                    })
-                                    .collect();
-                                Some(EnumFields::Named(fields))
-                            }
-                        };
-                        IdlEnumVariant { name, fields }
+                    .map(|f: &syn::Field| {
+                        let mut tts = proc_macro2::TokenStream::new();
+                        f.ty.to_tokens(&mut tts);
+                        Ok(IdlField {
+                            name: f.ident.as_ref().unwrap().to_string().to_mixed_case(),
+                            ty: tts.to_string().parse()?,
+                        })
                     })
-                    .collect::<Vec<IdlEnumVariant>>();
-                Some(Ok(IdlTypeDefinition {
-                    name,
-                    ty: IdlTypeDefinitionTy::Enum { variants },
-                }))
-            }
-            _ => None,
+                    .collect::<Result<Vec<IdlField>>>(),
+                _ => panic!("Only named structs are allowed."),
+            };
+
+            Some(fields.map(|fields| IdlTypeDefinition {
+                name,
+                ty: IdlTypeDefinitionTy::Struct { fields },
+            }))
         })
+        .chain(ctx.enums().map(|enm| {
+            let name = enm.ident.to_string();
+            let variants = enm
+                .variants
+                .iter()
+                .map(|variant: &syn::Variant| {
+                    let name = variant.ident.to_string();
+                    let fields = match &variant.fields {
+                        syn::Fields::Unit => None,
+                        syn::Fields::Unnamed(fields) => {
+                            let fields: Vec<IdlType> =
+                                fields.unnamed.iter().map(to_idl_type).collect();
+                            Some(EnumFields::Tuple(fields))
+                        }
+                        syn::Fields::Named(fields) => {
+                            let fields: Vec<IdlField> = fields
+                                .named
+                                .iter()
+                                .map(|f: &syn::Field| {
+                                    let name = f.ident.as_ref().unwrap().to_string();
+                                    let ty = to_idl_type(f);
+                                    IdlField { name, ty }
+                                })
+                                .collect();
+                            Some(EnumFields::Named(fields))
+                        }
+                    };
+                    IdlEnumVariant { name, fields }
+                })
+                .collect::<Vec<IdlEnumVariant>>();
+            Ok(IdlTypeDefinition {
+                name,
+                ty: IdlTypeDefinitionTy::Enum { variants },
+            })
+        }))
         .collect()
 }
 

+ 178 - 0
lang/syn/src/parser/context.rs

@@ -0,0 +1,178 @@
+use std::collections::HashMap;
+use std::path::{Path, PathBuf};
+
+use syn::parse::{Error as ParseError, Result as ParseResult};
+
+/// Crate parse context
+///
+/// Keeps track of modules defined within a crate.
+pub struct CrateContext {
+    modules: HashMap<String, ParsedModule>,
+}
+
+impl CrateContext {
+    pub fn structs(&self) -> impl Iterator<Item = &syn::ItemStruct> {
+        self.modules.iter().flat_map(|(_, ctx)| ctx.structs())
+    }
+
+    pub fn enums(&self) -> impl Iterator<Item = &syn::ItemEnum> {
+        self.modules.iter().flat_map(|(_, ctx)| ctx.enums())
+    }
+
+    pub fn modules(&self) -> impl Iterator<Item = ModuleContext> {
+        self.modules
+            .iter()
+            .map(move |(_, detail)| ModuleContext { detail })
+    }
+
+    pub fn root_module(&self) -> ModuleContext {
+        ModuleContext {
+            detail: self.modules.get("crate").unwrap(),
+        }
+    }
+
+    pub fn parse(root: impl AsRef<Path>) -> Result<Self, anyhow::Error> {
+        Ok(CrateContext {
+            modules: ParsedModule::parse_recursive(root.as_ref())?,
+        })
+    }
+}
+
+/// Module parse context
+///
+/// Keeps track of items defined within a module.
+#[derive(Copy, Clone)]
+pub struct ModuleContext<'krate> {
+    detail: &'krate ParsedModule,
+}
+
+impl<'krate> ModuleContext<'krate> {
+    pub fn items(&self) -> impl Iterator<Item = &syn::Item> {
+        self.detail.items.iter()
+    }
+}
+struct ParsedModule {
+    name: String,
+    file: PathBuf,
+    path: String,
+    items: Vec<syn::Item>,
+}
+
+impl ParsedModule {
+    fn parse_recursive(root: &Path) -> Result<HashMap<String, ParsedModule>, anyhow::Error> {
+        let mut modules = HashMap::new();
+
+        let root_content = std::fs::read_to_string(root)?;
+        let root_file = syn::parse_file(&root_content)?;
+        let root_mod = Self::new(
+            String::new(),
+            root.to_owned(),
+            "crate".to_owned(),
+            root_file.items,
+        );
+
+        struct UnparsedModule {
+            file: PathBuf,
+            path: String,
+            name: String,
+            item: syn::ItemMod,
+        }
+
+        let mut unparsed = root_mod
+            .submodules()
+            .map(|item| UnparsedModule {
+                file: root_mod.file.clone(),
+                path: root_mod.path.clone(),
+                name: item.ident.to_string(),
+                item: item.clone(),
+            })
+            .collect::<Vec<_>>();
+
+        while let Some(to_parse) = unparsed.pop() {
+            let path = format!("{}::{}", to_parse.path, to_parse.name);
+            let name = to_parse.name;
+            let module = Self::from_item_mod(&to_parse.file, &path, to_parse.item)?;
+
+            unparsed.extend(module.submodules().map(|item| UnparsedModule {
+                item: item.clone(),
+                file: module.file.clone(),
+                path: module.path.clone(),
+                name: name.clone(),
+            }));
+            modules.insert(name.clone(), module);
+        }
+
+        modules.insert(root_mod.name.clone(), root_mod);
+
+        Ok(modules)
+    }
+
+    fn from_item_mod(
+        parent_file: &Path,
+        parent_path: &str,
+        item: syn::ItemMod,
+    ) -> ParseResult<Self> {
+        let path = format!("{}::{}", parent_path, item.ident);
+
+        Ok(match item.content {
+            Some((_, items)) => {
+                // The module content is within the parent file being parsed
+                Self::new(path, parent_file.to_owned(), item.ident.to_string(), items)
+            }
+            None => {
+                // The module is referencing some other file, so we need to load that
+                // to parse the items it has.
+                let parent_dir = parent_file.parent().unwrap();
+                let parent_filename = parent_file.file_stem().unwrap().to_str().unwrap();
+                let parent_mod_dir = parent_dir.join(parent_filename);
+
+                let possible_file_paths = vec![
+                    parent_dir.join(format!("{}.rs", item.ident)),
+                    parent_dir.join(format!("{}/mod.rs", item.ident)),
+                    parent_mod_dir.join(format!("{}.rs", item.ident)),
+                    parent_mod_dir.join(format!("{}/mod.rs", item.ident)),
+                ];
+
+                let mod_file_path = possible_file_paths
+                    .into_iter()
+                    .find(|p| p.exists())
+                    .ok_or_else(|| ParseError::new_spanned(&item, "could not find file"))?;
+                let mod_file_content = std::fs::read_to_string(&mod_file_path)
+                    .map_err(|_| ParseError::new_spanned(&item, "could not read file"))?;
+                let mod_file = syn::parse_file(&mod_file_content)?;
+
+                Self::new(path, mod_file_path, item.ident.to_string(), mod_file.items)
+            }
+        })
+    }
+
+    fn new(path: String, file: PathBuf, name: String, items: Vec<syn::Item>) -> Self {
+        Self {
+            name,
+            file,
+            path,
+            items,
+        }
+    }
+
+    fn submodules(&self) -> impl Iterator<Item = &syn::ItemMod> {
+        self.items.iter().filter_map(|i| match i {
+            syn::Item::Mod(item) => Some(item),
+            _ => None,
+        })
+    }
+
+    fn structs(&self) -> impl Iterator<Item = &syn::ItemStruct> {
+        self.items.iter().filter_map(|i| match i {
+            syn::Item::Struct(item) => Some(item),
+            _ => None,
+        })
+    }
+
+    fn enums(&self) -> impl Iterator<Item = &syn::ItemEnum> {
+        self.items.iter().filter_map(|i| match i {
+            syn::Item::Enum(item) => Some(item),
+            _ => None,
+        })
+    }
+}

+ 1 - 0
lang/syn/src/parser/mod.rs

@@ -1,4 +1,5 @@
 pub mod accounts;
+pub mod context;
 pub mod error;
 pub mod program;