From 420fe81bf6335be733df79076ba3fec0714e427a Mon Sep 17 00:00:00 2001 From: Dexter Kennedy Date: Wed, 10 Apr 2024 22:23:37 -0400 Subject: [PATCH 1/2] new note on UNION_ATTR error --- mypy/messages.py | 5 +++++ test-data/unit/check-unions.test | 13 ++++++++++--- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/mypy/messages.py b/mypy/messages.py index 199b7c42b11b..916d013b933c 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -529,6 +529,11 @@ def has_no_attr( context, code=codes.UNION_ATTR, ) + self.note( + 'You can use "if is not None" to protect against a None value', + context, + code=codes.UNION_ATTR, + ) return codes.UNION_ATTR elif isinstance(original_type, TypeVarType): bound = get_proper_type(original_type.upper_bound) diff --git a/test-data/unit/check-unions.test b/test-data/unit/check-unions.test index d79ab14184c6..c0f667df3014 100644 --- a/test-data/unit/check-unions.test +++ b/test-data/unit/check-unions.test @@ -65,15 +65,19 @@ z: str if int(): y = w.y v.y # E: Item "C" of "Union[C, D]" has no attribute "y" \ + # N: You can use "if is not None" to protect against a None value \ # E: Item "D" of "Union[C, D]" has no attribute "y" u.y # E: Item "C" of "Union[A, C, D]" has no attribute "y" \ + # N: You can use "if is not None" to protect against a None value \ # E: Item "D" of "Union[A, C, D]" has no attribute "y" if int(): z = w.y # E: Incompatible types in assignment (expression has type "int", variable has type "str") w.y = 'a' # E: Incompatible types in assignment (expression has type "str", variable has type "int") if int(): - y = x.y # E: Item "C" of "Union[A, C]" has no attribute "y" -zz = x.y # E: Item "C" of "Union[A, C]" has no attribute "y" + y = x.y # E: Item "C" of "Union[A, C]" has no attribute "y" \ + # N: You can use "if is not None" to protect against a None value +zz = x.y # E: Item "C" of "Union[A, C]" has no attribute "y" \ + # N: You can use "if is not None" to protect against a None value if int(): z = zz # E: Incompatible types in assignment (expression has type "Union[int, Any]", variable has type "str") @@ -350,6 +354,7 @@ def foo(a: Union[A, B, C]): reveal_type(a) # N: Revealed type is "Union[Tuple[builtins.int, fallback=__main__.B], Tuple[builtins.int, fallback=__main__.C]]" a.x a.y # E: Item "B" of "Union[B, C]" has no attribute "y" \ + # N: You can use "if is not None" to protect against a None value \ # E: Item "C" of "Union[B, C]" has no attribute "y" b = a # type: Union[B, C] [builtins fixtures/isinstance.pyi] @@ -935,7 +940,8 @@ a: Any d: Dict[str, Tuple[List[Tuple[str, str]], str]] x, _ = d.get(a, (None, None)) -for y in x: pass # E: Item "None" of "Optional[List[Tuple[str, str]]]" has no attribute "__iter__" (not iterable) +for y in x: pass # E: Item "None" of "Optional[List[Tuple[str, str]]]" has no attribute "__iter__" (not iterable) \ + # N: You can use "if is not None" to protect against a None value if x: for s, t in x: reveal_type(s) # N: Revealed type is "builtins.str" @@ -951,6 +957,7 @@ d: Dict[str, Tuple[List[Tuple[str, str]], str]] x, _ = d.get(a, (None, None)) for y in x: pass # E: Item "None" of "Optional[List[Tuple[str, str]]]" has no attribute "__iter__" (not iterable) + # N: You can use "if is not None" to protect against a None value if x: for s, t in x: reveal_type(s) # N: Revealed type is "builtins.str" From 4937edd56007ea42e4ae8dbf742fee4b54483822 Mon Sep 17 00:00:00 2001 From: Dexter Kennedy Date: Thu, 11 Apr 2024 22:48:37 -0400 Subject: [PATCH 2/2] Improved test cases --- test-data/unit/check-classes.test | 3 ++- test-data/unit/check-errorcodes.test | 3 ++- test-data/unit/check-generics.test | 3 ++- test-data/unit/check-inference.test | 6 ++++-- test-data/unit/check-isinstance.test | 9 ++++++--- test-data/unit/check-narrowing.test | 3 ++- test-data/unit/check-optional.test | 6 ++++-- test-data/unit/check-overloading.test | 1 + test-data/unit/check-unions.test | 2 +- 9 files changed, 24 insertions(+), 12 deletions(-) diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index 983cb8454a05..c003681b6ce0 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -3482,7 +3482,8 @@ def process(cls: Type[Union[BasicUser, ProUser]]): obj = cls() cls.bar(obj) cls.mro() # Defined in class type - cls.error # E: Item "type" of "Union[Type[BasicUser], Type[ProUser]]" has no attribute "error" + cls.error # E: Item "type" of "Union[Type[BasicUser], Type[ProUser]]" has no attribute "error" \ + # N: You can use "if is not None" to protect against a None value [builtins fixtures/classmethod.pyi] [out] diff --git a/test-data/unit/check-errorcodes.test b/test-data/unit/check-errorcodes.test index 9d49480539e0..0d975f378c8a 100644 --- a/test-data/unit/check-errorcodes.test +++ b/test-data/unit/check-errorcodes.test @@ -344,7 +344,8 @@ class A: class B: y: str a: Union[A, B] -a.x # E: Item "B" of "Union[A, B]" has no attribute "x" [union-attr] +a.x # E: Item "B" of "Union[A, B]" has no attribute "x" [union-attr] \ + # N: You can use "if is not None" to protect against a None value [case testErrorCodeFunctionHasNoAnnotation] # flags: --disallow-untyped-defs diff --git a/test-data/unit/check-generics.test b/test-data/unit/check-generics.test index b1d1ff3f46a1..7389fa9e9a80 100644 --- a/test-data/unit/check-generics.test +++ b/test-data/unit/check-generics.test @@ -813,7 +813,8 @@ if not isinstance(s, str): z = None # type: TNode # Same as TNode[Any] z.x -z.foo() # E: Item "Node[int]" of "Union[Any, Node[int]]" has no attribute "foo" +z.foo() # E: Item "Node[int]" of "Union[Any, Node[int]]" has no attribute "foo" \ + # N: You can use "if is not None" to protect against a None value [builtins fixtures/isinstance.pyi] diff --git a/test-data/unit/check-inference.test b/test-data/unit/check-inference.test index 08b53ab16972..8c5e68b6d633 100644 --- a/test-data/unit/check-inference.test +++ b/test-data/unit/check-inference.test @@ -2828,7 +2828,8 @@ class C: 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" + C.a.y # E: Item "None" of "Optional[Any]" has no attribute "y" \ + # N: You can use "if is not None" to protect against a None value [case testLocalPartialTypesAccessPartialNoneAttribute2] # flags: --local-partial-types @@ -2836,7 +2837,8 @@ class C: 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" + self.a.y # E: Item "None" of "Optional[Any]" has no attribute "y" \ + # N: You can use "if is not None" to protect against a None value -- Special case for assignment to '_' -- ---------------------------------- diff --git a/test-data/unit/check-isinstance.test b/test-data/unit/check-isinstance.test index b7ee38b69d00..8301bac19e67 100644 --- a/test-data/unit/check-isinstance.test +++ b/test-data/unit/check-isinstance.test @@ -578,7 +578,8 @@ v = A() # type: Union[A, B, C] if isinstance(v, (B, C)): v.method2(123) - v.method3('xyz') # E: Item "B" of "Union[B, C]" has no attribute "method3" + v.method3('xyz') # E: Item "B" of "Union[B, C]" has no attribute "method3" \ + # N: You can use "if is not None" to protect against a None value [builtins fixtures/isinstance.pyi] [case testIsinstanceNeverWidens] @@ -1007,7 +1008,8 @@ def bar() -> None: if isinstance(x, int): x + 1 else: - x.a # E: Item "str" of "Union[str, A]" has no attribute "a" + x.a # E: Item "str" of "Union[str, A]" has no attribute "a" \ + # N: You can use "if is not None" to protect against a None value x = 'a' [builtins fixtures/isinstancelist.pyi] @@ -2104,7 +2106,8 @@ reveal_type(x) # N: Revealed type is "Any" from typing import Union from foo import A # type: ignore def f(x: Union[A, str]) -> None: - x.method_only_in_a() # E: Item "str" of "Union[Any, str]" has no attribute "method_only_in_a" + x.method_only_in_a() # E: Item "str" of "Union[Any, str]" has no attribute "method_only_in_a" \ + # N: You can use "if is not None" to protect against a None value if isinstance(x, A): x.method_only_in_a() [builtins fixtures/isinstance.pyi] diff --git a/test-data/unit/check-narrowing.test b/test-data/unit/check-narrowing.test index 4d117687554e..647539bd3b88 100644 --- a/test-data/unit/check-narrowing.test +++ b/test-data/unit/check-narrowing.test @@ -420,7 +420,8 @@ else: reveal_type(ok_mixture) # N: Revealed type is "Tuple[Literal[__main__.Key.C], fallback=__main__.KeyedNamedTuple]" impossible_mixture: Union[KeyedObject, KeyedTypedDict] -if impossible_mixture.key is Key.A: # E: Item "KeyedTypedDict" of "Union[KeyedObject, KeyedTypedDict]" has no attribute "key" +if impossible_mixture.key is Key.A: # E: Item "KeyedTypedDict" of "Union[KeyedObject, KeyedTypedDict]" has no attribute "key" \ + # N: You can use "if is not None" to protect against a None value reveal_type(impossible_mixture) # N: Revealed type is "Union[__main__.KeyedObject, TypedDict('__main__.KeyedTypedDict', {'key': Literal[__main__.Key.B]})]" else: reveal_type(impossible_mixture) # N: Revealed type is "Union[__main__.KeyedObject, TypedDict('__main__.KeyedTypedDict', {'key': Literal[__main__.Key.B]})]" diff --git a/test-data/unit/check-optional.test b/test-data/unit/check-optional.test index 70f3c4486e14..71b58ac48b86 100644 --- a/test-data/unit/check-optional.test +++ b/test-data/unit/check-optional.test @@ -564,7 +564,8 @@ if int(): if int(): x = f('x') # E: Argument 1 to "f" has incompatible type "str"; expected "int" -x.x = 1 # E: Item "None" of "Optional[Node[int]]" has no attribute "x" +x.x = 1 # E: Item "None" of "Optional[Node[int]]" has no attribute "x" \ + # N: You can use "if is not None" to protect against a None value if x is not None: x.x = 1 # OK here @@ -617,7 +618,8 @@ A = None # type: Any class C(A): pass x = None # type: Optional[C] -x.foo() # E: Item "None" of "Optional[C]" has no attribute "foo" +x.foo() # E: Item "None" of "Optional[C]" has no attribute "foo" \ + # N: You can use "if is not None" to protect against a None value [case testIsinstanceAndOptionalAndAnyBase] from typing import Any, Optional diff --git a/test-data/unit/check-overloading.test b/test-data/unit/check-overloading.test index 7bca5cc7b508..a1f486526d8d 100644 --- a/test-data/unit/check-overloading.test +++ b/test-data/unit/check-overloading.test @@ -5145,6 +5145,7 @@ def foo(iterable: Iterable[_T]) -> None: ... def foo(iterable = None) -> None: pass foo(bar('lol').foo()) # E: Item "int" of "Union[A, int]" has no attribute "foo" \ + # N: You can use "if is not None" to protect against a None value \ # E: Argument 1 to "bar" has incompatible type "str"; expected "int" diff --git a/test-data/unit/check-unions.test b/test-data/unit/check-unions.test index c0f667df3014..5b04122f60b4 100644 --- a/test-data/unit/check-unions.test +++ b/test-data/unit/check-unions.test @@ -956,7 +956,7 @@ x = None d: Dict[str, Tuple[List[Tuple[str, str]], str]] x, _ = d.get(a, (None, None)) -for y in x: pass # E: Item "None" of "Optional[List[Tuple[str, str]]]" has no attribute "__iter__" (not iterable) +for y in x: pass # E: Item "None" of "Optional[List[Tuple[str, str]]]" has no attribute "__iter__" (not iterable) \ # N: You can use "if is not None" to protect against a None value if x: for s, t in x: