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

Consider adding --non-numeric-bool mode #8363

Open
sobolevn opened this issue Feb 4, 2020 · 10 comments · May be fixed by #17487
Open

Consider adding --non-numeric-bool mode #8363

sobolevn opened this issue Feb 4, 2020 · 10 comments · May be fixed by #17487
Labels

Comments

@sobolevn
Copy link
Member

sobolevn commented Feb 4, 2020

As it was discussed in #8069, sometimes we need to logically separate int and bool types.

Currently mypy allows this to happen:

def some(a: int): ...

some(x > 1)

Because bool is subtype of int. I am proposing to add a strict-bool mode, so bool would be treated as a non-int type. And this code would generate an error.

It can be a new config option: --strict-bool or a new disabled by default error code in the future as @JukkaL suggested.

@hauntsaninja
Copy link
Collaborator

Agree that this could be useful. For an amusing real life instance of this problem: psf/black#762 followed by psf/black#1092

@Akuli
Copy link
Contributor

Akuli commented Aug 4, 2020

this happens to me a lot, and I think this could be a more useful behaviour for a --strict-bool flag:

def foo() -> bool:
    ...

if foo:
    ...

Functions are not bools, so this problem occurs only when if or while or something else converts things to bool implicitly. With --strict-bool-conversion, if foo would be allowed only in these cases:

  • type of foo is bool
  • type of foo is Optional[something]
  • type of foo is Any
  • type of foo has __bool__
  • type of foo has __len__ (python handles foo.__len__() == 0 even without overriding __bool__)

@Akuli
Copy link
Contributor

Akuli commented Aug 4, 2020

type of foo is Optional[something]

Maybe instead of this, it should be more general, such as "type of foo is a Union such that some unioned types can be true and some can be false". This way, Optional[Literal[False]] aka Union[None, Literal[False]] would be disallowed.

@Akuli
Copy link
Contributor

Akuli commented Jan 5, 2021

I think my previous two comments can be ignored. There's already --warn-unreachable, which partially solves my problem.

@JukkaL
Copy link
Collaborator

JukkaL commented Jan 6, 2021

Since we now support enabling individual error codes (#9172), this should be implemented as one or more optionally enabled error codes instead of a command-line flag such as --strict-bool.

We may want two separate error codes, since there are at least two separate issues that would be useful to catch:

  • Using bool as a subtype of int (at least in many contexts -- this may be hard to implement in full generality).
  • Using callable type or Type[x] in a boolean context, such as if func: (when it should be if func():), or possibly what @Akuli suggested above, but it might be noisy.

Additionally, --strict should probably enable these error codes.

@sobolevn
Copy link
Member Author

Using callable type or Type[x] in a boolean context, such as if func: (when it should be if func():), or possibly what @Akuli suggested above, but it might be noisy.

Highly related #10666

@jorenham
Copy link

jorenham commented Nov 7, 2024

but

>>> issubclass(bool, int)
True

@gandhis1
Copy link

gandhis1 commented Nov 7, 2024

but

>>> issubclass(bool, int)
True

That's the point. Just like datetime vs date, this is a subclass relationship that can obscure potential bugs.

@jorenham
Copy link

jorenham commented Nov 7, 2024

but

>>> issubclass(bool, int)
True

That's the point. Just like datetime vs date, this is a subclass relationship that can obscure potential bugs.

I understand the problem here all too well, and I also wish that there was a solution.
But I can guarantee you that this will cause more problems that it solves.
Because we have deviated from the agreed-upon rules before, several times actually (e.g. the incorrect "promotion" of int, float, complex, bytearray, memoryview, etc). And each time the consequence is a cocktail of confusion, bugs, more rules, and many users giving up on typing.

I don't want that again.


There have been other solutions proposed that would disallow assigning bool to an int, such as a LiteralInt, or even better, Just[int]. The latter would also make make it possible to workaround the incorrect (and claimed to be permanent) int to float to complex "promotion" rules.


Adding this flag will globally result in incorrect type inference for int.
This will force the maintainers of typing libraries to change every case of int to int | bool in order to ensure correct behavior for those that'll use this flag.
But several popular linters and type-checkers that will flag this as a redundant expression, because according the the typing spec, that is indeed the case. And the recommend removing it, because that will help with readability, maintainability, and type-checker performance.
However, if each int must now be rewritten to int | bool, these linter rules most likely be disabled project-wide.

This is only one of the ways in which such a flag could result in a huge mess. But the biggest problem when deviating from the agreed-upon typing rules is that of the unknown unknown unknowns. That makes it impossible to predict all of the consequences, all of which negative, that this could have.


Disabling the flag by default doesn't matter:
Those that will use it will inevitably experience issues with third party libraries, which they will raise, and maintainers will listen.
I predict that many will enable it, mostly because the term "strict" suggests that it's more correct, and that's false.


So please, don't deviate from the agreed-upon rules. It's seriously not worth it.

@jorenham
Copy link

jorenham commented Nov 7, 2024

There have been other solutions proposed that would disallow assigning bool to an int, such as a LiteralInt, or even better, Just[int].

This Just[T] can already be implemented BTW:

class Just[~T](Protocol):  # T@Just is invariant
    @property
    def __class__(self, /) -> type[T]: ...
    @__class__.setter
    def __class__(self, t: type[T], /) -> None: ...

And it even works (for int, not for float, and not with pyright): https://mypy-play.net/?mypy=latest&python=3.12&gist=905c545a5be1b3c3515d16009776e4b9

sobolevn added a commit to sobolevn/useful_types that referenced this issue Nov 10, 2024
@hauntsaninja hauntsaninja changed the title Consider adding strict-bool mode Consider adding --non-numeric-bool mode Nov 26, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging a pull request may close this issue.

6 participants