From 620e28148afb4c8c04fbc0255e0c04769431c6b2 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Mon, 24 Jun 2024 18:38:37 +0300 Subject: [PATCH] Do not report plugin-generated methods with `explicit-override` (#17433) Closes https://github.com/typeddjango/django-stubs/issues/2226 Closes https://github.com/python/mypy/issues/17417 Closes https://github.com/python/mypy/pull/17370 Closes https://github.com/python/mypy/issues/17224 This is an alternative to https://github.com/python/mypy/pull/17418 Thanks a lot to @sterliakov, I took a dataclasses test case from #17370 --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- mypy/checker.py | 9 ++++++- test-data/unit/check-custom-plugin.test | 33 +++++++++++++++++++++++++ test-data/unit/check-dataclasses.test | 14 +++++++++++ test-data/unit/plugins/add_method.py | 23 +++++++++++++++++ 4 files changed, 78 insertions(+), 1 deletion(-) create mode 100644 test-data/unit/plugins/add_method.py diff --git a/mypy/checker.py b/mypy/checker.py index 4f20c6ee8493..2df74cf7be8d 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -1938,8 +1938,15 @@ def check_explicit_override_decorator( found_method_base_classes: list[TypeInfo] | None, context: Context | None = None, ) -> None: + plugin_generated = False + if defn.info and (node := defn.info.get(defn.name)) and node.plugin_generated: + # Do not report issues for plugin generated nodes, + # they can't realistically use `@override` for their methods. + plugin_generated = True + if ( - found_method_base_classes + not plugin_generated + and found_method_base_classes and not defn.is_explicit_override and defn.name not in ("__init__", "__new__") and not is_private(defn.name) diff --git a/test-data/unit/check-custom-plugin.test b/test-data/unit/check-custom-plugin.test index 63529cf165ce..2b3b3f4a8695 100644 --- a/test-data/unit/check-custom-plugin.test +++ b/test-data/unit/check-custom-plugin.test @@ -1050,6 +1050,39 @@ reveal_type(my_class.stmethod) # N: Revealed type is "Overload(def (arg: builti \[mypy] plugins=/test-data/unit/plugins/add_overloaded_method.py +[case testAddMethodPluginExplicitOverride] +# flags: --python-version 3.12 --config-file tmp/mypy.ini +from typing import override, TypeVar + +T = TypeVar('T', bound=type) + +def inject_foo(t: T) -> T: + # Imitates: + # t.foo_implicit = some_method + return t + +class BaseWithoutFoo: pass + +@inject_foo +class ChildWithFoo(BaseWithoutFoo): pass +reveal_type(ChildWithFoo.foo_implicit) # N: Revealed type is "def (self: __main__.ChildWithFoo)" + +@inject_foo +class SomeWithFoo(ChildWithFoo): pass +reveal_type(SomeWithFoo.foo_implicit) # N: Revealed type is "def (self: __main__.SomeWithFoo)" + +class ExplicitOverride(SomeWithFoo): + @override + def foo_implicit(self) -> None: pass + +class ImplicitOverride(SomeWithFoo): + def foo_implicit(self) -> None: pass # E: Method "foo_implicit" is not using @override but is overriding a method in class "__main__.SomeWithFoo" +[file mypy.ini] +\[mypy] +plugins=/test-data/unit/plugins/add_method.py +enable_error_code = explicit-override +[typing fixtures/typing-override.pyi] + [case testCustomErrorCodePlugin] # flags: --config-file tmp/mypy.ini --show-error-codes def main() -> int: diff --git a/test-data/unit/check-dataclasses.test b/test-data/unit/check-dataclasses.test index 924f9c7bb5be..f26ccd9a4854 100644 --- a/test-data/unit/check-dataclasses.test +++ b/test-data/unit/check-dataclasses.test @@ -2475,3 +2475,17 @@ class Base: class Child(Base): y: int [builtins fixtures/dataclasses.pyi] + + +[case testDataclassInheritanceWorksWithExplicitOverridesAndOrdering] +# flags: --enable-error-code explicit-override +from dataclasses import dataclass + +@dataclass(order=True) +class Base: + x: int + +@dataclass(order=True) +class Child(Base): + y: int +[builtins fixtures/dataclasses.pyi] diff --git a/test-data/unit/plugins/add_method.py b/test-data/unit/plugins/add_method.py new file mode 100644 index 000000000000..f3a7ebdb95ed --- /dev/null +++ b/test-data/unit/plugins/add_method.py @@ -0,0 +1,23 @@ +from __future__ import annotations + +from typing import Callable + +from mypy.plugin import ClassDefContext, Plugin +from mypy.plugins.common import add_method +from mypy.types import NoneType + + +class AddOverrideMethodPlugin(Plugin): + def get_class_decorator_hook_2(self, fullname: str) -> Callable[[ClassDefContext], bool] | None: + if fullname == "__main__.inject_foo": + return add_extra_methods_hook + return None + + +def add_extra_methods_hook(ctx: ClassDefContext) -> bool: + add_method(ctx, "foo_implicit", [], NoneType()) + return True + + +def plugin(version: str) -> type[AddOverrideMethodPlugin]: + return AddOverrideMethodPlugin