Skip to content

Commit

Permalink
Literal[A] <= Union[Literal[A], ...] x 2.
Browse files Browse the repository at this point in the history
This commit is the last in a commit chain improving our Decidedly
Object-Oriented Runtime-checking (DOOR) API to correctly collate unions
of literals against single literals, resolving both issues #276 and
beartype/plum#99 kindly submitted by numerical physicist @PhilipVinc
(Filippo Vicentini). @beartype now correctly orders the following two
relations for all supported literal objects `A`:

* `TypeHint(Literal[A]) <= TypeHint(Union[Literal[A], ...]) is True`.
* `TypeHint(Union[Literal[A], ...]) <= TypeHint(Literal[A]) is False`.

This commit chain required surprisingly extensive refactorings to the
internal implementation of the `beartype.door` API and has thus been
isolated to a temporary branch. Naturally, everything is working yet
again. Praise be to the Sun, dismal though it is in Canada! (*Sunny bunnies!*)
  • Loading branch information
leycec committed Sep 14, 2023
1 parent c79393e commit f604f05
Show file tree
Hide file tree
Showing 19 changed files with 310 additions and 307 deletions.
6 changes: 3 additions & 3 deletions beartype/_util/hint/pep/proposal/pep484/utilpep484typevar.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ def get_hint_pep484_typevar_bound_or_none(
'''
PEP-compliant type hint synthesized from all bounded constraints
parametrizing the passed :pep:`484`-compliant **type variable** (i.e.,
:class:`typing.TypeVar` instance) if any *or* ``None`` otherwise (i.e., if
this type variable was parametrized by *no* bounded constraints).
:class:`typing.TypeVar` instance) if any *or* :data:`None` otherwise (i.e.,
if this type variable was parametrized by *no* bounded constraints).
Specifically, if this type variable was parametrized by:
Expand Down Expand Up @@ -85,7 +85,7 @@ def get_hint_pep484_typevar_bound_or_none(
* If this type variable was parametrized by one or more constraints, a
new PEP-compliant union type hint aggregating those constraints.
* If this type variable was parametrized by an upper bound, that bound.
* Else, ``None``.
* Else, :data:`None`.
Raises
----------
Expand Down
77 changes: 77 additions & 0 deletions beartype/_util/hint/pep/utilpepget.py
Original file line number Diff line number Diff line change
Expand Up @@ -773,6 +773,80 @@ def get_hint_pep_origin_or_none(hint: Any) -> Optional[Any]:
return getattr(hint, '__origin__', None)

# ....................{ GETTERS ~ origin : type }....................
#FIXME: Unit test us up, please.
@callable_cached
def get_hint_pep_origin_type_or_none(hint: Any) -> Optional[type]:
'''
**Standard origin type** (i.e., isinstanceable class declared by Python's
standard library such that *all* objects satisfying the passed
PEP-compliant type hint are instances of this class) originating this hint
if this hint originates from such a type *or* :data:`None` otherwise (i.e.,
if this hint does *not* originate from such a type).
This getter is memoized for efficiency.
Caveats
----------
**This high-level getter should always be called in lieu of the low-level**
:func:`get_hint_pep_origin_or_none` **getter on attempting to
directly access the low-level** ``__origin__`` **dunder attribute.**
Parameters
----------
hint : object
Object to be inspected.
Returns
----------
Optional[type]
Either:
* If this hint originates from a standard origin type, that type.
* Else, :data:`None`.
See Also
----------
:func:`.get_hint_pep_origin_or_none`
Further details.
'''

# Origin type originating this hint if any *OR* "None" otherwise,
# initialized to the arbitrary object set as the "hint.__origin__" dunder
# attribute if this hint defines that attribute.
hint_origin: Optional[type] = get_hint_pep_origin_or_none(hint) # type: ignore[assignment]

# If this origin is *NOT* a type...
#
# Ideally, this attribute would *ALWAYS* be a type for all possible
# PEP-compliant type hints. For unknown reasons, type hint factories defined
# by the standard "typing" module often set their origins to those same type
# hint factories, despite those factories *NOT* being types. Why? Frankly,
# we have no idea and neither does anyone else:
# >>> import typing
# >>> typing.Literal[1, 2].__origin__
# typing.Literal # <-- do you even know what you are doing, python?
# >>> typing.Optional[int].__origin__
# typing.Union # <-- wut? this is insane, python.
if not isinstance(hint_origin, type):
# Default this origin type to either...
hint_origin = (
# If this hint is itself a type, this hint could be euphemistically
# said to originate from "itself." In this case, this hint.
#
# Look. Just go with it. We wave our hands in the air.
hint if isinstance(hint, type) else
# Else, this hint is *NOT* a type. Default to "None". No choice, yo!
None
)
# Else, this origin is a type.

# Return this origin.
return hint_origin


#FIXME: Is this even required or desired anymore? Can't we just replace all
#calls to this frankly non-ideal getter with calls to a dramatically superior
#get_hint_pep_origin_type() getter? Excise us up, please.
def get_hint_pep_origin_type_isinstanceable(hint: object) -> type:
'''
**Isinstanceable origin type** (i.e., class passable as the second argument
Expand Down Expand Up @@ -821,6 +895,9 @@ def get_hint_pep_origin_type_isinstanceable(hint: object) -> type:
return hint_origin_type


#FIXME: Is this even required or desired anymore? Can't we just replace all
#calls to this frankly non-ideal getter with calls to the dramatically superior
#get_hint_pep_origin_type_or_none() getter? Excise us up, please.
def get_hint_pep_origin_type_isinstanceable_or_none(
hint: Any) -> Optional[type]:
'''
Expand Down
5 changes: 1 addition & 4 deletions beartype/door/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,7 @@
from beartype.door._cls.pep.pep484.doorpep484typevar import (
TypeVarTypeHint as TypeVarTypeHint)
from beartype.door._cls.pep.pep484585.doorpep484585callable import (
CallableTypeHint as CallableTypeHint,
# CallableParamsAnyTypeHint as CallableParamsAnyTypeHint,
# CallableParamsNoneTypeHint as CallableParamsNoneTypeHint,
)
CallableTypeHint as CallableTypeHint)

#FIXME: Actually, let's *NOT* publicly expose this for the moment. Why? Because
#we still need to split this into fixed and variadic tuple subclasses.
Expand Down
9 changes: 4 additions & 5 deletions beartype/door/_cls/doormeta.py
Original file line number Diff line number Diff line change
Expand Up @@ -234,11 +234,10 @@ def _make_wrapper(cls: '_TypeHintMeta', hint: object) -> object:
lock_type=RLock,
)
'''
**Type hint wrapper cache** (i.e., non-thread-safe cache mapping
from the machine-readable representations of all type hints to
cached singleton instances of concrete subclasses of the
:class:`beartype.door.TypeHint` abstract base class (ABC) wrapping
those hints).**
**Type hint wrapper cache** (i.e., non-thread-safe cache mapping from the
machine-readable representations of all type hints to cached singleton instances
of concrete subclasses of the :class:`beartype.door.TypeHint` abstract base
class (ABC) wrapping those hints).
Design
--------------
Expand Down
69 changes: 6 additions & 63 deletions beartype/door/_cls/doorsub.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,15 @@
from abc import abstractmethod
from beartype.door._cls.doorsuper import TypeHint
from beartype.roar import BeartypeDoorException
from beartype.typing import (
Any,
)
from beartype._util.cache.utilcachecall import property_cached
# from beartype.typing import (
# Any,
# )
# from beartype._util.cache.utilcachecall import property_cached
# from beartype._util.cls.utilclstest import is_type_subclass

# ....................{ SUBCLASSES }....................
#FIXME: Excise us up, please.
#FIXME: Excise us up, please. Globally replace all instances of
#"_TypeHintSubscripted" with simply "TypeHint".
class _TypeHintSubscripted(TypeHint):
'''
**Subscripted type hint wrapper** (i.e., high-level object encapsulating a
Expand All @@ -32,64 +33,6 @@ class _TypeHintSubscripted(TypeHint):

pass

# # ..................{ PRIVATE ~ properties : concrete }..................
# @property # type: ignore[misc]
# @property_cached
# def _is_args_ignorable(self) -> bool:
# '''
# :data:`True` only if all child type hints subscripting (indexing) this
# parent type hint are ignorable.
#
# Note that this property is *not* equivalent to the :meth:`is_ignorable`
# property. Although related, a non-ignorable parent type hint can
# trivially have ignorable child type hints (e.g., ``list[Any]``).
# '''
#
# #FIXME: Kinda unsure about this. Above, we define "_origin" as:
# # self._origin: type = get_hint_pep_origin_or_none(hint) or hint # type: ignore
# #
# #That effectively reduces to:
# # self._origin: type = hint.__origin__ or hint # type: ignore
# #FIXME: Ideally, this should read:
# # return all(
# # hint_child.is_ignorable
# # for hint_child in self._args_wrapped_tuple
# # )
# #
# #Sadly, that refactoring currently raises non-trivial test failures.
# #Let's investigate at a later time, please.
# return all(
# hint_child._origin is Any
# # hint_child.is_ignorable
# for hint_child in self._args_wrapped_tuple
# )
#
# # ..................{ PRIVATE ~ testers }..................
# def _is_subhint_branch(self, branch: TypeHint) -> bool:
#
# # If the other branch was *NOT* subscripted, we assume it was
# # subscripted by "Any" and simply check that the origins are compatible.
# if branch._is_args_ignorable:
# return issubclass(self._origin, branch._origin)
# # Else, the other branch was subscripted.
#
# # Return true only if...
# return (
# # That branch is also a type hint wrapper of the same concrete
# # subclass as this type hint wrapper *AND*...
# isinstance(branch, type(self)) and
# # The class originating this hint is a subclass of the class
# # originating that branch...
# issubclass(self._origin, branch._origin) and
# # All child type hints of this parent type hint are subhints of the
# # corresponding child type hints of that branch.
# all(
# self_child <= branch_child
# for self_child, branch_child in zip(
# self._args_wrapped_tuple, branch._args_wrapped_tuple)
# )
# )

# ....................{ SUBCLASSES ~ isinstanceable }....................
class _TypeHintOriginIsinstanceable(_TypeHintSubscripted):
'''
Expand Down

0 comments on commit f604f05

Please sign in to comment.