From 7e9eef4868f9c0a140ca7db973fbc17abb3a5702 Mon Sep 17 00:00:00 2001 From: leycec Date: Sun, 21 Apr 2024 02:51:57 -0400 Subject: [PATCH] Nested beartype validators. This commit resolves a critical low-level issue in @beartype's dynamic type-checking code generator for **nested beartype validator-in-container type hints** (e.g., type hints of the form `list[typing.Annotated[{type}, Is[{validator}]]]`), resolving issue #372 kindly submitted by @ArneBachmann, fearless author of the peerless programming language [AWFUL (Arguably Worst F*cked-Up Language)](https://github.com/ArneBachmann/awful), which @leycec is promptly going to port the @beartype codebase to. Press F to doubt. The accursed @beartype 0.18.X release cycle continues to bedevil our world. (*Inchoate incoherence is the GOAT!*) --- beartype/_check/code/codecls.py | 116 ++++++------ beartype/_check/code/codemake.py | 99 +++++++---- beartype/_check/code/snip/codesnipstr.py | 71 +++++--- beartype/vale/_util/_valeutilsnip.py | 2 +- .../a20_util/api/test_utilapibeartype.py | 6 +- .../data/hint/pep/proposal/_data_pep593.py | 167 ++++++++++++++---- 6 files changed, 315 insertions(+), 146 deletions(-) diff --git a/beartype/_check/code/codecls.py b/beartype/_check/code/codecls.py index 1666dd97..24ae060a 100644 --- a/beartype/_check/code/codecls.py +++ b/beartype/_check/code/codecls.py @@ -82,8 +82,30 @@ def __init__(self) -> None: #FIXME: *NO*. We actually need these to be independent copies. Ergo, #there's *NO* alternative but to iteratively define one new instance - #of this dataclass for each index of this list. - obj_init=HintMeta(), + #of this dataclass for each index of this list. Indeed, this is + #actually a profound benefit. How? We can precompute the values of + #* "hint_curr_placeholder" *AT PYTHON STARTUP*. Just iteratively + # assign each "hint_curr_placeholder" according to its index. Do so + # based on the current logic of the enqueue_hint_child() method. + #* "pith_curr_var_name" *AT PYTHON STARTUP* in the exact same way. + # + #Indeed, this suggests we probably no longer need either: + #* "pith_curr_var_name_index". + #* The entire "codesnipcls" submodule. + # + #Well, isn't this turning out to be a significant facepalm. + #FIXME: Actually, the default of "None" here is fine. Let's instead: + #* Redefine the __getitem__() dunder method to dynamically inspect + # the item at the passed index. If: + # * "None", then replace this item with a new "HintMeta" instance + # suitable for the passed index. + # * Else, return the existing "HintMeta" instance at this index. + #* Refactor the enqueue_hint_child() method to then reassign the + # fields of this "HintMeta" instance to the desired values. + # + #This approach avoids expensive up-front computation at app startup, + #instead amortizing these costs across the app lifetime. Heh. + # obj_init=HintMeta(), ) # 0-based index of metadata describing the last visitable hint in this @@ -287,56 +309,46 @@ class HintMeta(object): indent_level_curr: int # ..................{ INITIALIZERS }.................. - def __init__(self) -> None: + def __init__( + self, + + # Mandatory parameters. + hint_curr_placeholder: str, + pith_curr_var_name_index: int, + + # Optional parameters. + hint_curr: object = None, + pith_curr_expr: str = '', + indent_level_curr: int = 2, + ) -> None: ''' Initialize this type-checking metadata dataclass. - ''' - # Initialize all instance variables to reasonable defaults. - self.hint_curr = None - self.hint_curr_placeholder = '' - self.pith_curr_expr = '' - self.pith_curr_var_name_index = 0 - self.indent_level_curr = 2 - - - #FIXME: Excise us up, sadly. - # def __init__( - # self, - # hint_curr: object, - # hint_curr_placeholder: str, - # pith_curr_expr: str, - # pith_curr_var_name_index: int, - # indent_level_curr: int, - # ) -> None: - # ''' - # Initialize this type-checking metadata dataclass. - # - # Parameters - # ---------- - # hint_curr : object - # Type hint currently visited by this BFS. - # hint_curr_placeholder : str - # Type-checking placeholder substring. See the class docstring. - # pith_curr_expr : str - # Pith expression. See the class docstring. - # pith_curr_var_name_index : int - # Pith variable name index. See the class docstring. - # indent_level_curr : int - # Indentation level. See the class docstring. - # ''' - # assert isinstance(hint_curr_placeholder, str) - # assert isinstance(pith_curr_expr, str) - # assert isinstance(pith_curr_var_name_index, int) - # assert isinstance(indent_level_curr, int) - # assert hint_curr_placeholder - # assert pith_curr_expr - # assert pith_curr_var_name_index >= 0 - # assert indent_level_curr > 1 - # - # # Classify all passed parameters. - # self.hint_curr = hint_curr - # self.hint_curr_placeholder = hint_curr_placeholder - # self.pith_curr_expr = pith_curr_expr - # self.pith_curr_var_name_index = pith_curr_var_name_index - # self.indent_level_curr = indent_level_curr + Parameters + ---------- + hint_curr : object + Type hint currently visited by this BFS. + hint_curr_placeholder : str + Type-checking placeholder substring. See the class docstring. + pith_curr_expr : str + Pith expression. See the class docstring. + pith_curr_var_name_index : int + Pith variable name index. See the class docstring. + indent_level_curr : int + Indentation level. See the class docstring. + ''' + assert isinstance(hint_curr_placeholder, str) + assert isinstance(pith_curr_expr, str) + assert isinstance(pith_curr_var_name_index, int) + assert isinstance(indent_level_curr, int) + assert hint_curr_placeholder + assert pith_curr_expr + assert pith_curr_var_name_index >= 0 + assert indent_level_curr > 1 + + # Classify all passed parameters. + self.hint_curr = hint_curr + self.hint_curr_placeholder = hint_curr_placeholder + self.pith_curr_expr = pith_curr_expr + self.pith_curr_var_name_index = pith_curr_var_name_index + self.indent_level_curr = indent_level_curr diff --git a/beartype/_check/code/codemake.py b/beartype/_check/code/codemake.py index 42c72ee0..5281670e 100644 --- a/beartype/_check/code/codemake.py +++ b/beartype/_check/code/codemake.py @@ -66,6 +66,7 @@ CODE_PEP484604_UNION_CHILD_NONPEP_format, CODE_PEP484604_UNION_PREFIX, CODE_PEP484604_UNION_SUFFIX, + CODE_PEP572_PITH_ASSIGN_EXPR_format, CODE_PEP586_LITERAL_format, CODE_PEP586_PREFIX_format, CODE_PEP586_SUFFIX, @@ -73,8 +74,6 @@ CODE_PEP593_VALIDATOR_METAHINT_format, CODE_PEP593_VALIDATOR_PREFIX, CODE_PEP593_VALIDATOR_SUFFIX_format, - CODE_PEP572_PITH_ASSIGN_AND_format, - CODE_PEP572_PITH_ASSIGN_EXPR_format, ) from beartype._check.convert.convsanify import ( sanify_hint_child_if_unignorable_or_none, @@ -1290,8 +1289,7 @@ def _enqueue_hint_child(pith_child_expr: str) -> str: ), )) - # For each PEP-compliant child hint of this union, generate - # and append code type-checking this child hint. + # For the 0-based index and each child hint of this union... for hint_child_index, hint_child in enumerate( hint_childs_pep): # Code deeply type-checking this child hint. @@ -1747,24 +1745,40 @@ def _enqueue_hint_child(pith_child_expr: str) -> str: exception_prefix=EXCEPTION_PREFIX, ) - # If the expression assigning the current pith expression to - # a local variable is *NOT* the current pith expression, - # then the current pith expression requires assignment - # *BEFORE* access. This is the common case when this pith is - # a child pith rather than the root pith. In this case... - if pith_curr_assign_expr is not pith_curr_expr: - # Code assigning the current pith expression. - func_curr_code += CODE_PEP572_PITH_ASSIGN_AND_format( - indent_curr=indent_curr, - pith_curr_assign_expr=pith_curr_assign_expr, - pith_curr_var_name=pith_curr_var_name, - ) - # Else, the current pith expression does *NOT* require - # assignment access. - - # If this metahint is unignorable, deeply type-check - # this metahint. Specifically... - if hint_child is not None: + # Python expression yielding the value of the current pith, + # defaulting to the name of the local variable assigned to + # by the assignment expression performed below. + hint_curr_expr = pith_curr_var_name + + # Tuple of the one or more beartype validators annotating + # this metahint. + hints_child = get_hint_pep593_metadata(hint_curr) + # print(f'hints_child: {repr(hints_child)}') + + # If this metahint is ignorable... + if hint_child is None: + # If this metahint is annotated by only one beartype + # validator, the most efficient expression yielding the + # value of the current pith is simply the full Python + # expression *WITHOUT* assigning that value to a + # reusable local variable in an assignment expression. + # *NO* assignment expression is needed in this case. + # + # Why? Because beartype validators are *NEVER* recursed + # into. Each beartype validator is guaranteed to be the + # leaf of a type-checking subtree, guaranteeing this + # pith to be evaluated only once. + if len(hints_child) == 1: + hint_curr_expr = pith_curr_expr + # Else, this metahint is annotated by two or more + # beartype validators. In this case, the most efficient + # expression yielding the value of the current pith is + # the assignment expression assigning this value to a + # reusable local variable. + else: + hint_curr_expr = pith_curr_assign_expr + # Else, this metahint is unignorable. In this case... + else: # Code deeply type-checking this metahint. func_curr_code += CODE_PEP593_VALIDATOR_METAHINT_format( indent_curr=indent_curr, @@ -1780,12 +1794,13 @@ def _enqueue_hint_child(pith_child_expr: str) -> str: # child hint and one or more arbitrary objects. # Ergo, we need *NOT* explicitly validate that here. hint_child_placeholder=_enqueue_hint_child( - pith_curr_var_name), + pith_curr_assign_expr), ) # Else, this metahint is ignorable. - # For each beartype validator annotating this metahint... - for hint_child in get_hint_pep593_metadata(hint_curr): + # For the 0-based index and each beartype validator + # annotating this metahint... + for hint_child_index, hint_child in enumerate(hints_child): # print(f'Type-checking PEP 593 type hint {repr(hint_curr)} argument {repr(hint_child)}...') # If this is *NOT* a beartype validator, raise an # exception. @@ -1804,18 +1819,40 @@ def _enqueue_hint_child(pith_child_expr: str) -> str: f'beartype validator).' ) # Else, this argument is beartype-specific. - - # Generate and append efficient code type-checking this - # validator by embedding this code as is. + # + # If this is any beartype validator *EXCEPT* the first, + # set the Python expression yielding the value of the + # current pith to the name of the local variable + # assigned to by the prior assignment expression. By + # deduction, it *MUST* be the case now that either: + # * This metahint was unignorable, in which case this + # assignment uselessly reduplicates the exact same + # assignment performed above. While non-ideal, this + # assignment is sufficiently efficient to make any + # optimizations here effectively worthless. + # * This metahint was ignorable, in which case this + # expression was set above to the assignment + # expression assigning this pith for the first + # beartype validator. Since this iteration has already + # processed the first beartype validator, this + # assignment expression has already been performed. + # Avoid inefficiently re-performing this assignment + # expression for each additional beartype validator by + # efficiently reusing the previously assigned local. + elif hint_child_index: + hint_curr_expr = pith_curr_var_name + # Else, this is the first beartype validator. See above. + + # Code deeply type-checking this validator. func_curr_code += CODE_PEP593_VALIDATOR_IS_format( indent_curr=indent_curr, # Python expression formatting the current pith into - # the "{obj}" variable already embedded by that - # class into this code. + # the "{obj}" format substring previously embedded + # by this validator into this code string. hint_child_expr=hint_child._is_valid_code.format( # Indentation unique to this child hint. indent=INDENT_LEVEL_TO_CODE[indent_level_child], - obj=pith_curr_var_name, + obj=hint_curr_expr, ), ) diff --git a/beartype/_check/code/snip/codesnipstr.py b/beartype/_check/code/snip/codesnipstr.py index d0a2ecc8..6ccb294f 100644 --- a/beartype/_check/code/snip/codesnipstr.py +++ b/beartype/_check/code/snip/codesnipstr.py @@ -65,29 +65,30 @@ ''' -CODE_PEP572_PITH_ASSIGN_AND = ''' -{indent_curr} # Localize this pith as a stupidly fast assignment expression. -{indent_curr} ({pith_curr_assign_expr}) is {pith_curr_var_name} and''' -''' -Code snippet embedding an assignment expression assigning the full Python -expression yielding the value of the current pith to a unique local variable. - -This snippet is itself intended to be embedded in higher-level code snippets as -the first child expression of those snippets, enabling subsequent expressions in -those snippets to efficiently obtain this pith via this efficient variable -rather than via this inefficient full Python expression. - -This snippet is a tautology that is guaranteed to evaluate to :data:`True` whose -side effect is this assignment expression. Note that there exist numerous less -efficient alternatives, including: - -* ``({pith_curr_assign_expr}).__class__``, which is also guaranteed to evaluate - to :data:`True` but which implicitly triggers the ``__getattr__()`` dunder - method and thus incurs a performance penalty for user-defined objects - inefficiently overriding that method. -* ``isinstance({pith_curr_assign_expr}, object)``, which is also guaranteed to - evaluate :data:`True` but which is surprisingly inefficient in all cases. -''' +#FIXME: Preserved for posterity in the likelihood we'll need this again. *sigh* +# CODE_PEP572_PITH_ASSIGN_AND = ''' +# {indent_curr} # Localize this pith as a stupidly fast assignment expression. +# {indent_curr} ({pith_curr_assign_expr}) is {pith_curr_var_name} and''' +# ''' +# Code snippet embedding an assignment expression assigning the full Python +# expression yielding the value of the current pith to a unique local variable. +# +# This snippet is itself intended to be embedded in higher-level code snippets as +# the first child expression of those snippets, enabling subsequent expressions in +# those snippets to efficiently obtain this pith via this efficient variable +# rather than via this inefficient full Python expression. +# +# This snippet is a tautology that is guaranteed to evaluate to :data:`True` whose +# side effect is this assignment expression. Note that there exist numerous less +# efficient alternatives, including: +# +# * ``({pith_curr_assign_expr}).__class__``, which is also guaranteed to evaluate +# to :data:`True` but which implicitly triggers the ``__getattr__()`` dunder +# method and thus incurs a performance penalty for user-defined objects +# inefficiently overriding that method. +# * ``isinstance({pith_curr_assign_expr}, object)``, which is also guaranteed to +# evaluate :data:`True` but which is surprisingly inefficient in all cases. +# ''' # ....................{ HINT ~ pep : (484|585) : generic }.................... CODE_PEP484585_GENERIC_PREFIX = '''( @@ -390,12 +391,26 @@ ''' # ....................{ HINT ~ pep : 484 : instance }.................... -CODE_PEP484_INSTANCE = ( - '''isinstance({pith_curr_expr}, {hint_curr_expr})''') +CODE_PEP484_INSTANCE = '''isinstance({pith_curr_expr}, {hint_curr_expr})''' ''' :pep:`484`-compliant code snippet type-checking the current pith against the current child PEP-compliant type expected to be a trivial non-:mod:`typing` type (e.g., :class:`int`, :class:`str`). + +Caveats +------- +**This snippet is intentionally compact rather than embedding a human-readable +comment.** For example, this snippet intentionally avoids doing this: + +.. code-block:: python + + CODE_PEP484_INSTANCE = ' + {indent_curr}# True only if this pith is of this type. + {indent_curr}isinstance({pith_curr_expr}, {hint_curr_expr})' + +Although feasible, doing that would significantly complicate code generation for +little to *no* tangible gain. Indeed, we actually tried doing that once. We +failed hard after breaking everything. **Avoid the mistakes of the past.** ''' # ....................{ HINT ~ pep : 484 : union }.................... @@ -519,7 +534,7 @@ CODE_PEP593_VALIDATOR_IS = ''' {indent_curr} # True only if this pith satisfies this caller-defined -{indent_curr} # validator of this annotated. +{indent_curr} # validator of this annotated metahint. {indent_curr} {hint_child_expr} and''' ''' :pep:`593`-compliant code snippet type-checking the current pith against @@ -575,8 +590,8 @@ CODE_PEP484604_UNION_CHILD_PEP.format) CODE_PEP484604_UNION_CHILD_NONPEP_format: Callable = ( CODE_PEP484604_UNION_CHILD_NONPEP.format) -CODE_PEP572_PITH_ASSIGN_AND_format: Callable = ( - CODE_PEP572_PITH_ASSIGN_AND.format) +# CODE_PEP572_PITH_ASSIGN_AND_format: Callable = ( +# CODE_PEP572_PITH_ASSIGN_AND.format) CODE_PEP572_PITH_ASSIGN_EXPR_format: Callable = ( CODE_PEP572_PITH_ASSIGN_EXPR.format) CODE_PEP586_LITERAL_format: Callable = ( diff --git a/beartype/vale/_util/_valeutilsnip.py b/beartype/vale/_util/_valeutilsnip.py index 48f52c55..f63d8fd8 100644 --- a/beartype/vale/_util/_valeutilsnip.py +++ b/beartype/vale/_util/_valeutilsnip.py @@ -35,7 +35,7 @@ VALE_CODE_CHECK_ISINSTANCE_TEST = ''' -{{indent}}# True only if this pith is an object instancing this superclass. +{{indent}}# True only if this pith is of this type. {{indent}}isinstance({{obj}}, {param_name_types})''' ''' :attr:`beartype.vale.IsInstance`-specific code snippet validating an arbitrary diff --git a/beartype_test/a00_unit/a20_util/api/test_utilapibeartype.py b/beartype_test/a00_unit/a20_util/api/test_utilapibeartype.py index c9bfb931..86364423 100644 --- a/beartype_test/a00_unit/a20_util/api/test_utilapibeartype.py +++ b/beartype_test/a00_unit/a20_util/api/test_utilapibeartype.py @@ -30,9 +30,9 @@ def test_is_func_beartyped() -> None: @beartype def where_that_or() -> str: ''' - Arbitrary callable decorated by the :func:`beartype.beartype` - decorator intentionally annotated by one or more arbitrary unignorable - type hints to prevent that decorator from silently reducing to a noop. + Arbitrary callable decorated by the :func:`beartype.beartype` decorator + intentionally annotated by one or more arbitrary unignorable type hints + to prevent that decorator from silently reducing to a noop. ''' return 'In the still cave of the witch Poesy,' diff --git a/beartype_test/a00_unit/data/hint/pep/proposal/_data_pep593.py b/beartype_test/a00_unit/data/hint/pep/proposal/_data_pep593.py index 14a9f474..ed9eb482 100644 --- a/beartype_test/a00_unit/data/hint/pep/proposal/_data_pep593.py +++ b/beartype_test/a00_unit/data/hint/pep/proposal/_data_pep593.py @@ -22,6 +22,7 @@ def hints_pep593_meta() -> 'List[HintPepMetadata]': from beartype.typing import ( List, Sequence, + TypeVar, Union, ) from beartype.vale import ( @@ -37,6 +38,7 @@ def hints_pep593_meta() -> 'List[HintPepMetadata]': HintSignUnion, ) from beartype._util.api.utilapityping import get_typing_attrs + from beartype._util.py.utilpyversion import IS_PYTHON_AT_LEAST_3_10 from beartype_test.a00_unit.data.data_type import ( Class, Subclass, @@ -77,11 +79,21 @@ def is_true(ignorable_arg, obj): is_true, 'Drank its inspiring radiance, and the wind') # ..................{ VALIDATORS ~ is }.................. - # Beartype validators defined as lambda functions. + # PEP 484-compliant union of all builtin scalar types. + Number = Union[int, float, complex] + + # Generic beartype validators defined as lambda functions. + IsNonEmpty = Is[lambda obj: bool(obj)] + + # Numeric beartype validators defined as lambda functions. + IsNonNegative = Is[lambda number: number >= 0] + IsIntNonZero = Is[lambda number: isinstance(number, int) and number != 0] + + # Textual beartype validators defined as lambda functions. IsLengthy = Is[lambda text: len(text) > 30] IsSentence = Is[lambda text: text and text[-1] == '.'] - # Beartype validators defined as non-lambda functions. + # Textual beartype validators defined as non-lambda functions. def _is_quoted(text): return '"' in text or "'" in text def _is_exceptional(obj): @@ -89,7 +101,7 @@ def _is_exceptional(obj): IsQuoted = Is[_is_quoted] IsExceptional = Is[_is_exceptional] - # Beartype validator synthesized from the above validators via the + # Textual beartype validator synthesized from the above validators via the # domain-specific language (DSL) implemented by those validators. IsLengthyOrUnquotedSentence = IsLengthy | (IsSentence & ~IsQuoted) @@ -269,8 +281,8 @@ def __init__(self) -> None: ), # Annotated of an isinstanceable type annotated by two or more - # beartype-specific validators all defined as functions, specified - # with comma-delimited list syntax. + # beartype validators all defined as functions, specified with + # comma-delimited list syntax. HintPepMetadata( hint=Annotated[str, IsLengthy, IsSentence, IsQuoted], pep_sign=HintSignAnnotated, @@ -304,9 +316,8 @@ def __init__(self) -> None: ), ), - # Annotated of an isinstanceable type annotated by one - # beartype-specific validator synthesized from all possible - # operators. + # Annotated of an isinstanceable type annotated by one beartype + # validator synthesized from all possible operators. HintPepMetadata( hint=Annotated[str, IsLengthyOrUnquotedSentence], pep_sign=HintSignAnnotated, @@ -341,8 +352,8 @@ def __init__(self) -> None: # ..............{ ANNOTATED ~ beartype : is : & }.............. # Annotated of an isinstanceable type annotated by two or more - # validators all defined as functions, specified with "&"-delimited - # operator syntax. + # beartype validators all defined as functions, specified with + # "&"-delimited operator syntax. HintPepMetadata( hint=Annotated[str, IsLengthy & IsSentence & IsQuoted], pep_sign=HintSignAnnotated, @@ -377,10 +388,10 @@ def __init__(self) -> None: ), # Annotated of an isinstanceable type annotated by two or more - # validators all defined as functions, specified with "&"-delimited - # operator syntax such that the first such validator short-circuits - # all subsequent such validators, which all intentionally raise - # exceptions to prove they are silently ignored. + # beartype validators all defined as functions, specified with + # "&"-delimited operator syntax such that the first such validator + # short-circuits all subsequent such validators, which all + # intentionally raise exceptions to prove they are silently ignored. # # Note this hint is *NOT* safely satisfiable. Ergo, we # intentionally do *NOT* validate this hint to be satisfied. @@ -399,10 +410,10 @@ def __init__(self) -> None: # ..............{ ANNOTATED ~ beartype : is : | }.............. # Annotated of an isinstanceable type annotated by two or more - # validators all defined as functions, specified with "|"-delimited - # operator syntax such that the first such validator short-circuits - # all subsequent such validators, which all intentionally raise - # exceptions to prove they are silently ignored. + # beartype validators all defined as functions, specified with + # "|"-delimited operator syntax such that the first such validator + # short-circuits all subsequent such validators, which all + # intentionally raise exceptions to prove they are silently ignored. # # Note this hint is *NOT* safely unsatisfiable. Ergo, we # intentionally do *NOT* validate this hint to be unsatisfied. @@ -423,7 +434,7 @@ def __init__(self) -> None: # ..............{ ANNOTATED ~ beartype : is : nest }.............. # Annotated of an annotated of an isinstanceable type, each - # annotated by a beartype-specific validator defined as a function. + # annotated by a beartype validator defined as a function. HintPepMetadata( hint=Annotated[Annotated[str, IsLengthy], IsSentence], pep_sign=HintSignAnnotated, @@ -443,7 +454,7 @@ def __init__(self) -> None: ), # List of annotateds of isinstanceable types annotated by one - # beartype-specific validator defined as a lambda function. + # beartype validator defined as a lambda function. HintPepMetadata( hint=List[AnnotatedStrIsLength], pep_sign=HintSignList, @@ -473,9 +484,63 @@ def __init__(self) -> None: ), ), + # List of lists of annotateds of an ignorable type hint annotated by + # a type variable bounded by a beartype validator defined as a + # lambda function. + HintPepMetadata( + hint=List[List[TypeVar('AWhirlwindSweptItOn', bound=( + Annotated[object, IsIntNonZero]))]], + pep_sign=HintSignList, + isinstanceable_type=list, + is_pep585_builtin_subscripted=List is list, + is_typevars=True, + piths_meta=( + # List of lists of non-zero integer constants. + HintPithSatisfiedMetadata([[16, 17, 20], [21, 64, 65, 68]]), + # String constant *NOT* an instance of the expected type. + HintPithUnsatisfiedMetadata( + 'The waves arose. Higher and higher still'), + # List of lists of non-integers and zeroes. + HintPithUnsatisfiedMetadata([ + [ + 'Their fierce necks writhed', + "beneath the tempest's scourge", + ], + [0, 0, 0, 0], + ]), + ), + ), + + # List of lists of annotateds of an ignorable type hint annotated by + # a type variable bounded by two beartype validators, one defined as + # a lambda function and one not. + HintPepMetadata( + hint=List[List[TypeVar('WithFierceGusts', bound=( + Annotated[object, IsInstance[int], IsNonEmpty]))]], + pep_sign=HintSignList, + isinstanceable_type=list, + is_pep585_builtin_subscripted=List is list, + is_typevars=True, + piths_meta=( + # List of lists of non-zero integer constants. + HintPithSatisfiedMetadata([[16, 17, 20], [21, 64, 65, 68]]), + # String constant *NOT* an instance of the expected type. + HintPithUnsatisfiedMetadata( + 'The waves arose. Higher and higher still'), + # List of lists of non-integers and zeroes. + HintPithUnsatisfiedMetadata([ + [ + 'Their fierce necks writhed', + "beneath the tempest's scourge", + ], + [0, 0, 0, 0], + ]), + ), + ), + # ..............{ ANNOTATED ~ beartype : isattr }.............. - # Annotated of an isinstanceable type annotated by one - # beartype-specific attribute validator. + # Annotated of an isinstanceable type annotated by one beartype + # attribute validator. HintPepMetadata( hint=Annotated[ CathecticallyEnsconceYouIn, IsAttrThisMobbedTristeOf], @@ -497,8 +562,8 @@ def __init__(self) -> None: ), # ..............{ ANNOTATED ~ beartype : isequal }.............. - # Annotated of an isinstanceable type annotated by one - # beartype-specific equality validator. + # Annotated of an isinstanceable type annotated by one beartype + # equality validator. HintPepMetadata( hint=Annotated[List[str], IsEqual[AMPLY_IMPISH]], pep_sign=HintSignAnnotated, @@ -525,8 +590,8 @@ def __init__(self) -> None: ), # ..............{ ANNOTATED ~ beartype : isinstance }.............. - # Annotated of an isinstanceable type annotated by one - # beartype-specific type instance validator. + # Annotated of an isinstanceable type annotated by one beartype type + # instance validator. HintPepMetadata( hint=Annotated[Class, ~IsInstance[SubclassSubclass]], pep_sign=HintSignAnnotated, @@ -555,9 +620,9 @@ def __init__(self) -> None: ), # Type hint matching *ANY* sequence of strings, defined as the union - # of a validator matching any sequence of strings that is *NOT* - # itself a string with a string. Although odd, this exercises an - # obscure edge case in code generation. + # of a beartype validator matching any sequence of strings that is + # *NOT* itself a string with a string. Although odd, this exercises + # an obscure edge case in code generation. HintPepMetadata( hint=Union[SequenceNonstrOfStr, str], pep_sign=HintSignUnion, @@ -591,8 +656,8 @@ def __init__(self) -> None: ), # ..............{ ANNOTATED ~ beartype : issubclass }.............. - # Annotated of an isinstanceable type annotated by one - # beartype-specific type inheritance validator. + # Annotated of an isinstanceable type annotated by one beartype type + # inheritance validator. HintPepMetadata( hint=Annotated[type, IsSubclass[Class]], pep_sign=HintSignAnnotated, @@ -615,6 +680,46 @@ def __init__(self) -> None: ), )) + # ..................{ VERSION }.................. + # If the active Python interpreter targets Python >= 3.10, the + # "typing.Annotated" type factory supports the "|" operator. In this + # case, defined unions of annotateds with this operator. + if IS_PYTHON_AT_LEAST_3_10: + # Add PEP 593-specific test type hints to this tuple global. + hints_pep_meta.extend(( + # ..............{ ANNOTATED ~ beartype : is : nes}.............. + # List of lists of annotateds of a union of isinstanceable types + # annotated by a type variable bounded by a union of beartype + # validators defined as lambda functions. + HintPepMetadata( + hint=List[List[TypeVar('TheStrainingBoat', bound=( + Annotated[Number, IsNonNegative] | + Annotated[str, IsNonEmpty] + ))]], + pep_sign=HintSignList, + isinstanceable_type=list, + is_pep585_builtin_subscripted=List is list, + is_typevars=True, + piths_meta=( + # List of lists of positive number constants. + HintPithSatisfiedMetadata([[11, 0.11], [1, 110, 1101100]]), + # List of lists of non-empty string constants. + HintPithSatisfiedMetadata([ + ['The straining boat.', '—A whirlwind swept it on,'], + ['With fierce gusts and', 'precipitating force,'], + ]), + # String constant *NOT* an instance of the expected type. + HintPithUnsatisfiedMetadata( + 'Through the white ridges of the chafèd sea.'), + # List of lists of negative numbers and empty strings. + HintPithUnsatisfiedMetadata([[-1, '', -0.4], ['', -5, '']]), + ), + ), + )) + # Else, the active Python interpreter targets Python < 3.10. In this + # case, the "typing.Annotated" type factory fails to support the "|" + # operator. + # ..................{ RETURN }.................. # Return this list of all PEP-specific type hint metadata. return hints_pep_meta