Skip to content

Commit

Permalink
PEP 604-inconsistent type hint protection x 2.
Browse files Browse the repository at this point in the history
This commit is the last in a commit chain explicitly prohibiting all
**PEP 604-inconsistent type hints** (i.e., object permissible as an item
of a PEP 604-compliant new union whose machine-readable representation
is *not* the machine-readable representation of this hint in new
unions). Specifically, this commit improves the detection of PEP
604-inconsistent type hints to be robust against unlikely edge cases
that could destroy the fabric of our fragile world like an egg popping
with a disgusting squelching noise. (*Honourable honey or an ouroboros!?*)
  • Loading branch information
leycec committed Nov 17, 2023
1 parent a469322 commit 7ab1216
Show file tree
Hide file tree
Showing 7 changed files with 87 additions and 26 deletions.
2 changes: 1 addition & 1 deletion beartype/_check/code/_codescope.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
# previously called express_func_scope_type_forwardref() function, then
# *THIS REFERENCE INDICATES A RECURSIVE TYPE HINT.* In this case:
# * Replace this forward reference with a new recursive type-checking
# "beartype._check.forward._fwdref.BeartypeForwardRef_{forwardref}"
# "beartype._check.forward.fwdref.BeartypeForwardRef_{forwardref}"
# subclass whose is_instance() tester method recursively calls itself
# indefinitely. If doing so generates a "RecursionError", @beartype
# considers that the user's problem. *wink*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -192,8 +192,17 @@ def __repr__( # type: ignore[misc]
'''

# Machine-readable representation to be returned.
#
# Note that this representation is intentionally prefixed by the
# @beartype-specific substring "<forwardref ", resembling the
# representation of classes (e.g., "<class 'bool'>"). Why? Because
# various other @beartype submodules ignore objects whose
# representations are prefixed by the "<" character, which are usefully
# treated as having a standard representation that is ignorable for most
# intents and purposes. This includes:
# * The die_if_hint_pep604_inconsistent() raiser.
cls_repr = (
f'{cls.__name__}('
f'<forwardref {cls.__name__}('
f'__beartype_scope_name__={repr(cls.__beartype_scope_name__)}'
f', __beartype_name__={repr(cls.__beartype_name__)}'
)
Expand Down Expand Up @@ -222,7 +231,7 @@ def __repr__( # type: ignore[misc]
pass

# Close this representation.
cls_repr += ')'
cls_repr += ')>'

# Return this representation.
return cls_repr
Expand Down Expand Up @@ -530,6 +539,28 @@ def __class_getitem__(cls, *args, **kwargs) -> (
superclass to reduce space and time consumption.
'''

# ....................{ TESTERS }....................
#FIXME: Unit test us up, please.
def is_forwardref(obj: object) -> bool:
'''
:data:`True` only if the passed object is a **forward reference subclass**
(i.e., class whose metaclass is class:`._BeartypeForwardRefMeta`).
Parameters
----------
obj : object
Object to be tested.
Returns
----------
bool
:data:`True` only if this object is a forward reference subclass.
'''

# Return true only if the class of this object is the metaclass of all
# forward reference subclasses, implying this object to be such a subclass.
return obj.__class__ is _BeartypeForwardRefMeta

# ....................{ FACTORIES }....................
@callable_cached
def make_forwardref_indexable_subtype(
Expand Down
4 changes: 2 additions & 2 deletions beartype/_check/forward/fwdscope.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
from beartype.roar import BeartypeDecorHintForwardRefException
from beartype.typing import Type
from beartype._data.hint.datahinttyping import LexicalScope
from beartype._check.forward._fwdref import (
from beartype._check.forward.fwdref import (
make_forwardref_indexable_subtype,
_BeartypeForwardRefIndexableABC,
)
Expand Down Expand Up @@ -143,7 +143,7 @@ def __missing__(self, hint_name: str) -> Type[
This method transparently replaces this unresolved type hint with a
**forward reference proxy** (i.e., concrete subclass of the private
:class:`beartype._check.forward._fwdref._BeartypeForwardRefABC` abstract
:class:`beartype._check.forward.fwdref._BeartypeForwardRefABC` abstract
base class (ABC), which resolves this type hint on the first call to the
:func:`isinstance` builtin whose second argument is that subclass).
Expand Down
4 changes: 2 additions & 2 deletions beartype/_util/cache/utilcachecall.py
Original file line number Diff line number Diff line change
Expand Up @@ -594,9 +594,9 @@ def property_cached(func: _CallableT) -> _CallableT:
filtering) to uniquely match and act upon these variables.
* Is intentionally prefixed by double rather than single underscores (i.e.,
``"__"`` rather than ``"_"``), ensuring that our
:meth:`beartype._check.forward._fwdref._BeartypeForwardRefMeta.__getattr__`
:meth:`beartype._check.forward.fwdref._BeartypeForwardRefMeta.__getattr__`
dunder method ignores the private instance variables cached by our cached
:meth:`beartype._check.forward._fwdref._BeartypeForwardRefMeta.__beartype_type__`
:meth:`beartype._check.forward.fwdref._BeartypeForwardRefMeta.__beartype_type__`
property.
'''

Expand Down
19 changes: 17 additions & 2 deletions beartype/_util/hint/pep/proposal/utilpep604.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,23 @@ def die_if_hint_pep604_inconsistent(hint: object) -> None:
# Machine-readable representation of this hint.
hint_repr = get_hint_repr(hint)

# if hint_repr.startswith("<class '"):
# return
# If this representation is prefixed by the "<" character, this
# representation is assumed to be (at least *SOMEWHAT*) standardized and
# thus internally consistent. This includes:
# * Standard classes (e.g., "<class 'bool'>").
# * @beartype-specific forward reference subclasses (e.g., "<forwardref
# UndeclaredClass(__beartype_scope__='some_package')>").
#
# This is *NOT* simply an optimization. Standardized representations
# *MUST* be excluded from consideration, as the representations of new
# unions containing these hints is *NOT* prefixed by "<": e.g.,
# >>> repr(bool)
# <class 'bool'>
# >>> bool | None
# bool | None
if hint_repr[0] == '<':
return
# Else, this representation is *NOT* prefixed by the "<" character.

# Arbitrary PEP 604-compliant new union defined as the conjunction
# of this hint with an arbitrary builtin type guaranteed to exist.
Expand Down
43 changes: 29 additions & 14 deletions beartype/_util/hint/pep/utilpepget.py
Original file line number Diff line number Diff line change
Expand Up @@ -556,20 +556,20 @@ class like :class:`list` or :class:`tuple` *or* an abstract base class
# Else, this hint is *NOT* identifiable by its necessarily subscripted
# representation.

# If this hint is inconsistent with respect to PEP 604-style new unions,
# raise an exception. Although awkward, this is ultimately the ideal
# location for this validation. Why? Because this validation:
# * *ONLY* applies to hints permissible as items of PEP 604-compliant
# new unions; this means classes and subscripted generics. If this
# hint is identifiable by its classname, this hint is neither a class
# *NOR* subscripted generic. Since this hint is *NOT* identifiable by
# its classname, however, this hint could still be either a class *OR*
# subscripted generic. It's best not to ask.
# * Does *NOT* apply to well-known type hints detected above (e.g.,
# those produced by Python itself, the standard library, and
# well-known third-party type hint factories), which are all
# guaranteed to be consistent with respect to PEP 604.
die_if_hint_pep604_inconsistent(hint)
# # If this hint is inconsistent with respect to PEP 604-style new unions,
# # raise an exception. Although awkward, this is ultimately the ideal
# # location for this validation. Why? Because this validation:
# # * *ONLY* applies to hints permissible as items of PEP 604-compliant
# # new unions; this means classes and subscripted generics. If this
# # hint is identifiable by its classname, this hint is neither a class
# # *NOR* subscripted generic. Since this hint is *NOT* identifiable by
# # its classname, however, this hint could still be either a class *OR*
# # subscripted generic. It's best not to ask.
# # * Does *NOT* apply to well-known type hints detected above (e.g.,
# # those produced by Python itself, the standard library, and
# # well-known third-party type hint factories), which are all
# # guaranteed to be consistent with respect to PEP 604.
# die_if_hint_pep604_inconsistent(hint)
# Else, this representation (and thus this hint) is unsubscripted.

# ..................{ PHASE ~ repr : trie }..................
Expand Down Expand Up @@ -699,6 +699,21 @@ class like :class:`list` or :class:`tuple` *or* an abstract base class
# Else, this hint is unrecognized. In this case, this hint is of unknown
# third-party origin and provenance.

# If this hint is inconsistent with respect to PEP 604-style new unions,
# raise an exception. Although awkward, this is ultimately the ideal
# location for this validation. Why? Because this validation:
# * *ONLY* applies to hints permissible as items of PEP 604-compliant new
# unions; this means classes and subscripted generics. If this hint is
# identifiable by its classname, this hint is neither a class *NOR*
# subscripted generic. Since this hint is *NOT* identifiable by its
# classname, however, this hint could still be either a class *OR*
# subscripted generic. It's best not to ask.
# * Does *NOT* apply to well-known type hints detected above (e.g., those
# produced by Python itself, the standard library, and well-known
# third-party type hint factories), which are all guaranteed to be
# consistent with respect to PEP 604.
die_if_hint_pep604_inconsistent(hint)

# Return "None".
return None

Expand Down
6 changes: 3 additions & 3 deletions beartype_test/a00_unit/a60_check/a80_forward/test_fwdref.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
'''
Beartype decorator **forward reference utility** unit tests.
This submodule unit tests the :func:`beartype._check.forward._fwdref` submodule.
This submodule unit tests the :func:`beartype._check.forward.fwdref` submodule.
'''

# ....................{ IMPORTS }....................
Expand All @@ -19,13 +19,13 @@
def test_make_forwardref_indexable_subtype() -> None:
'''
Test the
:func:`beartype._check.forward._fwdref.make_forwardref_indexable_subtype`
:func:`beartype._check.forward.fwdref.make_forwardref_indexable_subtype`
factory.
'''

# ....................{ LOCALS }....................
# Defer test-specific imports.
from beartype._check.forward._fwdref import (
from beartype._check.forward.fwdref import (
make_forwardref_indexable_subtype)
from beartype_test.a00_unit.data.data_type import Subclass
# from pytest import raises
Expand Down

0 comments on commit 7ab1216

Please sign in to comment.