diff --git a/CHANGELOG.md b/CHANGELOG.md index 4fd32ea99a..e477eb4ba1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,6 +37,7 @@ The minor version will be incremented upon a breaking change and the patch versi ### Fixes +- lang: Make idl build time way faster by caching `CrateContext` ([#4325](https://github.com/solana-foundation/anchor/pull/4325)). - lang: Add missing `Lazy` bound on generics ([#4240](https://github.com/solana-foundation/anchor/pull/4240)). - lang: Fix wrong generated error code in declare_program! ([#4129](https://github.com/solana-foundation/anchor/pull/4129)). - idl: Fix defined types with unsupported fields not producing an error ([#4088](https://github.com/solana-foundation/anchor/pull/4088)). diff --git a/lang/syn/src/idl/defined.rs b/lang/syn/src/idl/defined.rs index 607f2b00d2..86322c2b24 100644 --- a/lang/syn/src/idl/defined.rs +++ b/lang/syn/src/idl/defined.rs @@ -494,15 +494,64 @@ pub fn gen_idl_type( use super::{common::find_path, external::get_external_type}; use crate::parser::context::CrateContext; use quote::ToTokens; + use std::{ + collections::{HashMap, HashSet}, + sync::OnceLock, + }; + + struct CachedCrateData { + /// Names of all structs and enums defined in the crate + defined_names: HashSet, + /// Type aliases stored as (name, source_text) for re-parsing + type_aliases: HashMap, + } + + static CRATE_DATA_CACHE: OnceLock< + std::result::Result, + > = OnceLock::new(); // If no path was found, just return an empty path and let the find_path function handle it let source_path = proc_macro2::Span::call_site() .local_file() .unwrap_or_default(); - if let Ok(Ok(ctx)) = find_path("lib.rs", &source_path).map(CrateContext::parse) { + if let Ok(lib_path) = find_path("lib.rs", &source_path) { let name = path.path.segments.last().unwrap().ident.to_string(); - let alias = ctx.type_aliases().find(|ty| ty.ident == name); + + let cache = CRATE_DATA_CACHE.get_or_init(|| { + CrateContext::parse(&lib_path).map(|ctx| { + let defined_names: HashSet = ctx + .structs() + .map(|s| s.ident.to_string()) + .chain(ctx.enums().map(|e| e.ident.to_string())) + .collect(); + let type_aliases: HashMap = ctx + .type_aliases() + .map(|ty| (ty.ident.to_string(), ty.to_token_stream().to_string())) + .collect(); + CachedCrateData { + defined_names, + type_aliases, + } + }) + }); + + let cache = match cache { + Ok(data) => data, + Err(e) => { + return Err(syn::Error::new( + path.span(), + format!("Failed to parse crate: {e}"), + )); + } + }; + + let alias_src = cache.type_aliases.get(&name).cloned(); + let is_external = !cache.defined_names.contains(&name); + + let alias: Option = + alias_src.and_then(|src| syn::parse_str(&src).ok()); + if let Some(alias) = alias { if let Some(segment) = path.path.segments.last() { if let syn::PathArguments::AngleBracketed(args) = &segment.arguments { @@ -563,12 +612,6 @@ pub fn gen_idl_type( } // 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);