-
-
Notifications
You must be signed in to change notification settings - Fork 2.9k
Fix inference logic for isinstance #2997
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
Conversation
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.
Can you add unit tests?
mypy/checker.py
Outdated
@@ -2711,9 +2711,14 @@ def get_isinstance_type(expr: Expression, type_map: Dict[Expression, Type]) -> T | |||
|
|||
types.append(type) | |||
|
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 think there are too many blank lines here. IMO the only blank line should be outside the for-loop, before the assert on line 2720 below.
a61bf32
to
79a8085
Compare
mypy/semanal.py
Outdated
@@ -2640,8 +2640,7 @@ def visit_member_expr(self, expr: MemberExpr) -> None: | |||
# This branch handles the case foo.bar where foo is a module. | |||
# In this case base.node is the module's MypyFile and we look up | |||
# bar in its namespace. This must be done for all types of bar. | |||
file = base.node | |||
assert isinstance(file, (MypyFile, type(None))) | |||
file = cast(Optional[MypyFile], base.node) |
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.
Add comment describing why we don't use assert isinstance
here.
mypy/semanal.py
Outdated
@@ -2658,7 +2657,8 @@ def visit_member_expr(self, expr: MemberExpr) -> None: | |||
# one type checker run. If we reported errors here, | |||
# the build would terminate after semantic analysis | |||
# and we wouldn't be able to report any type errors. | |||
full_name = '%s.%s' % (file.fullname() if file is not None else None, expr.name) | |||
full_name = '%s.%s' % (file.fullname() if file is not None |
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.
This change seems unnecessary now?
test-data/unit/check-isinstance.test
Outdated
from typing import * | ||
def f(x: Union[int, str], typ: type) -> None: | ||
if isinstance(x, (typ, int)): | ||
x + 1 # E: Unsupported operand types for + (likely involving Union) |
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.
Add also an else:
block and do a reveal_type(x)
in that block. Add another reveal_type(x)
just after the if statement.
test-data/unit/check-isinstance.test
Outdated
if isinstance(x, (typ, int)): | ||
x + 1 # E: Unsupported operand types for + (likely involving Union) | ||
[builtins fixtures/isinstancelist.pyi] | ||
|
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.
Add test for the type object that is Type[A]
or similar. Example:
class A: pass
def f(x: Union[int, A], a: Type[A]) -> None:
if isinstance(x, a):
reveal_type(x)
elif isinstance(x, int):
reveal_type(x)
else:
reveal_type(x)
reveal_type(x)
bca997d
to
6ea4f4d
Compare
2fa26d1
to
be30faa
Compare
mypy/checker.py
Outdated
return UnionType(types) | ||
types.append(TypeRange(type, is_upper_bound=False)) | ||
elif isinstance(type, TypeType): | ||
types.append(TypeRange(type.item, is_upper_bound=True)) |
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.
Add comment here describing why is_upper_bound=True
.
mypy/checker.py
Outdated
types.append(TypeRange(type.item, is_upper_bound=True)) | ||
else: # we didn't see an actual type, but rather a variable whose value is unknown to us | ||
return None | ||
assert len(types) != 0 |
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 wonder if we should return None
if the length if 0? For example, what happens if we have isinstance(x, ())
?
test-data/unit/check-isinstance.test
Outdated
x + 1 # E: Unsupported operand types for + (likely involving Union) | ||
reveal_type(x) # E: Revealed type is 'Union[builtins.int, builtins.str]' | ||
else: | ||
reveal_type(x) # E: Revealed type is 'Union[builtins.int, builtins.str]' |
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.
Here we could actually infer the type to be builtins.str
, since we know that it can't be builtins.int
due to the isinstance
check.
reveal_type(x) # E: Revealed type is '__main__.A' | ||
reveal_type(x) # E: Revealed type is 'Union[builtins.int, __main__.A]' | ||
|
||
[builtins fixtures/isinstancelist.pyi] |
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.
Not directly related, but can you add a test case that does something like isinstance(x, ())
?
mypy/checker.py
Outdated
if current_type: | ||
if is_proper_subtype(current_type, proposed_type): | ||
# Expression is always of type proposed_type | ||
if not any(type_range.is_upper_bound for type_range in proposed_type_ranges) \ |
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.
Use if (...)"
to avoid the use of \
.
Fixes #2993