Skip to content

Commit

Permalink
Nested beartype validators.
Browse files Browse the repository at this point in the history
This commit resolves a critical low-level issue in @beartype's dynamic
type-checking code generator for **nested beartype
validator-in-container type hints** (e.g., type hints of the form
`list[typing.Annotated[{type}, Is[{validator}]]]`), resolving issue #372
kindly submitted by @ArneBachmann, fearless author of the peerless
programming language [AWFUL (Arguably Worst F*cked-Up
Language)](https://github.com/ArneBachmann/awful), which @leycec is
promptly going to port the @beartype codebase to. Press F to doubt. The
accursed @beartype 0.18.X release cycle continues to bedevil our world.
(*Inchoate incoherence is the GOAT!*)
  • Loading branch information
leycec committed Apr 21, 2024
1 parent d8abf08 commit 7e9eef4
Show file tree
Hide file tree
Showing 6 changed files with 315 additions and 146 deletions.
116 changes: 64 additions & 52 deletions beartype/_check/code/codecls.py
Expand Up @@ -82,8 +82,30 @@ def __init__(self) -> None:

#FIXME: *NO*. We actually need these to be independent copies. Ergo,
#there's *NO* alternative but to iteratively define one new instance
#of this dataclass for each index of this list.
obj_init=HintMeta(),
#of this dataclass for each index of this list. Indeed, this is
#actually a profound benefit. How? We can precompute the values of
#* "hint_curr_placeholder" *AT PYTHON STARTUP*. Just iteratively
# assign each "hint_curr_placeholder" according to its index. Do so
# based on the current logic of the enqueue_hint_child() method.
#* "pith_curr_var_name" *AT PYTHON STARTUP* in the exact same way.
#
#Indeed, this suggests we probably no longer need either:
#* "pith_curr_var_name_index".
#* The entire "codesnipcls" submodule.
#
#Well, isn't this turning out to be a significant facepalm.
#FIXME: Actually, the default of "None" here is fine. Let's instead:
#* Redefine the __getitem__() dunder method to dynamically inspect
# the item at the passed index. If:
# * "None", then replace this item with a new "HintMeta" instance
# suitable for the passed index.
# * Else, return the existing "HintMeta" instance at this index.
#* Refactor the enqueue_hint_child() method to then reassign the
# fields of this "HintMeta" instance to the desired values.
#
#This approach avoids expensive up-front computation at app startup,
#instead amortizing these costs across the app lifetime. Heh.
# obj_init=HintMeta(),
)

# 0-based index of metadata describing the last visitable hint in this
Expand Down Expand Up @@ -287,56 +309,46 @@ class HintMeta(object):
indent_level_curr: int

# ..................{ INITIALIZERS }..................
def __init__(self) -> None:
def __init__(
self,

# Mandatory parameters.
hint_curr_placeholder: str,
pith_curr_var_name_index: int,

# Optional parameters.
hint_curr: object = None,
pith_curr_expr: str = '',
indent_level_curr: int = 2,
) -> None:
'''
Initialize this type-checking metadata dataclass.
'''
# Initialize all instance variables to reasonable defaults.
self.hint_curr = None
self.hint_curr_placeholder = ''
self.pith_curr_expr = ''
self.pith_curr_var_name_index = 0
self.indent_level_curr = 2


#FIXME: Excise us up, sadly.
# def __init__(
# self,
# hint_curr: object,
# hint_curr_placeholder: str,
# pith_curr_expr: str,
# pith_curr_var_name_index: int,
# indent_level_curr: int,
# ) -> None:
# '''
# Initialize this type-checking metadata dataclass.
#
# Parameters
# ----------
# hint_curr : object
# Type hint currently visited by this BFS.
# hint_curr_placeholder : str
# Type-checking placeholder substring. See the class docstring.
# pith_curr_expr : str
# Pith expression. See the class docstring.
# pith_curr_var_name_index : int
# Pith variable name index. See the class docstring.
# indent_level_curr : int
# Indentation level. See the class docstring.
# '''
# assert isinstance(hint_curr_placeholder, str)
# assert isinstance(pith_curr_expr, str)
# assert isinstance(pith_curr_var_name_index, int)
# assert isinstance(indent_level_curr, int)
# assert hint_curr_placeholder
# assert pith_curr_expr
# assert pith_curr_var_name_index >= 0
# assert indent_level_curr > 1
#
# # Classify all passed parameters.
# self.hint_curr = hint_curr
# self.hint_curr_placeholder = hint_curr_placeholder
# self.pith_curr_expr = pith_curr_expr
# self.pith_curr_var_name_index = pith_curr_var_name_index
# self.indent_level_curr = indent_level_curr
Parameters
----------
hint_curr : object
Type hint currently visited by this BFS.
hint_curr_placeholder : str
Type-checking placeholder substring. See the class docstring.
pith_curr_expr : str
Pith expression. See the class docstring.
pith_curr_var_name_index : int
Pith variable name index. See the class docstring.
indent_level_curr : int
Indentation level. See the class docstring.
'''
assert isinstance(hint_curr_placeholder, str)
assert isinstance(pith_curr_expr, str)
assert isinstance(pith_curr_var_name_index, int)
assert isinstance(indent_level_curr, int)
assert hint_curr_placeholder
assert pith_curr_expr
assert pith_curr_var_name_index >= 0
assert indent_level_curr > 1

# Classify all passed parameters.
self.hint_curr = hint_curr
self.hint_curr_placeholder = hint_curr_placeholder
self.pith_curr_expr = pith_curr_expr
self.pith_curr_var_name_index = pith_curr_var_name_index
self.indent_level_curr = indent_level_curr
99 changes: 68 additions & 31 deletions beartype/_check/code/codemake.py
Expand Up @@ -66,15 +66,14 @@
CODE_PEP484604_UNION_CHILD_NONPEP_format,
CODE_PEP484604_UNION_PREFIX,
CODE_PEP484604_UNION_SUFFIX,
CODE_PEP572_PITH_ASSIGN_EXPR_format,
CODE_PEP586_LITERAL_format,
CODE_PEP586_PREFIX_format,
CODE_PEP586_SUFFIX,
CODE_PEP593_VALIDATOR_IS_format,
CODE_PEP593_VALIDATOR_METAHINT_format,
CODE_PEP593_VALIDATOR_PREFIX,
CODE_PEP593_VALIDATOR_SUFFIX_format,
CODE_PEP572_PITH_ASSIGN_AND_format,
CODE_PEP572_PITH_ASSIGN_EXPR_format,
)
from beartype._check.convert.convsanify import (
sanify_hint_child_if_unignorable_or_none,
Expand Down Expand Up @@ -1290,8 +1289,7 @@ def _enqueue_hint_child(pith_child_expr: str) -> str:
),
))

# For each PEP-compliant child hint of this union, generate
# and append code type-checking this child hint.
# For the 0-based index and each child hint of this union...
for hint_child_index, hint_child in enumerate(
hint_childs_pep):
# Code deeply type-checking this child hint.
Expand Down Expand Up @@ -1747,24 +1745,40 @@ def _enqueue_hint_child(pith_child_expr: str) -> str:
exception_prefix=EXCEPTION_PREFIX,
)

# If the expression assigning the current pith expression to
# a local variable is *NOT* the current pith expression,
# then the current pith expression requires assignment
# *BEFORE* access. This is the common case when this pith is
# a child pith rather than the root pith. In this case...
if pith_curr_assign_expr is not pith_curr_expr:
# Code assigning the current pith expression.
func_curr_code += CODE_PEP572_PITH_ASSIGN_AND_format(
indent_curr=indent_curr,
pith_curr_assign_expr=pith_curr_assign_expr,
pith_curr_var_name=pith_curr_var_name,
)
# Else, the current pith expression does *NOT* require
# assignment access.

# If this metahint is unignorable, deeply type-check
# this metahint. Specifically...
if hint_child is not None:
# Python expression yielding the value of the current pith,
# defaulting to the name of the local variable assigned to
# by the assignment expression performed below.
hint_curr_expr = pith_curr_var_name

# Tuple of the one or more beartype validators annotating
# this metahint.
hints_child = get_hint_pep593_metadata(hint_curr)
# print(f'hints_child: {repr(hints_child)}')

# If this metahint is ignorable...
if hint_child is None:
# If this metahint is annotated by only one beartype
# validator, the most efficient expression yielding the
# value of the current pith is simply the full Python
# expression *WITHOUT* assigning that value to a
# reusable local variable in an assignment expression.
# *NO* assignment expression is needed in this case.
#
# Why? Because beartype validators are *NEVER* recursed
# into. Each beartype validator is guaranteed to be the
# leaf of a type-checking subtree, guaranteeing this
# pith to be evaluated only once.
if len(hints_child) == 1:
hint_curr_expr = pith_curr_expr
# Else, this metahint is annotated by two or more
# beartype validators. In this case, the most efficient
# expression yielding the value of the current pith is
# the assignment expression assigning this value to a
# reusable local variable.
else:
hint_curr_expr = pith_curr_assign_expr
# Else, this metahint is unignorable. In this case...
else:
# Code deeply type-checking this metahint.
func_curr_code += CODE_PEP593_VALIDATOR_METAHINT_format(
indent_curr=indent_curr,
Expand All @@ -1780,12 +1794,13 @@ def _enqueue_hint_child(pith_child_expr: str) -> str:
# child hint and one or more arbitrary objects.
# Ergo, we need *NOT* explicitly validate that here.
hint_child_placeholder=_enqueue_hint_child(
pith_curr_var_name),
pith_curr_assign_expr),
)
# Else, this metahint is ignorable.

# For each beartype validator annotating this metahint...
for hint_child in get_hint_pep593_metadata(hint_curr):
# For the 0-based index and each beartype validator
# annotating this metahint...
for hint_child_index, hint_child in enumerate(hints_child):
# print(f'Type-checking PEP 593 type hint {repr(hint_curr)} argument {repr(hint_child)}...')
# If this is *NOT* a beartype validator, raise an
# exception.
Expand All @@ -1804,18 +1819,40 @@ def _enqueue_hint_child(pith_child_expr: str) -> str:
f'beartype validator).'
)
# Else, this argument is beartype-specific.

# Generate and append efficient code type-checking this
# validator by embedding this code as is.
#
# If this is any beartype validator *EXCEPT* the first,
# set the Python expression yielding the value of the
# current pith to the name of the local variable
# assigned to by the prior assignment expression. By
# deduction, it *MUST* be the case now that either:
# * This metahint was unignorable, in which case this
# assignment uselessly reduplicates the exact same
# assignment performed above. While non-ideal, this
# assignment is sufficiently efficient to make any
# optimizations here effectively worthless.
# * This metahint was ignorable, in which case this
# expression was set above to the assignment
# expression assigning this pith for the first
# beartype validator. Since this iteration has already
# processed the first beartype validator, this
# assignment expression has already been performed.
# Avoid inefficiently re-performing this assignment
# expression for each additional beartype validator by
# efficiently reusing the previously assigned local.
elif hint_child_index:
hint_curr_expr = pith_curr_var_name
# Else, this is the first beartype validator. See above.

# Code deeply type-checking this validator.
func_curr_code += CODE_PEP593_VALIDATOR_IS_format(
indent_curr=indent_curr,
# Python expression formatting the current pith into
# the "{obj}" variable already embedded by that
# class into this code.
# the "{obj}" format substring previously embedded
# by this validator into this code string.
hint_child_expr=hint_child._is_valid_code.format(
# Indentation unique to this child hint.
indent=INDENT_LEVEL_TO_CODE[indent_level_child],
obj=pith_curr_var_name,
obj=hint_curr_expr,
),
)

Expand Down
71 changes: 43 additions & 28 deletions beartype/_check/code/snip/codesnipstr.py
Expand Up @@ -65,29 +65,30 @@
'''


CODE_PEP572_PITH_ASSIGN_AND = '''
{indent_curr} # Localize this pith as a stupidly fast assignment expression.
{indent_curr} ({pith_curr_assign_expr}) is {pith_curr_var_name} and'''
'''
Code snippet embedding an assignment expression assigning the full Python
expression yielding the value of the current pith to a unique local variable.
This snippet is itself intended to be embedded in higher-level code snippets as
the first child expression of those snippets, enabling subsequent expressions in
those snippets to efficiently obtain this pith via this efficient variable
rather than via this inefficient full Python expression.
This snippet is a tautology that is guaranteed to evaluate to :data:`True` whose
side effect is this assignment expression. Note that there exist numerous less
efficient alternatives, including:
* ``({pith_curr_assign_expr}).__class__``, which is also guaranteed to evaluate
to :data:`True` but which implicitly triggers the ``__getattr__()`` dunder
method and thus incurs a performance penalty for user-defined objects
inefficiently overriding that method.
* ``isinstance({pith_curr_assign_expr}, object)``, which is also guaranteed to
evaluate :data:`True` but which is surprisingly inefficient in all cases.
'''
#FIXME: Preserved for posterity in the likelihood we'll need this again. *sigh*
# CODE_PEP572_PITH_ASSIGN_AND = '''
# {indent_curr} # Localize this pith as a stupidly fast assignment expression.
# {indent_curr} ({pith_curr_assign_expr}) is {pith_curr_var_name} and'''
# '''
# Code snippet embedding an assignment expression assigning the full Python
# expression yielding the value of the current pith to a unique local variable.
#
# This snippet is itself intended to be embedded in higher-level code snippets as
# the first child expression of those snippets, enabling subsequent expressions in
# those snippets to efficiently obtain this pith via this efficient variable
# rather than via this inefficient full Python expression.
#
# This snippet is a tautology that is guaranteed to evaluate to :data:`True` whose
# side effect is this assignment expression. Note that there exist numerous less
# efficient alternatives, including:
#
# * ``({pith_curr_assign_expr}).__class__``, which is also guaranteed to evaluate
# to :data:`True` but which implicitly triggers the ``__getattr__()`` dunder
# method and thus incurs a performance penalty for user-defined objects
# inefficiently overriding that method.
# * ``isinstance({pith_curr_assign_expr}, object)``, which is also guaranteed to
# evaluate :data:`True` but which is surprisingly inefficient in all cases.
# '''

# ....................{ HINT ~ pep : (484|585) : generic }....................
CODE_PEP484585_GENERIC_PREFIX = '''(
Expand Down Expand Up @@ -390,12 +391,26 @@
'''

# ....................{ HINT ~ pep : 484 : instance }....................
CODE_PEP484_INSTANCE = (
'''isinstance({pith_curr_expr}, {hint_curr_expr})''')
CODE_PEP484_INSTANCE = '''isinstance({pith_curr_expr}, {hint_curr_expr})'''
'''
:pep:`484`-compliant code snippet type-checking the current pith against the
current child PEP-compliant type expected to be a trivial non-:mod:`typing`
type (e.g., :class:`int`, :class:`str`).
Caveats
-------
**This snippet is intentionally compact rather than embedding a human-readable
comment.** For example, this snippet intentionally avoids doing this:
.. code-block:: python
CODE_PEP484_INSTANCE = '
{indent_curr}# True only if this pith is of this type.
{indent_curr}isinstance({pith_curr_expr}, {hint_curr_expr})'
Although feasible, doing that would significantly complicate code generation for
little to *no* tangible gain. Indeed, we actually tried doing that once. We
failed hard after breaking everything. **Avoid the mistakes of the past.**
'''

# ....................{ HINT ~ pep : 484 : union }....................
Expand Down Expand Up @@ -519,7 +534,7 @@

CODE_PEP593_VALIDATOR_IS = '''
{indent_curr} # True only if this pith satisfies this caller-defined
{indent_curr} # validator of this annotated.
{indent_curr} # validator of this annotated metahint.
{indent_curr} {hint_child_expr} and'''
'''
:pep:`593`-compliant code snippet type-checking the current pith against
Expand Down Expand Up @@ -575,8 +590,8 @@
CODE_PEP484604_UNION_CHILD_PEP.format)
CODE_PEP484604_UNION_CHILD_NONPEP_format: Callable = (
CODE_PEP484604_UNION_CHILD_NONPEP.format)
CODE_PEP572_PITH_ASSIGN_AND_format: Callable = (
CODE_PEP572_PITH_ASSIGN_AND.format)
# CODE_PEP572_PITH_ASSIGN_AND_format: Callable = (
# CODE_PEP572_PITH_ASSIGN_AND.format)
CODE_PEP572_PITH_ASSIGN_EXPR_format: Callable = (
CODE_PEP572_PITH_ASSIGN_EXPR.format)
CODE_PEP586_LITERAL_format: Callable = (
Expand Down
2 changes: 1 addition & 1 deletion beartype/vale/_util/_valeutilsnip.py
Expand Up @@ -35,7 +35,7 @@


VALE_CODE_CHECK_ISINSTANCE_TEST = '''
{{indent}}# True only if this pith is an object instancing this superclass.
{{indent}}# True only if this pith is of this type.
{{indent}}isinstance({{obj}}, {param_name_types})'''
'''
:attr:`beartype.vale.IsInstance`-specific code snippet validating an arbitrary
Expand Down

2 comments on commit 7e9eef4

@ArneBachmann
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh no, please don't advertise AWFUL... It was a fun project, but the big test case fails in an undebuggable way, so it was never finished in good shape :-(
I never learned how to write parsers and compilers, so this was a struggle. But I like how all the little features play together in an interesting way :-D

@leycec
Copy link
Member Author

@leycec leycec commented on 7e9eef4 Apr 21, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm so sorry. I fell in love with the AWFUL acronym – and the rest was history. I couldn't help myself! 😄

I know your pain, though. I have the exact same mixed feelings about a language I too made years ago when I had youthful hair: zeshy, a zsh-based shell scripting language so painfully broken I shudder to link anyone to it. Unlike AWFUL, zeshy didn't even have unit tests. It was the manifest failure of zeshy (and all my hopes and dreams of being awesome with it) that inspired me to go hard on automated QA devtools like @beartype – and the rest was history.

Thank the GitHub Gods for zeshy! For it has given us @beartype.

Please sign in to comment.