Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions mypy/messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
Expression,
FuncDef,
IndexExpr,
MemberExpr,
MypyFile,
NameExpr,
ReturnStmt,
Expand All @@ -56,6 +57,7 @@
TypeInfo,
Var,
get_func_def,
get_member_expr_fullname,
reverse_builtin_aliases,
)
from mypy.operators import op_methods, op_methods_to_symbols
Expand Down Expand Up @@ -512,6 +514,21 @@ def has_no_attr(
context,
code=codes.UNION_ATTR,
)
if typ_format == '"None"':
var_name: str | None = None
if isinstance(context, MemberExpr) and isinstance(context.expr, NameExpr):
var_name = context.expr.name
elif isinstance(context, MemberExpr) and isinstance(context.expr, MemberExpr):
var_name = get_member_expr_fullname(context.expr)
elif isinstance(context, NameExpr):
var_name = context.name
if var_name is not None:
self.note(
'You can use "if {} is not None" to guard'
" against a None value".format(var_name),
context,
code=codes.UNION_ATTR,
)
return codes.UNION_ATTR
elif isinstance(original_type, TypeVarType):
bound = get_proper_type(original_type.upper_bound)
Expand Down
6 changes: 4 additions & 2 deletions test-data/unit/check-inference.test
Original file line number Diff line number Diff line change
Expand Up @@ -3001,15 +3001,17 @@ class C:
a = None # E: Need type annotation for "a" (hint: "a: <type> | None = ...")

def f(self, x) -> None:
C.a.y # E: Item "None" of "Any | None" has no attribute "y"
C.a.y # E: Item "None" of "Any | None" 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
class C:
a = None # E: Need type annotation for "a" (hint: "a: <type> | None = ...")

def f(self, x) -> None:
self.a.y # E: Item "None" of "Any | None" has no attribute "y"
self.a.y # E: Item "None" of "Any | None" 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 '_'
-- ----------------------------------
Expand Down
6 changes: 4 additions & 2 deletions test-data/unit/check-optional.test
Original file line number Diff line number Diff line change
Expand Up @@ -580,7 +580,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 "Node[int] | None" has no attribute "x"
x.x = 1 # E: Item "None" of "Node[int] | None" 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

Expand Down Expand Up @@ -633,7 +634,8 @@ A = None # type: Any
class C(A):
pass
x = None # type: Optional[C]
x.foo() # E: Item "None" of "C | None" has no attribute "foo"
x.foo() # E: Item "None" of "C | None" 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
Expand Down
22 changes: 18 additions & 4 deletions test-data/unit/check-unions.test
Original file line number Diff line number Diff line change
Expand Up @@ -921,12 +921,15 @@ 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 "list[tuple[str, str]] | None" has no attribute "__iter__" (not iterable)
for y in x: pass
if x:
for s, t in x:
reveal_type(s) # N: Revealed type is "builtins.str"
reveal_type(s)
[builtins fixtures/dict.pyi]
[out]
main:7: error: Item "None" of "list[tuple[str, str]] | None" has no attribute "__iter__" (not iterable)
main:7: note: You can use "if x is not None" to guard against a None value
main:10: note: Revealed type is "builtins.str"

[case testUnpackUnionNoCrashOnPartialNone2]
from typing import Dict, Tuple, List, Any
Expand All @@ -936,12 +939,23 @@ 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 "list[tuple[str, str]] | None" has no attribute "__iter__" (not iterable)
for y in x: pass
if x:
for s, t in x:
reveal_type(s) # N: Revealed type is "builtins.str"
reveal_type(s)
[builtins fixtures/dict.pyi]
[out]
main:8: error: Item "None" of "list[tuple[str, str]] | None" has no attribute "__iter__" (not iterable)
main:8: note: You can use "if x is not None" to guard against a None value
main:11: note: Revealed type is "builtins.str"

[case testUnionAttributeNoneNarrowingHint]
from typing import Optional

def f(s: Optional[str]) -> bool:
return s.startswith('x') # E: Item "None" of "str | None" has no attribute "startswith" \
# N: You can use "if s is not None" to guard against a None value
[builtins fixtures/ops.pyi]

[case testUnpackUnionNoCrashOnPartialNoneBinder]
from typing import Dict, Tuple, List, Any
Expand Down