-
-
Notifications
You must be signed in to change notification settings - Fork 2.9k
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
Fail when conditioning on implicit bool #10557
Conversation
@@ -665,6 +665,24 @@ consistently when using the call-based syntax. Example: | |||
# Error: First argument to namedtuple() should be "Point2D", not "Point" | |||
Point2D = NamedTuple("Point", [("x", int), ("y", int)]) | |||
|
|||
|
|||
|
|||
Implicitly converting to bool always evaluates as True [implicit-bool] |
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.
I'm not sure whether "implicit-bool" should be the code, and whether "implicit conversion" captures what's happening here. Perhaps there's some accepted terminology for this in cpython?
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.
The Python docs seem to use the term "boolean operations"
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.
"context of Boolean operation" is also used
flags = re.search('# flags: (.*)$', program_text, flags=re.MULTILINE) | ||
if incremental_step > 1: | ||
flags2 = re.search('# flags{}: (.*)$'.format(incremental_step), program_text, | ||
flags=re.MULTILINE) | ||
if flags2: | ||
flags = flags2 | ||
|
||
disabled_error_codes = {codes.IMPLICIT_BOOL} |
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.
Too many fixtures would need multiple changes to add __len__
to list, __bool__
to int, etc. and I understand that making fixtures more "complete" across the board is discouraged (to keep tests lean).
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.
@@ -665,6 +665,24 @@ consistently when using the call-based syntax. Example: | |||
# Error: First argument to namedtuple() should be "Point2D", not "Point" | |||
Point2D = NamedTuple("Point", [("x", int), ("y", int)]) | |||
|
|||
|
|||
|
|||
Implicitly converting to bool always evaluates as True [implicit-bool] |
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.
The Python docs seem to use the term "boolean operations"
TYPE_CONVERTS_TO_BOOL_IMPLICITLY = \ | ||
'"{}" does not implement __bool__ or __len__ so the expression is always truthy' # type: Final | ||
ALL_TYPES_IN_UNION_CONVERT_TO_BOOL_IMPLICITLY = \ | ||
'Neither of {} implements __bool__ or __len__ '\ |
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.
nit:
'Neither of {} implements __bool__ or __len__ '\ | |
'No members of {} implement __bool__ or __len__ '\ |
So, this opens the door for a more generic always-true/false check, and this was actually my first approach - to leverage the if t.can_be_true and not t.can_be_false:
self.fail('expression is always true', ...)
elif not t.can_be_true and t.can_be_false:
self.fail('expression is always false', ...) For this to detect the "implicit" case, I had to teach class Instance(ProperType):
...
def __init__(self, ...):
...
if self.type and not self.type.has_readable_member('__bool__') and \
not self.type.has_readable_member('__len__'):
self.can_be_true = True
self.can_be_false = False This has the benefit of handling many of the details (literals, unions). However:
Initially I hesitated trying to make a generic "always-true/false" checker simply because I felt the error wouldn't be as useful to flag a very common pitfall of using an object as a boolean. |
I'll try a different approach. |
In the past, I have used Mypy already has a flag that makes it warn about code that never runs. If the scope of this PR grows, maybe consider how it interacts with that? |
Yes, and there's special treatment for "always true" and "always false" that's established during semantic analysis, e.g. so that Right now there's no special handling, so e.g. if False:
print('x') would result in:
|
Perhaps it's good that mypy warns about |
Merged #10666 instead since it had a cooler number. |
Description
Fails conditionals where the expression's type does not implement
__bool__
nor__len__
since such expressions would always evaluate as truthy and likely an error (e.g. the common pitfall of conditioning on a callable rather than its result).This feature was proposed by @pkch in #3195 (comment) as an alternative to the deprecated
--strict-bool
.Test Plan
Test case was added.