@@ -1644,6 +1644,107 @@ mod tests {
16441644 assert_eq ! ( substituted, ty) ;
16451645 }
16461646
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+
16471748 #[ rstest]
16481749 #[ case( "&i32" ) ]
16491750 #[ case( "std::string::String" ) ]
0 commit comments