Skip to content

Commit

Permalink
Reiterables x 7.
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. (*Numerous Numenoreans in a Hyperborean hyperbole!*)
  • Loading branch information
leycec committed Jun 15, 2024
1 parent 7082240 commit 0704e95
Show file tree
Hide file tree
Showing 5 changed files with 233 additions and 246 deletions.
2 changes: 0 additions & 2 deletions beartype/_check/code/codemake.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,6 @@
CODE_PEP593_VALIDATOR_METAHINT_format,
CODE_PEP593_VALIDATOR_PREFIX,
CODE_PEP593_VALIDATOR_SUFFIX_format,
HINT_SIGN_PEP484585_CONTAINER_ARGS_1_TO_CODE_format,
HINT_SIGN_PEP484585_CONTAINER_ARGS_1_TO_PITH_CHILD_EXPR_format,
)
from beartype._check.convert.convsanify import (
sanify_hint_child_if_unignorable_or_none,
Expand Down
244 changes: 0 additions & 244 deletions beartype/_check/code/snip/codesnipstr.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,181 +146,6 @@
this parent type has been generated.
'''

# ....................{ HINT ~ pep : {484,585} : container }....................
#FIXME: Consider shifting this elsewhere, please.
from beartype.typing import (
TYPE_CHECKING,
Optional,
)
class HintSignCoder(object):
'''
**Hint sign coder** (i.e., dataclass encapsulating all low-level Python code
snippets and associated metadata required to dynamically generate a
high-level Python code snippet fully type-checking some kind of type hint
uniquely identified by a common sign).
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, the following 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 less intuitive).
Attributes
----------
code_format : CallableStrFormat
:meth:`str.format` method bound to a Python code snippet fully
type-checking the current pith against this kind of type hint.
is_var_random_int_needed : bool
True only if the Python code snippet dynamically generated by calling
the :attr:`code_format` method requires a pseudo-random integer by
accessing the :data:`VAR_NAME_RANDOM_INT` local. If true, the body of
the current wrapper function will be prefixed by a Python statement
assigning such an integer to this local.
pith_child_expr_format : CallableStrFormat
:meth:`str.format` method bound to a Python expression efficiently
yielding the value of the next item (which will then be type-checked)
contained in the **current pith** (which is the parent container
currently being type-checked).
'''

# ..................{ CLASS VARIABLES }..................
# Slot all instance variables defined on this object to minimize the time
# complexity of both reading and writing variables across frequently called
# cache dunder methods. Slotting has been shown to reduce read and write
# costs by approximately ~10%, which is non-trivial.
__slots__ = (
'code_format',
'is_var_random_int_needed',
'pith_child_expr_format',
)

# Squelch false negatives from mypy. This is absurd. This is mypy. See:
# https://github.com/python/mypy/issues/5941
if TYPE_CHECKING:
code_format : CallableStrFormat
is_var_random_int_needed : bool
pith_child_expr_format : CallableStrFormat

# ..................{ INITIALIZERS }..................
def __init__(
self,

# For convenience, permit callers to avoid having to initially define
# all possible parameters all-at-once by defaulting all parameters to
# *REASONABLY* sane defaults.
code_format: Optional[CallableStrFormat] = None,
is_var_random_int_needed: bool = False,
pith_child_expr_format: Optional[CallableStrFormat] = None,
) -> None:
'''
Initialize this hint sign coder.
Parameters
----------
See the class docstring for further details.
'''

# Classify all passed parameters.
self.code_format = code_format # type: ignore[assignment]
self.is_var_random_int_needed = is_var_random_int_needed
self.pith_child_expr_format = pith_child_expr_format # type: ignore[assignment]


#FIXME: Refactor the dictionaries defined below into a single dictionary
#"HINT_SIGN_PEP484585_CONTAINER_ARGS_1_TO_CODER", please.

# Initialized by the _init() function defined below.
HINT_SIGN_PEP484585_CONTAINER_ARGS_1_TO_CODE_format: (
HintSignToCallableStrFormat) = {}
'''
Dictionary mapping from the sign uniquely identifying each applicable kind of
**standard single-argument 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 *all* items contained 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).
'''


# Initialized by the _init() function defined below.
HINT_SIGN_PEP484585_CONTAINER_ARGS_1_TO_PITH_CHILD_EXPR_format: (
HintSignToCallableStrFormat) = {}
'''
Dictionary mapping from the sign uniquely identifying each applicable kind of
**standard single-argument 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 *all* items contained in that
container) to the :meth:`str.format` method bound to a Python expression
efficiently yielding the value of the next item (which will then be
type-checked) contained in the **current pith** (which is the parent container
currently being type-checked).
'''


# Initialized by the _init() function defined below.
HINT_SIGN_PEP484585_CONTAINER_ARGS_1_TO_IS_VAR_RANDOM_INT_NEEDED: (
Dict[HintSign, bool]) = {}
'''
Dictionary mapping from the sign uniquely identifying each applicable kind of
**standard single-argument 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 *all* items contained in that
container) to the :meth:`str.format` method bound to a Python expression
# True only if one or more possibly nested type hints visitable from this
# root hint require a pseudo-random integer. If true, logic below prefixes
# the body of this wrapper function with code generating this integer.
'''

# ....................{ 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 @@ -824,72 +649,3 @@ def __init__(
CODE_PEP593_VALIDATOR_METAHINT.format)
CODE_PEP593_VALIDATOR_SUFFIX_format: CallableStrFormat = (
CODE_PEP593_VALIDATOR_SUFFIX.format)

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

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

# ....................{ SNIPPETS }....................
# 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})'''

# PEP 484- and 585-compliant Python expression yielding the first item of
# the current reiterable pith.
_CODE_PEP484585_REITERABLE_ARGS_1_PITH_CHILD_EXPR = (
'''next(iter({pith_curr_var_name}))''')

# PEP 484- and 585-compliant Python expression yielding a randomly indexed
# item of the current sequence pith.
_CODE_PEP484585_SEQUENCE_ARGS_1_PITH_CHILD_EXPR = (
f'''{{pith_curr_var_name}}[{VAR_NAME_RANDOM_INT} % len({{pith_curr_var_name}})]''')

# ....................{ LOCALS }....................
# str.format() methods bound to string locals defined above.
_CODE_PEP484585_CONTAINER_ARGS_1_format = (
_CODE_PEP484585_CONTAINER_ARGS_1.format)
_CODE_PEP484585_REITERABLE_ARGS_1_PITH_CHILD_EXPR_format = (
_CODE_PEP484585_REITERABLE_ARGS_1_PITH_CHILD_EXPR.format)
_CODE_PEP484585_SEQUENCE_ARGS_1_PITH_CHILD_EXPR_format = (
_CODE_PEP484585_SEQUENCE_ARGS_1_PITH_CHILD_EXPR.format)

# ....................{ DEFINE }....................
# For each sign identifying some kind of single-argument container hint...
for hint_sign in HINT_SIGNS_CONTAINER_ARGS_1:
# Map this sign to this str.format() method.
HINT_SIGN_PEP484585_CONTAINER_ARGS_1_TO_CODE_format[
hint_sign] = _CODE_PEP484585_CONTAINER_ARGS_1_format

# For each sign identifying a single-argument reiterable hint...
for hint_sign in HINT_SIGNS_REITERABLE_ARGS_1:
# Map this sign to this str.format() method.
HINT_SIGN_PEP484585_CONTAINER_ARGS_1_TO_PITH_CHILD_EXPR_format[
hint_sign] = (
_CODE_PEP484585_REITERABLE_ARGS_1_PITH_CHILD_EXPR_format)

# For each sign identifying a single-argument sequence hint...
for hint_sign in HINT_SIGNS_SEQUENCE_ARGS_1:
# Map this sign to this str.format() method.
HINT_SIGN_PEP484585_CONTAINER_ARGS_1_TO_PITH_CHILD_EXPR_format[
hint_sign] = _CODE_PEP484585_SEQUENCE_ARGS_1_PITH_CHILD_EXPR_format


# Initialize this submodule.
_init()
Empty file.
120 changes: 120 additions & 0 deletions beartype/_check/logic/logiccls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
#!/usr/bin/env python3
# --------------------( LICENSE )--------------------
# Copyright (c) 2014-2024 Beartype authors.
# See "LICENSE" for further details.

'''
Beartype **hint sign logic class hierarchy** (i.e., dataclasses encapsulating
all low-level Python code snippets and associated metadata required to
dynamically generate high-level Python code snippets fully type-checking various
kinds of type hints uniquely identified by common signs).
This private submodule is *not* intended for importation by downstream callers.
'''

# ....................{ IMPORTS }....................
from beartype._data.hint.datahinttyping import CallableStrFormat
from beartype.typing import (
TYPE_CHECKING,
Optional,
)

# ....................{ CLASSES }....................
class HintSignLogic(object):
'''
**Hint sign logic** (i.e., dataclass encapsulating all low-level Python code
snippets and associated metadata required to dynamically generate a
high-level Python code snippet fully type-checking some kind of type hint
uniquely identified by a common sign).
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, the following 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 less intuitive).
Attributes
----------
code_format : CallableStrFormat
:meth:`str.format` method bound to a Python code snippet fully
type-checking the current pith against this kind of type hint.
is_var_random_int_needed : bool
True only if the Python code snippet dynamically generated by calling
the :attr:`code_format` method requires a pseudo-random integer by
accessing the :data:`VAR_NAME_RANDOM_INT` local. If true, the body of
the current wrapper function will be prefixed by a Python statement
assigning such an integer to this local.
pith_child_expr_format : CallableStrFormat
:meth:`str.format` method bound to a Python expression efficiently
yielding the value of the next item (which will then be type-checked)
contained in the **current pith** (which is the parent container
currently being type-checked).
'''

# ..................{ CLASS VARIABLES }..................
# Slot all instance variables defined on this object to minimize the time
# complexity of both reading and writing variables across frequently called
# cache dunder methods. Slotting has been shown to reduce read and write
# costs by approximately ~10%, which is non-trivial.
__slots__ = (
'code_format',
'is_var_random_int_needed',
'pith_child_expr_format',
)

# Squelch false negatives from mypy. This is absurd. This is mypy. See:
# https://github.com/python/mypy/issues/5941
if TYPE_CHECKING:
code_format : CallableStrFormat
is_var_random_int_needed : bool
pith_child_expr_format : CallableStrFormat

# ..................{ INITIALIZERS }..................
def __init__(
self,

# Mandatory parameters.
code_format: CallableStrFormat,

# Optional parameters.
#
# For convenience, permit callers to avoid having to initially define
# all possible parameters all-at-once by defaulting all parameters to
# *REASONABLY* sane defaults.
is_var_random_int_needed: bool = False,
pith_child_expr_format: Optional[CallableStrFormat] = None,
) -> None:
'''
Initialize this hint sign logic.
Parameters
----------
See the class docstring for further details.
'''

# Classify all passed parameters.
self.code_format = code_format
self.is_var_random_int_needed = is_var_random_int_needed
self.pith_child_expr_format = pith_child_expr_format # type: ignore[assignment]
Loading

0 comments on commit 0704e95

Please sign in to comment.