diff --git a/mypy/messages.py b/mypy/messages.py index 199b7c42b11b..8075789b348b 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -40,6 +40,7 @@ Expression, FuncDef, IndexExpr, + MemberExpr, MypyFile, NameExpr, ReturnStmt, @@ -48,6 +49,7 @@ SymbolTable, TypeInfo, Var, + get_member_expr_fullname, reverse_builtin_aliases, ) from mypy.operators import op_methods, op_methods_to_symbols @@ -529,6 +531,20 @@ def has_no_attr( context, code=codes.UNION_ATTR, ) + if typ_format == '"None"': + 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{var_name} is not None" to guard 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-inference.test b/test-data/unit/check-inference.test index 08b53ab16972..98b5663cfc34 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 C.a is not None" to guard 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 self.a is not None" to guard against a None value -- Special case for assignment to '_' -- ---------------------------------- diff --git a/test-data/unit/check-optional.test b/test-data/unit/check-optional.test index 70f3c4486e14..bb0b4ed2d3bf 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 x is not None" to guard 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 x is not None" to guard against a None value [case testIsinstanceAndOptionalAndAnyBase] from typing import Any, Optional diff --git a/test-data/unit/check-unions.test b/test-data/unit/check-unions.test index 2e69a96f0c78..53e495b7c769 100644 --- a/test-data/unit/check-unions.test +++ b/test-data/unit/check-unions.test @@ -79,6 +79,16 @@ if int(): [builtins fixtures/isinstance.pyi] +[case testUnionAttributeAccessWithNoneItem] +from typing import Union + +class A: + def foo(self) -> int: pass + +def f(x: Union[A, None]) -> int: + return x.foo() # E: Item "None" of "Optional[A]" has no attribute "foo" \ + # N: You can use "if x is not None" to guard against a None value + [case testUnionMethodCalls] from typing import Union @@ -935,7 +945,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 x is not None" to guard against a None value if x: for s, t in x: reveal_type(s) # N: Revealed type is "builtins.str" @@ -950,7 +961,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 x is not None" to guard against a None value if x: for s, t in x: reveal_type(s) # N: Revealed type is "builtins.str"