From cc73b66884baf5c605bdbf4fe851f78e7a34e7cd Mon Sep 17 00:00:00 2001 From: prabhat455 Date: Wed, 24 Jul 2024 12:19:23 +0545 Subject: [PATCH 1/3] feat: add note for Union type missing attribute --- mypy/messages.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/mypy/messages.py b/mypy/messages.py index 62846c536f3d..0fe6d3c848f2 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -42,12 +42,14 @@ IndexExpr, MypyFile, NameExpr, + MemberExpr, ReturnStmt, StrExpr, SymbolNode, SymbolTable, TypeInfo, Var, + get_member_expr_fullname, reverse_builtin_aliases, ) from mypy.operators import op_methods, op_methods_to_symbols @@ -534,6 +536,20 @@ def has_no_attr( context, code=codes.UNION_ATTR, ) + if isinstance(context, NameExpr): + var_name = f" {context.name}" + elif isinstance(context, MemberExpr) and isinstance(context.expr, NameExpr): + var_name = f" {context.expr.name}" + elif isinstance(context, MemberExpr) and isinstance(context.expr, MemberExpr): + var_name = f" {get_member_expr_fullname(context.expr)}" + else: + var_name = " " + self.note( + f"You can use 'if hasattr({var_name}, '{member}'):' to guard against missing attribute error", + context, + code=codes.UNION_ATTR, + ) + return codes.UNION_ATTR elif isinstance(original_type, TypeVarType): bound = get_proper_type(original_type.upper_bound) From 3f5a85fdd85bcfc55e6d9082d2238ad5994c47fd Mon Sep 17 00:00:00 2001 From: prabhat455 Date: Wed, 24 Jul 2024 12:20:44 +0545 Subject: [PATCH 2/3] test: update test data with note for missing attributes --- 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 | 10 +++++++--- test-data/unit/check-narrowing.test | 3 ++- test-data/unit/check-optional.test | 7 ++++--- test-data/unit/check-overloading.test | 1 + test-data/unit/check-unions.test | 15 +++++++++++---- 9 files changed, 35 insertions(+), 16 deletions(-) diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index 82208d27df41..256ead810743 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -3515,7 +3515,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 hasattr( cls, 'error'):' to guard against missing attribute error [builtins fixtures/classmethod.pyi] [out] diff --git a/test-data/unit/check-errorcodes.test b/test-data/unit/check-errorcodes.test index c4d72388fba9..db2002764d19 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 hasattr( a, 'x'):' to guard against missing attribute error [case testErrorCodeFunctionHasNoAnnotation] # flags: --disallow-untyped-defs diff --git a/test-data/unit/check-generics.test b/test-data/unit/check-generics.test index b8cc0422b749..175476cd2f53 100644 --- a/test-data/unit/check-generics.test +++ b/test-data/unit/check-generics.test @@ -817,7 +817,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 hasattr( z, 'foo'):' to guard against missing attribute error [builtins fixtures/isinstance.pyi] diff --git a/test-data/unit/check-inference.test b/test-data/unit/check-inference.test index 0dbefbc774a3..ae5c06073dc2 100644 --- a/test-data/unit/check-inference.test +++ b/test-data/unit/check-inference.test @@ -2832,7 +2832,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 hasattr( C.a, 'y'):' to guard against missing attribute error [case testLocalPartialTypesAccessPartialNoneAttribute2] # flags: --local-partial-types @@ -2840,7 +2841,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 hasattr( self.a, 'y'):' to guard against missing attribute error -- Special case for assignment to '_' -- ---------------------------------- diff --git a/test-data/unit/check-isinstance.test b/test-data/unit/check-isinstance.test index b7ee38b69d00..e74d0616e2a5 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 hasattr( v, 'method3'):' to guard against missing attribute error [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 hasattr( x, 'a'):' to guard against missing attribute error x = 'a' [builtins fixtures/isinstancelist.pyi] @@ -2104,7 +2106,9 @@ 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 hasattr( x, 'method_only_in_a'):' to guard against missing attribute \ + error 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 8612df9bc663..ad0383d46199 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 hasattr( impossible_mixture, 'key'):' to guard against missing attribute error 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 f80aa5115bc3..62eb531df187 100644 --- a/test-data/unit/check-optional.test +++ b/test-data/unit/check-optional.test @@ -563,8 +563,8 @@ if int(): x = f(1) 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: # N: You can use 'if hasattr( x, 'x'):' to guard against missing attribute error if x is not None: x.x = 1 # OK here @@ -617,7 +617,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 hasattr( x, 'foo'):' to guard against missing attribute error [case testIsinstanceAndOptionalAndAnyBase] from typing import Any, Optional diff --git a/test-data/unit/check-overloading.test b/test-data/unit/check-overloading.test index 48d5996b226f..7ff8f9241d40 100644 --- a/test-data/unit/check-overloading.test +++ b/test-data/unit/check-overloading.test @@ -5167,6 +5167,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 hasattr( , 'foo'):' to guard against missing attribute error \ # 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 329896f7a1a7..7f724fd844cf 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 hasattr( v, 'y'):' to guard against missing attribute error \ # 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 hasattr( u, 'y'):' to guard against missing attribute error \ # 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 hasattr( x, 'y'):' to guard against missing attribute error +zz = x.y # E: Item "C" of "Union[A, C]" has no attribute "y" \ + # N: You can use 'if hasattr( x, 'y'):' to guard against missing attribute error 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 hasattr( a, 'y'):' to guard against missing attribute error \ # 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 hasattr( x, '__iter__'):' to guard against missing attribute error if x: for s, t in x: reveal_type(s) # N: Revealed type is "builtins.str" @@ -950,7 +956,8 @@ 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 hasattr( x, '__iter__'):' to guard against missing attribute error if x: for s, t in x: reveal_type(s) # N: Revealed type is "builtins.str" From b4b909488b05213a366d4c3bcddf7fb64ded5c34 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 24 Jul 2024 07:58:30 +0000 Subject: [PATCH 3/3] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mypy/messages.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypy/messages.py b/mypy/messages.py index 0fe6d3c848f2..ffa5c4325cbd 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -40,9 +40,9 @@ Expression, FuncDef, IndexExpr, + MemberExpr, MypyFile, NameExpr, - MemberExpr, ReturnStmt, StrExpr, SymbolNode,