@@ -171,6 +171,21 @@ fn try_resolve_entry<E: PrebindgenExt>(
171171 Direction :: Input => ext. on_input_type_rank_0 ( key_ty, registry) ,
172172 Direction :: Output => ext. on_output_type_rank_0 ( key_ty, registry) ,
173173 } ;
174+ // Zero-arg `impl Fn() + Send + Sync + 'static` fallback: after
175+ // the implementer's own rank-0 handler returns None, route the
176+ // empty arg-list to `dispatch_fn_input`. Non-empty Fn arities
177+ // are handled in the rank-N loop below.
178+ let res = res. or_else ( || {
179+ if dir != Direction :: Input {
180+ return None ;
181+ }
182+ let args = crate :: core:: registry:: extract_fn_trait_args ( key_ty) ?;
183+ if args. is_empty ( ) {
184+ ext. dispatch_fn_input ( & args, registry)
185+ } else {
186+ None
187+ }
188+ } ) ;
174189 return res. map ( |c| TypeEntry {
175190 destination : c. destination ,
176191 function : c. function ,
@@ -198,6 +213,25 @@ fn try_resolve_entry<E: PrebindgenExt>(
198213 }
199214 _ => unreachable ! ( "rank N is bounded to 0..=3 by MAX_RANK" ) ,
200215 } ;
216+ // Fallback for `impl Fn(args...) + Send + Sync + 'static`: after
217+ // the implementer's own rank-N handler returns None, route the
218+ // canonical `impl Fn(_, _, …)` pattern (every arg slot is a
219+ // wildcard) to `dispatch_fn_input`. Non-canonical patterns like
220+ // `impl Fn(Option<_>, _)` are left for user rank handlers — only
221+ // the all-wildcards shape gets the framework default.
222+ let result = result. or_else ( || {
223+ if dir != Direction :: Input {
224+ return None ;
225+ }
226+ let pat_args = crate :: core:: registry:: extract_fn_trait_args ( & pattern) ?;
227+ if pat_args. len ( ) != subs. len ( ) {
228+ return None ;
229+ }
230+ if !pat_args. iter ( ) . all ( |t| matches ! ( t, syn:: Type :: Infer ( _) ) ) {
231+ return None ;
232+ }
233+ ext. dispatch_fn_input ( subs. as_slice ( ) , registry)
234+ } ) ;
201235 // Last-step fallback for `impl Into<_> + Send + 'static`:
202236 // after the implementer's own rank-1 handler returns None,
203237 // assemble the source list (identity arm first when `target`
@@ -685,6 +719,44 @@ mod tests {
685719 assert_eq ! ( s[ 0 ] . 1 , vec![ "KeyExpr < 'static >" ] ) ;
686720 }
687721
722+ /// Codifies the canonical-shape detection used by the rank-N
723+ /// `dispatch_fn_input` fallback in [`try_resolve_entry`]. For
724+ /// `impl Fn(Option<Sample>)` at rank 1, `enumerate_wildcard_subs`
725+ /// emits both `impl Fn(_)` (canonical — every Fn arg slot is a
726+ /// wildcard, fallback should fire) and `impl Fn(Option<_>)`
727+ /// (non-canonical — fallback must skip so a user rank-1 handler
728+ /// can claim it). The fallback distinguishes them by checking that
729+ /// every element of `extract_fn_trait_args(&pattern)` is a
730+ /// `Type::Infer`; this test pins that invariant.
731+ #[ test]
732+ fn impl_fn_canonical_pattern_is_distinguishable_from_nested ( ) {
733+ use crate :: core:: registry:: extract_fn_trait_args;
734+ let t = ty ( "impl Fn(Option<Sample>) + Send + Sync + 'static" ) ;
735+ let v = enumerate_wildcard_subs ( & t, 1 ) ;
736+ // Two rank-1 variants: deepest-first (Option<_> over Sample),
737+ // then the canonical Fn(_) over Option<Sample>.
738+ assert_eq ! ( v. len( ) , 2 ) ;
739+ let mut canonical_count = 0 ;
740+ let mut nested_count = 0 ;
741+ for ( pattern, _subs) in & v {
742+ let args = extract_fn_trait_args ( pattern) . unwrap ( ) ;
743+ let all_infer = args. iter ( ) . all ( |a| matches ! ( a, syn:: Type :: Infer ( _) ) ) ;
744+ if all_infer {
745+ canonical_count += 1 ;
746+ } else {
747+ nested_count += 1 ;
748+ }
749+ }
750+ assert_eq ! (
751+ canonical_count, 1 ,
752+ "exactly one canonical `impl Fn(_)` pattern is expected"
753+ ) ;
754+ assert_eq ! (
755+ nested_count, 1 ,
756+ "exactly one nested `impl Fn(Option<_>)` pattern is expected"
757+ ) ;
758+ }
759+
688760 #[ test]
689761 fn impl_into_recognized_only_with_send_static ( ) {
690762 use crate :: core:: registry:: extract_into_trait_arg;
0 commit comments