From d21379a5e1159c10a558b147ac9edd3875c6be76 Mon Sep 17 00:00:00 2001 From: Akash thota <0x4ka5h@osec.io> Date: Tue, 11 Aug 2026 12:21:26 +0530 Subject: [PATCH 1/4] fix(lang-v2): preserve qself in InitSpace type paths --- lang-v2/derive/src/init_space.rs | 5 +++-- lang-v2/tests/init_space.rs | 20 ++++++++++++++++++++ 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/lang-v2/derive/src/init_space.rs b/lang-v2/derive/src/init_space.rs index 02a5bb61c0..41247cdf4b 100644 --- a/lang-v2/derive/src/init_space.rs +++ b/lang-v2/derive/src/init_space.rs @@ -204,8 +204,9 @@ fn len_from_type(ty: Type, attrs: &mut Option>) -> TokenS } } _ => { - let ty = &ty_path.path; - quote!(<#ty as anchor_lang::Space>::INIT_SPACE) + // Keep the full TypePath so `::Assoc` retains + // its qself; quoting only `.path` would emit `Trait::Assoc`. + quote!(<#ty_path as anchor_lang::Space>::INIT_SPACE) } } } diff --git a/lang-v2/tests/init_space.rs b/lang-v2/tests/init_space.rs index d1cd177fad..3ee86b20fe 100644 --- a/lang-v2/tests/init_space.rs +++ b/lang-v2/tests/init_space.rs @@ -126,6 +126,26 @@ fn nested_struct_uses_inner_init_space() { assert_eq!(Outer::INIT_SPACE, 8 + 24); } +trait Schema { + type Value: Space; +} + +struct SchemaHost; + +impl Schema for SchemaHost { + type Value = u64; +} + +#[derive(InitSpace)] +struct WithQualifiedAssoc { + _value: ::Value, +} + +#[test] +fn qualified_associated_type_preserves_qself() { + assert_eq!(WithQualifiedAssoc::INIT_SPACE, 8); +} + #[derive(InitSpace)] enum Variant { A, // 0 From 66c06351f1e79e3f60ffdebc47260c76eb3e0fc0 Mon Sep 17 00:00:00 2001 From: Akash thota <0x4ka5h@osec.io> Date: Tue, 11 Aug 2026 13:04:26 +0530 Subject: [PATCH 2/4] fix(lang-v2): buildin shortcuts now apply only to unqualified single-segment paths --- lang-v2/Cargo.toml | 3 + lang-v2/derive/src/init_space.rs | 134 ++++++++++++++++++++++--------- lang-v2/tests/init_space.rs | 20 +++++ 3 files changed, 119 insertions(+), 38 deletions(-) diff --git a/lang-v2/Cargo.toml b/lang-v2/Cargo.toml index 69c9e4aedc..fe05fa3069 100644 --- a/lang-v2/Cargo.toml +++ b/lang-v2/Cargo.toml @@ -169,6 +169,9 @@ required-features = ["testing"] [[test]] name = "address_idl_surface" +[[test]] +name = "init_space" + [[test]] name = "update_hook_order" required-features = ["testing"] diff --git a/lang-v2/derive/src/init_space.rs b/lang-v2/derive/src/init_space.rs index 41247cdf4b..7e8b7e4953 100644 --- a/lang-v2/derive/src/init_space.rs +++ b/lang-v2/derive/src/init_space.rs @@ -164,50 +164,63 @@ fn len_from_type(ty: Type, attrs: &mut Option>) -> TokenS quote!((#array_len * #type_len)) } Type::Path(ty_path) => { - let path_segment = ty_path - .path - .segments - .last() - .expect("syn::TypePath always has at least one segment"); - let ident = &path_segment.ident; - let type_name = ident.to_string(); - let first_ty = get_first_ty_arg(&path_segment.arguments); + if let Some(type_name) = builtin_type_name(&ty_path) { + let path_segment = ty_path + .path + .segments + .last() + .expect("syn::TypePath always has at least one segment"); + let ident = &path_segment.ident; + let first_ty = get_first_ty_arg(&path_segment.arguments); - match type_name.as_str() { - "i8" | "u8" | "bool" => quote!(1), - "i16" | "u16" => quote!(2), - "i32" | "u32" | "f32" => quote!(4), - "i64" | "u64" | "f64" => quote!(8), - "i128" | "u128" => quote!(16), - "String" => { - let max_len = get_next_arg(ident, attrs); - quote!((4 + #max_len)) - } - "Pubkey" | "Address" => quote!(32), - "Option" => { - if let Some(ty) = first_ty { - let type_len = len_from_type(ty, attrs); + match type_name { + "i8" | "u8" | "bool" => quote!(1), + "i16" | "u16" => quote!(2), + "i32" | "u32" | "f32" => quote!(4), + "i64" | "u64" | "f64" => quote!(8), + "i128" | "u128" => quote!(16), + "String" => { + let max_len = get_next_arg(ident, attrs); + quote!((4 + #max_len)) + } + "Pubkey" | "Address" => quote!(32), + "Option" => { + if let Some(ty) = first_ty { + let type_len = len_from_type(ty, attrs); - quote!((1 + #type_len)) - } else { - quote_spanned!(ident.span() => compile_error!("Invalid argument in Option")) + quote!((1 + #type_len)) + } else { + quote_spanned!(ident.span() => compile_error!("Invalid argument in Option")) + } } - } - "Vec" => { - if let Some(ty) = first_ty { - let max_len = get_next_arg(ident, attrs); - let type_len = len_from_type(ty, attrs); + "Vec" => { + if let Some(ty) = first_ty { + let max_len = get_next_arg(ident, attrs); + let type_len = len_from_type(ty, attrs); - quote!((4 + #type_len * #max_len)) - } else { - quote_spanned!(ident.span() => compile_error!("Invalid argument in Vec")) + quote!((4 + #type_len * #max_len)) + } else { + quote_spanned!(ident.span() => compile_error!("Invalid argument in Vec")) + } + } + _ => { + // Keep the full TypePath so `::Assoc` retains + // its qself; quoting only `.path` would emit `Trait::Assoc`. + // + // Arbitrary qualified paths such as `custom::Address` + // must not match built-in shortcuts keyed on the final + // segment name alone. + quote!(<#ty_path as anchor_lang::Space>::INIT_SPACE) } } - _ => { - // Keep the full TypePath so `::Assoc` retains - // its qself; quoting only `.path` would emit `Trait::Assoc`. - quote!(<#ty_path as anchor_lang::Space>::INIT_SPACE) - } + } else { + // Keep the full TypePath so `::Assoc` retains its + // qself; quoting only `.path` would emit `Trait::Assoc`. + // + // Arbitrary qualified paths such as `custom::Address` must not + // match built-in shortcuts keyed on the final segment name + // alone. + quote!(<#ty_path as anchor_lang::Space>::INIT_SPACE) } } Type::Tuple(ty_tuple) => { @@ -232,6 +245,51 @@ fn len_from_type(ty: Type, attrs: &mut Option>) -> TokenS } } +fn builtin_type_name(ty_path: &syn::TypePath) -> Option<&'static str> { + const PRIMITIVES_AND_ALIASES: &[(&str, &[&str])] = &[ + ("i8", &["i8"]), + ("u8", &["u8"]), + ("bool", &["bool"]), + ("i16", &["i16"]), + ("u16", &["u16"]), + ("i32", &["i32"]), + ("u32", &["u32"]), + ("f32", &["f32"]), + ("i64", &["i64"]), + ("u64", &["u64"]), + ("f64", &["f64"]), + ("i128", &["i128"]), + ("u128", &["u128"]), + ("String", &["String"]), + ("String", &["alloc", "string", "String"]), + ("String", &["std", "string", "String"]), + ("Pubkey", &["Pubkey"]), + ("Address", &["Address"]), + ("Option", &["Option"]), + ("Option", &["core", "option", "Option"]), + ("Option", &["std", "option", "Option"]), + ("Vec", &["Vec"]), + ("Vec", &["alloc", "vec", "Vec"]), + ("Vec", &["std", "vec", "Vec"]), + ]; + + PRIMITIVES_AND_ALIASES + .iter() + .find_map(|(name, segments)| path_matches(ty_path, segments).then_some(*name)) +} + +fn path_matches(ty_path: &syn::TypePath, segments: &[&str]) -> bool { + ty_path.qself.is_none() + && ty_path.path.leading_colon.is_none() + && ty_path.path.segments.len() == segments.len() + && ty_path + .path + .segments + .iter() + .zip(segments.iter()) + .all(|(segment, expected)| segment.ident == *expected) +} + fn get_first_ty_arg(args: &PathArguments) -> Option { match args { PathArguments::AngleBracketed(bracket) => bracket.args.iter().find_map(|el| match el { diff --git a/lang-v2/tests/init_space.rs b/lang-v2/tests/init_space.rs index 3ee86b20fe..061efcab10 100644 --- a/lang-v2/tests/init_space.rs +++ b/lang-v2/tests/init_space.rs @@ -146,6 +146,26 @@ fn qualified_associated_type_preserves_qself() { assert_eq!(WithQualifiedAssoc::INIT_SPACE, 8); } +mod custom { + use super::Space; + + pub struct Address; + + impl Space for Address { + const INIT_SPACE: usize = 8; + } +} + +#[derive(InitSpace)] +struct WithCustomAddress { + _addr: custom::Address, +} + +#[test] +fn qualified_path_does_not_use_builtin_address_size() { + assert_eq!(WithCustomAddress::INIT_SPACE, 8); +} + #[derive(InitSpace)] enum Variant { A, // 0 From e46c09f326c9c8453481cd1f726bab9549fc987a Mon Sep 17 00:00:00 2001 From: Akash thota <0x4ka5h@osec.io> Date: Tue, 11 Aug 2026 13:43:44 +0530 Subject: [PATCH 3/4] fix(lang-v2): support qualified init space container paths --- lang-v2/derive/src/init_space.rs | 16 ++++------------ lang-v2/tests/init_space.rs | 33 ++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 12 deletions(-) diff --git a/lang-v2/derive/src/init_space.rs b/lang-v2/derive/src/init_space.rs index 7e8b7e4953..03d3edc11f 100644 --- a/lang-v2/derive/src/init_space.rs +++ b/lang-v2/derive/src/init_space.rs @@ -203,19 +203,12 @@ fn len_from_type(ty: Type, attrs: &mut Option>) -> TokenS quote_spanned!(ident.span() => compile_error!("Invalid argument in Vec")) } } - _ => { - // Keep the full TypePath so `::Assoc` retains - // its qself; quoting only `.path` would emit `Trait::Assoc`. - // - // Arbitrary qualified paths such as `custom::Address` - // must not match built-in shortcuts keyed on the final - // segment name alone. - quote!(<#ty_path as anchor_lang::Space>::INIT_SPACE) - } +<<<<<<< HEAD + _ => unreachable!("all builtin type names should be covered"), } } else { - // Keep the full TypePath so `::Assoc` retains its - // qself; quoting only `.path` would emit `Trait::Assoc`. + // Keep the full TypePath so `::Assoc` retains + // its qself; quoting only `.path` would emit `Trait::Assoc`. // // Arbitrary qualified paths such as `custom::Address` must not // match built-in shortcuts keyed on the final segment name @@ -280,7 +273,6 @@ fn builtin_type_name(ty_path: &syn::TypePath) -> Option<&'static str> { fn path_matches(ty_path: &syn::TypePath, segments: &[&str]) -> bool { ty_path.qself.is_none() - && ty_path.path.leading_colon.is_none() && ty_path.path.segments.len() == segments.len() && ty_path .path diff --git a/lang-v2/tests/init_space.rs b/lang-v2/tests/init_space.rs index 061efcab10..c7ccd43446 100644 --- a/lang-v2/tests/init_space.rs +++ b/lang-v2/tests/init_space.rs @@ -9,6 +9,7 @@ #![allow(dead_code)] use anchor_lang::{prelude::*, InitSpace, Space}; +extern crate alloc; #[derive(InitSpace)] struct Primitives { @@ -66,6 +67,16 @@ fn option_adds_one_byte_discriminator() { assert_eq!(WithOption::INIT_SPACE, 9); } +#[derive(InitSpace)] +struct WithQualifiedCoreOption { + _maybe: core::option::Option, // 1 + 8 +} + +#[test] +fn qualified_core_option_uses_option_layout() { + assert_eq!(WithQualifiedCoreOption::INIT_SPACE, 9); +} + #[derive(InitSpace)] struct WithString { #[max_len(32)] @@ -77,6 +88,17 @@ fn string_reserves_max_len_plus_length_prefix() { assert_eq!(WithString::INIT_SPACE, 36); } +#[derive(InitSpace)] +struct WithQualifiedAllocString { + #[max_len(32)] + _name: alloc::string::String, // 4 + 32 +} + +#[test] +fn qualified_alloc_string_uses_string_layout() { + assert_eq!(WithQualifiedAllocString::INIT_SPACE, 36); +} + #[derive(InitSpace)] struct WithVec { #[max_len(10)] @@ -88,6 +110,17 @@ fn vec_reserves_max_len_times_element_plus_prefix() { assert_eq!(WithVec::INIT_SPACE, 84); } +#[derive(InitSpace)] +struct WithQualifiedAllocVec { + #[max_len(10)] + _xs: alloc::vec::Vec, // 4 + 8 * 10 +} + +#[test] +fn qualified_alloc_vec_uses_vec_layout() { + assert_eq!(WithQualifiedAllocVec::INIT_SPACE, 84); +} + #[derive(InitSpace)] struct WithVecOfStrings { #[max_len(4, 16)] From 05a30a3f5b68c91d95bd9aeb79ea66a4ed9b0d31 Mon Sep 17 00:00:00 2001 From: Akash thota <0x4ka5h@osec.io> Date: Thu, 13 Aug 2026 18:02:29 +0530 Subject: [PATCH 4/4] Clean up init space qself rebase --- lang-v2/derive/src/init_space.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/lang-v2/derive/src/init_space.rs b/lang-v2/derive/src/init_space.rs index 03d3edc11f..42af9c6d40 100644 --- a/lang-v2/derive/src/init_space.rs +++ b/lang-v2/derive/src/init_space.rs @@ -203,7 +203,6 @@ fn len_from_type(ty: Type, attrs: &mut Option>) -> TokenS quote_spanned!(ident.span() => compile_error!("Invalid argument in Vec")) } } -<<<<<<< HEAD _ => unreachable!("all builtin type names should be covered"), } } else {