From e802f536902570c457489eac1d58cc389416135c Mon Sep 17 00:00:00 2001 From: Gabriele Picco Date: Sat, 9 Mar 2024 12:32:13 +0100 Subject: [PATCH] :sparkles: Dynamic types resolver (#24) * :sparkles: Dynamic types resolver * :recycle: Code Refactoring * :recycle: Code Refactor * :recycle: Use bolt_lang in macro definition --- Anchor.toml | 23 +-- Cargo.lock | 11 + cli/Cargo.toml | 4 +- cli/src/lib.rs | 195 +++++++++++++++++- cli/src/rust_template.rs | 187 ++++++++++++++++- .../component-deserialize/src/lib.rs | 33 ++- .../attribute/system-input/src/lib.rs | 73 +++---- crates/bolt-lang/src/lib.rs | 4 + crates/types/Cargo.toml | 13 ++ ...zEdyb55fsyduWS94mYHizGhJZuhvjX6DVvrmGbQ.rs | 11 + crates/types/src/lib.rs | 3 + examples/system-simple-movement/Cargo.toml | 1 + examples/system-simple-movement/src/lib.rs | 10 +- 13 files changed, 492 insertions(+), 76 deletions(-) create mode 100644 crates/types/Cargo.toml create mode 100644 crates/types/src/component_Fn1JzzEdyb55fsyduWS94mYHizGhJZuhvjX6DVvrmGbQ.rs create mode 100644 crates/types/src/lib.rs diff --git a/Anchor.toml b/Anchor.toml index 652bad3a..85d29335 100644 --- a/Anchor.toml +++ b/Anchor.toml @@ -4,17 +4,17 @@ seeds = true skip-lint = false +[programs.devnet] +world = "WorLD15A7CrDwLcLy4fRqtaTb9fbd8o8iqiEMUDse2n" + [programs.localnet] bolt-component = "CmP2djJgABZ4cRokm4ndxuq6LerqpNHLBsaUv2XKEJua" bolt-system = "7X4EFsDJ5aYTcEjKzJ94rD8FRKgQeXC89fkpeTS4KaqP" position = "Fn1JzzEdyb55fsyduWS94mYHizGhJZuhvjX6DVvrmGbQ" -velocity = "CbHEFbSQdRN4Wnoby9r16umnJ1zWbULBHg4yqzGQonU1" system-apply-velocity = "6LHhFVwif6N9Po3jHtSmMVtPjF6zRfL3xMosSzcrQAS8" system-fly = "HT2YawJjkNmqWcLNfPAMvNsLdWwPvvvbKA5bpMw4eUpq" system-simple-movement = "FSa6qoJXFBR3a7ThQkTAMrC15p6NkchPEjBdd4n6dXxA" -world = "WorLD15A7CrDwLcLy4fRqtaTb9fbd8o8iqiEMUDse2n" - -[programs.devnet] +velocity = "CbHEFbSQdRN4Wnoby9r16umnJ1zWbULBHg4yqzGQonU1" world = "WorLD15A7CrDwLcLy4fRqtaTb9fbd8o8iqiEMUDse2n" [registry] @@ -24,17 +24,8 @@ url = "https://api.apr.dev" cluster = "localnet" wallet = "./tests/fixtures/provider.json" +[workspace] +members = ["programs/bolt-component", "programs/bolt-system", "programs/world", "examples/component-position", "examples/component-velocity", "examples/system-apply-velocity", "examples/system-fly", "examples/system-simple-movement"] + [scripts] test = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/bolt.ts" - -[workspace] -members = [ - "programs/bolt-component", - "programs/bolt-system", - "programs/world", - "examples/component-position", - "examples/component-velocity", - "examples/system-apply-velocity", - "examples/system-fly", - "examples/system-simple-movement", -] diff --git a/Cargo.lock b/Cargo.lock index 734b8ef5..aa8905f4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -970,9 +970,11 @@ version = "0.1.0" dependencies = [ "anchor-cli", "anchor-client", + "anchor-syn 0.29.0 (git+https://github.com/coral-xyz/anchor.git)", "anyhow", "clap 4.4.11", "heck 0.4.1", + "serde_json", "syn 1.0.109", ] @@ -1032,6 +1034,14 @@ dependencies = [ "bolt-helpers-system-template", ] +[[package]] +name = "bolt-types" +version = "0.1.0" +dependencies = [ + "anchor-lang 0.29.0 (registry+https://github.com/rust-lang/crates.io-index)", + "bolt-lang", +] + [[package]] name = "bolt-utils" version = "0.1.0" @@ -5068,6 +5078,7 @@ version = "0.1.0" dependencies = [ "anchor-lang 0.29.0 (registry+https://github.com/rust-lang/crates.io-index)", "bolt-lang", + "bolt-types", "serde", ] diff --git a/cli/Cargo.toml b/cli/Cargo.toml index dbdc401a..42c0c95a 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -23,7 +23,9 @@ dev = [] [dependencies] anchor-cli = { git = "https://github.com/coral-xyz/anchor.git" } anchor-client = { git = "https://github.com/coral-xyz/anchor.git" } +anchor-syn = { git = "https://github.com/coral-xyz/anchor.git" } anyhow = { workspace = true } +serde_json = { workspace = true } heck = { workspace = true } clap = { workspace = true } -syn = { workspace = true, features = ["full", "extra-traits"] } +syn = { workspace = true, features = ["full", "extra-traits"] } \ No newline at end of file diff --git a/cli/src/lib.rs b/cli/src/lib.rs index 3adec672..0cf1c3a0 100644 --- a/cli/src/lib.rs +++ b/cli/src/lib.rs @@ -1,17 +1,21 @@ mod rust_template; use crate::rust_template::{create_component, create_system}; +use anchor_cli::config; use anchor_cli::config::{ BootstrapMode, Config, ConfigOverride, GenesisEntry, ProgramArch, ProgramDeployment, TestValidator, Validator, WithPath, }; use anchor_client::Cluster; +use anchor_syn::idl::types::Idl; use anyhow::{anyhow, Result}; use clap::{Parser, Subcommand}; use heck::{ToKebabCase, ToSnakeCase}; use std::collections::BTreeMap; -use std::fs::{self, File}; +use std::fs::{self, create_dir_all, File, OpenOptions}; use std::io::Write; +use std::io::{self, BufRead}; +use std::path::{Path, PathBuf}; use std::process::Stdio; pub const VERSION: &str = env!("CARGO_PKG_VERSION"); @@ -49,6 +53,9 @@ pub struct SystemCommand { #[derive(Debug, Parser)] #[clap(version = VERSION)] pub struct Opts { + /// Rebuild the auto-generated types + #[clap(global = true, long, action)] + pub rebuild_types: bool, #[clap(flatten)] pub cfg_override: ConfigOverride, #[clap(subcommand)] @@ -105,6 +112,7 @@ pub fn entry(opts: Opts) -> Result<()> { cargo_args, no_docs, arch, + opts.rebuild_types, ), _ => { let opts = anchor_cli::Opts { @@ -415,7 +423,23 @@ pub fn build( cargo_args: Vec, no_docs: bool, arch: ProgramArch, + rebuild_types: bool, ) -> Result<()> { + let cfg = Config::discover(cfg_override)?.expect("Not in workspace."); + let types_path = "crates/types/src"; + + // If rebuild_types is true and the types directory exists, remove it + if rebuild_types && Path::new(types_path).exists() { + fs::remove_dir_all( + PathBuf::from(types_path) + .parent() + .ok_or_else(|| anyhow::format_err!("Failed to remove types directory"))?, + )?; + } + create_dir_all(types_path)?; + build_dynamic_types(cfg, cfg_override, types_path)?; + + // Build the programs anchor_cli::build( cfg_override, idl, @@ -472,9 +496,9 @@ fn new_component(cfg_override: &ConfigOverride, name: String) -> Result<()> { programs.insert( name.clone(), - anchor_cli::config::ProgramDeployment { + ProgramDeployment { address: { - rust_template::create_component(&name)?; + create_component(&name)?; anchor_cli::rust_template::get_or_create_program_id(&name) }, path: None, @@ -581,3 +605,168 @@ fn set_workspace_dir_or_exit() { } } } + +fn discover_cluster_url(cfg_override: &ConfigOverride) -> Result { + let url = match Config::discover(cfg_override)? { + Some(cfg) => cluster_url(&cfg, &cfg.test_validator), + None => { + if let Some(cluster) = cfg_override.cluster.clone() { + cluster.url().to_string() + } else { + config::get_solana_cfg_url()? + } + } + }; + Ok(url) +} + +fn cluster_url(cfg: &Config, test_validator: &Option) -> String { + let is_localnet = cfg.provider.cluster == Cluster::Localnet; + match is_localnet { + // Cluster is Localnet, assume the intent is to use the configuration + // for solana-test-validator + true => test_validator_rpc_url(test_validator), + false => cfg.provider.cluster.url().to_string(), + } +} + +// Return the URL that solana-test-validator should be running on given the +// configuration +fn test_validator_rpc_url(test_validator: &Option) -> String { + match test_validator { + Some(TestValidator { + validator: Some(validator), + .. + }) => format!("http://{}:{}", validator.bind_address, validator.rpc_port), + _ => "http://127.0.0.1:8899".to_string(), + } +} + +fn build_dynamic_types( + cfg: WithPath, + cfg_override: &ConfigOverride, + types_path: &str, +) -> Result<()> { + let cur_dir = std::env::current_dir()?; + for p in cfg.get_rust_program_list()? { + process_program_path(&p, cfg_override, types_path)?; + } + let types_path = PathBuf::from(types_path); + let cargo_path = types_path + .parent() + .unwrap_or(&types_path) + .join("Cargo.toml"); + if !cargo_path.exists() { + let mut file = File::create(cargo_path)?; + file.write_all(rust_template::types_cargo_toml().as_bytes())?; + } + std::env::set_current_dir(cur_dir)?; + Ok(()) +} + +fn process_program_path( + program_path: &Path, + cfg_override: &ConfigOverride, + types_path: &str, +) -> Result<()> { + let lib_rs_path = Path::new(types_path).join("lib.rs"); + let file = File::open(program_path.join("src").join("lib.rs"))?; + let lines = io::BufReader::new(file).lines(); + let mut contains_dynamic_components = false; + for line in lines.map_while(Result::ok) { + if let Some(component_id) = extract_component_id(&line) { + let file_path = PathBuf::from(format!("{}/component_{}.rs", types_path, component_id)); + if !file_path.exists() { + println!("Generating type for Component: {}", component_id); + generate_component_type_file(&file_path, cfg_override, component_id)?; + append_component_to_lib_rs(&lib_rs_path, component_id)?; + } + contains_dynamic_components = true; + } + } + if contains_dynamic_components { + let program_name = program_path.file_name().unwrap().to_str().unwrap(); + add_types_crate_dependency(program_name, &types_path.replace("/src", ""))?; + } + + Ok(()) +} + +fn add_types_crate_dependency(program_name: &str, types_path: &str) -> Result<()> { + std::process::Command::new("cargo") + .arg("add") + .arg("--package") + .arg(program_name) + .arg("--path") + .arg(types_path) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .spawn() + .map_err(|e| { + anyhow::format_err!( + "error adding types as dependency to the program: {}", + e.to_string() + ) + })?; + Ok(()) +} + +fn extract_component_id(line: &str) -> Option<&str> { + let component_id_marker = "#[component_id("; + line.find(component_id_marker).map(|start| { + let start = start + component_id_marker.len(); + let end = line[start..].find(')').unwrap() + start; + line[start..end].trim_matches('"') + }) +} + +fn fetch_idl_for_component(component_id: &str, url: &str) -> Result { + let output = std::process::Command::new("bolt") + .arg("idl") + .arg("fetch") + .arg(component_id) + .arg("--provider.cluster") + .arg(url) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .output()?; + + if output.status.success() { + let idl_string = String::from_utf8(output.stdout) + .map_err(|e| anyhow!("Failed to decode IDL output as UTF-8: {}", e))? + .to_string(); + Ok(idl_string) + } else { + let error_message = String::from_utf8(output.stderr) + .unwrap_or(format!( + "Error trying to dynamically generate the type \ + for component {}, unable to fetch the idl. \nEnsure that the idl is available. Specify \ + the appropriate cluster using the --provider.cluster option", + component_id + )) + .to_string(); + Err(anyhow!("Command failed with error: {}", error_message)) + } +} + +fn generate_component_type_file( + file_path: &Path, + cfg_override: &ConfigOverride, + component_id: &str, +) -> Result<()> { + let url = discover_cluster_url(cfg_override)?; + let idl_string = fetch_idl_for_component(component_id, &url)?; + let idl: Idl = serde_json::from_str(&idl_string)?; + let mut file = File::create(file_path)?; + file.write_all(rust_template::component_type(&idl, component_id)?.as_bytes())?; + Ok(()) +} + +fn append_component_to_lib_rs(lib_rs_path: &Path, component_id: &str) -> Result<()> { + let mut file = OpenOptions::new() + .create(true) + .append(true) + .open(lib_rs_path)?; + file.write_all(rust_template::component_type_import(component_id).as_bytes())?; + Ok(()) +} diff --git a/cli/src/rust_template.rs b/cli/src/rust_template.rs index cc1dab35..e11ebec8 100644 --- a/cli/src/rust_template.rs +++ b/cli/src/rust_template.rs @@ -1,6 +1,9 @@ use crate::ANCHOR_VERSION; use crate::VERSION; use anchor_cli::Files; +use anchor_syn::idl::types::{ + Idl, IdlDefinedTypeArg, IdlField, IdlType, IdlTypeDefinition, IdlTypeDefinitionTy, +}; use anyhow::Result; use heck::{ToSnakeCase, ToUpperCamelCase}; use std::path::{Path, PathBuf}; @@ -404,7 +407,7 @@ fn cargo_toml(name: &str) -> String { format!( r#"[package] name = "{0}" -version = "0.0.1" +version = "{2}" description = "Created with Bolt" edition = "2021" @@ -480,3 +483,185 @@ pub fn registry_account() -> &'static str { } "# } + +/// Automatic generation of crates from the components idl + +pub fn component_type(idl: &Idl, component_id: &str) -> Result { + let component_account = &idl.accounts[0]; + let component_code = component_to_rust_code(component_account, component_id); + let types_code = component_types_to_rust_code(&idl.types); + Ok(format!( + r#"use bolt_lang::*; + +#[component_deserialize] +#[derive(Clone, Copy)] +{} + +{} +"#, + component_code, types_code + )) +} + +/// Convert the component type definition to rust code +fn component_to_rust_code(component: &IdlTypeDefinition, component_id: &str) -> String { + let mut code = String::new(); + // Add documentation comments, if any + if let Some(docs) = &component.docs { + for doc in docs { + code += &format!("/// {}\n", doc); + } + } + // Handle generics + let generics = if let Some(gen) = &component.generics { + format!("<{}>", gen.join(", ")) + } else { + String::new() + }; + let composite_name = format!("Component{}", component_id); + if let IdlTypeDefinitionTy::Struct { fields } = &component.ty { + code += &format!("pub struct {}{} {{\n", composite_name, generics); + code += &*component_fields_to_rust_code(fields); + code += "}\n\n"; + code += &format!("pub use {} as {};", composite_name, component.name); + } + code +} + +/// Code to expose the generated type, to be added to lib.rs +pub fn component_type_import(component_id: &str) -> String { + format!( + r#"#[allow(non_snake_case)] +mod component_{0}; +pub use component_{0}::*; +"#, + component_id, + ) +} + +/// Convert fields to rust code +fn component_fields_to_rust_code(fields: &[IdlField]) -> String { + let mut code = String::new(); + for field in fields { + // Skip BoltMetadata field, as it is added automatically + if field.name.to_lowercase() == "boltmetadata" { + continue; + } + if let Some(docs) = &field.docs { + for doc in docs { + code += &format!(" /// {}\n", doc); + } + } + let field_type = idl_type_to_rust_type(&field.ty); + code += &format!(" pub {}: {},\n", field.name, field_type); + } + code +} + +/// Map Idl type to rust type +fn idl_type_to_rust_type(idl_type: &IdlType) -> String { + match idl_type { + IdlType::Bool => "bool".to_string(), + IdlType::U8 => "u8".to_string(), + IdlType::I8 => "i8".to_string(), + IdlType::U16 => "u16".to_string(), + IdlType::I16 => "i16".to_string(), + IdlType::U32 => "u32".to_string(), + IdlType::I32 => "i32".to_string(), + IdlType::F32 => "f32".to_string(), + IdlType::U64 => "u64".to_string(), + IdlType::I64 => "i64".to_string(), + IdlType::F64 => "f64".to_string(), + IdlType::U128 => "u128".to_string(), + IdlType::I128 => "i128".to_string(), + IdlType::U256 => "U256".to_string(), + IdlType::I256 => "I256".to_string(), + IdlType::Bytes => "Vec".to_string(), + IdlType::String => "String".to_string(), + IdlType::PublicKey => "PublicKey".to_string(), + IdlType::Defined(name) => name.clone(), + IdlType::Option(ty) => format!("Option<{}>", idl_type_to_rust_type(ty)), + IdlType::Vec(ty) => format!("Vec<{}>", idl_type_to_rust_type(ty)), + IdlType::Array(ty, size) => format!("[{}; {}]", idl_type_to_rust_type(ty), size), + IdlType::GenericLenArray(ty, _) => format!("Vec<{}>", idl_type_to_rust_type(ty)), + IdlType::Generic(name) => name.clone(), + IdlType::DefinedWithTypeArgs { name, args } => { + let args_str = args + .iter() + .map(idl_defined_type_arg_to_rust_type) + .collect::>() + .join(", "); + format!("{}<{}>", name, args_str) + } + } +} + +/// Map type args +fn idl_defined_type_arg_to_rust_type(arg: &IdlDefinedTypeArg) -> String { + match arg { + IdlDefinedTypeArg::Generic(name) => name.clone(), + IdlDefinedTypeArg::Value(value) => value.clone(), + IdlDefinedTypeArg::Type(ty) => idl_type_to_rust_type(ty), + } +} + +/// Convert the component types definition to rust code +fn component_types_to_rust_code(types: &[IdlTypeDefinition]) -> String { + types + .iter() + // Skip BoltMetadata type, as it is added automatically + .filter(|ty| ty.name.to_lowercase() != "boltmetadata") + .map(component_type_to_rust_code) + .collect::>() + .join("\n") +} + +/// Convert the component type definition to rust code +fn component_type_to_rust_code(component_type: &IdlTypeDefinition) -> String { + let mut code = String::new(); + // Add documentation comments, if any + if let Some(docs) = &component_type.docs { + for doc in docs { + code += &format!("/// {}\n", doc); + } + } + // Handle generics + let generics = if let Some(gen) = &component_type.generics { + format!("<{}>", gen.join(", ")) + } else { + String::new() + }; + if let IdlTypeDefinitionTy::Struct { fields } = &component_type.ty { + code += &format!( + "#[component_deserialize]\n#[derive(Clone, Copy)]\npub struct {}{} {{\n", + component_type.name, generics + ); + code += &*component_fields_to_rust_code(fields); + code += "}\n\n"; + } + code +} + +pub(crate) fn types_cargo_toml() -> String { + let name = "bolt-types"; + format!( + r#"[package] +name = "{0}" +version = "{2}" +description = "Autogenerate types for the bolt language" +edition = "2021" + +[lib] +crate-type = ["cdylib", "lib"] +name = "{1}" + +[dependencies] +bolt-lang = "{2}" +anchor-lang = "{3}" +"#, + name, + name.to_snake_case(), + VERSION, + anchor_cli::VERSION, + ) +} diff --git a/crates/bolt-lang/attribute/component-deserialize/src/lib.rs b/crates/bolt-lang/attribute/component-deserialize/src/lib.rs index 87a3aba1..2d07977a 100644 --- a/crates/bolt-lang/attribute/component-deserialize/src/lib.rs +++ b/crates/bolt-lang/attribute/component-deserialize/src/lib.rs @@ -18,32 +18,51 @@ pub fn component_deserialize(_attr: TokenStream, item: TokenStream) -> TokenStre let mut input = parse_macro_input!(item as DeriveInput); // Add the AnchorDeserialize and AnchorSerialize derives to the struct - let additional_derives: Attribute = syn::parse_quote! { #[derive(anchor_lang::AnchorDeserialize, anchor_lang::AnchorSerialize)] }; + let additional_derives: Attribute = + syn::parse_quote! { #[derive(bolt_lang::AnchorDeserialize, bolt_lang::AnchorSerialize)] }; input.attrs.push(additional_derives); add_bolt_metadata(&mut input); let name = &input.ident; + // Assume that the component_id is the same as the struct name, minus the "Component" prefix + let name_str = name.to_string(); + let component_id = name_str.strip_prefix("Component").unwrap(); let expanded = quote! { #input #[automatically_derived] impl bolt_lang::ComponentDeserialize for #name{ - fn from_account_info(account: &anchor_lang::prelude::AccountInfo) -> anchor_lang::Result<#name> { + fn from_account_info(account: &bolt_lang::AccountInfo) -> bolt_lang::Result<#name> { #name::try_deserialize_unchecked(&mut &*(*account.data.borrow()).as_ref()).map_err(Into::into) } } #[automatically_derived] - impl anchor_lang::AccountDeserialize for #name { - fn try_deserialize(buf: &mut &[u8]) -> anchor_lang::Result { + impl bolt_lang::AccountDeserialize for #name { + fn try_deserialize(buf: &mut &[u8]) -> bolt_lang::Result { Self::try_deserialize_unchecked(buf) } - fn try_deserialize_unchecked(buf: &mut &[u8]) -> anchor_lang::Result { + fn try_deserialize_unchecked(buf: &mut &[u8]) -> bolt_lang::Result { let mut data: &[u8] = &buf[8..]; - anchor_lang::AnchorDeserialize::deserialize(&mut data) - .map_err(|_| anchor_lang::error::ErrorCode::AccountDidNotDeserialize.into()) + bolt_lang::AnchorDeserialize::deserialize(&mut data) + .map_err(|_| bolt_lang::AccountDidNotDeserializeErrorCode.into()) + } + } + + #[automatically_derived] + impl bolt_lang::AccountSerialize for #name { + fn try_serialize(&self, _writer: &mut W) -> Result<()> { + Ok(()) + } + } + + use std::str::FromStr; + #[automatically_derived] + impl Owner for #name { + fn owner() -> Pubkey { + Pubkey::from_str(#component_id).unwrap() } } }; diff --git a/crates/bolt-lang/attribute/system-input/src/lib.rs b/crates/bolt-lang/attribute/system-input/src/lib.rs index bd1a7ac6..cb65d948 100644 --- a/crates/bolt-lang/attribute/system-input/src/lib.rs +++ b/crates/bolt-lang/attribute/system-input/src/lib.rs @@ -1,6 +1,7 @@ use proc_macro::TokenStream; + use quote::quote; -use syn::{parse_macro_input, Fields, ItemStruct, Lit, Meta, MetaNameValue}; +use syn::{parse_macro_input, Fields, ItemStruct, Lit, Meta, NestedMeta}; /// This macro attribute is used to define a BOLT system input. /// @@ -21,50 +22,44 @@ pub fn system_input(_attr: TokenStream, item: TokenStream) -> TokenStream { let input = parse_macro_input!(item as ItemStruct); // Ensure the struct has named fields - let fields = if let Fields::Named(fields) = &input.fields { - &fields.named - } else { - panic!("system_input macro only supports structs with named fields"); + let fields = match &input.fields { + Fields::Named(fields) => &fields.named, + _ => panic!("system_input macro only supports structs with named fields"), }; let name = &input.ident; - // Impls Owner for each account and - let owners_impls = fields.iter().filter_map(|field| { - field.attrs.iter().find_map(|attr| { - if let Ok(Meta::List(meta_list)) = attr.parse_meta() { - if meta_list.path.is_ident("component_id") { - for nested_meta in meta_list.nested.iter() { - if let syn::NestedMeta::Meta(Meta::NameValue(MetaNameValue { - path, - lit: Lit::Str(lit_str), - .. - })) = nested_meta - { - if path.is_ident("address") { - let address = lit_str.value(); - let field_type = &field.ty; - return Some(quote! { - use std::str::FromStr; - impl Owner for #field_type { - - fn owner() -> Pubkey { - Pubkey::from_str(#address).unwrap() - } - } - impl AccountSerialize for #field_type { - fn try_serialize(&self, _writer: &mut W) -> Result<()> { - Ok(()) - } - } - }); + // Collect imports for components + let components_imports: Vec<_> = fields + .iter() + .filter_map(|field| { + field.attrs.iter().find_map(|attr| { + if let Ok(Meta::List(meta_list)) = attr.parse_meta() { + if meta_list.path.is_ident("component_id") { + meta_list.nested.first().and_then(|nested_meta| { + if let NestedMeta::Lit(Lit::Str(lit_str)) = nested_meta { + let component_type = + format!("bolt_types::Component{}", lit_str.value()); + if let Ok(parsed_component_type) = + syn::parse_str::(&component_type) + { + let field_type = &field.ty; + let component_import = quote! { + use #parsed_component_type as #field_type; + }; + return Some(component_import); + } } - } + None + }) + } else { + None } + } else { + None } - } - None + }) }) - }); + .collect(); // Transform fields for the struct definition let transformed_fields = fields.iter().map(|f| { @@ -114,7 +109,7 @@ pub fn system_input(_attr: TokenStream, item: TokenStream) -> TokenStream { let output = quote! { #output_struct #output_impl - #(#owners_impls)* + #(#components_imports)* }; TokenStream::from(output) diff --git a/crates/bolt-lang/src/lib.rs b/crates/bolt-lang/src/lib.rs index bb658d62..2783036c 100644 --- a/crates/bolt-lang/src/lib.rs +++ b/crates/bolt-lang/src/lib.rs @@ -1,4 +1,8 @@ +pub use anchor_lang::error::ErrorCode::AccountDidNotDeserialize as AccountDidNotDeserializeErrorCode; pub use anchor_lang::prelude::*; +pub use anchor_lang::{ + AccountDeserialize, AccountSerialize, AnchorDeserialize, AnchorSerialize, Result, +}; pub use bolt_attribute_bolt_component::component; pub use bolt_attribute_bolt_component_deserialize::component_deserialize; diff --git a/crates/types/Cargo.toml b/crates/types/Cargo.toml new file mode 100644 index 00000000..942513fb --- /dev/null +++ b/crates/types/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "bolt-types" +version = "0.1.0" +description = "Autogenerate types for the bolt language" +edition = "2021" + +[lib] +crate-type = ["cdylib", "lib"] +name = "bolt_types" + +[dependencies] +bolt-lang = { path = "../bolt-lang" } +anchor-lang = "0.29.0" diff --git a/crates/types/src/component_Fn1JzzEdyb55fsyduWS94mYHizGhJZuhvjX6DVvrmGbQ.rs b/crates/types/src/component_Fn1JzzEdyb55fsyduWS94mYHizGhJZuhvjX6DVvrmGbQ.rs new file mode 100644 index 00000000..f317cf8d --- /dev/null +++ b/crates/types/src/component_Fn1JzzEdyb55fsyduWS94mYHizGhJZuhvjX6DVvrmGbQ.rs @@ -0,0 +1,11 @@ +use bolt_lang::*; + +#[component_deserialize] +#[derive(Clone, Copy)] +pub struct ComponentFn1JzzEdyb55fsyduWS94mYHizGhJZuhvjX6DVvrmGbQ { + pub x: i64, + pub y: i64, + pub z: i64, +} + +pub use ComponentFn1JzzEdyb55fsyduWS94mYHizGhJZuhvjX6DVvrmGbQ as Position; diff --git a/crates/types/src/lib.rs b/crates/types/src/lib.rs new file mode 100644 index 00000000..df912cd3 --- /dev/null +++ b/crates/types/src/lib.rs @@ -0,0 +1,3 @@ +#[allow(non_snake_case)] +mod component_Fn1JzzEdyb55fsyduWS94mYHizGhJZuhvjX6DVvrmGbQ; +pub use component_Fn1JzzEdyb55fsyduWS94mYHizGhJZuhvjX6DVvrmGbQ::*; diff --git a/examples/system-simple-movement/Cargo.toml b/examples/system-simple-movement/Cargo.toml index 0b89037a..6668845e 100644 --- a/examples/system-simple-movement/Cargo.toml +++ b/examples/system-simple-movement/Cargo.toml @@ -23,4 +23,5 @@ idl-build = ["anchor-lang/idl-build"] [dependencies] anchor-lang = { workspace = true } bolt-lang = { path = "../../crates/bolt-lang" } +bolt-types = { version = "0.1.0", path = "../../crates/types" } serde = { version = "*", features = ["derive"] } diff --git a/examples/system-simple-movement/src/lib.rs b/examples/system-simple-movement/src/lib.rs index e33277a1..bbc4ceac 100644 --- a/examples/system-simple-movement/src/lib.rs +++ b/examples/system-simple-movement/src/lib.rs @@ -23,18 +23,10 @@ pub mod system_simple_movement { #[system_input] pub struct Components { - #[component_id(address = "Fn1JzzEdyb55fsyduWS94mYHizGhJZuhvjX6DVvrmGbQ")] + #[component_id("Fn1JzzEdyb55fsyduWS94mYHizGhJZuhvjX6DVvrmGbQ")] pub position: Position, } - #[component_deserialize] - #[derive(Clone)] - pub struct Position { - pub x: i64, - pub y: i64, - pub z: i64, - } - // Define the structs to deserialize the arguments #[derive(BoltSerialize, BoltDeserialize)] struct Args {