Skip to content

Commit ad62a08

Browse files
ddfishergvanrossum
authored andcommitted
Don't turn collections containing None into partial types (#2289)
This means mypy will no longer give Need type annotation for variable errors for assignments with values of type Generator[str, None, None] or List[None] or similar. However, lists, sets, and dicts that are initialized with None values that were previously inferred may now need type annotations. I.e. constructs that look like: x = [None] x.append(0) will now be type errors unless they have explicit annotations. Supplants #2225. Fixes #2246. Fixes #2195. Fixes #2258.
1 parent f9e5003 commit ad62a08

File tree

2 files changed

+36
-6
lines changed

2 files changed

+36
-6
lines changed

mypy/checker.py

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2698,22 +2698,38 @@ 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[<uninhabited>].
2702+
2703+
When not doing strict Optional checking, all types containing None are
2704+
invalid. When doing strict Optional checking, only None and types that are
2705+
incompletely defined (i.e. contain UninhabitedType) are invalid.
27022706
"""
27032707
if is_same_type(typ, NoneTyp()):
27042708
# With strict Optional checking, we *may* eventually infer NoneTyp, but
27052709
# we only do that if we can't infer a specific Optional type. This
27062710
# resolution happens in leave_partial_types when we pop a partial types
27072711
# scope.
27082712
return False
2713+
return is_valid_inferred_type_component(typ)
2714+
2715+
2716+
def is_valid_inferred_type_component(typ: Type) -> bool:
2717+
"""Is this part of a type a valid inferred type?
2718+
2719+
In strict Optional mode this excludes bare None types, as otherwise every
2720+
type containing None would be invalid.
2721+
"""
2722+
if not experiments.STRICT_OPTIONAL:
2723+
if is_same_type(typ, NoneTyp()):
2724+
return False
27092725
if is_same_type(typ, UninhabitedType()):
27102726
return False
27112727
elif isinstance(typ, Instance):
27122728
for arg in typ.args:
2713-
if not is_valid_inferred_type(arg):
2729+
if not is_valid_inferred_type_component(arg):
27142730
return False
27152731
elif isinstance(typ, TupleType):
27162732
for item in typ.items:
2717-
if not is_valid_inferred_type(item):
2733+
if not is_valid_inferred_type_component(item):
27182734
return False
27192735
return True

test-data/unit/check-optional.test

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -167,8 +167,7 @@ reveal_type(x) # E: Revealed type is 'Union[builtins.int, builtins.None]'
167167

168168
[case testInferOptionalListType]
169169
x = [None]
170-
x.append(1)
171-
reveal_type(x) # E: Revealed type is 'builtins.list[Union[builtins.int, builtins.None]]'
170+
x.append(1) # E: Argument 1 to "append" of "list" has incompatible type "int"; expected None
172171
[builtins fixtures/list.pyi]
173172

174173
[case testInferNonOptionalListType]
@@ -180,8 +179,10 @@ x() # E: List[int] not callable
180179
[case testInferOptionalDictKeyValueTypes]
181180
x = {None: None}
182181
x["bar"] = 1
183-
reveal_type(x) # E: Revealed type is 'builtins.dict[Union[builtins.str, builtins.None], Union[builtins.int, builtins.None]]'
184182
[builtins fixtures/dict.pyi]
183+
[out]
184+
main:2: error: Invalid index type "str" for "dict"
185+
main:2: error: Incompatible types in assignment (expression has type "int", target has type None)
185186

186187
[case testInferNonOptionalDictType]
187188
x = {}
@@ -437,3 +438,16 @@ def g() -> Dict[None, None]:
437438
[case testRaiseFromNone]
438439
raise BaseException from None
439440
[builtins fixtures/exception.pyi]
441+
442+
[case testOptionalNonPartialTypeWithNone]
443+
from typing import Generator
444+
def f() -> Generator[str, None, None]: pass
445+
x = f()
446+
reveal_type(x) # E: Revealed type is 'typing.Generator[builtins.str, builtins.None, builtins.None]'
447+
l = [f()]
448+
reveal_type(l) # E: Revealed type is 'builtins.list[typing.Generator*[builtins.str, builtins.None, builtins.None]]'
449+
[builtins fixtures/list.pyi]
450+
451+
[case testNoneListTernary]
452+
x = [None] if "" else [1] # E: List item 0 has incompatible type "int"
453+
[builtins fixtures/list.pyi]

0 commit comments

Comments
 (0)