Skip to content

Commit 8643103

Browse files
committed
Allow assignment of types with Nones without explicit annotation
Currently, mypy will not infer types containing None unless they are valid PartialTypes (i.e. are bare None, list, set, or dict). This means that all assignments of such types must be explicitly annotated. It doesn't make sense to require this with strict Optional, as None is a perfectly valid type. This PR makes types containing None valid inference targets. Fixes #2195.
1 parent ea23ac0 commit 8643103

File tree

2 files changed

+36
-5
lines changed

2 files changed

+36
-5
lines changed

mypy/checker.py

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2698,22 +2698,46 @@ def infer_operator_assignment_method(type: Type, operator: str) -> Tuple[bool, s
26982698
def is_valid_inferred_type(typ: Type) -> bool:
26992699
"""Is an inferred type valid?
27002700
2701-
Examples of invalid types include the None type or a type with a None component.
2701+
Examples of invalid types include the None type or List[None].
2702+
2703+
When not doing strict Optional checking, all types containing None are
2704+
invalid. When doing strict Optional checking, only types that are
2705+
incompletely defined (i.e. contain UninhabitedType) or can be turned into
2706+
PartialTypes are invalid.
27022707
"""
27032708
if is_same_type(typ, NoneTyp()):
27042709
# With strict Optional checking, we *may* eventually infer NoneTyp, but
27052710
# we only do that if we can't infer a specific Optional type. This
27062711
# resolution happens in leave_partial_types when we pop a partial types
27072712
# scope.
27082713
return False
2714+
return is_valid_inferred_type_component(typ)
2715+
2716+
2717+
def is_valid_inferred_type_component(typ: Type) -> bool:
2718+
"""Is this part of a type a valid inferred type?
2719+
2720+
In strict Optional mode this excludes bare None types, as otherwise every
2721+
type containing None would be invalid.
2722+
"""
2723+
if not experiments.STRICT_OPTIONAL:
2724+
if is_same_type(typ, NoneTyp()):
2725+
return False
27092726
if is_same_type(typ, UninhabitedType()):
27102727
return False
27112728
elif isinstance(typ, Instance):
2712-
for arg in typ.args:
2713-
if not is_valid_inferred_type(arg):
2714-
return False
2729+
fullname = typ.type.fullname()
2730+
if ((fullname == 'builtins.list' or
2731+
fullname == 'builtins.set' or
2732+
fullname == 'builtins.dict') and
2733+
all(isinstance(t, (NoneTyp, UninhabitedType)) for t in typ.args)):
2734+
return False
2735+
else:
2736+
for arg in typ.args:
2737+
if not is_valid_inferred_type_component(arg):
2738+
return False
27152739
elif isinstance(typ, TupleType):
27162740
for item in typ.items:
2717-
if not is_valid_inferred_type(item):
2741+
if not is_valid_inferred_type_component(item):
27182742
return False
27192743
return True

test-data/unit/check-optional.test

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -428,3 +428,10 @@ x + 1
428428
[out]
429429
main:2: note: In module imported here:
430430
tmp/a.py:3: error: Unsupported left operand type for + (some union)
431+
432+
[case testOptionalNonPartialTypeWithNone]
433+
from typing import Generator
434+
def f() -> Generator[str, None, None]: pass
435+
x = f()
436+
l = [f()]
437+
[builtins fixtures/list.pyi]

0 commit comments

Comments
 (0)