From 3d1323e718aaa72bb167548c21e8094d43db4506 Mon Sep 17 00:00:00 2001 From: Pranav Rajpal <78008260+pranavrajpal@users.noreply.github.com> Date: Thu, 19 May 2022 18:16:32 -0700 Subject: [PATCH 1/4] Improve error message for partial Nones 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[]. --- mypy/messages.py | 24 ++++++++++++++++-------- test-data/unit/check-inference.test | 12 ++++++------ 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/mypy/messages.py b/mypy/messages.py index 70d79384c1a9..014818d673c5 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -1193,18 +1193,26 @@ def need_annotation_for_var(self, node: SymbolNode, context: Context, python_version: Optional[Tuple[int, int]] = None) -> None: hint = '' has_variable_annotations = not python_version or python_version >= (3, 6) + # type to recommend the user adds + recommended_type = None # Only gives hint if it's a variable declaration and the partial type is a builtin type - if (python_version and isinstance(node, Var) and isinstance(node.type, PartialType) and - node.type.type and node.type.type.fullname in reverse_builtin_aliases): - alias = reverse_builtin_aliases[node.type.type.fullname] - alias = alias.split('.')[-1] + if python_version and isinstance(node, Var) and isinstance(node.type, PartialType): type_dec = '' - if alias == 'Dict': - type_dec = f'{type_dec}, {type_dec}' + if not node.type.type: + # partial None + recommended_type = f'Optional[{type_dec}]' + elif node.type.type.fullname in reverse_builtin_aliases: + # partial types other than partial None + alias = reverse_builtin_aliases[node.type.type.fullname] + alias = alias.split('.')[-1] + if alias == 'Dict': + type_dec = f'{type_dec}, {type_dec}' + recommended_type = f'{alias}[{type_dec}]' + if recommended_type is not None: if has_variable_annotations: - hint = f' (hint: "{node.name}: {alias}[{type_dec}] = ...")' + hint = f' (hint: "{node.name}: {recommended_type} = ...")' else: - hint = f' (hint: "{node.name} = ... # type: {alias}[{type_dec}]")' + hint = f' (hint: "{node.name} = ... # type: {recommended_type}")' if has_variable_annotations: needed = 'annotation' diff --git a/test-data/unit/check-inference.test b/test-data/unit/check-inference.test index 21c96bf2df45..0c2fe386023c 100644 --- a/test-data/unit/check-inference.test +++ b/test-data/unit/check-inference.test @@ -2394,7 +2394,7 @@ if bool(): [case testLocalPartialTypesWithGlobalInitializedToNone] # flags: --local-partial-types -x = None # E: Need type annotation for "x" +x = None # E: Need type annotation for "x" (hint: "x: Optional[] = ...") def f() -> None: global x @@ -2405,7 +2405,7 @@ reveal_type(x) # N: Revealed type is "None" [case testLocalPartialTypesWithGlobalInitializedToNone2] # flags: --local-partial-types -x = None # E: Need type annotation for "x" +x = None # E: Need type annotation for "x" (hint: "x: Optional[] = ...") def f(): global x @@ -2454,7 +2454,7 @@ reveal_type(a) # N: Revealed type is "builtins.str" [case testLocalPartialTypesWithClassAttributeInitializedToNone] # flags: --local-partial-types class A: - x = None # E: Need type annotation for "x" + x = None # E: Need type annotation for "x" (hint: "x: Optional[] = ...") def f(self) -> None: self.x = 1 @@ -2637,7 +2637,7 @@ from typing import List def f(x): pass class A: - x = None # E: Need type annotation for "x" + x = None # E: Need type annotation for "x" (hint: "x: Optional[] = ...") def f(self, p: List[str]) -> None: self.x = f(p) @@ -2647,7 +2647,7 @@ class A: [case testLocalPartialTypesAccessPartialNoneAttribute] # flags: --local-partial-types class C: - a = None # E: Need type annotation for "a" + a = None # E: Need type annotation for "a" (hint: "a: Optional[] = ...") def f(self, x) -> None: C.a.y # E: Item "None" of "Optional[Any]" has no attribute "y" @@ -2655,7 +2655,7 @@ class C: [case testLocalPartialTypesAccessPartialNoneAttribute2] # flags: --local-partial-types class C: - a = None # E: Need type annotation for "a" + a = None # E: Need type annotation for "a" (hint: "a: Optional[] = ...") def f(self, x) -> None: self.a.y # E: Item "None" of "Optional[Any]" has no attribute "y" From 6c91b91ac18cc54cb5f4292fff054bbeca5dbb92 Mon Sep 17 00:00:00 2001 From: Pranav Rajpal <78008260+pranavrajpal@users.noreply.github.com> Date: Thu, 19 May 2022 21:23:18 -0700 Subject: [PATCH 2/4] Fix fine grained tests Fix error messages in fine grained tests --- test-data/unit/fine-grained.test | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test-data/unit/fine-grained.test b/test-data/unit/fine-grained.test index fa6dc52262dd..c6d677fb4344 100644 --- a/test-data/unit/fine-grained.test +++ b/test-data/unit/fine-grained.test @@ -4165,9 +4165,9 @@ y = 0 [file a.py.2] y = '' [out] -main:4: error: Need type annotation for "x" +main:4: error: Need type annotation for "x" (hint: "x: Optional[] = ...") == -main:4: error: Need type annotation for "x" +main:4: error: Need type annotation for "x" (hint: "x: Optional[] = ...") [case testNonePartialType2] import a @@ -4183,9 +4183,9 @@ y = 0 [file a.py.2] y = '' [out] -main:4: error: Need type annotation for "x" +main:4: error: Need type annotation for "x" (hint: "x: Optional[] = ...") == -main:4: error: Need type annotation for "x" +main:4: error: Need type annotation for "x" (hint: "x: Optional[] = ...") [case testNonePartialType3] import a @@ -4197,7 +4197,7 @@ def f() -> None: y = '' [out] == -a.py:1: error: Need type annotation for "y" +a.py:1: error: Need type annotation for "y" (hint: "y: Optional[] = ...") [case testNonePartialType4] import a @@ -4213,7 +4213,7 @@ def f() -> None: global y y = '' [out] -a.py:1: error: Need type annotation for "y" +a.py:1: error: Need type annotation for "y" (hint: "y: Optional[] = ...") == [case testSkippedClass1] From c46956733460f0fbc8452860827ecfb807a1b328 Mon Sep 17 00:00:00 2001 From: Pranav Rajpal <78008260+pranavrajpal@users.noreply.github.com> Date: Fri, 20 May 2022 16:58:39 -0700 Subject: [PATCH 3/4] Suggest PEP 604 annotation when on Python 3.10 Code running on Python 3.10 can use PEP 604 annotations, so suggest those instead. --- mypy/messages.py | 6 +++++- test-data/unit/check-inference.test | 4 ++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/mypy/messages.py b/mypy/messages.py index 014818d673c5..17a931915247 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -1193,6 +1193,7 @@ def need_annotation_for_var(self, node: SymbolNode, context: Context, python_version: Optional[Tuple[int, int]] = None) -> None: hint = '' has_variable_annotations = not python_version or python_version >= (3, 6) + pep604_supported = python_version and python_version >= (3, 10) # type to recommend the user adds recommended_type = None # Only gives hint if it's a variable declaration and the partial type is a builtin type @@ -1200,7 +1201,10 @@ def need_annotation_for_var(self, node: SymbolNode, context: Context, type_dec = '' if not node.type.type: # partial None - recommended_type = f'Optional[{type_dec}]' + if pep604_supported: + recommended_type = f'{type_dec} | None' + else: + recommended_type = f'Optional[{type_dec}]' elif node.type.type.fullname in reverse_builtin_aliases: # partial types other than partial None alias = reverse_builtin_aliases[node.type.type.fullname] diff --git a/test-data/unit/check-inference.test b/test-data/unit/check-inference.test index 0c2fe386023c..f2b3d39510fc 100644 --- a/test-data/unit/check-inference.test +++ b/test-data/unit/check-inference.test @@ -3255,3 +3255,7 @@ reveal_type(x) # N: Revealed type is "builtins.bytes" if x: reveal_type(x) # N: Revealed type is "builtins.bytes" [builtins fixtures/dict.pyi] + +[case testSuggestPep604AnnotationForPartialNone] +# flags: --local-partial-types --python-version 3.10 +x = None # E: Need type annotation for "x" (hint: "x: | None = ...") From 0dff5105a11f32aca641e059c9130b39db577db3 Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Fri, 2 Sep 2022 12:45:14 -0700 Subject: [PATCH 4/4] blacken, fix nit --- mypy/messages.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/mypy/messages.py b/mypy/messages.py index 18278e164ef7..15ea5c3ffb56 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -1526,25 +1526,25 @@ def need_annotation_for_var( ) -> None: hint = "" has_variable_annotations = not python_version or python_version >= (3, 6) - pep604_supported = python_version and python_version >= (3, 10) + pep604_supported = not python_version or python_version >= (3, 10) # type to recommend the user adds recommended_type = None # Only gives hint if it's a variable declaration and the partial type is a builtin type if python_version and isinstance(node, Var) and isinstance(node.type, PartialType): - type_dec = '' + type_dec = "" if not node.type.type: # partial None if pep604_supported: - recommended_type = f'{type_dec} | None' + recommended_type = f"{type_dec} | None" else: - recommended_type = f'Optional[{type_dec}]' + recommended_type = f"Optional[{type_dec}]" elif node.type.type.fullname in reverse_builtin_aliases: # partial types other than partial None alias = reverse_builtin_aliases[node.type.type.fullname] - alias = alias.split('.')[-1] - if alias == 'Dict': - type_dec = f'{type_dec}, {type_dec}' - recommended_type = f'{alias}[{type_dec}]' + alias = alias.split(".")[-1] + if alias == "Dict": + type_dec = f"{type_dec}, {type_dec}" + recommended_type = f"{alias}[{type_dec}]" if recommended_type is not None: if has_variable_annotations: hint = f' (hint: "{node.name}: {recommended_type} = ...")'