@@ -984,4 +984,203 @@ mod tests {
984984 "non-strict should not check for unknown fields"
985985 ) ;
986986 }
987+
988+ // ─── process_fields direct tests ────────────────────────────────────
989+ //
990+ // Exercise process_fields directly to ensure quote! token construction
991+ // for each branch (parse_value, strict assignment, field matching) is
992+ // fully traced by the coverage tool.
993+
994+ fn parse_fields_from ( code : & str ) -> syn:: DeriveInput {
995+ syn:: parse_str ( code) . unwrap ( )
996+ }
997+
998+ fn get_named_fields (
999+ input : & syn:: DeriveInput ,
1000+ ) -> & syn:: punctuated:: Punctuated < syn:: Field , syn:: token:: Comma > {
1001+ match & input. data {
1002+ syn:: Data :: Struct ( s) => match & s. fields {
1003+ Fields :: Named ( n) => & n. named ,
1004+ _ => panic ! ( "expected named fields" ) ,
1005+ } ,
1006+ _ => panic ! ( "expected struct" ) ,
1007+ }
1008+ }
1009+
1010+ #[ test]
1011+ fn test_process_fields_required_field_generates_parse_value ( ) {
1012+ let input = parse_fields_from ( "struct T { pub name: String }" ) ;
1013+ let fields = get_named_fields ( & input) ;
1014+ let cg = process_fields ( fields. iter ( ) , None , false , false ) ;
1015+
1016+ // parse_value is interpolated into each assignment
1017+ let assignment_code = cg
1018+ . assignments
1019+ . iter ( )
1020+ . map ( ToString :: to_string)
1021+ . collect :: < Vec < _ > > ( )
1022+ . join ( " " ) ;
1023+ assert ! (
1024+ assignment_code. contains( "TryFromFieldWithState" ) ,
1025+ "parse_value should contain turbofish call"
1026+ ) ;
1027+ assert ! (
1028+ assignment_code. contains( "try_from_field_with_state" ) ,
1029+ "should call try_from_field_with_state"
1030+ ) ;
1031+ assert ! (
1032+ assignment_code. contains( "\" name\" " ) ,
1033+ "should match on field name"
1034+ ) ;
1035+
1036+ // post_loop should have MissingField check for required fields
1037+ let post_code = cg
1038+ . post_loop
1039+ . iter ( )
1040+ . map ( ToString :: to_string)
1041+ . collect :: < Vec < _ > > ( )
1042+ . join ( " " ) ;
1043+ assert ! (
1044+ post_code. contains( "MissingField" ) ,
1045+ "required field should have MissingField check"
1046+ ) ;
1047+ }
1048+
1049+ #[ test]
1050+ fn test_process_fields_strict_required_field_generates_duplicate_check ( ) {
1051+ let input = parse_fields_from ( "struct T { pub name: String, pub age: i32 }" ) ;
1052+ let fields = get_named_fields ( & input) ;
1053+ let cg = process_fields ( fields. iter ( ) , None , true , false ) ;
1054+
1055+ // strict mode: assignments should contain is_none + DuplicateField check
1056+ let assignment_code = cg
1057+ . assignments
1058+ . iter ( )
1059+ . map ( ToString :: to_string)
1060+ . collect :: < Vec < _ > > ( )
1061+ . join ( " " ) ;
1062+ assert ! (
1063+ assignment_code. contains( "is_none" ) ,
1064+ "strict assignment should check is_none"
1065+ ) ;
1066+ assert ! (
1067+ assignment_code. contains( "DuplicateField" ) ,
1068+ "strict assignment should have DuplicateField"
1069+ ) ;
1070+ assert ! (
1071+ assignment_code. contains( "\" name\" " ) ,
1072+ "should match name field"
1073+ ) ;
1074+ assert ! (
1075+ assignment_code. contains( "\" age\" " ) ,
1076+ "should match age field"
1077+ ) ;
1078+
1079+ // Both fields should have parse_value with turbofish
1080+ assert ! (
1081+ assignment_code. contains( "TryFromFieldWithState" ) ,
1082+ "should contain turbofish"
1083+ ) ;
1084+ }
1085+
1086+ #[ test]
1087+ fn test_process_fields_vec_field_generates_push ( ) {
1088+ let input = parse_fields_from ( "struct T { pub tags: Vec<String> }" ) ;
1089+ let fields = get_named_fields ( & input) ;
1090+ let cg = process_fields ( fields. iter ( ) , None , false , false ) ;
1091+
1092+ let decl_code = cg
1093+ . declarations
1094+ . iter ( )
1095+ . map ( ToString :: to_string)
1096+ . collect :: < Vec < _ > > ( )
1097+ . join ( " " ) ;
1098+ assert ! (
1099+ decl_code. contains( "Vec :: new" ) ,
1100+ "Vec field should initialize with Vec::new()"
1101+ ) ;
1102+
1103+ let assignment_code = cg
1104+ . assignments
1105+ . iter ( )
1106+ . map ( ToString :: to_string)
1107+ . collect :: < Vec < _ > > ( )
1108+ . join ( " " ) ;
1109+ assert ! (
1110+ assignment_code. contains( "push" ) ,
1111+ "Vec field assignment should use push"
1112+ ) ;
1113+
1114+ // Vec fields should NOT have post_loop (no MissingField check)
1115+ assert ! (
1116+ cg. post_loop. is_empty( ) ,
1117+ "Vec fields should not have post-loop checks"
1118+ ) ;
1119+ }
1120+
1121+ #[ test]
1122+ fn test_process_fields_option_field_no_missing_check ( ) {
1123+ let input = parse_fields_from ( "struct T { pub bio: Option<String> }" ) ;
1124+ let fields = get_named_fields ( & input) ;
1125+ let cg = process_fields ( fields. iter ( ) , None , false , false ) ;
1126+
1127+ let decl_code = cg
1128+ . declarations
1129+ . iter ( )
1130+ . map ( ToString :: to_string)
1131+ . collect :: < Vec < _ > > ( )
1132+ . join ( " " ) ;
1133+ assert ! (
1134+ decl_code. contains( "Option :: None" ) ,
1135+ "Option field should initialize to None"
1136+ ) ;
1137+
1138+ // Option fields should NOT have post_loop
1139+ assert ! (
1140+ cg. post_loop. is_empty( ) ,
1141+ "Option fields should not have post-loop checks"
1142+ ) ;
1143+ }
1144+
1145+ #[ test]
1146+ fn test_process_fields_strict_vec_field_uses_push_not_duplicate ( ) {
1147+ let input = parse_fields_from ( "struct T { pub tags: Vec<String> }" ) ;
1148+ let fields = get_named_fields ( & input) ;
1149+ let cg = process_fields ( fields. iter ( ) , None , true , false ) ;
1150+
1151+ // Even in strict mode, Vec fields use push (not duplicate check)
1152+ let assignment_code = cg
1153+ . assignments
1154+ . iter ( )
1155+ . map ( ToString :: to_string)
1156+ . collect :: < Vec < _ > > ( )
1157+ . join ( " " ) ;
1158+ assert ! (
1159+ assignment_code. contains( "push" ) ,
1160+ "Vec in strict mode should still use push"
1161+ ) ;
1162+ assert ! (
1163+ !assignment_code. contains( "DuplicateField" ) ,
1164+ "Vec should not have duplicate check"
1165+ ) ;
1166+ }
1167+
1168+ #[ test]
1169+ fn test_process_fields_mixed_types ( ) {
1170+ let input = parse_fields_from (
1171+ "struct T { pub name: String, pub tags: Vec<String>, pub bio: Option<String> }" ,
1172+ ) ;
1173+ let fields = get_named_fields ( & input) ;
1174+ let cg = process_fields ( fields. iter ( ) , None , false , false ) ;
1175+
1176+ assert_eq ! ( cg. idents. len( ) , 3 , "should have 3 fields" ) ;
1177+ assert_eq ! ( cg. declarations. len( ) , 3 , "should have 3 declarations" ) ;
1178+ assert_eq ! ( cg. assignments. len( ) , 3 , "should have 3 assignments" ) ;
1179+ // Only 'name' is required (not Option, not Vec), so 1 post_loop
1180+ assert_eq ! (
1181+ cg. post_loop. len( ) ,
1182+ 1 ,
1183+ "only required field should have post-loop"
1184+ ) ;
1185+ }
9871186}
0 commit comments