Skip to content

Commit cf7495f

Browse files
Improve error message for partial None with --local-partial-types (#12822)
When --local-partial-types is set and we can't infer a complete type for a type that we initially inferred as partial None, show an error message that suggests to add a type annotation of the form Optional[<type>]. Co-authored-by: hauntsaninja <[email protected]>
1 parent 38eb6e8 commit cf7495f

File tree

3 files changed

+36
-25
lines changed

3 files changed

+36
-25
lines changed

mypy/messages.py

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1526,23 +1526,30 @@ def need_annotation_for_var(
15261526
) -> None:
15271527
hint = ""
15281528
has_variable_annotations = not python_version or python_version >= (3, 6)
1529+
pep604_supported = not python_version or python_version >= (3, 10)
1530+
# type to recommend the user adds
1531+
recommended_type = None
15291532
# Only gives hint if it's a variable declaration and the partial type is a builtin type
1530-
if (
1531-
python_version
1532-
and isinstance(node, Var)
1533-
and isinstance(node.type, PartialType)
1534-
and node.type.type
1535-
and node.type.type.fullname in reverse_builtin_aliases
1536-
):
1537-
alias = reverse_builtin_aliases[node.type.type.fullname]
1538-
alias = alias.split(".")[-1]
1533+
if python_version and isinstance(node, Var) and isinstance(node.type, PartialType):
15391534
type_dec = "<type>"
1540-
if alias == "Dict":
1541-
type_dec = f"{type_dec}, {type_dec}"
1535+
if not node.type.type:
1536+
# partial None
1537+
if pep604_supported:
1538+
recommended_type = f"{type_dec} | None"
1539+
else:
1540+
recommended_type = f"Optional[{type_dec}]"
1541+
elif node.type.type.fullname in reverse_builtin_aliases:
1542+
# partial types other than partial None
1543+
alias = reverse_builtin_aliases[node.type.type.fullname]
1544+
alias = alias.split(".")[-1]
1545+
if alias == "Dict":
1546+
type_dec = f"{type_dec}, {type_dec}"
1547+
recommended_type = f"{alias}[{type_dec}]"
1548+
if recommended_type is not None:
15421549
if has_variable_annotations:
1543-
hint = f' (hint: "{node.name}: {alias}[{type_dec}] = ...")'
1550+
hint = f' (hint: "{node.name}: {recommended_type} = ...")'
15441551
else:
1545-
hint = f' (hint: "{node.name} = ... # type: {alias}[{type_dec}]")'
1552+
hint = f' (hint: "{node.name} = ... # type: {recommended_type}")'
15461553

15471554
if has_variable_annotations:
15481555
needed = "annotation"

test-data/unit/check-inference.test

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2393,7 +2393,7 @@ if bool():
23932393

23942394
[case testLocalPartialTypesWithGlobalInitializedToNone]
23952395
# flags: --local-partial-types
2396-
x = None # E: Need type annotation for "x"
2396+
x = None # E: Need type annotation for "x" (hint: "x: Optional[<type>] = ...")
23972397

23982398
def f() -> None:
23992399
global x
@@ -2404,7 +2404,7 @@ reveal_type(x) # N: Revealed type is "None"
24042404

24052405
[case testLocalPartialTypesWithGlobalInitializedToNone2]
24062406
# flags: --local-partial-types
2407-
x = None # E: Need type annotation for "x"
2407+
x = None # E: Need type annotation for "x" (hint: "x: Optional[<type>] = ...")
24082408

24092409
def f():
24102410
global x
@@ -2453,7 +2453,7 @@ reveal_type(a) # N: Revealed type is "builtins.str"
24532453
[case testLocalPartialTypesWithClassAttributeInitializedToNone]
24542454
# flags: --local-partial-types
24552455
class A:
2456-
x = None # E: Need type annotation for "x"
2456+
x = None # E: Need type annotation for "x" (hint: "x: Optional[<type>] = ...")
24572457

24582458
def f(self) -> None:
24592459
self.x = 1
@@ -2636,7 +2636,7 @@ from typing import List
26362636
def f(x): pass
26372637

26382638
class A:
2639-
x = None # E: Need type annotation for "x"
2639+
x = None # E: Need type annotation for "x" (hint: "x: Optional[<type>] = ...")
26402640

26412641
def f(self, p: List[str]) -> None:
26422642
self.x = f(p)
@@ -2646,15 +2646,15 @@ class A:
26462646
[case testLocalPartialTypesAccessPartialNoneAttribute]
26472647
# flags: --local-partial-types
26482648
class C:
2649-
a = None # E: Need type annotation for "a"
2649+
a = None # E: Need type annotation for "a" (hint: "a: Optional[<type>] = ...")
26502650

26512651
def f(self, x) -> None:
26522652
C.a.y # E: Item "None" of "Optional[Any]" has no attribute "y"
26532653

26542654
[case testLocalPartialTypesAccessPartialNoneAttribute2]
26552655
# flags: --local-partial-types
26562656
class C:
2657-
a = None # E: Need type annotation for "a"
2657+
a = None # E: Need type annotation for "a" (hint: "a: Optional[<type>] = ...")
26582658

26592659
def f(self, x) -> None:
26602660
self.a.y # E: Item "None" of "Optional[Any]" has no attribute "y"
@@ -3248,6 +3248,10 @@ if x:
32483248
reveal_type(x) # N: Revealed type is "builtins.bytes"
32493249
[builtins fixtures/dict.pyi]
32503250

3251+
[case testSuggestPep604AnnotationForPartialNone]
3252+
# flags: --local-partial-types --python-version 3.10
3253+
x = None # E: Need type annotation for "x" (hint: "x: <type> | None = ...")
3254+
32513255
[case testTupleContextFromIterable]
32523256
from typing import TypeVar, Iterable, List, Union
32533257

test-data/unit/fine-grained.test

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4305,9 +4305,9 @@ y = 0
43054305
[file a.py.2]
43064306
y = ''
43074307
[out]
4308-
main:4: error: Need type annotation for "x"
4308+
main:4: error: Need type annotation for "x" (hint: "x: Optional[<type>] = ...")
43094309
==
4310-
main:4: error: Need type annotation for "x"
4310+
main:4: error: Need type annotation for "x" (hint: "x: Optional[<type>] = ...")
43114311

43124312
[case testNonePartialType2]
43134313
import a
@@ -4323,9 +4323,9 @@ y = 0
43234323
[file a.py.2]
43244324
y = ''
43254325
[out]
4326-
main:4: error: Need type annotation for "x"
4326+
main:4: error: Need type annotation for "x" (hint: "x: Optional[<type>] = ...")
43274327
==
4328-
main:4: error: Need type annotation for "x"
4328+
main:4: error: Need type annotation for "x" (hint: "x: Optional[<type>] = ...")
43294329

43304330
[case testNonePartialType3]
43314331
import a
@@ -4337,7 +4337,7 @@ def f() -> None:
43374337
y = ''
43384338
[out]
43394339
==
4340-
a.py:1: error: Need type annotation for "y"
4340+
a.py:1: error: Need type annotation for "y" (hint: "y: Optional[<type>] = ...")
43414341

43424342
[case testNonePartialType4]
43434343
import a
@@ -4353,7 +4353,7 @@ def f() -> None:
43534353
global y
43544354
y = ''
43554355
[out]
4356-
a.py:1: error: Need type annotation for "y"
4356+
a.py:1: error: Need type annotation for "y" (hint: "y: Optional[<type>] = ...")
43574357
==
43584358

43594359
[case testSkippedClass1]

0 commit comments

Comments
 (0)