diff --git a/doc/whatsnew/fragments/7529.false_positive b/doc/whatsnew/fragments/7529.false_positive new file mode 100644 index 0000000000..7775d90862 --- /dev/null +++ b/doc/whatsnew/fragments/7529.false_positive @@ -0,0 +1,4 @@ +Fix the message for ``unnecessary-dunder-call`` for ``__aiter__`` and ``__aneext__``. Also +only emit the warning when ``py-version`` >= 3.10. + +Closes #7529 diff --git a/pylint/checkers/dunder_methods.py b/pylint/checkers/dunder_methods.py index d753fceba6..1b61be7d4f 100644 --- a/pylint/checkers/dunder_methods.py +++ b/pylint/checkers/dunder_methods.py @@ -16,101 +16,105 @@ from pylint.lint import PyLinter -DUNDER_METHODS: dict[str, str] = { - "__init__": "Instantiate class directly", - "__del__": "Use del keyword", - "__repr__": "Use repr built-in function", - "__str__": "Use str built-in function", - "__bytes__": "Use bytes built-in function", - "__format__": "Use format built-in function, format string method, or f-string", - "__lt__": "Use < operator", - "__le__": "Use <= operator", - "__eq__": "Use == operator", - "__ne__": "Use != operator", - "__gt__": "Use > operator", - "__ge__": "Use >= operator", - "__hash__": "Use hash built-in function", - "__bool__": "Use bool built-in function", - "__getattr__": "Access attribute directly or use getattr built-in function", - "__getattribute__": "Access attribute directly or use getattr built-in function", - "__setattr__": "Set attribute directly or use setattr built-in function", - "__delattr__": "Use del keyword", - "__dir__": "Use dir built-in function", - "__get__": "Use get method", - "__set__": "Use set method", - "__delete__": "Use del keyword", - "__instancecheck__": "Use isinstance built-in function", - "__subclasscheck__": "Use issubclass built-in function", - "__call__": "Invoke instance directly", - "__len__": "Use len built-in function", - "__length_hint__": "Use length_hint method", - "__getitem__": "Access item via subscript", - "__setitem__": "Set item via subscript", - "__delitem__": "Use del keyword", - "__iter__": "Use iter built-in function", - "__next__": "Use next built-in function", - "__reversed__": "Use reversed built-in function", - "__contains__": "Use in keyword", - "__add__": "Use + operator", - "__sub__": "Use - operator", - "__mul__": "Use * operator", - "__matmul__": "Use @ operator", - "__truediv__": "Use / operator", - "__floordiv__": "Use // operator", - "__mod__": "Use % operator", - "__divmod__": "Use divmod built-in function", - "__pow__": "Use ** operator or pow built-in function", - "__lshift__": "Use << operator", - "__rshift__": "Use >> operator", - "__and__": "Use & operator", - "__xor__": "Use ^ operator", - "__or__": "Use | operator", - "__radd__": "Use + operator", - "__rsub__": "Use - operator", - "__rmul__": "Use * operator", - "__rmatmul__": "Use @ operator", - "__rtruediv__": "Use / operator", - "__rfloordiv__": "Use // operator", - "__rmod__": "Use % operator", - "__rdivmod__": "Use divmod built-in function", - "__rpow__": "Use ** operator or pow built-in function", - "__rlshift__": "Use << operator", - "__rrshift__": "Use >> operator", - "__rand__": "Use & operator", - "__rxor__": "Use ^ operator", - "__ror__": "Use | operator", - "__iadd__": "Use += operator", - "__isub__": "Use -= operator", - "__imul__": "Use *= operator", - "__imatmul__": "Use @= operator", - "__itruediv__": "Use /= operator", - "__ifloordiv__": "Use //= operator", - "__imod__": "Use %= operator", - "__ipow__": "Use **= operator", - "__ilshift__": "Use <<= operator", - "__irshift__": "Use >>= operator", - "__iand__": "Use &= operator", - "__ixor__": "Use ^= operator", - "__ior__": "Use |= operator", - "__neg__": "Multiply by -1 instead", - "__pos__": "Multiply by +1 instead", - "__abs__": "Use abs built-in function", - "__invert__": "Use ~ operator", - "__complex__": "Use complex built-in function", - "__int__": "Use int built-in function", - "__float__": "Use float built-in function", - "__index__": "Use index method", - "__round__": "Use round built-in function", - "__trunc__": "Use math.trunc function", - "__floor__": "Use math.floor function", - "__ceil__": "Use math.ceil function", - "__enter__": "Invoke context manager directly", - "__aiter__": "Use iter built-in function", - "__anext__": "Use next built-in function", - "__aenter__": "Invoke context manager directly", - "__copy__": "Use copy.copy function", - "__deepcopy__": "Use copy.deepcopy function", - "__fspath__": "Use os.fspath function instead", +DUNDER_METHODS: dict[tuple[int, int], dict[str, str]] = { + (0, 0): { + "__init__": "Instantiate class directly", + "__del__": "Use del keyword", + "__repr__": "Use repr built-in function", + "__str__": "Use str built-in function", + "__bytes__": "Use bytes built-in function", + "__format__": "Use format built-in function, format string method, or f-string", + "__lt__": "Use < operator", + "__le__": "Use <= operator", + "__eq__": "Use == operator", + "__ne__": "Use != operator", + "__gt__": "Use > operator", + "__ge__": "Use >= operator", + "__hash__": "Use hash built-in function", + "__bool__": "Use bool built-in function", + "__getattr__": "Access attribute directly or use getattr built-in function", + "__getattribute__": "Access attribute directly or use getattr built-in function", + "__setattr__": "Set attribute directly or use setattr built-in function", + "__delattr__": "Use del keyword", + "__dir__": "Use dir built-in function", + "__get__": "Use get method", + "__set__": "Use set method", + "__delete__": "Use del keyword", + "__instancecheck__": "Use isinstance built-in function", + "__subclasscheck__": "Use issubclass built-in function", + "__call__": "Invoke instance directly", + "__len__": "Use len built-in function", + "__length_hint__": "Use length_hint method", + "__getitem__": "Access item via subscript", + "__setitem__": "Set item via subscript", + "__delitem__": "Use del keyword", + "__iter__": "Use iter built-in function", + "__next__": "Use next built-in function", + "__reversed__": "Use reversed built-in function", + "__contains__": "Use in keyword", + "__add__": "Use + operator", + "__sub__": "Use - operator", + "__mul__": "Use * operator", + "__matmul__": "Use @ operator", + "__truediv__": "Use / operator", + "__floordiv__": "Use // operator", + "__mod__": "Use % operator", + "__divmod__": "Use divmod built-in function", + "__pow__": "Use ** operator or pow built-in function", + "__lshift__": "Use << operator", + "__rshift__": "Use >> operator", + "__and__": "Use & operator", + "__xor__": "Use ^ operator", + "__or__": "Use | operator", + "__radd__": "Use + operator", + "__rsub__": "Use - operator", + "__rmul__": "Use * operator", + "__rmatmul__": "Use @ operator", + "__rtruediv__": "Use / operator", + "__rfloordiv__": "Use // operator", + "__rmod__": "Use % operator", + "__rdivmod__": "Use divmod built-in function", + "__rpow__": "Use ** operator or pow built-in function", + "__rlshift__": "Use << operator", + "__rrshift__": "Use >> operator", + "__rand__": "Use & operator", + "__rxor__": "Use ^ operator", + "__ror__": "Use | operator", + "__iadd__": "Use += operator", + "__isub__": "Use -= operator", + "__imul__": "Use *= operator", + "__imatmul__": "Use @= operator", + "__itruediv__": "Use /= operator", + "__ifloordiv__": "Use //= operator", + "__imod__": "Use %= operator", + "__ipow__": "Use **= operator", + "__ilshift__": "Use <<= operator", + "__irshift__": "Use >>= operator", + "__iand__": "Use &= operator", + "__ixor__": "Use ^= operator", + "__ior__": "Use |= operator", + "__neg__": "Multiply by -1 instead", + "__pos__": "Multiply by +1 instead", + "__abs__": "Use abs built-in function", + "__invert__": "Use ~ operator", + "__complex__": "Use complex built-in function", + "__int__": "Use int built-in function", + "__float__": "Use float built-in function", + "__index__": "Use index method", + "__round__": "Use round built-in function", + "__trunc__": "Use math.trunc function", + "__floor__": "Use math.floor function", + "__ceil__": "Use math.ceil function", + "__enter__": "Invoke context manager directly", + "__aenter__": "Invoke context manager directly", + "__copy__": "Use copy.copy function", + "__deepcopy__": "Use copy.deepcopy function", + "__fspath__": "Use os.fspath function instead", + }, + (3, 10): { + "__aiter__": "Use aiter built-in function", + "__anext__": "Use anext built-in function", + }, } @@ -143,6 +147,12 @@ class DunderCallChecker(BaseChecker): } options = () + def open(self) -> None: + self._dunder_methods: dict[str, str] = {} + for since_vers, dunder_methods in DUNDER_METHODS.items(): + if since_vers <= self.linter.config.py_version: + self._dunder_methods.update(dunder_methods) + @staticmethod def within_dunder_def(node: nodes.NodeNG) -> bool: """Check if dunder method call is within a dunder method definition.""" @@ -161,7 +171,7 @@ def visit_call(self, node: nodes.Call) -> None: """Check if method being called is an unnecessary dunder method.""" if ( isinstance(node.func, nodes.Attribute) - and node.func.attrname in DUNDER_METHODS + and node.func.attrname in self._dunder_methods and not self.within_dunder_def(node) and not ( isinstance(node.func.expr, nodes.Call) @@ -177,7 +187,7 @@ def visit_call(self, node: nodes.Call) -> None: self.add_message( "unnecessary-dunder-call", node=node, - args=(node.func.attrname, DUNDER_METHODS[node.func.attrname]), + args=(node.func.attrname, self._dunder_methods[node.func.attrname]), confidence=HIGH, ) diff --git a/tests/functional/u/unnecessary/unnecessary_dunder_call_async_py310.py b/tests/functional/u/unnecessary/unnecessary_dunder_call_async_py310.py new file mode 100644 index 0000000000..c2ab58a57b --- /dev/null +++ b/tests/functional/u/unnecessary/unnecessary_dunder_call_async_py310.py @@ -0,0 +1,15 @@ +"""Checks for unnecessary-dunder-call on __aiter__/__anext__ with py-version=3.10.""" + + +class MyClass: + """A class implementing __aiter__ and __anext__.""" + + def __aiter__(self): + ... + + async def __anext__(self): + ... + + +MyClass().__aiter__() # [unnecessary-dunder-call] +MyClass().__anext__() # [unnecessary-dunder-call] diff --git a/tests/functional/u/unnecessary/unnecessary_dunder_call_async_py310.rc b/tests/functional/u/unnecessary/unnecessary_dunder_call_async_py310.rc new file mode 100644 index 0000000000..7d7b7aa0c1 --- /dev/null +++ b/tests/functional/u/unnecessary/unnecessary_dunder_call_async_py310.rc @@ -0,0 +1,2 @@ +[master] +py-version=3.10 diff --git a/tests/functional/u/unnecessary/unnecessary_dunder_call_async_py310.txt b/tests/functional/u/unnecessary/unnecessary_dunder_call_async_py310.txt new file mode 100644 index 0000000000..bcb5647f94 --- /dev/null +++ b/tests/functional/u/unnecessary/unnecessary_dunder_call_async_py310.txt @@ -0,0 +1,2 @@ +unnecessary-dunder-call:14:0:14:21::Unnecessarily calls dunder method __aiter__. Use aiter built-in function.:HIGH +unnecessary-dunder-call:15:0:15:21::Unnecessarily calls dunder method __anext__. Use anext built-in function.:HIGH diff --git a/tests/functional/u/unnecessary/unnecessary_dunder_call_async_py39.py b/tests/functional/u/unnecessary/unnecessary_dunder_call_async_py39.py new file mode 100644 index 0000000000..589524170f --- /dev/null +++ b/tests/functional/u/unnecessary/unnecessary_dunder_call_async_py39.py @@ -0,0 +1,15 @@ +"""Checks for unnecessary-dunder-call on __aiter__/__anext__ with py-version=3.9.""" + + +class MyClass: + """A class implementing __aiter__ and __anext__.""" + + def __aiter__(self): + ... + + async def __anext__(self): + ... + + +MyClass().__aiter__() +MyClass().__anext__() diff --git a/tests/functional/u/unnecessary/unnecessary_dunder_call_async_py39.rc b/tests/functional/u/unnecessary/unnecessary_dunder_call_async_py39.rc new file mode 100644 index 0000000000..aed012f734 --- /dev/null +++ b/tests/functional/u/unnecessary/unnecessary_dunder_call_async_py39.rc @@ -0,0 +1,2 @@ +[MAIN] +py-version=3.9