Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix --warn-return-any for NotImplemented #4545

Merged
merged 3 commits into from
Feb 6, 2018
Merged
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
12 changes: 10 additions & 2 deletions mypy/checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
from mypy.meet import is_overlapping_types
from mypy.options import Options
from mypy.plugin import Plugin, CheckerPluginInterface
from mypy.sharedparse import BINARY_MAGIC_METHODS

from mypy import experiments

Expand Down Expand Up @@ -2179,8 +2180,11 @@ def check_return_stmt(self, s: ReturnStmt) -> None:
if isinstance(typ, AnyType):
# (Unless you asked to be warned in that case, and the
# function is not declared to return Any)
if (self.options.warn_return_any and not self.current_node_deferred and
not is_proper_subtype(AnyType(TypeOfAny.special_form), return_type)):
if (self.options.warn_return_any
and not self.current_node_deferred
and not is_proper_subtype(AnyType(TypeOfAny.special_form), return_type)
and not (defn.name() in BINARY_MAGIC_METHODS and
is_literal_not_implemented(s.expr))):
self.msg.incorrectly_returning_any(return_type, s)
return

Expand Down Expand Up @@ -3232,6 +3236,10 @@ def remove_optional(typ: Type) -> Type:
return typ


def is_literal_not_implemented(n: Expression) -> bool:
return isinstance(n, NameExpr) and n.fullname == 'builtins.NotImplemented'


def builtin_item_type(tp: Type) -> Optional[Type]:
"""Get the item type of a builtin container.

Expand Down
47 changes: 46 additions & 1 deletion mypy/sharedparse.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
"__delitem__",
"__divmod__",
"__div__",
"__divmod__",
"__enter__",
"__exit__",
"__eq__",
Expand Down Expand Up @@ -92,6 +91,52 @@

MAGIC_METHODS_POS_ARGS_ONLY = MAGIC_METHODS - MAGIC_METHODS_ALLOWING_KWARGS

BINARY_MAGIC_METHODS = {
"__add__",
"__and__",
"__cmp__",
"__divmod__",
"__div__",
"__eq__",
"__floordiv__",
"__ge__",
"__gt__",
"__iadd__",
"__iand__",
"__idiv__",
"__ifloordiv__",
"__ilshift__",
"__imod__",
"__imul__",
"__ior__",
"__ipow__",
"__irshift__",
"__isub__",
"__ixor__",
"__le__",
"__lshift__",
"__lt__",
"__mod__",
"__mul__",
"__or__",
"__pow__",
"__radd__",
"__rand__",
"__rdiv__",
"__rfloordiv__",
"__rlshift__",
"__rmod__",
"__rmul__",
"__ror__",
"__rpow__",
"__rrshift__",
"__rshift__",
"__rsub__",
"__rxor__",
"__sub__",
"__xor__",
}


def special_function_elide_names(name: str) -> bool:
return name in MAGIC_METHODS_POS_ARGS_ONLY
Expand Down
15 changes: 15 additions & 0 deletions test-data/unit/check-warnings.test
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,21 @@ def f() -> int: return g()
[out]
main:4: warning: Returning Any from function declared to return "int"

[case testReturnAnyForNotImplementedInBinaryMagicMethods]
# flags: --warn-return-any
class A:
def __eq__(self, other: object) -> bool: return NotImplemented
[builtins fixtures/notimplemented.pyi]
[out]

[case testReturnAnyForNotImplementedInNormalMethods]
# flags: --warn-return-any
class A:
def some(self) -> bool: return NotImplemented
[builtins fixtures/notimplemented.pyi]
[out]
main:3: warning: Returning Any from function declared to return "bool"

[case testReturnAnyFromTypedFunctionWithSpecificFormatting]
# flags: --warn-return-any
from typing import Any, Tuple
Expand Down
13 changes: 13 additions & 0 deletions test-data/unit/fixtures/notimplemented.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# builtins stub used in NotImplemented related cases.
from typing import Any, cast


class object:
def __init__(self) -> None: pass

class type: pass
class function: pass
class bool: pass
class int: pass
class str: pass
NotImplemented = cast(Any, None)