Skip to content

Commit

Permalink
Reiterables x 4.
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 reduces problematic DRY (Don't Repeat
Yourself) violations in @beartype's internal type-checking code
generator. (*Utmost udders, mostly!*)
  • Loading branch information
leycec committed Jun 7, 2024
1 parent cd6725e commit 34bbca4
Show file tree
Hide file tree
Showing 3 changed files with 161 additions and 120 deletions.
102 changes: 38 additions & 64 deletions beartype/_check/code/codemake.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@
HintSignGeneric,
HintSignLiteral,
HintSignTuple,
HintSignTupleFixed,
HintSignType,
HintSignUnion,
)
Expand Down Expand Up @@ -268,7 +269,7 @@ class variable or method annotated by this hint *or* :data:`None`).

# Python expression evaluating to an isinstanceable type (e.g., origin type)
# associated with the currently visited type hint if any.
hint_curr_expr = None
hint_curr_expr: str = None # type: ignore[assignment]

# Placeholder string to be globally replaced in the Python code snippet to
# be returned (i.e., "func_wrapper_code") by a Python code snippet
Expand Down Expand Up @@ -362,12 +363,6 @@ class variable or method annotated by this hint *or* :data:`None`).
hints_meta_index_last = -1

# ..................{ LOCALS ~ func : code }..................
#FIXME: [SPEED] Consider refactoring this into a "collections.deque[str]"
#data structure instead -- assuming that calling the deque.append() method
#is actually faster than performing string concatenations as we currently
#do, anyway. In theory, a deque *SHOULD* be substantially faster -- but
#everything depends on Python's internal implementation, really.

# Python code snippet type-checking the current pith against the currently
# visited hint (to be appended to the "func_wrapper_code" string).
func_curr_code: str = None # type: ignore[assignment]
Expand Down Expand Up @@ -569,6 +564,14 @@ def _enqueue_hint_child(pith_child_expr: str) -> str:
# Python code snippet to be returned, seeded with a placeholder to be
# replaced on the first iteration of the breadth-first search performed
# below with a snippet type-checking the root pith against the root hint.
#
# Note that, shockingly, brute-force string concatenation has been
# personally profiled by @leycec to be substantially faster than *ALL*
# alternatives under CPython up to a large number of iterations (e.g.,
# 100,000). This includes these popular alternatives, *ALL* of which are
# orders of magnitude slower than brute-force string concatenation:
# * deque.append().
# * list.append().
func_wrapper_code = func_root_code

# ..................{ SEARCH }..................
Expand Down Expand Up @@ -628,6 +631,12 @@ def _enqueue_hint_child(pith_child_expr: str) -> str:
# '{} {!r} placeholder {} not found in wrapper body:\n{}'.format(
# hint_curr_exception_prefix, hint, hint_curr_placeholder, func_wrapper_code))

# Code snippet type-checking the current pith against the current hint.
func_curr_code = None # type: ignore[assignment]

# Code expression evaluating to the origin type of the current hint.
hint_curr_expr = None # type: ignore[assignment]

# ................{ PEP }................
# If this hint is PEP-compliant...
if is_hint_pep(hint_curr):
Expand Down Expand Up @@ -1454,11 +1463,6 @@ def _enqueue_hint_child(pith_child_expr: str) -> str:
# Else, this child hint is ignorable. In this case, fallback
# to trivial code shallowly type-checking this pith as an
# instance of this origin type.
else:
func_curr_code = CODE_PEP484_INSTANCE_format(
pith_curr_expr=pith_curr_expr,
hint_curr_expr=hint_curr_expr,
)
# Else, this hint is neither a standard sequence *NOR* variadic
# tuple.
#
Expand Down Expand Up @@ -1725,25 +1729,13 @@ def _enqueue_hint_child(pith_child_expr: str) -> str:
# ignorable. In this case, fallback to trivial code
# shallowly type-checking this pith as an instance of this
# origin type.
else:
func_curr_code = CODE_PEP484_INSTANCE_format(
pith_curr_expr=pith_curr_expr,
hint_curr_expr=hint_curr_expr,
)
# Else, this hint is *NOT* a mapping.
#
# ..........{ REITERABLES }............
#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:
# * Define a new "HintSignTupleFixed" sign in the standard way.
# * The existing "HintSignTuple" sign should be preserved as is
# but *ONLY* match variable-length tuple type hints. Why?
# Because this sign naturally matches the unsubscripted
# "typing.Tuple" attribute, which is semantically equivalent
# to the "typing.Tuple[object, ...]" type hint, which is the
# widest possible variable-length tuple type hint.
# * Refactor the get_hint_pep_sign() getter to assign *ALL*
# fixed-length tuple type hints the "HintSignTupleFixed" sign
# rather than the "HintSignTuple" sign.
Expand Down Expand Up @@ -1836,25 +1828,9 @@ def _enqueue_hint_child(pith_child_expr: str) -> str:
CODE_PEP484585_REITERABLE_ARGS_1_PITH_CHILD_EXPR_format(
pith_curr_var_name=pith_curr_var_name)),
)
#FIXME: We're repeating this same block *OVER* and *OVER*
#again, plainly violating DRY. The solution is probably to:
#* Far above, initialize "func_curr_code = None" at the
# start of each iteration.
#* Far below, simply define this fallback:
# if func_curr_code is None:
# func_curr_code = CODE_PEP484_INSTANCE_format(
# pith_curr_expr=pith_curr_expr,
# hint_curr_expr=hint_curr_expr,
# )

# Else, this child hint is ignorable. In this case, fallback
# to trivial code shallowly type-checking this pith as an
# instance of this origin type.
else:
func_curr_code = CODE_PEP484_INSTANCE_format(
pith_curr_expr=pith_curr_expr,
hint_curr_expr=hint_curr_expr,
)
# Else, this hint is *NOT* a single-argument reiterable.
#
# ............{ ANNOTATED }............
Expand Down Expand Up @@ -2096,17 +2072,6 @@ def _enqueue_hint_child(pith_child_expr: str) -> str:
hint_curr_expr=hint_curr_expr,
indent_curr=indent_curr,
)
#FIXME: We're repeating this same block *OVER* and *OVER*
#again, plainly violating DRY. The solution is probably to:
#* Far above, initialize "func_curr_code = None" at the
# start of each iteration.
#* Far below, simply define this fallback:
# if func_curr_code is None:
# func_curr_code = CODE_PEP484_INSTANCE_format(
# pith_curr_expr=pith_curr_expr,
# hint_curr_expr=hint_curr_expr,
# )

# Else, this child hint is ignorable. In this case...
else:
# Python expression evaluating to the origin type of
Expand All @@ -2120,10 +2085,6 @@ def _enqueue_hint_child(pith_child_expr: str) -> str:

# Fallback to trivial code shallowly type-checking this
# pith as an instance of this origin type.
func_curr_code = CODE_PEP484_INSTANCE_format(
pith_curr_expr=pith_curr_expr,
hint_curr_expr=hint_curr_expr,
)
# Else, this hint is *NOT* a subclass type hint.
#
# ............{ GENERIC or PROTOCOL }............
Expand Down Expand Up @@ -2329,15 +2290,11 @@ def _enqueue_hint_child(pith_child_expr: str) -> str:
# were the root type hint, it would have already been passed into a
# faster submodule generating PEP-noncompliant code instead.
elif isinstance(hint_curr, type):
# Code type-checking the current pith against this type.
func_curr_code = CODE_PEP484_INSTANCE_format(
pith_curr_expr=pith_curr_expr,
# Python expression evaluating to this type.
hint_curr_expr=add_func_scope_type(
cls=hint_curr,
func_scope=func_wrapper_scope,
exception_prefix=EXCEPTION_PREFIX_HINT,
),
# Python expression evaluating to this type.
hint_curr_expr = add_func_scope_type(
cls=hint_curr,
func_scope=func_wrapper_scope,
exception_prefix=EXCEPTION_PREFIX_HINT,
)
# ................{ NON-PEP ~ bad }................
# Else, this hint is neither PEP-compliant *NOR* a class. In this case,
Expand All @@ -2358,6 +2315,23 @@ def _enqueue_hint_child(pith_child_expr: str) -> str:
)

# ................{ CLEANUP }................
# If prior logic generated *NO* code snippet type-checking the current
# pith against the currently visited hint, fall back to a trivial code
# snippet shallowly type-checking this pith as an instance of the origin
# type of this hint.
if func_curr_code is None:
assert hint_curr_expr is not None, (
f'{EXCEPTION_PREFIX}type hint {repr(hint_curr)} '
f'expression undefined.'
)

func_curr_code = CODE_PEP484_INSTANCE_format(
pith_curr_expr=pith_curr_expr,
hint_curr_expr=hint_curr_expr,
)
# Else, prior logic generated a code snippet type-checking the current
# pith against the currently visited hint. Preserve this snippet.

# Inject this code into the body of this wrapper.
func_wrapper_code = replace_str_substrs(
text=func_wrapper_code,
Expand Down
44 changes: 43 additions & 1 deletion beartype/_data/hint/pep/sign/datapepsigns.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,24 @@
HintSignOptional = _HintSign(name='Optional')
HintSignParamSpec = _HintSign(name='ParamSpec')
HintSignProtocol = _HintSign(name='Protocol')

HintSignTuple = _HintSign(name='Tuple')
'''
Sign uniquely identifying **variable-length tuple type hints,** including:
* :pep:`484`-compliant type hints of the form ``typing.Tuple[{hint_child_1},
...]`` where the last child type hint subscripting this parent hint is an
ellipses (i.e., ``"..."`` string, :data:`Ellipses` singleton).
* :pep:`585`-compliant type hints of the form ``tuple[{hint_child_1}, ...]``
where the last child type hint subscripting this parent hint is an ellipses
(i.e., ``"..."`` string, :data:`Ellipses` singleton).
See Also
--------
HintSignTupleFixed
Sign uniquely identifying **fixed-length tuple type hints.**
'''

HintSignType = _HintSign(name='Type')
HintSignTypeVar = _HintSign(name='TypeVar')
HintSignTypeVarTuple = _HintSign(name='TypeVarTuple')
Expand Down Expand Up @@ -197,12 +214,37 @@

HintSignNone = _HintSign(name='None')
'''
:pep:`484` explicitly supports the :data:`None` singleton, albeit implicitly:
Sign uniquely identifying the :data:`None` singleton, explicitly supported by
:pep:`484` but lacking an explicit analogue in the standard :mod:`typing`
module:
When used in a type hint, the expression None is considered equivalent to
type(None).
'''


HintSignTupleFixed = _HintSign(name='TupleFixed')
'''
Sign uniquely identifying **fixed-length tuple type hints,** including:
* :pep:`484`-compliant type hints of the form ``typing.Tuple[{hint_child_1},
..., {hint_child_N}]`` where ``{hint_child_N}`` is *not* an ellipses (i.e.,
``"..."`` string, :data:`Ellipses` singleton).
* :pep:`585`-compliant type hints of the form ``tuple[{hint_child_1}, ...,
{hint_child_N}]`` where ``{hint_child_N}`` is *not* an ellipses (i.e.,
``"..."`` string, :data:`Ellipses` singleton).
Note that:
* The ``"..."`` substring above is *not* a literal ellipses but simply denotes
an arbitrary number of non-ellipses child type hints.
* The existing :data:`.HintSignTuple` sign uniquely identifies variable-length
tuple type hints. Why? Because that sign naturally matches the unsubscripted
:obj:`typing.Tuple` type hint factory, which is semantically equivalent to the
``typing.Tuple[object, ...]`` type hint, which is the widest possible
variable-length tuple type hint.
'''

# ....................{ SIGNS ~ implicit : lib }....................
# Signs identifying PEP-noncompliant third-party type hints published by...
#
Expand Down
Loading

0 comments on commit 34bbca4

Please sign in to comment.