From 3c2adc18f52a11dae40175c25d0019f5945ee222 Mon Sep 17 00:00:00 2001 From: Danilo Guanabara Date: Wed, 24 Sep 2025 13:48:51 -0300 Subject: [PATCH 1/2] :sparkles: Implicit execute lifetimes --- crates/bolt-lang/attribute/system/src/lib.rs | 80 ++++++++++++++++++++ examples/escrow-funding/src/lib.rs | 5 +- examples/system-simple-movement/Cargo.toml | 2 +- 3 files changed, 82 insertions(+), 5 deletions(-) diff --git a/crates/bolt-lang/attribute/system/src/lib.rs b/crates/bolt-lang/attribute/system/src/lib.rs index 32d2c128..4deb7948 100644 --- a/crates/bolt-lang/attribute/system/src/lib.rs +++ b/crates/bolt-lang/attribute/system/src/lib.rs @@ -123,6 +123,8 @@ impl VisitMut for SystemTransform { // Modify the return type of the system function to Result,*> fn visit_item_fn_mut(&mut self, item_fn: &mut ItemFn) { if item_fn.sig.ident == "execute" { + // Ensure execute has lifetimes and a fully-qualified Context + Self::inject_lifetimes_and_context(item_fn); // Modify the return type to Result> if necessary if let ReturnType::Type(_, type_box) = &item_fn.sig.output { if let Type::Path(type_path) = &**type_box { @@ -186,6 +188,84 @@ impl VisitMut for SystemTransform { } impl SystemTransform { + fn inject_lifetimes_and_context(item_fn: &mut ItemFn) { + // Add lifetimes <'a, 'b, 'c, 'info> if missing + let lifetime_idents = ["a", "b", "c", "info"]; + for name in lifetime_idents.iter() { + let exists = item_fn + .sig + .generics + .params + .iter() + .any(|p| match p { + syn::GenericParam::Lifetime(l) => l.lifetime.ident == *name, + _ => false, + }); + if !exists { + let lifetime: syn::Lifetime = syn::parse_str(&format!("'{}", name)).expect("valid lifetime"); + let gp: syn::GenericParam = syn::parse_quote!(#lifetime); + item_fn.sig.generics.params.push(gp); + } + } + + // Update the first argument type from Context to Context<'a, 'b, 'c, 'info, Components<'info>> + if let Some(first_arg) = item_fn.sig.inputs.first_mut() { + if let FnArg::Typed(pat_type) = first_arg { + if let Type::Path(type_path) = pat_type.ty.as_mut() { + if let Some(last_segment) = type_path.path.segments.last_mut() { + if last_segment.ident == "Context" { + // Extract Components path from existing generic args (if any) + let mut components_ty_opt: Option = None; + if let PathArguments::AngleBracketed(args) = &last_segment.arguments { + for ga in args.args.iter() { + if let GenericArgument::Type(t) = ga { + components_ty_opt = Some(t.clone()); + break; + } + } + } + + // If not found, leave early + if let Some(components_ty) = components_ty_opt { + // Ensure Components<'info> + let components_with_info: Type = match components_ty { + Type::Path(mut tp) => { + let seg = tp.path.segments.last_mut().unwrap(); + match &mut seg.arguments { + PathArguments::AngleBracketed(ab) => { + if ab.args.is_empty() { + ab.args.push(GenericArgument::Lifetime(syn::parse_quote!('info))); + } + } + _ => { + seg.arguments = PathArguments::AngleBracketed( + syn::AngleBracketedGenericArguments { + colon2_token: None, + lt_token: Default::default(), + args: std::iter::once(GenericArgument::Lifetime(syn::parse_quote!('info))) + .collect(), + gt_token: Default::default(), + }, + ); + } + } + Type::Path(tp) + } + other => other, + }; + + // Build new Context<'a, 'b, 'c, 'info, Components<'info>> type + let new_ty: Type = syn::parse_quote! { + Context<'a, 'b, 'c, 'info, #components_with_info> + }; + pat_type.ty = Box::new(new_ty); + } + } + } + } + } + } + } fn add_variadic_execute_function(content: &mut Vec) { content.push(syn::parse2(quote! { pub fn bolt_execute<'a, 'b, 'info>(ctx: Context<'a, 'b, 'info, 'info, VariadicBoltComponents<'info>>, args: Vec) -> Result>> { diff --git a/examples/escrow-funding/src/lib.rs b/examples/escrow-funding/src/lib.rs index ea8c6de1..d5f8bdb3 100644 --- a/examples/escrow-funding/src/lib.rs +++ b/examples/escrow-funding/src/lib.rs @@ -6,10 +6,7 @@ declare_id!("4Um2d8SvyfWyLLtfu2iJMFhM77DdjjyQusEy7K3VhPkd"); #[system] pub mod escrow_funding { - pub fn execute<'info>( - ctx: Context<'_, '_, '_, 'info, Components<'info>>, - args: Args, - ) -> Result> { + pub fn execute(ctx: Context,args: Args) -> Result { let receiver = ctx.accounts.receiver.to_account_info(); let sender = ctx.sender()?.clone(); let system_program = ctx.system_program()?.clone(); diff --git a/examples/system-simple-movement/Cargo.toml b/examples/system-simple-movement/Cargo.toml index 5a48ad84..35234a1d 100644 --- a/examples/system-simple-movement/Cargo.toml +++ b/examples/system-simple-movement/Cargo.toml @@ -26,4 +26,4 @@ custom-panic = [] [dependencies] serde.workspace = true bolt-lang.workspace = true -bolt-types = { version = "0.2.4", path = "../../crates/types" } +bolt-types = { version = "0.2.5", path = "../../crates/types" } From 02a33f4401c14cce58ba507827cab12269a76430 Mon Sep 17 00:00:00 2001 From: Danilo Guanabara Date: Wed, 24 Sep 2025 14:34:40 -0300 Subject: [PATCH 2/2] :rotating_light: Fixing linter warnings --- crates/bolt-lang/attribute/system/src/lib.rs | 108 +++++++++---------- examples/escrow-funding/src/lib.rs | 2 +- 2 files changed, 55 insertions(+), 55 deletions(-) diff --git a/crates/bolt-lang/attribute/system/src/lib.rs b/crates/bolt-lang/attribute/system/src/lib.rs index 4deb7948..ef49a41f 100644 --- a/crates/bolt-lang/attribute/system/src/lib.rs +++ b/crates/bolt-lang/attribute/system/src/lib.rs @@ -192,74 +192,74 @@ impl SystemTransform { // Add lifetimes <'a, 'b, 'c, 'info> if missing let lifetime_idents = ["a", "b", "c", "info"]; for name in lifetime_idents.iter() { - let exists = item_fn - .sig - .generics - .params - .iter() - .any(|p| match p { - syn::GenericParam::Lifetime(l) => l.lifetime.ident == *name, - _ => false, - }); + let exists = item_fn.sig.generics.params.iter().any(|p| match p { + syn::GenericParam::Lifetime(l) => l.lifetime.ident == *name, + _ => false, + }); if !exists { - let lifetime: syn::Lifetime = syn::parse_str(&format!("'{}", name)).expect("valid lifetime"); + let lifetime: syn::Lifetime = + syn::parse_str(&format!("'{}", name)).expect("valid lifetime"); let gp: syn::GenericParam = syn::parse_quote!(#lifetime); item_fn.sig.generics.params.push(gp); } } // Update the first argument type from Context to Context<'a, 'b, 'c, 'info, Components<'info>> - if let Some(first_arg) = item_fn.sig.inputs.first_mut() { - if let FnArg::Typed(pat_type) = first_arg { - if let Type::Path(type_path) = pat_type.ty.as_mut() { - if let Some(last_segment) = type_path.path.segments.last_mut() { - if last_segment.ident == "Context" { - // Extract Components path from existing generic args (if any) - let mut components_ty_opt: Option = None; - if let PathArguments::AngleBracketed(args) = &last_segment.arguments { - for ga in args.args.iter() { - if let GenericArgument::Type(t) = ga { - components_ty_opt = Some(t.clone()); - break; - } + if let Some(FnArg::Typed(pat_type)) = item_fn.sig.inputs.first_mut() { + if let Type::Path(type_path) = pat_type.ty.as_mut() { + if let Some(last_segment) = type_path.path.segments.last_mut() { + if last_segment.ident == "Context" { + // Extract Components path from existing generic args (if any) + let mut components_ty_opt: Option = None; + if let PathArguments::AngleBracketed(args) = &last_segment.arguments { + for ga in args.args.iter() { + if let GenericArgument::Type(t) = ga { + components_ty_opt = Some(t.clone()); + break; } } + } - // If not found, leave early - if let Some(components_ty) = components_ty_opt { - // Ensure Components<'info> - let components_with_info: Type = match components_ty { - Type::Path(mut tp) => { - let seg = tp.path.segments.last_mut().unwrap(); - match &mut seg.arguments { - PathArguments::AngleBracketed(ab) => { - if ab.args.is_empty() { - ab.args.push(GenericArgument::Lifetime(syn::parse_quote!('info))); - } - } - _ => { - seg.arguments = PathArguments::AngleBracketed( - syn::AngleBracketedGenericArguments { - colon2_token: None, - lt_token: Default::default(), - args: std::iter::once(GenericArgument::Lifetime(syn::parse_quote!('info))) - .collect(), - gt_token: Default::default(), - }, - ); + // If not found, leave early + if let Some(components_ty) = components_ty_opt { + // Ensure Components<'info> + let components_with_info: Type = match components_ty { + Type::Path(mut tp) => { + let seg = tp.path.segments.last_mut().unwrap(); + match &mut seg.arguments { + PathArguments::AngleBracketed(ab) => { + if ab.args.is_empty() { + ab.args.push(GenericArgument::Lifetime( + syn::parse_quote!('info), + )); } } - Type::Path(tp) + _ => { + seg.arguments = PathArguments::AngleBracketed( + syn::AngleBracketedGenericArguments { + colon2_token: None, + lt_token: Default::default(), + args: std::iter::once( + GenericArgument::Lifetime( + syn::parse_quote!('info), + ), + ) + .collect(), + gt_token: Default::default(), + }, + ); + } } - other => other, - }; + Type::Path(tp) + } + other => other, + }; - // Build new Context<'a, 'b, 'c, 'info, Components<'info>> type - let new_ty: Type = syn::parse_quote! { - Context<'a, 'b, 'c, 'info, #components_with_info> - }; - pat_type.ty = Box::new(new_ty); - } + // Build new Context<'a, 'b, 'c, 'info, Components<'info>> type + let new_ty: Type = syn::parse_quote! { + Context<'a, 'b, 'c, 'info, #components_with_info> + }; + pat_type.ty = Box::new(new_ty); } } } diff --git a/examples/escrow-funding/src/lib.rs b/examples/escrow-funding/src/lib.rs index d5f8bdb3..135d4da5 100644 --- a/examples/escrow-funding/src/lib.rs +++ b/examples/escrow-funding/src/lib.rs @@ -6,7 +6,7 @@ declare_id!("4Um2d8SvyfWyLLtfu2iJMFhM77DdjjyQusEy7K3VhPkd"); #[system] pub mod escrow_funding { - pub fn execute(ctx: Context,args: Args) -> Result { + pub fn execute(ctx: Context, args: Args) -> Result { let receiver = ctx.accounts.receiver.to_account_info(); let sender = ctx.sender()?.clone(); let system_program = ctx.system_program()?.clone();