-
-
Notifications
You must be signed in to change notification settings - Fork 50
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This commit deprioritizes @beartype-specific **forward reference proxies** (i.e., internal objects proxying external user-defined types that have yet to be defined) in type tuples passed as the second arguments to the `isinstance()` builtin, reducing the likelihood that type-checks involving forward references will raise unexpected exceptions. For example, consider this simple example: ```python from beartype import beartype @beartype def muh_func(muh_arg: 'UndefinedType' | None = None): ... class UndefinedType(object): ... ``` In the above example, @beartype should ideally be able to type-check **at decoration time** that the default value of the optional `muh_arg` parameter of the `muh_func()` function satisfies the type hint `'UndefinedType' | None` – despite the fact that the `UndefinedType` class is undefined! To do so, @beartype now internally reorders the types comprising this union when type-checking this default value: ```python # ...from this default type-check, which would raise a decoration-time # exception due to "UndefinedType" being undefined... isinstance(muh_arg, (UndefinedTypeProxy, NoneType)) # ...to this default type-check, which should raise *NO* decoration-time # exception. Why? Because the default value "None" for the "muh_arg" # parameter satisfies the first "NoneType" type, which then # short-circuits the isinstance() call and thus ignores the problematic # "UndefinedTypeProxy" type altogether. isinstance(muh_arg, (NoneType, UndefinedTypeProxy)) ``` In short, a nothingburger – but a promising nothingburger that may yield future delights in the incautious event that we actually elect to try type-checking defaults at decoration time again. *Gulp.* (*Helpless kelp!*)
- Loading branch information
Showing
9 changed files
with
322 additions
and
197 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
3f6354c
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note: since using an undefined type before its defined (ie class/obj) results in a
NameError
anyway, it should be impossible (with this change) for python to not pre-abort before the problem ever gets to beartype.def muh_func(muh_arg: 'Undef' | None = Undef()): # raises NameError 'Undef'
But is there any "special" types that would invalidate this assumption?
3f6354c
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hah, hah! You're exactly right. It's possible that the upcoming PEP 649 of which much has been said and shouted about would invalidate this assumption – but probably not. That's just a distraction.
No, this commit is intended solely to handle the opposite case to the one you helpfully presented, @JWCS:
One of the reasons that @beartype 0.18.0 blew up so catastrophically in my pasty face is that I failed to account for the above edge case. It turned out that whether or not @beartype 0.18.0 correctly type-checked the above default value of
None
was entirely non-deterministic. Depending on whether @beartype internally ordered the undefinedUndef
type before the definedtype(None)
type, that type-check would spuriously fail with abeartype.roar.BeartypeCallHintForwardRefException
exception. Why? Because @beartype type-checks that default value with an internal call to theisinstance()
builtin passed a 2-tuple resembling either:isinstance(muh_arg, (UndefForwardRef, type(None))
, which is bad.isinstance(muh_arg, (type(None), UndefForwardRef)
, which is good.Now, @beartype ensures that internal call always resembles the good ordering. And one less catastrophic edge case was born.
Look. Runtime type-checking is Hell™. I can only stroke my neckbeard and pretend I know what I'm doing. 🧙♂️