@@ -399,16 +399,45 @@ def set_continuation(self, continuation: invocation.ConfiguredContinuation) -> N
399399 def _sameish_invocations (
400400 self , same : invocation .StubbedInvocation
401401 ) -> list [invocation .StubbedInvocation ]:
402- return [
403- invoc
404- for invoc in self .stubbed_invocations
405- if (
406- invoc is not same
407- and invoc .method_name == same .method_name
408- and invoc .matches (same )
409- and same .matches (invoc )
410- )
411- ]
402+ """Find prior stubs that are *mutually* signature-compatible.
403+
404+ This is used only for continuation bookkeeping (value-vs-chain mode),
405+ not for runtime call dispatch. We intentionally do a symmetric check
406+ (`a.matches(b)` and `b.matches(a)`) to approximate "same signature"
407+ despite one-way matchers like `any()`.
408+
409+ Why this exists: repeated selectors such as
410+
411+ when(cat).meow().purr()
412+ when(cat).meow().roll()
413+
414+ should share the same root continuation for `meow()`.
415+ """
416+ sameish : list [invocation .StubbedInvocation ] = []
417+ for invoc in self .stubbed_invocations :
418+ if invoc is same :
419+ continue
420+
421+ if invoc .method_name != same .method_name :
422+ continue
423+
424+ if self ._invocations_are_sameish (invoc , same ):
425+ sameish .append (invoc )
426+
427+ return sameish
428+
429+ def _invocations_are_sameish (
430+ self ,
431+ left : invocation .StubbedInvocation ,
432+ right : invocation .StubbedInvocation ,
433+ ) -> bool :
434+ # Be conservative in internal equivalence probing: user predicates from
435+ # `arg_that` can throw when evaluated against matcher/sentinel objects.
436+ # In this phase, exceptions should mean "not equivalent", not failure.
437+ try :
438+ return left .matches (right ) and right .matches (left )
439+ except Exception :
440+ return False
412441
413442 def get_original_method (self , method_name : str ) -> object | None :
414443 return self ._original_methods .get (method_name , None )
0 commit comments