Skip to content

Commit

Permalink
Reiterables x 3.
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 unit testing this implementation.
Some things are tested, so it's probably *not* totally busted anymore.
But... just wow. Should have done this two years ago, huh? This is why
we can't have good type-checking: @leycec gets distracted by shiny
glittery stuff way too easily. (*Electable connectives in an electric delicatessen!*)
  • Loading branch information
leycec committed May 23, 2024
1 parent 83866f1 commit dccd8df
Show file tree
Hide file tree
Showing 2 changed files with 275 additions and 8 deletions.
40 changes: 40 additions & 0 deletions beartype/_check/code/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,46 @@
# See "LICENSE" for further details.

# ....................{ TODO }....................
#FIXME: [PEP] Type-check "collections.abc.Collection" and "typing.Collection" in
#a more thoughtful manner. Currently, we only type-check collections as if they
#were reiterables (i.e., by only type-checking the first items of those
#collections). Although collections *ARE* guaranteed to be reiterables, they
#could be considerably more. Specifically, for our purposes, they could be
#sequences -- a considerably more powerful form of reiterable.
#
#When time permits, consider differentiating between mere reiterables (which are
#guaranteed to be safely type-checkable only by type-checking their first items)
#and full-blown collections (which could be sequences) as follows:
#* Remove the "HintSignCollection" sign from the
# "HINT_SIGNS_REITERABLE_ARGS_1" frozen set.
#* Define a new "CODE_PEP484585_COLLECTION" string global in the
# "beartype._check.code.snip.codesnipstr" submodule resembling:
#CODE_PEP484585_COLLECTION = '''(
# {indent_curr} # True only if this pith is a collection *AND*...
# {indent_curr} isinstance({pith_curr_assign_expr}, {hint_curr_expr}) and
# {indent_curr} # True only if either...
# {indent_curr} (
# {indent_curr} # This a collection is empty *OR*...
# {indent_curr} not {pith_curr_var_name} or
# {indent_curr} (
# {indent_curr} # This collection is a sequence and a random item of
# {indent_curr} # this collection satisfies this hint;
# {indent_curr} {hint_sequence_child_placeholder}
# {indent_curr} if isinstance({pith_curr_var_name}, SequenceABC) else
# {indent_curr} # Else, this collection is *NOT* a sequence. In this
# {indent_curr} # case, fall back to checking that the first item of
# {indent_curr} # this collection satisfies this hint.
# {indent_curr} {hint_reiterable_child_placeholder}
# {indent_curr} )
# {indent_curr} )
# {indent_curr})'''

#FIXME: [PEP] Type-check "collections.deque" and "typing.Deque" in a more
#thoughtful manner. Currently, we only type-check deques as if they were
#reiterables (i.e., by only type-checking the first items of those deques).
#Although deques *ARE* guaranteed to be reiterables, they're somewhat more. We
#should additionally be type-checking their last items as well.

#FIXME: [SPEED] There exists a significant optimization that we *ABSOLUTELY*
#should implement. Currently, the "hints_meta" data structure is represented as
#a FixedList of size j, each item of which is a k-length tuple. If you briefly
Expand Down
243 changes: 235 additions & 8 deletions beartype_test/a00_unit/data/hint/pep/proposal/data_pep484.py
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,7 @@ def hints_pep484_meta() -> 'List[HintPepMetadata]':
HintSignAny,
HintSignByteString,
HintSignCallable,
HintSignCollection,
HintSignContextManager,
HintSignDefaultDict,
HintSignDict,
Expand Down Expand Up @@ -280,6 +281,9 @@ def hints_pep484_meta() -> 'List[HintPepMetadata]':
IS_PYTHON_AT_LEAST_3_10,
IS_PYTHON_AT_LEAST_3_9,
)
from beartype_test.a00_unit.data.data_type import (
sync_generator,
)
from beartype_test.a00_unit.data.hint.util.data_hintmetacls import (
HintPepMetadata,
HintPithSatisfiedMetadata,
Expand All @@ -290,6 +294,7 @@ def hints_pep484_meta() -> 'List[HintPepMetadata]':
defaultdict,
)
from collections.abc import (
Collection as CollectionABC,
Hashable as HashableABC,
Mapping as MappingABC,
MutableMapping as MutableMappingABC,
Expand All @@ -306,14 +311,17 @@ def hints_pep484_meta() -> 'List[HintPepMetadata]':
DefaultDict,
Dict,
ForwardRef,
FrozenSet,
Hashable,
Match,
Mapping,
MutableMapping,
MutableSequence,
MutableSet,
NewType,
OrderedDict,
Pattern,
Set,
Sized,
Type,
Optional,
Expand Down Expand Up @@ -567,6 +575,153 @@ def hints_pep484_meta() -> 'List[HintPepMetadata]':
),
),

# ................{ COLLECTION }................
# Note that:
# * Beartype type-checks collections in an optimal manner by
# explicitly covering the proper subset of collections that are:
# * Sequences (e.g., lists). If a collection is a sequence, beartype
# prefers type-checking a random item for maximal coverage.
# * Reiterables (e.g., sets). If a collection is *NOT* a sequence,
# beartype falls back to type-checking only the first item.
# Ergo, both sequences and reiterables *MUST* be tested below.
# * Collections define the __contains__(), __iter__(), and __len__()
# dunder methods. Reasonable candidates for objects that are *NOT*
# collections include:
# * Numeric scalars, which fail to define all three dunder methods.
# However, note that textual scalars (including both strings and
# byte strings) are sequences and thus collections.
# * Generators, which define __iter__() but fail to define
# __contains__() and __len__().

# Unsubscripted "Collection" attribute.
HintPepMetadata(
hint=Collection,
pep_sign=HintSignCollection,
warning_type=PEP585_DEPRECATION_WARNING,
isinstanceable_type=CollectionABC,
is_args=_IS_ARGS_HIDDEN,
is_typevars=_IS_TYPEVARS_HIDDEN,
piths_meta=(
# Empty set.
HintPithSatisfiedMetadata(set()),
# Empty tuple.
HintPithSatisfiedMetadata(()),
# Set containing arbitrary items.
HintPithSatisfiedMetadata({
'The boat fled on,', '—the boiling torrent drove,—', 82,}),
# Tuple containing arbitrary items.
HintPithSatisfiedMetadata((
'The shattered mountain', 'overhung the sea,', 79,)),
# Integer constant.
HintPithUnsatisfiedMetadata(0xFEEDBEAF),
# Synchronous Generator.
HintPithUnsatisfiedMetadata(sync_generator),
),
),

# # Collection of ignorable items.
# HintPepMetadata(
# hint=Collection[object],
# pep_sign=HintSignCollection,
# warning_type=PEP585_DEPRECATION_WARNING,
# isinstanceable_type=CollectionABC,
# piths_meta=(
# # Set of arbitrary objects.
# HintPithSatisfiedMetadata({
# 'Had been an elemental god.', b'At midnight',}),
# # String constant.
# HintPithUnsatisfiedMetadata(
# 'The moon arose: and lo! the ethereal cliffs'),
# ),
# ),
#
# # Collection of unignorable items.
# HintPepMetadata(
# hint=Collection[str],
# pep_sign=HintSignCollection,
# warning_type=PEP585_DEPRECATION_WARNING,
# isinstanceable_type=CollectionABC,
# piths_meta=(
# # Set of strings.
# HintPithSatisfiedMetadata({
# 'Now pausing on the edge of the riven wave;',
# 'Now leaving far behind the bursting mass',
# }),
# # String constant.
# HintPithUnsatisfiedMetadata(
# 'That fell, convulsing ocean. Safely fled—'),
# # Set of byte strings. Since only the first items of sets are
# # type-checked, a set of one item suffices.
# HintPithUnsatisfiedMetadata(
# pith={b'As if that frail and wasted human form,'},
# # Match that the exception message raised for this set...
# exception_str_match_regexes=(
# # Declares the index of the first item violating this
# # hint.
# r'\b[Ss]et index 0 item\b',
# # Preserves this item as is.
# r"\bAs if that frail and wasted human form,",
# ),
# ),
# ),
# ),
#
# # Generic collection.
# HintPepMetadata(
# hint=Collection[T],
# pep_sign=HintSignCollection,
# warning_type=PEP585_DEPRECATION_WARNING,
# isinstanceable_type=CollectionABC,
# is_typevars=True,
# piths_meta=(
# # Set of objects of one type.
# HintPithSatisfiedMetadata({
# 'Of Caucasus,', 'whose icy summits shone',}),
# # String constant.
# HintPithUnsatisfiedMetadata(
# 'Among the stars like sunlight, and around'),
# ),
# ),
#
#
# # Nested collections of nested collections of... you get the idea.
# HintPepMetadata(
# hint=Collection[Collection[str]],
# pep_sign=HintSignCollection,
# warning_type=PEP585_DEPRECATION_WARNING,
# isinstanceable_type=CollectionABC,
# piths_meta=(
# # Set of sets of frozen sets of strings.
# HintPithSatisfiedMetadata({
# frozenset((
# frozenset(('Whose caverned base',)),
# frozenset(('the whirlpools and the waves',)),
# )),
# }),
# # String constant.
# HintPithUnsatisfiedMetadata(
# 'Bursting and eddying irresistibly'),
# # Set of frozen sets of sets of byte strings. Since only the
# # first item of sets are type-checked, sets of one item suffice.
# HintPithUnsatisfiedMetadata(
# pith={
# frozenset((
# frozenset((b'Rage and resound for ever.',)),
# frozenset(('—Who shall save?—',)),
# )),
# },
# # Match that the exception message raised for this set
# # declares all items on the path to the item violating this
# # hint.
# exception_str_match_regexes=(
# r'\b[Ss]et index 0 item\b',
# r'\b[Ff]rozenset index 0 item\b',
# r"\bRage and resound for ever\.",
# ),
# ),
# ),
# ),

# ................{ CONTEXTMANAGER }................
# Context manager yielding strings.
HintPepMetadata(
Expand Down Expand Up @@ -976,7 +1131,7 @@ def hints_pep484_meta() -> 'List[HintPepMetadata]':
isinstanceable_type=dict,
is_typevars=True,
piths_meta=(
# Dictionary mapping string keys to integer values.
# Dictionary mapping keys of one type to values of another.
HintPithSatisfiedMetadata({
'Less-ons"-chastened': 2,
'Chanson': 1,
Expand Down Expand Up @@ -1051,9 +1206,9 @@ def hints_pep484_meta() -> 'List[HintPepMetadata]':
},
},
},
# Match that the exception message raised for this object
# declares all key-value pairs on the path to the value
# violating this hint.
# Match that the exception message raised for this
# dictionary declares all key-value pairs on the path to the
# value violating this hint.
exception_str_match_regexes=(
r'\bkey int 1\b',
r"\bkey str 'With thine,' ",
Expand Down Expand Up @@ -1433,6 +1588,22 @@ def hints_pep484_meta() -> 'List[HintPepMetadata]':
),
),

# Abstract set of ignorable items.
HintPepMetadata(
hint=AbstractSet[object],
pep_sign=HintSignAbstractSet,
warning_type=PEP585_DEPRECATION_WARNING,
isinstanceable_type=SetABC,
piths_meta=(
# Set of arbitrary objects.
HintPithSatisfiedMetadata({
'Had been an elemental god.', b'At midnight',}),
# String constant.
HintPithUnsatisfiedMetadata(
'The moon arose: and lo! the ethereal cliffs'),
),
),

# Abstract set of unignorable items.
HintPepMetadata(
hint=AbstractSet[str],
Expand All @@ -1452,18 +1623,74 @@ def hints_pep484_meta() -> 'List[HintPepMetadata]':
# type-checked, a set of one item suffices.
HintPithUnsatisfiedMetadata(
pith={b'As if that frail and wasted human form,'},
# Match that the exception message raised for this object...
# Match that the exception message raised for this set...
exception_str_match_regexes=(
# Declares the index of the random list item violating
# this hint.
# Declares the index of the first item violating this
# hint.
r'\b[Ss]et index 0 item\b',
# Preserves the value of this item as is.
# Preserves this item as is.
r"\bAs if that frail and wasted human form,",
),
),
),
),

# Generic abstract set.
HintPepMetadata(
hint=AbstractSet[T],
pep_sign=HintSignAbstractSet,
warning_type=PEP585_DEPRECATION_WARNING,
isinstanceable_type=SetABC,
is_typevars=True,
piths_meta=(
# Set of objects of one type.
HintPithSatisfiedMetadata({
'Of Caucasus,', 'whose icy summits shone',}),
# String constant.
HintPithUnsatisfiedMetadata(
'Among the stars like sunlight, and around'),
),
),


# Nested abstract sets of nested frozen sets.
HintPepMetadata(
hint=AbstractSet[FrozenSet[str]],
pep_sign=HintSignAbstractSet,
warning_type=PEP585_DEPRECATION_WARNING,
isinstanceable_type=SetABC,
piths_meta=(
# Set of frozen sets of of strings.
HintPithSatisfiedMetadata({
frozenset((
'Whose caverned base',
'the whirlpools and the waves',
)),
}),
# String constant.
HintPithUnsatisfiedMetadata(
'Bursting and eddying irresistibly'),
# Set of frozen sets of sets of byte strings. Since only the
# first item of sets are type-checked, sets of one item suffice.
HintPithUnsatisfiedMetadata(
pith={
frozenset((
b'Rage and resound for ever.',
b'-Who shall save?-',
)),
},
# Match that the exception message raised for this set
# declares all items on the path to the item violating this
# hint.
exception_str_match_regexes=(
r'\b[Ss]et index 0 item\b',
r'\b[Ff]rozenset index 0 item\b',
r"\bRage and resound for ever\.",
),
),
),
),

# ................{ SEQUENCE ~ list }................
# Unsubscripted "List" attribute.
HintPepMetadata(
Expand Down

0 comments on commit dccd8df

Please sign in to comment.