@@ -699,35 +699,118 @@ pub fn parse_struct_to_schema(
699699}
700700
701701fn substitute_type ( ty : & Type , generic_params : & [ String ] , concrete_types : & [ & Type ] ) -> Type {
702- // Check if this is a generic parameter
703- if let Type :: Path ( type_path) = ty
704- && let Some ( segment) = type_path. path . segments . last ( )
705- {
706- let ident_str = segment. ident . to_string ( ) ;
707- if generic_params. contains ( & ident_str) && segment. arguments . is_none ( ) {
708- // Find the index and substitute
709- if let Some ( index) = generic_params. iter ( ) . position ( |p| p == & ident_str)
710- && let Some ( concrete_ty) = concrete_types. get ( index)
711- {
712- return ( * concrete_ty) . clone ( ) ;
702+ match ty {
703+ Type :: Path ( type_path) => {
704+ let path = & type_path. path ;
705+ if path. segments . is_empty ( ) {
706+ return ty. clone ( ) ;
713707 }
714- }
715- }
716708
717- // For complex types, use quote! to regenerate with substitutions
718- let tokens = quote:: quote! { #ty } ;
719- let mut new_tokens = tokens. to_string ( ) ;
709+ // Check if this is a direct generic parameter (e.g., just "T" with no arguments)
710+ if path. segments . len ( ) == 1 {
711+ let segment = & path. segments [ 0 ] ;
712+ let ident_str = segment. ident . to_string ( ) ;
713+
714+ if let syn:: PathArguments :: None = & segment. arguments {
715+ // Direct generic parameter substitution
716+ if let Some ( index) = generic_params. iter ( ) . position ( |p| p == & ident_str)
717+ && let Some ( concrete_ty) = concrete_types. get ( index) {
718+ return ( * concrete_ty) . clone ( ) ;
719+ }
720+ }
721+ }
720722
721- // Replace generic parameter names with concrete types
722- for ( param, concrete_ty) in generic_params. iter ( ) . zip ( concrete_types. iter ( ) ) {
723- // Replace standalone generic parameter (not part of another identifier)
724- let pattern = format ! ( r"\b{}\b" , param) ;
725- let replacement = quote:: quote! { #concrete_ty } . to_string ( ) ;
726- new_tokens = new_tokens. replace ( & pattern, & replacement) ;
727- }
723+ // For types with generic arguments (e.g., Vec<T>, Option<T>, HashMap<K, V>),
724+ // recursively substitute the type arguments
725+ let mut new_segments = syn:: punctuated:: Punctuated :: new ( ) ;
726+ for segment in & path. segments {
727+ let new_arguments = match & segment. arguments {
728+ syn:: PathArguments :: AngleBracketed ( args) => {
729+ let mut new_args = syn:: punctuated:: Punctuated :: new ( ) ;
730+ for arg in & args. args {
731+ let new_arg = match arg {
732+ syn:: GenericArgument :: Type ( inner_ty) => syn:: GenericArgument :: Type (
733+ substitute_type ( inner_ty, generic_params, concrete_types) ,
734+ ) ,
735+ other => other. clone ( ) ,
736+ } ;
737+ new_args. push ( new_arg) ;
738+ }
739+ syn:: PathArguments :: AngleBracketed ( syn:: AngleBracketedGenericArguments {
740+ colon2_token : args. colon2_token ,
741+ lt_token : args. lt_token ,
742+ args : new_args,
743+ gt_token : args. gt_token ,
744+ } )
745+ }
746+ other => other. clone ( ) ,
747+ } ;
748+
749+ new_segments. push ( syn:: PathSegment {
750+ ident : segment. ident . clone ( ) ,
751+ arguments : new_arguments,
752+ } ) ;
753+ }
728754
729- // Parse the substituted type
730- syn:: parse_str :: < Type > ( & new_tokens) . unwrap_or_else ( |_| ty. clone ( ) )
755+ Type :: Path ( syn:: TypePath {
756+ qself : type_path. qself . clone ( ) ,
757+ path : syn:: Path {
758+ leading_colon : path. leading_colon ,
759+ segments : new_segments,
760+ } ,
761+ } )
762+ }
763+ Type :: Reference ( type_ref) => {
764+ // Handle &T, &mut T
765+ Type :: Reference ( syn:: TypeReference {
766+ and_token : type_ref. and_token ,
767+ lifetime : type_ref. lifetime . clone ( ) ,
768+ mutability : type_ref. mutability ,
769+ elem : Box :: new ( substitute_type (
770+ & type_ref. elem ,
771+ generic_params,
772+ concrete_types,
773+ ) ) ,
774+ } )
775+ }
776+ Type :: Slice ( type_slice) => {
777+ // Handle [T]
778+ Type :: Slice ( syn:: TypeSlice {
779+ bracket_token : type_slice. bracket_token ,
780+ elem : Box :: new ( substitute_type (
781+ & type_slice. elem ,
782+ generic_params,
783+ concrete_types,
784+ ) ) ,
785+ } )
786+ }
787+ Type :: Array ( type_array) => {
788+ // Handle [T; N]
789+ Type :: Array ( syn:: TypeArray {
790+ bracket_token : type_array. bracket_token ,
791+ elem : Box :: new ( substitute_type (
792+ & type_array. elem ,
793+ generic_params,
794+ concrete_types,
795+ ) ) ,
796+ semi_token : type_array. semi_token ,
797+ len : type_array. len . clone ( ) ,
798+ } )
799+ }
800+ Type :: Tuple ( type_tuple) => {
801+ // Handle (T1, T2, ...)
802+ let new_elems = type_tuple
803+ . elems
804+ . iter ( )
805+ . map ( |elem| substitute_type ( elem, generic_params, concrete_types) )
806+ . collect ( ) ;
807+ Type :: Tuple ( syn:: TypeTuple {
808+ paren_token : type_tuple. paren_token ,
809+ elems : new_elems,
810+ } )
811+ }
812+ _ => ty. clone ( ) ,
813+ }
731814}
732815
733816pub ( super ) fn is_primitive_type ( ty : & Type ) -> bool {
@@ -1561,6 +1644,107 @@ mod tests {
15611644 assert_eq ! ( substituted, ty) ;
15621645 }
15631646
1647+ #[ rstest]
1648+ // Direct generic param substitution
1649+ #[ case( "T" , & [ "T" ] , & [ "String" ] , "String" ) ]
1650+ // Vec<T> substitution
1651+ #[ case( "Vec<T>" , & [ "T" ] , & [ "String" ] , "Vec < String >" ) ]
1652+ // Option<T> substitution
1653+ #[ case( "Option<T>" , & [ "T" ] , & [ "i32" ] , "Option < i32 >" ) ]
1654+ // Nested: Vec<Option<T>>
1655+ #[ case( "Vec<Option<T>>" , & [ "T" ] , & [ "String" ] , "Vec < Option < String > >" ) ]
1656+ // Deeply nested: Option<Vec<Option<T>>>
1657+ #[ case( "Option<Vec<Option<T>>>" , & [ "T" ] , & [ "bool" ] , "Option < Vec < Option < bool > > >" ) ]
1658+ // Multiple generic params
1659+ #[ case( "HashMap<K, V>" , & [ "K" , "V" ] , & [ "String" , "i32" ] , "HashMap < String , i32 >" ) ]
1660+ // Generic param not in list (unchanged)
1661+ #[ case( "Vec<U>" , & [ "T" ] , & [ "String" ] , "Vec < U >" ) ]
1662+ // Non-generic type (unchanged)
1663+ #[ case( "String" , & [ "T" ] , & [ "i32" ] , "String" ) ]
1664+ // Reference type: &T
1665+ #[ case( "&T" , & [ "T" ] , & [ "String" ] , "& String" ) ]
1666+ // Mutable reference: &mut T
1667+ #[ case( "&mut T" , & [ "T" ] , & [ "i32" ] , "& mut i32" ) ]
1668+ // Slice type: [T]
1669+ #[ case( "[T]" , & [ "T" ] , & [ "String" ] , "[String]" ) ]
1670+ // Array type: [T; 5]
1671+ #[ case( "[T; 5]" , & [ "T" ] , & [ "u8" ] , "[u8 ; 5]" ) ]
1672+ // Tuple type: (T, U)
1673+ #[ case( "(T, U)" , & [ "T" , "U" ] , & [ "String" , "i32" ] , "(String , i32)" ) ]
1674+ // Complex nested tuple
1675+ #[ case( "(Vec<T>, Option<U>)" , & [ "T" , "U" ] , & [ "String" , "bool" ] , "(Vec < String > , Option < bool >)" ) ]
1676+ // Reference to Vec<T>
1677+ #[ case( "&Vec<T>" , & [ "T" ] , & [ "String" ] , "& Vec < String >" ) ]
1678+ // Multi-segment path (no substitution for crate::Type)
1679+ #[ case( "std::vec::Vec<T>" , & [ "T" ] , & [ "String" ] , "std :: vec :: Vec < String >" ) ]
1680+ fn test_substitute_type_comprehensive (
1681+ #[ case] input : & str ,
1682+ #[ case] params : & [ & str ] ,
1683+ #[ case] concrete : & [ & str ] ,
1684+ #[ case] expected : & str ,
1685+ ) {
1686+ let ty: Type = syn:: parse_str ( input) . unwrap ( ) ;
1687+ let generic_params: Vec < String > = params. iter ( ) . map ( |s| s. to_string ( ) ) . collect ( ) ;
1688+ let concrete_types: Vec < Type > = concrete. iter ( ) . map ( |s| syn:: parse_str ( s) . unwrap ( ) ) . collect ( ) ;
1689+ let concrete_refs: Vec < & Type > = concrete_types. iter ( ) . collect ( ) ;
1690+
1691+ let result = substitute_type ( & ty, & generic_params, & concrete_refs) ;
1692+ let result_str = quote:: quote!( #result) . to_string ( ) ;
1693+
1694+ assert_eq ! ( result_str, expected, "Input: {}" , input) ;
1695+ }
1696+
1697+ #[ test]
1698+ fn test_substitute_type_empty_path_segments ( ) {
1699+ // Create a TypePath with empty segments
1700+ let ty = Type :: Path ( syn:: TypePath {
1701+ qself : None ,
1702+ path : syn:: Path {
1703+ leading_colon : None ,
1704+ segments : syn:: punctuated:: Punctuated :: new ( ) ,
1705+ } ,
1706+ } ) ;
1707+ let concrete: Type = syn:: parse_str ( "String" ) . unwrap ( ) ;
1708+ let result = substitute_type ( & ty, & [ String :: from ( "T" ) ] , & [ & concrete] ) ;
1709+ // Should return the original type unchanged
1710+ assert_eq ! ( result, ty) ;
1711+ }
1712+
1713+ #[ test]
1714+ fn test_substitute_type_with_lifetime_generic_argument ( ) {
1715+ // Test type with lifetime: Cow<'static, T>
1716+ // The lifetime argument should be preserved while T is substituted
1717+ let ty: Type = syn:: parse_str ( "std::borrow::Cow<'static, T>" ) . unwrap ( ) ;
1718+ let concrete: Type = syn:: parse_str ( "String" ) . unwrap ( ) ;
1719+ let result = substitute_type ( & ty, & [ String :: from ( "T" ) ] , & [ & concrete] ) ;
1720+ let result_str = quote:: quote!( #result) . to_string ( ) ;
1721+ // Lifetime 'static should be preserved, T should be substituted
1722+ assert_eq ! ( result_str, "std :: borrow :: Cow < 'static , String >" ) ;
1723+ }
1724+
1725+ #[ test]
1726+ fn test_substitute_type_parenthesized_args ( ) {
1727+ // Fn(T) -> U style (parenthesized arguments)
1728+ // This tests the `other => other.clone()` branch for PathArguments
1729+ let ty: Type = syn:: parse_str ( "fn(T) -> U" ) . unwrap ( ) ;
1730+ let concrete_t: Type = syn:: parse_str ( "String" ) . unwrap ( ) ;
1731+ let concrete_u: Type = syn:: parse_str ( "i32" ) . unwrap ( ) ;
1732+ let result = substitute_type ( & ty, & [ String :: from ( "T" ) , String :: from ( "U" ) ] , & [ & concrete_t, & concrete_u] ) ;
1733+ // Type::BareFn doesn't go through the Path branch, falls to _ => ty.clone()
1734+ assert_eq ! ( result, ty) ;
1735+ }
1736+
1737+ #[ test]
1738+ fn test_substitute_type_path_without_angle_brackets ( ) {
1739+ // Test path with parenthesized arguments: Fn(T) -> U as a trait
1740+ let ty: Type = syn:: parse_str ( "dyn Fn(T) -> U" ) . unwrap ( ) ;
1741+ let concrete_t: Type = syn:: parse_str ( "String" ) . unwrap ( ) ;
1742+ let concrete_u: Type = syn:: parse_str ( "i32" ) . unwrap ( ) ;
1743+ let result = substitute_type ( & ty, & [ String :: from ( "T" ) , String :: from ( "U" ) ] , & [ & concrete_t, & concrete_u] ) ;
1744+ // Type::TraitObject falls to _ => ty.clone()
1745+ assert_eq ! ( result, ty) ;
1746+ }
1747+
15641748 #[ rstest]
15651749 #[ case( "&i32" ) ]
15661750 #[ case( "std::string::String" ) ]
0 commit comments