Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Analyse x.__bool__() <=> bool(x) for reachabilty #5515

Closed
Gobot1234 opened this issue Jul 15, 2023 · 6 comments
Closed

Analyse x.__bool__() <=> bool(x) for reachabilty #5515

Gobot1234 opened this issue Jul 15, 2023 · 6 comments
Labels
as designed Not a bug, working as intended enhancement request New feature or request

Comments

@Gobot1234
Copy link

Is your feature request related to a problem? Please describe.
Currently objects which are marked as returning Literal[False/True] do not actually have their return types used for reachabilty analysis, there is also no way to get the return type using bool(x) which should act an alias for x.__bool__() (or maybe type(x).__bool__(x) if you'd prefer 😉)

Describe the solution you'd like
I'd like to have the return of __bool__ be used for type checking and also have bool() call the methods bool to get the return type and not just have it be bool when it could be more specific.

Additional context
MyPy is implementing a similar feature and I've found it very successful at finding bugs python/mypy#15645

@Gobot1234 Gobot1234 added the enhancement request New feature or request label Jul 15, 2023
@erictraut
Copy link
Collaborator

This seems like something that should be handled in the typeshed stubs, not in special-case logic within a type checker. The bool.__new__ method is currently defined as:

    def __new__(cls, __o: object = ...) -> Self: ...

It could be defined as an overload that returns Literal[True] or Literal[False] if the supplied object implements a __bool__ method that returns a literal value.

@erictraut erictraut added the as designed Not a bug, working as intended label Jul 15, 2023
@Gobot1234
Copy link
Author

I think pyright has some issues with defining the new method of bool like that

# builtins.pyi
class _Truthy(Protocol):
    def __bool__(self) -> Literal[True]: ...

class _Falsy(Protocol):
    def __bool__(self) -> Literal[False]: ...

@final
class bool(int):
    @overload
    def __new__(cls, __o: _Truthy) -> Literal[True]: ...
    @overload
    def __new__(cls, __o: _Falsy) -> Literal[False]: ...
    @overload
    def __new__(cls, __o: object = ...) -> bool: ...
# test.py
class MissingSentinel(Any if TYPE_CHECKING else object):
    __slots__ = ()

    def __eq__(self, other: object) -> Literal[False]:
        return False

    def __bool__(self) -> Literal[False]:
        return False

    def __hash__(self) -> Literal[0]:
        return 0

    def __repr__(self) -> Literal["..."]:
        return "..."

MISSING: Final = MissingSentinel()
reveal_type(MISSING.__bool__)  # type is () -> Literal[False]
reveal_type(bool(MISSING.__bool__()))  # revealed type is bool?
reveal_type(bool(MISSING))  # revealed type is Literal[True]???

@Gobot1234
Copy link
Author

This appears to be caused by the weird bases but I'll run this change by the typeshed folk cause it seems to mostly work apart from this

@Gobot1234
Copy link
Author

Also adding to this the types of True/False.__bool__() isn't a Literal which I think could be improved

@erictraut
Copy link
Collaborator

When you define a class that derives from Any, many things won't work correctly. Out of curiosity, why are you doing that? If you delete Any if TYPE_CHECKING else object, you'll get better results.

@Gobot1234
Copy link
Author

This class is for a private sentinel that shouldn't be exposed to users and can be assigned to any type where things like None don't make sense as a default.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
as designed Not a bug, working as intended enhancement request New feature or request
Projects
None yet
Development

No branches or pull requests

2 participants