@@ -108,10 +108,12 @@ impl UnionValidator {
108108 state : & mut ValidationState < ' _ , ' py > ,
109109 ) -> ValResult < PyObject > {
110110 let old_exactness = state. exactness ;
111+ let old_fields_set_count = state. fields_set_count ;
112+
111113 let strict = state. strict_or ( self . strict ) ;
112114 let mut errors = MaybeErrors :: new ( self . custom_error . as_ref ( ) ) ;
113115
114- let mut success = None ;
116+ let mut best_match : Option < ( Py < PyAny > , Exactness , Option < usize > ) > = None ;
115117
116118 for ( choice, label) in & self . choices {
117119 let state = & mut state. rebind_extra ( |extra| {
@@ -120,47 +122,67 @@ impl UnionValidator {
120122 }
121123 } ) ;
122124 state. exactness = Some ( Exactness :: Exact ) ;
125+ state. fields_set_count = None ;
123126 let result = choice. validate ( py, input, state) ;
124127 match result {
125- Ok ( new_success) => match state. exactness {
126- // exact match, return
127- Some ( Exactness :: Exact ) => {
128+ Ok ( new_success) => match ( state. exactness , state . fields_set_count ) {
129+ ( Some ( Exactness :: Exact ) , None ) => {
130+ // exact match with no fields set data, return immediately
128131 return {
129132 // exact match, return, restore any previous exactness
130133 state. exactness = old_exactness;
134+ state. fields_set_count = old_fields_set_count;
131135 Ok ( new_success)
132136 } ;
133137 }
134138 _ => {
135139 // success should always have an exactness
136140 debug_assert_ne ! ( state. exactness, None ) ;
141+
137142 let new_exactness = state. exactness . unwrap_or ( Exactness :: Lax ) ;
138- // if the new result has higher exactness than the current success, replace it
139- if success
140- . as_ref ( )
141- . map_or ( true , |( _, current_exactness) | * current_exactness < new_exactness)
142- {
143- // TODO: is there a possible optimization here, where once there has
144- // been one success, we turn on strict mode, to avoid unnecessary
145- // coercions for further validation?
146- success = Some ( ( new_success, new_exactness) ) ;
143+ let new_fields_set_count = state. fields_set_count ;
144+
145+ // we use both the exactness and the fields_set_count to determine the best union member match
146+ // if fields_set_count is available for the current best match and the new candidate, we use this
147+ // as the primary metric. If the new fields_set_count is greater, the new candidate is better.
148+ // if the fields_set_count is the same, we use the exactness as a tie breaker to determine the best match.
149+ // if the fields_set_count is not available for either the current best match or the new candidate,
150+ // we use the exactness to determine the best match.
151+ let new_success_is_best_match: bool =
152+ best_match
153+ . as_ref ( )
154+ . map_or ( true , |( _, cur_exactness, cur_fields_set_count) | {
155+ match ( * cur_fields_set_count, new_fields_set_count) {
156+ ( Some ( cur) , Some ( new) ) if cur != new => cur < new,
157+ _ => * cur_exactness < new_exactness,
158+ }
159+ } ) ;
160+
161+ if new_success_is_best_match {
162+ best_match = Some ( ( new_success, new_exactness, new_fields_set_count) ) ;
147163 }
148164 }
149165 } ,
150166 Err ( ValError :: LineErrors ( lines) ) => {
151167 // if we don't yet know this validation will succeed, record the error
152- if success . is_none ( ) {
168+ if best_match . is_none ( ) {
153169 errors. push ( choice, label. as_deref ( ) , lines) ;
154170 }
155171 }
156172 otherwise => return otherwise,
157173 }
158174 }
175+
176+ // restore previous validation state to prepare for any future validations
159177 state. exactness = old_exactness;
178+ state. fields_set_count = old_fields_set_count;
160179
161- if let Some ( ( success , exactness) ) = success {
180+ if let Some ( ( best_match , exactness, fields_set_count ) ) = best_match {
162181 state. floor_exactness ( exactness) ;
163- return Ok ( success) ;
182+ if let Some ( count) = fields_set_count {
183+ state. add_fields_set ( count) ;
184+ }
185+ return Ok ( best_match) ;
164186 }
165187
166188 // no matches, build errors
0 commit comments