Skip to content

Commit fed00ef

Browse files
authored
Allow comparisons to refine Optional types without strict-optional (#4523)
Fixes #4520.
1 parent e2fd043 commit fed00ef

File tree

6 files changed

+40
-17
lines changed

6 files changed

+40
-17
lines changed

mypy/binder.py

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -267,15 +267,13 @@ def assign_type(self, expr: Expression,
267267
return
268268

269269
enclosing_type = self.most_recent_enclosing_type(expr, type)
270-
if (isinstance(enclosing_type, AnyType)
271-
and not restrict_any):
270+
if isinstance(enclosing_type, AnyType) and not restrict_any:
272271
# If x is Any and y is int, after x = y we do not infer that x is int.
273272
# This could be changed.
274-
if not isinstance(type, AnyType):
275-
# We narrowed type from Any in a recent frame (probably an
276-
# isinstance check), but now it is reassigned, so broaden back
277-
# to Any (which is the most recent enclosing type)
278-
self.put(expr, enclosing_type)
273+
# Instead, since we narrowed type from Any in a recent frame (probably an
274+
# isinstance check), but now it is reassigned, we broaden back
275+
# to Any (which is the most recent enclosing type)
276+
self.put(expr, enclosing_type)
279277
# As a special case, when assigning Any to a variable with a
280278
# declared Optional type that has been narrowed to None,
281279
# replace all the Nones in the declared Union type with Any.

mypy/checker.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3328,7 +3328,7 @@ def find_isinstance_check(self, node: Expression
33283328
if literal(expr) == LITERAL_TYPE:
33293329
vartype = type_map[expr]
33303330
return self.conditional_callable_type_map(expr, vartype)
3331-
elif isinstance(node, ComparisonExpr) and experiments.STRICT_OPTIONAL:
3331+
elif isinstance(node, ComparisonExpr):
33323332
# Check for `x is None` and `x is not None`.
33333333
is_not = node.operators == ['is not']
33343334
if any(is_literal_none(n) for n in node.operands) and (

test-data/unit/check-isinstance.test

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2077,9 +2077,7 @@ class A: pass
20772077

20782078
def foo1(x: Union[A, str, None]) -> None:
20792079
if x is None:
2080-
# Since None is a subtype of all types in no-strict-optional,
2081-
# we can't really narrow the type here
2082-
reveal_type(x) # E: Revealed type is 'Union[__main__.A, builtins.str, None]'
2080+
reveal_type(x) # E: Revealed type is 'None'
20832081
elif isinstance(x, A):
20842082
# Note that Union[None, A] == A in no-strict-optional
20852083
reveal_type(x) # E: Revealed type is '__main__.A'
@@ -2088,12 +2086,12 @@ def foo1(x: Union[A, str, None]) -> None:
20882086

20892087
def foo2(x: Optional[str]) -> None:
20902088
if x is None:
2091-
reveal_type(x) # E: Revealed type is 'Union[builtins.str, None]'
2089+
reveal_type(x) # E: Revealed type is 'None'
20922090
elif isinstance(x, A):
20932091
# Mypy should, however, be able to skip impossible cases
20942092
reveal_type(x)
20952093
else:
2096-
reveal_type(x) # E: Revealed type is 'Union[builtins.str, None]'
2094+
reveal_type(x) # E: Revealed type is 'builtins.str'
20972095
[builtins fixtures/isinstance.pyi]
20982096

20992097
[case testNoneCheckDoesNotNarrowWhenUsingTypeVars]
@@ -2153,7 +2151,7 @@ def bar(x: Union[List[str], List[int], None]) -> None:
21532151
from typing import Union, Optional, List
21542152

21552153
# This test is the same as the one above, except for strict-optional.
2156-
# It isn't testing anything explicitly and mostly exists for the sake
2154+
# It isn't testing anything explicitly and mostly exists for the sake
21572155
# of completeness.
21582156

21592157
def foo(x: Optional[List[str]]) -> None:
@@ -2166,4 +2164,3 @@ def bar(x: Union[List[str], List[int], None]) -> None:
21662164
assert isinstance(x, list)
21672165
reveal_type(x) # E: Revealed type is 'Union[builtins.list[builtins.str], builtins.list[builtins.int]]'
21682166
[builtins fixtures/isinstancelist.pyi]
2169-

test-data/unit/check-tuples.test

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1099,7 +1099,7 @@ possibles: Tuple[int, Tuple[A]]
10991099
x: Optional[Tuple[B]]
11001100

11011101
if x in possibles:
1102-
reveal_type(x) # E: Revealed type is 'Union[Tuple[__main__.B], None]'
1102+
reveal_type(x) # E: Revealed type is 'Tuple[__main__.B]'
11031103
else:
11041104
reveal_type(x) # E: Revealed type is 'Union[Tuple[__main__.B], None]'
11051105

test-data/unit/check-unions.test

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -948,3 +948,31 @@ MYTYPE = List[Union[str, "MYTYPE"]]
948948
[builtins fixtures/list.pyi]
949949
[out]
950950
main:2: error: Recursive types not fully supported yet, nested types replaced with "Any"
951+
952+
[case testNonStrictOptional]
953+
from typing import Optional, List
954+
955+
def union_test1(x):
956+
# type: (Optional[List[int]]) -> Optional[int]
957+
if x is None:
958+
return x
959+
else:
960+
return x[0]
961+
962+
def union_test2(x):
963+
# type: (Optional[List[int]]) -> Optional[int]
964+
if isinstance(x, type(None)):
965+
return x
966+
else:
967+
return x[0]
968+
969+
def f(): return 0
970+
971+
def union_test3():
972+
# type: () -> int
973+
x = f()
974+
assert x is None
975+
x = f()
976+
return x + 1
977+
978+
[builtins fixtures/isinstancelist.pyi]

test-data/unit/check-unreachable-code.test

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -618,7 +618,7 @@ class Child(Parent):
618618
def foo(self) -> int:
619619
reveal_type(self) # E: Revealed type is '__main__.Child'
620620
if self is None:
621-
reveal_type(self) # E: Revealed type is '__main__.Child'
621+
reveal_type(self) # E: Revealed type is 'None'
622622
return None
623623
reveal_type(self) # E: Revealed type is '__main__.Child'
624624
return 3

0 commit comments

Comments
 (0)