@@ -203,74 +203,6 @@ def register_constant(name: str, constant: Any):
203203 registered_constants [name ] = constant
204204
205205
206- def coerce (value ): # Nobody uses coerce. pylint: disable=redefined-builtin
207- """Returns the 'intended' matcher given by `value`.
208-
209- If `value` is already a matcher, then this is what is returned.
210-
211- If `value` is anything else, then coerce returns `ImplicitEquals(value)`.
212-
213- Args:
214- value: Either a Matcher, or a value to compare for equality.
215- """
216- if isinstance (value , Matcher ):
217- return value
218- else :
219- return ImplicitEquals (value )
220-
221-
222- def _coerce_list (values ):
223- return [coerce (v ) for v in values ]
224-
225-
226- # TODO(b/199577701): drop the **kwargs: Any in the *_attrib functions.
227-
228- _IS_SUBMATCHER_ATTRIB = __name__ + '._IS_SUBMATCHER_ATTRIB'
229- _IS_SUBMATCHER_LIST_ATTRIB = __name__ + '._IS_SUBMATCHER_LIST_ATTRIB'
230-
231-
232- def submatcher_attrib (* args , walk : bool = True , ** kwargs : Any ):
233- """Creates an attr.ib that is marked as a submatcher.
234-
235- This will cause the matcher to be automatically walked as part of the
236- computation of .bind_variables. Any submatcher that can introduce a binding
237- must be listed as a submatcher_attrib or submatcher_list_attrib.
238-
239- Args:
240- *args: Forwarded to attr.ib.
241- walk: Whether or not to walk to accumulate .bind_variables.
242- **kwargs: Forwarded to attr.ib.
243-
244- Returns:
245- An attr.ib()
246- """
247- if walk :
248- kwargs .setdefault ('metadata' , {})[_IS_SUBMATCHER_ATTRIB ] = True
249- kwargs .setdefault ('converter' , coerce )
250- return attr .ib (* args , ** kwargs )
251-
252-
253- def submatcher_list_attrib (* args , walk : bool = True , ** kwargs : Any ):
254- """Creates an attr.ib that is marked as an iterable of submatchers.
255-
256- This will cause the matcher to be automatically walked as part of the
257- computation of .bind_variables. Any submatcher that can introduce a binding
258- must be listed as a submatcher_attrib or submatcher_list_attrib.
259-
260- Args:
261- *args: Forwarded to attr.ib.
262- walk: Whether or not to walk to accumulate .bind_variables.
263- **kwargs: Forwarded to attr.ib.
264-
265- Returns:
266- An attr.ib()
267- """
268- if walk :
269- kwargs .setdefault ('metadata' , {})[_IS_SUBMATCHER_LIST_ATTRIB ] = True
270- kwargs .setdefault ('converter' , _coerce_list )
271- return attr .ib (* args , ** kwargs )
272-
273-
274206# TODO: make MatchObject, MatchInfo, and Matcher generic, parameterized
275207# by match type. Since pytype doesn't support generics yet, that's not an
276208# option, but it'd greatly clarify the API by allowing us to classify matchers
@@ -921,6 +853,16 @@ def bind_variables(self):
921853 type_filter = None
922854
923855
856+ class ContextualMatcher (Matcher ):
857+ """A matcher which requires special understanding in context.
858+
859+ By default, contextual matchers are not allowed inside of a submatcher
860+ attribute.
861+ To allow one, specify, for instance,
862+ ``submatcher_attrib(contextual=MyContextualMatcher)``.
863+ """
864+
865+ pass
924866
925867
926868def accumulating_matcher (f ):
@@ -1026,6 +968,106 @@ class ParseError(Exception):
1026968 """
1027969
1028970
971+ def coerce (value ): # Nobody uses coerce. pylint: disable=redefined-builtin
972+ """Returns the 'intended' matcher given by `value`.
973+
974+ If `value` is already a matcher, then this is what is returned.
975+
976+ If `value` is anything else, then coerce returns `ImplicitEquals(value)`.
977+
978+ Args:
979+ value: Either a Matcher, or a value to compare for equality.
980+ """
981+ if isinstance (value , Matcher ):
982+ return value
983+ else :
984+ return ImplicitEquals (value )
985+
986+
987+ def _coerce_list (values ):
988+ return [coerce (v ) for v in values ]
989+
990+
991+ # TODO(b/199577701): drop the **kwargs: Any in the *_attrib functions.
992+
993+ _IS_SUBMATCHER_ATTRIB = __name__ + '._IS_SUBMATCHER_ATTRIB'
994+ _IS_SUBMATCHER_LIST_ATTRIB = __name__ + '._IS_SUBMATCHER_LIST_ATTRIB'
995+
996+
997+ def _submatcher_validator (old_validator , contextual ):
998+ def validator (o : object , attribute : attr .Attribute , m : Matcher ):
999+ if isinstance (m , ContextualMatcher ) and not isinstance (m , contextual ):
1000+ raise TypeError (
1001+ f'Cannot use a `{ m } ` in `{ type (o ).__name__ } .{ attribute .name } `.'
1002+ )
1003+ if old_validator is not None :
1004+ old_validator (o , attribute , m )
1005+
1006+ return validator
1007+
1008+
1009+ def submatcher_attrib (
1010+ * args ,
1011+ walk : bool = True ,
1012+ contextual : type [ContextualMatcher ]
1013+ | tuple [type [ContextualMatcher ], ...] = (),
1014+ ** kwargs : Any ,
1015+ ):
1016+ """Creates an attr.ib that is marked as a submatcher.
1017+
1018+ This will cause the matcher to be automatically walked as part of the
1019+ computation of .bind_variables. Any submatcher that can introduce a binding
1020+ must be listed as a submatcher_attrib or submatcher_list_attrib.
1021+
1022+ Args:
1023+ *args: Forwarded to attr.ib.
1024+ walk: Whether or not to walk to accumulate .bind_variables.
1025+ contextual: The contextual matcher classes to allow, if any.
1026+ **kwargs: Forwarded to attr.ib.
1027+
1028+ Returns:
1029+ An attr.ib()
1030+ """
1031+ if walk :
1032+ kwargs .setdefault ('metadata' , {})[_IS_SUBMATCHER_ATTRIB ] = True
1033+ kwargs .setdefault ('converter' , coerce )
1034+ kwargs ['validator' ] = _submatcher_validator (
1035+ kwargs .get ('validator' ), contextual
1036+ )
1037+ return attr .ib (* args , ** kwargs )
1038+
1039+
1040+ def submatcher_list_attrib (
1041+ * args ,
1042+ walk : bool = True ,
1043+ contextual : type [ContextualMatcher ]
1044+ | tuple [type [ContextualMatcher ], ...] = (),
1045+ ** kwargs : Any ,
1046+ ):
1047+ """Creates an attr.ib that is marked as an iterable of submatchers.
1048+
1049+ This will cause the matcher to be automatically walked as part of the
1050+ computation of .bind_variables. Any submatcher that can introduce a binding
1051+ must be listed as a submatcher_attrib or submatcher_list_attrib.
1052+
1053+ Args:
1054+ *args: Forwarded to attr.ib.
1055+ walk: Whether or not to walk to accumulate .bind_variables.
1056+ contextual: The contextual matcher classes to allow, if any.
1057+ **kwargs: Forwarded to attr.ib.
1058+
1059+ Returns:
1060+ An attr.ib()
1061+ """
1062+ if walk :
1063+ kwargs .setdefault ('metadata' , {})[_IS_SUBMATCHER_LIST_ATTRIB ] = True
1064+ kwargs .setdefault ('converter' , _coerce_list )
1065+ kwargs ['validator' ] = attr .validators .deep_iterable (
1066+ _submatcher_validator (kwargs .get ('validator' ), contextual )
1067+ )
1068+ return attr .ib (* args , ** kwargs )
1069+
1070+
10291071@attr .s (frozen = True )
10301072class _CompareById :
10311073 """Wrapper object to compare things by identity."""
0 commit comments