Sfoglia il codice sorgente

Fix IDL (#2824)

* Rewrite IDL type spec

* Rewrite IDL generation

* Partially rewrite the TS package with the new IDL, improved account resolution and types
acheron 1 anno fa
parent
commit
d9a9f19394
100 ha cambiato i file con 3535 aggiunte e 3394 eliminazioni
  1. 15 6
      .github/actions/setup/action.yaml
  2. 3 2
      .github/workflows/reusable-tests.yaml
  3. 18 0
      CHANGELOG.md
  4. 13 1
      Cargo.lock
  5. 1 0
      Cargo.toml
  6. 1 1
      cli/Cargo.toml
  7. 25 6
      cli/src/config.rs
  8. 132 352
      cli/src/lib.rs
  9. 46 26
      cli/src/rust_template.rs
  10. 1 0
      examples/tutorial/basic-0/programs/basic-0/Cargo.toml
  11. 1 0
      examples/tutorial/basic-1/programs/basic-1/Cargo.toml
  12. 1 0
      examples/tutorial/basic-2/programs/basic-2/Cargo.toml
  13. 1 0
      examples/tutorial/basic-3/programs/puppet-master/Cargo.toml
  14. 1 0
      examples/tutorial/basic-3/programs/puppet/Cargo.toml
  15. 1 0
      examples/tutorial/basic-4/programs/basic-4/Cargo.toml
  16. 1 0
      examples/tutorial/basic-5/programs/basic-5/Cargo.toml
  17. 30 0
      idl/Cargo.toml
  18. 252 0
      idl/src/build.rs
  19. 6 0
      idl/src/lib.rs
  20. 3 0
      idl/src/types.rs
  21. 28 5
      lang/attribute/account/src/lib.rs
  22. 3 4
      lang/attribute/constant/src/lib.rs
  23. 1 1
      lang/attribute/event/src/lib.rs
  24. 1 1
      lang/derive/accounts/src/lib.rs
  25. 5 10
      lang/derive/serde/src/lib.rs
  26. 10 0
      lang/src/bpf_upgradeable_state.rs
  27. 4 3
      lang/syn/Cargo.toml
  28. 1 1
      lang/syn/src/codegen/accounts/constraints.rs
  29. 1 4
      lang/syn/src/codegen/accounts/mod.rs
  30. 2 5
      lang/syn/src/codegen/error.rs
  31. 2 4
      lang/syn/src/codegen/program/mod.rs
  32. 0 948
      lang/syn/src/idl/build.rs
  33. 392 0
      lang/syn/src/idl/build/accounts.rs
  34. 15 0
      lang/syn/src/idl/build/address.rs
  35. 43 0
      lang/syn/src/idl/build/common.rs
  36. 37 0
      lang/syn/src/idl/build/constant.rs
  37. 654 0
      lang/syn/src/idl/build/defined.rs
  38. 44 0
      lang/syn/src/idl/build/error.rs
  39. 81 0
      lang/syn/src/idl/build/event.rs
  40. 143 0
      lang/syn/src/idl/build/external.rs
  41. 21 0
      lang/syn/src/idl/build/mod.rs
  42. 140 0
      lang/syn/src/idl/build/program.rs
  43. 0 3
      lang/syn/src/idl/mod.rs
  44. 0 590
      lang/syn/src/idl/parse/file.rs
  45. 0 120
      lang/syn/src/idl/parse/mod.rs
  46. 0 389
      lang/syn/src/idl/parse/pda.rs
  47. 0 19
      lang/syn/src/idl/parse/relations.rs
  48. 370 116
      lang/syn/src/idl/types.rs
  49. 1 2
      lang/syn/src/lib.rs
  50. 38 42
      lang/syn/src/parser/context.rs
  51. 1 3
      lang/syn/src/parser/mod.rs
  52. 5 0
      spl/src/governance.rs
  53. 32 0
      spl/src/idl_build.rs
  54. 3 0
      spl/src/lib.rs
  55. 0 9
      spl/src/metadata.rs
  56. 0 3
      spl/src/stake.rs
  57. 0 6
      spl/src/token.rs
  58. 2 8
      spl/src/token_interface.rs
  59. 1 0
      tests/anchor-cli-account/programs/account-command/Cargo.toml
  60. 2 2
      tests/anchor-cli-account/tests/account.ts
  61. 1 0
      tests/anchor-cli-idl/programs/idl-commands-one/Cargo.toml
  62. 1 0
      tests/anchor-cli-idl/programs/idl-commands-two/Cargo.toml
  63. 7 2
      tests/anchor-cli-idl/test.sh
  64. 4 4
      tests/anchor-cli-idl/tests/idl.ts
  65. 1 0
      tests/auction-house/programs/auction-house/Cargo.toml
  66. 3 1
      tests/auction-house/tests/auction-house.ts
  67. 1 0
      tests/bench/programs/bench/Cargo.toml
  68. 6 3
      tests/bench/tests/binary-size.ts
  69. 4 4
      tests/bench/tests/compute-units.ts
  70. 3 2
      tests/bench/tests/stack-memory.ts
  71. 1 0
      tests/bpf-upgradeable-state/programs/bpf-upgradeable-state/Cargo.toml
  72. 3 1
      tests/bpf-upgradeable-state/programs/bpf-upgradeable-state/src/lib.rs
  73. 1 0
      tests/cashiers-check/programs/cashiers-check/Cargo.toml
  74. 1 0
      tests/chat/programs/chat/Cargo.toml
  75. 1 0
      tests/composite/programs/composite/Cargo.toml
  76. 1 0
      tests/cpi-returns/programs/callee/Cargo.toml
  77. 1 0
      tests/cpi-returns/programs/caller/Cargo.toml
  78. 1 1
      tests/cpi-returns/tests/cpi-return.ts
  79. 1 0
      tests/custom-coder/programs/native-system/Cargo.toml
  80. 1 0
      tests/custom-coder/programs/spl-associated-token/Cargo.toml
  81. 1 0
      tests/custom-coder/programs/spl-token/Cargo.toml
  82. 1 0
      tests/declare-id/programs/declare-id/Cargo.toml
  83. 1 0
      tests/docs/programs/docs/Cargo.toml
  84. 1 0
      tests/errors/programs/errors/Cargo.toml
  85. 1 1
      tests/errors/tests/errors.ts
  86. 1 0
      tests/escrow/programs/escrow/Cargo.toml
  87. 1 0
      tests/events/programs/events/Cargo.toml
  88. 4 4
      tests/events/tests/events.ts
  89. 1 0
      tests/floats/programs/floats/Cargo.toml
  90. 3 4
      tests/idl/Anchor.toml
  91. 3 4
      tests/idl/generate.sh
  92. 169 66
      tests/idl/idls/generics.json
  93. 500 366
      tests/idl/idls/new.json
  94. 5 5
      tests/idl/idls/old.json
  95. 151 0
      tests/idl/idls/relations.json
  96. 0 82
      tests/idl/idls/relations_build.json
  97. 0 19
      tests/idl/programs/client-interactions/Cargo.toml
  98. 0 131
      tests/idl/programs/client-interactions/src/lib.rs
  99. 1 0
      tests/idl/programs/docs/Cargo.toml
  100. 13 2
      tests/idl/programs/external/src/lib.rs

+ 15 - 6
.github/actions/setup/action.yaml

@@ -3,9 +3,18 @@ description: "Setup"
 runs:
   using: "composite"
   steps:
-      - run: sudo apt-get update && sudo apt-get install -y pkg-config build-essential libudev-dev
-        shell: bash
-      - run: echo "ANCHOR_VERSION=$(cat ./VERSION)" >> $GITHUB_ENV
-        shell: bash
-      - run: git submodule update --init --recursive --depth 1
-        shell: bash
+    - run: sudo apt-get update && sudo apt-get install -y pkg-config build-essential libudev-dev
+      shell: bash
+    - run: echo "ANCHOR_VERSION=$(cat ./VERSION)" >> $GITHUB_ENV
+      shell: bash
+    - run: git submodule update --init --recursive --depth 1
+      shell: bash
+    # `nightly` toolchain is currently required for building the IDL.
+    #
+    # Pinning the toolchain to an older date in order to fix
+    # `error[E0635]: unknown feature stdsimd` error from `ahash`.
+    # See: https://github.com/tkaitchuck/aHash/issues/200
+    #
+    # TODO: Unpin `nightly` release after upgrading Solana to `1.18`.
+    - run: rustup toolchain install nightly-2024-01-30
+      shell: bash

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

@@ -452,8 +452,9 @@ jobs:
             path: tests/bench
           - cmd: cd tests/idl && ./test.sh
             path: tests/idl
-          - cmd: cd tests/solang && anchor test
-            path: tests/solang
+          # TODO: Enable when `solang` becomes compatible with the new IDL spec
+          # - cmd: cd tests/solang && anchor test
+          #   path: tests/solang
     steps:
       - uses: actions/checkout@v3
       - uses: ./.github/actions/setup/

+ 18 - 0
CHANGELOG.md

@@ -27,6 +27,13 @@ The minor version will be incremented upon a breaking change and the patch versi
 - cli: Check `@coral-xyz/anchor` package and CLI version compatibility ([#2813](https://github.com/coral-xyz/anchor/pull/2813)).
 - cli: Accept package name as program name ([#2816](https://github.com/coral-xyz/anchor/pull/2816)).
 - cli: Add ability to build and test only a specified program ([#2823](https://github.com/coral-xyz/anchor/pull/2823)).
+- idl: Add new IDL spec ([#2824](https://github.com/coral-xyz/anchor/pull/2824)).
+- idl: Add support for `repr`s ([#2824](https://github.com/coral-xyz/anchor/pull/2824)).
+- idl: Add support for expression evaluation ([#2824](https://github.com/coral-xyz/anchor/pull/2824)).
+- idl: Add support for using external types when generating the IDL ([#2824](https://github.com/coral-xyz/anchor/pull/2824)).
+- idl, ts: Add unit and tuple struct support ([#2824](https://github.com/coral-xyz/anchor/pull/2824)).
+- idl, ts: Add generics support ([#2824](https://github.com/coral-xyz/anchor/pull/2824)).
+- ts: Add `accountsPartial` method to keep the old `accounts` method behavior ([#2824](https://github.com/coral-xyz/anchor/pull/2824)).
 
 ### Fixes
 
@@ -46,6 +53,9 @@ The minor version will be incremented upon a breaking change and the patch versi
 - cli: Fix `migrate` command not working without global `ts-node` installation ([#2767](https://github.com/coral-xyz/anchor/pull/2767)).
 - client, lang, spl, syn: Enable all features for docs.rs build ([#2774](https://github.com/coral-xyz/anchor/pull/2774)).
 - ts: Fix construction of field layouts for type aliased instruction arguments ([#2821](https://github.com/coral-xyz/anchor/pull/2821))
+- idl: Fix IDL ([#2824](https://github.com/coral-xyz/anchor/pull/2824)).
+- idl, ts: Make casing consistent ([#2824](https://github.com/coral-xyz/anchor/pull/2824)).
+- ts: Fix not being able to use numbers in instruction, account, or event names in some cases due to case conversion ([#2824](https://github.com/coral-xyz/anchor/pull/2824)).
 
 ### Breaking
 
@@ -58,6 +68,14 @@ The minor version will be incremented upon a breaking change and the patch versi
 - ts: Remove `associated`, `account.associated` and `account.associatedAddress` methods ([#2749](https://github.com/coral-xyz/anchor/pull/2749)).
 - cli: `idl upgrade` command closes the IDL buffer account ([#2760](https://github.com/coral-xyz/anchor/pull/2760)).
 - cli: Remove `--jest` option from the `init` command ([#2805](https://github.com/coral-xyz/anchor/pull/2805)).
+- cli: Require `idl-build` feature in program `Cargo.toml` ([#2824](https://github.com/coral-xyz/anchor/pull/2824)).
+- cli: Rename `seeds` feature to `resolution` and make it enabled by default ([#2824](https://github.com/coral-xyz/anchor/pull/2824)).
+- cli: Remove `idl parse` command ([#2824](https://github.com/coral-xyz/anchor/pull/2824)).
+- idl: Change IDL spec ([#2824](https://github.com/coral-xyz/anchor/pull/2824)).
+- syn: Remove `idl-parse` and `seeds` features ([#2824](https://github.com/coral-xyz/anchor/pull/2824)).
+- ts: Change `accounts` method to no longer accept resolvable accounts ([#2824](https://github.com/coral-xyz/anchor/pull/2824)).
+- ts: `Program` instances use camelCase for everything ([#2824](https://github.com/coral-xyz/anchor/pull/2824)).
+- ts: Remove discriminator functions ([#2824](https://github.com/coral-xyz/anchor/pull/2824)).
 
 ## [0.29.0] - 2023-10-16
 

+ 13 - 1
Cargo.lock

@@ -180,8 +180,8 @@ name = "anchor-cli"
 version = "0.29.0"
 dependencies = [
  "anchor-client",
+ "anchor-idl",
  "anchor-lang",
- "anchor-syn",
  "anyhow",
  "base64 0.21.4",
  "bincode",
@@ -257,6 +257,17 @@ dependencies = [
  "syn 1.0.109",
 ]
 
+[[package]]
+name = "anchor-idl"
+version = "0.29.0"
+dependencies = [
+ "anchor-syn",
+ "anyhow",
+ "regex",
+ "serde",
+ "serde_json",
+]
+
 [[package]]
 name = "anchor-lang"
 version = "0.29.0"
@@ -304,6 +315,7 @@ version = "0.29.0"
 dependencies = [
  "anyhow",
  "bs58 0.5.0",
+ "cargo_toml",
  "heck 0.3.3",
  "proc-macro2",
  "quote",

+ 1 - 0
Cargo.toml

@@ -9,6 +9,7 @@ members = [
     "avm",
     "cli",
     "client",
+    "idl",
     "lang",
     "lang/attribute/*",
     "lang/derive/*",

+ 1 - 1
cli/Cargo.toml

@@ -17,8 +17,8 @@ dev = []
 
 [dependencies]
 anchor-client = { path = "../client", version = "0.29.0" }
+anchor-idl = { path = "../idl", features = ["build"], version = "0.29.0" }
 anchor-lang = { path = "../lang", version = "0.29.0" }
-anchor-syn = { path = "../lang/syn", features = ["event-cpi", "idl-parse", "init-if-needed"], version = "0.29.0" }
 anyhow = "1.0.32"
 base64 = "0.21"
 bincode = "1.3.3"

+ 25 - 6
cli/src/config.rs

@@ -1,6 +1,6 @@
 use crate::is_hidden;
 use anchor_client::Cluster;
-use anchor_syn::idl::types::Idl;
+use anchor_idl::types::Idl;
 use anyhow::{anyhow, bail, Context, Error, Result};
 use clap::{Parser, ValueEnum};
 use dirs::home_dir;
@@ -375,14 +375,33 @@ pub struct ToolchainConfig {
     pub solana_version: Option<String>,
 }
 
-#[derive(Default, Clone, Debug, Serialize, Deserialize)]
+#[derive(Clone, Debug, Serialize, Deserialize)]
 pub struct FeaturesConfig {
-    #[serde(default)]
-    pub seeds: bool,
+    /// Enable account resolution.
+    ///
+    /// Not able to specify default bool value: https://github.com/serde-rs/serde/issues/368
+    #[serde(default = "FeaturesConfig::get_default_resolution")]
+    pub resolution: bool,
+    /// Disable safety comment checks
     #[serde(default, rename = "skip-lint")]
     pub skip_lint: bool,
 }
 
+impl FeaturesConfig {
+    fn get_default_resolution() -> bool {
+        true
+    }
+}
+
+impl Default for FeaturesConfig {
+    fn default() -> Self {
+        Self {
+            resolution: Self::get_default_resolution(),
+            skip_lint: false,
+        }
+    }
+}
+
 #[derive(Clone, Debug, Serialize, Deserialize)]
 pub struct RegistryConfig {
     pub url: String,
@@ -619,8 +638,8 @@ impl FromStr for Config {
     type Err = Error;
 
     fn from_str(s: &str) -> Result<Self, Self::Err> {
-        let cfg: _Config = toml::from_str(s)
-            .map_err(|e| anyhow::format_err!("Unable to deserialize config: {}", e.to_string()))?;
+        let cfg: _Config =
+            toml::from_str(s).map_err(|e| anyhow!("Unable to deserialize config: {e}"))?;
         Ok(Config {
             toolchain: cfg.toolchain.unwrap_or_default(),
             features: cfg.features.unwrap_or_default(),

+ 132 - 352
cli/src/lib.rs

@@ -1,15 +1,14 @@
+#![cfg_attr(nightly, feature(proc_macro_span))]
+
 use crate::config::{
     AnchorPackage, BootstrapMode, BuildConfig, Config, ConfigOverride, Manifest, ProgramArch,
     ProgramDeployment, ProgramWorkspace, ScriptsConfig, TestValidator, WithPath,
     DEFAULT_LEDGER_PATH, SHUTDOWN_WAIT, STARTUP_WAIT,
 };
 use anchor_client::Cluster;
+use anchor_idl::types::{Idl, IdlArrayLen, IdlDefinedFields, IdlType, IdlTypeDefTy};
 use anchor_lang::idl::{IdlAccount, IdlInstruction, ERASED_AUTHORITY};
 use anchor_lang::{AccountDeserialize, AnchorDeserialize, AnchorSerialize};
-use anchor_syn::idl::types::{
-    EnumFields, Idl, IdlConst, IdlErrorCode, IdlEvent, IdlType, IdlTypeDefinition,
-    IdlTypeDefinitionTy,
-};
 use anyhow::{anyhow, Context, Result};
 use checks::{check_anchor_version, check_overflow};
 use clap::Parser;
@@ -24,7 +23,7 @@ use reqwest::blocking::multipart::{Form, Part};
 use reqwest::blocking::Client;
 use rust_template::{ProgramTemplate, TestTemplate};
 use semver::{Version, VersionReq};
-use serde::{Deserialize, Serialize};
+use serde::Deserialize;
 use serde_json::{json, Map, Value as JsonValue};
 use solana_client::rpc_client::RpcClient;
 use solana_program::instruction::{AccountMeta, Instruction};
@@ -440,23 +439,11 @@ pub enum IdlCommand {
         /// The program to view.
         program_id: Pubkey,
     },
-    /// Parses an IDL from source.
-    Parse {
-        /// Path to the program's interface definition.
-        #[clap(short, long)]
-        file: String,
-        /// Output file for the IDL (stdout if not specified).
-        #[clap(short, long)]
-        out: Option<String>,
-        /// Output file for the TypeScript IDL.
-        #[clap(short = 't', long)]
-        out_ts: Option<String>,
-        /// Suppress doc strings in output
-        #[clap(long)]
-        no_docs: bool,
-    },
     /// Generates the IDL for the program using the compilation method.
     Build {
+        // Program name to build the IDL of(current dir's program if not specified)
+        #[clap(short, long)]
+        program_name: Option<String>,
         /// Output file for the IDL (stdout if not specified)
         #[clap(short, long)]
         out: Option<String>,
@@ -466,6 +453,9 @@ pub enum IdlCommand {
         /// Suppress doc strings in output
         #[clap(long)]
         no_docs: bool,
+        /// Do not check for safety comments
+        #[clap(long)]
+        skip_lint: bool,
     },
     /// Fetches an IDL for the given address from a cluster.
     /// The address can be a program, IDL account, or IDL buffer.
@@ -1464,12 +1454,12 @@ fn build_cwd_verifiable(
             let idl = generate_idl(cfg, skip_lint, no_docs)?;
             // Write out the JSON file.
             println!("Writing the IDL file");
-            let out_file = workspace_dir.join(format!("target/idl/{}.json", idl.name));
+            let out_file = workspace_dir.join(format!("target/idl/{}.json", idl.metadata.name));
             write_idl(&idl, OutFile::File(out_file))?;
 
             // Write out the TypeScript type.
             println!("Writing the .ts file");
-            let ts_file = workspace_dir.join(format!("target/types/{}.ts", idl.name));
+            let ts_file = workspace_dir.join(format!("target/types/{}.ts", idl.metadata.name));
             fs::write(&ts_file, rust_template::idl_ts(&idl)?)?;
 
             // Copy out the TypeScript type.
@@ -1478,7 +1468,7 @@ fn build_cwd_verifiable(
                     ts_file,
                     workspace_dir
                         .join(&cfg.workspace.types)
-                        .join(idl.name)
+                        .join(idl.metadata.name)
                         .with_extension("ts"),
                 )?;
             }
@@ -1758,13 +1748,17 @@ fn _build_rust_cwd(
     let idl = generate_idl(cfg, skip_lint, no_docs)?;
     // JSON out path.
     let out = match idl_out {
-        None => PathBuf::from(".").join(&idl.name).with_extension("json"),
-        Some(o) => PathBuf::from(&o.join(&idl.name).with_extension("json")),
+        None => PathBuf::from(".")
+            .join(&idl.metadata.name)
+            .with_extension("json"),
+        Some(o) => PathBuf::from(&o.join(&idl.metadata.name).with_extension("json")),
     };
     // TS out path.
     let ts_out = match idl_ts_out {
-        None => PathBuf::from(".").join(&idl.name).with_extension("ts"),
-        Some(o) => PathBuf::from(&o.join(&idl.name).with_extension("ts")),
+        None => PathBuf::from(".")
+            .join(&idl.metadata.name)
+            .with_extension("ts"),
+        Some(o) => PathBuf::from(&o.join(&idl.metadata.name).with_extension("ts")),
     };
 
     // Write out the JSON file.
@@ -1778,7 +1772,7 @@ fn _build_rust_cwd(
             &ts_out,
             cfg_parent
                 .join(&cfg.workspace.types)
-                .join(&idl.name)
+                .join(&idl.metadata.name)
                 .with_extension("ts"),
         )?;
     }
@@ -1839,8 +1833,10 @@ fn _build_solidity_cwd(
 
     // TS out path.
     let ts_out = match idl_ts_out {
-        None => PathBuf::from(".").join(&idl.name).with_extension("ts"),
-        Some(o) => PathBuf::from(&o.join(&idl.name).with_extension("ts")),
+        None => PathBuf::from(".")
+            .join(&idl.metadata.name)
+            .with_extension("ts"),
+        Some(o) => PathBuf::from(&o.join(&idl.metadata.name).with_extension("ts")),
     };
 
     // Write out the TypeScript type.
@@ -1852,7 +1848,7 @@ fn _build_solidity_cwd(
             &ts_out,
             cfg_parent
                 .join(&cfg.workspace.types)
-                .join(&idl.name)
+                .join(&idl.metadata.name)
                 .with_extension("ts"),
         )?;
     }
@@ -2101,17 +2097,13 @@ fn idl(cfg_override: &ConfigOverride, subcmd: IdlCommand) -> Result<()> {
         } => idl_set_authority(cfg_override, program_id, address, new_authority, print_only),
         IdlCommand::EraseAuthority { program_id } => idl_erase_authority(cfg_override, program_id),
         IdlCommand::Authority { program_id } => idl_authority(cfg_override, program_id),
-        IdlCommand::Parse {
-            file,
-            out,
-            out_ts,
-            no_docs,
-        } => idl_parse(cfg_override, file, out, out_ts, no_docs),
         IdlCommand::Build {
+            program_name,
             out,
             out_ts,
             no_docs,
-        } => idl_build(out, out_ts, no_docs),
+            skip_lint,
+        } => idl_build(cfg_override, program_name, out, out_ts, no_docs, skip_lint),
         IdlCommand::Fetch { address, out } => idl_fetch(cfg_override, address, out),
     }
 }
@@ -2419,10 +2411,6 @@ fn idl_close_account(
 // and sending multiple transactions in the event the IDL doesn't fit into
 // a single transaction.
 fn idl_write(cfg: &Config, program_id: &Pubkey, idl: &Idl, idl_address: Pubkey) -> Result<()> {
-    // Remove the metadata before deploy.
-    let mut idl = idl.clone();
-    idl.metadata = None;
-
     // Misc.
     let keypair = solana_sdk::signature::read_keypair_file(&cfg.provider.wallet.to_string())
         .map_err(|_| anyhow!("Unable to read keypair file"))?;
@@ -2431,7 +2419,7 @@ fn idl_write(cfg: &Config, program_id: &Pubkey, idl: &Idl, idl_address: Pubkey)
 
     // Serialize and compress the idl.
     let idl_data = {
-        let json_bytes = serde_json::to_vec(&idl)?;
+        let json_bytes = serde_json::to_vec(idl)?;
         let mut e = ZlibEncoder::new(Vec::new(), Compression::default());
         e.write_all(&json_bytes)?;
         e.finish()?
@@ -2473,287 +2461,78 @@ fn idl_write(cfg: &Config, program_id: &Pubkey, idl: &Idl, idl_address: Pubkey)
     Ok(())
 }
 
-fn idl_parse(
+fn idl_build(
     cfg_override: &ConfigOverride,
-    file: String,
+    program_name: Option<String>,
     out: Option<String>,
     out_ts: Option<String>,
     no_docs: bool,
+    skip_lint: bool,
 ) -> Result<()> {
-    let cfg = Config::discover(cfg_override)?.expect("Not in workspace.");
-    let file = shellexpand::tilde(&file);
-    let manifest_path = std::env::current_dir()?.join(PathBuf::from(&*file).parent().unwrap());
-    let manifest = Manifest::discover_from_path(manifest_path)?
-        .ok_or_else(|| anyhow!("Cargo.toml not found"))?;
-    let idl = generate_idl_parse(
-        &*file,
-        manifest.version(),
-        cfg.features.seeds,
+    let cfg = Config::discover(cfg_override)?.expect("Not in workspace");
+    let program_path = match program_name {
+        Some(name) => cfg.get_program(&name)?.path,
+        None => {
+            let current_dir = std::env::current_dir()?;
+            cfg.read_all_programs()?
+                .into_iter()
+                .find(|program| program.path == current_dir)
+                .ok_or_else(|| anyhow!("Not in a program directory"))?
+                .path
+        }
+    };
+    let idl = anchor_idl::build::build_idl(
+        program_path,
+        cfg.features.resolution,
+        cfg.features.skip_lint || skip_lint,
         no_docs,
-        !cfg.features.skip_lint,
     )?;
-
     let out = match out {
+        Some(path) => OutFile::File(PathBuf::from(path)),
         None => OutFile::Stdout,
-        Some(out) => OutFile::File(PathBuf::from(out)),
     };
     write_idl(&idl, out)?;
 
-    // Write out the TypeScript IDL.
-    if let Some(out) = out_ts {
-        fs::write(out, rust_template::idl_ts(&idl)?)?;
+    if let Some(path) = out_ts {
+        fs::write(path, rust_template::idl_ts(&idl)?)?;
     }
 
     Ok(())
 }
 
-fn idl_build(out: Option<String>, out_ts: Option<String>, no_docs: bool) -> Result<()> {
-    let idls = generate_idl_build(no_docs)?;
-    if idls.len() == 1 {
-        let idl = &idls[0];
-        let out = match out {
-            None => OutFile::Stdout,
-            Some(path) => OutFile::File(PathBuf::from(path)),
-        };
-        write_idl(idl, out)?;
-
-        if let Some(path) = out_ts {
-            fs::write(path, rust_template::idl_ts(idl)?)?;
-        }
-    } else {
-        println!("{}", serde_json::to_string_pretty(&idls)?);
-    };
-
-    Ok(())
-}
-
 /// Generate IDL with method decided by whether manifest file has `idl-build` feature or not.
 fn generate_idl(cfg: &WithPath<Config>, skip_lint: bool, no_docs: bool) -> Result<Idl> {
-    let manifest = Manifest::discover()?.ok_or_else(|| anyhow!("Cargo.toml not found"))?;
-
     // Check whether the manifest has `idl-build` feature
+    let manifest = Manifest::discover()?.ok_or_else(|| anyhow!("Cargo.toml not found"))?;
     let is_idl_build = manifest
         .features
         .iter()
         .any(|(feature, _)| feature == "idl-build");
-    if is_idl_build {
-        generate_idl_build(no_docs)?
-            .into_iter()
-            .next()
-            .ok_or_else(|| anyhow!("Could not build IDL"))
-    } else {
-        generate_idl_parse(
-            "src/lib.rs",
-            manifest.version(),
-            cfg.features.seeds,
-            no_docs,
-            !(cfg.features.skip_lint || skip_lint),
-        )
-    }
-}
-
-/// Generate IDL with the parsing method(default).
-fn generate_idl_parse(
-    path: impl AsRef<Path>,
-    version: String,
-    seeds_feature: bool,
-    no_docs: bool,
-    safety_checks: bool,
-) -> Result<Idl> {
-    anchor_syn::idl::parse::file::parse(path, version, seeds_feature, no_docs, safety_checks)
-}
-
-/// Generate IDL with the build method.
-fn generate_idl_build(no_docs: bool) -> Result<Vec<Idl>> {
-    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_BUILD_NO_DOCS", no_docs)
-        .env("ANCHOR_IDL_BUILD_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 IdlBuildEventPrint {
-        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 output = String::from_utf8_lossy(&exit.stdout);
-    for line in output.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.name.clone(), 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: IdlBuildEventPrint = serde_json::from_str(&lines.join("\n"))?;
-                    events.push(event.event);
-                    defined_types.extend(
-                        event
-                            .defined_types
-                            .into_iter()
-                            .map(|ty| (ty.name.clone(), 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());
-            }
-        }
-    }
-
-    // Convert path to name if there are no conflicts
-    let path_regex = Regex::new(r#""((\w+::)+)(\w+)""#).unwrap();
-    let idls = idls
-        .into_iter()
-        .filter_map(|idl| {
-            let mut modified_idl = serde_json::to_string(&idl).unwrap();
-
-            // TODO: Remove. False positive https://github.com/rust-lang/rust-clippy/issues/10577
-            #[allow(clippy::redundant_clone)]
-            for captures in path_regex.captures_iter(&modified_idl.clone()) {
-                let path = captures.get(0).unwrap().as_str();
-                let name = captures.get(3).unwrap().as_str();
-
-                // Replace path with name
-                let replaced_idl = modified_idl.replace(path, &format!(r#""{name}""#));
-
-                // Check whether there is a conflict
-                let has_conflict = replaced_idl.contains(&format!(r#"::{name}""#));
-                if !has_conflict {
-                    modified_idl = replaced_idl;
-                }
-            }
+    if !is_idl_build {
+        let path = manifest.path().display();
+        let anchor_spl_idl_build = manifest
+            .dependencies
+            .iter()
+            .any(|dep| dep.0 == "anchor-spl")
+            .then_some(r#", "anchor-spl/idl-build""#)
+            .unwrap_or_default();
 
-            serde_json::from_str::<Idl>(&modified_idl).ok()
-        })
-        .collect::<Vec<_>>();
+        return Err(anyhow!(
+            r#"`idl-build` feature is missing. To solve, add
 
-    // Verify IDLs are valid
-    for idl in &idls {
-        let full_path_account = idl
-            .accounts
-            .iter()
-            .find(|account| account.name.contains("::"));
+[features]
+idl-build = ["anchor-lang/idl-build"{anchor_spl_idl_build}]
 
-        if let Some(account) = full_path_account {
-            return Err(anyhow!(
-                "Conflicting accounts names are not allowed.\nProgram: {}\nAccount: {}",
-                idl.name,
-                account.name
-            ));
-        }
+in `{path}`."#
+        ));
     }
 
-    Ok(idls)
+    anchor_idl::build::build_idl(
+        std::env::current_dir()?,
+        cfg.features.resolution,
+        cfg.features.skip_lint || skip_lint,
+        no_docs,
+    )
 }
 
 fn idl_fetch(cfg_override: &ConfigOverride, address: Pubkey, out: Option<String>) -> Result<()> {
@@ -2841,7 +2620,7 @@ fn account(
             let bytes = fs::read(idl_path).expect("Unable to read IDL.");
             let idl: Idl = serde_json::from_reader(&*bytes).expect("Invalid IDL format.");
 
-            if idl.name != program_name {
+            if idl.metadata.name != program_name {
                 panic!("IDL does not match program {program_name}.");
             }
 
@@ -2882,25 +2661,40 @@ fn deserialize_idl_defined_type_to_json(
     data: &mut &[u8],
 ) -> Result<JsonValue, anyhow::Error> {
     let defined_type = &idl
-        .types
+        .accounts
         .iter()
-        .chain(idl.accounts.iter())
-        .find(|defined_type| defined_type.name == defined_type_name)
+        .find(|acc| acc.name == defined_type_name)
+        .and_then(|acc| idl.types.iter().find(|ty| ty.name == acc.name))
+        .or_else(|| idl.types.iter().find(|ty| ty.name == defined_type_name))
         .ok_or_else(|| anyhow!("Type `{}` not found in IDL.", defined_type_name))?
         .ty;
 
     let mut deserialized_fields = Map::new();
 
     match defined_type {
-        IdlTypeDefinitionTy::Struct { fields } => {
-            for field in fields {
-                deserialized_fields.insert(
-                    field.name.clone(),
-                    deserialize_idl_type_to_json(&field.ty, data, idl)?,
-                );
+        IdlTypeDefTy::Struct { fields } => {
+            if let Some(fields) = fields {
+                match fields {
+                    IdlDefinedFields::Named(fields) => {
+                        for field in fields {
+                            deserialized_fields.insert(
+                                field.name.clone(),
+                                deserialize_idl_type_to_json(&field.ty, data, idl)?,
+                            );
+                        }
+                    }
+                    IdlDefinedFields::Tuple(fields) => {
+                        let mut values = Vec::new();
+                        for field in fields {
+                            values.push(deserialize_idl_type_to_json(field, data, idl)?);
+                        }
+                        deserialized_fields
+                            .insert(defined_type_name.to_owned(), JsonValue::Array(values));
+                    }
+                }
             }
         }
-        IdlTypeDefinitionTy::Enum { variants } => {
+        IdlTypeDefTy::Enum { variants } => {
             let repr = <u8 as AnchorDeserialize>::deserialize(data)?;
 
             let variant = variants
@@ -2911,25 +2705,21 @@ fn deserialize_idl_defined_type_to_json(
 
             if let Some(enum_field) = &variant.fields {
                 match enum_field {
-                    EnumFields::Named(fields) => {
+                    IdlDefinedFields::Named(fields) => {
                         let mut values = Map::new();
-
                         for field in fields {
                             values.insert(
                                 field.name.clone(),
                                 deserialize_idl_type_to_json(&field.ty, data, idl)?,
                             );
                         }
-
                         value = JsonValue::Object(values);
                     }
-                    EnumFields::Tuple(fields) => {
+                    IdlDefinedFields::Tuple(fields) => {
                         let mut values = Vec::new();
-
                         for field in fields {
                             values.push(deserialize_idl_type_to_json(field, data, idl)?);
                         }
-
                         value = JsonValue::Array(values);
                     }
                 }
@@ -2937,8 +2727,8 @@ fn deserialize_idl_defined_type_to_json(
 
             deserialized_fields.insert(variant.name.clone(), value);
         }
-        IdlTypeDefinitionTy::Alias { value } => {
-            return deserialize_idl_type_to_json(value, data, idl);
+        IdlTypeDefTy::Type { alias } => {
+            return deserialize_idl_type_to_json(alias, data, idl);
         }
     }
 
@@ -3000,12 +2790,22 @@ fn deserialize_idl_type_to_json(
                 .collect(),
         ),
         IdlType::String => json!(<String as AnchorDeserialize>::deserialize(data)?),
-        IdlType::PublicKey => {
+        IdlType::Pubkey => {
             json!(<Pubkey as AnchorDeserialize>::deserialize(data)?.to_string())
         }
-        IdlType::Defined(type_name) => {
-            deserialize_idl_defined_type_to_json(parent_idl, type_name, data)?
-        }
+        IdlType::Array(ty, size) => match size {
+            IdlArrayLen::Value(size) => {
+                let mut array_data: Vec<JsonValue> = Vec::with_capacity(*size);
+
+                for _ in 0..*size {
+                    array_data.push(deserialize_idl_type_to_json(ty, data, parent_idl)?);
+                }
+
+                JsonValue::Array(array_data)
+            }
+            // TODO:
+            IdlArrayLen::Generic(_) => unimplemented!("Generic array length is not yet supported"),
+        },
         IdlType::Option(ty) => {
             let is_present = <u8 as AnchorDeserialize>::deserialize(data)?;
 
@@ -3028,20 +2828,14 @@ fn deserialize_idl_type_to_json(
 
             JsonValue::Array(vec_data)
         }
-        IdlType::Array(ty, size) => {
-            let mut array_data: Vec<JsonValue> = Vec::with_capacity(*size);
-
-            for _ in 0..*size {
-                array_data.push(deserialize_idl_type_to_json(ty, data, parent_idl)?);
-            }
-
-            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 { name: _, args: _ } => {
-            todo!("Defined types with type args are not yet supported")
+        IdlType::Defined {
+            name,
+            generics: _generics,
+        } => {
+            // TODO: Generics
+            deserialize_idl_defined_type_to_json(parent_idl, name, data)?
         }
+        IdlType::Generic(generic) => json!(generic),
     })
 }
 
@@ -3314,11 +3108,11 @@ fn validator_flags(
 
         if let Some(idl) = program.idl.as_mut() {
             // Add program address to the IDL.
-            idl.metadata = Some(serde_json::to_value(IdlTestMetadata { address })?);
+            idl.address = address;
 
             // Persist it.
             let idl_out = PathBuf::from("target/idl")
-                .join(&idl.name)
+                .join(&idl.metadata.name)
                 .with_extension("json");
             write_idl(idl, OutFile::File(idl_out))?;
         }
@@ -3443,22 +3237,15 @@ fn stream_logs(config: &WithPath<Config>, rpc_url: &str) -> Result<Vec<std::proc
         let mut contents = vec![];
         file.read_to_end(&mut contents)?;
         let idl: Idl = serde_json::from_slice(&contents)?;
-        let metadata = idl.metadata.ok_or_else(|| {
-            anyhow!(
-                "Metadata property not found in IDL of program: {}",
-                program.lib_name
-            )
-        })?;
-        let metadata: IdlTestMetadata = serde_json::from_value(metadata)?;
 
         let log_file = File::create(format!(
             "{}/{}.{}.log",
-            program_logs_dir, metadata.address, program.lib_name,
+            program_logs_dir, idl.address, program.lib_name,
         ))?;
         let stdio = std::process::Stdio::from(log_file);
         let child = std::process::Command::new("solana")
             .arg("logs")
-            .arg(metadata.address)
+            .arg(idl.address)
             .arg("--url")
             .arg(rpc_url)
             .stdout(stdio)
@@ -3485,11 +3272,6 @@ fn stream_logs(config: &WithPath<Config>, rpc_url: &str) -> Result<Vec<std::proc
     Ok(handles)
 }
 
-#[derive(Debug, Serialize, Deserialize)]
-pub struct IdlTestMetadata {
-    address: String,
-}
-
 fn start_test_validator(
     cfg: &Config,
     test_validator: &Option<TestValidator>,
@@ -3720,13 +3502,11 @@ fn deploy(
 
             if let Some(idl) = program.idl.as_mut() {
                 // Add program address to the IDL.
-                idl.metadata = Some(serde_json::to_value(IdlTestMetadata {
-                    address: program_id.to_string(),
-                })?);
+                idl.address = program_id.to_string();
 
                 // Persist it.
                 let idl_out = PathBuf::from("target/idl")
-                    .join(&idl.name)
+                    .join(&idl.metadata.name)
                     .with_extension("json");
                 write_idl(idl, OutFile::File(idl_out))?;
             }
@@ -4046,7 +3826,7 @@ fn shell(cfg_override: &ConfigOverride) -> Result<()> {
                 .filter(|program| program.idl.is_some())
                 .map(|program| {
                     (
-                        program.idl.as_ref().unwrap().name.clone(),
+                        program.idl.as_ref().unwrap().metadata.name.clone(),
                         program.idl.clone().unwrap(),
                     )
                 })

+ 46 - 26
cli/src/rust_template.rs

@@ -2,10 +2,11 @@ use crate::{
     config::ProgramWorkspace, create_files, override_or_create_files, solidity_template, Files,
     VERSION,
 };
-use anchor_syn::idl::types::Idl;
+use anchor_idl::types::Idl;
 use anyhow::Result;
 use clap::{Parser, ValueEnum};
-use heck::{ToLowerCamelCase, ToSnakeCase, ToUpperCamelCase};
+use heck::{ToLowerCamelCase, ToPascalCase, ToSnakeCase};
+use regex::Regex;
 use solana_sdk::{
     pubkey::Pubkey,
     signature::{read_keypair_file, write_keypair_file, Keypair},
@@ -17,6 +18,7 @@ use std::{
     io::Write as _,
     path::Path,
     process::Stdio,
+    str::FromStr,
 };
 
 /// Program initialization template
@@ -183,11 +185,12 @@ crate-type = ["cdylib", "lib"]
 name = "{1}"
 
 [features]
+default = []
+cpi = ["no-entrypoint"]
 no-entrypoint = []
 no-idl = []
 no-log-ix-name = []
-cpi = ["no-entrypoint"]
-default = []
+idl-build = ["anchor-lang/idl-build"]
 
 [dependencies]
 anchor-lang = "{2}"
@@ -228,20 +231,37 @@ token = "{token}"
 }
 
 pub fn idl_ts(idl: &Idl) -> Result<String> {
-    let mut idl = idl.clone();
-    for acc in idl.accounts.iter_mut() {
-        acc.name = acc.name.to_lower_camel_case();
-    }
-    let idl_json = serde_json::to_string_pretty(&idl)?;
-    Ok(format!(
-        r#"export type {} = {};
+    let idl_name = &idl.metadata.name;
+    let type_name = idl_name.to_pascal_case();
+    let idl = serde_json::to_string(idl)?;
+
+    // Convert every field of the IDL to camelCase
+    let camel_idl = Regex::new(r#""\w+":"([\w\d]+)""#)?
+        .captures_iter(&idl)
+        .fold(idl.clone(), |acc, cur| {
+            let name = cur.get(1).unwrap().as_str();
+
+            // Do not modify pubkeys
+            if Pubkey::from_str(name).is_ok() {
+                return acc;
+            }
 
-export const IDL: {} = {};
-"#,
-        idl.name.to_upper_camel_case(),
-        idl_json,
-        idl.name.to_upper_camel_case(),
-        idl_json
+            let camel_name = name.to_lower_camel_case();
+            acc.replace(&format!(r#""{name}""#), &format!(r#""{camel_name}""#))
+        });
+
+    // Pretty format
+    let camel_idl = serde_json::to_string_pretty(&serde_json::from_str::<Idl>(&camel_idl)?)?;
+
+    Ok(format!(
+        r#"/**
+ * Program IDL in camelCase format in order to be used in JS/TS.
+ *
+ * Note that this is only a type helper and is not the actual IDL. The original
+ * IDL can be found at `target/idl/{idl_name}.json`.
+ */
+export type {type_name} = {camel_idl};
+"#
     ))
 }
 
@@ -347,7 +367,7 @@ describe("{}", () => {{
 }});
 "#,
         name,
-        name.to_upper_camel_case(),
+        name.to_pascal_case(),
     )
 }
 
@@ -368,7 +388,7 @@ describe("{}", () => {{
 }});
 "#,
         name,
-        name.to_upper_camel_case(),
+        name.to_pascal_case(),
     )
 }
 
@@ -478,11 +498,11 @@ describe("{}", () => {{
   }});
 }});
 "#,
-        name.to_upper_camel_case(),
+        name.to_pascal_case(),
         name.to_snake_case(),
         name,
-        name.to_upper_camel_case(),
-        name.to_upper_camel_case(),
+        name.to_pascal_case(),
+        name.to_pascal_case(),
     )
 }
 
@@ -505,11 +525,11 @@ describe("{}", () => {{
   }});
 }});
 "#,
-        name.to_upper_camel_case(),
+        name.to_pascal_case(),
         name.to_snake_case(),
         name,
-        name.to_upper_camel_case(),
-        name.to_upper_camel_case(),
+        name.to_pascal_case(),
+        name.to_pascal_case(),
     )
 }
 
@@ -606,7 +626,7 @@ anchor.setProvider(provider);
             r#"
 anchor.workspace.{} = new anchor.Program({}, new PublicKey("{}"), provider);
 "#,
-            program.name.to_upper_camel_case(),
+            program.name.to_pascal_case(),
             serde_json::to_string(&program.idl)?,
             program.program_id
         )?;

+ 1 - 0
examples/tutorial/basic-0/programs/basic-0/Cargo.toml

@@ -12,6 +12,7 @@ name = "basic_0"
 [features]
 no-entrypoint = []
 cpi = ["no-entrypoint"]
+idl-build = ["anchor-lang/idl-build"]
 
 [dependencies]
 anchor-lang = { path = "../../../../../lang" }

+ 1 - 0
examples/tutorial/basic-1/programs/basic-1/Cargo.toml

@@ -12,6 +12,7 @@ name = "basic_1"
 [features]
 no-entrypoint = []
 cpi = ["no-entrypoint"]
+idl-build = ["anchor-lang/idl-build"]
 
 [dependencies]
 anchor-lang = { path = "../../../../../lang" }

+ 1 - 0
examples/tutorial/basic-2/programs/basic-2/Cargo.toml

@@ -12,6 +12,7 @@ name = "basic_2"
 [features]
 no-entrypoint = []
 cpi = ["no-entrypoint"]
+idl-build = ["anchor-lang/idl-build"]
 
 [dependencies]
 anchor-lang = { path = "../../../../../lang" }

+ 1 - 0
examples/tutorial/basic-3/programs/puppet-master/Cargo.toml

@@ -12,6 +12,7 @@ name = "puppet_master"
 [features]
 no-entrypoint = []
 cpi = ["no-entrypoint"]
+idl-build = ["anchor-lang/idl-build"]
 
 [dependencies]
 anchor-lang = { path = "../../../../../lang" }

+ 1 - 0
examples/tutorial/basic-3/programs/puppet/Cargo.toml

@@ -12,6 +12,7 @@ name = "puppet"
 [features]
 no-entrypoint = []
 cpi = ["no-entrypoint"]
+idl-build = ["anchor-lang/idl-build"]
 
 [dependencies]
 anchor-lang = { path = "../../../../../lang" }

+ 1 - 0
examples/tutorial/basic-4/programs/basic-4/Cargo.toml

@@ -12,6 +12,7 @@ name = "basic_4"
 [features]
 no-entrypoint = []
 cpi = ["no-entrypoint"]
+idl-build = ["anchor-lang/idl-build"]
 
 [dependencies]
 anchor-lang = { path = "../../../../../lang" }

+ 1 - 0
examples/tutorial/basic-5/programs/basic-5/Cargo.toml

@@ -12,6 +12,7 @@ name = "basic_5"
 [features]
 no-entrypoint = []
 cpi = ["no-entrypoint"]
+idl-build = ["anchor-lang/idl-build"]
 
 [dependencies]
 anchor-lang = { path = "../../../../../lang" }

+ 30 - 0
idl/Cargo.toml

@@ -0,0 +1,30 @@
+[package]
+name = "anchor-idl"
+version = "0.29.0"
+authors = ["Anchor Maintainers <accounts@200ms.io>"]
+repository = "https://github.com/coral-xyz/anchor"
+rust-version = "1.60"
+edition = "2021"
+license = "Apache-2.0"
+description = "Anchor framework IDL"
+
+[package.metadata.docs.rs]
+all-features = true
+rustdoc-args = ["--cfg", "docsrs"]
+
+[features]
+build = [
+    "anyhow",
+    "regex",
+    "serde",
+    "serde_json",
+]
+
+[dependencies]
+anchor-syn = { path = "../lang/syn", version = "0.29.0", features = ["idl-types"] }
+
+# `build` feature only
+anyhow = { version = "1", optional = true }
+regex = { version = "1", optional = true }
+serde = { version = "1", features = ["derive"], optional = true }
+serde_json = { version = "1", optional = true }

+ 252 - 0
idl/src/build.rs

@@ -0,0 +1,252 @@
+use std::{
+    collections::BTreeMap,
+    env, mem,
+    path::Path,
+    process::{Command, Stdio},
+};
+
+use anchor_syn::parser::context::CrateContext;
+use anyhow::{anyhow, Result};
+use regex::Regex;
+use serde::Deserialize;
+
+use crate::types::{Idl, IdlEvent, IdlTypeDef};
+
+/// Generate IDL via compilation.
+pub fn build_idl(
+    program_path: impl AsRef<Path>,
+    resolution: bool,
+    skip_lint: bool,
+    no_docs: bool,
+) -> Result<Idl> {
+    // Check safety comments
+    let program_path = program_path.as_ref();
+    let lib_path = program_path.join("src").join("lib.rs");
+    let ctx = CrateContext::parse(lib_path)?;
+    if !skip_lint {
+        ctx.safety_checks()?;
+    }
+
+    let idl = build(program_path, resolution, no_docs)?;
+    let idl = convert_module_paths(idl);
+    let idl = sort(idl);
+    verify(&idl)?;
+
+    Ok(idl)
+}
+
+// Build IDL.
+fn build(program_path: &Path, resolution: bool, no_docs: bool) -> Result<Idl> {
+    // `nightly` toolchain is currently required for building the IDL.
+    //
+    // Pinning the toolchain to an older date in order to fix
+    // `error[E0635]: unknown feature stdsimd` error from `ahash`.
+    // See: https://github.com/tkaitchuck/aHash/issues/200
+    //
+    // There is also another error when using a date after 2024-01-30
+    // `error[E0412]: cannot find type `T` in this scope``
+    //
+    // TODO: Unpin `nightly` release after upgrading Solana to `1.18`.
+    const TOOLCHAIN: &str = "+nightly-2024-01-30";
+    install_toolchain_if_needed(TOOLCHAIN)?;
+
+    let output = Command::new("cargo")
+        .args([
+            TOOLCHAIN,
+            "test",
+            "__anchor_private_print_idl",
+            "--features",
+            "idl-build",
+            "--",
+            "--show-output",
+            "--quiet",
+        ])
+        .env(
+            "ANCHOR_IDL_BUILD_NO_DOCS",
+            if no_docs { "TRUE" } else { "FALSE" },
+        )
+        .env(
+            "ANCHOR_IDL_BUILD_RESOLUTION",
+            if resolution { "TRUE" } else { "FALSE" },
+        )
+        .env("RUSTFLAGS", "--cfg procmacro2_semver_exempt")
+        .current_dir(program_path)
+        .stderr(Stdio::inherit())
+        .output()?;
+    if !output.status.success() {
+        return Err(anyhow!("Building IDL failed"));
+    }
+
+    enum State {
+        Pass,
+        Address,
+        Constants(Vec<String>),
+        Events(Vec<String>),
+        Errors(Vec<String>),
+        Program(Vec<String>),
+    }
+
+    let mut address = String::new();
+    let mut events = vec![];
+    let mut error_codes = vec![];
+    let mut constants = vec![];
+    let mut types = BTreeMap::new();
+    let mut idl: Option<Idl> = None;
+
+    let output = String::from_utf8_lossy(&output.stdout);
+    if env::var("ANCHOR_LOG").is_ok() {
+        println!("{}", output);
+    }
+
+    let mut state = State::Pass;
+    for line in output.lines() {
+        match &mut state {
+            State::Pass => match line {
+                "--- IDL begin address ---" => state = State::Address,
+                "--- IDL begin const ---" => state = State::Constants(vec![]),
+                "--- IDL begin event ---" => state = State::Events(vec![]),
+                "--- IDL begin errors ---" => state = State::Errors(vec![]),
+                "--- IDL begin program ---" => state = State::Program(vec![]),
+                _ => {
+                    if line.starts_with("test result: ok") {
+                        if let Some(idl) = idl.as_mut() {
+                            idl.address = mem::take(&mut address);
+                            idl.constants = mem::take(&mut constants);
+                            idl.events = mem::take(&mut events);
+                            idl.errors = mem::take(&mut error_codes);
+                            idl.types = {
+                                let prog_ty = mem::take(&mut idl.types);
+                                let mut types = mem::take(&mut types);
+                                types.extend(prog_ty.into_iter().map(|ty| (ty.name.clone(), ty)));
+                                types.into_values().collect()
+                            };
+                        }
+                    }
+                }
+            },
+            State::Address => {
+                address = line.replace(|c: char| !c.is_alphanumeric(), "");
+                state = State::Pass;
+                continue;
+            }
+            State::Constants(lines) => {
+                if line == "--- IDL end const ---" {
+                    let constant = serde_json::from_str(&lines.join("\n"))?;
+                    constants.push(constant);
+                    state = State::Pass;
+                    continue;
+                }
+
+                lines.push(line.to_owned());
+            }
+            State::Events(lines) => {
+                if line == "--- IDL end event ---" {
+                    #[derive(Deserialize)]
+                    struct IdlBuildEventPrint {
+                        event: IdlEvent,
+                        types: Vec<IdlTypeDef>,
+                    }
+
+                    let event = serde_json::from_str::<IdlBuildEventPrint>(&lines.join("\n"))?;
+                    events.push(event.event);
+                    types.extend(event.types.into_iter().map(|ty| (ty.name.clone(), ty)));
+                    state = State::Pass;
+                    continue;
+                }
+
+                lines.push(line.to_owned());
+            }
+            State::Errors(lines) => {
+                if line == "--- IDL end errors ---" {
+                    error_codes = serde_json::from_str(&lines.join("\n"))?;
+                    state = State::Pass;
+                    continue;
+                }
+
+                lines.push(line.to_owned());
+            }
+            State::Program(lines) => {
+                if line == "--- IDL end program ---" {
+                    idl = Some(serde_json::from_str(&lines.join("\n"))?);
+                    state = State::Pass;
+                    continue;
+                }
+
+                lines.push(line.to_owned());
+            }
+        }
+    }
+
+    idl.ok_or_else(|| anyhow!("IDL doesn't exist"))
+}
+
+/// Install the given toolchain if it's not already installed.
+fn install_toolchain_if_needed(toolchain: &str) -> Result<()> {
+    let is_installed = Command::new("cargo")
+        .arg(toolchain)
+        .output()?
+        .status
+        .success();
+    if !is_installed {
+        Command::new("rustup")
+            .args(["toolchain", "install", toolchain.trim_start_matches('+')])
+            .spawn()?
+            .wait()?;
+    }
+
+    Ok(())
+}
+
+/// Convert paths to name if there are no conflicts.
+fn convert_module_paths(idl: Idl) -> Idl {
+    let idl = serde_json::to_string(&idl).unwrap();
+    let idl = Regex::new(r#""((\w+::)+)(\w+)""#)
+        .unwrap()
+        .captures_iter(&idl.clone())
+        .fold(idl, |acc, cur| {
+            let path = cur.get(0).unwrap().as_str();
+            let name = cur.get(3).unwrap().as_str();
+
+            // Replace path with name
+            let replaced_idl = acc.replace(path, &format!(r#""{name}""#));
+
+            // Check whether there is a conflict
+            let has_conflict = replaced_idl.contains(&format!(r#"::{name}""#));
+            if has_conflict {
+                acc
+            } else {
+                replaced_idl
+            }
+        });
+
+    serde_json::from_str(&idl).expect("Invalid IDL")
+}
+
+/// Alphabetically sort fields for consistency.
+fn sort(mut idl: Idl) -> Idl {
+    idl.accounts.sort_by(|a, b| a.name.cmp(&b.name));
+    idl.constants.sort_by(|a, b| a.name.cmp(&b.name));
+    idl.events.sort_by(|a, b| a.name.cmp(&b.name));
+    idl.instructions.sort_by(|a, b| a.name.cmp(&b.name));
+    idl.types.sort_by(|a, b| a.name.cmp(&b.name));
+
+    idl
+}
+
+/// Verify IDL is valid.
+fn verify(idl: &Idl) -> Result<()> {
+    // Check full path accounts
+    if let Some(account) = idl
+        .accounts
+        .iter()
+        .find(|account| account.name.contains("::"))
+    {
+        return Err(anyhow!(
+            "Conflicting accounts names are not allowed.\nProgram: `{}`\nAccount: `{}`",
+            idl.metadata.name,
+            account.name
+        ));
+    }
+
+    Ok(())
+}

+ 6 - 0
idl/src/lib.rs

@@ -0,0 +1,6 @@
+//! Anchor IDL.
+
+pub mod types;
+
+#[cfg(feature = "build")]
+pub mod build;

+ 3 - 0
idl/src/types.rs

@@ -0,0 +1,3 @@
+//! IDL types are re-exported from [`anchor_syn`].
+
+pub use anchor_syn::idl::types::*;

+ 28 - 5
lang/attribute/account/src/lib.rs

@@ -1,7 +1,5 @@
 extern crate proc_macro;
 
-#[cfg(feature = "idl-build")]
-use anchor_syn::idl::build::*;
 use quote::quote;
 use syn::parse_macro_input;
 
@@ -404,8 +402,18 @@ pub fn zero_copy(
 
     #[cfg(feature = "idl-build")]
     {
-        let no_docs = get_no_docs();
-        let idl_build_impl = gen_idl_build_impl_for_struct(&account_strct, no_docs);
+        let derive_unsafe = if is_unsafe {
+            // Not a real proc-macro but exists in order to pass the serialization info
+            quote! { #[derive(bytemuck::Unsafe)] }
+        } else {
+            quote! {}
+        };
+        let zc_struct = syn::parse2(quote! {
+            #derive_unsafe
+            #ret
+        })
+        .unwrap();
+        let idl_build_impl = anchor_syn::idl::build::impl_idl_build_struct(&zc_struct);
         return proc_macro::TokenStream::from(quote! {
             #ret
             #idl_build_impl
@@ -420,6 +428,21 @@ pub fn zero_copy(
 /// based programs.
 #[proc_macro]
 pub fn declare_id(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
+    #[cfg(feature = "idl-build")]
+    let address = input.clone().to_string();
+
     let id = parse_macro_input!(input as id::Id);
-    proc_macro::TokenStream::from(quote! {#id})
+    let ret = quote! { #id };
+
+    #[cfg(feature = "idl-build")]
+    {
+        let idl_print = anchor_syn::idl::build::gen_idl_print_fn_address(address);
+        return proc_macro::TokenStream::from(quote! {
+            #ret
+            #idl_print
+        });
+    }
+
+    #[allow(unreachable_code)]
+    proc_macro::TokenStream::from(ret)
 }

+ 3 - 4
lang/attribute/constant/src/lib.rs

@@ -1,8 +1,5 @@
 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]
@@ -12,9 +9,11 @@ pub fn constant(
 ) -> proc_macro::TokenStream {
     #[cfg(feature = "idl-build")]
     {
+        use quote::quote;
+
         let ts = match syn::parse(input).unwrap() {
             syn::Item::Const(item) => {
-                let idl_print = gen_idl_print_function_for_constant(&item);
+                let idl_print = anchor_syn::idl::build::gen_idl_print_fn_constant(&item);
                 quote! {
                     #item
                     #idl_print

+ 1 - 1
lang/attribute/event/src/lib.rs

@@ -46,7 +46,7 @@ pub fn event(
 
     #[cfg(feature = "idl-build")]
     {
-        let idl_build = anchor_syn::idl::build::gen_idl_print_function_for_event(&event_strct);
+        let idl_build = anchor_syn::idl::build::gen_idl_print_fn_event(&event_strct);
         return proc_macro::TokenStream::from(quote! {
             #ret
             #idl_build

+ 1 - 1
lang/derive/accounts/src/lib.rs

@@ -628,7 +628,7 @@ use syn::parse_macro_input;
 ///     <tbody>
 /// </table>
 #[proc_macro_derive(Accounts, attributes(account, instruction))]
-pub fn derive_anchor_deserialize(item: TokenStream) -> TokenStream {
+pub fn derive_accounts(item: TokenStream) -> TokenStream {
     parse_macro_input!(item as anchor_syn::AccountsStruct)
         .to_token_stream()
         .into()

+ 5 - 10
lang/derive/serde/src/lib.rs

@@ -5,9 +5,6 @@ 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());
 
@@ -35,15 +32,13 @@ pub fn anchor_serialize(input: TokenStream) -> TokenStream {
 
     #[cfg(feature = "idl-build")]
     {
-        let no_docs = get_no_docs();
+        use anchor_syn::idl::build::*;
+        use quote::quote;
 
         let idl_build_impl = match syn::parse(input).unwrap() {
-            Item::Struct(item) => gen_idl_build_impl_for_struct(&item, no_docs),
-            Item::Enum(item) => gen_idl_build_impl_for_enum(item, no_docs),
-            Item::Union(item) => {
-                // unions are not included in the IDL - TODO print a warning
-                idl_build_impl_skeleton(quote! {None}, quote! {}, &item.ident, &item.generics)
-            }
+            Item::Struct(item) => impl_idl_build_struct(&item),
+            Item::Enum(item) => impl_idl_build_enum(&item),
+            Item::Union(item) => impl_idl_build_union(&item),
             // Derive macros can only be defined on structs, enums, and unions.
             _ => unreachable!(),
         };

+ 10 - 0
lang/src/bpf_upgradeable_state.rs

@@ -72,3 +72,13 @@ impl AccountDeserialize for UpgradeableLoaderState {
         bincode::deserialize(buf).map_err(|_| ProgramError::InvalidAccountData.into())
     }
 }
+
+#[cfg(feature = "idl-build")]
+mod idl_build {
+    use super::*;
+
+    impl crate::IdlBuild for ProgramData {}
+    impl crate::Discriminator for ProgramData {
+        const DISCRIMINATOR: [u8; 8] = [u8::MAX; 8];
+    }
+}

+ 4 - 3
lang/syn/Cargo.toml

@@ -17,12 +17,10 @@ allow-missing-optionals = []
 anchor-debug = []
 event-cpi = []
 hash = []
-idl-build = ["idl-parse", "idl-types"]
-idl-parse = ["idl-types"]
+idl-build = ["idl-types", "cargo_toml"]
 idl-types = []
 init-if-needed = []
 interface-instructions = []
-seeds = []
 
 [dependencies]
 anyhow = "1"
@@ -35,3 +33,6 @@ serde_json = "1"
 sha2 = "0.10"
 syn = { version = "1", features = ["full", "extra-traits", "parsing"] }
 thiserror = "1"
+
+# `idl-build` feature only
+cargo_toml = { version = "0.15", optional = true }

+ 1 - 1
lang/syn/src/codegen/accounts/constraints.rs

@@ -1098,7 +1098,7 @@ impl<'a> OptionalCheckScope<'a> {
         check_scope
     }
     pub fn generate_check(&mut self, field: impl ToTokens) -> TokenStream {
-        let field_name = tts_to_string(&field);
+        let field_name = parser::tts_to_string(&field);
         if self.seen.contains(&field_name) {
             quote! {}
         } else {

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

@@ -37,10 +37,7 @@ pub fn generate(accs: &AccountsStruct) -> proc_macro2::TokenStream {
 
     #[cfg(feature = "idl-build")]
     {
-        #![allow(warnings)]
-        let no_docs = crate::idl::build::get_no_docs();
-        let idl_build_impl =
-            crate::idl::build::gen_idl_build_impl_for_accounts_struct(&accs, no_docs);
+        let idl_build_impl = crate::idl::build::gen_idl_build_impl_accounts_struct(accs);
         return quote! {
             #ret
             #idl_build_impl

+ 2 - 5
lang/syn/src/codegen/error.rs

@@ -1,9 +1,6 @@
 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;
@@ -103,10 +100,10 @@ pub fn generate(error: Error) -> proc_macro2::TokenStream {
 
     #[cfg(feature = "idl-build")]
     {
-        let idl_build = gen_idl_print_function_for_error(&error);
+        let idl_print = crate::idl::build::gen_idl_print_fn_error(&error);
         return quote! {
             #ret
-            #idl_build
+            #idl_print
         };
     };
 

+ 2 - 4
lang/syn/src/codegen/program/mod.rs

@@ -39,12 +39,10 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
 
     #[cfg(feature = "idl-build")]
     {
-        let no_docs = crate::idl::build::get_no_docs();
-        let idl_build = crate::idl::build::gen_idl_print_function_for_program(program, no_docs);
-
+        let idl_build_impl = crate::idl::build::gen_idl_print_fn_program(program);
         return quote! {
             #ret
-            #idl_build
+            #idl_build_impl
         };
     };
 

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

@@ -1,948 +0,0 @@
-pub use serde_json;
-
-use crate::{parser::docs, AccountField, AccountsStruct, Error, Program};
-use heck::MixedCase;
-use proc_macro2::TokenStream;
-use quote::{format_ident, quote};
-use syn::{Ident, ItemEnum, ItemStruct};
-
-/// A trait that types must implement in order to generate the IDL via compilation.
-///
-/// This trait is automatically implemented for Anchor all types that use the `AnchorSerialize`
-/// proc macro. Note that manually implementing the `AnchorSerialize` trait will **NOT** have the
-/// same effect.
-///
-/// Types that don't implement this trait will cause a compile error during the IDL generation.
-///
-/// The methods have default implementation that allows the program to compile but the type will
-/// **NOT** be included in the IDL.
-pub trait IdlBuild {
-    /// Returns the full module path of the type.
-    fn __anchor_private_full_path() -> String {
-        String::default()
-    }
-
-    /// Returns the IDL type definition of the type or `None` if it doesn't exist.
-    fn __anchor_private_gen_idl_type() -> Option<super::types::IdlTypeDefinition> {
-        None
-    }
-
-    /// Insert the type definition to the defined types hashmap.
-    fn __anchor_private_insert_idl_defined(
-        _defined_types: &mut std::collections::HashMap<String, super::types::IdlTypeDefinition>,
-    ) {
-    }
-}
-
-#[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_BUILD_NO_DOCS")
-        .map(|val| val == "TRUE")
-        .unwrap_or(false)
-}
-
-#[inline(always)]
-pub fn get_seeds_feature() -> bool {
-    std::option_env!("ANCHOR_IDL_BUILD_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 {
-                            name: <#path>::__anchor_private_full_path(),
-                            args: #params
-                        } },
-                        defined,
-                    ))
-                } else {
-                    Ok((
-                        quote! { #idl::IdlType::Defined(<#path>::__anchor_private_full_path()) },
-                        vec![path.clone()],
-                    ))
-                }
-            }
-        }
-        syn::Type::Reference(reference) => match reference.elem.as_ref() {
-            syn::Type::Slice(slice) if matches!(&*slice.elem, syn::Type::Path(path) if the_only_segment_is(path, "u8")) =>
-            {
-                return Ok((quote! {#idl::IdlType::Bytes}, vec![]));
-            }
-            _ => panic!("Reference types other than byte slice(`&[u8]`) are not allowed"),
-        },
-        _ => 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 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: 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 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: Self::__anchor_private_full_path(),
-                generics: #generics,
-                docs: #docs,
-                ty: #idl::IdlTypeDefinitionTy::Enum{
-                    variants: vec![
-                        #(#variants),*
-                    ]
-                }
-            }
-        },
-        defined,
-    ))
-}
-
-pub fn idl_build_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();
-    let idl_build_trait = quote! {anchor_lang::anchor_syn::idl::build::IdlBuild};
-
-    quote! {
-        impl #impl_generics #idl_build_trait for #ident #ty_generics #where_clause {
-            fn __anchor_private_full_path() -> String {
-                format!("{}::{}", std::module_path!(), #name)
-            }
-
-            fn __anchor_private_gen_idl_type() -> Option<#idl::IdlTypeDefinition> {
-                #idl_type_definition_ts
-            }
-
-            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_build_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_build_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_build_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_build_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_build_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_build_impl_for_accounts_struct(
-    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_build_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 ----");
-        }
-    }
-}

+ 392 - 0
lang/syn/src/idl/build/accounts.rs

@@ -0,0 +1,392 @@
+use anyhow::{anyhow, Result};
+use proc_macro2::TokenStream;
+use quote::{quote, ToTokens};
+
+use super::common::{get_idl_module_path, get_no_docs};
+use crate::{AccountField, AccountsStruct, Field, Ty};
+
+/// Generate the IDL build impl for the Accounts struct.
+pub fn gen_idl_build_impl_accounts_struct(accounts: &AccountsStruct) -> TokenStream {
+    let resolution = option_env!("ANCHOR_IDL_BUILD_RESOLUTION")
+        .map(|val| val == "TRUE")
+        .unwrap_or_default();
+    let no_docs = get_no_docs();
+    let idl = get_idl_module_path();
+
+    let ident = &accounts.ident;
+    let (impl_generics, ty_generics, where_clause) = accounts.generics.split_for_impl();
+
+    let (accounts, defined) = accounts
+        .fields
+        .iter()
+        .map(|acc| match acc {
+            AccountField::Field(acc) => {
+                let name = acc.ident.to_string();
+                let writable = acc.constraints.is_mutable();
+                let signer = match acc.ty {
+                    Ty::Signer => true,
+                    _ => acc.constraints.is_signer(),
+                };
+                let optional = acc.is_optional;
+                let docs = match &acc.docs {
+                    Some(docs) if !no_docs => quote! { vec![#(#docs.into()),*] },
+                    _ => quote! { vec![] },
+                };
+
+                let (address, pda, relations) = if resolution {
+                    (
+                        get_address(acc),
+                        get_pda(acc, accounts),
+                        get_relations(acc, accounts),
+                    )
+                } else {
+                    (quote! { None }, quote! { None }, quote! { vec![] })
+                };
+
+                let acc_type_path = match &acc.ty {
+                    Ty::Account(ty)
+                    // Skip `UpgradeableLoaderState` type for now until `bincode` serialization
+                    // is supported.
+                    //
+                    // TODO: Remove this once either `bincode` serialization is supported or
+                    // we wrap the type in order to implement `IdlBuild` in `anchor-lang`.
+                        if !ty
+                            .account_type_path
+                            .path
+                            .to_token_stream()
+                            .to_string()
+                            .contains("UpgradeableLoaderState") =>
+                    {
+                        Some(&ty.account_type_path)
+                    }
+                    Ty::AccountLoader(ty) => Some(&ty.account_type_path),
+                    Ty::InterfaceAccount(ty) => Some(&ty.account_type_path),
+                    _ => None,
+                };
+
+                (
+                    quote! {
+                        #idl::IdlInstructionAccountItem::Single(#idl::IdlInstructionAccount {
+                            name: #name.into(),
+                            docs: #docs,
+                            writable: #writable,
+                            signer: #signer,
+                            optional: #optional,
+                            address: #address,
+                            pda: #pda,
+                            relations: #relations,
+                        })
+                    },
+                    acc_type_path,
+                )
+            }
+            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!(
+                        "Compose field type must be a path but received: {:?}",
+                        comp_f.raw_field.ty
+                    )
+                };
+                let name = comp_f.ident.to_string();
+
+                (
+                    quote! {
+                        #idl::IdlInstructionAccountItem::Composite(#idl::IdlInstructionAccounts {
+                            name: #name.into(),
+                            accounts: <#ty>::__anchor_private_gen_idl_accounts(accounts, types),
+                        })
+                    },
+                    None,
+                )
+            }
+        })
+        .unzip::<_, _, Vec<_>, Vec<_>>();
+    let defined = defined.into_iter().flatten().collect::<Vec<_>>();
+
+    quote! {
+        impl #impl_generics #ident #ty_generics #where_clause {
+            pub fn __anchor_private_gen_idl_accounts(
+                accounts: &mut std::collections::BTreeMap<String, #idl::IdlAccount>,
+                types: &mut std::collections::BTreeMap<String, #idl::IdlTypeDef>,
+            ) -> Vec<#idl::IdlInstructionAccountItem> {
+                #(
+                    if let Some(ty) = <#defined>::create_type() {
+                        let account = #idl::IdlAccount {
+                            name: ty.name.clone(),
+                            discriminator: <#defined as anchor_lang::Discriminator>::DISCRIMINATOR.into(),
+                        };
+                        accounts.insert(account.name.clone(), account);
+                        types.insert(ty.name.clone(), ty);
+                        <#defined>::insert_types(types);
+                    }
+                );*
+
+                vec![#(#accounts),*]
+            }
+        }
+    }
+}
+
+fn get_address(acc: &Field) -> TokenStream {
+    match &acc.ty {
+        Ty::Program(ty) => ty
+            .account_type_path
+            .path
+            .segments
+            .last()
+            .map(|seg| &seg.ident)
+            .map(|ident| quote! { Some(#ident::id().to_string()) })
+            .unwrap_or_else(|| quote! { None }),
+        Ty::Sysvar(_) => {
+            let ty = acc.account_ty();
+            let sysvar_id_trait = quote!(anchor_lang::solana_program::sysvar::SysvarId);
+            quote! { Some(<#ty as #sysvar_id_trait>::id().to_string()) }
+        }
+        _ => acc
+            .constraints
+            .address
+            .as_ref()
+            .map(|constraint| &constraint.address)
+            .map(|address| quote! { Some(#address.to_string()) })
+            .unwrap_or_else(|| quote! { None }),
+    }
+}
+
+fn get_pda(acc: &Field, accounts: &AccountsStruct) -> TokenStream {
+    let idl = get_idl_module_path();
+    let seed_constraints = acc.constraints.seeds.as_ref();
+    let seeds = seed_constraints
+        .map(|seed| seed.seeds.iter().map(|seed| parse_seed(seed, accounts)))
+        .and_then(|seeds| seeds.collect::<Result<Vec<_>>>().ok());
+    let program = seed_constraints
+        .and_then(|seed| seed.program_seed.as_ref())
+        .and_then(|program| parse_seed(program, accounts).ok())
+        .map(|program| quote! { Some(#program) })
+        .unwrap_or_else(|| quote! { None });
+    match seeds {
+        Some(seeds) => quote! {
+            Some(
+                #idl::IdlPda {
+                    seeds: vec![#(#seeds),*],
+                    program: #program,
+                }
+            )
+        },
+        _ => quote! { None },
+    }
+}
+
+/// Parse a seeds constraint, extracting the `IdlSeed` types.
+///
+/// Note: This implementation makes assumptions about the types that can be used (e.g., no
+/// program-defined function calls in seeds).
+///
+/// This probably doesn't cover all cases. If you see a warning log, you can add a new case here.
+/// In the worst case, we miss a seed and the parser will treat the given seeds as empty and so
+/// clients will simply fail to automatically populate the PDA accounts.
+///
+/// # Seed assumptions
+///
+/// Seeds must be of one of the following forms:
+///
+/// - Constant
+/// - Instruction argument
+/// - Account key or field
+fn parse_seed(seed: &syn::Expr, accounts: &AccountsStruct) -> Result<TokenStream> {
+    let idl = get_idl_module_path();
+    let args = accounts.instruction_args().unwrap_or_default();
+    match seed {
+        syn::Expr::MethodCall(_) => {
+            let seed_path = SeedPath::new(seed)?;
+
+            if args.contains_key(&seed_path.name) {
+                let path = seed_path.path();
+
+                Ok(quote! {
+                    #idl::IdlSeed::Arg(
+                        #idl::IdlSeedArg {
+                            path: #path.into(),
+                        }
+                    )
+                })
+            } else if let Some(account_field) = accounts
+                .fields
+                .iter()
+                .find(|field| *field.ident() == seed_path.name)
+            {
+                let path = seed_path.path();
+                let account = match account_field.ty_name() {
+                    Some(name) if !seed_path.subfields.is_empty() => {
+                        quote! { Some(#name.into()) }
+                    }
+                    _ => quote! { None },
+                };
+
+                Ok(quote! {
+                    #idl::IdlSeed::Account(
+                        #idl::IdlSeedAccount {
+                            path: #path.into(),
+                            account: #account,
+                        }
+                    )
+                })
+            } else if seed_path.name.contains('"') {
+                let seed = seed_path.name.trim_start_matches("b\"").trim_matches('"');
+                Ok(quote! {
+                    #idl::IdlSeed::Const(
+                        #idl::IdlSeedConst {
+                            value: #seed.into(),
+                        }
+                    )
+                })
+            } else {
+                Ok(quote! {
+                    #idl::IdlSeed::Const(
+                        #idl::IdlSeedConst {
+                            value: #seed.into(),
+                        }
+                    )
+                })
+            }
+        }
+        syn::Expr::Path(path) => {
+            let seed = path
+                .path
+                .get_ident()
+                .map(|ident| ident.to_string())
+                .filter(|ident| args.contains_key(ident))
+                .map(|path| {
+                    quote! {
+                        #idl::IdlSeed::Arg(
+                            #idl::IdlSeedArg {
+                                path: #path.into(),
+                            }
+                        )
+                    }
+                })
+                .unwrap_or_else(|| {
+                    // Not all types can be converted to `Vec<u8>` with `.into` call e.g. `Pubkey`.
+                    // This is problematic for `seeds::program` but a hacky way to handle this
+                    // scenerio is to check whether the last segment of the path ends with `ID`.
+                    let seed = path
+                        .path
+                        .segments
+                        .last()
+                        .filter(|seg| seg.ident.to_string().ends_with("ID"))
+                        .map(|_| quote! { #seed.as_ref() })
+                        .unwrap_or_else(|| quote! { #seed });
+                    quote! {
+                        #idl::IdlSeed::Const(
+                            #idl::IdlSeedConst {
+                                value: #seed.into(),
+                            }
+                        )
+                    }
+                });
+            Ok(seed)
+        }
+        syn::Expr::Lit(_) => Ok(quote! {
+            #idl::IdlSeed::Const(
+                #idl::IdlSeedConst {
+                    value: #seed.into(),
+                }
+            )
+        }),
+        syn::Expr::Reference(rf) => parse_seed(&rf.expr, accounts),
+        _ => Err(anyhow!("Unexpected seed: {seed:?}")),
+    }
+}
+
+/// SeedPath represents the deconstructed syntax of a single pda seed,
+/// consisting of a variable name and a vec of all the sub fields accessed
+/// on that variable name. For example, if a seed is `my_field.my_data.as_ref()`,
+/// then the field name is `my_field` and the vec of sub fields is `[my_data]`.
+struct SeedPath {
+    /// Seed name
+    name: String,
+    /// All path components for the subfields accessed on this seed
+    subfields: Vec<String>,
+}
+
+impl SeedPath {
+    /// Extract the seed path from a single seed expression.
+    fn new(seed: &syn::Expr) -> Result<Self> {
+        // Convert the seed into the raw string representation.
+        let seed_str = seed.to_token_stream().to_string();
+
+        // Break up the seed into each subfield component.
+        let mut components = seed_str.split('.').collect::<Vec<_>>();
+        if components.len() <= 1 {
+            return Err(anyhow!("Seed is in unexpected format: {seed:#?}"));
+        }
+
+        // The name of the variable (or field).
+        let name = components.remove(0).to_owned();
+
+        // The path to the seed (only if the `name` type is a struct).
+        let mut path = Vec::new();
+        while !components.is_empty() {
+            let subfield = components.remove(0);
+            if subfield.contains("()") {
+                break;
+            }
+            path.push(subfield.into());
+        }
+        if path.len() == 1 && (path[0] == "key" || path[0] == "key()") {
+            path = Vec::new();
+        }
+
+        Ok(SeedPath {
+            name,
+            subfields: path,
+        })
+    }
+
+    /// Get the full path to the data this seed represents.
+    fn path(&self) -> String {
+        match self.subfields.len() {
+            0 => self.name.to_owned(),
+            _ => format!("{}.{}", self.name, self.subfields.join(".")),
+        }
+    }
+}
+
+fn get_relations(acc: &Field, accounts: &AccountsStruct) -> TokenStream {
+    let relations = accounts
+        .fields
+        .iter()
+        .filter_map(|af| match af {
+            AccountField::Field(f) => f
+                .constraints
+                .has_one
+                .iter()
+                .filter_map(|c| match &c.join_target {
+                    syn::Expr::Path(path) => path
+                        .path
+                        .segments
+                        .first()
+                        .filter(|seg| seg.ident == acc.ident)
+                        .map(|_| Some(f.ident.to_string())),
+                    _ => None,
+                })
+                .collect::<Option<Vec<_>>>(),
+            _ => None,
+        })
+        .flatten()
+        .collect::<Vec<_>>();
+    quote! { vec![#(#relations.into()),*] }
+}

+ 15 - 0
lang/syn/src/idl/build/address.rs

@@ -0,0 +1,15 @@
+use proc_macro2::TokenStream;
+use quote::quote;
+
+use super::common::gen_print_section;
+
+pub fn gen_idl_print_fn_address(address: String) -> TokenStream {
+    let fn_body = gen_print_section("address", quote! { #address });
+
+    quote! {
+        #[test]
+        pub fn __anchor_private_print_idl_address() {
+            #fn_body
+        }
+    }
+}

+ 43 - 0
lang/syn/src/idl/build/common.rs

@@ -0,0 +1,43 @@
+use std::{
+    fs,
+    path::{Path, PathBuf},
+};
+
+use anyhow::Result;
+use proc_macro2::TokenStream;
+use quote::{quote, ToTokens};
+
+pub fn find_path(name: &str, path: impl AsRef<Path>) -> Result<PathBuf> {
+    let parent_path = path.as_ref().parent().unwrap();
+    for entry in fs::read_dir(parent_path)? {
+        let entry = entry?;
+        if entry.file_name().to_string_lossy() == name {
+            return entry.path().canonicalize().map_err(Into::into);
+        }
+    }
+
+    find_path(name, parent_path)
+}
+
+pub fn get_no_docs() -> bool {
+    option_env!("ANCHOR_IDL_BUILD_NO_DOCS")
+        .map(|val| val == "TRUE")
+        .unwrap_or_default()
+}
+
+pub fn get_idl_module_path() -> TokenStream {
+    quote!(anchor_lang::anchor_syn::idl::types)
+}
+
+pub fn get_serde_json_module_path() -> TokenStream {
+    quote!(anchor_lang::anchor_syn::idl::build::serde_json)
+}
+
+pub fn gen_print_section(name: &str, value: impl ToTokens) -> TokenStream {
+    let serde_json = get_serde_json_module_path();
+    quote! {
+        println!("--- IDL begin {} ---", #name);
+        println!("{}", #serde_json::to_string_pretty(&{ #value }).unwrap());
+        println!("--- IDL end {} ---", #name);
+    }
+}

+ 37 - 0
lang/syn/src/idl/build/constant.rs

@@ -0,0 +1,37 @@
+use heck::SnakeCase;
+use proc_macro2::TokenStream;
+use quote::{format_ident, quote};
+
+use super::{
+    common::{gen_print_section, get_idl_module_path},
+    defined::gen_idl_type,
+};
+
+pub fn gen_idl_print_fn_constant(item: &syn::ItemConst) -> TokenStream {
+    let idl = get_idl_module_path();
+
+    let name = item.ident.to_string();
+    let expr = &item.expr;
+    let fn_name = format_ident!("__anchor_private_print_idl_const_{}", name.to_snake_case());
+
+    let fn_body = match gen_idl_type(&item.ty, &[]) {
+        Ok((ty, _)) => gen_print_section(
+            "const",
+            quote! {
+                #idl::IdlConst {
+                    name: #name.into(),
+                    ty: #ty,
+                    value: format!("{:?}", #expr),
+                }
+            },
+        ),
+        _ => quote! {},
+    };
+
+    quote! {
+        #[test]
+        pub fn #fn_name() {
+            #fn_body
+        }
+    }
+}

+ 654 - 0
lang/syn/src/idl/build/defined.rs

@@ -0,0 +1,654 @@
+use std::collections::BTreeMap;
+
+use anyhow::{anyhow, Result};
+use proc_macro2::TokenStream;
+use quote::quote;
+
+use super::common::{get_idl_module_path, get_no_docs};
+use crate::{idl::types::IdlTypeDef, parser::docs};
+
+/// A trait that types must implement in order to include the type in the IDL definition.
+///
+/// This trait is automatically implemented for Anchor all types that use the `AnchorSerialize`
+/// proc macro. Note that manually implementing the `AnchorSerialize` trait does **NOT** have the
+/// same effect.
+///
+/// Types that don't implement this trait will cause a compile error during the IDL generation.
+///
+/// The default implementation of the trait allows the program to compile but the type does **NOT**
+/// get included in the IDL.
+pub trait IdlBuild {
+    /// Create an IDL type definition for the type.
+    ///
+    /// The type is only included in the IDL if this method returns `Some`.
+    fn create_type() -> Option<IdlTypeDef> {
+        None
+    }
+
+    /// Insert all types that are included in the current type definition to the given map.
+    fn insert_types(_types: &mut BTreeMap<String, IdlTypeDef>) {}
+
+    /// Get the full module path of the type.
+    ///
+    /// The full path will be used in the case of a conflicting type definition, e.g. when there
+    /// are multiple structs with the same name.
+    ///
+    /// The default implementation covers most cases.
+    fn get_full_path() -> String {
+        std::any::type_name::<Self>().into()
+    }
+}
+
+/// Generate [`IdlBuild`] impl for a struct.
+pub fn impl_idl_build_struct(item: &syn::ItemStruct) -> TokenStream {
+    impl_idl_build(&item.ident, &item.generics, gen_idl_type_def_struct(item))
+}
+
+/// Generate [`IdlBuild`] impl for an enum.
+pub fn impl_idl_build_enum(item: &syn::ItemEnum) -> TokenStream {
+    impl_idl_build(&item.ident, &item.generics, gen_idl_type_def_enum(item))
+}
+
+/// Generate [`IdlBuild`] impl for a union.
+///
+/// Unions are not currently supported in the IDL.
+pub fn impl_idl_build_union(item: &syn::ItemUnion) -> TokenStream {
+    impl_idl_build(
+        &item.ident,
+        &item.generics,
+        Err(anyhow!("Unions are not supported")),
+    )
+}
+
+/// Generate [`IdlBuild`] implementation.
+fn impl_idl_build(
+    ident: &syn::Ident,
+    generics: &syn::Generics,
+    type_def: Result<(TokenStream, Vec<syn::TypePath>)>,
+) -> TokenStream {
+    let idl = get_idl_module_path();
+    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
+    let idl_build_trait = quote!(anchor_lang::anchor_syn::idl::build::IdlBuild);
+
+    let (idl_type_def, insert_defined) = match type_def {
+        Ok((ts, defined)) => (
+            quote! { Some(#ts) },
+            quote! {
+                #(
+                    if let Some(ty) = <#defined>::create_type() {
+                        types.insert(<#defined>::get_full_path(), ty);
+                        <#defined>::insert_types(types);
+                    }
+                );*
+            },
+        ),
+        _ => (quote! { None }, quote! {}),
+    };
+
+    quote! {
+        impl #impl_generics #idl_build_trait for #ident #ty_generics #where_clause {
+            fn create_type() -> Option<#idl::IdlTypeDef> {
+                #idl_type_def
+            }
+
+            fn insert_types(
+                types: &mut std::collections::BTreeMap<String, #idl::IdlTypeDef>
+            ) {
+                #insert_defined
+            }
+
+            fn get_full_path() -> String {
+                format!("{}::{}", module_path!(), stringify!(#ident))
+            }
+        }
+    }
+}
+
+pub fn gen_idl_type_def_struct(
+    strct: &syn::ItemStruct,
+) -> Result<(TokenStream, Vec<syn::TypePath>)> {
+    gen_idl_type_def(&strct.attrs, &strct.generics, |generic_params| {
+        let no_docs = get_no_docs();
+        let idl = get_idl_module_path();
+
+        let (fields, defined) = match &strct.fields {
+            syn::Fields::Unit => (quote! { None }, vec![]),
+            syn::Fields::Named(fields) => {
+                let (fields, defined) = fields
+                    .named
+                    .iter()
+                    .map(|f| gen_idl_field(f, generic_params, no_docs))
+                    .collect::<Result<Vec<_>>>()?
+                    .into_iter()
+                    .unzip::<_, _, Vec<_>, Vec<_>>();
+
+                (
+                    quote! { Some(#idl::IdlDefinedFields::Named(vec![#(#fields),*])) },
+                    defined,
+                )
+            }
+            syn::Fields::Unnamed(fields) => {
+                let (types, defined) = fields
+                    .unnamed
+                    .iter()
+                    .map(|f| gen_idl_type(&f.ty, generic_params))
+                    .collect::<Result<Vec<_>>>()?
+                    .into_iter()
+                    .unzip::<_, Vec<_>, Vec<_>, Vec<_>>();
+
+                (
+                    quote! { Some(#idl::IdlDefinedFields::Tuple(vec![#(#types),*])) },
+                    defined,
+                )
+            }
+        };
+        let defined = defined.into_iter().flatten().collect::<Vec<_>>();
+
+        Ok((
+            quote! {
+                #idl::IdlTypeDefTy::Struct {
+                    fields: #fields,
+                }
+            },
+            defined,
+        ))
+    })
+}
+
+fn gen_idl_type_def_enum(enm: &syn::ItemEnum) -> Result<(TokenStream, Vec<syn::TypePath>)> {
+    gen_idl_type_def(&enm.attrs, &enm.generics, |generic_params| {
+        let no_docs = get_no_docs();
+        let idl = get_idl_module_path();
+
+        let (variants, defined) = enm
+            .variants
+            .iter()
+            .map(|variant| {
+                let name = variant.ident.to_string();
+                let (fields, defined) = match &variant.fields {
+                    syn::Fields::Unit => (quote! { None }, vec![]),
+                    syn::Fields::Named(fields) => {
+                        let (fields, defined) = fields
+                            .named
+                            .iter()
+                            .map(|f| gen_idl_field(f, generic_params, no_docs))
+                            .collect::<Result<Vec<_>>>()?
+                            .into_iter()
+                            .unzip::<_, Vec<_>, Vec<_>, Vec<_>>();
+                        let defined = defined.into_iter().flatten().collect::<Vec<_>>();
+
+                        (
+                            quote! { Some(#idl::IdlDefinedFields::Named(vec![#(#fields),*])) },
+                            defined,
+                        )
+                    }
+                    syn::Fields::Unnamed(fields) => {
+                        let (types, defined) = fields
+                            .unnamed
+                            .iter()
+                            .map(|f| gen_idl_type(&f.ty, generic_params))
+                            .collect::<Result<Vec<_>>>()?
+                            .into_iter()
+                            .unzip::<_, Vec<_>, Vec<_>, Vec<_>>();
+                        let defined = defined.into_iter().flatten().collect::<Vec<_>>();
+
+                        (
+                            quote! { Some(#idl::IdlDefinedFields::Tuple(vec![#(#types),*])) },
+                            defined,
+                        )
+                    }
+                };
+
+                Ok((
+                    quote! { #idl::IdlEnumVariant { name: #name.into(), fields: #fields } },
+                    defined,
+                ))
+            })
+            .collect::<Result<Vec<_>>>()?
+            .into_iter()
+            .unzip::<_, _, Vec<_>, Vec<_>>();
+        let defined = defined.into_iter().flatten().collect::<Vec<_>>();
+
+        Ok((
+            quote! {
+                #idl::IdlTypeDefTy::Enum {
+                    variants: vec![#(#variants),*],
+                }
+            },
+            defined,
+        ))
+    })
+}
+
+fn gen_idl_type_def<F>(
+    attrs: &[syn::Attribute],
+    generics: &syn::Generics,
+    create_fields: F,
+) -> Result<(TokenStream, Vec<syn::TypePath>)>
+where
+    F: Fn(&[syn::Ident]) -> Result<(TokenStream, Vec<syn::TypePath>)>,
+{
+    let no_docs = get_no_docs();
+    let idl = get_idl_module_path();
+
+    let docs = match docs::parse(attrs) {
+        Some(docs) if !no_docs => quote! { vec![#(#docs.into()),*] },
+        _ => quote! { vec![] },
+    };
+
+    let serialization = get_attr_str("derive", attrs)
+        .and_then(|derive| {
+            if derive.contains("bytemuck") {
+                if derive.to_lowercase().contains("unsafe") {
+                    Some(quote! { #idl::IdlSerialization::BytemuckUnsafe })
+                } else {
+                    Some(quote! { #idl::IdlSerialization::Bytemuck })
+                }
+            } else {
+                None
+            }
+        })
+        .unwrap_or_else(|| quote! { #idl::IdlSerialization::default() });
+
+    let repr = get_attr_str("repr", attrs)
+        .map(|repr| {
+            let packed = repr.contains("packed");
+            let align = repr
+                .find("align")
+                .and_then(|i| repr.get(i..))
+                .and_then(|align| {
+                    align
+                        .find('(')
+                        .and_then(|start| align.find(')').and_then(|end| align.get(start + 1..end)))
+                })
+                .and_then(|size| size.parse::<usize>().ok())
+                .map(|size| quote! { Some(#size) })
+                .unwrap_or_else(|| quote! { None });
+            let modifier = quote! {
+                #idl::IdlReprModifier {
+                    packed: #packed,
+                    align: #align,
+                }
+            };
+
+            if repr.contains("transparent") {
+                quote! { #idl::IdlRepr::Transparent }
+            } else if repr.contains('C') {
+                quote! { #idl::IdlRepr::C(#modifier) }
+            } else {
+                quote! { #idl::IdlRepr::Rust(#modifier) }
+            }
+        })
+        .map(|repr| quote! { Some(#repr) })
+        .unwrap_or_else(|| quote! { None });
+
+    let generic_params = 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 (ty, defined) = create_fields(&generic_params)?;
+
+    let generics = generics
+        .params
+        .iter()
+        .filter_map(|p| match p {
+            syn::GenericParam::Type(ty) => {
+                let name = ty.ident.to_string();
+                Some(quote! {
+                    #idl::IdlTypeDefGeneric::Type {
+                        name: #name.into(),
+                    }
+                })
+            }
+            syn::GenericParam::Const(c) => {
+                let name = c.ident.to_string();
+                let ty = match &c.ty {
+                    syn::Type::Path(path) => get_first_segment(path).ident.to_string(),
+                    _ => unreachable!("Const generic type can only be path"),
+                };
+                Some(quote! {
+                    #idl::IdlTypeDefGeneric::Const {
+                        name: #name.into(),
+                        ty: #ty.into(),
+                    }
+                })
+            }
+            _ => None,
+        })
+        .collect::<Vec<_>>();
+
+    Ok((
+        quote! {
+            #idl::IdlTypeDef {
+                name: Self::get_full_path(),
+                docs: #docs,
+                serialization: #serialization,
+                repr: #repr,
+                generics: vec![#(#generics.into()),*],
+                ty: #ty,
+            }
+        },
+        defined,
+    ))
+}
+
+fn get_attr_str(name: impl AsRef<str>, attrs: &[syn::Attribute]) -> Option<String> {
+    attrs
+        .iter()
+        .filter(|attr| {
+            attr.path
+                .segments
+                .first()
+                .filter(|seg| seg.ident == name)
+                .is_some()
+        })
+        .map(|attr| attr.tokens.to_string())
+        .reduce(|acc, cur| {
+            format!(
+                "{} , {}",
+                acc.get(..acc.len() - 1).unwrap(),
+                cur.get(1..).unwrap()
+            )
+        })
+}
+
+fn gen_idl_field(
+    field: &syn::Field,
+    generic_params: &[syn::Ident],
+    no_docs: bool,
+) -> Result<(TokenStream, Vec<syn::TypePath>)> {
+    let idl = get_idl_module_path();
+
+    let name = field.ident.as_ref().unwrap().to_string();
+    let docs = match docs::parse(&field.attrs) {
+        Some(docs) if !no_docs => quote! { vec![#(#docs.into()),*] },
+        _ => quote! { vec![] },
+    };
+    let (ty, defined) = gen_idl_type(&field.ty, generic_params)?;
+
+    Ok((
+        quote! {
+            #idl::IdlField {
+                name: #name.into(),
+                docs: #docs,
+                ty: #ty,
+            }
+        },
+        defined,
+    ))
+}
+
+pub fn gen_idl_type(
+    ty: &syn::Type,
+    generic_params: &[syn::Ident],
+) -> Result<(TokenStream, Vec<syn::TypePath>)> {
+    let idl = get_idl_module_path();
+
+    fn the_only_segment_is(path: &syn::TypePath, cmp: &str) -> bool {
+        if path.path.segments.len() != 1 {
+            return false;
+        };
+        return get_first_segment(path).ident == cmp;
+    }
+
+    fn get_angle_bracketed_type_args(seg: &syn::PathSegment) -> Vec<&syn::Type> {
+        match &seg.arguments {
+            syn::PathArguments::AngleBracketed(ab) => ab
+                .args
+                .iter()
+                .filter_map(|arg| match arg {
+                    syn::GenericArgument::Type(ty) => Some(ty),
+                    _ => None,
+                })
+                .collect(),
+            _ => panic!("No angle bracket for {seg:#?}"),
+        }
+    }
+
+    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::Pubkey }, vec![]))
+        }
+        syn::Type::Path(path) if the_only_segment_is(path, "Option") => {
+            let segment = get_first_segment(path);
+            let arg = get_angle_bracketed_type_args(segment)
+                .into_iter()
+                .next()
+                .unwrap();
+            let (inner, defined) = gen_idl_type(arg, generic_params)?;
+            Ok((quote! { #idl::IdlType::Option(Box::new(#inner)) }, defined))
+        }
+        syn::Type::Path(path) if the_only_segment_is(path, "Vec") => {
+            let segment = get_first_segment(path);
+            let arg = get_angle_bracketed_type_args(segment)
+                .into_iter()
+                .next()
+                .unwrap();
+            match arg {
+                syn::Type::Path(path) if the_only_segment_is(path, "u8") => {
+                    return Ok((quote! {#idl::IdlType::Bytes}, vec![]));
+                }
+                _ => (),
+            };
+            let (inner, defined) = gen_idl_type(arg, generic_params)?;
+            Ok((quote! { #idl::IdlType::Vec(Box::new(#inner)) }, defined))
+        }
+        syn::Type::Path(path) if the_only_segment_is(path, "Box") => {
+            let segment = get_first_segment(path);
+            let arg = get_angle_bracketed_type_args(segment)
+                .into_iter()
+                .next()
+                .unwrap();
+            gen_idl_type(arg, generic_params)
+        }
+        syn::Type::Array(arr) => {
+            let len = &arr.len;
+            let is_generic = generic_params.iter().any(|param| match len {
+                syn::Expr::Path(path) => path.path.is_ident(param),
+                _ => false,
+            });
+
+            let len = if is_generic {
+                match len {
+                    syn::Expr::Path(len) => {
+                        let len = len.path.get_ident().unwrap().to_string();
+                        quote! { #idl::IdlArrayLen::Generic(#len.into()) }
+                    }
+                    _ => unreachable!("Array length can only be a generic parameter"),
+                }
+            } else {
+                quote! { #idl::IdlArrayLen::Value(#len) }
+            };
+
+            let (inner, defined) = gen_idl_type(&arr.elem, generic_params)?;
+            Ok((
+                quote! { #idl::IdlType::Array(Box::new(#inner), #len) },
+                defined,
+            ))
+        }
+        // Defined
+        syn::Type::Path(path) => {
+            let is_generic_param = generic_params.iter().any(|param| path.path.is_ident(param));
+            if is_generic_param {
+                let generic = get_first_segment(path).ident.to_string();
+                return Ok((quote! { #idl::IdlType::Generic(#generic.into()) }, vec![]));
+            }
+
+            // Handle type aliases and external types
+            #[cfg(procmacro2_semver_exempt)]
+            {
+                use super::{common::find_path, external::get_external_type};
+                use crate::parser::context::CrateContext;
+                use quote::ToTokens;
+
+                let source_path = proc_macro2::Span::call_site().source_file().path();
+                let lib_path = find_path("lib.rs", &source_path).expect("lib.rs should exist");
+
+                if let Ok(ctx) = CrateContext::parse(lib_path) {
+                    let name = path.path.segments.last().unwrap().ident.to_string();
+                    let alias = ctx.type_aliases().find(|ty| ty.ident == name);
+                    if let Some(alias) = alias {
+                        if let Some(segment) = path.path.segments.last() {
+                            if let syn::PathArguments::AngleBracketed(args) = &segment.arguments {
+                                let inners = args
+                                    .args
+                                    .iter()
+                                    .map(|arg| match arg {
+                                        syn::GenericArgument::Type(ty) => match ty {
+                                            syn::Type::Path(inner_ty) => {
+                                                inner_ty.path.to_token_stream().to_string()
+                                            }
+                                            _ => {
+                                                unimplemented!("Inner type not implemented: {ty:?}")
+                                            }
+                                        },
+                                        syn::GenericArgument::Const(c) => {
+                                            c.to_token_stream().to_string()
+                                        }
+                                        _ => unimplemented!("Arg not implemented: {arg:?}"),
+                                    })
+                                    .collect::<Vec<_>>();
+
+                                let outer = match &*alias.ty {
+                                    syn::Type::Path(outer_ty) => outer_ty.path.to_token_stream(),
+                                    syn::Type::Array(outer_ty) => outer_ty.to_token_stream(),
+                                    _ => unimplemented!("Type not implemented: {:?}", alias.ty),
+                                }
+                                .to_string();
+
+                                let resolved_alias = alias
+                                    .generics
+                                    .params
+                                    .iter()
+                                    .map(|param| match param {
+                                        syn::GenericParam::Const(param) => param.ident.to_string(),
+                                        syn::GenericParam::Type(param) => param.ident.to_string(),
+                                        _ => panic!("Lifetime parameters are not allowed"),
+                                    })
+                                    .enumerate()
+                                    .fold(outer, |acc, (i, cur)| {
+                                        let inner = &inners[i];
+                                        acc.replace(&format!(" {cur}"), &format!(" {inner} "))
+                                            .replace(&format!(" {cur},"), &format!(" {inner},"))
+                                            .replace(&format!("[{cur} "), &format!("[{inner} ",))
+                                            .replace(&format!(" {cur}]"), &format!(" {inner}]"))
+                                    });
+                                if let Ok(ty) = syn::parse_str(&resolved_alias) {
+                                    return gen_idl_type(&ty, generic_params);
+                                }
+                            }
+                        };
+
+                        // Non-generic type alias e.g. `type UnixTimestamp = i64`
+                        return gen_idl_type(&*alias.ty, generic_params);
+                    }
+
+                    // Handle external types
+                    let is_external = ctx
+                        .structs()
+                        .map(|s| s.ident.to_string())
+                        .chain(ctx.enums().map(|e| e.ident.to_string()))
+                        .find(|defined| defined == &name)
+                        .is_none();
+                    if is_external {
+                        if let Ok(Some(ty)) = get_external_type(&name, source_path) {
+                            return gen_idl_type(&ty, generic_params);
+                        }
+                    }
+                }
+            }
+
+            // Defined in crate
+            let mut generics = vec![];
+            let mut defined = vec![path.clone()];
+
+            if let Some(segment) = path.path.segments.last() {
+                if let syn::PathArguments::AngleBracketed(args) = &segment.arguments {
+                    for arg in &args.args {
+                        match arg {
+                            syn::GenericArgument::Type(ty) => {
+                                let (ty, def) = gen_idl_type(ty, generic_params)?;
+                                generics.push(quote! { #idl::IdlGenericArg::Type { ty: #ty } });
+                                defined.extend(def);
+                            }
+                            syn::GenericArgument::Const(c) => generics.push(
+                                quote! { #idl::IdlGenericArg::Const { value: #c.to_string() } },
+                            ),
+                            _ => (),
+                        }
+                    }
+                }
+            }
+
+            Ok((
+                quote! {
+                    #idl::IdlType::Defined {
+                        name: <#path>::get_full_path(),
+                        generics: vec![#(#generics),*],
+                    }
+                },
+                defined,
+            ))
+        }
+        syn::Type::Reference(reference) => match reference.elem.as_ref() {
+            syn::Type::Slice(slice) if matches!(&*slice.elem, syn::Type::Path(path) if the_only_segment_is(path, "u8")) => {
+                Ok((quote! {#idl::IdlType::Bytes}, vec![]))
+            }
+            _ => gen_idl_type(&reference.elem, generic_params),
+        },
+        _ => Err(anyhow!("Unknown type: {ty:#?}")),
+    }
+}
+
+fn get_first_segment(type_path: &syn::TypePath) -> &syn::PathSegment {
+    type_path.path.segments.first().unwrap()
+}

+ 44 - 0
lang/syn/src/idl/build/error.rs

@@ -0,0 +1,44 @@
+use heck::SnakeCase;
+use proc_macro2::TokenStream;
+use quote::{format_ident, quote};
+
+use super::common::{gen_print_section, get_idl_module_path};
+use crate::Error;
+
+pub fn gen_idl_print_fn_error(error: &Error) -> TokenStream {
+    let idl = get_idl_module_path();
+
+    let fn_name = format_ident!(
+        "__anchor_private_print_idl_error_{}",
+        error.ident.to_string().to_snake_case()
+    );
+
+    let error_codes = error
+        .codes
+        .iter()
+        .map(|code| {
+            let id = code.id;
+            let name = code.ident.to_string();
+            let msg = match &code.msg {
+                Some(msg) => quote! { Some(#msg.into()) },
+                None => quote! { None },
+            };
+
+            quote! {
+                #idl::IdlErrorCode {
+                    code: anchor_lang::error::ERROR_CODE_OFFSET + #id,
+                    name: #name.into(),
+                    msg: #msg,
+                }
+            }
+        })
+        .collect::<Vec<_>>();
+    let fn_body = gen_print_section("errors", quote! { vec![#(#error_codes),*] });
+
+    quote! {
+        #[test]
+        pub fn #fn_name() {
+            #fn_body
+        }
+    }
+}

+ 81 - 0
lang/syn/src/idl/build/event.rs

@@ -0,0 +1,81 @@
+use heck::SnakeCase;
+use proc_macro2::TokenStream;
+use quote::{format_ident, quote};
+
+use super::{
+    common::{gen_print_section, get_idl_module_path, get_serde_json_module_path},
+    defined::gen_idl_type_def_struct,
+};
+
+pub fn gen_idl_print_fn_event(event_struct: &syn::ItemStruct) -> TokenStream {
+    let idl = get_idl_module_path();
+    let serde_json = get_serde_json_module_path();
+
+    let ident = &event_struct.ident;
+    let fn_name = format_ident!(
+        "__anchor_private_print_idl_event_{}",
+        ident.to_string().to_snake_case()
+    );
+    let idl_build_impl = impl_idl_build_event(event_struct);
+
+    let print_ts = gen_print_section(
+        "event",
+        quote! {
+            #serde_json::json!({
+                "event": event,
+                "types": types.into_values().collect::<Vec<_>>()
+            })
+        },
+    );
+
+    quote! {
+        #idl_build_impl
+
+        #[test]
+        pub fn #fn_name() {
+            let mut types: std::collections::BTreeMap<String, #idl::IdlTypeDef> =
+                std::collections::BTreeMap::new();
+            if let Some(event) = #ident::__anchor_private_gen_idl_event(&mut types) {
+                #print_ts
+            }
+        }
+    }
+}
+
+/// Generate IDL build impl for an event.
+fn impl_idl_build_event(event_struct: &syn::ItemStruct) -> TokenStream {
+    let idl = get_idl_module_path();
+
+    let ident = &event_struct.ident;
+    let (impl_generics, ty_generics, where_clause) = event_struct.generics.split_for_impl();
+
+    let fn_body = match gen_idl_type_def_struct(event_struct) {
+        Ok((ts, defined)) => quote! {
+            #(
+                if let Some(ty) = <#defined>::create_type() {
+                    types.insert(<#defined>::get_full_path(), ty);
+                    <#defined>::insert_types(types);
+                }
+            );*
+
+            let ty = #ts;
+            let event = #idl::IdlEvent {
+                name: ty.name.clone(),
+                discriminator: <Self as anchor_lang::Discriminator>::DISCRIMINATOR.into(),
+            };
+            types.insert(ty.name.clone(), ty);
+            Some(event)
+        },
+        _ => quote! { None },
+    };
+
+    quote! {
+        impl #impl_generics #ident #ty_generics #where_clause {
+            pub fn __anchor_private_gen_idl_event(
+                types: &mut std::collections::BTreeMap<String, #idl::IdlTypeDef>,
+            ) -> Option<#idl::IdlEvent> {
+                #fn_body
+            }
+        }
+    }
+}

+ 143 - 0
lang/syn/src/idl/build/external.rs

@@ -0,0 +1,143 @@
+use std::{
+    env, fs,
+    path::{Path, PathBuf},
+};
+
+use anyhow::{anyhow, Result};
+use cargo_toml::Manifest;
+use quote::ToTokens;
+
+use super::common::find_path;
+use crate::parser::context::CrateContext;
+
+pub fn get_external_type(name: &str, path: impl AsRef<Path>) -> Result<Option<syn::Type>> {
+    let use_path = get_uses(path.as_ref())?
+        .into_iter()
+        .find(|u| u.split("::").last().unwrap() == name)
+        .ok_or_else(|| anyhow!("`{name}` not found in use statements"))?;
+
+    // Get crate name and version from lock file
+    let lib_path = find_path("lib.rs", path)?;
+    let lock_path = find_path("Cargo.lock", lib_path)?;
+    let lock_file = parse_lock_file(lock_path)?;
+    let registry_path = get_registry_path()?;
+
+    recursively_find_type(name, &use_path, &registry_path, &lock_file)
+}
+
+fn recursively_find_type(
+    defined_name: &str,
+    use_path: &str,
+    registry_path: &Path,
+    lock_file: &[(String, String)],
+) -> Result<Option<syn::Type>> {
+    let crate_name = use_path.split("::").next().unwrap();
+    let (crate_name, version) = lock_file
+        .iter()
+        .find(|(name, _)| name == crate_name || name == &crate_name.replace('_', "-"))
+        .ok_or_else(|| anyhow!("Crate should exist in the lock file"))?;
+
+    let crate_path = registry_path.join(format!("{crate_name}-{version}"));
+    let lib_path = crate_path.join("src").join("lib.rs");
+    let ctx = CrateContext::parse(&lib_path)?;
+
+    // TODO: Struct and enum
+
+    let alias = ctx.type_aliases().find(|item| item.ident == defined_name);
+    match alias {
+        Some(alias) => Ok(Some(*alias.ty.to_owned())),
+        None => {
+            // Check re-exported deps e.g. `anchor_lang::solana_program::...`
+            let cargo_toml_path = find_path("Cargo.toml", &lib_path)?;
+            let deps = Manifest::from_path(cargo_toml_path)?.dependencies;
+            let paths = use_path.split("::").skip(1).collect::<Vec<_>>();
+            let paths = paths.iter().enumerate().filter_map(|(i, path)| {
+                if deps.contains_key(*path) || deps.contains_key(&path.replace('_', "-")) {
+                    Some(paths.iter().skip(i).copied().collect::<Vec<_>>().join("::"))
+                } else {
+                    None
+                }
+            });
+            for path in paths {
+                let result = recursively_find_type(defined_name, &path, registry_path, lock_file);
+                if result.is_ok() {
+                    return result;
+                }
+            }
+
+            Ok(None)
+        }
+    }
+}
+
+fn get_registry_path() -> Result<PathBuf> {
+    #[allow(deprecated)]
+    let path = env::home_dir()
+        .unwrap()
+        .join(".cargo")
+        .join("registry")
+        .join("src");
+    fs::read_dir(&path)?
+        .filter_map(|entry| entry.ok())
+        .find_map(|entry| {
+            let file_name = entry.file_name();
+            if file_name.to_string_lossy().starts_with("index.crates.io") {
+                Some(file_name)
+            } else {
+                None
+            }
+        })
+        .map(|name| path.join(name))
+        .ok_or_else(|| anyhow!("crates.io registry not found"))
+}
+
+fn parse_lock_file(path: impl AsRef<Path>) -> Result<Vec<(String, String)>> {
+    let parsed = fs::read_to_string(path.as_ref())?
+        .split("[[package]]")
+        .skip(1)
+        .map(|pkg| {
+            let get_value = |key: &str| -> String {
+                pkg.lines()
+                    .find(|line| line.starts_with(key))
+                    .expect(&format!("`{key}` line not found"))
+                    .split('"')
+                    .nth(1)
+                    .unwrap()
+                    .to_owned()
+            };
+            let name = get_value("name");
+            let version = get_value("version");
+            (name, version)
+        })
+        .collect::<Vec<_>>();
+    Ok(parsed)
+}
+
+fn get_uses(path: impl AsRef<Path>) -> Result<Vec<String>> {
+    let content = fs::read_to_string(path.as_ref())?;
+    let uses = syn::parse_file(&content)?
+        .items
+        .into_iter()
+        .filter_map(|item| match item {
+            syn::Item::Use(u) => Some(flatten_uses(&u.tree)),
+            _ => None,
+        })
+        .flatten()
+        .collect::<Vec<_>>();
+    Ok(uses)
+}
+
+fn flatten_uses(tree: &syn::UseTree) -> Vec<String> {
+    match tree {
+        syn::UseTree::Group(group) => group.items.iter().flat_map(flatten_uses).collect(),
+        syn::UseTree::Path(path) => flatten_uses(&path.tree)
+            .into_iter()
+            .map(|item| format!("{}::{}", path.ident, item))
+            .collect(),
+        syn::UseTree::Glob(glob) => {
+            vec![format!("{}", glob.star_token.to_token_stream().to_string())]
+        }
+        syn::UseTree::Name(name) => vec![name.ident.to_string()],
+        syn::UseTree::Rename(rename) => vec![rename.ident.to_string()],
+    }
+}

+ 21 - 0
lang/syn/src/idl/build/mod.rs

@@ -0,0 +1,21 @@
+#![allow(dead_code)]
+
+mod accounts;
+mod address;
+mod common;
+mod constant;
+mod defined;
+mod error;
+mod event;
+mod external;
+mod program;
+
+pub use accounts::gen_idl_build_impl_accounts_struct;
+pub use address::gen_idl_print_fn_address;
+pub use constant::gen_idl_print_fn_constant;
+pub use defined::{impl_idl_build_enum, impl_idl_build_struct, impl_idl_build_union, IdlBuild};
+pub use error::gen_idl_print_fn_error;
+pub use event::gen_idl_print_fn_event;
+pub use program::gen_idl_print_fn_program;
+
+pub use serde_json;

+ 140 - 0
lang/syn/src/idl/build/program.rs

@@ -0,0 +1,140 @@
+use anyhow::Result;
+use heck::CamelCase;
+use proc_macro2::TokenStream;
+use quote::{format_ident, quote};
+
+use super::{
+    common::{gen_print_section, get_idl_module_path, get_no_docs},
+    defined::gen_idl_type,
+};
+use crate::{parser::docs, Program};
+
+/// Generate the IDL build print function for the program module.
+pub fn gen_idl_print_fn_program(program: &Program) -> TokenStream {
+    let idl = get_idl_module_path();
+    let no_docs = get_no_docs();
+
+    let name = program.name.to_string();
+    let docs = match &program.docs {
+        Some(docs) if !no_docs => quote! { vec![#(#docs.into()),*] },
+        _ => quote! { vec![] },
+    };
+
+    let (instructions, defined) = program
+        .ixs
+        .iter()
+        .flat_map(|ix| -> Result<_> {
+            let name = ix.ident.to_string();
+            let name_pascal = format_ident!("{}", name.to_camel_case());
+            let ctx_ident = &ix.anchor_ident;
+            let discriminator = quote! {
+                <crate::instruction::#name_pascal as anchor_lang::Discriminator>::DISCRIMINATOR
+            };
+
+            let docs = match &ix.docs {
+                Some(docs) if !no_docs => quote! { vec![#(#docs.into()),*] },
+                _ => quote! { vec![] },
+            };
+
+            let (args, mut defined) = ix
+                .args
+                .iter()
+                .map(|arg| {
+                    let name = arg.name.to_string();
+                    let docs = match docs::parse(&arg.raw_arg.attrs) {
+                        Some(docs) if !no_docs => quote! { vec![#(#docs.into()),*] },
+                        _ => quote! { vec![] },
+                    };
+                    let (ty, defined) = gen_idl_type(&arg.raw_arg.ty, &[])?;
+
+                    Ok((
+                        quote! {
+                            #idl::IdlField {
+                                name: #name.into(),
+                                docs: #docs,
+                                ty: #ty,
+                            }
+                        },
+                        defined,
+                    ))
+                })
+                .collect::<Result<Vec<_>>>()?
+                .into_iter()
+                .unzip::<_, Vec<_>, Vec<_>, Vec<_>>();
+
+            let returns = match gen_idl_type(&ix.returns.ty, &[]) {
+                Ok((ty, def)) => {
+                    defined.push(def);
+                    quote! { Some(#ty) }
+                }
+                _ => quote! { None },
+            };
+
+            Ok((
+                quote! {
+                    #idl::IdlInstruction {
+                        name: #name.into(),
+                        docs: #docs,
+                        discriminator: #discriminator.into(),
+                        accounts: #ctx_ident::__anchor_private_gen_idl_accounts(
+                            &mut accounts,
+                            &mut types,
+                        ),
+                        args: vec![#(#args),*],
+                        returns: #returns,
+                    }
+                },
+                defined,
+            ))
+        })
+        .unzip::<_, Vec<_>, Vec<_>, Vec<_>>();
+    let defined = defined.into_iter().flatten().flatten().collect::<Vec<_>>();
+
+    let fn_body = gen_print_section(
+        "program",
+        quote! {
+            let mut accounts: std::collections::BTreeMap<String, #idl::IdlAccount> =
+                std::collections::BTreeMap::new();
+            let mut types: std::collections::BTreeMap<String, #idl::IdlTypeDef> =
+                std::collections::BTreeMap::new();
+
+            #(
+                if let Some(ty) = <#defined>::create_type() {
+                    types.insert(<#defined>::get_full_path(), ty);
+                    <#defined>::insert_types(&mut types);
+                }
+            );*
+
+            #idl::Idl {
+                address: Default::default(),
+                metadata: #idl::IdlMetadata {
+                    name: #name.into(),
+                    version: env!("CARGO_PKG_VERSION").into(),
+                    spec: #idl::IDL_SPEC.into(),
+                    description: option_env!("CARGO_PKG_DESCRIPTION")
+                        .filter(|d| !d.is_empty())
+                        .map(|d| d.into()),
+                    repository: option_env!("CARGO_PKG_REPOSITORY")
+                        .filter(|r| !r.is_empty())
+                        .map(|r| r.into()),
+                    dependencies: Default::default(),
+                    contact: Default::default(),
+                },
+                docs: #docs,
+                instructions: vec![#(#instructions),*],
+                accounts: accounts.into_values().collect(),
+                events: Default::default(),
+                errors: Default::default(),
+                types: types.into_values().collect(),
+                constants: Default::default(),
+            }
+        },
+    );
+
+    quote! {
+        #[test]
+        pub fn __anchor_private_print_idl_program() {
+            #fn_body
+        }
+    }
+}

+ 0 - 3
lang/syn/src/idl/mod.rs

@@ -2,6 +2,3 @@ pub mod types;
 
 #[cfg(feature = "idl-build")]
 pub mod build;
-
-#[cfg(feature = "idl-parse")]
-pub mod parse;

+ 0 - 590
lang/syn/src/idl/parse/file.rs

@@ -1,590 +0,0 @@
-use crate::idl::types::*;
-use crate::parser::context::CrateContext;
-use crate::parser::{self, accounts, docs, error, program};
-use crate::Ty;
-use crate::{AccountField, AccountsStruct};
-use anyhow::anyhow;
-use anyhow::Result;
-use heck::MixedCase;
-use quote::ToTokens;
-use std::collections::{HashMap, HashSet};
-use std::path::Path;
-use std::str::FromStr;
-use syn::{
-    Expr, ExprLit, ItemConst,
-    Lit::{Byte, ByteStr},
-};
-
-use super::relations;
-
-const DERIVE_NAME: &str = "Accounts";
-// TODO: share this with `anchor_lang` crate.
-const ERROR_CODE_OFFSET: u32 = 6000;
-
-/// Parse an entire interface file.
-pub fn parse(
-    path: impl AsRef<Path>,
-    version: String,
-    seeds_feature: bool,
-    no_docs: bool,
-    safety_checks: bool,
-) -> Result<Idl> {
-    let ctx = CrateContext::parse(path)?;
-    if safety_checks {
-        ctx.safety_checks()?;
-    }
-
-    let program_mod = parse_program_mod(&ctx)?;
-    let mut program = program::parse(program_mod)?;
-
-    if no_docs {
-        program.docs = None;
-        for ix in &mut program.ixs {
-            ix.docs = None;
-        }
-    }
-
-    let accs = parse_account_derives(&ctx);
-
-    let error = parse_error_enum(&ctx).map(|mut e| error::parse(&mut e, None));
-    let error_codes = error.as_ref().map(|e| {
-        e.codes
-            .iter()
-            .map(|code| IdlErrorCode {
-                code: ERROR_CODE_OFFSET + code.id,
-                name: code.ident.to_string(),
-                msg: code.msg.clone(),
-            })
-            .collect::<Vec<IdlErrorCode>>()
-    });
-
-    let instructions = program
-        .ixs
-        .iter()
-        .map(|ix| {
-            let args = ix
-                .args
-                .iter()
-                .map(|arg| {
-                    let doc = if !no_docs {
-                        docs::parse(&arg.raw_arg.attrs)
-                    } else {
-                        None
-                    };
-                    IdlField {
-                        name: arg.name.to_string().to_mixed_case(),
-                        docs: doc,
-                        ty: to_idl_type(&ctx, &arg.raw_arg.ty),
-                    }
-                })
-                .collect::<Vec<_>>();
-            // todo: don't unwrap
-            let accounts_strct = accs.get(&ix.anchor_ident.to_string()).unwrap();
-            let accounts = idl_accounts(&ctx, accounts_strct, &accs, seeds_feature, no_docs);
-            let ret_type_str = ix.returns.ty.to_token_stream().to_string();
-            let returns = match ret_type_str.as_str() {
-                "()" => None,
-                _ => Some(ret_type_str.parse().unwrap()),
-            };
-            IdlInstruction {
-                name: ix.ident.to_string().to_mixed_case(),
-                docs: ix.docs.clone(),
-                accounts,
-                args,
-                returns,
-            }
-        })
-        .collect::<Vec<_>>();
-
-    let events = parse_events(&ctx)
-        .iter()
-        .map(|e: &&syn::ItemStruct| {
-            let fields = match &e.fields {
-                syn::Fields::Named(n) => n,
-                _ => panic!("Event fields must be named"),
-            };
-            let fields = fields
-                .named
-                .iter()
-                .map(|f: &syn::Field| {
-                    let index = match f.attrs.first() {
-                        None => false,
-                        Some(i) => parser::tts_to_string(&i.path) == "index",
-                    };
-                    IdlEventField {
-                        name: f.ident.clone().unwrap().to_string().to_mixed_case(),
-                        ty: to_idl_type(&ctx, &f.ty),
-                        index,
-                    }
-                })
-                .collect::<Vec<IdlEventField>>();
-
-            IdlEvent {
-                name: e.ident.to_string(),
-                fields,
-            }
-        })
-        .collect::<Vec<IdlEvent>>();
-
-    // All user defined types.
-    let mut accounts = vec![];
-    let mut types = vec![];
-    let ty_defs = parse_ty_defs(&ctx, no_docs)?;
-
-    let account_structs = parse_accounts(&ctx);
-    let account_names: HashSet<String> = account_structs
-        .iter()
-        .map(|a| a.ident.to_string())
-        .collect::<HashSet<_>>();
-
-    let error_name = error.map(|e| e.name).unwrap_or_default();
-
-    // All types that aren't in the accounts section, are in the types section.
-    for ty_def in ty_defs {
-        // Don't add the error type to the types or accounts sections.
-        if ty_def.name != error_name {
-            if account_names.contains(&ty_def.name) {
-                accounts.push(ty_def);
-            } else if !events.iter().any(|e| e.name == ty_def.name) {
-                types.push(ty_def);
-            }
-        }
-    }
-
-    let constants = parse_consts(&ctx)
-        .iter()
-        .map(|c: &&syn::ItemConst| to_idl_const(c))
-        .collect::<Vec<IdlConst>>();
-
-    Ok(Idl {
-        version,
-        name: program.name.to_string(),
-        docs: program.docs.clone(),
-        instructions,
-        types,
-        accounts,
-        events: if events.is_empty() {
-            None
-        } else {
-            Some(events)
-        },
-        errors: error_codes,
-        metadata: None,
-        constants,
-    })
-}
-
-/// Parse the main program mod.
-fn parse_program_mod(ctx: &CrateContext) -> Result<syn::ItemMod> {
-    let root = ctx.root_module();
-    let mods = root
-        .items()
-        .filter_map(|i| match i {
-            syn::Item::Mod(item_mod) => {
-                let mod_count = item_mod
-                    .attrs
-                    .iter()
-                    .filter(|attr| attr.path.segments.last().unwrap().ident == "program")
-                    .count();
-                if mod_count != 1 {
-                    return None;
-                }
-                Some(item_mod)
-            }
-            _ => None,
-        })
-        .collect::<Vec<_>>();
-
-    match mods.len() {
-        0 => Err(anyhow!("Program module not found")),
-        1 => Ok(mods[0].clone()),
-        _ => Err(anyhow!("Multiple program modules are not allowed")),
-    }
-}
-
-fn parse_error_enum(ctx: &CrateContext) -> Option<syn::ItemEnum> {
-    ctx.enums()
-        .find(|item_enum| {
-            let attrs_count = item_enum
-                .attrs
-                .iter()
-                .filter(|attr| {
-                    let segment = attr.path.segments.last().unwrap();
-                    segment.ident == "error_code"
-                })
-                .count();
-            match attrs_count {
-                0 => false,
-                1 => true,
-                _ => panic!("Invalid syntax: one error attribute allowed"),
-            }
-        })
-        .cloned()
-}
-
-fn parse_events(ctx: &CrateContext) -> Vec<&syn::ItemStruct> {
-    ctx.structs()
-        .filter(|item_strct| {
-            let attrs_count = item_strct
-                .attrs
-                .iter()
-                .filter(|attr| {
-                    let segment = attr.path.segments.last().unwrap();
-                    segment.ident == "event"
-                })
-                .count();
-            match attrs_count {
-                0 => false,
-                1 => true,
-                _ => panic!("Invalid syntax: one event attribute allowed"),
-            }
-        })
-        .collect()
-}
-
-fn parse_accounts(ctx: &CrateContext) -> Vec<&syn::ItemStruct> {
-    ctx.structs()
-        .filter(|item_strct| {
-            let attrs_count = item_strct
-                .attrs
-                .iter()
-                .filter(|attr| {
-                    let segment = attr.path.segments.last().unwrap();
-                    segment.ident == "account" || segment.ident == "associated"
-                })
-                .count();
-            match attrs_count {
-                0 => false,
-                1 => true,
-                _ => panic!("Invalid syntax: one account attribute allowed"),
-            }
-        })
-        .collect()
-}
-
-// Parse all structs implementing the `Accounts` trait.
-fn parse_account_derives(ctx: &CrateContext) -> HashMap<String, AccountsStruct> {
-    // TODO: parse manual implementations. Currently we only look
-    //       for derives.
-    ctx.structs()
-        .filter_map(|i_strct| {
-            for attr in &i_strct.attrs {
-                if attr.path.is_ident("derive") && attr.tokens.to_string().contains(DERIVE_NAME) {
-                    let strct = accounts::parse(i_strct).expect("Code not parseable");
-                    return Some((strct.ident.to_string(), strct));
-                }
-            }
-            None
-        })
-        .collect()
-}
-
-fn parse_consts(ctx: &CrateContext) -> Vec<&syn::ItemConst> {
-    ctx.consts()
-        .filter(|item_strct| {
-            for attr in &item_strct.attrs {
-                if attr.path.segments.last().unwrap().ident == "constant" {
-                    return true;
-                }
-            }
-            false
-        })
-        .collect()
-}
-
-// Parse all user defined types in the file.
-fn parse_ty_defs(ctx: &CrateContext, no_docs: bool) -> Result<Vec<IdlTypeDefinition>> {
-    ctx.structs()
-        .filter_map(|item_strct| {
-            // Only take public types
-            if !matches!(&item_strct.vis, syn::Visibility::Public(_)) {
-                return None;
-            }
-
-            // Only take serializable types
-            let serializable = item_strct.attrs.iter().any(|attr| {
-                let attr_string = attr.tokens.to_string();
-                let attr_name = attr.path.segments.last().unwrap().ident.to_string();
-                let attr_serializable = ["account", "associated", "event", "zero_copy"];
-
-                let derived_serializable = attr_name == "derive"
-                    && attr_string.contains("AnchorSerialize")
-                    && attr_string.contains("AnchorDeserialize");
-
-                attr_serializable.iter().any(|a| *a == attr_name) || derived_serializable
-            });
-
-            if !serializable {
-                return None;
-            }
-
-            let name = item_strct.ident.to_string();
-            let doc = if !no_docs {
-                docs::parse(&item_strct.attrs)
-            } else {
-                None
-            };
-            let fields = match &item_strct.fields {
-                syn::Fields::Named(fields) => fields
-                    .named
-                    .iter()
-                    .map(|f: &syn::Field| {
-                        let doc = if !no_docs {
-                            docs::parse(&f.attrs)
-                        } else {
-                            None
-                        };
-                        Ok(IdlField {
-                            name: f.ident.as_ref().unwrap().to_string().to_mixed_case(),
-                            docs: doc,
-                            ty: to_idl_type(ctx, &f.ty),
-                        })
-                    })
-                    .collect::<Result<Vec<IdlField>>>(),
-                syn::Fields::Unnamed(_) => return None,
-                _ => panic!("Empty structs are not allowed."),
-            };
-
-            Some(fields.map(|fields| IdlTypeDefinition {
-                name,
-                generics: None,
-                docs: doc,
-                ty: IdlTypeDefinitionTy::Struct { fields },
-            }))
-        })
-        .chain(ctx.enums().filter_map(|enm| {
-            // Only take public types
-            if !matches!(&enm.vis, syn::Visibility::Public(_)) {
-                return None;
-            }
-
-            let name = enm.ident.to_string();
-            let doc = if !no_docs {
-                docs::parse(&enm.attrs)
-            } else {
-                None
-            };
-            let variants = enm
-                .variants
-                .iter()
-                .map(|variant: &syn::Variant| {
-                    let name = variant.ident.to_string();
-                    let fields = match &variant.fields {
-                        syn::Fields::Unit => None,
-                        syn::Fields::Unnamed(fields) => {
-                            let fields: Vec<IdlType> = fields
-                                .unnamed
-                                .iter()
-                                .map(|f| to_idl_type(ctx, &f.ty))
-                                .collect();
-                            Some(EnumFields::Tuple(fields))
-                        }
-                        syn::Fields::Named(fields) => {
-                            let fields: Vec<IdlField> = fields
-                                .named
-                                .iter()
-                                .map(|f: &syn::Field| {
-                                    let name =
-                                        f.ident.as_ref().unwrap().to_string().to_mixed_case();
-                                    let doc = if !no_docs {
-                                        docs::parse(&f.attrs)
-                                    } else {
-                                        None
-                                    };
-                                    let ty = to_idl_type(ctx, &f.ty);
-                                    IdlField {
-                                        name,
-                                        docs: doc,
-                                        ty,
-                                    }
-                                })
-                                .collect();
-                            Some(EnumFields::Named(fields))
-                        }
-                    };
-                    IdlEnumVariant { name, fields }
-                })
-                .collect::<Vec<IdlEnumVariant>>();
-
-            Some(Ok(IdlTypeDefinition {
-                name,
-                generics: None,
-                docs: doc,
-                ty: IdlTypeDefinitionTy::Enum { variants },
-            }))
-        }))
-        .chain(ctx.type_aliases().filter_map(|alias| {
-            // Only take public types
-            if !matches!(&alias.vis, syn::Visibility::Public(_)) {
-                return None;
-            }
-            // Generic type aliases are not currently supported
-            if alias.generics.lt_token.is_some() {
-                return None;
-            }
-
-            let name = alias.ident.to_string();
-            let doc = if !no_docs {
-                docs::parse(&alias.attrs)
-            } else {
-                None
-            };
-            let result = IdlType::from_str(&alias.ty.to_token_stream().to_string()).map(|value| {
-                IdlTypeDefinition {
-                    name,
-                    generics: None,
-                    docs: doc,
-                    ty: IdlTypeDefinitionTy::Alias { value },
-                }
-            });
-
-            match &result {
-                Ok(_) => Some(result),
-                Err(_) => None,
-            }
-        }))
-        .collect()
-}
-
-// Replace variable array lengths with values
-fn resolve_variable_array_lengths(ctx: &CrateContext, mut tts_string: String) -> String {
-    for constant in ctx.consts().filter(|c| match *c.ty {
-        // Filter to only those consts that are of type usize or could be cast to usize
-        syn::Type::Path(ref p) => {
-            let segment = p.path.segments.last().unwrap();
-            matches!(
-                segment.ident.to_string().as_str(),
-                "usize"
-                    | "u8"
-                    | "u16"
-                    | "u32"
-                    | "u64"
-                    | "u128"
-                    | "isize"
-                    | "i8"
-                    | "i16"
-                    | "i32"
-                    | "i64"
-                    | "i128"
-            )
-        }
-        _ => false,
-    }) {
-        let mut check_string = tts_string.clone();
-        // Strip whitespace to handle accidental double whitespaces
-        check_string.retain(|c| !c.is_whitespace());
-        let size_string = format!("{}]", &constant.ident.to_string());
-        let cast_size_string = format!("{}asusize]", &constant.ident.to_string());
-        // Check for something to replace
-        let mut replacement_string = None;
-        if check_string.contains(cast_size_string.as_str()) {
-            replacement_string = Some(cast_size_string);
-        } else if check_string.contains(size_string.as_str()) {
-            replacement_string = Some(size_string);
-        }
-        if let Some(replacement_string) = replacement_string {
-            // Check for the existence of consts existing elsewhere in the
-            // crate which have the same name, are usize, and have a
-            // different value. We can't know which was intended for the
-            // array size from ctx.
-            if ctx.consts().any(|c| {
-                c != constant
-                    && c.ident == constant.ident
-                    && c.ty == constant.ty
-                    && c.expr != constant.expr
-            }) {
-                panic!("Crate wide unique name required for array size const.");
-            }
-            // Replace the match, don't break because there might be multiple replacements to be
-            // made in the case of multidimensional arrays
-            tts_string = check_string.replace(
-                &replacement_string,
-                format!("{}]", &constant.expr.to_token_stream()).as_str(),
-            );
-        }
-    }
-    tts_string
-}
-
-fn to_idl_type(ctx: &CrateContext, ty: &syn::Type) -> IdlType {
-    let mut tts_string = parser::tts_to_string(ty);
-    if tts_string.starts_with('[') {
-        tts_string = resolve_variable_array_lengths(ctx, tts_string);
-    }
-    // Box<FooType> -> FooType
-    tts_string = tts_string
-        .strip_prefix("Box < ")
-        .and_then(|t| t.strip_suffix(" >"))
-        .unwrap_or(&tts_string)
-        .into();
-
-    tts_string.parse().unwrap()
-}
-
-// TODO parse other issues
-fn to_idl_const(item: &ItemConst) -> IdlConst {
-    let name = item.ident.to_string();
-
-    if let Expr::Lit(ExprLit { lit, .. }) = &*item.expr {
-        match lit {
-            ByteStr(lit_byte_str) => {
-                return IdlConst {
-                    name,
-                    ty: IdlType::Bytes,
-                    value: format!("{:?}", lit_byte_str.value()),
-                }
-            }
-            Byte(lit_byte) => {
-                return IdlConst {
-                    name,
-                    ty: IdlType::U8,
-                    value: lit_byte.value().to_string(),
-                }
-            }
-            _ => (),
-        }
-    }
-
-    IdlConst {
-        name,
-        ty: item.ty.to_token_stream().to_string().parse().unwrap(),
-        value: item.expr.to_token_stream().to_string().parse().unwrap(),
-    }
-}
-
-fn idl_accounts(
-    ctx: &CrateContext,
-    accounts: &AccountsStruct,
-    global_accs: &HashMap<String, AccountsStruct>,
-    seeds_feature: bool,
-    no_docs: bool,
-) -> Vec<IdlAccountItem> {
-    accounts
-        .fields
-        .iter()
-        .map(|acc: &AccountField| match acc {
-            AccountField::CompositeField(comp_f) => {
-                let accs_strct = global_accs.get(&comp_f.symbol).unwrap_or_else(|| {
-                    panic!("Could not resolve Accounts symbol {}", comp_f.symbol)
-                });
-                let accounts = idl_accounts(ctx, accs_strct, global_accs, seeds_feature, no_docs);
-                IdlAccountItem::IdlAccounts(IdlAccounts {
-                    name: comp_f.ident.to_string().to_mixed_case(),
-                    accounts,
-                })
-            }
-            AccountField::Field(acc) => IdlAccountItem::IdlAccount(IdlAccount {
-                name: acc.ident.to_string().to_mixed_case(),
-                is_mut: acc.constraints.is_mutable(),
-                is_signer: match acc.ty {
-                    Ty::Signer => true,
-                    _ => acc.constraints.is_signer(),
-                },
-                is_optional: if acc.is_optional { Some(true) } else { None },
-                docs: if !no_docs { acc.docs.clone() } else { None },
-                pda: super::pda::parse(ctx, accounts, acc, seeds_feature),
-                relations: relations::parse(acc, seeds_feature),
-            }),
-        })
-        .collect::<Vec<_>>()
-}

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

@@ -1,120 +0,0 @@
-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))
-        )
-    }
-}

+ 0 - 389
lang/syn/src/idl/parse/pda.rs

@@ -1,389 +0,0 @@
-use crate::idl::types::*;
-use crate::parser;
-use crate::parser::context::CrateContext;
-use crate::ConstraintSeedsGroup;
-use crate::{AccountsStruct, Field};
-use std::collections::HashMap;
-use std::str::FromStr;
-use syn::{Expr, ExprLit, Lit, Path};
-
-// Parses a seeds constraint, extracting the IdlSeed types.
-//
-// Note: This implementation makes assumptions about the types that can be used
-//       (e.g., no program-defined function calls in seeds).
-//
-//       This probably doesn't cover all cases. If you see a warning log, you
-//       can add a new case here. In the worst case, we miss a seed and
-//       the parser will treat the given seeds as empty and so clients will
-//       simply fail to automatically populate the PDA accounts.
-//
-// Seed Assumptions: Seeds must be of one of the following forms:
-//
-// - instruction argument.
-// - account context field pubkey.
-// - account data, where the account is defined in the current program.
-//   We make an exception for the SPL token program, since it is so common
-//   and sometimes convenient to use fields as a seed (e.g. Auction house
-//   program). In the case of nested structs/account data, all nested structs
-//   must be defined in the current program as well.
-// - byte string literal (e.g. b"MY_SEED").
-// - byte string literal constant  (e.g. `pub const MY_SEED: [u8; 2] = *b"hi";`).
-// - array constants.
-//
-pub fn parse(
-    ctx: &CrateContext,
-    accounts: &AccountsStruct,
-    acc: &Field,
-    seeds_feature: bool,
-) -> Option<IdlPda> {
-    if !seeds_feature {
-        return None;
-    }
-    let pda_parser = PdaParser::new(ctx, accounts);
-    acc.constraints
-        .seeds
-        .as_ref()
-        .map(|s| pda_parser.parse(s))
-        .unwrap_or(None)
-}
-
-struct PdaParser<'a> {
-    ctx: &'a CrateContext,
-    // Accounts context.
-    accounts: &'a AccountsStruct,
-    // Maps var name to var type. These are the instruction arguments in a
-    // given accounts context.
-    ix_args: HashMap<String, String>,
-    // Constants available in the crate.
-    const_names: Vec<String>,
-    // Constants declared in impl blocks available in the crate
-    impl_const_names: Vec<String>,
-    // All field names of the accounts in the accounts context.
-    account_field_names: Vec<String>,
-}
-
-impl<'a> PdaParser<'a> {
-    fn new(ctx: &'a CrateContext, accounts: &'a AccountsStruct) -> Self {
-        // All the available sources of seeds.
-        let ix_args = accounts.instruction_args().unwrap_or_default();
-        let const_names: Vec<String> = ctx.consts().map(|c| c.ident.to_string()).collect();
-
-        let impl_const_names: Vec<String> = ctx
-            .impl_consts()
-            .map(|(ident, item)| format!("{} :: {}", ident, item.ident))
-            .collect();
-
-        let account_field_names = accounts.field_names();
-
-        Self {
-            ctx,
-            accounts,
-            ix_args,
-            const_names,
-            impl_const_names,
-            account_field_names,
-        }
-    }
-
-    fn parse(&self, seeds_grp: &ConstraintSeedsGroup) -> Option<IdlPda> {
-        // Extract the idl seed types from the constraints.
-        let seeds = seeds_grp
-            .seeds
-            .iter()
-            .map(|s| self.parse_seed(s))
-            .collect::<Option<Vec<_>>>()?;
-        // Parse the program id from the constraints.
-        let program_id = seeds_grp
-            .program_seed
-            .as_ref()
-            .map(|pid| self.parse_seed(pid))
-            .unwrap_or_default();
-
-        // Done.
-        Some(IdlPda { seeds, program_id })
-    }
-
-    fn parse_seed(&self, seed: &Expr) -> Option<IdlSeed> {
-        match seed {
-            Expr::MethodCall(_) => {
-                let seed_path = parse_seed_path(seed)?;
-
-                if self.is_instruction(&seed_path) {
-                    self.parse_instruction(&seed_path)
-                } else if self.is_const(&seed_path) {
-                    self.parse_const(&seed_path)
-                } else if self.is_impl_const(&seed_path) {
-                    self.parse_impl_const(&seed_path)
-                } else if self.is_account(&seed_path) {
-                    self.parse_account(&seed_path)
-                } else if self.is_str_literal(&seed_path) {
-                    self.parse_str_literal(&seed_path)
-                } else {
-                    println!("WARNING: unexpected seed category for var: {seed_path:?}");
-                    None
-                }
-            }
-            Expr::Reference(expr_reference) => self.parse_seed(&expr_reference.expr),
-            Expr::Index(_) => {
-                println!("WARNING: auto pda derivation not currently supported for slice literals");
-                None
-            }
-            Expr::Lit(ExprLit {
-                lit: Lit::ByteStr(lit_byte_str),
-                ..
-            }) => {
-                let seed_path: SeedPath = SeedPath(lit_byte_str.token().to_string(), Vec::new());
-                self.parse_str_literal(&seed_path)
-            }
-            Expr::Path(expr_path) => self.parse_const_path(&expr_path.path),
-            // Unknown type. Please file an issue.
-            _ => {
-                println!("WARNING: unexpected seed: {seed:?}");
-                None
-            }
-        }
-    }
-
-    fn parse_instruction(&self, seed_path: &SeedPath) -> Option<IdlSeed> {
-        let idl_ty = IdlType::from_str(self.ix_args.get(&seed_path.name()).unwrap()).ok()?;
-        Some(IdlSeed::Arg(IdlSeedArg {
-            ty: idl_ty,
-            path: seed_path.path(),
-        }))
-    }
-
-    fn parse_const_path(&self, path: &Path) -> Option<IdlSeed> {
-        let ident = &path.segments.first().unwrap().ident;
-
-        let const_item = self.ctx.consts().find(|c| c.ident == *ident).unwrap();
-        let idl_ty = IdlType::from_str(&parser::tts_to_string(&const_item.ty)).ok()?;
-
-        let idl_ty_value = parser::tts_to_string(&const_item.expr);
-        let idl_ty_value: String = str_lit_to_array(&idl_ty, &idl_ty_value);
-
-        let seed_path: SeedPath = SeedPath(idl_ty_value, Vec::new());
-
-        if self.is_str_literal(&seed_path) {
-            self.parse_str_literal(&seed_path)
-        } else {
-            println!("WARNING: unexpected constant path value: {seed_path:?}");
-            None
-        }
-    }
-
-    fn parse_const(&self, seed_path: &SeedPath) -> Option<IdlSeed> {
-        // Pull in the constant value directly into the IDL.
-        assert!(seed_path.components().is_empty());
-        let const_item = self
-            .ctx
-            .consts()
-            .find(|c| c.ident == seed_path.name())
-            .unwrap();
-        let idl_ty = IdlType::from_str(&parser::tts_to_string(&const_item.ty)).ok()?;
-
-        let idl_ty_value = parser::tts_to_string(&const_item.expr);
-        let idl_ty_value = str_lit_to_array(&idl_ty, &idl_ty_value);
-
-        Some(IdlSeed::Const(IdlSeedConst {
-            ty: idl_ty,
-            value: serde_json::from_str(&idl_ty_value).unwrap(),
-        }))
-    }
-
-    fn parse_impl_const(&self, seed_path: &SeedPath) -> Option<IdlSeed> {
-        // Pull in the constant value directly into the IDL.
-        assert!(seed_path.components().is_empty());
-        let static_item = self
-            .ctx
-            .impl_consts()
-            .find(|(ident, item)| format!("{} :: {}", ident, item.ident) == seed_path.name())
-            .unwrap()
-            .1;
-
-        let idl_ty = IdlType::from_str(&parser::tts_to_string(&static_item.ty)).ok()?;
-
-        let idl_ty_value = parser::tts_to_string(&static_item.expr);
-        let idl_ty_value = str_lit_to_array(&idl_ty, &idl_ty_value);
-
-        Some(IdlSeed::Const(IdlSeedConst {
-            ty: idl_ty,
-            value: serde_json::from_str(&idl_ty_value).unwrap(),
-        }))
-    }
-
-    fn parse_account(&self, seed_path: &SeedPath) -> Option<IdlSeed> {
-        // Get the anchor account field from the derive accounts struct.
-        let account_field = self
-            .accounts
-            .fields
-            .iter()
-            .find(|field| *field.ident() == seed_path.name())
-            .unwrap();
-
-        // Follow the path to find the seed type.
-        let ty = {
-            let mut path = seed_path.components();
-            match path.len() {
-                0 => IdlType::PublicKey,
-                1 => {
-                    // Name of the account struct.
-                    let account = account_field.ty_name()?;
-                    if account == "TokenAccount" {
-                        assert!(path.len() == 1);
-                        match path[0].as_str() {
-                            "mint" => IdlType::PublicKey,
-                            "amount" => IdlType::U64,
-                            "authority" => IdlType::PublicKey,
-                            "delegated_amount" => IdlType::U64,
-                            _ => {
-                                println!("WARNING: token field isn't supported: {}", &path[0]);
-                                return None;
-                            }
-                        }
-                    } else {
-                        // Get the rust representation of the field's struct.
-                        let strct = self.ctx.structs().find(|s| s.ident == account).unwrap();
-                        parse_field_path(self.ctx, strct, &mut path)
-                    }
-                }
-                _ => panic!("invariant violation"),
-            }
-        };
-
-        Some(IdlSeed::Account(IdlSeedAccount {
-            ty,
-            account: account_field.ty_name(),
-            path: seed_path.path(),
-        }))
-    }
-
-    fn parse_str_literal(&self, seed_path: &SeedPath) -> Option<IdlSeed> {
-        let mut var_name = seed_path.name();
-        // Remove the byte `b` prefix if the string is of the form `b"seed".
-        if var_name.starts_with("b\"") {
-            var_name.remove(0);
-        }
-        let value_string: String = var_name.chars().filter(|c| *c != '"').collect();
-        Some(IdlSeed::Const(IdlSeedConst {
-            value: serde_json::Value::String(value_string),
-            ty: IdlType::String,
-        }))
-    }
-
-    fn is_instruction(&self, seed_path: &SeedPath) -> bool {
-        self.ix_args.contains_key(&seed_path.name())
-    }
-
-    fn is_const(&self, seed_path: &SeedPath) -> bool {
-        self.const_names.contains(&seed_path.name())
-    }
-
-    fn is_impl_const(&self, seed_path: &SeedPath) -> bool {
-        self.impl_const_names.contains(&seed_path.name())
-    }
-
-    fn is_account(&self, seed_path: &SeedPath) -> bool {
-        self.account_field_names.contains(&seed_path.name())
-    }
-
-    fn is_str_literal(&self, seed_path: &SeedPath) -> bool {
-        seed_path.components().is_empty() && seed_path.name().contains('"')
-    }
-}
-
-// SeedPath represents the deconstructed syntax of a single pda seed,
-// consisting of a variable name and a vec of all the sub fields accessed
-// on that variable name. For example, if a seed is `my_field.my_data.as_ref()`,
-// then the field name is `my_field` and the vec of sub fields is `[my_data]`.
-#[derive(Debug)]
-struct SeedPath(String, Vec<String>);
-
-impl SeedPath {
-    fn name(&self) -> String {
-        self.0.clone()
-    }
-
-    // Full path to the data this seed represents.
-    fn path(&self) -> String {
-        match self.1.len() {
-            0 => self.0.clone(),
-            _ => format!("{}.{}", self.name(), self.components().join(".")),
-        }
-    }
-
-    // All path components for the subfields accessed on this seed.
-    fn components(&self) -> &[String] {
-        &self.1
-    }
-}
-
-// Extracts the seed path from a single seed expression.
-fn parse_seed_path(seed: &Expr) -> Option<SeedPath> {
-    // Convert the seed into the raw string representation.
-    let seed_str = parser::tts_to_string(seed);
-
-    // Break up the seed into each sub field component.
-    let mut components: Vec<&str> = seed_str.split(" . ").collect();
-    if components.len() <= 1 {
-        println!("WARNING: seeds are in an unexpected format: {seed:?}");
-        return None;
-    }
-
-    // The name of the variable (or field).
-    let name = components.remove(0).to_string();
-
-    // The path to the seed (only if the `name` type is a struct).
-    let mut path = Vec::new();
-    while !components.is_empty() {
-        let c = components.remove(0);
-        if c.contains("()") {
-            break;
-        }
-        path.push(c.to_string());
-    }
-    if path.len() == 1 && (path[0] == "key" || path[0] == "key()") {
-        path = Vec::new();
-    }
-
-    Some(SeedPath(name, path))
-}
-
-fn parse_field_path(ctx: &CrateContext, strct: &syn::ItemStruct, path: &mut &[String]) -> IdlType {
-    let field_name = &path[0];
-    *path = &path[1..];
-
-    // Get the type name for the field.
-    let next_field = strct
-        .fields
-        .iter()
-        .find(|f| &f.ident.clone().unwrap().to_string() == field_name)
-        .unwrap();
-    let next_field_ty_str = parser::tts_to_string(&next_field.ty);
-
-    // The path is empty so this must be a primitive type.
-    if path.is_empty() {
-        return next_field_ty_str.parse().unwrap();
-    }
-
-    // Get the rust representation of hte field's struct.
-    let strct = ctx
-        .structs()
-        .find(|s| s.ident == next_field_ty_str)
-        .unwrap();
-
-    parse_field_path(ctx, strct, path)
-}
-
-fn str_lit_to_array(idl_ty: &IdlType, idl_ty_value: &String) -> String {
-    if let IdlType::Array(_ty, _size) = &idl_ty {
-        // Convert str literal to array.
-        if idl_ty_value.contains("b\"") {
-            let components: Vec<&str> = idl_ty_value.split('b').collect();
-            assert_eq!(components.len(), 2);
-            let mut str_lit = components[1].to_string();
-            str_lit.retain(|c| c != '"');
-            return format!("{:?}", str_lit.as_bytes());
-        }
-    }
-    idl_ty_value.to_string()
-}

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

@@ -1,19 +0,0 @@
-use crate::Field;
-use syn::Expr;
-
-pub fn parse(acc: &Field, seeds_feature: bool) -> Vec<String> {
-    if !seeds_feature {
-        return vec![];
-    }
-    acc.constraints
-        .has_one
-        .iter()
-        .flat_map(|s| match &s.join_target {
-            Expr::Path(path) => path.path.segments.first().map(|l| l.ident.to_string()),
-            _ => {
-                println!("WARNING: unexpected seed: {s:?}");
-                None
-            }
-        })
-        .collect()
-}

+ 370 - 116
lang/syn/src/idl/types.rs

@@ -1,92 +1,104 @@
+use std::str::FromStr;
+
+use anyhow::anyhow;
 use serde::{Deserialize, Serialize};
 
+/// IDL specification Semantic Version
+pub const IDL_SPEC: &str = "0.1.0";
+
 #[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 address: String,
+    pub metadata: IdlMetadata,
+    #[serde(default, skip_serializing_if = "is_default")]
+    pub docs: Vec<String>,
     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<serde_json::Value>,
+    #[serde(default, skip_serializing_if = "is_default")]
+    pub accounts: Vec<IdlAccount>,
+    #[serde(default, skip_serializing_if = "is_default")]
+    pub events: Vec<IdlEvent>,
+    #[serde(default, skip_serializing_if = "is_default")]
+    pub errors: Vec<IdlErrorCode>,
+    #[serde(default, skip_serializing_if = "is_default")]
+    pub types: Vec<IdlTypeDef>,
+    #[serde(default, skip_serializing_if = "is_default")]
+    pub constants: Vec<IdlConst>,
 }
 
 #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
-pub struct IdlConst {
+pub struct IdlMetadata {
     pub name: String,
-    #[serde(rename = "type")]
-    pub ty: IdlType,
-    pub value: String,
+    pub version: String,
+    pub spec: String,
+    #[serde(skip_serializing_if = "is_default")]
+    pub description: Option<String>,
+    #[serde(skip_serializing_if = "is_default")]
+    pub repository: Option<String>,
+    #[serde(default, skip_serializing_if = "is_default")]
+    pub dependencies: Vec<IdlDependency>,
+    #[serde(skip_serializing_if = "is_default")]
+    pub contact: Option<String>,
 }
 
 #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
-pub struct IdlState {
-    #[serde(rename = "struct")]
-    pub strct: IdlTypeDefinition,
-    pub methods: Vec<IdlInstruction>,
+pub struct IdlDependency {
+    pub name: String,
+    pub version: String,
 }
 
 #[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>,
+    #[serde(default, skip_serializing_if = "is_default")]
+    pub docs: Vec<String>,
+    pub discriminator: IdlDiscriminator,
+    pub accounts: Vec<IdlInstructionAccountItem>,
     pub args: Vec<IdlField>,
-    #[serde(skip_serializing_if = "Option::is_none")]
+    #[serde(skip_serializing_if = "is_default")]
     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),
+pub enum IdlInstructionAccountItem {
+    Composite(IdlInstructionAccounts),
+    Single(IdlInstructionAccount),
 }
 
 #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
-#[serde(rename_all = "camelCase")]
-pub struct IdlAccount {
+pub struct IdlInstructionAccount {
     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)]
+    #[serde(default, skip_serializing_if = "is_default")]
+    pub docs: Vec<String>,
+    #[serde(default, skip_serializing_if = "is_default")]
+    pub writable: bool,
+    #[serde(default, skip_serializing_if = "is_default")]
+    pub signer: bool,
+    #[serde(default, skip_serializing_if = "is_default")]
+    pub optional: bool,
+    #[serde(skip_serializing_if = "is_default")]
+    pub address: Option<String>,
+    #[serde(skip_serializing_if = "is_default")]
     pub pda: Option<IdlPda>,
-    #[serde(skip_serializing_if = "Vec::is_empty", default)]
+    #[serde(default, skip_serializing_if = "is_default")]
     pub relations: Vec<String>,
 }
 
 #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
-#[serde(rename_all = "camelCase")]
+pub struct IdlInstructionAccounts {
+    pub name: String,
+    pub accounts: Vec<IdlInstructionAccountItem>,
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
 pub struct IdlPda {
     pub seeds: Vec<IdlSeed>,
-    #[serde(skip_serializing_if = "Option::is_none", default)]
-    pub program_id: Option<IdlSeed>,
+    #[serde(skip_serializing_if = "is_default")]
+    pub program: Option<IdlSeed>,
 }
 
 #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
-#[serde(rename_all = "camelCase", tag = "kind")]
+#[serde(tag = "kind", rename_all = "lowercase")]
 pub enum IdlSeed {
     Const(IdlSeedConst),
     Arg(IdlSeedArg),
@@ -94,96 +106,164 @@ pub enum IdlSeed {
 }
 
 #[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,
+pub struct IdlSeedConst {
+    pub value: Vec<u8>,
 }
 
 #[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,
+pub struct IdlSeedAccount {
+    pub path: String,
+    #[serde(skip_serializing_if = "is_default")]
+    pub account: Option<String>,
 }
 
 #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
-pub struct IdlField {
+pub struct IdlAccount {
     pub name: String,
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub docs: Option<Vec<String>>,
-    #[serde(rename = "type")]
-    pub ty: IdlType,
+    pub discriminator: IdlDiscriminator,
 }
 
 #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
 pub struct IdlEvent {
     pub name: String,
-    pub fields: Vec<IdlEventField>,
+    pub discriminator: IdlDiscriminator,
 }
 
 #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
-pub struct IdlEventField {
+pub struct IdlConst {
     pub name: String,
     #[serde(rename = "type")]
     pub ty: IdlType,
-    pub index: bool,
+    pub value: String,
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
+pub struct IdlErrorCode {
+    pub code: u32,
+    pub name: String,
+    #[serde(skip_serializing_if = "is_default")]
+    pub msg: Option<String>,
 }
 
 #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
-pub struct IdlTypeDefinition {
-    /// - `idl-parse`: always the name of the type
-    /// - `idl-build`: full path if there is a name conflict, otherwise the name of the type
+pub struct IdlField {
     pub name: String,
-    /// Documentation comments
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub docs: Option<Vec<String>>,
-    /// Generics, only supported with `idl-build`
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub generics: Option<Vec<String>>,
-    /// Type definition, `struct` or `enum`
+    #[serde(default, skip_serializing_if = "is_default")]
+    pub docs: Vec<String>,
     #[serde(rename = "type")]
-    pub ty: IdlTypeDefinitionTy,
+    pub ty: IdlType,
 }
 
 #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
-#[serde(rename_all = "lowercase", tag = "kind")]
-pub enum IdlTypeDefinitionTy {
-    Struct { fields: Vec<IdlField> },
-    Enum { variants: Vec<IdlEnumVariant> },
-    Alias { value: IdlType },
+pub struct IdlTypeDef {
+    pub name: String,
+    #[serde(default, skip_serializing_if = "is_default")]
+    pub docs: Vec<String>,
+    #[serde(default, skip_serializing_if = "is_default")]
+    pub serialization: IdlSerialization,
+    #[serde(skip_serializing_if = "is_default")]
+    pub repr: Option<IdlRepr>,
+    #[serde(default, skip_serializing_if = "is_default")]
+    pub generics: Vec<IdlTypeDefGeneric>,
+    #[serde(rename = "type")]
+    pub ty: IdlTypeDefTy,
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
+#[serde(rename_all = "lowercase")]
+pub enum IdlSerialization {
+    #[default]
+    Borsh,
+    Bytemuck,
+    BytemuckUnsafe,
+    Custom(String),
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
+#[serde(tag = "kind", rename_all = "lowercase")]
+pub enum IdlRepr {
+    Rust(IdlReprModifier),
+    C(IdlReprModifier),
+    Transparent,
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
+pub struct IdlReprModifier {
+    #[serde(default, skip_serializing_if = "is_default")]
+    pub packed: bool,
+    #[serde(skip_serializing_if = "is_default")]
+    pub align: Option<usize>,
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
+#[serde(tag = "kind", rename_all = "lowercase")]
+pub enum IdlTypeDefGeneric {
+    Type {
+        name: String,
+    },
+    Const {
+        name: String,
+        #[serde(rename = "type")]
+        ty: String,
+    },
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
+#[serde(tag = "kind", rename_all = "lowercase")]
+pub enum IdlTypeDefTy {
+    Struct {
+        #[serde(skip_serializing_if = "is_default")]
+        fields: Option<IdlDefinedFields>,
+    },
+    Enum {
+        variants: Vec<IdlEnumVariant>,
+    },
+    Type {
+        alias: IdlType,
+    },
 }
 
 #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
 pub struct IdlEnumVariant {
     pub name: String,
-    #[serde(skip_serializing_if = "Option::is_none", default)]
-    pub fields: Option<EnumFields>,
+    #[serde(skip_serializing_if = "is_default")]
+    pub fields: Option<IdlDefinedFields>,
 }
 
 #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
 #[serde(untagged)]
-pub enum EnumFields {
+pub enum IdlDefinedFields {
     Named(Vec<IdlField>),
     Tuple(Vec<IdlType>),
 }
 
 #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
-#[serde(rename_all = "camelCase")]
+#[serde(rename_all = "lowercase")]
+pub enum IdlArrayLen {
+    Generic(String),
+    #[serde(untagged)]
+    Value(usize),
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
+#[serde(tag = "kind", rename_all = "lowercase")]
+pub enum IdlGenericArg {
+    Type {
+        #[serde(rename = "type")]
+        ty: IdlType,
+    },
+    Const {
+        value: String,
+    },
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
+#[serde(rename_all = "lowercase")]
 pub enum IdlType {
     Bool,
     U8,
@@ -202,31 +282,205 @@ pub enum IdlType {
     I256,
     Bytes,
     String,
-    PublicKey,
-    Defined(String),
+    Pubkey,
     Option(Box<IdlType>),
     Vec(Box<IdlType>),
-    Array(Box<IdlType>, usize),
-    GenericLenArray(Box<IdlType>, String),
-    Generic(String),
-    DefinedWithTypeArgs {
+    Array(Box<IdlType>, IdlArrayLen),
+    Defined {
         name: String,
-        args: Vec<IdlDefinedTypeArg>,
+        #[serde(default, skip_serializing_if = "is_default")]
+        generics: Vec<IdlGenericArg>,
     },
+    Generic(String),
 }
 
-#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
-#[serde(rename_all = "camelCase")]
-pub enum IdlDefinedTypeArg {
-    Generic(String),
-    Value(String),
-    Type(IdlType),
+impl FromStr for IdlType {
+    type Err = anyhow::Error;
+
+    fn from_str(s: &str) -> Result<Self, Self::Err> {
+        let mut s = s.to_owned();
+        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::Pubkey,
+            _ => {
+                if let Some(inner) = s.strip_prefix("Option<") {
+                    let inner_ty = Self::from_str(
+                        inner
+                            .strip_suffix('>')
+                            .ok_or_else(|| anyhow!("Invalid Option"))?,
+                    )?;
+                    return Ok(IdlType::Option(Box::new(inner_ty)));
+                }
+
+                if let Some(inner) = s.strip_prefix("Vec<") {
+                    let inner_ty = Self::from_str(
+                        inner
+                            .strip_suffix('>')
+                            .ok_or_else(|| anyhow!("Invalid Vec"))?,
+                    )?;
+                    return Ok(IdlType::Vec(Box::new(inner_ty)));
+                }
+
+                if s.starts_with('[') {
+                    fn array_from_str(inner: &str) -> IdlType {
+                        match inner.strip_suffix(']') {
+                            Some(nested_inner) => array_from_str(&nested_inner[1..]),
+                            None => {
+                                let (raw_type, raw_length) = inner.rsplit_once(';').unwrap();
+                                let ty = IdlType::from_str(raw_type).unwrap();
+                                let len = match raw_length.replace('_', "").parse::<usize>() {
+                                    Ok(len) => IdlArrayLen::Value(len),
+                                    Err(_) => IdlArrayLen::Generic(raw_length.to_owned()),
+                                };
+                                IdlType::Array(Box::new(ty), len)
+                            }
+                        }
+                    }
+                    return Ok(array_from_str(&s));
+                }
+
+                // Defined
+                let (name, generics) = if let Some(i) = s.find('<') {
+                    (
+                        s.get(..i).unwrap().to_owned(),
+                        s.get(i + 1..)
+                            .unwrap()
+                            .strip_suffix('>')
+                            .unwrap()
+                            .split(',')
+                            .map(|g| g.trim().to_owned())
+                            .map(|g| {
+                                if g.parse::<bool>().is_ok()
+                                    || g.parse::<u128>().is_ok()
+                                    || g.parse::<i128>().is_ok()
+                                    || g.parse::<char>().is_ok()
+                                {
+                                    Ok(IdlGenericArg::Const { value: g })
+                                } else {
+                                    Self::from_str(&g).map(|ty| IdlGenericArg::Type { ty })
+                                }
+                            })
+                            .collect::<Result<Vec<_>, _>>()?,
+                    )
+                } else {
+                    (s.to_owned(), vec![])
+                };
+
+                IdlType::Defined { name, generics }
+            }
+        };
+        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>,
+pub type IdlDiscriminator = Vec<u8>;
+
+/// Get whether the given data is the default of its type.
+fn is_default<T: Default + PartialEq>(it: &T) -> bool {
+    *it == T::default()
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[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))
+        )
+    }
+
+    #[test]
+    fn array() {
+        assert_eq!(
+            IdlType::from_str("[Pubkey; 16]").unwrap(),
+            IdlType::Array(Box::new(IdlType::Pubkey), IdlArrayLen::Value(16))
+        );
+    }
+
+    #[test]
+    fn array_with_underscored_length() {
+        assert_eq!(
+            IdlType::from_str("[u8; 50_000]").unwrap(),
+            IdlType::Array(Box::new(IdlType::U8), IdlArrayLen::Value(50000))
+        );
+    }
+
+    #[test]
+    fn multidimensional_array() {
+        assert_eq!(
+            IdlType::from_str("[[u8; 16]; 32]").unwrap(),
+            IdlType::Array(
+                Box::new(IdlType::Array(
+                    Box::new(IdlType::U8),
+                    IdlArrayLen::Value(16)
+                )),
+                IdlArrayLen::Value(32)
+            )
+        );
+    }
+
+    #[test]
+    fn generic_array() {
+        assert_eq!(
+            IdlType::from_str("[u64; T]").unwrap(),
+            IdlType::Array(Box::new(IdlType::U64), IdlArrayLen::Generic("T".into()))
+        );
+    }
+
+    #[test]
+    fn defined() {
+        assert_eq!(
+            IdlType::from_str("MyStruct").unwrap(),
+            IdlType::Defined {
+                name: "MyStruct".into(),
+                generics: vec![]
+            }
+        )
+    }
+
+    #[test]
+    fn defined_with_generics() {
+        assert_eq!(
+            IdlType::from_str("MyStruct<Pubkey, u64, 8>").unwrap(),
+            IdlType::Defined {
+                name: "MyStruct".into(),
+                generics: vec![
+                    IdlGenericArg::Type {
+                        ty: IdlType::Pubkey
+                    },
+                    IdlGenericArg::Type { ty: IdlType::U64 },
+                    IdlGenericArg::Const { value: "8".into() },
+                ],
+            }
+        )
+    }
 }

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

@@ -11,7 +11,6 @@ pub mod hash;
 #[cfg(not(feature = "hash"))]
 pub(crate) mod hash;
 
-use crate::parser::tts_to_string;
 use codegen::accounts as accounts_codegen;
 use codegen::program as program_codegen;
 use parser::accounts as accounts_parser;
@@ -175,7 +174,7 @@ impl AccountsStruct {
         let matching_field = self
             .fields
             .iter()
-            .find(|f| *f.ident() == tts_to_string(field));
+            .find(|f| *f.ident() == parser::tts_to_string(field));
         if let Some(matching_field) = matching_field {
             matching_field.is_optional()
         } else {

+ 38 - 42
lang/syn/src/parser/context.rs

@@ -1,4 +1,4 @@
-use anyhow::anyhow;
+use anyhow::{anyhow, Result};
 use std::collections::BTreeMap;
 use std::path::{Path, PathBuf};
 use syn::parse::{Error as ParseError, Result as ParseResult};
@@ -12,6 +12,12 @@ pub struct CrateContext {
 }
 
 impl CrateContext {
+    pub fn parse(root: impl AsRef<Path>) -> Result<Self> {
+        Ok(CrateContext {
+            modules: ParsedModule::parse_recursive(root.as_ref())?,
+        })
+    }
+
     pub fn consts(&self) -> impl Iterator<Item = &syn::ItemConst> {
         self.modules.iter().flat_map(|(_, ctx)| ctx.consts())
     }
@@ -42,16 +48,10 @@ impl CrateContext {
         }
     }
 
-    pub fn parse(root: impl AsRef<Path>) -> Result<Self, anyhow::Error> {
-        Ok(CrateContext {
-            modules: ParsedModule::parse_recursive(root.as_ref())?,
-        })
-    }
-
     // Perform Anchor safety checks on the parsed create
-    pub fn safety_checks(&self) -> Result<(), anyhow::Error> {
+    pub fn safety_checks(&self) -> Result<()> {
         // Check all structs for unsafe field types, i.e. AccountInfo and UncheckedAccount.
-        for (_, ctx) in self.modules.iter() {
+        for ctx in self.modules.values() {
             for unsafe_field in ctx.unsafe_struct_fields() {
                 // Check if unsafe field type has been documented with a /// SAFETY: doc string.
                 let is_documented = unsafe_field.attrs.iter().any(|attr| {
@@ -104,8 +104,15 @@ struct ParsedModule {
     items: Vec<syn::Item>,
 }
 
+struct UnparsedModule {
+    file: PathBuf,
+    path: String,
+    name: String,
+    item: syn::ItemMod,
+}
+
 impl ParsedModule {
-    fn parse_recursive(root: &Path) -> Result<BTreeMap<String, ParsedModule>, anyhow::Error> {
+    fn parse_recursive(root: &Path) -> Result<BTreeMap<String, ParsedModule>> {
         let mut modules = BTreeMap::new();
 
         let root_content = std::fs::read_to_string(root)?;
@@ -117,35 +124,13 @@ impl ParsedModule {
             root_file.items,
         );
 
-        struct UnparsedModule {
-            file: PathBuf,
-            path: String,
-            name: String,
-            item: syn::ItemMod,
-        }
-
-        let mut unparsed = root_mod
-            .submodules()
-            .map(|item| UnparsedModule {
-                file: root_mod.file.clone(),
-                path: root_mod.path.clone(),
-                name: item.ident.to_string(),
-                item: item.clone(),
-            })
-            .collect::<Vec<_>>();
-
+        let mut unparsed = root_mod.unparsed_submodules();
         while let Some(to_parse) = unparsed.pop() {
             let path = format!("{}::{}", to_parse.path, to_parse.name);
-            let name = to_parse.name;
             let module = Self::from_item_mod(&to_parse.file, &path, to_parse.item)?;
 
-            unparsed.extend(module.submodules().map(|item| UnparsedModule {
-                item: item.clone(),
-                file: module.file.clone(),
-                path: module.path.clone(),
-                name: item.ident.to_string(),
-            }));
-            modules.insert(format!("{}{}", module.path.clone(), name.clone()), module);
+            unparsed.extend(module.unparsed_submodules());
+            modules.insert(format!("{}{}", module.path, to_parse.name), module);
         }
 
         modules.insert(root_mod.name.clone(), root_mod);
@@ -209,6 +194,17 @@ impl ParsedModule {
         }
     }
 
+    fn unparsed_submodules(&self) -> Vec<UnparsedModule> {
+        self.submodules()
+            .map(|item| UnparsedModule {
+                file: self.file.clone(),
+                path: self.path.clone(),
+                name: item.ident.to_string(),
+                item: item.clone(),
+            })
+            .collect()
+    }
+
     fn submodules(&self) -> impl Iterator<Item = &syn::ItemMod> {
         self.items.iter().filter_map(|i| match i {
             syn::Item::Mod(item) => Some(item),
@@ -259,6 +255,13 @@ impl ParsedModule {
         })
     }
 
+    fn type_aliases(&self) -> impl Iterator<Item = &syn::ItemType> {
+        self.items.iter().filter_map(|i| match i {
+            syn::Item::Type(item) => Some(item),
+            _ => None,
+        })
+    }
+
     fn consts(&self) -> impl Iterator<Item = &syn::ItemConst> {
         self.items.iter().filter_map(|i| match i {
             syn::Item::Const(item) => Some(item),
@@ -297,11 +300,4 @@ impl ParsedModule {
             })
             .flatten()
     }
-
-    fn type_aliases(&self) -> impl Iterator<Item = &syn::ItemType> {
-        self.items.iter().filter_map(|i| match i {
-            syn::Item::Type(item) => Some(item),
-            _ => None,
-        })
-    }
 }

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

@@ -6,7 +6,5 @@ pub mod program;
 pub mod spl_interface;
 
 pub fn tts_to_string<T: quote::ToTokens>(item: T) -> String {
-    let mut tts = proc_macro2::TokenStream::new();
-    item.to_tokens(&mut tts);
-    tts.to_string()
+    item.to_token_stream().to_string()
 }

+ 5 - 0
spl/src/governance.rs

@@ -57,5 +57,10 @@ macro_rules! vote_weight_record {
 
         #[cfg(feature = "idl-build")]
         impl anchor_lang::IdlBuild for VoterWeightRecord {}
+
+        #[cfg(feature = "idl-build")]
+        impl anchor_lang::Discriminator for VoterWeightRecord {
+            const DISCRIMINATOR: [u8; 8] = [0; 8];
+        }
     };
 }

+ 32 - 0
spl/src/idl_build.rs

@@ -0,0 +1,32 @@
+/// Crate a default [`anchor_lang::IdlBuild`] implementation for the given type.
+///
+/// This is used in order to make wrapper accounts of `anchor-spl` work with `idl-build` feature.
+macro_rules! impl_idl_build {
+    ($ty: ty) => {
+        impl anchor_lang::IdlBuild for $ty {}
+
+        // This is not used for the IDL generation since default `IdlBuild` impl doesn't include
+        // the type in the IDL but it stil needs to be added in order to make compilation work.
+        //
+        // TODO: Find a better way to handle discriminators of wrapped external accounts.
+        impl anchor_lang::Discriminator for $ty {
+            const DISCRIMINATOR: [u8; 8] = [0; 8];
+        }
+    };
+}
+
+#[cfg(feature = "metadata")]
+impl_idl_build!(crate::metadata::MetadataAccount);
+#[cfg(feature = "metadata")]
+impl_idl_build!(crate::metadata::MasterEditionAccount);
+#[cfg(feature = "metadata")]
+impl_idl_build!(crate::metadata::TokenRecordAccount);
+
+#[cfg(feature = "stake")]
+impl_idl_build!(crate::stake::StakeAccount);
+
+impl_idl_build!(crate::token::Mint);
+impl_idl_build!(crate::token::TokenAccount);
+
+impl_idl_build!(crate::token_interface::Mint);
+impl_idl_build!(crate::token_interface::TokenAccount);

+ 3 - 0
spl/src/lib.rs

@@ -31,3 +31,6 @@ pub mod metadata;
 
 #[cfg(feature = "memo")]
 pub mod memo;
+
+#[cfg(feature = "idl-build")]
+mod idl_build;

+ 0 - 9
spl/src/metadata.rs

@@ -810,9 +810,6 @@ impl Deref for MetadataAccount {
     }
 }
 
-#[cfg(feature = "idl-build")]
-impl anchor_lang::IdlBuild for MetadataAccount {}
-
 #[derive(Clone, Debug, PartialEq)]
 pub struct MasterEditionAccount(mpl_token_metadata::accounts::MasterEdition);
 
@@ -846,9 +843,6 @@ impl anchor_lang::Owner for MasterEditionAccount {
     }
 }
 
-#[cfg(feature = "idl-build")]
-impl anchor_lang::IdlBuild for MasterEditionAccount {}
-
 #[derive(Clone, Debug, PartialEq)]
 pub struct TokenRecordAccount(mpl_token_metadata::accounts::TokenRecord);
 
@@ -885,9 +879,6 @@ impl Deref for TokenRecordAccount {
     }
 }
 
-#[cfg(feature = "idl-build")]
-impl anchor_lang::IdlBuild for TokenRecordAccount {}
-
 #[derive(Clone)]
 pub struct Metadata;
 

+ 0 - 3
spl/src/stake.rs

@@ -156,9 +156,6 @@ impl Deref for StakeAccount {
     }
 }
 
-#[cfg(feature = "idl-build")]
-impl anchor_lang::IdlBuild for StakeAccount {}
-
 #[derive(Clone)]
 pub struct Stake;
 

+ 0 - 6
spl/src/token.rs

@@ -471,9 +471,6 @@ impl Deref for TokenAccount {
     }
 }
 
-#[cfg(feature = "idl-build")]
-impl anchor_lang::IdlBuild for TokenAccount {}
-
 #[derive(Clone, Debug, Default, PartialEq)]
 pub struct Mint(spl_token::state::Mint);
 
@@ -505,9 +502,6 @@ impl Deref for Mint {
     }
 }
 
-#[cfg(feature = "idl-build")]
-impl anchor_lang::IdlBuild for Mint {}
-
 #[derive(Clone)]
 pub struct Token;
 

+ 2 - 8
spl/src/token_interface.rs

@@ -1,6 +1,8 @@
 use anchor_lang::solana_program::pubkey::Pubkey;
 use std::ops::Deref;
 
+pub use crate::token_2022::*;
+
 static IDS: [Pubkey; 2] = [spl_token::ID, spl_token_2022::ID];
 
 #[derive(Clone, Debug, Default, PartialEq)]
@@ -32,9 +34,6 @@ impl Deref for TokenAccount {
     }
 }
 
-#[cfg(feature = "idl-build")]
-impl anchor_lang::IdlBuild for TokenAccount {}
-
 #[derive(Clone, Debug, Default, PartialEq)]
 pub struct Mint(spl_token_2022::state::Mint);
 
@@ -62,9 +61,6 @@ impl Deref for Mint {
     }
 }
 
-#[cfg(feature = "idl-build")]
-impl anchor_lang::IdlBuild for Mint {}
-
 #[derive(Clone)]
 pub struct TokenInterface;
 
@@ -73,5 +69,3 @@ impl anchor_lang::Ids for TokenInterface {
         &IDS
     }
 }
-
-pub use crate::token_2022::*;

+ 1 - 0
tests/anchor-cli-account/programs/account-command/Cargo.toml

@@ -14,6 +14,7 @@ no-idl = []
 no-log-ix-name = []
 cpi = ["no-entrypoint"]
 default = []
+idl-build = ["anchor-lang/idl-build"]
 
 [dependencies]
 anchor-lang = { path = "../../../../lang" }

+ 2 - 2
tests/anchor-cli-account/tests/account.ts

@@ -55,9 +55,9 @@ describe("Test CLI account commands", () => {
       await sleep(5000);
     }
 
-    assert(output.balance == balance, "Balance deserialized incorrectly");
+    assert(output.balance === balance, "Balance deserialized incorrectly");
     assert(
-      output.delegatePubkey == provider.wallet.publicKey,
+      output.delegate_pubkey === provider.wallet.publicKey.toBase58(),
       "delegatePubkey deserialized incorrectly"
     );
     assert(

+ 1 - 0
tests/anchor-cli-idl/programs/idl-commands-one/Cargo.toml

@@ -14,6 +14,7 @@ no-idl = []
 no-log-ix-name = []
 cpi = ["no-entrypoint"]
 default = []
+idl-build = ["anchor-lang/idl-build"]
 
 [dependencies]
 anchor-lang = { path = "../../../../lang" }

+ 1 - 0
tests/anchor-cli-idl/programs/idl-commands-two/Cargo.toml

@@ -14,6 +14,7 @@ no-idl = []
 no-log-ix-name = []
 cpi = ["no-entrypoint"]
 default = []
+idl-build = ["anchor-lang/idl-build"]
 
 [dependencies]
 anchor-lang = { path = "../../../../lang" }

+ 7 - 2
tests/anchor-cli-idl/test.sh

@@ -8,8 +8,12 @@ RANDOM_DATA=$(openssl rand -base64 $((10*1680)) | sed 's/.*/"&",/')
 
 # Create the JSON object with the "docs" field containing random data
 echo '{
-  "version": "0.1.0",
-  "name": "idl_commands_one",
+  "address": "2uA3amp95zsEHUpo8qnLMhcFAUsiKVEcKHXS1JetFjU5",
+  "metadata": {
+    "name": "idl_commands_one",
+    "version": "0.1.0",
+    "spec": "0.1.0"
+  },
   "instructions": [
     {
       "name": "initialize",
@@ -17,6 +21,7 @@ echo '{
         '"$RANDOM_DATA"'
         "trailing comma begone"
       ],
+      "discriminator": [],
       "accounts": [],
       "args": []
     }

+ 4 - 4
tests/anchor-cli-idl/tests/idl.ts

@@ -24,12 +24,12 @@ describe("Test CLI IDL commands", () => {
 
   it("Can fetch an IDL using the TypeScript client", async () => {
     const idl = await anchor.Program.fetchIdl(programOne.programId, provider);
-    assert.deepEqual(idl, programOne.idl);
+    assert.deepEqual(idl, programOne.rawIdl);
   });
 
   it("Can fetch an IDL via the CLI", async () => {
     const idl = execSync(`anchor idl fetch ${programOne.programId}`).toString();
-    assert.deepEqual(JSON.parse(idl), programOne.idl);
+    assert.deepEqual(JSON.parse(idl), programOne.rawIdl);
   });
 
   it("Can write a new IDL using the upgrade command", async () => {
@@ -39,7 +39,7 @@ describe("Test CLI IDL commands", () => {
       { stdio: "inherit" }
     );
     const idl = await anchor.Program.fetchIdl(programOne.programId, provider);
-    assert.deepEqual(idl, programTwo.idl);
+    assert.deepEqual(idl, programTwo.rawIdl);
   });
 
   it("Can write a new IDL using write-buffer and set-buffer", async () => {
@@ -53,7 +53,7 @@ describe("Test CLI IDL commands", () => {
       { stdio: "inherit" }
     );
     const idl = await anchor.Program.fetchIdl(programOne.programId, provider);
-    assert.deepEqual(idl, programOne.idl);
+    assert.deepEqual(idl, programOne.rawIdl);
   });
 
   it("Can fetch an IDL authority via the CLI", async () => {

+ 1 - 0
tests/auction-house/programs/auction-house/Cargo.toml

@@ -14,6 +14,7 @@ no-entrypoint = []
 no-idl = []
 cpi = ["no-entrypoint"]
 default = []
+idl-build = ["anchor-lang/idl-build", "anchor-spl/idl-build"]
 
 [dependencies]
 anchor-lang = { path = "../../../../lang" }

+ 3 - 1
tests/auction-house/tests/auction-house.ts

@@ -15,7 +15,9 @@ import {
 import { u64, Token, TOKEN_PROGRAM_ID } from "@solana/spl-token";
 import * as metaplex from "@metaplex/js";
 import * as assert from "assert";
-import { IDL, AuctionHouse } from "../target/types/auction_house";
+import { AuctionHouse } from "../target/types/auction_house";
+
+const IDL = require("../target/idl/auction_house.json");
 
 const MetadataDataData = metaplex.programs.metadata.MetadataDataData;
 const CreateMetadata = metaplex.programs.metadata.CreateMetadata;

+ 1 - 0
tests/bench/programs/bench/Cargo.toml

@@ -10,6 +10,7 @@ crate-type = ["cdylib", "lib"]
 [features]
 no-entrypoint = []
 cpi = ["no-entrypoint"]
+idl-build = ["anchor-lang/idl-build", "anchor-spl/idl-build"]
 
 [dependencies]
 anchor-lang = { path = "../../../../lang" }

+ 6 - 3
tests/bench/tests/binary-size.ts

@@ -1,15 +1,18 @@
 import * as fs from "fs/promises";
 import path from "path";
 
-import { IDL } from "../target/types/bench";
 import { BenchData, BinarySize } from "../scripts/utils";
 
+const IDL = require("../target/idl/bench.json");
+
 describe("Binary size", () => {
   const binarySize: BinarySize = {};
 
   it("Measure binary size", async () => {
-    const stat = await fs.stat(path.join("target", "deploy", `${IDL.name}.so`));
-    binarySize[IDL.name] = stat.size;
+    const stat = await fs.stat(
+      path.join("target", "deploy", `${IDL.metadata.name}.so`)
+    );
+    binarySize[IDL.metadata.name] = stat.size;
   });
 
   after(async () => {

+ 4 - 4
tests/bench/tests/compute-units.ts

@@ -1,7 +1,7 @@
 import * as anchor from "@coral-xyz/anchor";
 import * as token from "@coral-xyz/spl-token";
 
-import { Bench, IDL } from "../target/types/bench";
+import { Bench } from "../target/types/bench";
 import { BenchData, ComputeUnits } from "../scripts/utils";
 
 describe("Compute units", () => {
@@ -31,7 +31,7 @@ describe("Compute units", () => {
     for (const accountCount of options.accountCounts) {
       // Check whether the init version of the instruction exists
       const ixNameInit = `${ixName}Init`;
-      const hasInitVersion = IDL.instructions.some((ix) =>
+      const hasInitVersion = program.idl.instructions.some((ix) =>
         ix.name.startsWith(ixNameInit)
       );
 
@@ -53,7 +53,7 @@ describe("Compute units", () => {
           signers.splice(0);
         }
 
-        for (const ix of IDL.instructions) {
+        for (const ix of program.idl.instructions) {
           if (ix.name !== method) continue;
 
           for (const account of ix.accounts) {
@@ -80,7 +80,7 @@ describe("Compute units", () => {
             const keypair = options.generateKeypair(account.name);
             accounts[account.name] = keypair.publicKey;
 
-            if (account.isSigner) {
+            if (account.signer) {
               signers.push(keypair);
             }
           }

+ 3 - 2
tests/bench/tests/stack-memory.ts

@@ -2,7 +2,8 @@ import path from "path";
 import fs from "fs/promises";
 
 import { BenchData, StackMemory, spawn } from "../scripts/utils";
-import { IDL } from "../target/types/bench";
+
+const IDL = require("../target/idl/bench.json");
 
 describe("Stack memory", () => {
   const stackMemory: StackMemory = {};
@@ -15,7 +16,7 @@ describe("Stack memory", () => {
   ].join("\n\t\t");
 
   it("Measure stack memory usage", async () => {
-    const libPath = path.join("programs", IDL.name, "src", "lib.rs");
+    const libPath = path.join("programs", IDL.metadata.name, "src", "lib.rs");
     const lib = await fs.readFile(libPath, "utf8");
     const indices = [...lib.matchAll(/fn\s[\w\d]+\(/g)]
       .map((match) => match.index)

+ 1 - 0
tests/bpf-upgradeable-state/programs/bpf-upgradeable-state/Cargo.toml

@@ -14,6 +14,7 @@ no-entrypoint = []
 no-idl = []
 cpi = ["no-entrypoint"]
 default = []
+idl-build = ["anchor-lang/idl-build"]
 
 [dependencies]
 anchor-lang = { path = "../../../../lang" }

+ 3 - 1
tests/bpf-upgradeable-state/programs/bpf-upgradeable-state/src/lib.rs

@@ -1,5 +1,7 @@
 use anchor_lang::prelude::*;
 
+use crate::program::BpfUpgradeableState;
+
 declare_id!("Cum9tTyj5HwcEiAmhgaS7Bbj4UczCwsucrCkxRECzM4e");
 
 #[program]
@@ -73,7 +75,7 @@ pub struct SetAdminSettingsUseProgramState<'info> {
     #[account(mut)]
     pub authority: Signer<'info>,
     #[account(constraint = program.programdata_address()? == Some(program_data.key()))]
-    pub program: Program<'info, crate::program::BpfUpgradeableState>,
+    pub program: Program<'info, BpfUpgradeableState>,
     #[account(constraint = program_data.upgrade_authority_address == Some(authority.key()))]
     pub program_data: Account<'info, ProgramData>,
     pub system_program: Program<'info, System>,

+ 1 - 0
tests/cashiers-check/programs/cashiers-check/Cargo.toml

@@ -14,6 +14,7 @@ no-entrypoint = []
 no-idl = []
 cpi = ["no-entrypoint"]
 default = []
+idl-build = ["anchor-lang/idl-build", "anchor-spl/idl-build"]
 
 [dependencies]
 anchor-lang = { path = "../../../../lang" }

+ 1 - 0
tests/chat/programs/chat/Cargo.toml

@@ -14,6 +14,7 @@ no-entrypoint = []
 no-idl = []
 cpi = ["no-entrypoint"]
 default = []
+idl-build = ["anchor-lang/idl-build"]
 
 [dependencies]
 anchor-lang = { path = "../../../../lang" }

+ 1 - 0
tests/composite/programs/composite/Cargo.toml

@@ -12,6 +12,7 @@ name = "composite"
 [features]
 no-entrypoint = []
 cpi = ["no-entrypoint"]
+idl-build = ["anchor-lang/idl-build"]
 
 [dependencies]
 anchor-lang = { path = "../../../../lang" }

+ 1 - 0
tests/cpi-returns/programs/callee/Cargo.toml

@@ -14,6 +14,7 @@ no-idl = []
 no-log-ix-name = []
 cpi = ["no-entrypoint"]
 default = []
+idl-build = ["anchor-lang/idl-build"]
 
 [dependencies]
 anchor-lang = { path = "../../../../lang", features = ["init-if-needed"] }

+ 1 - 0
tests/cpi-returns/programs/caller/Cargo.toml

@@ -14,6 +14,7 @@ no-idl = []
 no-log-ix-name = []
 cpi = ["no-entrypoint"]
 default = []
+idl-build = ["anchor-lang/idl-build"]
 
 [dependencies]
 anchor-lang = { path = "../../../../lang", features = ["init-if-needed"] }

+ 1 - 1
tests/cpi-returns/tests/cpi-return.ts

@@ -159,7 +159,7 @@ describe("CPI return", () => {
       (f) => f.name == "returnStruct"
     );
     assert.deepStrictEqual(returnStructInstruction.returns, {
-      defined: "StructReturn",
+      defined: { name: "structReturn" },
     });
   });
 

+ 1 - 0
tests/custom-coder/programs/native-system/Cargo.toml

@@ -15,6 +15,7 @@ no-idl = []
 no-log-ix-name = []
 cpi = ["no-entrypoint"]
 default = []
+idl-build = ["anchor-lang/idl-build"]
 
 [dependencies]
 anchor-lang = { path = "../../../../lang" }

+ 1 - 0
tests/custom-coder/programs/spl-associated-token/Cargo.toml

@@ -14,6 +14,7 @@ no-idl = []
 no-log-ix-name = []
 cpi = ["no-entrypoint"]
 default = []
+idl-build = ["anchor-lang/idl-build"]
 
 [profile.release]
 overflow-checks = true

+ 1 - 0
tests/custom-coder/programs/spl-token/Cargo.toml

@@ -15,6 +15,7 @@ no-idl = []
 no-log-ix-name = []
 cpi = ["no-entrypoint"]
 default = []
+idl-build = ["anchor-lang/idl-build"]
 
 [dependencies]
 anchor-lang = { path = "../../../../lang" }

+ 1 - 0
tests/declare-id/programs/declare-id/Cargo.toml

@@ -11,6 +11,7 @@ name = "declare_id"
 [features]
 no-entrypoint = []
 cpi = ["no-entrypoint"]
+idl-build = ["anchor-lang/idl-build"]
 
 [dependencies]
 anchor-lang = { path = "../../../../lang" }

+ 1 - 0
tests/docs/programs/docs/Cargo.toml

@@ -11,6 +11,7 @@ name = "errors"
 [features]
 no-entrypoint = []
 cpi = ["no-entrypoint"]
+idl-build = ["anchor-lang/idl-build"]
 
 [dependencies]
 anchor-lang = { path = "../../../../lang" }

+ 1 - 0
tests/errors/programs/errors/Cargo.toml

@@ -12,6 +12,7 @@ name = "errors"
 [features]
 no-entrypoint = []
 cpi = ["no-entrypoint"]
+idl-build = ["anchor-lang/idl-build"]
 
 [dependencies]
 anchor-lang = { path = "../../../../lang" }

+ 1 - 1
tests/errors/tests/errors.ts

@@ -258,7 +258,7 @@ describe("errors", () => {
             },
           ],
           programId: program.programId,
-          data: program.coder.instruction.encode("signer_error", {}),
+          data: program.coder.instruction.encode("signerError", {}),
         })
       );
       await program.provider.sendAndConfirm(tx);

+ 1 - 0
tests/escrow/programs/escrow/Cargo.toml

@@ -14,6 +14,7 @@ no-entrypoint = []
 no-idl = []
 cpi = ["no-entrypoint"]
 default = []
+idl-build = ["anchor-lang/idl-build", "anchor-spl/idl-build"]
 
 [dependencies]
 anchor-lang = { path = "../../../../lang" }

+ 1 - 0
tests/events/programs/events/Cargo.toml

@@ -14,6 +14,7 @@ no-entrypoint = []
 no-idl = []
 cpi = ["no-entrypoint"]
 default = []
+idl-build = ["anchor-lang/idl-build"]
 
 [dependencies]
 anchor-lang = { path = "../../../../lang", features = ["event-cpi"] }

+ 4 - 4
tests/events/tests/events.ts

@@ -27,15 +27,15 @@ describe("Events", () => {
 
   describe("Normal event", () => {
     it("Single event works", async () => {
-      const event = await getEvent("MyEvent", "initialize");
+      const event = await getEvent("myEvent", "initialize");
 
       assert.strictEqual(event.data.toNumber(), 5);
       assert.strictEqual(event.label, "hello");
     });
 
     it("Multiple events work", async () => {
-      const eventOne = await getEvent("MyEvent", "initialize");
-      const eventTwo = await getEvent("MyOtherEvent", "testEvent");
+      const eventOne = await getEvent("myEvent", "initialize");
+      const eventTwo = await getEvent("myOtherEvent", "testEvent");
 
       assert.strictEqual(eventOne.data.toNumber(), 5);
       assert.strictEqual(eventOne.label, "hello");
@@ -61,7 +61,7 @@ describe("Events", () => {
       const eventData = anchor.utils.bytes.base64.encode(ixData.slice(8));
       const event = program.coder.events.decode(eventData);
 
-      assert.strictEqual(event.name, "MyOtherEvent");
+      assert.strictEqual(event.name, "myOtherEvent");
       assert.strictEqual(event.data.label, "cpi");
       assert.strictEqual((event.data.data as anchor.BN).toNumber(), 7);
     });

+ 1 - 0
tests/floats/programs/floats/Cargo.toml

@@ -14,6 +14,7 @@ no-idl = []
 no-log-ix-name = []
 cpi = ["no-entrypoint"]
 default = []
+idl-build = ["anchor-lang/idl-build"]
 
 [dependencies]
 anchor-lang = { path = "../../../../lang" }

+ 3 - 4
tests/idl/Anchor.toml

@@ -1,16 +1,15 @@
 [features]
-seeds = true
+skip-lint = true
 
 [programs.localnet]
-client_interactions = "C1ient1nteractions1111111111111111111111111"
 docs = "Docs111111111111111111111111111111111111111"
 external = "Externa1111111111111111111111111111111111111"
 generics = "Generics111111111111111111111111111111111111"
 idl = "id11111111111111111111111111111111111111111"
-idl_build_features = "id1Bui1dFeatures111111111111111111111111111"
 relations_derivation = "Re1ationsDerivation111111111111111111111111"
 non_existent = { address = "NonExistent11111111111111111111111111111111", idl = "non-existent.json" }
-numbers_123 = { address = "Numbers111111111111111111111111111111111111", idl = "idls/relations_build.json" }
+numbers_123 = { address = "Numbers111111111111111111111111111111111111", idl = "idls/relations.json" }
+new_idl = "Newid11111111111111111111111111111111111111"
 
 [provider]
 cluster = "localnet"

+ 3 - 4
tests/idl/generate.sh

@@ -8,11 +8,10 @@ else
 fi
 
 cd programs/idl
-anchor idl parse --file src/lib.rs -o $dir/parse.json
-anchor idl build -o $dir/build.json
+anchor idl build -o $dir/new.json
 
 cd ../generics
-anchor idl build -o $dir/generics_build.json
+anchor idl build -o $dir/generics.json
 
 cd ../relations-derivation
-anchor idl build -o $dir/relations_build.json
+anchor idl build -o $dir/relations.json

+ 169 - 66
tests/idl/idls/generics_build.json → tests/idl/idls/generics.json

@@ -1,40 +1,55 @@
 {
-  "version": "0.1.0",
-  "name": "generics",
+  "address": "Generics111111111111111111111111111111111111",
+  "metadata": {
+    "name": "generics",
+    "version": "0.1.0",
+    "spec": "0.1.0",
+    "description": "Created with Anchor"
+  },
   "instructions": [
     {
       "name": "generic",
+      "discriminator": [
+        63,
+        235,
+        150,
+        148,
+        7,
+        255,
+        185,
+        159
+      ],
       "accounts": [
         {
-          "name": "genericAcc",
-          "isMut": false,
-          "isSigner": false
+          "name": "generic_acc"
         },
         {
           "name": "payer",
-          "isMut": true,
-          "isSigner": true
+          "writable": true,
+          "signer": true
         },
         {
-          "name": "systemProgram",
-          "isMut": false,
-          "isSigner": false
+          "name": "system_program",
+          "address": "11111111111111111111111111111111"
         }
       ],
       "args": [
         {
-          "name": "genericField",
+          "name": "generic_field",
           "type": {
-            "definedWithTypeArgs": {
+            "defined": {
               "name": "GenericType",
-              "args": [
+              "generics": [
                 {
+                  "kind": "type",
                   "type": "u32"
                 },
                 {
+                  "kind": "type",
                   "type": "u64"
                 },
                 {
+                  "kind": "const",
                   "value": "10"
                 }
               ]
@@ -45,6 +60,21 @@
     }
   ],
   "accounts": [
+    {
+      "name": "GenericAccount",
+      "discriminator": [
+        10,
+        71,
+        68,
+        49,
+        51,
+        72,
+        147,
+        245
+      ]
+    }
+  ],
+  "types": [
     {
       "name": "GenericAccount",
       "type": {
@@ -53,16 +83,19 @@
           {
             "name": "data",
             "type": {
-              "definedWithTypeArgs": {
+              "defined": {
                 "name": "GenericType",
-                "args": [
+                "generics": [
                   {
+                    "kind": "type",
                     "type": "u32"
                   },
                   {
+                    "kind": "type",
                     "type": "u64"
                   },
                   {
+                    "kind": "const",
                     "value": "10"
                   }
                 ]
@@ -71,27 +104,23 @@
           }
         ]
       }
-    }
-  ],
-  "types": [
-    {
-      "name": "Baz",
-      "type": {
-        "kind": "struct",
-        "fields": [
-          {
-            "name": "someField",
-            "type": "u8"
-          }
-        ]
-      }
     },
     {
       "name": "GenericEnum",
       "generics": [
-        "T",
-        "U",
-        "N"
+        {
+          "kind": "type",
+          "name": "T"
+        },
+        {
+          "kind": "type",
+          "name": "U"
+        },
+        {
+          "kind": "const",
+          "name": "N",
+          "type": "usize"
+        }
       ],
       "type": {
         "kind": "enum",
@@ -128,15 +157,17 @@
             "name": "Struct",
             "fields": [
               {
-                "definedWithTypeArgs": {
+                "defined": {
                   "name": "GenericNested",
-                  "args": [
+                  "generics": [
                     {
+                      "kind": "type",
                       "type": {
                         "generic": "T"
                       }
                     },
                     {
+                      "kind": "type",
                       "type": {
                         "generic": "U"
                       }
@@ -150,11 +181,13 @@
             "name": "Arr",
             "fields": [
               {
-                "genericLenArray": [
+                "array": [
                   {
                     "generic": "T"
                   },
-                  "N"
+                  {
+                    "generic": "N"
+                  }
                 ]
               }
             ]
@@ -165,8 +198,14 @@
     {
       "name": "GenericNested",
       "generics": [
-        "V",
-        "Z"
+        {
+          "kind": "type",
+          "name": "V"
+        },
+        {
+          "kind": "type",
+          "name": "Z"
+        }
       ],
       "type": {
         "kind": "struct",
@@ -189,9 +228,19 @@
     {
       "name": "GenericType",
       "generics": [
-        "T",
-        "U",
-        "N"
+        {
+          "kind": "type",
+          "name": "T"
+        },
+        {
+          "kind": "type",
+          "name": "U"
+        },
+        {
+          "kind": "const",
+          "name": "N",
+          "type": "usize"
+        }
       ],
       "type": {
         "kind": "struct",
@@ -211,13 +260,15 @@
           {
             "name": "gen3",
             "type": {
-              "definedWithTypeArgs": {
+              "defined": {
                 "name": "GenericNested",
-                "args": [
+                "generics": [
                   {
+                    "kind": "type",
                     "type": "u32"
                   },
                   {
+                    "kind": "type",
                     "type": {
                       "generic": "U"
                     }
@@ -229,17 +280,21 @@
           {
             "name": "gen4",
             "type": {
-              "definedWithTypeArgs": {
+              "defined": {
                 "name": "GenericNested",
-                "args": [
+                "generics": [
                   {
+                    "kind": "type",
                     "type": {
                       "generic": "T"
                     }
                   },
                   {
+                    "kind": "type",
                     "type": {
-                      "defined": "Baz"
+                      "defined": {
+                        "name": "MyStruct"
+                      }
                     }
                   }
                 ]
@@ -249,15 +304,17 @@
           {
             "name": "gen5",
             "type": {
-              "definedWithTypeArgs": {
+              "defined": {
                 "name": "GenericNested",
-                "args": [
+                "generics": [
                   {
+                    "kind": "type",
                     "type": {
                       "generic": "T"
                     }
                   },
                   {
+                    "kind": "type",
                     "type": {
                       "generic": "U"
                     }
@@ -269,13 +326,15 @@
           {
             "name": "gen6",
             "type": {
-              "definedWithTypeArgs": {
+              "defined": {
                 "name": "GenericNested",
-                "args": [
+                "generics": [
                   {
+                    "kind": "type",
                     "type": "u32"
                   },
                   {
+                    "kind": "type",
                     "type": "u64"
                   }
                 ]
@@ -285,25 +344,29 @@
           {
             "name": "gen7",
             "type": {
-              "definedWithTypeArgs": {
+              "defined": {
                 "name": "GenericNested",
-                "args": [
+                "generics": [
                   {
+                    "kind": "type",
                     "type": {
                       "generic": "T"
                     }
                   },
                   {
+                    "kind": "type",
                     "type": {
-                      "definedWithTypeArgs": {
+                      "defined": {
                         "name": "GenericNested",
-                        "args": [
+                        "generics": [
                           {
+                            "kind": "type",
                             "type": {
                               "generic": "T"
                             }
                           },
                           {
+                            "kind": "type",
                             "type": {
                               "generic": "U"
                             }
@@ -319,19 +382,22 @@
           {
             "name": "arr",
             "type": {
-              "genericLenArray": [
+              "array": [
                 "u8",
-                "N"
+                {
+                  "generic": "N"
+                }
               ]
             }
           },
           {
             "name": "warr",
             "type": {
-              "definedWithTypeArgs": {
+              "defined": {
                 "name": "WrappedU8Array",
-                "args": [
+                "generics": [
                   {
+                    "kind": "type",
                     "type": {
                       "generic": "N"
                     }
@@ -343,10 +409,11 @@
           {
             "name": "warrval",
             "type": {
-              "definedWithTypeArgs": {
+              "defined": {
                 "name": "WrappedU8Array",
-                "args": [
+                "generics": [
                   {
+                    "kind": "const",
                     "value": "10"
                   }
                 ]
@@ -356,20 +423,23 @@
           {
             "name": "enm1",
             "type": {
-              "definedWithTypeArgs": {
+              "defined": {
                 "name": "GenericEnum",
-                "args": [
+                "generics": [
                   {
+                    "kind": "type",
                     "type": {
                       "generic": "T"
                     }
                   },
                   {
+                    "kind": "type",
                     "type": {
                       "generic": "U"
                     }
                   },
                   {
+                    "kind": "type",
                     "type": {
                       "generic": "N"
                     }
@@ -381,20 +451,23 @@
           {
             "name": "enm2",
             "type": {
-              "definedWithTypeArgs": {
+              "defined": {
                 "name": "GenericEnum",
-                "args": [
+                "generics": [
                   {
+                    "kind": "type",
                     "type": {
-                      "definedWithTypeArgs": {
+                      "defined": {
                         "name": "GenericNested",
-                        "args": [
+                        "generics": [
                           {
+                            "kind": "type",
                             "type": {
                               "generic": "T"
                             }
                           },
                           {
+                            "kind": "type",
                             "type": "u64"
                           }
                         ]
@@ -402,9 +475,11 @@
                     }
                   },
                   {
+                    "kind": "type",
                     "type": "u32"
                   },
                   {
+                    "kind": "const",
                     "value": "30"
                   }
                 ]
@@ -413,6 +488,34 @@
           }
         ]
       }
+    },
+    {
+      "name": "MyStruct",
+      "type": {
+        "kind": "struct",
+        "fields": [
+          {
+            "name": "some_field",
+            "type": "u8"
+          }
+        ]
+      }
+    },
+    {
+      "name": "WrappedU8Array",
+      "generics": [
+        {
+          "kind": "const",
+          "name": "N",
+          "type": "usize"
+        }
+      ],
+      "type": {
+        "kind": "struct",
+        "fields": [
+          "u8"
+        ]
+      }
     }
   ]
 }

+ 500 - 366
tests/idl/idls/build.json → tests/idl/idls/new.json

@@ -1,268 +1,266 @@
 {
-  "version": "0.1.0",
-  "name": "idl",
+  "address": "id11111111111111111111111111111111111111111",
+  "metadata": {
+    "name": "idl",
+    "version": "0.1.0",
+    "spec": "0.1.0",
+    "description": "Created with Anchor"
+  },
   "docs": [
     "IDL test program documentation."
   ],
-  "constants": [
-    {
-      "name": "BYTES_STR",
-      "type": "bytes",
-      "value": "[116, 101, 115, 116]"
-    },
-    {
-      "name": "BYTE_STR",
-      "type": "u8",
-      "value": "116"
-    },
+  "instructions": [
     {
-      "name": "I128",
-      "type": "i128",
-      "value": "1000000"
+      "name": "cause_error",
+      "discriminator": [
+        67,
+        104,
+        37,
+        17,
+        2,
+        155,
+        68,
+        17
+      ],
+      "accounts": [],
+      "args": []
     },
-    {
-      "name": "U8",
-      "type": "u8",
-      "value": "6"
-    }
-  ],
-  "instructions": [
     {
       "name": "initialize",
+      "discriminator": [
+        175,
+        175,
+        109,
+        31,
+        13,
+        152,
+        155,
+        237
+      ],
       "accounts": [
         {
           "name": "state",
-          "isMut": true,
-          "isSigner": true,
           "docs": [
             "State account"
-          ]
+          ],
+          "writable": true,
+          "signer": true
         },
         {
           "name": "nested",
           "accounts": [
             {
               "name": "clock",
-              "isMut": false,
-              "isSigner": false,
               "docs": [
                 "Sysvar clock"
-              ]
+              ],
+              "address": "SysvarC1ock11111111111111111111111111111111"
             },
             {
               "name": "rent",
-              "isMut": false,
-              "isSigner": false
+              "address": "SysvarRent111111111111111111111111111111111"
             }
           ]
         },
         {
-          "name": "zcAccount",
-          "isMut": false,
-          "isSigner": false
+          "name": "zc_account"
         },
         {
-          "name": "tokenAccount",
-          "isMut": false,
-          "isSigner": false
+          "name": "token_account"
         },
         {
-          "name": "mintAccount",
-          "isMut": false,
-          "isSigner": false
+          "name": "mint_account"
         },
         {
-          "name": "tokenInterfaceAccount",
-          "isMut": false,
-          "isSigner": false
+          "name": "token_interface_account"
         },
         {
-          "name": "mintInterfaceAccount",
-          "isMut": false,
-          "isSigner": false
+          "name": "mint_interface_account"
         },
         {
           "name": "payer",
-          "isMut": true,
-          "isSigner": true
+          "writable": true,
+          "signer": true
         },
         {
-          "name": "systemProgram",
-          "isMut": false,
-          "isSigner": false
+          "name": "system_program",
+          "address": "11111111111111111111111111111111"
         }
       ],
       "args": []
     },
     {
-      "name": "initializeWithValues",
+      "name": "initialize_with_values",
       "docs": [
         "Initializes an account with specified values"
       ],
+      "discriminator": [
+        220,
+        73,
+        8,
+        213,
+        178,
+        69,
+        181,
+        141
+      ],
       "accounts": [
         {
           "name": "state",
-          "isMut": true,
-          "isSigner": true,
           "docs": [
             "State account"
-          ]
+          ],
+          "writable": true,
+          "signer": true
         },
         {
           "name": "nested",
           "accounts": [
             {
               "name": "clock",
-              "isMut": false,
-              "isSigner": false,
               "docs": [
                 "Sysvar clock"
-              ]
+              ],
+              "address": "SysvarC1ock11111111111111111111111111111111"
             },
             {
               "name": "rent",
-              "isMut": false,
-              "isSigner": false
+              "address": "SysvarRent111111111111111111111111111111111"
             }
           ]
         },
         {
-          "name": "zcAccount",
-          "isMut": false,
-          "isSigner": false
+          "name": "zc_account"
         },
         {
-          "name": "tokenAccount",
-          "isMut": false,
-          "isSigner": false
+          "name": "token_account"
         },
         {
-          "name": "mintAccount",
-          "isMut": false,
-          "isSigner": false
+          "name": "mint_account"
         },
         {
-          "name": "tokenInterfaceAccount",
-          "isMut": false,
-          "isSigner": false
+          "name": "token_interface_account"
         },
         {
-          "name": "mintInterfaceAccount",
-          "isMut": false,
-          "isSigner": false
+          "name": "mint_interface_account"
         },
         {
           "name": "payer",
-          "isMut": true,
-          "isSigner": true
+          "writable": true,
+          "signer": true
         },
         {
-          "name": "systemProgram",
-          "isMut": false,
-          "isSigner": false
+          "name": "system_program",
+          "address": "11111111111111111111111111111111"
         }
       ],
       "args": [
         {
-          "name": "boolField",
+          "name": "bool_field",
           "type": "bool"
         },
         {
-          "name": "u8Field",
+          "name": "u8_field",
           "type": "u8"
         },
         {
-          "name": "i8Field",
+          "name": "i8_field",
           "type": "i8"
         },
         {
-          "name": "u16Field",
+          "name": "u16_field",
           "type": "u16"
         },
         {
-          "name": "i16Field",
+          "name": "i16_field",
           "type": "i16"
         },
         {
-          "name": "u32Field",
+          "name": "u32_field",
           "type": "u32"
         },
         {
-          "name": "i32Field",
+          "name": "i32_field",
           "type": "i32"
         },
         {
-          "name": "f32Field",
+          "name": "f32_field",
           "type": "f32"
         },
         {
-          "name": "u64Field",
+          "name": "u64_field",
           "type": "u64"
         },
         {
-          "name": "i64Field",
+          "name": "i64_field",
           "type": "i64"
         },
         {
-          "name": "f64Field",
+          "name": "f64_field",
           "type": "f64"
         },
         {
-          "name": "u128Field",
+          "name": "u128_field",
           "type": "u128"
         },
         {
-          "name": "i128Field",
+          "name": "i128_field",
           "type": "i128"
         },
         {
-          "name": "bytesField",
+          "name": "bytes_field",
           "type": "bytes"
         },
         {
-          "name": "stringField",
+          "name": "string_field",
           "type": "string"
         },
         {
-          "name": "pubkeyField",
-          "type": "publicKey"
+          "name": "pubkey_field",
+          "type": "pubkey"
         },
         {
-          "name": "vecField",
+          "name": "vec_field",
           "type": {
             "vec": "u64"
           }
         },
         {
-          "name": "vecStructField",
+          "name": "vec_struct_field",
           "type": {
             "vec": {
-              "defined": "FooStruct"
+              "defined": {
+                "name": "FooStruct"
+              }
             }
           }
         },
         {
-          "name": "optionField",
+          "name": "option_field",
           "type": {
             "option": "bool"
           }
         },
         {
-          "name": "optionStructField",
+          "name": "option_struct_field",
           "type": {
             "option": {
-              "defined": "FooStruct"
+              "defined": {
+                "name": "FooStruct"
+              }
             }
           }
         },
         {
-          "name": "structField",
+          "name": "struct_field",
           "type": {
-            "defined": "FooStruct"
+            "defined": {
+              "name": "FooStruct"
+            }
           }
         },
         {
-          "name": "arrayField",
+          "name": "array_field",
           "type": {
             "array": [
               "bool",
@@ -271,57 +269,74 @@
           }
         },
         {
-          "name": "enumField1",
+          "name": "enum_field_1",
           "type": {
-            "defined": "FooEnum"
+            "defined": {
+              "name": "FooEnum"
+            }
           }
         },
         {
-          "name": "enumField2",
+          "name": "enum_field_2",
           "type": {
-            "defined": "FooEnum"
+            "defined": {
+              "name": "FooEnum"
+            }
           }
         },
         {
-          "name": "enumField3",
+          "name": "enum_field_3",
           "type": {
-            "defined": "FooEnum"
+            "defined": {
+              "name": "FooEnum"
+            }
           }
         },
         {
-          "name": "enumField4",
+          "name": "enum_field_4",
           "type": {
-            "defined": "FooEnum"
+            "defined": {
+              "name": "FooEnum"
+            }
           }
         }
       ]
     },
     {
-      "name": "initializeWithValues2",
+      "name": "initialize_with_values2",
       "docs": [
         "a separate instruction due to initialize_with_values having too many arguments",
         "https://github.com/solana-labs/solana/issues/23978"
       ],
+      "discriminator": [
+        248,
+        190,
+        21,
+        97,
+        239,
+        148,
+        39,
+        181
+      ],
       "accounts": [
         {
           "name": "state",
-          "isMut": true,
-          "isSigner": true
+          "writable": true,
+          "signer": true
         },
         {
           "name": "payer",
-          "isMut": true,
-          "isSigner": true
+          "writable": true,
+          "signer": true
         },
         {
-          "name": "systemProgram",
-          "isMut": false,
-          "isSigner": false
+          "name": "system_program",
+          "address": "11111111111111111111111111111111"
         }
       ],
       "args": [
         {
-          "name": "vecOfOption",
+          "name": "vec_of_option",
           "type": {
             "vec": {
               "option": "u64"
@@ -329,214 +344,90 @@
           }
         },
         {
-          "name": "boxField",
+          "name": "box_field",
           "type": "bool"
         }
       ],
       "returns": {
-        "defined": "SomeRetStruct"
+        "defined": {
+          "name": "SomeRetStruct"
+        }
       }
-    },
-    {
-      "name": "causeError",
-      "accounts": [],
-      "args": []
     }
   ],
   "accounts": [
     {
       "name": "SomeZcAccount",
-      "type": {
-        "kind": "struct",
-        "fields": [
-          {
-            "name": "field",
-            "type": {
-              "defined": "ZcStruct"
-            }
-          }
-        ]
-      }
+      "discriminator": [
+        56,
+        72,
+        82,
+        194,
+        210,
+        35,
+        17,
+        191
+      ]
     },
     {
       "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"
-            }
-          }
-        ]
-      }
+      "discriminator": [
+        216,
+        146,
+        107,
+        94,
+        104,
+        75,
+        182,
+        177
+      ]
     },
     {
       "name": "State2",
-      "type": {
-        "kind": "struct",
-        "fields": [
-          {
-            "name": "vecOfOption",
-            "type": {
-              "vec": {
-                "option": "u64"
-              }
-            }
-          },
-          {
-            "name": "boxField",
-            "type": "bool"
-          }
-        ]
-      }
+      "discriminator": [
+        106,
+        97,
+        255,
+        161,
+        250,
+        205,
+        185,
+        192
+      ]
     }
   ],
-  "types": [
+  "events": [
     {
-      "name": "external::Baz",
-      "type": {
-        "kind": "struct",
-        "fields": [
-          {
-            "name": "someField",
-            "type": "u8"
-          }
-        ]
-      }
+      "name": "SomeEvent",
+      "discriminator": [
+        39,
+        221,
+        150,
+        148,
+        91,
+        206,
+        29,
+        93
+      ]
+    }
+  ],
+  "errors": [
+    {
+      "code": 6000,
+      "name": "SomeError",
+      "msg": "Example error."
     },
+    {
+      "code": 6001,
+      "name": "OtherError",
+      "msg": "Another error."
+    },
+    {
+      "code": 6002,
+      "name": "ErrorWithoutMsg"
+    }
+  ],
+  "types": [
     {
       "name": "BarStruct",
       "docs": [
@@ -546,14 +437,14 @@
         "kind": "struct",
         "fields": [
           {
-            "name": "someField",
+            "name": "some_field",
             "docs": [
               "Some field"
             ],
             "type": "bool"
           },
           {
-            "name": "otherField",
+            "name": "other_field",
             "type": "u8"
           }
         ]
@@ -573,7 +464,9 @@
               "bool",
               "u8",
               {
-                "defined": "BarStruct"
+                "defined": {
+                  "name": "BarStruct"
+                }
               }
             ]
           },
@@ -581,7 +474,9 @@
             "name": "UnnamedSingle",
             "fields": [
               {
-                "defined": "BarStruct"
+                "defined": {
+                  "name": "BarStruct"
+                }
               }
             ]
           },
@@ -589,20 +484,22 @@
             "name": "Named",
             "fields": [
               {
-                "name": "boolField",
+                "name": "bool_field",
                 "docs": [
                   "A bool field inside a struct tuple kind"
                 ],
                 "type": "bool"
               },
               {
-                "name": "u8Field",
+                "name": "u8_field",
                 "type": "u8"
               },
               {
                 "name": "nested",
                 "type": {
-                  "defined": "BarStruct"
+                  "defined": {
+                    "name": "BarStruct"
+                  }
                 }
               }
             ]
@@ -611,7 +508,9 @@
             "name": "Struct",
             "fields": [
               {
-                "defined": "BarStruct"
+                "defined": {
+                  "name": "BarStruct"
+                }
               }
             ]
           },
@@ -620,7 +519,9 @@
             "fields": [
               {
                 "option": {
-                  "defined": "BarStruct"
+                  "defined": {
+                    "name": "BarStruct"
+                  }
                 }
               }
             ]
@@ -630,7 +531,9 @@
             "fields": [
               {
                 "vec": {
-                  "defined": "BarStruct"
+                  "defined": {
+                    "name": "BarStruct"
+                  }
                 }
               }
             ]
@@ -657,29 +560,65 @@
           {
             "name": "nested",
             "type": {
-              "defined": "BarStruct"
+              "defined": {
+                "name": "BarStruct"
+              }
             }
           },
           {
-            "name": "vecNested",
+            "name": "vec_nested",
             "type": {
               "vec": {
-                "defined": "BarStruct"
+                "defined": {
+                  "name": "BarStruct"
+                }
               }
             }
           },
           {
-            "name": "optionNested",
+            "name": "option_nested",
             "type": {
               "option": {
-                "defined": "BarStruct"
+                "defined": {
+                  "name": "BarStruct"
+                }
+              }
+            }
+          },
+          {
+            "name": "enum_field",
+            "type": {
+              "defined": {
+                "name": "FooEnum"
+              }
+            }
+          }
+        ]
+      }
+    },
+    {
+      "name": "SomeEvent",
+      "type": {
+        "kind": "struct",
+        "fields": [
+          {
+            "name": "bool_field",
+            "type": "bool"
+          },
+          {
+            "name": "external_my_struct",
+            "type": {
+              "defined": {
+                "name": "external::MyStruct"
               }
             }
           },
           {
-            "name": "enumField",
+            "name": "other_module_my_struct",
             "type": {
-              "defined": "FooEnum"
+              "defined": {
+                "name": "idl::some_other_module::MyStruct"
+              }
             }
           }
         ]
@@ -691,77 +630,272 @@
         "kind": "struct",
         "fields": [
           {
-            "name": "someField",
+            "name": "some_field",
             "type": "u8"
           }
         ]
       }
     },
+    {
+      "name": "SomeZcAccount",
+      "serialization": "bytemuck",
+      "repr": {
+        "kind": "c"
+      },
+      "type": {
+        "kind": "struct",
+        "fields": [
+          {
+            "name": "field",
+            "type": {
+              "defined": {
+                "name": "ZcStruct"
+              }
+            }
+          }
+        ]
+      }
+    },
+    {
+      "name": "State",
+      "docs": [
+        "An account containing various fields"
+      ],
+      "type": {
+        "kind": "struct",
+        "fields": [
+          {
+            "name": "bool_field",
+            "docs": [
+              "A boolean field"
+            ],
+            "type": "bool"
+          },
+          {
+            "name": "u8_field",
+            "type": "u8"
+          },
+          {
+            "name": "i8_field",
+            "type": "i8"
+          },
+          {
+            "name": "u16_field",
+            "type": "u16"
+          },
+          {
+            "name": "i16_field",
+            "type": "i16"
+          },
+          {
+            "name": "u32_field",
+            "type": "u32"
+          },
+          {
+            "name": "i32_field",
+            "type": "i32"
+          },
+          {
+            "name": "f32_field",
+            "type": "f32"
+          },
+          {
+            "name": "u64_field",
+            "type": "u64"
+          },
+          {
+            "name": "i64_field",
+            "type": "i64"
+          },
+          {
+            "name": "f64_field",
+            "type": "f64"
+          },
+          {
+            "name": "u128_field",
+            "type": "u128"
+          },
+          {
+            "name": "i128_field",
+            "type": "i128"
+          },
+          {
+            "name": "bytes_field",
+            "type": "bytes"
+          },
+          {
+            "name": "string_field",
+            "type": "string"
+          },
+          {
+            "name": "pubkey_field",
+            "type": "pubkey"
+          },
+          {
+            "name": "vec_field",
+            "type": {
+              "vec": "u64"
+            }
+          },
+          {
+            "name": "vec_struct_field",
+            "type": {
+              "vec": {
+                "defined": {
+                  "name": "FooStruct"
+                }
+              }
+            }
+          },
+          {
+            "name": "option_field",
+            "type": {
+              "option": "bool"
+            }
+          },
+          {
+            "name": "option_struct_field",
+            "type": {
+              "option": {
+                "defined": {
+                  "name": "FooStruct"
+                }
+              }
+            }
+          },
+          {
+            "name": "struct_field",
+            "type": {
+              "defined": {
+                "name": "FooStruct"
+              }
+            }
+          },
+          {
+            "name": "array_field",
+            "type": {
+              "array": [
+                "bool",
+                3
+              ]
+            }
+          },
+          {
+            "name": "enum_field_1",
+            "type": {
+              "defined": {
+                "name": "FooEnum"
+              }
+            }
+          },
+          {
+            "name": "enum_field_2",
+            "type": {
+              "defined": {
+                "name": "FooEnum"
+              }
+            }
+          },
+          {
+            "name": "enum_field_3",
+            "type": {
+              "defined": {
+                "name": "FooEnum"
+              }
+            }
+          },
+          {
+            "name": "enum_field_4",
+            "type": {
+              "defined": {
+                "name": "FooEnum"
+              }
+            }
+          }
+        ]
+      }
+    },
+    {
+      "name": "State2",
+      "type": {
+        "kind": "struct",
+        "fields": [
+          {
+            "name": "vec_of_option",
+            "type": {
+              "vec": {
+                "option": "u64"
+              }
+            }
+          },
+          {
+            "name": "box_field",
+            "type": "bool"
+          }
+        ]
+      }
+    },
     {
       "name": "ZcStruct",
+      "serialization": "bytemuck",
+      "repr": {
+        "kind": "c"
+      },
       "type": {
         "kind": "struct",
         "fields": [
           {
-            "name": "someField",
+            "name": "some_field",
             "type": "u16"
           }
         ]
       }
     },
     {
-      "name": "idl::some_other_module::Baz",
+      "name": "external::MyStruct",
       "type": {
         "kind": "struct",
         "fields": [
           {
-            "name": "someU8",
+            "name": "some_field",
             "type": "u8"
           }
         ]
       }
-    }
-  ],
-  "events": [
+    },
     {
-      "name": "SomeEvent",
-      "fields": [
-        {
-          "name": "boolField",
-          "type": "bool",
-          "index": false
-        },
-        {
-          "name": "externalBaz",
-          "type": {
-            "defined": "external::Baz"
-          },
-          "index": false
-        },
-        {
-          "name": "otherModuleBaz",
-          "type": {
-            "defined": "idl::some_other_module::Baz"
-          },
-          "index": false
-        }
-      ]
+      "name": "idl::some_other_module::MyStruct",
+      "type": {
+        "kind": "struct",
+        "fields": [
+          {
+            "name": "some_u8",
+            "type": "u8"
+          }
+        ]
+      }
     }
   ],
-  "errors": [
+  "constants": [
     {
-      "code": 6000,
-      "name": "SomeError",
-      "msg": "Example error."
+      "name": "BYTES_STR",
+      "type": "bytes",
+      "value": "[116, 101, 115, 116]"
     },
     {
-      "code": 6001,
-      "name": "OtherError",
-      "msg": "Another error."
+      "name": "BYTE_STR",
+      "type": "u8",
+      "value": "116"
     },
     {
-      "code": 6002,
-      "name": "ErrorWithoutMsg"
+      "name": "I128",
+      "type": "i128",
+      "value": "1000000"
+    },
+    {
+      "name": "U8",
+      "type": "u8",
+      "value": "6"
     }
   ]
 }

+ 5 - 5
tests/idl/idls/parse.json → tests/idl/idls/old.json

@@ -526,7 +526,7 @@
   ],
   "types": [
     {
-      "name": "Baz",
+      "name": "MyStruct",
       "type": {
         "kind": "struct",
         "fields": [
@@ -720,16 +720,16 @@
           "index": false
         },
         {
-          "name": "externalBaz",
+          "name": "externalMyStruct",
           "type": {
-            "defined": "external::Baz"
+            "defined": "external::MyStruct"
           },
           "index": false
         },
         {
-          "name": "otherModuleBaz",
+          "name": "otherModuleMyStruct",
           "type": {
-            "defined": "some_other_module::Baz"
+            "defined": "some_other_module::MyStruct"
           },
           "index": false
         }

+ 151 - 0
tests/idl/idls/relations.json

@@ -0,0 +1,151 @@
+{
+  "address": "Re1ationsDerivation111111111111111111111111",
+  "metadata": {
+    "name": "relations_derivation",
+    "version": "0.1.0",
+    "spec": "0.1.0",
+    "description": "Created with Anchor"
+  },
+  "instructions": [
+    {
+      "name": "init_base",
+      "discriminator": [
+        85,
+        87,
+        185,
+        141,
+        241,
+        191,
+        213,
+        88
+      ],
+      "accounts": [
+        {
+          "name": "my_account",
+          "writable": true,
+          "signer": true
+        },
+        {
+          "name": "account",
+          "writable": true,
+          "pda": {
+            "seeds": [
+              {
+                "kind": "const",
+                "value": [
+                  115,
+                  101,
+                  101,
+                  100
+                ]
+              }
+            ]
+          }
+        },
+        {
+          "name": "system_program",
+          "address": "11111111111111111111111111111111"
+        }
+      ],
+      "args": []
+    },
+    {
+      "name": "test_relation",
+      "discriminator": [
+        247,
+        199,
+        255,
+        202,
+        7,
+        0,
+        197,
+        158
+      ],
+      "accounts": [
+        {
+          "name": "my_account",
+          "relations": [
+            "account"
+          ]
+        },
+        {
+          "name": "account",
+          "pda": {
+            "seeds": [
+              {
+                "kind": "const",
+                "value": [
+                  115,
+                  101,
+                  101,
+                  100
+                ]
+              }
+            ]
+          }
+        },
+        {
+          "name": "nested",
+          "accounts": [
+            {
+              "name": "my_account",
+              "relations": [
+                "account"
+              ]
+            },
+            {
+              "name": "account",
+              "pda": {
+                "seeds": [
+                  {
+                    "kind": "const",
+                    "value": [
+                      115,
+                      101,
+                      101,
+                      100
+                    ]
+                  }
+                ]
+              }
+            }
+          ]
+        }
+      ],
+      "args": []
+    }
+  ],
+  "accounts": [
+    {
+      "name": "MyAccount",
+      "discriminator": [
+        246,
+        28,
+        6,
+        87,
+        251,
+        45,
+        50,
+        42
+      ]
+    }
+  ],
+  "types": [
+    {
+      "name": "MyAccount",
+      "type": {
+        "kind": "struct",
+        "fields": [
+          {
+            "name": "my_account",
+            "type": "pubkey"
+          },
+          {
+            "name": "bump",
+            "type": "u8"
+          }
+        ]
+      }
+    }
+  ]
+}

+ 0 - 82
tests/idl/idls/relations_build.json

@@ -1,82 +0,0 @@
-{
-  "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",
-      "type": {
-        "kind": "struct",
-        "fields": [
-          {
-            "name": "myAccount",
-            "type": "publicKey"
-          },
-          {
-            "name": "bump",
-            "type": "u8"
-          }
-        ]
-      }
-    }
-  ]
-}

+ 0 - 19
tests/idl/programs/client-interactions/Cargo.toml

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

+ 0 - 131
tests/idl/programs/client-interactions/src/lib.rs

@@ -1,131 +0,0 @@
-use anchor_lang::prelude::*;
-
-declare_id!("C1ient1nteractions1111111111111111111111111");
-
-#[program]
-pub mod client_interactions {
-    use super::*;
-
-    pub fn int(ctx: Context<Int>, i8: i8, i16: i16, i32: i32, i64: i64, i128: i128) -> Result<()> {
-        ctx.accounts.account.i8 = i8;
-        ctx.accounts.account.i16 = i16;
-        ctx.accounts.account.i32 = i32;
-        ctx.accounts.account.i64 = i64;
-        ctx.accounts.account.i128 = i128;
-        Ok(())
-    }
-
-    pub fn uint(
-        ctx: Context<UnsignedInt>,
-        u8: u8,
-        u16: u16,
-        u32: u32,
-        u64: u64,
-        u128: u128,
-    ) -> Result<()> {
-        ctx.accounts.account.u8 = u8;
-        ctx.accounts.account.u16 = u16;
-        ctx.accounts.account.u32 = u32;
-        ctx.accounts.account.u64 = u64;
-        ctx.accounts.account.u128 = u128;
-        Ok(())
-    }
-
-    pub fn enm(ctx: Context<Enum>, enum_arg: MyEnum) -> Result<()> {
-        ctx.accounts.account.enum_field = enum_arg;
-        Ok(())
-    }
-
-    pub fn type_alias(
-        ctx: Context<TypeAlias>,
-        type_alias_u8: TypeAliasU8,
-        type_alias_u8_array: TypeAliasU8Array,
-        type_alias_struct: TypeAliasStruct,
-    ) -> Result<()> {
-        ctx.accounts.account.type_alias_u8 = type_alias_u8;
-        ctx.accounts.account.type_alias_u8_array = type_alias_u8_array;
-        ctx.accounts.account.type_alias_struct = type_alias_struct;
-        Ok(())
-    }
-}
-
-#[derive(Accounts)]
-pub struct Int<'info> {
-    #[account(zero)]
-    pub account: Account<'info, IntAccount>,
-}
-
-#[account]
-pub struct IntAccount {
-    pub i8: i8,
-    pub i16: i16,
-    pub i32: i32,
-    pub i64: i64,
-    pub i128: i128,
-}
-
-#[derive(Accounts)]
-pub struct UnsignedInt<'info> {
-    #[account(zero)]
-    pub account: Account<'info, UnsignedIntAccount>,
-}
-
-#[account]
-pub struct UnsignedIntAccount {
-    pub u8: u8,
-    pub u16: u16,
-    pub u32: u32,
-    pub u64: u64,
-    pub u128: u128,
-}
-
-#[derive(Accounts)]
-pub struct Enum<'info> {
-    #[account(zero)]
-    pub account: Account<'info, EnumAccount>,
-}
-
-#[account]
-pub struct EnumAccount {
-    pub enum_field: MyEnum,
-}
-
-#[derive(AnchorSerialize, AnchorDeserialize, Clone, Copy, Debug, Eq, PartialEq)]
-pub enum MyEnum {
-    Unit,
-    Named { point_x: u64, point_y: u64 },
-    Unnamed(u8, u8, u16, u16),
-    UnnamedStruct(MyStruct),
-}
-
-#[derive(AnchorSerialize, AnchorDeserialize, Clone, Copy, Debug, Eq, PartialEq)]
-pub struct MyStruct {
-    pub u8: u8,
-    pub u16: u16,
-    pub u32: u32,
-    pub u64: u64,
-}
-
-#[derive(Accounts)]
-pub struct TypeAlias<'info> {
-    #[account(zero)]
-    pub account: Account<'info, TypeAliasAccount>,
-}
-
-#[account]
-pub struct TypeAliasAccount {
-    pub type_alias_u8: TypeAliasU8,
-    pub type_alias_u8_array: TypeAliasU8Array,
-    pub type_alias_struct: TypeAliasStruct,
-}
-
-pub type TypeAliasU8 = u8;
-pub type TypeAliasU8Array = [TypeAliasU8; 8];
-pub type TypeAliasStruct = MyStruct;
-
-/// Generic type aliases should not get included in the IDL
-pub type TypeAliasNotSupported<'a, T> = NotSupported<'a, T>;
-pub struct NotSupported<'a, T> {
-    _t: T,
-    _s: &'a str,
-}

+ 1 - 0
tests/idl/programs/docs/Cargo.toml

@@ -14,6 +14,7 @@ no-entrypoint = []
 no-idl = []
 cpi = ["no-entrypoint"]
 default = []
+idl-build = ["anchor-lang/idl-build"]
 
 [dependencies]
 anchor-lang = { path = "../../../../lang" }

+ 13 - 2
tests/idl/programs/external/src/lib.rs

@@ -6,15 +6,26 @@ declare_id!("Externa1111111111111111111111111111111111111");
 pub mod external {
     use super::*;
 
-    pub fn initialize(_ctx: Context<Initialize>, _baz: Baz) -> Result<()> {
+    pub fn initialize(_ctx: Context<Initialize>) -> Result<()> {
         Ok(())
     }
 }
 
 #[derive(AnchorSerialize, AnchorDeserialize, Clone)]
-pub struct Baz {
+pub struct MyStruct {
     some_field: u8,
 }
 
+#[derive(AnchorSerialize, AnchorDeserialize, Clone)]
+pub enum MyEnum {
+    Unit,
+    Named { name: String },
+    Tuple(String),
+}
+
+pub struct NonBorshStruct {
+    pub data: i32,
+}
+
 #[derive(Accounts)]
 pub struct Initialize {}

Some files were not shown because too many files changed in this diff