diff --git a/typegen/src/tests/mod.rs b/typegen/src/tests/mod.rs index c69cee2..f06bed4 100644 --- a/typegen/src/tests/mod.rs +++ b/typegen/src/tests/mod.rs @@ -1774,3 +1774,85 @@ fn alloc_crate_path_replacement() { assert_eq!(std_code.to_string(), expected_std_code.to_string()); assert_eq!(no_std_code.to_string(), expected_no_std_code.to_string()); } +#[test] +fn ensure_unique_names_recursion() { + use scale_info::PortableRegistry; + + #[allow(unused)] + #[derive(TypeInfo)] + #[scale_info(skip_type_params(A))] + struct Example { + id: A, + rest: B, + inner: Vec>, + } + + let mut registry = Testgen::new() + .with::>() + .with::>() + .with::>() + .into_portable_registry(); + + let sorted_type_paths = |registry: &PortableRegistry| -> Vec { + let mut lines = registry + .types + .iter() + .map(|t| t.ty.path.segments.clone().join("::")) + .filter(|e| !e.is_empty()) + .collect::>(); + lines.sort(); + lines + }; + + let e1 = sorted_type_paths(®istry); + assert_eq!( + e1, + vec![ + "scale_typegen::tests::Example", + "scale_typegen::tests::Example", + "scale_typegen::tests::Example", + ] + ); + + ensure_unique_type_paths(&mut registry); + + let e2 = sorted_type_paths(®istry); + assert_eq!( + e2, + vec![ + "scale_typegen::tests::Example1", + "scale_typegen::tests::Example2", + "scale_typegen::tests::Example3", + ] + ); + + let generated = Testgen::new() + .with::>() + .with::>() + .with::>() + .try_gen_tests_mod(Default::default(), true) + .unwrap(); + + let expected = quote! { + pub mod tests { + use super::types; + pub struct Example1<_1> { + pub id: ::std::string::String, + pub rest: _1, + pub inner: ::std::vec::Vec >, + } + pub struct Example2<_1> { + pub id: ::core::primitive::u8, + pub rest: _1, + pub inner: ::std::vec::Vec >, + } + pub struct Example3<_1> { + pub id: ::core::primitive::u16, + pub rest: _1, + pub inner: ::std::vec::Vec >, + } + } + }; + + assert_eq!(expected.to_string(), generated.to_string()) +} diff --git a/typegen/src/tests/utils.rs b/typegen/src/tests/utils.rs index 2d52c92..1ac0888 100644 --- a/typegen/src/tests/utils.rs +++ b/typegen/src/tests/utils.rs @@ -78,6 +78,7 @@ pub(super) fn subxt_settings() -> TypeGeneratorSettings { compact_as_type_path: Some(parse_quote!(::subxt_path::ext::codec::CompactAs)), compact_type_path: Some(parse_quote!(::subxt_path::ext::codec::Compact)), alloc_crate_path: Default::default(), + parent_path: std::cell::RefCell::default(), } } /// Derives mirroring the subxt default derives diff --git a/typegen/src/typegen/ir/module_ir.rs b/typegen/src/typegen/ir/module_ir.rs index 848cf5b..5a4b8ec 100644 --- a/typegen/src/typegen/ir/module_ir.rs +++ b/typegen/src/typegen/ir/module_ir.rs @@ -1,5 +1,6 @@ use std::collections::BTreeMap; +use crate::typegen::settings::substitutes::TryIntoSynPath; use crate::TypeGeneratorSettings; use super::type_ir::TypeIR; @@ -32,8 +33,26 @@ impl ToTokensWithSettings for ModuleIR { .map(|ir| ir.to_token_stream(settings)); let types = self .types - .values() - .map(|(_, ir)| ir.to_token_stream(settings)) + .iter() + .map(|(path, (_, ir))| { + let parent_path = path.syn_path().map(|mut path| { + // add the root module to the parent_path + let extension = syn::PathSegment { + ident: settings.types_mod_ident.clone(), + arguments: syn::PathArguments::None, + }; + let punctuated = { + let mut buf = syn::punctuated::Punctuated::new(); + buf.push_value(extension); + buf.push_punct(syn::token::PathSep::default()); + buf.extend(path.segments); + buf + }; + path.segments = punctuated; + path + }); + settings.with_parent_path(parent_path, |settings| ir.to_token_stream(settings)) + }) .clone(); tokens.extend(quote! { diff --git a/typegen/src/typegen/ir/type_ir.rs b/typegen/src/typegen/ir/type_ir.rs index ff434bb..721e9e4 100644 --- a/typegen/src/typegen/ir/type_ir.rs +++ b/typegen/src/typegen/ir/type_ir.rs @@ -294,7 +294,7 @@ impl CompositeIR { impl ToTokensWithSettings for CompositeFieldIR { fn to_tokens(&self, tokens: &mut TokenStream, settings: &TypeGeneratorSettings) { - let ty_path = &self.type_path.to_syn_type(&settings.alloc_crate_path); + let ty_path = &self.type_path.to_syn_type(settings); if self.is_boxed { let alloc_path = &settings.alloc_crate_path; tokens.extend(quote! { #alloc_path::boxed::Box<#ty_path> }) diff --git a/typegen/src/typegen/settings/mod.rs b/typegen/src/typegen/settings/mod.rs index 8be6d7e..0c8df7b 100644 --- a/typegen/src/typegen/settings/mod.rs +++ b/typegen/src/typegen/settings/mod.rs @@ -1,3 +1,5 @@ +use std::cell::RefCell; + use derives::DerivesRegistry; use proc_macro2::Ident; use quote::{quote, ToTokens}; @@ -41,6 +43,8 @@ pub struct TypeGeneratorSettings { /// `alloc::string::String`, `alloc::vec::Vec` and `alloc::boxed::Box`. The default is `AllocCratePath::Std` which /// uses the types from the `std` library instead. pub alloc_crate_path: AllocCratePath, + /// path of the parent type + pub parent_path: RefCell>, } /// Information about how to construct the type paths for types that need allocation, e.g. @@ -78,6 +82,7 @@ impl Default for TypeGeneratorSettings { compact_type_path: None, insert_codec_attributes: false, alloc_crate_path: Default::default(), + parent_path: RefCell::default(), } } } @@ -88,6 +93,17 @@ impl TypeGeneratorSettings { Self::default() } + /// Run function with modified parent_path + pub fn with_parent_path(&self, path: Option, f: T) -> proc_macro2::TokenStream + where + T: FnOnce(&Self) -> proc_macro2::TokenStream, + { + let old_path = self.parent_path.replace(path); + let result = f(self); + self.parent_path.replace(old_path); + result + } + /// Sets the `type_mod_name` field. pub fn type_mod_name(mut self, type_mod_name: &str) -> Self { self.types_mod_ident = diff --git a/typegen/src/typegen/settings/substitutes.rs b/typegen/src/typegen/settings/substitutes.rs index 3a04602..8d89cbe 100644 --- a/typegen/src/typegen/settings/substitutes.rs +++ b/typegen/src/typegen/settings/substitutes.rs @@ -295,7 +295,7 @@ fn replace_path_params_recursively, P: Borrow>( }; if let Some(ident) = get_ident_from_type_path(path) { if let Some((_, replacement)) = params.iter().find(|(i, _)| ident == i.borrow()) { - *ty = replacement.borrow().to_syn_type(&settings.alloc_crate_path); + *ty = replacement.borrow().to_syn_type(settings); continue; } } diff --git a/typegen/src/typegen/type_path.rs b/typegen/src/typegen/type_path.rs index cbcca0a..92a24f3 100644 --- a/typegen/src/typegen/type_path.rs +++ b/typegen/src/typegen/type_path.rs @@ -30,7 +30,7 @@ pub enum TypePathInner { impl ToTokensWithSettings for TypePath { fn to_tokens(&self, tokens: &mut TokenStream, settings: &TypeGeneratorSettings) { - let syn_type = self.to_syn_type(&settings.alloc_crate_path); + let syn_type = self.to_syn_type(settings); syn_type.to_tokens(tokens) } } @@ -56,10 +56,10 @@ impl TypePath { })) } - pub(crate) fn to_syn_type(&self, alloc_crate_path: &AllocCratePath) -> syn::Type { + pub(crate) fn to_syn_type(&self, settings: &TypeGeneratorSettings) -> syn::Type { match &self.0 { TypePathInner::Parameter(ty_param) => syn::Type::Path(parse_quote! { #ty_param }), - TypePathInner::Type(ty) => ty.to_syn_type(alloc_crate_path), + TypePathInner::Type(ty) => ty.to_syn_type(settings), } } @@ -283,29 +283,53 @@ impl TypePathType { ) } - fn to_syn_type(&self, alloc_crate_path: &AllocCratePath) -> syn::Type { + fn to_syn_type(&self, settings: &TypeGeneratorSettings) -> syn::Type { + let alloc_crate_path = &settings.alloc_crate_path; match &self { TypePathType::Path { path, params } => { let path = if params.is_empty() { parse_quote! { #path } } else { - let params = params.iter().map(|e| e.to_syn_type(alloc_crate_path)); + let params = params.iter().map(|e| e.to_syn_type(settings)); + let parent_path = settings + .parent_path + .borrow() + .as_ref() + .filter(|x| path == *x) + .map(|x| { + use syn::Path; + let Path { + segments, + leading_colon, + } = x; + let segments = { + let mut punctuated = syn::punctuated::Punctuated::new(); + let self_ident = segments.last().expect("this should not happen"); + punctuated.push_value(self_ident.clone()); + punctuated + }; + Path { + segments, + leading_colon: *leading_colon, + } + }); + let path = parent_path.as_ref().unwrap_or(path); parse_quote! { #path< #( #params ),* > } }; syn::Type::Path(path) } TypePathType::Vec { of } => { - let of = of.to_syn_type(alloc_crate_path); + let of = of.to_syn_type(settings); let type_path = parse_quote! { #alloc_crate_path::vec::Vec<#of> }; syn::Type::Path(type_path) } TypePathType::Array { len, of } => { - let of = of.to_syn_type(alloc_crate_path); + let of = of.to_syn_type(settings); let array = parse_quote! { [#of; #len] }; syn::Type::Array(array) } TypePathType::Tuple { elements } => { - let elements = elements.iter().map(|e| e.to_syn_type(alloc_crate_path)); + let elements = elements.iter().map(|e| e.to_syn_type(settings)); let tuple = parse_quote! { (#( # elements, )* ) }; syn::Type::Tuple(tuple) } @@ -331,7 +355,7 @@ impl TypePathType { is_field, compact_type_path, } => { - let inner = inner.to_syn_type(alloc_crate_path); + let inner = inner.to_syn_type(settings); let path = if *is_field { // compact fields can use the inner compact type directly and be annotated with // the `compact` attribute e.g. `#[codec(compact)] my_compact_field: u128` @@ -346,8 +370,8 @@ impl TypePathType { bit_store_type, decoded_bits_type_path, } => { - let bit_order_type = bit_order_type.to_syn_type(alloc_crate_path); - let bit_store_type = bit_store_type.to_syn_type(alloc_crate_path); + let bit_order_type = bit_order_type.to_syn_type(settings); + let bit_store_type = bit_store_type.to_syn_type(settings); let type_path = parse_quote! { #decoded_bits_type_path<#bit_store_type, #bit_order_type> }; syn::Type::Path(type_path)