Skip to content

Commit f20cdc3

Browse files
committed
Get rid of Optional when assigning Any in an is None branch
As a special case, when assigning Any to a variable with a declared Optional type that has been narrowed to None, replace all the Nones in the declared Union type with Any. The result of this is: ``` def f():... def g(x: Optional[int]) -> int: if x is None: x = f() reveal_type(x) # Union[int, Any] # <---- this is new (was Optional[int]) reveal_type(x) # Union[int, Any] # was also Optional[int] return x # Ok # was an error ``` Fixes #3526.
1 parent c3eeebf commit f20cdc3

File tree

2 files changed

+65
-2
lines changed

2 files changed

+65
-2
lines changed

mypy/binder.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
if MYPY:
77
from typing import DefaultDict
88

9-
from mypy.types import Type, AnyType, PartialType, UnionType, TypeOfAny
9+
from mypy.types import Type, AnyType, PartialType, UnionType, TypeOfAny, NoneTyp
1010
from mypy.subtypes import is_subtype
1111
from mypy.join import join_simple
1212
from mypy.sametypes import is_same_type
@@ -276,9 +276,23 @@ def assign_type(self, expr: Expression,
276276
# isinstance check), but now it is reassigned, so broaden back
277277
# to Any (which is the most recent enclosing type)
278278
self.put(expr, enclosing_type)
279+
# As a special case, when assigning Any to a variable with a
280+
# declared Optional type that has been narrowed to None,
281+
# replace all the Nones in the declared Union type with Any.
282+
# This overrides the normal behavior of ignoring Any assignments to variables
283+
# in order to prevent false positives.
284+
# (See discussion in #3526)
285+
elif (isinstance(type, AnyType)
286+
and isinstance(declared_type, UnionType)
287+
and any(isinstance(item, NoneTyp) for item in declared_type.items)
288+
and isinstance(self.most_recent_enclosing_type(expr, NoneTyp()), NoneTyp)):
289+
# Replace any Nones in the union type with Any
290+
new_items = [type if isinstance(item, NoneTyp) else item
291+
for item in declared_type.items]
292+
self.put(expr, UnionType(new_items))
279293
elif (isinstance(type, AnyType)
280294
and not (isinstance(declared_type, UnionType)
281-
and any(isinstance(item, AnyType) for item in declared_type.items))):
295+
and (any(isinstance(item, AnyType) for item in declared_type.items)))):
282296
# Assigning an Any value doesn't affect the type to avoid false negatives, unless
283297
# there is an Any item in a declared union type.
284298
self.put(expr, declared_type)

test-data/unit/check-optional.test

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -698,3 +698,52 @@ def g(x: str) -> None: pass
698698

699699
y = int()
700700
[builtins fixtures/bool.pyi]
701+
702+
[case testOptionalAssignAny1]
703+
from typing import Optional
704+
def f():
705+
return 0
706+
707+
def g(x: Optional[int]) -> int:
708+
if x is None:
709+
reveal_type(x) # E: Revealed type is 'None'
710+
# As a special case for Unions containing None, during
711+
x = f()
712+
reveal_type(x) # E: Revealed type is 'Union[builtins.int, Any]'
713+
reveal_type(x) # E: Revealed type is 'Union[builtins.int, Any]'
714+
return x
715+
716+
[builtins fixtures/bool.pyi]
717+
718+
[case testOptionalAssignAny2]
719+
from typing import Optional
720+
def f():
721+
return 0
722+
723+
def g(x: Optional[int]) -> int:
724+
if x is None:
725+
reveal_type(x) # E: Revealed type is 'None'
726+
x = 1
727+
reveal_type(x) # E: Revealed type is 'builtins.int'
728+
# Since we've assigned to x, the special case None behavior shouldn't happen
729+
x = f()
730+
reveal_type(x) # E: Revealed type is 'Union[builtins.int, None]'
731+
reveal_type(x) # E: Revealed type is 'Union[builtins.int, None]'
732+
return x # E: Incompatible return value type (got "Optional[int]", expected "int")
733+
734+
[builtins fixtures/bool.pyi]
735+
736+
[case testOptionalAssignAny3]
737+
from typing import Optional
738+
def f():
739+
return 0
740+
741+
def g(x: Optional[int]) -> int:
742+
if x is not None:
743+
return x
744+
reveal_type(x) # E: Revealed type is 'None'
745+
x = f()
746+
reveal_type(x) # E: Revealed type is 'Union[builtins.int, Any]'
747+
return x
748+
749+
[builtins fixtures/bool.pyi]

0 commit comments

Comments
 (0)