Sfoglia il codice sorgente

IDL generation through compilation (#2011)

Co-authored-by: acheron <acheroncrypto@gmail.com>
Krešimir Klas 2 anni fa
parent
commit
6ef6b79a6c
57 ha cambiato i file con 4385 aggiunte e 364 eliminazioni
  1. 2 0
      .github/workflows/reusable-tests.yaml
  2. 3 0
      CHANGELOG.md
  3. 14 0
      Cargo.lock
  4. 1 1
      cli/Cargo.toml
  5. 1 1
      cli/src/config.rs
  6. 175 2
      cli/src/lib.rs
  7. 1 1
      cli/src/rust_template.rs
  8. 1 1
      cli/src/solidity_template.rs
  9. 14 0
      lang/Cargo.toml
  10. 1 0
      lang/attribute/account/Cargo.toml
  11. 17 2
      lang/attribute/account/src/lib.rs
  12. 2 0
      lang/attribute/constant/Cargo.toml
  13. 22 0
      lang/attribute/constant/src/lib.rs
  14. 1 0
      lang/attribute/error/Cargo.toml
  15. 1 0
      lang/attribute/event/Cargo.toml
  16. 14 2
      lang/attribute/event/src/lib.rs
  17. 1 0
      lang/attribute/program/Cargo.toml
  18. 1 0
      lang/derive/accounts/Cargo.toml
  19. 24 0
      lang/derive/serde/Cargo.toml
  20. 82 0
      lang/derive/serde/src/lib.rs
  21. 6 1
      lang/src/lib.rs
  22. 3 1
      lang/syn/Cargo.toml
  23. 2 2
      lang/syn/src/codegen/accounts/__client_accounts.rs
  24. 15 1
      lang/syn/src/codegen/accounts/mod.rs
  25. 18 3
      lang/syn/src/codegen/error.rs
  26. 29 12
      lang/syn/src/codegen/program/mod.rs
  27. 915 0
      lang/syn/src/idl/build.rs
  28. 6 330
      lang/syn/src/idl/mod.rs
  29. 8 2
      lang/syn/src/idl/parse/file.rs
  30. 120 0
      lang/syn/src/idl/parse/mod.rs
  31. 1 1
      lang/syn/src/idl/parse/pda.rs
  32. 0 0
      lang/syn/src/idl/parse/relations.rs
  33. 229 0
      lang/syn/src/idl/types.rs
  34. 0 1
      lang/syn/src/lib.rs
  35. 13 0
      tests/idl-build/Anchor.toml
  36. 13 0
      tests/idl-build/Cargo.toml
  37. 11 0
      tests/idl-build/gen_testdata.sh
  38. 16 0
      tests/idl-build/package.json
  39. 25 0
      tests/idl-build/programs/generics/Cargo.toml
  40. 2 0
      tests/idl-build/programs/generics/Xargo.toml
  41. 80 0
      tests/idl-build/programs/generics/src/lib.rs
  42. 26 0
      tests/idl-build/programs/idl/Cargo.toml
  43. 2 0
      tests/idl-build/programs/idl/Xargo.toml
  44. 326 0
      tests/idl-build/programs/idl/src/lib.rs
  45. 22 0
      tests/idl-build/programs/relations-derivation/Cargo.toml
  46. 2 0
      tests/idl-build/programs/relations-derivation/Xargo.toml
  47. 68 0
      tests/idl-build/programs/relations-derivation/src/lib.rs
  48. 22 0
      tests/idl-build/programs/some_external_program/Cargo.toml
  49. 2 0
      tests/idl-build/programs/some_external_program/Xargo.toml
  50. 20 0
      tests/idl-build/programs/some_external_program/src/lib.rs
  51. 53 0
      tests/idl-build/test.sh
  52. 426 0
      tests/idl-build/tests/testdata/generics_build_exp.json
  53. 727 0
      tests/idl-build/tests/testdata/idl_build_exp.json
  54. 705 0
      tests/idl-build/tests/testdata/idl_parse_exp.json
  55. 83 0
      tests/idl-build/tests/testdata/relations_build_exp.json
  56. 10 0
      tests/idl-build/tsconfig.json
  57. 1 0
      tests/package.json

+ 2 - 0
.github/workflows/reusable-tests.yaml

@@ -443,6 +443,8 @@ jobs:
             path: tests/anchor-cli-account
           - cmd: cd tests/bench && anchor test --skip-lint
             path: tests/bench
+          - cmd: cd tests/idl-build && ./test.sh
+            path: tests/idl-build
     steps:
       - uses: actions/checkout@v3
       - uses: ./.github/actions/setup/

+ 3 - 0
CHANGELOG.md

@@ -15,6 +15,7 @@ The minor version will be incremented upon a breaking change and the patch versi
 - lang: Add `get_lamports`, `add_lamports` and `sub_lamports` methods for all account types ([#2552](https://github.com/coral-xyz/anchor/pull/2552)).
 - client: Add a helper struct `DynSigner` to simplify use of `Client<C> where <C: Clone + Deref<Target = impl Signer>>` with Solana clap CLI utils that loads `Signer` as `Box<dyn Signer>` ([#2550](https://github.com/coral-xyz/anchor/pull/2550)).
 - lang: Allow CPI calls matching an interface without pinning program ID ([#2559](https://github.com/coral-xyz/anchor/pull/2559)).
+- cli, lang: Add IDL generation through compilation. `anchor build` still uses parsing method to generate IDLs, use `anchor idl build` to generate IDLs with the build method ([#2011](https://github.com/coral-xyz/anchor/pull/2011)).
 
 ### Fixes
 
@@ -23,6 +24,8 @@ The minor version will be incremented upon a breaking change and the patch versi
 
 ### Breaking
 
+- syn: `idl` feature has been replaced with `idl-build`, `idl-parse` and `idl-types` features ([#2011](https://github.com/coral-xyz/anchor/pull/2011)).
+
 ## [0.28.0] - 2023-06-09
 
 ### Features

+ 14 - 0
Cargo.lock

@@ -138,6 +138,7 @@ version = "0.28.0"
 dependencies = [
  "anchor-syn",
  "proc-macro2 1.0.60",
+ "quote 1.0.28",
  "syn 1.0.109",
 ]
 
@@ -238,6 +239,17 @@ dependencies = [
  "syn 1.0.109",
 ]
 
+[[package]]
+name = "anchor-derive-serde"
+version = "0.28.0"
+dependencies = [
+ "anchor-syn",
+ "borsh-derive-internal 0.10.3",
+ "proc-macro2 1.0.60",
+ "quote 1.0.28",
+ "syn 1.0.109",
+]
+
 [[package]]
 name = "anchor-derive-space"
 version = "0.28.0"
@@ -258,7 +270,9 @@ dependencies = [
  "anchor-attribute-event",
  "anchor-attribute-program",
  "anchor-derive-accounts",
+ "anchor-derive-serde",
  "anchor-derive-space",
+ "anchor-syn",
  "arrayref",
  "base64 0.13.1",
  "bincode",

+ 1 - 1
cli/Cargo.toml

@@ -19,7 +19,7 @@ default = []
 [dependencies]
 anchor-client = { path = "../client", version = "0.28.0" }
 anchor-lang = { path = "../lang", version = "0.28.0" }
-anchor-syn = { path = "../lang/syn", features = ["event-cpi", "idl", "init-if-needed"], version = "0.28.0" }
+anchor-syn = { path = "../lang/syn", features = ["event-cpi", "idl-parse", "init-if-needed"], version = "0.28.0" }
 anyhow = "1.0.32"
 base64 = "0.13.1"
 bincode = "1.3.3"

+ 1 - 1
cli/src/config.rs

@@ -1,6 +1,6 @@
 use crate::is_hidden;
 use anchor_client::Cluster;
-use anchor_syn::idl::Idl;
+use anchor_syn::idl::types::Idl;
 use anyhow::{anyhow, bail, Context, Error, Result};
 use clap::{Parser, ValueEnum};
 use heck::ToSnakeCase;

+ 175 - 2
cli/src/lib.rs

@@ -6,7 +6,10 @@ use crate::config::{
 use anchor_client::Cluster;
 use anchor_lang::idl::{IdlAccount, IdlInstruction, ERASED_AUTHORITY};
 use anchor_lang::{AccountDeserialize, AnchorDeserialize, AnchorSerialize};
-use anchor_syn::idl::{EnumFields, Idl, IdlType, IdlTypeDefinitionTy};
+use anchor_syn::idl::types::{
+    EnumFields, Idl, IdlConst, IdlErrorCode, IdlEvent, IdlType, IdlTypeDefinition,
+    IdlTypeDefinitionTy,
+};
 use anyhow::{anyhow, Context, Result};
 use clap::Parser;
 use flate2::read::GzDecoder;
@@ -417,6 +420,11 @@ pub enum IdlCommand {
         #[clap(long)]
         no_docs: bool,
     },
+    /// Generates the IDL for the program using the compilation method.
+    Build {
+        #[clap(long)]
+        no_docs: bool,
+    },
     /// Fetches an IDL for the given address from a cluster.
     /// The address can be a program, IDL account, or IDL buffer.
     Fetch {
@@ -1834,7 +1842,7 @@ fn extract_idl(
     let manifest_from_path = std::env::current_dir()?.join(PathBuf::from(&*file).parent().unwrap());
     let cargo = Manifest::discover_from_path(manifest_from_path)?
         .ok_or_else(|| anyhow!("Cargo.toml not found"))?;
-    anchor_syn::idl::file::parse(
+    anchor_syn::idl::parse::file::parse(
         &*file,
         cargo.version(),
         cfg.features.seeds,
@@ -1880,6 +1888,7 @@ fn idl(cfg_override: &ConfigOverride, subcmd: IdlCommand) -> Result<()> {
             out_ts,
             no_docs,
         } => idl_parse(cfg_override, file, out, out_ts, no_docs),
+        IdlCommand::Build { no_docs } => idl_build(no_docs),
         IdlCommand::Fetch { address, out } => idl_fetch(cfg_override, address, out),
     }
 }
@@ -2225,6 +2234,165 @@ fn idl_parse(
     Ok(())
 }
 
+fn idl_build(no_docs: bool) -> Result<()> {
+    let no_docs = if no_docs { "TRUE" } else { "FALSE" };
+
+    let cfg = Config::discover(&ConfigOverride::default())?.expect("Not in workspace.");
+    let seeds_feature = if cfg.features.seeds { "TRUE" } else { "FALSE" };
+
+    let exit = std::process::Command::new("cargo")
+        .args([
+            "test",
+            "__anchor_private_print_idl",
+            "--features",
+            "idl-build",
+            "--",
+            "--show-output",
+            "--quiet",
+        ])
+        .env("ANCHOR_IDL_GEN_NO_DOCS", no_docs)
+        .env("ANCHOR_IDL_GEN_SEEDS_FEATURE", seeds_feature)
+        .stderr(Stdio::inherit())
+        .output()
+        .map_err(|e| anyhow::format_err!("{}", e.to_string()))?;
+    if !exit.status.success() {
+        std::process::exit(exit.status.code().unwrap_or(1));
+    }
+
+    enum State {
+        Pass,
+        ConstLines(Vec<String>),
+        EventLines(Vec<String>),
+        ErrorsLines(Vec<String>),
+        ProgramLines(Vec<String>),
+    }
+
+    #[derive(Serialize, Deserialize)]
+    struct IdlGenEventPrint {
+        event: IdlEvent,
+        defined_types: Vec<IdlTypeDefinition>,
+    }
+
+    let mut state = State::Pass;
+
+    let mut events: Vec<IdlEvent> = vec![];
+    let mut error_codes: Option<Vec<IdlErrorCode>> = None;
+    let mut constants: Vec<IdlConst> = vec![];
+    let mut defined_types: BTreeMap<String, IdlTypeDefinition> = BTreeMap::new();
+    let mut curr_idl: Option<Idl> = None;
+
+    let mut idls: Vec<Idl> = vec![];
+
+    let out = String::from_utf8_lossy(&exit.stdout);
+    for line in out.lines() {
+        match &mut state {
+            State::Pass => {
+                if line == "---- IDL begin const ----" {
+                    state = State::ConstLines(vec![]);
+                    continue;
+                } else if line == "---- IDL begin event ----" {
+                    state = State::EventLines(vec![]);
+                    continue;
+                } else if line == "---- IDL begin errors ----" {
+                    state = State::ErrorsLines(vec![]);
+                    continue;
+                } else if line == "---- IDL begin program ----" {
+                    state = State::ProgramLines(vec![]);
+                    continue;
+                } else if line.starts_with("test result: ok") {
+                    let events = std::mem::take(&mut events);
+                    let error_codes = error_codes.take();
+                    let constants = std::mem::take(&mut constants);
+                    let mut defined_types = std::mem::take(&mut defined_types);
+                    let curr_idl = curr_idl.take();
+
+                    let events = if !events.is_empty() {
+                        Some(events)
+                    } else {
+                        None
+                    };
+
+                    let mut idl = match curr_idl {
+                        Some(idl) => idl,
+                        None => continue,
+                    };
+
+                    idl.events = events;
+                    idl.errors = error_codes;
+                    idl.constants = constants;
+
+                    idl.constants.sort_by(|a, b| a.name.cmp(&b.name));
+                    idl.accounts.sort_by(|a, b| a.name.cmp(&b.name));
+                    if let Some(e) = idl.events.as_mut() {
+                        e.sort_by(|a, b| a.name.cmp(&b.name))
+                    }
+
+                    let prog_ty = std::mem::take(&mut idl.types);
+                    defined_types
+                        .extend(prog_ty.into_iter().map(|ty| (ty.path.clone().unwrap(), ty)));
+                    idl.types = defined_types.into_values().collect::<Vec<_>>();
+
+                    idls.push(idl);
+                    continue;
+                }
+            }
+            State::ConstLines(lines) => {
+                if line == "---- IDL end const ----" {
+                    let constant: IdlConst = serde_json::from_str(&lines.join("\n"))?;
+                    constants.push(constant);
+                    state = State::Pass;
+                    continue;
+                }
+                lines.push(line.to_string());
+            }
+            State::EventLines(lines) => {
+                if line == "---- IDL end event ----" {
+                    let event: IdlGenEventPrint = serde_json::from_str(&lines.join("\n"))?;
+                    events.push(event.event);
+                    defined_types.extend(
+                        event
+                            .defined_types
+                            .into_iter()
+                            .map(|ty| (ty.path.clone().unwrap(), ty)),
+                    );
+                    state = State::Pass;
+                    continue;
+                }
+                lines.push(line.to_string());
+            }
+            State::ErrorsLines(lines) => {
+                if line == "---- IDL end errors ----" {
+                    let errs: Vec<IdlErrorCode> = serde_json::from_str(&lines.join("\n"))?;
+                    error_codes = Some(errs);
+                    state = State::Pass;
+                    continue;
+                }
+                lines.push(line.to_string());
+            }
+            State::ProgramLines(lines) => {
+                if line == "---- IDL end program ----" {
+                    let idl: Idl = serde_json::from_str(&lines.join("\n"))?;
+                    curr_idl = Some(idl);
+                    state = State::Pass;
+                    continue;
+                }
+                lines.push(line.to_string());
+            }
+        }
+    }
+
+    if idls.len() == 1 {
+        println!(
+            "{}",
+            serde_json::to_string_pretty(&idls.first().unwrap()).unwrap()
+        );
+    } else if idls.len() >= 2 {
+        println!("{}", serde_json::to_string_pretty(&idls).unwrap());
+    };
+
+    Ok(())
+}
+
 fn idl_fetch(cfg_override: &ConfigOverride, address: Pubkey, out: Option<String>) -> Result<()> {
     let idl = fetch_idl(cfg_override, address)?;
     let out = match out {
@@ -2500,6 +2668,11 @@ fn deserialize_idl_type_to_json(
 
             JsonValue::Array(array_data)
         }
+        IdlType::GenericLenArray(_, _) => todo!("Generic length arrays are not yet supported"),
+        IdlType::Generic(_) => todo!("Generic types are not yet supported"),
+        IdlType::DefinedWithTypeArgs { path: _, args: _ } => {
+            todo!("Defined types with type args are not yet supported")
+        }
     })
 }
 

+ 1 - 1
cli/src/rust_template.rs

@@ -1,6 +1,6 @@
 use crate::config::ProgramWorkspace;
 use crate::VERSION;
-use anchor_syn::idl::Idl;
+use anchor_syn::idl::types::Idl;
 use anyhow::Result;
 use heck::{ToLowerCamelCase, ToSnakeCase, ToUpperCamelCase};
 use solana_sdk::{

+ 1 - 1
cli/src/solidity_template.rs

@@ -1,6 +1,6 @@
 use crate::config::ProgramWorkspace;
 use crate::VERSION;
-use anchor_syn::idl::Idl;
+use anchor_syn::idl::types::Idl;
 use anyhow::Result;
 use heck::{ToLowerCamelCase, ToSnakeCase, ToUpperCamelCase};
 use solana_sdk::pubkey::Pubkey;

+ 14 - 0
lang/Cargo.toml

@@ -14,6 +14,16 @@ init-if-needed = ["anchor-derive-accounts/init-if-needed"]
 derive = []
 default = []
 event-cpi = ["anchor-attribute-event/event-cpi"]
+idl-build = [
+    "anchor-syn/idl-build",
+    "anchor-derive-accounts/idl-build",
+    "anchor-derive-serde/idl-build",
+    "anchor-attribute-account/idl-build",
+    "anchor-attribute-constant/idl-build",
+    "anchor-attribute-event/idl-build",
+    "anchor-attribute-error/idl-build",
+    "anchor-attribute-program/idl-build",
+]
 anchor-debug = [
     "anchor-attribute-access-control/anchor-debug",
     "anchor-attribute-account/anchor-debug",
@@ -33,7 +43,11 @@ anchor-attribute-error = { path = "./attribute/error", version = "0.28.0" }
 anchor-attribute-event = { path = "./attribute/event", version = "0.28.0" }
 anchor-attribute-program = { path = "./attribute/program", version = "0.28.0" }
 anchor-derive-accounts = { path = "./derive/accounts", version = "0.28.0" }
+anchor-derive-serde = { path = "./derive/serde", version = "0.28.0" }
 anchor-derive-space = { path = "./derive/space", version = "0.28.0" }
+# anchor-syn can and should only be included only for idl-build. It won't compile
+# for bpf due to proc-macro2 crate.
+anchor-syn = { path = "./syn", version = "0.28.0", optional = true }
 arrayref = "0.3"
 base64 = "0.13"
 bincode = "1"

+ 1 - 0
lang/attribute/account/Cargo.toml

@@ -12,6 +12,7 @@ edition = "2021"
 proc-macro = true
 
 [features]
+idl-build = ["anchor-syn/idl-build"]
 anchor-debug = ["anchor-syn/anchor-debug"]
 
 [dependencies]

+ 17 - 2
lang/attribute/account/src/lib.rs

@@ -1,5 +1,7 @@
 extern crate proc_macro;
 
+#[cfg(feature = "idl-build")]
+use anchor_syn::idl::build::*;
 use quote::quote;
 use syn::parse_macro_input;
 
@@ -392,13 +394,26 @@ pub fn zero_copy(
         quote! {#[derive(::bytemuck::Zeroable)]}
     };
 
-    proc_macro::TokenStream::from(quote! {
+    let ret = quote! {
         #[derive(anchor_lang::__private::ZeroCopyAccessor, Copy, Clone)]
         #repr
         #pod
         #zeroable
         #account_strct
-    })
+    };
+
+    #[cfg(feature = "idl-build")]
+    {
+        let no_docs = get_no_docs();
+        let idl_gen_impl = gen_idl_gen_impl_for_struct(&account_strct, no_docs);
+        return proc_macro::TokenStream::from(quote! {
+            #ret
+            #idl_gen_impl
+        });
+    }
+
+    #[allow(unreachable_code)]
+    proc_macro::TokenStream::from(ret)
 }
 
 /// Defines the program's ID. This should be used at the root of all Anchor

+ 2 - 0
lang/attribute/constant/Cargo.toml

@@ -12,9 +12,11 @@ edition = "2021"
 proc-macro = true
 
 [features]
+idl-build = ["anchor-syn/idl-build"]
 anchor-debug = ["anchor-syn/anchor-debug"]
 
 [dependencies]
 anchor-syn = { path = "../../syn", version = "0.28.0" }
 proc-macro2 = "1"
+quote = "1"
 syn = { version = "1", features = ["full"] }

+ 22 - 0
lang/attribute/constant/src/lib.rs

@@ -1,5 +1,8 @@
 extern crate proc_macro;
 
+#[cfg(feature = "idl-build")]
+use {anchor_syn::idl::build::gen_idl_print_function_for_constant, quote::quote, syn};
+
 /// A marker attribute used to mark const values that should be included in the
 /// generated IDL but functionally does nothing.
 #[proc_macro_attribute]
@@ -7,5 +10,24 @@ pub fn constant(
     _attr: proc_macro::TokenStream,
     input: proc_macro::TokenStream,
 ) -> proc_macro::TokenStream {
+    #[cfg(feature = "idl-build")]
+    {
+        let ts = match syn::parse(input).unwrap() {
+            syn::Item::Const(item) => {
+                let idl_print = gen_idl_print_function_for_constant(&item);
+                quote! {
+                    #item
+                    #idl_print
+                }
+            }
+            item => quote! {#item},
+        };
+
+        return proc_macro::TokenStream::from(quote! {
+            #ts
+        });
+    };
+
+    #[allow(unreachable_code)]
     input
 }

+ 1 - 0
lang/attribute/error/Cargo.toml

@@ -12,6 +12,7 @@ edition = "2021"
 proc-macro = true
 
 [features]
+idl-build = ["anchor-syn/idl-build"]
 anchor-debug = ["anchor-syn/anchor-debug"]
 
 [dependencies]

+ 1 - 0
lang/attribute/event/Cargo.toml

@@ -12,6 +12,7 @@ edition = "2021"
 proc-macro = true
 
 [features]
+idl-build = ["anchor-syn/idl-build"]
 anchor-debug = ["anchor-syn/anchor-debug"]
 event-cpi = ["anchor-syn/event-cpi"]
 

+ 14 - 2
lang/attribute/event/src/lib.rs

@@ -29,7 +29,7 @@ pub fn event(
         format!("{discriminator:?}").parse().unwrap()
     };
 
-    proc_macro::TokenStream::from(quote! {
+    let ret = quote! {
         #[derive(anchor_lang::__private::EventIndex, AnchorSerialize, AnchorDeserialize)]
         #event_strct
 
@@ -44,7 +44,19 @@ pub fn event(
         impl anchor_lang::Discriminator for #event_name {
             const DISCRIMINATOR: [u8; 8] = #discriminator;
         }
-    })
+    };
+
+    #[cfg(feature = "idl-build")]
+    {
+        let idl_gen = anchor_syn::idl::build::gen_idl_print_function_for_event(&event_strct);
+        return proc_macro::TokenStream::from(quote! {
+            #ret
+            #idl_gen
+        });
+    }
+
+    #[allow(unreachable_code)]
+    proc_macro::TokenStream::from(ret)
 }
 
 // EventIndex is a marker macro. It functionally does nothing other than

+ 1 - 0
lang/attribute/program/Cargo.toml

@@ -12,6 +12,7 @@ edition = "2021"
 proc-macro = true
 
 [features]
+idl-build = ["anchor-syn/idl-build"]
 anchor-debug = ["anchor-syn/anchor-debug"]
 
 [dependencies]

+ 1 - 0
lang/derive/accounts/Cargo.toml

@@ -15,6 +15,7 @@ proc-macro = true
 allow-missing-optionals = ["anchor-syn/allow-missing-optionals"]
 init-if-needed = ["anchor-syn/init-if-needed"]
 default = []
+idl-build = ["anchor-syn/idl-build"]
 anchor-debug = ["anchor-syn/anchor-debug"]
 
 [dependencies]

+ 24 - 0
lang/derive/serde/Cargo.toml

@@ -0,0 +1,24 @@
+[package]
+name = "anchor-derive-serde"
+version = "0.28.0"
+authors = ["Anchor Maintainers <accounts@200ms.io>"]
+repository = "https://github.com/coral-xyz/anchor"
+license = "Apache-2.0"
+description = "Anchor Derive macro for serialization and deserialization"
+rust-version = "1.60"
+edition = "2021"
+
+[lib]
+proc-macro = true
+
+[features]
+idl-build = [
+    "anchor-syn/idl-build",
+]
+
+[dependencies]
+anchor-syn = { path = "../../syn", version = "0.28.0" }
+borsh-derive-internal = ">=0.9, <0.11"
+proc-macro2 = "1"
+syn = { version = "1", features = ["full"] }
+quote = "1"

+ 82 - 0
lang/derive/serde/src/lib.rs

@@ -0,0 +1,82 @@
+extern crate proc_macro;
+
+use borsh_derive_internal::*;
+use proc_macro::TokenStream;
+use proc_macro2::{Span, TokenStream as TokenStream2};
+use syn::{Ident, Item};
+
+#[cfg(feature = "idl-build")]
+use {anchor_syn::idl::build::*, quote::quote};
+
+fn gen_borsh_serialize(input: TokenStream) -> TokenStream2 {
+    let cratename = Ident::new("borsh", Span::call_site());
+
+    let item: Item = syn::parse(input).unwrap();
+    let res = match item {
+        Item::Struct(item) => struct_ser(&item, cratename),
+        Item::Enum(item) => enum_ser(&item, cratename),
+        Item::Union(item) => union_ser(&item, cratename),
+        // Derive macros can only be defined on structs, enums, and unions.
+        _ => unreachable!(),
+    };
+
+    match res {
+        Ok(res) => res,
+        Err(err) => err.to_compile_error(),
+    }
+}
+
+#[proc_macro_derive(AnchorSerialize, attributes(borsh_skip))]
+pub fn anchor_serialize(input: TokenStream) -> TokenStream {
+    #[cfg(not(feature = "idl-build"))]
+    let ret = gen_borsh_serialize(input);
+    #[cfg(feature = "idl-build")]
+    let ret = gen_borsh_serialize(input.clone());
+
+    #[cfg(feature = "idl-build")]
+    {
+        let no_docs = get_no_docs();
+
+        let idl_gen_impl = match syn::parse(input).unwrap() {
+            Item::Struct(item) => gen_idl_gen_impl_for_struct(&item, no_docs),
+            Item::Enum(item) => gen_idl_gen_impl_for_enum(item, no_docs),
+            Item::Union(item) => {
+                // unions are not included in the IDL - TODO print a warning
+                idl_gen_impl_skeleton(quote! {None}, quote! {}, &item.ident, &item.generics)
+            }
+            // Derive macros can only be defined on structs, enums, and unions.
+            _ => unreachable!(),
+        };
+
+        return TokenStream::from(quote! {
+            #ret
+            #idl_gen_impl
+        });
+    };
+
+    #[allow(unreachable_code)]
+    TokenStream::from(ret)
+}
+
+fn gen_borsh_deserialize(input: TokenStream) -> TokenStream2 {
+    let cratename = Ident::new("borsh", Span::call_site());
+
+    let item: Item = syn::parse(input).unwrap();
+    let res = match item {
+        Item::Struct(item) => struct_de(&item, cratename),
+        Item::Enum(item) => enum_de(&item, cratename),
+        Item::Union(item) => union_de(&item, cratename),
+        // Derive macros can only be defined on structs, enums, and unions.
+        _ => unreachable!(),
+    };
+
+    match res {
+        Ok(res) => res,
+        Err(err) => err.to_compile_error(),
+    }
+}
+
+#[proc_macro_derive(AnchorDeserialize, attributes(borsh_skip, borsh_init))]
+pub fn borsh_deserialize(input: TokenStream) -> TokenStream {
+    TokenStream::from(gen_borsh_deserialize(input))
+}

+ 6 - 1
lang/src/lib.rs

@@ -54,11 +54,16 @@ pub use anchor_attribute_event::{emit, event};
 pub use anchor_attribute_event::{emit_cpi, event_cpi};
 pub use anchor_attribute_program::program;
 pub use anchor_derive_accounts::Accounts;
+pub use anchor_derive_serde::{AnchorDeserialize, AnchorSerialize};
 pub use anchor_derive_space::InitSpace;
 /// Borsh is the default serialization format for instructions and accounts.
-pub use borsh::{BorshDeserialize as AnchorDeserialize, BorshSerialize as AnchorSerialize};
+pub use borsh::de::BorshDeserialize as AnchorDeserialize;
+pub use borsh::ser::BorshSerialize as AnchorSerialize;
 pub use solana_program;
 
+#[cfg(feature = "idl-build")]
+pub use anchor_syn;
+
 pub type Result<T> = std::result::Result<T, error::Error>;
 
 /// A data structure of validated accounts that can be deserialized from the

+ 3 - 1
lang/syn/Cargo.toml

@@ -11,7 +11,9 @@ edition = "2021"
 [features]
 allow-missing-optionals = []
 init-if-needed = []
-idl = []
+idl-build = []
+idl-parse = []
+idl-types = []
 hash = []
 default = []
 anchor-debug = []

+ 2 - 2
lang/syn/src/codegen/accounts/__client_accounts.rs

@@ -62,12 +62,12 @@ pub fn generate(accs: &AccountsStruct) -> proc_macro2::TokenStream {
                 if f.is_optional {
                     quote! {
                         #docs
-                        pub #name: Option<anchor_lang::solana_program::pubkey::Pubkey>
+                        pub #name: Option<Pubkey>
                     }
                 } else {
                     quote! {
                         #docs
-                        pub #name: anchor_lang::solana_program::pubkey::Pubkey
+                        pub #name: Pubkey
                     }
                 }
             }

+ 15 - 1
lang/syn/src/codegen/accounts/mod.rs

@@ -22,7 +22,7 @@ pub fn generate(accs: &AccountsStruct) -> proc_macro2::TokenStream {
     let __client_accounts_mod = __client_accounts::generate(accs);
     let __cpi_client_accounts_mod = __cpi_client_accounts::generate(accs);
 
-    quote! {
+    let ret = quote! {
         #impl_try_accounts
         #impl_to_account_infos
         #impl_to_account_metas
@@ -30,7 +30,21 @@ pub fn generate(accs: &AccountsStruct) -> proc_macro2::TokenStream {
 
         #__client_accounts_mod
         #__cpi_client_accounts_mod
+    };
+
+    #[cfg(feature = "idl-build")]
+    {
+        #![allow(warnings)]
+        let no_docs = crate::idl::build::get_no_docs();
+        let idl_gen_impl = crate::idl::build::gen_idl_gen_impl_for_accounts_strct(&accs, no_docs);
+        return quote! {
+            #ret
+            #idl_gen_impl
+        };
     }
+
+    #[allow(unreachable_code)]
+    ret
 }
 
 fn generics(accs: &AccountsStruct) -> ParsedGenerics {

+ 18 - 3
lang/syn/src/codegen/error.rs

@@ -1,6 +1,9 @@
 use crate::Error;
 use quote::quote;
 
+#[cfg(feature = "idl-build")]
+use crate::idl::build::gen_idl_print_function_for_error;
+
 pub fn generate(error: Error) -> proc_macro2::TokenStream {
     let error_enum = &error.raw_enum;
     let enum_name = &error.ident;
@@ -47,7 +50,7 @@ pub fn generate(error: Error) -> proc_macro2::TokenStream {
         })
         .collect();
 
-    let offset = match error.args {
+    let offset = match &error.args {
         None => quote! { anchor_lang::error::ERROR_CODE_OFFSET},
         Some(args) => {
             let offset = &args.offset;
@@ -55,7 +58,7 @@ pub fn generate(error: Error) -> proc_macro2::TokenStream {
         }
     };
 
-    quote! {
+    let ret = quote! {
         #[derive(std::fmt::Debug, Clone, Copy)]
         #[repr(u32)]
         #error_enum
@@ -96,5 +99,17 @@ pub fn generate(error: Error) -> proc_macro2::TokenStream {
                 }
             }
         }
-    }
+    };
+
+    #[cfg(feature = "idl-build")]
+    {
+        let idl_gen = gen_idl_print_function_for_error(&error);
+        return quote! {
+            #ret
+            #idl_gen
+        };
+    };
+
+    #[allow(unreachable_code)]
+    ret
 }

+ 29 - 12
lang/syn/src/codegen/program/mod.rs

@@ -21,16 +21,33 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
     let cpi = cpi::generate(program);
     let accounts = accounts::generate(program);
 
-    quote! {
-        // TODO: remove once we allow segmented paths in `Accounts` structs.
-        use self::#mod_name::*;
-
-        #entry
-        #dispatch
-        #handlers
-        #user_defined_program
-        #instruction
-        #cpi
-        #accounts
-    }
+    #[allow(clippy::let_and_return)]
+    let ret = {
+        quote! {
+            // TODO: remove once we allow segmented paths in `Accounts` structs.
+            use self::#mod_name::*;
+
+            #entry
+            #dispatch
+            #handlers
+            #user_defined_program
+            #instruction
+            #cpi
+            #accounts
+        }
+    };
+
+    #[cfg(feature = "idl-build")]
+    {
+        let no_docs = crate::idl::build::get_no_docs();
+        let idl_gen = crate::idl::build::gen_idl_print_function_for_program(program, no_docs);
+
+        return quote! {
+            #ret
+            #idl_gen
+        };
+    };
+
+    #[allow(unreachable_code)]
+    ret
 }

+ 915 - 0
lang/syn/src/idl/build.rs

@@ -0,0 +1,915 @@
+use crate::{parser::docs, AccountField, AccountsStruct, Error, Program};
+use heck::MixedCase;
+use proc_macro2::TokenStream;
+use quote::{format_ident, quote};
+pub use serde_json;
+use syn::{Ident, ItemEnum, ItemStruct};
+
+#[inline(always)]
+fn get_module_paths() -> (TokenStream, TokenStream) {
+    (
+        quote!(anchor_lang::anchor_syn::idl::types),
+        quote!(anchor_lang::anchor_syn::idl::build::serde_json),
+    )
+}
+
+#[inline(always)]
+pub fn get_no_docs() -> bool {
+    std::option_env!("ANCHOR_IDL_GEN_NO_DOCS")
+        .map(|val| val == "TRUE")
+        .unwrap_or(false)
+}
+
+#[inline(always)]
+pub fn get_seeds_feature() -> bool {
+    std::option_env!("ANCHOR_IDL_GEN_SEEDS_FEATURE")
+        .map(|val| val == "TRUE")
+        .unwrap_or(false)
+}
+
+// Returns TokenStream for IdlType enum and the syn::TypePath for the defined
+// type if any.
+// Returns Err when the type wasn't parsed successfully.
+#[allow(clippy::result_unit_err)]
+pub fn idl_type_ts_from_syn_type(
+    ty: &syn::Type,
+    type_params: &Vec<Ident>,
+) -> Result<(TokenStream, Vec<syn::TypePath>), ()> {
+    let (idl, _) = get_module_paths();
+
+    fn the_only_segment_is(path: &syn::TypePath, cmp: &str) -> bool {
+        if path.path.segments.len() != 1 {
+            return false;
+        };
+        return path.path.segments.first().unwrap().ident == cmp;
+    }
+
+    // Foo<first::path, second::path> -> first::path
+    fn get_first_angle_bracketed_path_arg(segment: &syn::PathSegment) -> Option<&syn::Type> {
+        match &segment.arguments {
+            syn::PathArguments::AngleBracketed(arguments) => match arguments.args.first() {
+                Some(syn::GenericArgument::Type(ty)) => Some(ty),
+                _ => None,
+            },
+            _ => None,
+        }
+    }
+
+    match ty {
+        syn::Type::Path(path) if the_only_segment_is(path, "bool") => {
+            Ok((quote! { #idl::IdlType::Bool }, vec![]))
+        }
+        syn::Type::Path(path) if the_only_segment_is(path, "u8") => {
+            Ok((quote! { #idl::IdlType::U8 }, vec![]))
+        }
+        syn::Type::Path(path) if the_only_segment_is(path, "i8") => {
+            Ok((quote! { #idl::IdlType::I8 }, vec![]))
+        }
+        syn::Type::Path(path) if the_only_segment_is(path, "u16") => {
+            Ok((quote! { #idl::IdlType::U16 }, vec![]))
+        }
+        syn::Type::Path(path) if the_only_segment_is(path, "i16") => {
+            Ok((quote! { #idl::IdlType::I16 }, vec![]))
+        }
+        syn::Type::Path(path) if the_only_segment_is(path, "u32") => {
+            Ok((quote! { #idl::IdlType::U32 }, vec![]))
+        }
+        syn::Type::Path(path) if the_only_segment_is(path, "i32") => {
+            Ok((quote! { #idl::IdlType::I32 }, vec![]))
+        }
+        syn::Type::Path(path) if the_only_segment_is(path, "f32") => {
+            Ok((quote! { #idl::IdlType::F32 }, vec![]))
+        }
+        syn::Type::Path(path) if the_only_segment_is(path, "u64") => {
+            Ok((quote! { #idl::IdlType::U64 }, vec![]))
+        }
+        syn::Type::Path(path) if the_only_segment_is(path, "i64") => {
+            Ok((quote! { #idl::IdlType::I64 }, vec![]))
+        }
+        syn::Type::Path(path) if the_only_segment_is(path, "f64") => {
+            Ok((quote! { #idl::IdlType::F64 }, vec![]))
+        }
+        syn::Type::Path(path) if the_only_segment_is(path, "u128") => {
+            Ok((quote! { #idl::IdlType::U128 }, vec![]))
+        }
+        syn::Type::Path(path) if the_only_segment_is(path, "i128") => {
+            Ok((quote! { #idl::IdlType::I128 }, vec![]))
+        }
+        syn::Type::Path(path)
+            if the_only_segment_is(path, "String") || the_only_segment_is(path, "&str") =>
+        {
+            Ok((quote! { #idl::IdlType::String }, vec![]))
+        }
+        syn::Type::Path(path) if the_only_segment_is(path, "Pubkey") => {
+            Ok((quote! { #idl::IdlType::PublicKey }, vec![]))
+        }
+        syn::Type::Path(path) if the_only_segment_is(path, "Vec") => {
+            let segment = path.path.segments.first().unwrap();
+            let arg = match get_first_angle_bracketed_path_arg(segment) {
+                Some(arg) => arg,
+                None => unreachable!("Vec arguments can only be of AngleBracketed variant"),
+            };
+            match arg {
+                syn::Type::Path(path) if the_only_segment_is(path, "u8") => {
+                    return Ok((quote! {#idl::IdlType::Bytes}, vec![]));
+                }
+                _ => (),
+            };
+            let (inner, defined) = idl_type_ts_from_syn_type(arg, type_params)?;
+            Ok((quote! { #idl::IdlType::Vec(Box::new(#inner)) }, defined))
+        }
+        syn::Type::Path(path) if the_only_segment_is(path, "Option") => {
+            let segment = path.path.segments.first().unwrap();
+            let arg = match get_first_angle_bracketed_path_arg(segment) {
+                Some(arg) => arg,
+                None => unreachable!("Option arguments can only be of AngleBracketed variant"),
+            };
+            let (inner, defined) = idl_type_ts_from_syn_type(arg, type_params)?;
+            Ok((quote! { #idl::IdlType::Option(Box::new(#inner)) }, defined))
+        }
+        syn::Type::Path(path) if the_only_segment_is(path, "Box") => {
+            let segment = path.path.segments.first().unwrap();
+            let arg = match get_first_angle_bracketed_path_arg(segment) {
+                Some(arg) => arg,
+                None => unreachable!("Box arguments can only be of AngleBracketed variant"),
+            };
+            let (ts, defined) = idl_type_ts_from_syn_type(arg, type_params)?;
+            Ok((quote! { #ts }, defined))
+        }
+        syn::Type::Array(arr) => {
+            let len = arr.len.clone();
+            let len_is_generic = type_params.iter().any(|param| match len {
+                syn::Expr::Path(ref path) => path.path.is_ident(param),
+                _ => false,
+            });
+
+            let (inner, defined) = idl_type_ts_from_syn_type(&arr.elem, type_params)?;
+
+            if len_is_generic {
+                match len {
+                    syn::Expr::Path(ref len) => {
+                        let len = len.path.get_ident().unwrap().to_string();
+                        Ok((
+                            quote! { #idl::IdlType::GenericLenArray(Box::new(#inner), #len.into()) },
+                            defined,
+                        ))
+                    }
+                    _ => unreachable!("Array length can only be a generic parameter"),
+                }
+            } else {
+                Ok((
+                    quote! { #idl::IdlType::Array(Box::new(#inner), #len) },
+                    defined,
+                ))
+            }
+        }
+        syn::Type::Path(path) => {
+            let is_generic_param = type_params.iter().any(|param| path.path.is_ident(param));
+
+            if is_generic_param {
+                let generic = format!("{}", path.path.get_ident().unwrap());
+                Ok((quote! { #idl::IdlType::Generic(#generic.into()) }, vec![]))
+            } else {
+                let mut params = vec![];
+                let mut defined = vec![path.clone()];
+
+                if let Some(segment) = &path.path.segments.last() {
+                    if let syn::PathArguments::AngleBracketed(ref args) = segment.arguments {
+                        for arg in &args.args {
+                            match arg {
+                                syn::GenericArgument::Type(ty) => {
+                                    let (ts, def) = idl_type_ts_from_syn_type(ty, type_params)?;
+                                    params.push(quote! { #idl::IdlDefinedTypeArg::Type(#ts) });
+                                    defined.extend(def);
+                                }
+                                syn::GenericArgument::Const(c) => params.push(
+                                    quote! { #idl::IdlDefinedTypeArg::Value(format!("{}", #c))},
+                                ),
+                                _ => (),
+                            }
+                        }
+                    }
+                }
+
+                if !params.is_empty() {
+                    let params = quote! { vec![#(#params),*] };
+                    Ok((
+                        quote! { #idl::IdlType::DefinedWithTypeArgs {
+                            path: <#path>::__anchor_private_full_path(),
+                            args: #params
+                        } },
+                        defined,
+                    ))
+                } else {
+                    Ok((
+                        quote! { #idl::IdlType::Defined(<#path>::__anchor_private_full_path()) },
+                        vec![path.clone()],
+                    ))
+                }
+            }
+        }
+        _ => Err(()),
+    }
+}
+
+// Returns TokenStream for IdlField struct and the syn::TypePath for the defined
+// type if any.
+// Returns Err when the type wasn't parsed successfully
+#[allow(clippy::result_unit_err)]
+pub fn idl_field_ts_from_syn_field(
+    field: &syn::Field,
+    no_docs: bool,
+    type_params: &Vec<syn::Ident>,
+) -> Result<(TokenStream, Vec<syn::TypePath>), ()> {
+    let (idl, _) = get_module_paths();
+
+    let name = field.ident.as_ref().unwrap().to_string().to_mixed_case();
+    let docs = match docs::parse(&field.attrs) {
+        Some(docs) if !no_docs => quote! {Some(vec![#(#docs.into()),*])},
+        _ => quote! {None},
+    };
+    let (ty, defined) = idl_type_ts_from_syn_type(&field.ty, type_params)?;
+
+    Ok((
+        quote! {
+            #idl::IdlField {
+                name: #name.into(),
+                docs: #docs,
+                ty: #ty,
+            }
+        },
+        defined,
+    ))
+}
+
+// Returns TokenStream for IdlEventField struct and the syn::TypePath for the defined
+// type if any.
+// Returns Err when the type wasn't parsed successfully
+#[allow(clippy::result_unit_err)]
+pub fn idl_event_field_ts_from_syn_field(
+    field: &syn::Field,
+) -> Result<(TokenStream, Vec<syn::TypePath>), ()> {
+    let (idl, _) = get_module_paths();
+
+    let name = field.ident.as_ref().unwrap().to_string().to_mixed_case();
+    let (ty, defined) = idl_type_ts_from_syn_type(&field.ty, &vec![])?;
+
+    let index: bool = field
+        .attrs
+        .get(0)
+        .and_then(|attr| attr.path.segments.first())
+        .map(|segment| segment.ident == "index")
+        .unwrap_or(false);
+
+    Ok((
+        quote! {
+            #idl::IdlEventField {
+                name: #name.into(),
+                ty: #ty,
+                index: #index,
+            }
+        },
+        defined,
+    ))
+}
+
+// Returns TokenStream for IdlTypeDefinitionTy::Struct and Vec<&syn::TypePath>
+// for the defined types if any.
+// Returns Err if any of the fields weren't parsed successfully.
+#[allow(clippy::result_unit_err)]
+pub fn idl_type_definition_ts_from_syn_struct(
+    item_strct: &syn::ItemStruct,
+    no_docs: bool,
+) -> Result<(TokenStream, Vec<syn::TypePath>), ()> {
+    let (idl, _) = get_module_paths();
+
+    let name = item_strct.ident.to_string();
+    let docs = match docs::parse(&item_strct.attrs) {
+        Some(docs) if !no_docs => quote! {Some(vec![#(#docs.into()),*])},
+        _ => quote! {None},
+    };
+
+    let type_params = item_strct
+        .generics
+        .params
+        .iter()
+        .filter_map(|p| match p {
+            syn::GenericParam::Type(ty) => Some(ty.ident.clone()),
+            syn::GenericParam::Const(c) => Some(c.ident.clone()),
+            _ => None,
+        })
+        .collect::<Vec<_>>();
+    let (fields, defined): (Vec<TokenStream>, Vec<Vec<syn::TypePath>>) = match &item_strct.fields {
+        syn::Fields::Named(fields) => fields
+            .named
+            .iter()
+            .map(|f: &syn::Field| idl_field_ts_from_syn_field(f, no_docs, &type_params))
+            .collect::<Result<Vec<_>, _>>()?
+            .into_iter()
+            .unzip::<_, _, Vec<_>, Vec<_>>(),
+        _ => return Err(()),
+    };
+    let defined = defined
+        .into_iter()
+        .flatten()
+        .collect::<Vec<syn::TypePath>>();
+
+    let generics = if !type_params.is_empty() {
+        let g: Vec<String> = type_params.iter().map(|id| id.to_string()).collect();
+        quote! { Some(vec![#(#g.into()),*]) }
+    } else {
+        quote! { None }
+    };
+
+    Ok((
+        quote! {
+            #idl::IdlTypeDefinition {
+                name: #name.into(),
+                path: Some(Self::__anchor_private_full_path()),
+                generics: #generics,
+                docs: #docs,
+                ty: #idl::IdlTypeDefinitionTy::Struct{
+                    fields: vec![
+                        #(#fields),*
+                    ]
+                }
+            },
+        },
+        defined,
+    ))
+}
+
+// Returns TokenStream for IdlTypeDefinitionTy::Enum and the Vec<&syn::TypePath>
+// for the defined types if any.
+// Returns Err if any of the fields didn't parse successfully.
+#[allow(clippy::result_unit_err)]
+pub fn idl_type_definition_ts_from_syn_enum(
+    enum_item: &syn::ItemEnum,
+    no_docs: bool,
+) -> Result<(TokenStream, Vec<syn::TypePath>), ()> {
+    let (idl, _) = get_module_paths();
+
+    let name = enum_item.ident.to_string();
+    let docs = match docs::parse(&enum_item.attrs) {
+        Some(docs) if !no_docs => quote! {Some(vec![#(#docs.into()),*])},
+        _ => quote! {None},
+    };
+
+    let type_params = enum_item
+        .generics
+        .params
+        .iter()
+        .filter_map(|p| match p {
+            syn::GenericParam::Type(ty) => Some(ty.ident.clone()),
+            syn::GenericParam::Const(c) => Some(c.ident.clone()),
+            _ => None,
+        })
+        .collect::<Vec<_>>();
+
+    let (variants, defined): (Vec<TokenStream>, Vec<Vec<syn::TypePath>>) = enum_item.variants.iter().map(|variant: &syn::Variant| {
+        let name = variant.ident.to_string();
+        let (fields, defined): (TokenStream, Vec<syn::TypePath>) = match &variant.fields {
+            syn::Fields::Unit => (quote!{None}, vec![]),
+            syn::Fields::Unnamed(fields) => {
+                let (types, defined) = fields.unnamed
+                    .iter()
+                    .map(|f| idl_type_ts_from_syn_type(&f.ty, &type_params))
+                    .collect::<Result<Vec<_>, _>>()?
+                    .into_iter()
+                    .unzip::<TokenStream, Vec<syn::TypePath>, Vec<TokenStream>, Vec<Vec<syn::TypePath>>>();
+                let defined = defined
+                    .into_iter()
+                    .flatten()
+                    .collect::<Vec<_>>();
+
+                (quote!{ Some(#idl::EnumFields::Tuple(vec![#(#types),*]))}, defined)
+            }
+            syn::Fields::Named(fields) => {
+                let (fields, defined) = fields.named
+                    .iter()
+                    .map(|f| idl_field_ts_from_syn_field(f, no_docs, &type_params))
+                    .collect::<Result<Vec<_>, _>>()?
+                    .into_iter()
+                    .unzip::<TokenStream, Vec<syn::TypePath>, Vec<TokenStream>, Vec<Vec<syn::TypePath>>>();
+                let defined = defined
+                    .into_iter()
+                    .flatten()
+                    .collect::<Vec<_>>();
+
+                (quote!{ Some(#idl::EnumFields::Named(vec![#(#fields),*]))}, defined)
+            }
+        };
+
+        Ok((quote!{ #idl::IdlEnumVariant{ name: #name.into(), fields: #fields }}, defined))
+    })
+    .collect::<Result<Vec<_>, _>>()?
+    .into_iter()
+    .unzip::<TokenStream, Vec<syn::TypePath>, Vec<TokenStream>, Vec<Vec<syn::TypePath>>>();
+
+    let defined = defined
+        .into_iter()
+        .flatten()
+        .collect::<Vec<syn::TypePath>>();
+
+    let generics = if !type_params.is_empty() {
+        let g: Vec<String> = type_params.iter().map(|id| id.to_string()).collect();
+        quote! { Some(vec![#(#g.into()),*]) }
+    } else {
+        quote! { None }
+    };
+
+    Ok((
+        quote! {
+            #idl::IdlTypeDefinition {
+                name: #name.into(),
+                path: Some(Self::__anchor_private_full_path()),
+                generics: #generics,
+                docs: #docs,
+                ty: #idl::IdlTypeDefinitionTy::Enum{
+                    variants: vec![
+                        #(#variants),*
+                    ]
+                }
+            }
+        },
+        defined,
+    ))
+}
+
+pub fn idl_gen_impl_skeleton(
+    idl_type_definition_ts: TokenStream,
+    insert_defined_ts: TokenStream,
+    ident: &Ident,
+    input_generics: &syn::Generics,
+) -> TokenStream {
+    let (idl, _) = get_module_paths();
+    let name = ident.to_string();
+    let (impl_generics, ty_generics, where_clause) = input_generics.split_for_impl();
+
+    quote! {
+        impl #impl_generics #ident #ty_generics #where_clause {
+            pub fn __anchor_private_full_path() -> String {
+                format!("{}::{}", std::module_path!(), #name)
+            }
+
+            pub fn __anchor_private_gen_idl_type() -> Option<#idl::IdlTypeDefinition> {
+                #idl_type_definition_ts
+            }
+
+            pub fn __anchor_private_insert_idl_defined(
+                defined_types: &mut std::collections::HashMap<String, #idl::IdlTypeDefinition>
+            ) {
+                #insert_defined_ts
+            }
+        }
+    }
+}
+
+// generates the IDL generation impl for for a struct
+pub fn gen_idl_gen_impl_for_struct(strct: &ItemStruct, no_docs: bool) -> TokenStream {
+    let idl_type_definition_ts: TokenStream;
+    let insert_defined_ts: TokenStream;
+
+    if let Ok((ts, defined)) = idl_type_definition_ts_from_syn_struct(strct, no_docs) {
+        idl_type_definition_ts = quote! {Some(#ts)};
+        insert_defined_ts = quote! {
+            #({
+                <#defined>::__anchor_private_insert_idl_defined(defined_types);
+
+                let path = <#defined>::__anchor_private_full_path();
+                <#defined>::__anchor_private_gen_idl_type()
+                    .and_then(|ty| defined_types.insert(path, ty));
+            });*
+        };
+    } else {
+        idl_type_definition_ts = quote! {None};
+        insert_defined_ts = quote! {};
+    }
+
+    let ident = &strct.ident;
+    let input_generics = &strct.generics;
+
+    idl_gen_impl_skeleton(
+        idl_type_definition_ts,
+        insert_defined_ts,
+        ident,
+        input_generics,
+    )
+}
+
+// generates the IDL generation impl for for an enum
+pub fn gen_idl_gen_impl_for_enum(enm: ItemEnum, no_docs: bool) -> TokenStream {
+    let idl_type_definition_ts: TokenStream;
+    let insert_defined_ts: TokenStream;
+
+    if let Ok((ts, defined)) = idl_type_definition_ts_from_syn_enum(&enm, no_docs) {
+        idl_type_definition_ts = quote! {Some(#ts)};
+        insert_defined_ts = quote! {
+            #({
+                <#defined>::__anchor_private_insert_idl_defined(defined_types);
+
+                let path = <#defined>::__anchor_private_full_path();
+                <#defined>::__anchor_private_gen_idl_type()
+                    .and_then(|ty| defined_types.insert(path, ty));
+            });*
+        };
+    } else {
+        idl_type_definition_ts = quote! {None};
+        insert_defined_ts = quote! {};
+    }
+
+    let ident = &enm.ident;
+    let input_generics = &enm.generics;
+
+    idl_gen_impl_skeleton(
+        idl_type_definition_ts,
+        insert_defined_ts,
+        ident,
+        input_generics,
+    )
+}
+
+// generates the IDL generation impl for for an event
+pub fn gen_idl_gen_impl_for_event(event_strct: &ItemStruct) -> TokenStream {
+    fn parse_fields(
+        fields: &syn::FieldsNamed,
+    ) -> Result<(Vec<TokenStream>, Vec<syn::TypePath>), ()> {
+        let (fields, defined) = fields
+            .named
+            .iter()
+            .map(idl_event_field_ts_from_syn_field)
+            .collect::<Result<Vec<_>, _>>()?
+            .into_iter()
+            .unzip::<_, _, Vec<_>, Vec<_>>();
+        let defined = defined
+            .into_iter()
+            .flatten()
+            .collect::<Vec<syn::TypePath>>();
+
+        Ok((fields, defined))
+    }
+
+    let res = match &event_strct.fields {
+        syn::Fields::Named(fields) => parse_fields(fields),
+        _ => Err(()),
+    };
+
+    let (idl, _) = get_module_paths();
+    let name = event_strct.ident.to_string();
+
+    let (ret_ts, types_ts) = match res {
+        Ok((fields, defined)) => {
+            let ret_ts = quote! {
+                Some(
+                    #idl::IdlEvent {
+                        name: #name.into(),
+                        fields: vec![#(#fields),*],
+                    }
+                )
+            };
+            let types_ts = quote! {
+                #({
+                    <#defined>::__anchor_private_insert_idl_defined(defined_types);
+
+                    let path = <#defined>::__anchor_private_full_path();
+                    <#defined>::__anchor_private_gen_idl_type()
+                        .and_then(|ty| defined_types.insert(path, ty));
+                });*
+            };
+            (ret_ts, types_ts)
+        }
+        Err(()) => (quote! { None }, quote! {}),
+    };
+
+    let ident = &event_strct.ident;
+    let input_generics = &event_strct.generics;
+    let (impl_generics, ty_generics, where_clause) = input_generics.split_for_impl();
+
+    quote! {
+        impl #impl_generics #ident #ty_generics #where_clause {
+            pub fn __anchor_private_gen_idl_event(
+                defined_types: &mut std::collections::HashMap<String, #idl::IdlTypeDefinition>,
+            ) -> Option<#idl::IdlEvent> {
+                #types_ts
+                #ret_ts
+            }
+        }
+    }
+}
+
+// generates the IDL generation impl for the Accounts struct
+pub fn gen_idl_gen_impl_for_accounts_strct(
+    accs_strct: &AccountsStruct,
+    no_docs: bool,
+) -> TokenStream {
+    let (idl, _) = get_module_paths();
+
+    let ident = &accs_strct.ident;
+    let (impl_generics, ty_generics, where_clause) = accs_strct.generics.split_for_impl();
+
+    let (accounts, acc_types): (Vec<TokenStream>, Vec<Option<&syn::TypePath>>) = accs_strct
+        .fields
+        .iter()
+        .map(|acc: &AccountField| match acc {
+            AccountField::CompositeField(comp_f) => {
+                let ty = if let syn::Type::Path(path) = &comp_f.raw_field.ty {
+                    // some::path::Foo<'info> -> some::path::Foo
+                    let mut res = syn::Path {
+                        leading_colon: path.path.leading_colon,
+                        segments: syn::punctuated::Punctuated::new(),
+                    };
+                    for segment in &path.path.segments {
+                        let s = syn::PathSegment {
+                            ident: segment.ident.clone(),
+                            arguments: syn::PathArguments::None,
+                        };
+                        res.segments.push(s);
+                    };
+                    res
+                } else {
+                    panic!("expecting path")
+                };
+                let name = comp_f.ident.to_string().to_mixed_case();
+                (quote!{
+                    #idl::IdlAccountItem::IdlAccounts(#idl::IdlAccounts {
+                        name: #name.into(),
+                        accounts: <#ty>::__anchor_private_gen_idl_accounts(accounts, defined_types),
+                    })
+                }, None)
+            }
+            AccountField::Field(acc) => {
+                let name = acc.ident.to_string().to_mixed_case();
+                let is_mut = acc.constraints.is_mutable();
+                let is_signer = match acc.ty {
+                    crate::Ty::Signer => true,
+                    _ => acc.constraints.is_signer()
+                };
+                let is_optional = if acc.is_optional { quote!{Some(true)} } else { quote!{None} };
+                let docs = match &acc.docs {
+                    Some(docs) if !no_docs => quote! {Some(vec![#(#docs.into()),*])},
+                    _ => quote! {None},
+                };
+                let pda = quote!{None}; // TODO
+                let relations = super::parse::relations::parse(acc, get_seeds_feature());
+
+                let acc_type_path = match &acc.ty {
+                    crate::Ty::Account(ty) => Some(&ty.account_type_path),
+                    crate::Ty::AccountLoader(ty) => Some(&ty.account_type_path),
+                    crate::Ty::InterfaceAccount(ty) => Some(&ty.account_type_path),
+                    _ => None,
+                };
+
+                (quote!{
+                    #idl::IdlAccountItem::IdlAccount(#idl::IdlAccount{
+                        name: #name.into(),
+                        is_mut: #is_mut,
+                        is_signer: #is_signer,
+                        is_optional: #is_optional,
+                        docs: #docs,
+                        pda: #pda,
+                        relations: vec![#(#relations.into()),*],
+                    })
+                }, acc_type_path)
+            }
+        })
+        .unzip::<TokenStream, Option<&syn::TypePath>, Vec<TokenStream>, Vec<Option<&syn::TypePath>>>();
+    let acc_types = acc_types
+        .into_iter()
+        .flatten()
+        .collect::<Vec<&syn::TypePath>>();
+
+    quote! {
+        impl #impl_generics #ident #ty_generics #where_clause {
+            pub fn __anchor_private_gen_idl_accounts(
+                accounts: &mut std::collections::HashMap<String, #idl::IdlTypeDefinition>,
+                defined_types: &mut std::collections::HashMap<String, #idl::IdlTypeDefinition>,
+            ) -> Vec<#idl::IdlAccountItem> {
+                #({
+                    <#acc_types>::__anchor_private_insert_idl_defined(defined_types);
+
+                    let path = <#acc_types>::__anchor_private_full_path();
+                    <#acc_types>::__anchor_private_gen_idl_type()
+                        .and_then(|ty| accounts.insert(path, ty));
+
+                });*
+
+                vec![#(#accounts),*]
+            }
+        }
+    }
+}
+
+// generates the IDL generation print function for the program module
+pub fn gen_idl_print_function_for_program(program: &Program, no_docs: bool) -> TokenStream {
+    let (idl, serde_json) = get_module_paths();
+
+    let (instructions, defined) = program
+        .ixs
+        .iter()
+        .flat_map(|ix| -> Result<_, ()> {
+            let name = ix.ident.to_string().to_mixed_case();
+            let docs = match &ix.docs {
+                Some(docs) if !no_docs => quote! {Some(vec![#(#docs.into()),*])},
+                _ => quote! {None},
+            };
+            let ctx_ident = &ix.anchor_ident;
+
+            let (args, mut defined) = ix
+                .args
+                .iter()
+                .map(|arg| {
+                    let arg_name = arg.name.to_string().to_mixed_case();
+                    let docs = match docs::parse(&arg.raw_arg.attrs) {
+                        Some(docs) if !no_docs => quote! {Some(vec![#(#docs.into()),*])},
+                        _ => quote! {None},
+                    };
+                    let (ty, defined) = idl_type_ts_from_syn_type(&arg.raw_arg.ty, &vec![])?;
+
+                    Ok((quote! {
+                        #idl::IdlField {
+                            name: #arg_name.into(),
+                            docs: #docs,
+                            ty: #ty,
+                        }
+                    }, defined))
+                })
+                .collect::<Result<Vec<_>, ()>>()?
+                .into_iter()
+                .unzip::<TokenStream, Vec<syn::TypePath>, Vec<TokenStream>, Vec<Vec<syn::TypePath>>>();
+
+            let returns = match idl_type_ts_from_syn_type(&ix.returns.ty, &vec![]) {
+                Ok((ty, def)) => {
+                    defined.push(def);
+                    quote!{ Some(#ty) }
+                },
+                Err(()) => quote!{ None }
+            };
+
+            Ok((quote! {
+                #idl::IdlInstruction {
+                    name: #name.into(),
+                    docs: #docs,
+                    accounts: #ctx_ident::__anchor_private_gen_idl_accounts(
+                        &mut accounts,
+                        &mut defined_types,
+                    ),
+                    args: vec![#(#args),*],
+                    returns: #returns,
+                }
+            }, defined))
+        })
+        .unzip::<TokenStream, Vec<Vec<syn::TypePath>>, Vec<TokenStream>, Vec<Vec<Vec<syn::TypePath>>>>();
+    let defined = defined
+        .into_iter()
+        .flatten()
+        .flatten()
+        .collect::<Vec<syn::TypePath>>();
+
+    let name = program.name.to_string();
+    let docs = match &program.docs {
+        Some(docs) if !no_docs => quote! {Some(vec![#(#docs.into()),*])},
+        _ => quote! {None},
+    };
+
+    quote! {
+        #[test]
+        pub fn __anchor_private_print_idl_program() {
+            let mut accounts: std::collections::HashMap<String, #idl::IdlTypeDefinition> =
+                std::collections::HashMap::new();
+            let mut defined_types: std::collections::HashMap<String, #idl::IdlTypeDefinition> =
+                std::collections::HashMap::new();
+
+            #({
+                <#defined>::__anchor_private_insert_idl_defined(&mut defined_types);
+
+                let path = <#defined>::__anchor_private_full_path();
+                <#defined>::__anchor_private_gen_idl_type()
+                    .and_then(|ty| defined_types.insert(path, ty));
+            });*
+
+            let instructions = vec![#(#instructions),*];
+
+            let idl = #idl::Idl {
+                version: env!("CARGO_PKG_VERSION").into(),
+                name: #name.into(),
+                docs: #docs,
+                constants: vec![],
+                instructions,
+                accounts: accounts.into_values().collect(),
+                types: defined_types.into_values().collect(),
+                events: None,
+                errors: None,
+                metadata: None,
+            };
+
+            println!("---- IDL begin program ----");
+            println!("{}", #serde_json::to_string_pretty(&idl).unwrap());
+            println!("---- IDL end program ----");
+        }
+    }
+}
+
+pub fn gen_idl_print_function_for_event(event: &ItemStruct) -> TokenStream {
+    let (idl, serde_json) = get_module_paths();
+
+    let ident = &event.ident;
+    let fn_name = format_ident!("__anchor_private_print_idl_event_{}", ident.to_string());
+    let impl_gen = gen_idl_gen_impl_for_event(event);
+
+    quote! {
+        #impl_gen
+
+        #[test]
+        pub fn #fn_name() {
+            let mut defined_types: std::collections::HashMap<String, #idl::IdlTypeDefinition> = std::collections::HashMap::new();
+            let event = #ident::__anchor_private_gen_idl_event(&mut defined_types);
+
+            if let Some(event) = event {
+                let json = #serde_json::json!({
+                    "event": event,
+                    "defined_types": defined_types.into_values().collect::<Vec<#idl::IdlTypeDefinition>>()
+                });
+
+                println!("---- IDL begin event ----");
+                println!("{}", #serde_json::to_string_pretty(&json).unwrap());
+                println!("---- IDL end event ----");
+            }
+        }
+    }
+}
+
+pub fn gen_idl_print_function_for_constant(item: &syn::ItemConst) -> TokenStream {
+    let fn_name = format_ident!(
+        "__anchor_private_print_idl_const_{}",
+        item.ident.to_string()
+    );
+    let (idl, serde_json) = get_module_paths();
+
+    let name = item.ident.to_string();
+    let expr = &item.expr;
+
+    let impl_ts = match idl_type_ts_from_syn_type(&item.ty, &vec![]) {
+        Ok((ty, _)) => quote! {
+            let value = format!("{}", #expr);
+
+            let idl = #idl::IdlConst {
+                name: #name.into(),
+                ty: #ty,
+                value,
+            };
+
+            println!("---- IDL begin const ----");
+            println!("{}", #serde_json::to_string_pretty(&idl).unwrap());
+            println!("---- IDL end const ----");
+        },
+        Err(()) => quote! {},
+    };
+
+    quote! {
+        #[test]
+        pub fn #fn_name() {
+            #impl_ts
+        }
+    }
+}
+
+pub fn gen_idl_print_function_for_error(error: &Error) -> TokenStream {
+    let fn_name = format_ident!(
+        "__anchor_private_print_idl_error_{}",
+        error.ident.to_string()
+    );
+    let (idl, serde_json) = get_module_paths();
+
+    let error_codes = error
+        .codes
+        .iter()
+        .map(|code| {
+            let id = code.id;
+            let name = code.ident.to_string();
+
+            let msg = match code.msg.clone() {
+                Some(msg) => quote! { Some(#msg.to_string()) },
+                None => quote! { None },
+            };
+
+            quote! {
+                #idl::IdlErrorCode {
+                    code: anchor_lang::error::ERROR_CODE_OFFSET + #id,
+                    name: #name.into(),
+                    msg: #msg,
+                }
+            }
+        })
+        .collect::<Vec<TokenStream>>();
+
+    quote! {
+        #[test]
+        pub fn #fn_name() {
+            let errors = vec![#(#error_codes),*];
+
+            println!("---- IDL begin errors ----");
+            println!("{}", #serde_json::to_string_pretty(&errors).unwrap());
+            println!("---- IDL end errors ----");
+        }
+    }
+}

+ 6 - 330
lang/syn/src/idl/mod.rs

@@ -1,330 +1,6 @@
-use serde::{Deserialize, Serialize};
-use serde_json::Value as JsonValue;
-
-pub mod file;
-pub mod pda;
-pub mod relations;
-
-#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
-pub struct Idl {
-    pub version: String,
-    pub name: String,
-    #[serde(skip_serializing_if = "Option::is_none", default)]
-    pub docs: Option<Vec<String>>,
-    #[serde(skip_serializing_if = "Vec::is_empty", default)]
-    pub constants: Vec<IdlConst>,
-    pub instructions: Vec<IdlInstruction>,
-    #[serde(skip_serializing_if = "Vec::is_empty", default)]
-    pub accounts: Vec<IdlTypeDefinition>,
-    #[serde(skip_serializing_if = "Vec::is_empty", default)]
-    pub types: Vec<IdlTypeDefinition>,
-    #[serde(skip_serializing_if = "Option::is_none", default)]
-    pub events: Option<Vec<IdlEvent>>,
-    #[serde(skip_serializing_if = "Option::is_none", default)]
-    pub errors: Option<Vec<IdlErrorCode>>,
-    #[serde(skip_serializing_if = "Option::is_none", default)]
-    pub metadata: Option<JsonValue>,
-}
-
-#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
-pub struct IdlConst {
-    pub name: String,
-    #[serde(rename = "type")]
-    pub ty: IdlType,
-    pub value: String,
-}
-
-#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
-pub struct IdlState {
-    #[serde(rename = "struct")]
-    pub strct: IdlTypeDefinition,
-    pub methods: Vec<IdlInstruction>,
-}
-
-#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
-pub struct IdlInstruction {
-    pub name: String,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub docs: Option<Vec<String>>,
-    pub accounts: Vec<IdlAccountItem>,
-    pub args: Vec<IdlField>,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub returns: Option<IdlType>,
-}
-
-#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
-#[serde(rename_all = "camelCase")]
-pub struct IdlAccounts {
-    pub name: String,
-    pub accounts: Vec<IdlAccountItem>,
-}
-
-#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
-#[serde(untagged)]
-pub enum IdlAccountItem {
-    IdlAccount(IdlAccount),
-    IdlAccounts(IdlAccounts),
-}
-
-#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
-#[serde(rename_all = "camelCase")]
-pub struct IdlAccount {
-    pub name: String,
-    pub is_mut: bool,
-    pub is_signer: bool,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub is_optional: Option<bool>,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub docs: Option<Vec<String>>,
-    #[serde(skip_serializing_if = "Option::is_none", default)]
-    pub pda: Option<IdlPda>,
-    #[serde(skip_serializing_if = "Vec::is_empty", default)]
-    pub relations: Vec<String>,
-}
-
-#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
-#[serde(rename_all = "camelCase")]
-pub struct IdlPda {
-    pub seeds: Vec<IdlSeed>,
-    #[serde(skip_serializing_if = "Option::is_none", default)]
-    pub program_id: Option<IdlSeed>,
-}
-
-#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
-#[serde(rename_all = "camelCase", tag = "kind")]
-pub enum IdlSeed {
-    Const(IdlSeedConst),
-    Arg(IdlSeedArg),
-    Account(IdlSeedAccount),
-}
-
-#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
-#[serde(rename_all = "camelCase")]
-pub struct IdlSeedAccount {
-    #[serde(rename = "type")]
-    pub ty: IdlType,
-    // account_ty points to the entry in the "accounts" section.
-    // Some only if the `Account<T>` type is used.
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub account: Option<String>,
-    pub path: String,
-}
-
-#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
-#[serde(rename_all = "camelCase")]
-pub struct IdlSeedArg {
-    #[serde(rename = "type")]
-    pub ty: IdlType,
-    pub path: String,
-}
-
-#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
-#[serde(rename_all = "camelCase")]
-pub struct IdlSeedConst {
-    #[serde(rename = "type")]
-    pub ty: IdlType,
-    pub value: serde_json::Value,
-}
-
-#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
-pub struct IdlField {
-    pub name: String,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub docs: Option<Vec<String>>,
-    #[serde(rename = "type")]
-    pub ty: IdlType,
-}
-
-#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
-pub struct IdlEvent {
-    pub name: String,
-    pub fields: Vec<IdlEventField>,
-}
-
-#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
-pub struct IdlEventField {
-    pub name: String,
-    #[serde(rename = "type")]
-    pub ty: IdlType,
-    pub index: bool,
-}
-
-#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
-pub struct IdlTypeDefinition {
-    pub name: String,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub docs: Option<Vec<String>>,
-    #[serde(rename = "type")]
-    pub ty: IdlTypeDefinitionTy,
-}
-
-#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
-#[serde(rename_all = "lowercase", tag = "kind")]
-pub enum IdlTypeDefinitionTy {
-    Struct { fields: Vec<IdlField> },
-    Enum { variants: Vec<IdlEnumVariant> },
-}
-
-#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
-pub struct IdlEnumVariant {
-    pub name: String,
-    #[serde(skip_serializing_if = "Option::is_none", default)]
-    pub fields: Option<EnumFields>,
-}
-
-#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
-#[serde(untagged)]
-pub enum EnumFields {
-    Named(Vec<IdlField>),
-    Tuple(Vec<IdlType>),
-}
-
-#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
-#[serde(rename_all = "camelCase")]
-pub enum IdlType {
-    Bool,
-    U8,
-    I8,
-    U16,
-    I16,
-    U32,
-    I32,
-    F32,
-    U64,
-    I64,
-    F64,
-    U128,
-    I128,
-    U256,
-    I256,
-    Bytes,
-    String,
-    PublicKey,
-    Defined(String),
-    Option(Box<IdlType>),
-    Vec(Box<IdlType>),
-    Array(Box<IdlType>, usize),
-}
-
-impl std::str::FromStr for IdlType {
-    type Err = anyhow::Error;
-
-    fn from_str(s: &str) -> Result<Self, Self::Err> {
-        let mut s = s.to_string();
-        fn array_from_str(inner: &str) -> IdlType {
-            match inner.strip_suffix(']') {
-                None => {
-                    let (raw_type, raw_length) = inner.rsplit_once(';').unwrap();
-                    let ty = IdlType::from_str(raw_type).unwrap();
-                    let len = raw_length.replace('_', "").parse::<usize>().unwrap();
-                    IdlType::Array(Box::new(ty), len)
-                }
-                Some(nested_inner) => array_from_str(&nested_inner[1..]),
-            }
-        }
-        s.retain(|c| !c.is_whitespace());
-
-        let r = match s.as_str() {
-            "bool" => IdlType::Bool,
-            "u8" => IdlType::U8,
-            "i8" => IdlType::I8,
-            "u16" => IdlType::U16,
-            "i16" => IdlType::I16,
-            "u32" => IdlType::U32,
-            "i32" => IdlType::I32,
-            "f32" => IdlType::F32,
-            "u64" => IdlType::U64,
-            "i64" => IdlType::I64,
-            "f64" => IdlType::F64,
-            "u128" => IdlType::U128,
-            "i128" => IdlType::I128,
-            "u256" => IdlType::U256,
-            "i256" => IdlType::I256,
-            "Vec<u8>" => IdlType::Bytes,
-            "String" | "&str" | "&'staticstr" => IdlType::String,
-            "Pubkey" => IdlType::PublicKey,
-            _ => match s.to_string().strip_prefix("Option<") {
-                None => match s.to_string().strip_prefix("Vec<") {
-                    None => {
-                        if s.to_string().starts_with('[') {
-                            array_from_str(&s)
-                        } else {
-                            IdlType::Defined(s.to_string())
-                        }
-                    }
-                    Some(inner) => {
-                        let inner_ty = Self::from_str(
-                            inner
-                                .strip_suffix('>')
-                                .ok_or_else(|| anyhow::anyhow!("Invalid option"))?,
-                        )?;
-                        IdlType::Vec(Box::new(inner_ty))
-                    }
-                },
-                Some(inner) => {
-                    let inner_ty = Self::from_str(
-                        inner
-                            .strip_suffix('>')
-                            .ok_or_else(|| anyhow::anyhow!("Invalid option"))?,
-                    )?;
-                    IdlType::Option(Box::new(inner_ty))
-                }
-            },
-        };
-        Ok(r)
-    }
-}
-
-#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
-pub struct IdlErrorCode {
-    pub code: u32,
-    pub name: String,
-    #[serde(skip_serializing_if = "Option::is_none", default)]
-    pub msg: Option<String>,
-}
-
-#[cfg(test)]
-mod tests {
-    use crate::idl::IdlType;
-    use std::str::FromStr;
-
-    #[test]
-    fn multidimensional_array() {
-        assert_eq!(
-            IdlType::from_str("[[u8;16];32]").unwrap(),
-            IdlType::Array(Box::new(IdlType::Array(Box::new(IdlType::U8), 16)), 32)
-        );
-    }
-
-    #[test]
-    fn array() {
-        assert_eq!(
-            IdlType::from_str("[Pubkey;16]").unwrap(),
-            IdlType::Array(Box::new(IdlType::PublicKey), 16)
-        );
-    }
-
-    #[test]
-    fn array_with_underscored_length() {
-        assert_eq!(
-            IdlType::from_str("[u8;50_000]").unwrap(),
-            IdlType::Array(Box::new(IdlType::U8), 50000)
-        );
-    }
-
-    #[test]
-    fn option() {
-        assert_eq!(
-            IdlType::from_str("Option<bool>").unwrap(),
-            IdlType::Option(Box::new(IdlType::Bool))
-        )
-    }
-
-    #[test]
-    fn vector() {
-        assert_eq!(
-            IdlType::from_str("Vec<bool>").unwrap(),
-            IdlType::Vec(Box::new(IdlType::Bool))
-        )
-    }
-}
+#[cfg(feature = "idl-build")]
+pub mod build;
+#[cfg(any(feature = "idl-parse", feature = "idl-build"))]
+pub mod parse;
+#[cfg(any(feature = "idl-types", feature = "idl-build", feature = "idl-parse"))]
+pub mod types;

+ 8 - 2
lang/syn/src/idl/file.rs → lang/syn/src/idl/parse/file.rs

@@ -1,4 +1,4 @@
-use crate::idl::*;
+use crate::idl::types::*;
 use crate::parser::context::CrateContext;
 use crate::parser::{self, accounts, docs, error, program};
 use crate::Ty;
@@ -13,6 +13,8 @@ use syn::{
     Lit::{Byte, ByteStr},
 };
 
+use super::relations;
+
 const DERIVE_NAME: &str = "Accounts";
 // TODO: share this with `anchor_lang` crate.
 const ERROR_CODE_OFFSET: u32 = 6000;
@@ -346,6 +348,8 @@ fn parse_ty_defs(ctx: &CrateContext, no_docs: bool) -> Result<Vec<IdlTypeDefinit
 
             Some(fields.map(|fields| IdlTypeDefinition {
                 name,
+                path: None,
+                generics: None,
                 docs: doc,
                 ty: IdlTypeDefinitionTy::Struct { fields },
             }))
@@ -404,6 +408,8 @@ fn parse_ty_defs(ctx: &CrateContext, no_docs: bool) -> Result<Vec<IdlTypeDefinit
                 .collect::<Vec<IdlEnumVariant>>();
             Some(Ok(IdlTypeDefinition {
                 name,
+                path: None,
+                generics: None,
                 docs: doc,
                 ty: IdlTypeDefinitionTy::Enum { variants },
             }))
@@ -547,7 +553,7 @@ fn idl_accounts(
                 },
                 is_optional: if acc.is_optional { Some(true) } else { None },
                 docs: if !no_docs { acc.docs.clone() } else { None },
-                pda: pda::parse(ctx, accounts, acc, seeds_feature),
+                pda: super::pda::parse(ctx, accounts, acc, seeds_feature),
                 relations: relations::parse(acc, seeds_feature),
             }),
         })

+ 120 - 0
lang/syn/src/idl/parse/mod.rs

@@ -0,0 +1,120 @@
+use crate::idl::types::*;
+
+pub mod file;
+pub mod pda;
+pub mod relations;
+
+impl std::str::FromStr for IdlType {
+    type Err = anyhow::Error;
+
+    fn from_str(s: &str) -> Result<Self, Self::Err> {
+        let mut s = s.to_string();
+        fn array_from_str(inner: &str) -> IdlType {
+            match inner.strip_suffix(']') {
+                None => {
+                    let (raw_type, raw_length) = inner.rsplit_once(';').unwrap();
+                    let ty = IdlType::from_str(raw_type).unwrap();
+                    let len = raw_length.replace('_', "").parse::<usize>().unwrap();
+                    IdlType::Array(Box::new(ty), len)
+                }
+                Some(nested_inner) => array_from_str(&nested_inner[1..]),
+            }
+        }
+        s.retain(|c| !c.is_whitespace());
+
+        let r = match s.as_str() {
+            "bool" => IdlType::Bool,
+            "u8" => IdlType::U8,
+            "i8" => IdlType::I8,
+            "u16" => IdlType::U16,
+            "i16" => IdlType::I16,
+            "u32" => IdlType::U32,
+            "i32" => IdlType::I32,
+            "f32" => IdlType::F32,
+            "u64" => IdlType::U64,
+            "i64" => IdlType::I64,
+            "f64" => IdlType::F64,
+            "u128" => IdlType::U128,
+            "i128" => IdlType::I128,
+            "u256" => IdlType::U256,
+            "i256" => IdlType::I256,
+            "Vec<u8>" => IdlType::Bytes,
+            "String" | "&str" | "&'staticstr" => IdlType::String,
+            "Pubkey" => IdlType::PublicKey,
+            _ => match s.to_string().strip_prefix("Option<") {
+                None => match s.to_string().strip_prefix("Vec<") {
+                    None => {
+                        if s.to_string().starts_with('[') {
+                            array_from_str(&s)
+                        } else {
+                            IdlType::Defined(s.to_string())
+                        }
+                    }
+                    Some(inner) => {
+                        let inner_ty = Self::from_str(
+                            inner
+                                .strip_suffix('>')
+                                .ok_or_else(|| anyhow::anyhow!("Invalid option"))?,
+                        )?;
+                        IdlType::Vec(Box::new(inner_ty))
+                    }
+                },
+                Some(inner) => {
+                    let inner_ty = Self::from_str(
+                        inner
+                            .strip_suffix('>')
+                            .ok_or_else(|| anyhow::anyhow!("Invalid option"))?,
+                    )?;
+                    IdlType::Option(Box::new(inner_ty))
+                }
+            },
+        };
+        Ok(r)
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use crate::idl::types::IdlType;
+    use std::str::FromStr;
+
+    #[test]
+    fn multidimensional_array() {
+        assert_eq!(
+            IdlType::from_str("[[u8;16];32]").unwrap(),
+            IdlType::Array(Box::new(IdlType::Array(Box::new(IdlType::U8), 16)), 32)
+        );
+    }
+
+    #[test]
+    fn array() {
+        assert_eq!(
+            IdlType::from_str("[Pubkey;16]").unwrap(),
+            IdlType::Array(Box::new(IdlType::PublicKey), 16)
+        );
+    }
+
+    #[test]
+    fn array_with_underscored_length() {
+        assert_eq!(
+            IdlType::from_str("[u8;50_000]").unwrap(),
+            IdlType::Array(Box::new(IdlType::U8), 50000)
+        );
+    }
+
+    #[test]
+    fn option() {
+        assert_eq!(
+            IdlType::from_str("Option<bool>").unwrap(),
+            IdlType::Option(Box::new(IdlType::Bool))
+        )
+    }
+
+    #[test]
+    fn vector() {
+        assert_eq!(
+            IdlType::from_str("Vec<bool>").unwrap(),
+            IdlType::Vec(Box::new(IdlType::Bool))
+        )
+    }
+}

+ 1 - 1
lang/syn/src/idl/pda.rs → lang/syn/src/idl/parse/pda.rs

@@ -1,4 +1,4 @@
-use crate::idl::*;
+use crate::idl::types::*;
 use crate::parser;
 use crate::parser::context::CrateContext;
 use crate::ConstraintSeedsGroup;

+ 0 - 0
lang/syn/src/idl/relations.rs → lang/syn/src/idl/parse/relations.rs


+ 229 - 0
lang/syn/src/idl/types.rs

@@ -0,0 +1,229 @@
+use serde::{Deserialize, Serialize};
+use serde_json::Value as JsonValue;
+
+#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
+pub struct Idl {
+    pub version: String,
+    pub name: String,
+    #[serde(skip_serializing_if = "Option::is_none", default)]
+    pub docs: Option<Vec<String>>,
+    #[serde(skip_serializing_if = "Vec::is_empty", default)]
+    pub constants: Vec<IdlConst>,
+    pub instructions: Vec<IdlInstruction>,
+    #[serde(skip_serializing_if = "Vec::is_empty", default)]
+    pub accounts: Vec<IdlTypeDefinition>,
+    #[serde(skip_serializing_if = "Vec::is_empty", default)]
+    pub types: Vec<IdlTypeDefinition>,
+    #[serde(skip_serializing_if = "Option::is_none", default)]
+    pub events: Option<Vec<IdlEvent>>,
+    #[serde(skip_serializing_if = "Option::is_none", default)]
+    pub errors: Option<Vec<IdlErrorCode>>,
+    #[serde(skip_serializing_if = "Option::is_none", default)]
+    pub metadata: Option<JsonValue>,
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
+pub struct IdlConst {
+    pub name: String,
+    #[serde(rename = "type")]
+    pub ty: IdlType,
+    pub value: String,
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
+pub struct IdlState {
+    #[serde(rename = "struct")]
+    pub strct: IdlTypeDefinition,
+    pub methods: Vec<IdlInstruction>,
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
+pub struct IdlInstruction {
+    pub name: String,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub docs: Option<Vec<String>>,
+    pub accounts: Vec<IdlAccountItem>,
+    pub args: Vec<IdlField>,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub returns: Option<IdlType>,
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
+#[serde(rename_all = "camelCase")]
+pub struct IdlAccounts {
+    pub name: String,
+    pub accounts: Vec<IdlAccountItem>,
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
+#[serde(untagged)]
+pub enum IdlAccountItem {
+    IdlAccount(IdlAccount),
+    IdlAccounts(IdlAccounts),
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
+#[serde(rename_all = "camelCase")]
+pub struct IdlAccount {
+    pub name: String,
+    pub is_mut: bool,
+    pub is_signer: bool,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub is_optional: Option<bool>,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub docs: Option<Vec<String>>,
+    #[serde(skip_serializing_if = "Option::is_none", default)]
+    pub pda: Option<IdlPda>,
+    #[serde(skip_serializing_if = "Vec::is_empty", default)]
+    pub relations: Vec<String>,
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
+#[serde(rename_all = "camelCase")]
+pub struct IdlPda {
+    pub seeds: Vec<IdlSeed>,
+    #[serde(skip_serializing_if = "Option::is_none", default)]
+    pub program_id: Option<IdlSeed>,
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
+#[serde(rename_all = "camelCase", tag = "kind")]
+pub enum IdlSeed {
+    Const(IdlSeedConst),
+    Arg(IdlSeedArg),
+    Account(IdlSeedAccount),
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
+#[serde(rename_all = "camelCase")]
+pub struct IdlSeedAccount {
+    #[serde(rename = "type")]
+    pub ty: IdlType,
+    // account_ty points to the entry in the "accounts" section.
+    // Some only if the `Account<T>` type is used.
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub account: Option<String>,
+    pub path: String,
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
+#[serde(rename_all = "camelCase")]
+pub struct IdlSeedArg {
+    #[serde(rename = "type")]
+    pub ty: IdlType,
+    pub path: String,
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
+#[serde(rename_all = "camelCase")]
+pub struct IdlSeedConst {
+    #[serde(rename = "type")]
+    pub ty: IdlType,
+    pub value: serde_json::Value,
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
+pub struct IdlField {
+    pub name: String,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub docs: Option<Vec<String>>,
+    #[serde(rename = "type")]
+    pub ty: IdlType,
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
+pub struct IdlEvent {
+    pub name: String,
+    pub fields: Vec<IdlEventField>,
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
+pub struct IdlEventField {
+    pub name: String,
+    #[serde(rename = "type")]
+    pub ty: IdlType,
+    pub index: bool,
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
+pub struct IdlTypeDefinition {
+    pub name: String,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub path: Option<String>,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub generics: Option<Vec<String>>,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub docs: Option<Vec<String>>,
+    #[serde(rename = "type")]
+    pub ty: IdlTypeDefinitionTy,
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
+#[serde(rename_all = "lowercase", tag = "kind")]
+pub enum IdlTypeDefinitionTy {
+    Struct { fields: Vec<IdlField> },
+    Enum { variants: Vec<IdlEnumVariant> },
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
+pub struct IdlEnumVariant {
+    pub name: String,
+    #[serde(skip_serializing_if = "Option::is_none", default)]
+    pub fields: Option<EnumFields>,
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
+#[serde(untagged)]
+pub enum EnumFields {
+    Named(Vec<IdlField>),
+    Tuple(Vec<IdlType>),
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
+#[serde(rename_all = "camelCase")]
+pub enum IdlType {
+    Bool,
+    U8,
+    I8,
+    U16,
+    I16,
+    U32,
+    I32,
+    F32,
+    U64,
+    I64,
+    F64,
+    U128,
+    I128,
+    U256,
+    I256,
+    Bytes,
+    String,
+    PublicKey,
+    Defined(String),
+    Option(Box<IdlType>),
+    Vec(Box<IdlType>),
+    Array(Box<IdlType>, usize),
+    GenericLenArray(Box<IdlType>, String),
+    Generic(String),
+    DefinedWithTypeArgs {
+        path: String,
+        args: Vec<IdlDefinedTypeArg>,
+    },
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
+#[serde(rename_all = "camelCase")]
+pub enum IdlDefinedTypeArg {
+    Generic(String),
+    Value(String),
+    Type(IdlType),
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
+pub struct IdlErrorCode {
+    pub code: u32,
+    pub name: String,
+    #[serde(skip_serializing_if = "Option::is_none", default)]
+    pub msg: Option<String>,
+}

+ 0 - 1
lang/syn/src/lib.rs

@@ -23,7 +23,6 @@ pub mod codegen;
 pub mod hash;
 #[cfg(not(feature = "hash"))]
 pub(crate) mod hash;
-#[cfg(feature = "idl")]
 pub mod idl;
 pub mod parser;
 

+ 13 - 0
tests/idl-build/Anchor.toml

@@ -0,0 +1,13 @@
+[features]
+seeds = true
+
+[programs.localnet]
+idl = "Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS"
+idl_2 = "Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS"
+
+[provider]
+cluster = "localnet"
+wallet = "~/.config/solana/id.json"
+
+[scripts]
+test = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts"

+ 13 - 0
tests/idl-build/Cargo.toml

@@ -0,0 +1,13 @@
+[workspace]
+members = [
+    "programs/*"
+]
+
+[profile.release]
+overflow-checks = true
+lto = "fat"
+codegen-units = 1
+[profile.release.build-override]
+opt-level = 3
+incremental = false
+codegen-units = 1

+ 11 - 0
tests/idl-build/gen_testdata.sh

@@ -0,0 +1,11 @@
+#!/usr/bin/env bash
+
+cd programs/idl
+anchor idl parse --file src/lib.rs > ../../tests/testdata/idl_parse_exp.json
+anchor idl build > ../../tests/testdata/idl_build_exp.json
+
+cd ../generics
+anchor idl build > ../../tests/testdata/generics_build_exp.json
+
+cd ../relations-derivation
+anchor idl build > ../../tests/testdata/relations_build_exp.json

+ 16 - 0
tests/idl-build/package.json

@@ -0,0 +1,16 @@
+{
+  "name": "idl-build",
+  "version": "0.28.0",
+  "license": "(MIT OR Apache-2.0)",
+  "homepage": "https://github.com/coral-xyz/anchor#readme",
+  "bugs": {
+    "url": "https://github.com/coral-xyz/anchor/issues"
+  },
+  "repository": {
+    "type": "git",
+    "url": "https://github.com/coral-xyz/anchor.git"
+  },
+  "engines": {
+    "node": ">=17"
+  }
+}

+ 25 - 0
tests/idl-build/programs/generics/Cargo.toml

@@ -0,0 +1,25 @@
+[package]
+name = "generics"
+version = "0.1.0"
+description = "Created with Anchor"
+edition = "2021"
+
+[lib]
+crate-type = ["cdylib", "lib"]
+name = "generics"
+
+[features]
+no-entrypoint = []
+no-idl = []
+no-log-ix-name = []
+cpi = ["no-entrypoint"]
+idl-build = [
+    "anchor-lang/idl-build",
+    "some-external-program/idl-build",
+]
+default = []
+
+[dependencies]
+anchor-lang = { path = "../../../../lang" }
+anchor-spl = { path = "../../../../spl" }
+some-external-program = { path = "../some_external_program", features = ["no-entrypoint"] }

+ 2 - 0
tests/idl-build/programs/generics/Xargo.toml

@@ -0,0 +1,2 @@
+[target.bpfel-unknown-unknown.dependencies.std]
+features = []

+ 80 - 0
tests/idl-build/programs/generics/src/lib.rs

@@ -0,0 +1,80 @@
+use anchor_lang::prelude::*;
+use some_external_program;
+
+declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");
+
+/// This is an example program used for testing
+#[program]
+pub mod example_program {
+    use super::*;
+
+    pub fn generic(
+        ctx: Context<GenericCtx>,
+        generic_field: GenericType::<u32, u64, 10>
+    ) -> Result<()>{
+        ctx.accounts.generic_acc.data = generic_field;
+        Ok(())
+    }
+}
+
+#[derive(Accounts)]
+pub struct GenericCtx<'info> {
+    generic_acc: Account<'info, GenericAccount>,
+
+    #[account(mut)]
+    payer: Signer<'info>,
+    system_program: Program<'info, System>,
+}
+
+#[account]
+pub struct GenericAccount {
+    pub data: GenericType<u32, u64, 10>
+}
+
+#[derive(AnchorSerialize, AnchorDeserialize, Clone)]
+pub struct GenericType<T, U, const N: usize>
+where
+    T: AnchorSerialize + AnchorDeserialize,
+    U: AnchorSerialize + AnchorDeserialize,
+{
+    pub gen1: T,
+    pub gen2: U,
+    pub gen3: GenericNested<u32, U>,
+    pub gen4: GenericNested<T, some_external_program::Baz>,
+    pub gen5: GenericNested<T, U>,
+    pub gen6: GenericNested<u32, u64>,
+    pub gen7: GenericNested<T, GenericNested<T, U>>,
+    pub arr: [u8; N],
+    pub warr: WrappedU8Array<N>,
+    pub warrval: WrappedU8Array<10>,
+    pub enm1: GenericEnum<T, U, N>,
+    pub enm2: GenericEnum<GenericNested<T, u64>, u32, 30>,
+}
+
+#[derive(AnchorSerialize, AnchorDeserialize, Clone, Copy, Default)]
+pub struct GenericNested<V, Z>
+where
+    V: AnchorSerialize + AnchorDeserialize,
+    Z: AnchorSerialize + AnchorDeserialize,
+{
+    pub gen1: V,
+    pub gen2: Z,
+}
+
+#[derive(AnchorSerialize, AnchorDeserialize, Clone)]
+pub struct WrappedU8Array<const N: usize>(u8);
+
+#[derive(AnchorSerialize, AnchorDeserialize, Clone)]
+pub enum GenericEnum<T, U, const N: usize>
+where
+    T: AnchorSerialize + AnchorDeserialize,
+    U: AnchorSerialize + AnchorDeserialize,
+{
+    Unnamed(T, U),
+    Named {
+        gen1: T,
+        gen2: U,
+    },
+    Struct(GenericNested<T, U>),
+    Arr([T; N]),
+}

+ 26 - 0
tests/idl-build/programs/idl/Cargo.toml

@@ -0,0 +1,26 @@
+[package]
+name = "idl"
+version = "0.1.0"
+description = "Created with Anchor"
+edition = "2021"
+
+[lib]
+crate-type = ["cdylib", "lib"]
+name = "idl"
+
+[features]
+no-entrypoint = []
+no-idl = []
+no-log-ix-name = []
+cpi = ["no-entrypoint"]
+idl-build = [
+    "anchor-lang/idl-build",
+    "some-external-program/idl-build",
+]
+default = []
+
+[dependencies]
+anchor-lang = { path = "../../../../lang" }
+anchor-spl = { path = "../../../../spl" }
+bytemuck = {version = "1.4.0", features = ["derive", "min_const_generics"]}
+some-external-program = { path = "../some_external_program", features = ["no-entrypoint"] }

+ 2 - 0
tests/idl-build/programs/idl/Xargo.toml

@@ -0,0 +1,2 @@
+[target.bpfel-unknown-unknown.dependencies.std]
+features = []

+ 326 - 0
tests/idl-build/programs/idl/src/lib.rs

@@ -0,0 +1,326 @@
+use anchor_lang::prelude::*;
+use some_external_program;
+use std::str::FromStr;
+
+declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");
+
+#[constant]
+pub const FOO_CONST: u128 = 1_000_000;
+#[constant]
+pub const BAR_CONST: u8 = 6;
+
+/// This is an example program used for testing
+#[program]
+pub mod example_program {
+    use super::*;
+
+    pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
+        ctx.accounts.state.set_inner(State::default());
+        Ok(())
+    }
+
+    /// Initializes an account with specified values
+    pub fn initialize_with_values(
+        ctx: Context<Initialize>,
+        bool_field: bool,
+        u8_field: u8,
+        i8_field: i8,
+        u16_field: u16,
+        i16_field: i16,
+        u32_field: u32,
+        i32_field: i32,
+        f32_field: f32,
+        u64_field: u64,
+        i64_field: i64,
+        f64_field: f64,
+        u128_field: u128,
+        i128_field: i128,
+        bytes_field: Vec<u8>,
+        string_field: String,
+        pubkey_field: Pubkey,
+        vec_field: Vec<u64>,
+        vec_struct_field: Vec<FooStruct>,
+        option_field: Option<bool>,
+        option_struct_field: Option<FooStruct>,
+        struct_field: FooStruct,
+        array_field: [bool; 3],
+        enum_field_1: FooEnum,
+        enum_field_2: FooEnum,
+        enum_field_3: FooEnum,
+        enum_field_4: FooEnum,
+    ) -> Result<()> {
+        ctx.accounts.state.set_inner(State {
+            bool_field,
+            u8_field,
+            i8_field,
+            u16_field,
+            i16_field,
+            u32_field,
+            i32_field,
+            f32_field,
+            u64_field,
+            i64_field,
+            f64_field,
+            u128_field,
+            i128_field,
+            bytes_field,
+            string_field,
+            pubkey_field,
+            vec_field,
+            vec_struct_field,
+            option_field,
+            option_struct_field,
+            struct_field,
+            array_field,
+            enum_field_1,
+            enum_field_2,
+            enum_field_3,
+            enum_field_4,
+        });
+
+        Ok(())
+    }
+
+    /// a separate instruction due to initialize_with_values having too many arguments
+    /// https://github.com/solana-labs/solana/issues/23978
+    pub fn initialize_with_values2(
+        ctx: Context<Initialize2>,
+        vec_of_option: Vec<Option<u64>>,
+        box_field: Box<bool>,
+    ) -> Result<SomeRetStruct> {
+        ctx.accounts.state.set_inner(State2 { vec_of_option, box_field });
+        Ok(SomeRetStruct { some_field: 3})
+    }
+
+    pub fn cause_error(_ctx: Context<CauseError>) -> Result<()> {
+        return Err(error!(ErrorCode::SomeError));
+    }
+}
+
+/// Enum type
+#[derive(AnchorSerialize, AnchorDeserialize, Clone)]
+pub enum FooEnum {
+    /// Tuple kind
+    Unnamed(bool, u8, BarStruct),
+    UnnamedSingle(BarStruct),
+    Named {
+        /// A bool field inside a struct tuple kind
+        bool_field: bool,
+        u8_field: u8,
+        nested: BarStruct,
+    },
+    Struct(BarStruct),
+    OptionStruct(Option<BarStruct>),
+    VecStruct(Vec<BarStruct>),
+    NoFields,
+}
+
+/// Bar struct type
+#[derive(AnchorSerialize, AnchorDeserialize, Clone)]
+pub struct BarStruct {
+    /// Some field
+    some_field: bool,
+    other_field: u8,
+}
+
+impl Default for BarStruct {
+    fn default() -> Self {
+        return BarStruct {
+            some_field: true,
+            other_field: 10,
+        };
+    }
+}
+
+#[derive(AnchorSerialize, AnchorDeserialize, Clone)]
+pub struct FooStruct {
+    field1: u8,
+    field2: u16,
+    nested: BarStruct,
+    vec_nested: Vec<BarStruct>,
+    option_nested: Option<BarStruct>,
+    enum_field: FooEnum,
+}
+
+impl Default for FooStruct {
+    fn default() -> Self {
+        return FooStruct {
+            field1: 123,
+            field2: 999,
+            nested: BarStruct::default(),
+            vec_nested: vec![BarStruct::default()],
+            option_nested: Some(BarStruct::default()),
+            enum_field: FooEnum::Named {
+                bool_field: true,
+                u8_field: 15,
+                nested: BarStruct::default(),
+            },
+        };
+    }
+}
+
+/// An account containing various fields
+#[account]
+pub struct State {
+    /// A boolean field
+    bool_field: bool,
+    u8_field: u8,
+    i8_field: i8,
+    u16_field: u16,
+    i16_field: i16,
+    u32_field: u32,
+    i32_field: i32,
+    f32_field: f32,
+    u64_field: u64,
+    i64_field: i64,
+    f64_field: f64,
+    u128_field: u128,
+    i128_field: i128,
+    bytes_field: Vec<u8>,
+    string_field: String,
+    pubkey_field: Pubkey,
+    vec_field: Vec<u64>,
+    vec_struct_field: Vec<FooStruct>,
+    option_field: Option<bool>,
+    option_struct_field: Option<FooStruct>,
+    struct_field: FooStruct,
+    array_field: [bool; 3],
+    enum_field_1: FooEnum,
+    enum_field_2: FooEnum,
+    enum_field_3: FooEnum,
+    enum_field_4: FooEnum,
+}
+
+impl Default for State {
+    fn default() -> Self {
+        // some arbitrary default values
+        return State {
+            bool_field: true,
+            u8_field: 234,
+            i8_field: -123,
+            u16_field: 62345,
+            i16_field: -31234,
+            u32_field: 1234567891,
+            i32_field: -1234567891,
+            f32_field: 123456.5,
+            u64_field: u64::MAX / 2 + 10,
+            i64_field: i64::MIN / 2 - 10,
+            f64_field: 1234567891.345,
+            u128_field: u128::MAX / 2 + 10,
+            i128_field: i128::MIN / 2 - 10,
+            bytes_field: vec![1, 2, 255, 254],
+            string_field: String::from("hello"),
+            pubkey_field: Pubkey::from_str("EPZP2wrcRtMxrAPJCXVEQaYD9eH7fH7h12YqKDcd4aS7").unwrap(),
+            vec_field: vec![1, 2, 100, 1000, u64::MAX],
+            vec_struct_field: vec![FooStruct::default()],
+            option_field: None,
+            option_struct_field: Some(FooStruct::default()),
+            struct_field: FooStruct::default(),
+            array_field: [true, false, true],
+            enum_field_1: FooEnum::Unnamed(false, 10, BarStruct::default()),
+            enum_field_2: FooEnum::Named {
+                bool_field: true,
+                u8_field: 20,
+                nested: BarStruct::default(),
+            },
+            enum_field_3: FooEnum::Struct(BarStruct::default()),
+            enum_field_4: FooEnum::NoFields,
+        };
+    }
+}
+
+#[account]
+pub struct State2 {
+    vec_of_option: Vec<Option<u64>>,
+    box_field: Box<bool>,
+}
+impl Default for State2 {
+    fn default() -> Self {
+        return State2 {
+            vec_of_option: vec![None, Some(10)],
+            box_field: Box::new(true),
+        };
+    }
+}
+
+#[derive(Accounts)]
+pub struct NestedAccounts<'info> {
+    /// Sysvar clock
+    clock: Sysvar<'info, Clock>,
+    rent: Sysvar<'info, Rent>,
+}
+
+#[derive(Accounts)]
+pub struct Initialize<'info> {
+    /// State account
+    #[account(
+        init,
+        space = 8 + 1000, // TODO: use exact space required
+        payer = payer,
+    )]
+    state: Account<'info, State>,
+
+    nested: NestedAccounts<'info>,
+    zc_account: AccountLoader<'info, SomeZcAccount>,
+
+    #[account(mut)]
+    payer: Signer<'info>,
+    system_program: Program<'info, System>,
+}
+
+#[derive(Accounts)]
+pub struct Initialize2<'info> {
+    #[account(
+        init,
+        space = 8 + 1000, // TODO: use exact space required
+        payer = payer,
+    )]
+    state: Account<'info, State2>,
+
+    #[account(mut)]
+    payer: Signer<'info>,
+    system_program: Program<'info, System>,
+}
+
+#[derive(Accounts)]
+pub struct CauseError {}
+
+#[error_code]
+pub enum ErrorCode {
+    #[msg("Example error.")]
+    SomeError,
+    #[msg("Another error.")]
+    OtherError,
+    ErrorWithoutMsg,
+}
+
+mod some_other_module {
+    use super::*;
+
+    #[derive(AnchorSerialize, AnchorDeserialize, Clone)]
+    pub struct Baz {
+        some_u8: u8,
+    }
+}
+
+#[event]
+pub struct SomeEvent {
+    bool_field: bool,
+    external_baz: some_external_program::Baz,
+    other_module_baz: some_other_module::Baz,
+}
+
+#[zero_copy]
+pub struct ZcStruct {
+    pub some_field: u16,
+}
+
+#[account(zero_copy)]
+pub struct SomeZcAccount {
+    field: ZcStruct,
+}
+
+#[derive(AnchorSerialize, AnchorDeserialize, Clone)]
+pub struct SomeRetStruct {
+    pub some_field: u8,
+}

+ 22 - 0
tests/idl-build/programs/relations-derivation/Cargo.toml

@@ -0,0 +1,22 @@
+[package]
+name = "relations-derivation"
+version = "0.1.0"
+description = "Created with Anchor"
+rust-version = "1.60"
+edition = "2021"
+
+[lib]
+crate-type = ["cdylib", "lib"]
+name = "relations_derivation"
+
+[features]
+no-entrypoint = []
+no-idl = []
+cpi = ["no-entrypoint"]
+idl-build = [
+    "anchor-lang/idl-build",
+]
+default = []
+
+[dependencies]
+anchor-lang = { path = "../../../../lang" }

+ 2 - 0
tests/idl-build/programs/relations-derivation/Xargo.toml

@@ -0,0 +1,2 @@
+[target.bpfel-unknown-unknown.dependencies.std]
+features = []

+ 68 - 0
tests/idl-build/programs/relations-derivation/src/lib.rs

@@ -0,0 +1,68 @@
+//! The typescript example serves to show how one would setup an Anchor
+//! workspace with TypeScript tests and migrations.
+
+use anchor_lang::prelude::*;
+
+declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");
+
+#[program]
+pub mod relations_derivation {
+    use super::*;
+
+    pub fn init_base(ctx: Context<InitBase>) -> Result<()> {
+        ctx.accounts.account.my_account = ctx.accounts.my_account.key();
+        ctx.accounts.account.bump = ctx.bumps["account"];
+        Ok(())
+    }
+    pub fn test_relation(_ctx: Context<TestRelation>) -> Result<()> {
+        Ok(())
+    }
+}
+
+#[derive(Accounts)]
+pub struct InitBase<'info> {
+    /// CHECK: yeah I know
+    #[account(mut)]
+    my_account: Signer<'info>,
+    #[account(
+      init,
+      payer = my_account,
+      seeds = [b"seed"],
+      space = 100,
+      bump,
+    )]
+    account: Account<'info, MyAccount>,
+    system_program: Program<'info, System>
+}
+
+#[derive(Accounts)]
+pub struct Nested<'info> {
+    /// CHECK: yeah I know
+    my_account: UncheckedAccount<'info>,
+    #[account(
+      has_one = my_account,
+      seeds = [b"seed"],
+      bump = account.bump
+    )]
+    account: Account<'info, MyAccount>,
+}
+
+#[derive(Accounts)]
+pub struct TestRelation<'info> {
+    /// CHECK: yeah I know
+    my_account: UncheckedAccount<'info>,
+    #[account(
+      has_one = my_account,
+      seeds = [b"seed"],
+      bump = account.bump
+    )]
+    account: Account<'info, MyAccount>,
+    nested: Nested<'info>,
+}
+
+
+#[account]
+pub struct MyAccount {
+    pub my_account: Pubkey,
+    pub bump: u8
+}

+ 22 - 0
tests/idl-build/programs/some_external_program/Cargo.toml

@@ -0,0 +1,22 @@
+[package]
+name = "some-external-program"
+version = "0.1.0"
+description = "Created with Anchor"
+edition = "2021"
+
+[lib]
+crate-type = ["cdylib", "lib"]
+name = "some_external_program"
+
+[features]
+no-entrypoint = []
+no-idl = []
+no-log-ix-name = []
+idl-build = ["anchor-lang/idl-build"]
+cpi = ["no-entrypoint"]
+default = []
+
+[dependencies]
+anchor-lang = { path = "../../../../lang" }
+anchor-spl = { path = "../../../../spl" }
+

+ 2 - 0
tests/idl-build/programs/some_external_program/Xargo.toml

@@ -0,0 +1,2 @@
+[target.bpfel-unknown-unknown.dependencies.std]
+features = []

+ 20 - 0
tests/idl-build/programs/some_external_program/src/lib.rs

@@ -0,0 +1,20 @@
+use anchor_lang::prelude::*;
+
+declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");
+
+#[program]
+pub mod idl_2 {
+    use super::*;
+
+    pub fn initialize(_ctx: Context<Initialize>, _baz: Baz) -> Result<()> {
+        Ok(())
+    }
+}
+
+#[derive(AnchorSerialize, AnchorDeserialize, Clone)]
+pub struct Baz {
+    some_field: u8,
+}
+
+#[derive(Accounts)]
+pub struct Initialize {}

+ 53 - 0
tests/idl-build/test.sh

@@ -0,0 +1,53 @@
+#!/usr/bin/env bash
+set -x
+set -e
+
+TMPDIR=$(mktemp -d)
+
+cd programs/idl
+anchor idl parse --file src/lib.rs > $TMPDIR/idl_parse_act.json
+anchor idl build > $TMPDIR/idl_build_act.json
+
+cd ../generics
+anchor idl build > $TMPDIR/generics_build_act.json
+
+cd ../relations-derivation
+anchor idl build > $TMPDIR/relations_build_act.json
+
+cd ../..
+echo "----------------------------------------------------"
+echo "idl parse before > after"
+echo "----------------------------------------------------"
+echo ""
+diff -y --color tests/testdata/idl_parse_exp.json $TMPDIR/idl_parse_act.json
+PARSE_RETCODE=$?
+
+echo ""
+echo ""
+echo "----------------------------------------------------"
+echo "idl build before > after"
+echo "----------------------------------------------------"
+echo ""
+diff -y --color tests/testdata/idl_build_exp.json $TMPDIR/idl_build_act.json
+GEN_RETCODE=$?
+
+echo ""
+echo ""
+echo "----------------------------------------------------"
+echo "idl generics build before > after"
+echo "----------------------------------------------------"
+echo ""
+diff -y --color tests/testdata/generics_build_exp.json $TMPDIR/generics_build_act.json
+GEN_GENERICS_RETCODE=$?
+
+echo ""
+echo ""
+echo "----------------------------------------------------"
+echo "idl relations build before > after"
+echo "----------------------------------------------------"
+echo ""
+diff -y --color tests/testdata/relations_build_exp.json $TMPDIR/relations_build_act.json
+GEN_RELATIONS_RETCODE=$?
+
+# returns 0 when ok, or a positive integer when there are differences
+exit $((PARSE_RETCODE+GEN_RETCODE+GEN_GENERICS_RETCODE+GEN_RELATIONS_RETCODE))

+ 426 - 0
tests/idl-build/tests/testdata/generics_build_exp.json

@@ -0,0 +1,426 @@
+{
+  "version": "0.1.0",
+  "name": "example_program",
+  "docs": [
+    "This is an example program used for testing"
+  ],
+  "instructions": [
+    {
+      "name": "generic",
+      "accounts": [
+        {
+          "name": "genericAcc",
+          "isMut": false,
+          "isSigner": false
+        },
+        {
+          "name": "payer",
+          "isMut": true,
+          "isSigner": true
+        },
+        {
+          "name": "systemProgram",
+          "isMut": false,
+          "isSigner": false
+        }
+      ],
+      "args": [
+        {
+          "name": "genericField",
+          "type": {
+            "definedWithTypeArgs": {
+              "path": "generics::GenericType",
+              "args": [
+                {
+                  "type": "u32"
+                },
+                {
+                  "type": "u64"
+                },
+                {
+                  "value": "10"
+                }
+              ]
+            }
+          }
+        }
+      ]
+    }
+  ],
+  "accounts": [
+    {
+      "name": "GenericAccount",
+      "path": "generics::GenericAccount",
+      "type": {
+        "kind": "struct",
+        "fields": [
+          {
+            "name": "data",
+            "type": {
+              "definedWithTypeArgs": {
+                "path": "generics::GenericType",
+                "args": [
+                  {
+                    "type": "u32"
+                  },
+                  {
+                    "type": "u64"
+                  },
+                  {
+                    "value": "10"
+                  }
+                ]
+              }
+            }
+          }
+        ]
+      }
+    }
+  ],
+  "types": [
+    {
+      "name": "GenericEnum",
+      "path": "generics::GenericEnum",
+      "generics": [
+        "T",
+        "U",
+        "N"
+      ],
+      "type": {
+        "kind": "enum",
+        "variants": [
+          {
+            "name": "Unnamed",
+            "fields": [
+              {
+                "generic": "T"
+              },
+              {
+                "generic": "U"
+              }
+            ]
+          },
+          {
+            "name": "Named",
+            "fields": [
+              {
+                "name": "gen1",
+                "type": {
+                  "generic": "T"
+                }
+              },
+              {
+                "name": "gen2",
+                "type": {
+                  "generic": "U"
+                }
+              }
+            ]
+          },
+          {
+            "name": "Struct",
+            "fields": [
+              {
+                "definedWithTypeArgs": {
+                  "path": "generics::GenericNested",
+                  "args": [
+                    {
+                      "type": {
+                        "generic": "T"
+                      }
+                    },
+                    {
+                      "type": {
+                        "generic": "U"
+                      }
+                    }
+                  ]
+                }
+              }
+            ]
+          },
+          {
+            "name": "Arr",
+            "fields": [
+              {
+                "genericLenArray": [
+                  {
+                    "generic": "T"
+                  },
+                  "N"
+                ]
+              }
+            ]
+          }
+        ]
+      }
+    },
+    {
+      "name": "GenericNested",
+      "path": "generics::GenericNested",
+      "generics": [
+        "V",
+        "Z"
+      ],
+      "type": {
+        "kind": "struct",
+        "fields": [
+          {
+            "name": "gen1",
+            "type": {
+              "generic": "V"
+            }
+          },
+          {
+            "name": "gen2",
+            "type": {
+              "generic": "Z"
+            }
+          }
+        ]
+      }
+    },
+    {
+      "name": "GenericType",
+      "path": "generics::GenericType",
+      "generics": [
+        "T",
+        "U",
+        "N"
+      ],
+      "type": {
+        "kind": "struct",
+        "fields": [
+          {
+            "name": "gen1",
+            "type": {
+              "generic": "T"
+            }
+          },
+          {
+            "name": "gen2",
+            "type": {
+              "generic": "U"
+            }
+          },
+          {
+            "name": "gen3",
+            "type": {
+              "definedWithTypeArgs": {
+                "path": "generics::GenericNested",
+                "args": [
+                  {
+                    "type": "u32"
+                  },
+                  {
+                    "type": {
+                      "generic": "U"
+                    }
+                  }
+                ]
+              }
+            }
+          },
+          {
+            "name": "gen4",
+            "type": {
+              "definedWithTypeArgs": {
+                "path": "generics::GenericNested",
+                "args": [
+                  {
+                    "type": {
+                      "generic": "T"
+                    }
+                  },
+                  {
+                    "type": {
+                      "defined": "some_external_program::Baz"
+                    }
+                  }
+                ]
+              }
+            }
+          },
+          {
+            "name": "gen5",
+            "type": {
+              "definedWithTypeArgs": {
+                "path": "generics::GenericNested",
+                "args": [
+                  {
+                    "type": {
+                      "generic": "T"
+                    }
+                  },
+                  {
+                    "type": {
+                      "generic": "U"
+                    }
+                  }
+                ]
+              }
+            }
+          },
+          {
+            "name": "gen6",
+            "type": {
+              "definedWithTypeArgs": {
+                "path": "generics::GenericNested",
+                "args": [
+                  {
+                    "type": "u32"
+                  },
+                  {
+                    "type": "u64"
+                  }
+                ]
+              }
+            }
+          },
+          {
+            "name": "gen7",
+            "type": {
+              "definedWithTypeArgs": {
+                "path": "generics::GenericNested",
+                "args": [
+                  {
+                    "type": {
+                      "generic": "T"
+                    }
+                  },
+                  {
+                    "type": {
+                      "definedWithTypeArgs": {
+                        "path": "generics::GenericNested",
+                        "args": [
+                          {
+                            "type": {
+                              "generic": "T"
+                            }
+                          },
+                          {
+                            "type": {
+                              "generic": "U"
+                            }
+                          }
+                        ]
+                      }
+                    }
+                  }
+                ]
+              }
+            }
+          },
+          {
+            "name": "arr",
+            "type": {
+              "genericLenArray": [
+                "u8",
+                "N"
+              ]
+            }
+          },
+          {
+            "name": "warr",
+            "type": {
+              "definedWithTypeArgs": {
+                "path": "generics::WrappedU8Array",
+                "args": [
+                  {
+                    "type": {
+                      "generic": "N"
+                    }
+                  }
+                ]
+              }
+            }
+          },
+          {
+            "name": "warrval",
+            "type": {
+              "definedWithTypeArgs": {
+                "path": "generics::WrappedU8Array",
+                "args": [
+                  {
+                    "value": "10"
+                  }
+                ]
+              }
+            }
+          },
+          {
+            "name": "enm1",
+            "type": {
+              "definedWithTypeArgs": {
+                "path": "generics::GenericEnum",
+                "args": [
+                  {
+                    "type": {
+                      "generic": "T"
+                    }
+                  },
+                  {
+                    "type": {
+                      "generic": "U"
+                    }
+                  },
+                  {
+                    "type": {
+                      "generic": "N"
+                    }
+                  }
+                ]
+              }
+            }
+          },
+          {
+            "name": "enm2",
+            "type": {
+              "definedWithTypeArgs": {
+                "path": "generics::GenericEnum",
+                "args": [
+                  {
+                    "type": {
+                      "definedWithTypeArgs": {
+                        "path": "generics::GenericNested",
+                        "args": [
+                          {
+                            "type": {
+                              "generic": "T"
+                            }
+                          },
+                          {
+                            "type": "u64"
+                          }
+                        ]
+                      }
+                    }
+                  },
+                  {
+                    "type": "u32"
+                  },
+                  {
+                    "value": "30"
+                  }
+                ]
+              }
+            }
+          }
+        ]
+      }
+    },
+    {
+      "name": "Baz",
+      "path": "some_external_program::Baz",
+      "type": {
+        "kind": "struct",
+        "fields": [
+          {
+            "name": "someField",
+            "type": "u8"
+          }
+        ]
+      }
+    }
+  ]
+}

+ 727 - 0
tests/idl-build/tests/testdata/idl_build_exp.json

@@ -0,0 +1,727 @@
+{
+  "version": "0.1.0",
+  "name": "example_program",
+  "docs": [
+    "This is an example program used for testing"
+  ],
+  "constants": [
+    {
+      "name": "BAR_CONST",
+      "type": "u8",
+      "value": "6"
+    },
+    {
+      "name": "FOO_CONST",
+      "type": "u128",
+      "value": "1000000"
+    }
+  ],
+  "instructions": [
+    {
+      "name": "initialize",
+      "accounts": [
+        {
+          "name": "state",
+          "isMut": true,
+          "isSigner": true,
+          "docs": [
+            "State account"
+          ]
+        },
+        {
+          "name": "nested",
+          "accounts": [
+            {
+              "name": "clock",
+              "isMut": false,
+              "isSigner": false,
+              "docs": [
+                "Sysvar clock"
+              ]
+            },
+            {
+              "name": "rent",
+              "isMut": false,
+              "isSigner": false
+            }
+          ]
+        },
+        {
+          "name": "zcAccount",
+          "isMut": false,
+          "isSigner": false
+        },
+        {
+          "name": "payer",
+          "isMut": true,
+          "isSigner": true
+        },
+        {
+          "name": "systemProgram",
+          "isMut": false,
+          "isSigner": false
+        }
+      ],
+      "args": []
+    },
+    {
+      "name": "initializeWithValues",
+      "docs": [
+        "Initializes an account with specified values"
+      ],
+      "accounts": [
+        {
+          "name": "state",
+          "isMut": true,
+          "isSigner": true,
+          "docs": [
+            "State account"
+          ]
+        },
+        {
+          "name": "nested",
+          "accounts": [
+            {
+              "name": "clock",
+              "isMut": false,
+              "isSigner": false,
+              "docs": [
+                "Sysvar clock"
+              ]
+            },
+            {
+              "name": "rent",
+              "isMut": false,
+              "isSigner": false
+            }
+          ]
+        },
+        {
+          "name": "zcAccount",
+          "isMut": false,
+          "isSigner": false
+        },
+        {
+          "name": "payer",
+          "isMut": true,
+          "isSigner": true
+        },
+        {
+          "name": "systemProgram",
+          "isMut": false,
+          "isSigner": false
+        }
+      ],
+      "args": [
+        {
+          "name": "boolField",
+          "type": "bool"
+        },
+        {
+          "name": "u8Field",
+          "type": "u8"
+        },
+        {
+          "name": "i8Field",
+          "type": "i8"
+        },
+        {
+          "name": "u16Field",
+          "type": "u16"
+        },
+        {
+          "name": "i16Field",
+          "type": "i16"
+        },
+        {
+          "name": "u32Field",
+          "type": "u32"
+        },
+        {
+          "name": "i32Field",
+          "type": "i32"
+        },
+        {
+          "name": "f32Field",
+          "type": "f32"
+        },
+        {
+          "name": "u64Field",
+          "type": "u64"
+        },
+        {
+          "name": "i64Field",
+          "type": "i64"
+        },
+        {
+          "name": "f64Field",
+          "type": "f64"
+        },
+        {
+          "name": "u128Field",
+          "type": "u128"
+        },
+        {
+          "name": "i128Field",
+          "type": "i128"
+        },
+        {
+          "name": "bytesField",
+          "type": "bytes"
+        },
+        {
+          "name": "stringField",
+          "type": "string"
+        },
+        {
+          "name": "pubkeyField",
+          "type": "publicKey"
+        },
+        {
+          "name": "vecField",
+          "type": {
+            "vec": "u64"
+          }
+        },
+        {
+          "name": "vecStructField",
+          "type": {
+            "vec": {
+              "defined": "idl::FooStruct"
+            }
+          }
+        },
+        {
+          "name": "optionField",
+          "type": {
+            "option": "bool"
+          }
+        },
+        {
+          "name": "optionStructField",
+          "type": {
+            "option": {
+              "defined": "idl::FooStruct"
+            }
+          }
+        },
+        {
+          "name": "structField",
+          "type": {
+            "defined": "idl::FooStruct"
+          }
+        },
+        {
+          "name": "arrayField",
+          "type": {
+            "array": [
+              "bool",
+              3
+            ]
+          }
+        },
+        {
+          "name": "enumField1",
+          "type": {
+            "defined": "idl::FooEnum"
+          }
+        },
+        {
+          "name": "enumField2",
+          "type": {
+            "defined": "idl::FooEnum"
+          }
+        },
+        {
+          "name": "enumField3",
+          "type": {
+            "defined": "idl::FooEnum"
+          }
+        },
+        {
+          "name": "enumField4",
+          "type": {
+            "defined": "idl::FooEnum"
+          }
+        }
+      ]
+    },
+    {
+      "name": "initializeWithValues2",
+      "docs": [
+        "a separate instruction due to initialize_with_values having too many arguments",
+        "https://github.com/solana-labs/solana/issues/23978"
+      ],
+      "accounts": [
+        {
+          "name": "state",
+          "isMut": true,
+          "isSigner": true
+        },
+        {
+          "name": "payer",
+          "isMut": true,
+          "isSigner": true
+        },
+        {
+          "name": "systemProgram",
+          "isMut": false,
+          "isSigner": false
+        }
+      ],
+      "args": [
+        {
+          "name": "vecOfOption",
+          "type": {
+            "vec": {
+              "option": "u64"
+            }
+          }
+        },
+        {
+          "name": "boxField",
+          "type": "bool"
+        }
+      ],
+      "returns": {
+        "defined": "idl::SomeRetStruct"
+      }
+    },
+    {
+      "name": "causeError",
+      "accounts": [],
+      "args": []
+    }
+  ],
+  "accounts": [
+    {
+      "name": "SomeZcAccount",
+      "path": "idl::SomeZcAccount",
+      "type": {
+        "kind": "struct",
+        "fields": [
+          {
+            "name": "field",
+            "type": {
+              "defined": "idl::ZcStruct"
+            }
+          }
+        ]
+      }
+    },
+    {
+      "name": "State",
+      "path": "idl::State",
+      "docs": [
+        "An account containing various fields"
+      ],
+      "type": {
+        "kind": "struct",
+        "fields": [
+          {
+            "name": "boolField",
+            "docs": [
+              "A boolean field"
+            ],
+            "type": "bool"
+          },
+          {
+            "name": "u8Field",
+            "type": "u8"
+          },
+          {
+            "name": "i8Field",
+            "type": "i8"
+          },
+          {
+            "name": "u16Field",
+            "type": "u16"
+          },
+          {
+            "name": "i16Field",
+            "type": "i16"
+          },
+          {
+            "name": "u32Field",
+            "type": "u32"
+          },
+          {
+            "name": "i32Field",
+            "type": "i32"
+          },
+          {
+            "name": "f32Field",
+            "type": "f32"
+          },
+          {
+            "name": "u64Field",
+            "type": "u64"
+          },
+          {
+            "name": "i64Field",
+            "type": "i64"
+          },
+          {
+            "name": "f64Field",
+            "type": "f64"
+          },
+          {
+            "name": "u128Field",
+            "type": "u128"
+          },
+          {
+            "name": "i128Field",
+            "type": "i128"
+          },
+          {
+            "name": "bytesField",
+            "type": "bytes"
+          },
+          {
+            "name": "stringField",
+            "type": "string"
+          },
+          {
+            "name": "pubkeyField",
+            "type": "publicKey"
+          },
+          {
+            "name": "vecField",
+            "type": {
+              "vec": "u64"
+            }
+          },
+          {
+            "name": "vecStructField",
+            "type": {
+              "vec": {
+                "defined": "idl::FooStruct"
+              }
+            }
+          },
+          {
+            "name": "optionField",
+            "type": {
+              "option": "bool"
+            }
+          },
+          {
+            "name": "optionStructField",
+            "type": {
+              "option": {
+                "defined": "idl::FooStruct"
+              }
+            }
+          },
+          {
+            "name": "structField",
+            "type": {
+              "defined": "idl::FooStruct"
+            }
+          },
+          {
+            "name": "arrayField",
+            "type": {
+              "array": [
+                "bool",
+                3
+              ]
+            }
+          },
+          {
+            "name": "enumField1",
+            "type": {
+              "defined": "idl::FooEnum"
+            }
+          },
+          {
+            "name": "enumField2",
+            "type": {
+              "defined": "idl::FooEnum"
+            }
+          },
+          {
+            "name": "enumField3",
+            "type": {
+              "defined": "idl::FooEnum"
+            }
+          },
+          {
+            "name": "enumField4",
+            "type": {
+              "defined": "idl::FooEnum"
+            }
+          }
+        ]
+      }
+    },
+    {
+      "name": "State2",
+      "path": "idl::State2",
+      "type": {
+        "kind": "struct",
+        "fields": [
+          {
+            "name": "vecOfOption",
+            "type": {
+              "vec": {
+                "option": "u64"
+              }
+            }
+          },
+          {
+            "name": "boxField",
+            "type": "bool"
+          }
+        ]
+      }
+    }
+  ],
+  "types": [
+    {
+      "name": "BarStruct",
+      "path": "idl::BarStruct",
+      "docs": [
+        "Bar struct type"
+      ],
+      "type": {
+        "kind": "struct",
+        "fields": [
+          {
+            "name": "someField",
+            "docs": [
+              "Some field"
+            ],
+            "type": "bool"
+          },
+          {
+            "name": "otherField",
+            "type": "u8"
+          }
+        ]
+      }
+    },
+    {
+      "name": "FooEnum",
+      "path": "idl::FooEnum",
+      "docs": [
+        "Enum type"
+      ],
+      "type": {
+        "kind": "enum",
+        "variants": [
+          {
+            "name": "Unnamed",
+            "fields": [
+              "bool",
+              "u8",
+              {
+                "defined": "idl::BarStruct"
+              }
+            ]
+          },
+          {
+            "name": "UnnamedSingle",
+            "fields": [
+              {
+                "defined": "idl::BarStruct"
+              }
+            ]
+          },
+          {
+            "name": "Named",
+            "fields": [
+              {
+                "name": "boolField",
+                "docs": [
+                  "A bool field inside a struct tuple kind"
+                ],
+                "type": "bool"
+              },
+              {
+                "name": "u8Field",
+                "type": "u8"
+              },
+              {
+                "name": "nested",
+                "type": {
+                  "defined": "idl::BarStruct"
+                }
+              }
+            ]
+          },
+          {
+            "name": "Struct",
+            "fields": [
+              {
+                "defined": "idl::BarStruct"
+              }
+            ]
+          },
+          {
+            "name": "OptionStruct",
+            "fields": [
+              {
+                "option": {
+                  "defined": "idl::BarStruct"
+                }
+              }
+            ]
+          },
+          {
+            "name": "VecStruct",
+            "fields": [
+              {
+                "vec": {
+                  "defined": "idl::BarStruct"
+                }
+              }
+            ]
+          },
+          {
+            "name": "NoFields"
+          }
+        ]
+      }
+    },
+    {
+      "name": "FooStruct",
+      "path": "idl::FooStruct",
+      "type": {
+        "kind": "struct",
+        "fields": [
+          {
+            "name": "field1",
+            "type": "u8"
+          },
+          {
+            "name": "field2",
+            "type": "u16"
+          },
+          {
+            "name": "nested",
+            "type": {
+              "defined": "idl::BarStruct"
+            }
+          },
+          {
+            "name": "vecNested",
+            "type": {
+              "vec": {
+                "defined": "idl::BarStruct"
+              }
+            }
+          },
+          {
+            "name": "optionNested",
+            "type": {
+              "option": {
+                "defined": "idl::BarStruct"
+              }
+            }
+          },
+          {
+            "name": "enumField",
+            "type": {
+              "defined": "idl::FooEnum"
+            }
+          }
+        ]
+      }
+    },
+    {
+      "name": "SomeRetStruct",
+      "path": "idl::SomeRetStruct",
+      "type": {
+        "kind": "struct",
+        "fields": [
+          {
+            "name": "someField",
+            "type": "u8"
+          }
+        ]
+      }
+    },
+    {
+      "name": "ZcStruct",
+      "path": "idl::ZcStruct",
+      "type": {
+        "kind": "struct",
+        "fields": [
+          {
+            "name": "someField",
+            "type": "u16"
+          }
+        ]
+      }
+    },
+    {
+      "name": "Baz",
+      "path": "idl::some_other_module::Baz",
+      "type": {
+        "kind": "struct",
+        "fields": [
+          {
+            "name": "someU8",
+            "type": "u8"
+          }
+        ]
+      }
+    },
+    {
+      "name": "Baz",
+      "path": "some_external_program::Baz",
+      "type": {
+        "kind": "struct",
+        "fields": [
+          {
+            "name": "someField",
+            "type": "u8"
+          }
+        ]
+      }
+    }
+  ],
+  "events": [
+    {
+      "name": "SomeEvent",
+      "fields": [
+        {
+          "name": "boolField",
+          "type": "bool",
+          "index": false
+        },
+        {
+          "name": "externalBaz",
+          "type": {
+            "defined": "some_external_program::Baz"
+          },
+          "index": false
+        },
+        {
+          "name": "otherModuleBaz",
+          "type": {
+            "defined": "idl::some_other_module::Baz"
+          },
+          "index": false
+        }
+      ]
+    }
+  ],
+  "errors": [
+    {
+      "code": 6000,
+      "name": "SomeError",
+      "msg": "Example error."
+    },
+    {
+      "code": 6001,
+      "name": "OtherError",
+      "msg": "Another error."
+    },
+    {
+      "code": 6002,
+      "name": "ErrorWithoutMsg"
+    }
+  ]
+}

+ 705 - 0
tests/idl-build/tests/testdata/idl_parse_exp.json

@@ -0,0 +1,705 @@
+{
+  "version": "0.1.0",
+  "name": "example_program",
+  "docs": [
+    "This is an example program used for testing"
+  ],
+  "constants": [
+    {
+      "name": "FOO_CONST",
+      "type": "u128",
+      "value": "1_000_000"
+    },
+    {
+      "name": "BAR_CONST",
+      "type": "u8",
+      "value": "6"
+    }
+  ],
+  "instructions": [
+    {
+      "name": "initialize",
+      "accounts": [
+        {
+          "name": "state",
+          "isMut": true,
+          "isSigner": true,
+          "docs": [
+            "State account"
+          ]
+        },
+        {
+          "name": "nested",
+          "accounts": [
+            {
+              "name": "clock",
+              "isMut": false,
+              "isSigner": false,
+              "docs": [
+                "Sysvar clock"
+              ]
+            },
+            {
+              "name": "rent",
+              "isMut": false,
+              "isSigner": false
+            }
+          ]
+        },
+        {
+          "name": "zcAccount",
+          "isMut": false,
+          "isSigner": false
+        },
+        {
+          "name": "payer",
+          "isMut": true,
+          "isSigner": true
+        },
+        {
+          "name": "systemProgram",
+          "isMut": false,
+          "isSigner": false
+        }
+      ],
+      "args": []
+    },
+    {
+      "name": "initializeWithValues",
+      "docs": [
+        "Initializes an account with specified values"
+      ],
+      "accounts": [
+        {
+          "name": "state",
+          "isMut": true,
+          "isSigner": true,
+          "docs": [
+            "State account"
+          ]
+        },
+        {
+          "name": "nested",
+          "accounts": [
+            {
+              "name": "clock",
+              "isMut": false,
+              "isSigner": false,
+              "docs": [
+                "Sysvar clock"
+              ]
+            },
+            {
+              "name": "rent",
+              "isMut": false,
+              "isSigner": false
+            }
+          ]
+        },
+        {
+          "name": "zcAccount",
+          "isMut": false,
+          "isSigner": false
+        },
+        {
+          "name": "payer",
+          "isMut": true,
+          "isSigner": true
+        },
+        {
+          "name": "systemProgram",
+          "isMut": false,
+          "isSigner": false
+        }
+      ],
+      "args": [
+        {
+          "name": "boolField",
+          "type": "bool"
+        },
+        {
+          "name": "u8Field",
+          "type": "u8"
+        },
+        {
+          "name": "i8Field",
+          "type": "i8"
+        },
+        {
+          "name": "u16Field",
+          "type": "u16"
+        },
+        {
+          "name": "i16Field",
+          "type": "i16"
+        },
+        {
+          "name": "u32Field",
+          "type": "u32"
+        },
+        {
+          "name": "i32Field",
+          "type": "i32"
+        },
+        {
+          "name": "f32Field",
+          "type": "f32"
+        },
+        {
+          "name": "u64Field",
+          "type": "u64"
+        },
+        {
+          "name": "i64Field",
+          "type": "i64"
+        },
+        {
+          "name": "f64Field",
+          "type": "f64"
+        },
+        {
+          "name": "u128Field",
+          "type": "u128"
+        },
+        {
+          "name": "i128Field",
+          "type": "i128"
+        },
+        {
+          "name": "bytesField",
+          "type": "bytes"
+        },
+        {
+          "name": "stringField",
+          "type": "string"
+        },
+        {
+          "name": "pubkeyField",
+          "type": "publicKey"
+        },
+        {
+          "name": "vecField",
+          "type": {
+            "vec": "u64"
+          }
+        },
+        {
+          "name": "vecStructField",
+          "type": {
+            "vec": {
+              "defined": "FooStruct"
+            }
+          }
+        },
+        {
+          "name": "optionField",
+          "type": {
+            "option": "bool"
+          }
+        },
+        {
+          "name": "optionStructField",
+          "type": {
+            "option": {
+              "defined": "FooStruct"
+            }
+          }
+        },
+        {
+          "name": "structField",
+          "type": {
+            "defined": "FooStruct"
+          }
+        },
+        {
+          "name": "arrayField",
+          "type": {
+            "array": [
+              "bool",
+              3
+            ]
+          }
+        },
+        {
+          "name": "enumField1",
+          "type": {
+            "defined": "FooEnum"
+          }
+        },
+        {
+          "name": "enumField2",
+          "type": {
+            "defined": "FooEnum"
+          }
+        },
+        {
+          "name": "enumField3",
+          "type": {
+            "defined": "FooEnum"
+          }
+        },
+        {
+          "name": "enumField4",
+          "type": {
+            "defined": "FooEnum"
+          }
+        }
+      ]
+    },
+    {
+      "name": "initializeWithValues2",
+      "docs": [
+        "a separate instruction due to initialize_with_values having too many arguments",
+        "https://github.com/solana-labs/solana/issues/23978"
+      ],
+      "accounts": [
+        {
+          "name": "state",
+          "isMut": true,
+          "isSigner": true
+        },
+        {
+          "name": "payer",
+          "isMut": true,
+          "isSigner": true
+        },
+        {
+          "name": "systemProgram",
+          "isMut": false,
+          "isSigner": false
+        }
+      ],
+      "args": [
+        {
+          "name": "vecOfOption",
+          "type": {
+            "vec": {
+              "option": "u64"
+            }
+          }
+        },
+        {
+          "name": "boxField",
+          "type": "bool"
+        }
+      ],
+      "returns": {
+        "defined": "SomeRetStruct"
+      }
+    },
+    {
+      "name": "causeError",
+      "accounts": [],
+      "args": []
+    }
+  ],
+  "accounts": [
+    {
+      "name": "State",
+      "docs": [
+        "An account containing various fields"
+      ],
+      "type": {
+        "kind": "struct",
+        "fields": [
+          {
+            "name": "boolField",
+            "docs": [
+              "A boolean field"
+            ],
+            "type": "bool"
+          },
+          {
+            "name": "u8Field",
+            "type": "u8"
+          },
+          {
+            "name": "i8Field",
+            "type": "i8"
+          },
+          {
+            "name": "u16Field",
+            "type": "u16"
+          },
+          {
+            "name": "i16Field",
+            "type": "i16"
+          },
+          {
+            "name": "u32Field",
+            "type": "u32"
+          },
+          {
+            "name": "i32Field",
+            "type": "i32"
+          },
+          {
+            "name": "f32Field",
+            "type": "f32"
+          },
+          {
+            "name": "u64Field",
+            "type": "u64"
+          },
+          {
+            "name": "i64Field",
+            "type": "i64"
+          },
+          {
+            "name": "f64Field",
+            "type": "f64"
+          },
+          {
+            "name": "u128Field",
+            "type": "u128"
+          },
+          {
+            "name": "i128Field",
+            "type": "i128"
+          },
+          {
+            "name": "bytesField",
+            "type": "bytes"
+          },
+          {
+            "name": "stringField",
+            "type": "string"
+          },
+          {
+            "name": "pubkeyField",
+            "type": "publicKey"
+          },
+          {
+            "name": "vecField",
+            "type": {
+              "vec": "u64"
+            }
+          },
+          {
+            "name": "vecStructField",
+            "type": {
+              "vec": {
+                "defined": "FooStruct"
+              }
+            }
+          },
+          {
+            "name": "optionField",
+            "type": {
+              "option": "bool"
+            }
+          },
+          {
+            "name": "optionStructField",
+            "type": {
+              "option": {
+                "defined": "FooStruct"
+              }
+            }
+          },
+          {
+            "name": "structField",
+            "type": {
+              "defined": "FooStruct"
+            }
+          },
+          {
+            "name": "arrayField",
+            "type": {
+              "array": [
+                "bool",
+                3
+              ]
+            }
+          },
+          {
+            "name": "enumField1",
+            "type": {
+              "defined": "FooEnum"
+            }
+          },
+          {
+            "name": "enumField2",
+            "type": {
+              "defined": "FooEnum"
+            }
+          },
+          {
+            "name": "enumField3",
+            "type": {
+              "defined": "FooEnum"
+            }
+          },
+          {
+            "name": "enumField4",
+            "type": {
+              "defined": "FooEnum"
+            }
+          }
+        ]
+      }
+    },
+    {
+      "name": "State2",
+      "type": {
+        "kind": "struct",
+        "fields": [
+          {
+            "name": "vecOfOption",
+            "type": {
+              "vec": {
+                "option": "u64"
+              }
+            }
+          },
+          {
+            "name": "boxField",
+            "type": "bool"
+          }
+        ]
+      }
+    },
+    {
+      "name": "SomeZcAccount",
+      "type": {
+        "kind": "struct",
+        "fields": [
+          {
+            "name": "field",
+            "type": {
+              "defined": "ZcStruct"
+            }
+          }
+        ]
+      }
+    }
+  ],
+  "types": [
+    {
+      "name": "Baz",
+      "type": {
+        "kind": "struct",
+        "fields": [
+          {
+            "name": "someU8",
+            "type": "u8"
+          }
+        ]
+      }
+    },
+    {
+      "name": "BarStruct",
+      "docs": [
+        "Bar struct type"
+      ],
+      "type": {
+        "kind": "struct",
+        "fields": [
+          {
+            "name": "someField",
+            "docs": [
+              "Some field"
+            ],
+            "type": "bool"
+          },
+          {
+            "name": "otherField",
+            "type": "u8"
+          }
+        ]
+      }
+    },
+    {
+      "name": "FooStruct",
+      "type": {
+        "kind": "struct",
+        "fields": [
+          {
+            "name": "field1",
+            "type": "u8"
+          },
+          {
+            "name": "field2",
+            "type": "u16"
+          },
+          {
+            "name": "nested",
+            "type": {
+              "defined": "BarStruct"
+            }
+          },
+          {
+            "name": "vecNested",
+            "type": {
+              "vec": {
+                "defined": "BarStruct"
+              }
+            }
+          },
+          {
+            "name": "optionNested",
+            "type": {
+              "option": {
+                "defined": "BarStruct"
+              }
+            }
+          },
+          {
+            "name": "enumField",
+            "type": {
+              "defined": "FooEnum"
+            }
+          }
+        ]
+      }
+    },
+    {
+      "name": "ZcStruct",
+      "type": {
+        "kind": "struct",
+        "fields": [
+          {
+            "name": "someField",
+            "type": "u16"
+          }
+        ]
+      }
+    },
+    {
+      "name": "SomeRetStruct",
+      "type": {
+        "kind": "struct",
+        "fields": [
+          {
+            "name": "someField",
+            "type": "u8"
+          }
+        ]
+      }
+    },
+    {
+      "name": "FooEnum",
+      "docs": [
+        "Enum type"
+      ],
+      "type": {
+        "kind": "enum",
+        "variants": [
+          {
+            "name": "Unnamed",
+            "fields": [
+              "bool",
+              "u8",
+              {
+                "defined": "BarStruct"
+              }
+            ]
+          },
+          {
+            "name": "UnnamedSingle",
+            "fields": [
+              {
+                "defined": "BarStruct"
+              }
+            ]
+          },
+          {
+            "name": "Named",
+            "fields": [
+              {
+                "name": "bool_field",
+                "docs": [
+                  "A bool field inside a struct tuple kind"
+                ],
+                "type": "bool"
+              },
+              {
+                "name": "u8_field",
+                "type": "u8"
+              },
+              {
+                "name": "nested",
+                "type": {
+                  "defined": "BarStruct"
+                }
+              }
+            ]
+          },
+          {
+            "name": "Struct",
+            "fields": [
+              {
+                "defined": "BarStruct"
+              }
+            ]
+          },
+          {
+            "name": "OptionStruct",
+            "fields": [
+              {
+                "option": {
+                  "defined": "BarStruct"
+                }
+              }
+            ]
+          },
+          {
+            "name": "VecStruct",
+            "fields": [
+              {
+                "vec": {
+                  "defined": "BarStruct"
+                }
+              }
+            ]
+          },
+          {
+            "name": "NoFields"
+          }
+        ]
+      }
+    }
+  ],
+  "events": [
+    {
+      "name": "SomeEvent",
+      "fields": [
+        {
+          "name": "boolField",
+          "type": "bool",
+          "index": false
+        },
+        {
+          "name": "externalBaz",
+          "type": {
+            "defined": "some_external_program::Baz"
+          },
+          "index": false
+        },
+        {
+          "name": "otherModuleBaz",
+          "type": {
+            "defined": "some_other_module::Baz"
+          },
+          "index": false
+        }
+      ]
+    }
+  ],
+  "errors": [
+    {
+      "code": 6000,
+      "name": "SomeError",
+      "msg": "Example error."
+    },
+    {
+      "code": 6001,
+      "name": "OtherError",
+      "msg": "Another error."
+    },
+    {
+      "code": 6002,
+      "name": "ErrorWithoutMsg"
+    }
+  ]
+}

+ 83 - 0
tests/idl-build/tests/testdata/relations_build_exp.json

@@ -0,0 +1,83 @@
+{
+  "version": "0.1.0",
+  "name": "relations_derivation",
+  "instructions": [
+    {
+      "name": "initBase",
+      "accounts": [
+        {
+          "name": "myAccount",
+          "isMut": true,
+          "isSigner": true
+        },
+        {
+          "name": "account",
+          "isMut": true,
+          "isSigner": false
+        },
+        {
+          "name": "systemProgram",
+          "isMut": false,
+          "isSigner": false
+        }
+      ],
+      "args": []
+    },
+    {
+      "name": "testRelation",
+      "accounts": [
+        {
+          "name": "myAccount",
+          "isMut": false,
+          "isSigner": false
+        },
+        {
+          "name": "account",
+          "isMut": false,
+          "isSigner": false,
+          "relations": [
+            "my_account"
+          ]
+        },
+        {
+          "name": "nested",
+          "accounts": [
+            {
+              "name": "myAccount",
+              "isMut": false,
+              "isSigner": false
+            },
+            {
+              "name": "account",
+              "isMut": false,
+              "isSigner": false,
+              "relations": [
+                "my_account"
+              ]
+            }
+          ]
+        }
+      ],
+      "args": []
+    }
+  ],
+  "accounts": [
+    {
+      "name": "MyAccount",
+      "path": "relations_derivation::MyAccount",
+      "type": {
+        "kind": "struct",
+        "fields": [
+          {
+            "name": "myAccount",
+            "type": "publicKey"
+          },
+          {
+            "name": "bump",
+            "type": "u8"
+          }
+        ]
+      }
+    }
+  ]
+}

+ 10 - 0
tests/idl-build/tsconfig.json

@@ -0,0 +1,10 @@
+{
+  "compilerOptions": {
+    "types": ["mocha", "chai"],
+    "typeRoots": ["./node_modules/@types"],
+    "lib": ["es2015"],
+    "module": "commonjs",
+    "target": "es6",
+    "esModuleInterop": true
+  }
+}

+ 1 - 0
tests/package.json

@@ -18,6 +18,7 @@
     "escrow",
     "events",
     "floats",
+    "idl-build",
     "ido-pool",
     "interface",
     "lockup",