Skip to content

Commit

Permalink
Reiterables x 5.
Browse files Browse the repository at this point in the history
This commit is the next in a commit chain deeply type-checking
**reiterables** (i.e., collections satisfying the
`collections.abc.Collection` protocol with guaranteed `O(1)` read-only
access to *only* the first collection item), en-route to *finally*
resolving feature request #167 kindly submitted by the perennial
brilliant @langfield (...*how I miss that awesome guy!*) several
lifetimes ago back when I was probably a wandering vagabond Buddhist
monk with a bad attitude, a begging bowl the size of my emaciated torso,
and an honestly pretty cool straw hat that glinted dangerously in the
firelight. Note that reiterables include *all* containers matched by one
or more of the following PEP 484- or 585-compliant type hints:

* `frozenset[...]`.
* `set[...]`.
* `collections.deque[...]`.
* `collections.abc.Collection[...]`.
* `collections.abc.KeysView[...]`.
* `collections.abc.MutableSet[...]`.
* `collections.abc.Set[...]`.
* `collections.abc.ValuesView[...]`.
* `typing.AbstractSet[...]`.
* `typing.Collection[...]`.
* `typing.Deque[...]`.
* `typing.FrozenSet[...]`.
* `typing.KeysView[...]`.
* `typing.MutableSet[...]`.
* `typing.Set[...]`.
* `typing.ValuesView[...]`.

Specifically, this commit continues reducing problematic DRY (Don't
Repeat Yourself) violations in @beartype's internal type-checking code
generator. (*Adriatic ranting on anti-fantastical aphantasia!*)
  • Loading branch information
leycec committed Jun 12, 2024
1 parent 465da19 commit c451c9e
Show file tree
Hide file tree
Showing 3 changed files with 124 additions and 42 deletions.
15 changes: 2 additions & 13 deletions beartype/_check/code/codemake.py
Original file line number Diff line number Diff line change
Expand Up @@ -1712,24 +1712,13 @@ def _enqueue_hint_child(pith_child_expr: str) -> str:
#FIXME: This logic is an almost one-for-one copy of the
#"hint_curr_sign in HINT_SIGNS_SEQUENCE_ARGS_1" block above.
#Unify as follows:
#* In the existing "datapepsigns" submodule:
# * Refactor the get_hint_pep_sign() getter to assign *ALL*
# fixed-length tuple type hints the "HintSignTupleFixed" sign
# rather than the "HintSignTuple" sign.
# * Refactor logic elsewhere to explicitly match
# "HintSignTupleFixed" rather than matching "HintSignTuple"
# and then attempting to disambiguate fixed-length tuple type
# hints from that by inspecting for "Ellipses" singletons. In
# theory, the only two places in the codebase this happens
# *SHOULD* be:
# * This submodule.
# * Some "beartype.door" submodule specific to tuples.
#* In the existing "codesnipstr" submodule:
# * Define a new
# "HINT_SIGN_TO_CODE_PEP484585_CONTAINER_ARGS_1_format"
# dictionary global resembling:
# # Initialized by the _init() function defined below.
# HINT_SIGN_TO_CODE_PEP484585_CONTAINER_ARGS_1_format: Dict[HintSign, str] = {}
# HINT_SIGN_TO_CODE_PEP484585_CONTAINER_ARGS_1_format: (
# Dict[HintSign, Callable[..., str]]) = {}
# def _init() -> None:
# _CODE_PEP484585_COLLECTION_ARGS_1 = '''(
# _{indent_curr} # True only if this pith is of this collection type *AND*...
Expand Down
142 changes: 114 additions & 28 deletions beartype/_check/code/snip/codesnipstr.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,15 @@
'''

# ....................{ IMPORTS }....................
from beartype.typing import (
Callable,
Dict,
)
from beartype._check.checkmagic import (
VAR_NAME_RANDOM_INT,
)
from collections.abc import Callable
from beartype._data.hint.datahinttyping import CallableStrFormat
from beartype._data.hint.pep.sign.datapepsigncls import HintSign

# ....................{ HINT ~ placeholder : child }....................
CODE_HINT_CHILD_PLACEHOLDER_PREFIX = '@['
Expand Down Expand Up @@ -142,6 +147,47 @@
this parent type has been generated.
'''

# ....................{ HINT ~ pep : (484|585) : container }....................
# Initialized by the _init() function defined below.
HINT_SIGN_TO_CODE_PEP484585_CONTAINER_ARGS_1_format: (
Dict[HintSign, CallableStrFormat]) = {}
'''
Dictionary mapping from the sign uniquely identifying each applicable kind of
**single-argument standard container type hint** (i.e., :pep:`484`- or
:pep:`585`-compliant type hint describing a standard container, subscripted by
exactly one child type hint constraining the type of *all* items residing in
that container) to the :meth:`str.format` method bound to a code snippet
type-checking the current pith against that kind of container.
Caveats
-------
**Each such snippet must not contain ternary conditionals.** For unknown reasons
suggesting a critical defect in the current implementation of Python 3.8's
assignment expressions, this snippet raises :exc:`UnboundLocalError` exceptions
resembling the following when this snippet contains one or more ternary
conditionals:
UnboundLocalError: local variable '__beartype_pith_1' referenced before
assignment
In particular, the initial draft of these snippets guarded against empty
sequences with a seemingly reasonable ternary conditional:
.. code-block:: python
CODE_PEP484585_SEQUENCE_ARGS_1 = \'\'\'(
{indent_curr} isinstance({pith_curr_assign_expr}, {hint_curr_expr}) and
{indent_curr} {hint_child_placeholder} if {pith_curr_var_name} else True
{indent_curr})\'\'\'
That should behave as expected, but doesn't, presumably due to obscure scoping
rules and a non-intuitive implementation of ternary conditionals in CPython.
Ergo, the current version of this snippet guards against empty sequences with
disjunctions and conjunctions (i.e., ``or`` and ``and`` operators) instead.
Happily, the current version is more efficient than the equivalent approach
based on ternary conditional (albeit slightly less intuitive).
'''

# ....................{ HINT ~ pep : (484|585) : mapping }....................
CODE_PEP484585_MAPPING = '''(
{indent_curr} # True only if this pith is of this mapping type *AND*...
Expand Down Expand Up @@ -583,57 +629,97 @@
# ..................{ FORMATTERS }..................
# str.format() methods, globalized to avoid inefficient dot lookups elsewhere.
# This is an absurd micro-optimization. *fight me, github developer community*
CODE_PEP484_INSTANCE_format: Callable = (
CODE_PEP484_INSTANCE_format: CallableStrFormat = (
CODE_PEP484_INSTANCE.format)
CODE_PEP484585_GENERIC_CHILD_format: Callable = (
CODE_PEP484585_GENERIC_CHILD_format: CallableStrFormat = (
CODE_PEP484585_GENERIC_CHILD.format)
CODE_PEP484585_MAPPING_format: Callable = (
CODE_PEP484585_MAPPING_format: CallableStrFormat = (
CODE_PEP484585_MAPPING.format)
CODE_PEP484585_MAPPING_KEY_ONLY_format: Callable = (
CODE_PEP484585_MAPPING_KEY_ONLY_format: CallableStrFormat = (
CODE_PEP484585_MAPPING_KEY_ONLY.format)
CODE_PEP484585_MAPPING_KEY_VALUE_format: Callable = (
CODE_PEP484585_MAPPING_KEY_VALUE_format: CallableStrFormat = (
CODE_PEP484585_MAPPING_KEY_VALUE.format)
CODE_PEP484585_MAPPING_VALUE_ONLY_format: Callable = (
CODE_PEP484585_MAPPING_VALUE_ONLY_format: CallableStrFormat = (
CODE_PEP484585_MAPPING_VALUE_ONLY.format)
CODE_PEP484585_MAPPING_KEY_ONLY_PITH_CHILD_EXPR_format: Callable = (
CODE_PEP484585_MAPPING_KEY_ONLY_PITH_CHILD_EXPR_format: CallableStrFormat = (
CODE_PEP484585_MAPPING_KEY_ONLY_PITH_CHILD_EXPR.format)
CODE_PEP484585_MAPPING_VALUE_ONLY_PITH_CHILD_EXPR_format: Callable = (
CODE_PEP484585_MAPPING_VALUE_ONLY_PITH_CHILD_EXPR_format: CallableStrFormat = (
CODE_PEP484585_MAPPING_VALUE_ONLY_PITH_CHILD_EXPR.format)
CODE_PEP484585_MAPPING_KEY_VALUE_PITH_CHILD_EXPR_format: Callable = (
CODE_PEP484585_MAPPING_KEY_VALUE_PITH_CHILD_EXPR_format: CallableStrFormat = (
CODE_PEP484585_MAPPING_KEY_VALUE_PITH_CHILD_EXPR.format)
CODE_PEP484585_REITERABLE_ARGS_1_format: Callable = (
CODE_PEP484585_REITERABLE_ARGS_1_format: CallableStrFormat = (
CODE_PEP484585_REITERABLE_ARGS_1.format)
CODE_PEP484585_REITERABLE_ARGS_1_PITH_CHILD_EXPR_format: Callable = (
CODE_PEP484585_REITERABLE_ARGS_1_PITH_CHILD_EXPR_format: CallableStrFormat = (
CODE_PEP484585_REITERABLE_ARGS_1_PITH_CHILD_EXPR.format)
CODE_PEP484585_SEQUENCE_ARGS_1_format: Callable = (
CODE_PEP484585_SEQUENCE_ARGS_1_format: CallableStrFormat = (
CODE_PEP484585_SEQUENCE_ARGS_1.format)
CODE_PEP484585_SEQUENCE_ARGS_1_PITH_CHILD_EXPR_format: Callable = (
CODE_PEP484585_SEQUENCE_ARGS_1_PITH_CHILD_EXPR_format: CallableStrFormat = (
CODE_PEP484585_SEQUENCE_ARGS_1_PITH_CHILD_EXPR.format)
CODE_PEP484585_SUBCLASS_format: Callable = (
CODE_PEP484585_SUBCLASS_format: CallableStrFormat = (
CODE_PEP484585_SUBCLASS.format)
CODE_PEP484585_TUPLE_FIXED_EMPTY_format: Callable = (
CODE_PEP484585_TUPLE_FIXED_EMPTY_format: CallableStrFormat = (
CODE_PEP484585_TUPLE_FIXED_EMPTY.format)
CODE_PEP484585_TUPLE_FIXED_LEN_format: Callable = (
CODE_PEP484585_TUPLE_FIXED_LEN_format: CallableStrFormat = (
CODE_PEP484585_TUPLE_FIXED_LEN.format)
CODE_PEP484585_TUPLE_FIXED_NONEMPTY_CHILD_format: Callable = (
CODE_PEP484585_TUPLE_FIXED_NONEMPTY_CHILD_format: CallableStrFormat = (
CODE_PEP484585_TUPLE_FIXED_NONEMPTY_CHILD.format)
CODE_PEP484585_TUPLE_FIXED_NONEMPTY_PITH_CHILD_EXPR_format: Callable = (
CODE_PEP484585_TUPLE_FIXED_NONEMPTY_PITH_CHILD_EXPR_format: CallableStrFormat = (
CODE_PEP484585_TUPLE_FIXED_NONEMPTY_PITH_CHILD_EXPR.format)
CODE_PEP484604_UNION_CHILD_PEP_format: Callable = (
CODE_PEP484604_UNION_CHILD_PEP_format: CallableStrFormat = (
CODE_PEP484604_UNION_CHILD_PEP.format)
CODE_PEP484604_UNION_CHILD_NONPEP_format: Callable = (
CODE_PEP484604_UNION_CHILD_NONPEP_format: CallableStrFormat = (
CODE_PEP484604_UNION_CHILD_NONPEP.format)
# CODE_PEP572_PITH_ASSIGN_AND_format: Callable = (
# CODE_PEP572_PITH_ASSIGN_AND_format: CallableStrFormat = (
# CODE_PEP572_PITH_ASSIGN_AND.format)
CODE_PEP572_PITH_ASSIGN_EXPR_format: Callable = (
CODE_PEP572_PITH_ASSIGN_EXPR_format: CallableStrFormat = (
CODE_PEP572_PITH_ASSIGN_EXPR.format)
CODE_PEP586_LITERAL_format: Callable = (
CODE_PEP586_LITERAL_format: CallableStrFormat = (
CODE_PEP586_LITERAL.format)
CODE_PEP586_PREFIX_format: Callable = (
CODE_PEP586_PREFIX_format: CallableStrFormat = (
CODE_PEP586_PREFIX.format)
CODE_PEP593_VALIDATOR_IS_format: Callable = (
CODE_PEP593_VALIDATOR_IS_format: CallableStrFormat = (
CODE_PEP593_VALIDATOR_IS.format)
CODE_PEP593_VALIDATOR_METAHINT_format: Callable = (
CODE_PEP593_VALIDATOR_METAHINT_format: CallableStrFormat = (
CODE_PEP593_VALIDATOR_METAHINT.format)
CODE_PEP593_VALIDATOR_SUFFIX_format: Callable = (
CODE_PEP593_VALIDATOR_SUFFIX_format: CallableStrFormat = (
CODE_PEP593_VALIDATOR_SUFFIX.format)

# ..................{ PRIVATE ~ main }..................
def _init() -> None:
'''
Initialize this submodule.
'''

# Defer function-specific imports.
from beartype._data.hint.pep.sign.datapepsignset import (
HINT_SIGNS_REITERABLE_ARGS_1,
HINT_SIGNS_SEQUENCE_ARGS_1,
)

# PEP 484- and 585-compliant code snippet generically type-checking the
# current pith against *any* arbitrary kind of single-argument standard
# container type hint.
_CODE_PEP484585_CONTAINER_ARGS_1 = '''(
_{indent_curr} # True only if this pith is of this container type *AND*...
_{indent_curr} isinstance({pith_curr_assign_expr}, {hint_curr_expr}) and
_{indent_curr} # True only if either this container is empty *OR* this container
_{indent_curr} # is both non-empty and the first item satisfies this hint.
_{indent_curr} (not {pith_curr_var_name} or {hint_child_placeholder})
_{indent_curr})'''

# str.format() method bound to this snippet.
_CODE_PEP484585_CONTAINER_ARGS_1_format = (
_CODE_PEP484585_CONTAINER_ARGS_1.format)

# For each sign identifying some kind of single-argument container hint...
for hint_sign in (
HINT_SIGNS_REITERABLE_ARGS_1 |
HINT_SIGNS_SEQUENCE_ARGS_1
):
# Map this sign to this str.format() method.
HINT_SIGN_TO_CODE_PEP484585_CONTAINER_ARGS_1_format[
hint_sign] = _CODE_PEP484585_CONTAINER_ARGS_1_format


# Initialize this submodule.
_init()
9 changes: 8 additions & 1 deletion beartype/_data/hint/datahinttyping.py
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,7 @@
argument accepted by the ``__getitem__` dunder method.
'''

# ....................{ CALLABLE ~ decor }....................
# ....................{ CALLABLE ~ decorator }....................
BeartypeConfedDecorator = Callable[[BeartypeableT], BeartypeableT]
'''
PEP-compliant type hint matching a **configured beartype decorator** (i.e.,
Expand All @@ -294,6 +294,13 @@
in both configuration and decoration modes.
'''

# ....................{ CALLABLE ~ decorator }....................
CallableStrFormat = Callable[..., str]
'''
PEP-compliant type hint matching the signature of the standard
:meth:`str.format` method.
'''

# ....................{ DICT }....................
HintSignTrie = Dict[str, Union[HintSign, 'HintSignTrie']]
'''
Expand Down

0 comments on commit c451c9e

Please sign in to comment.