From 8faf44ad44f19c2dcf3f31f12eed5e58494fc3a3 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Wed, 11 May 2022 15:16:19 +0100 Subject: [PATCH] Fix crash related to functools.total_ordering and forward reference (#12767) Run the plugin in a later pass to avoid placeholder nodes. Fixes #11728. --- mypy/plugins/default.py | 6 +++--- mypy/plugins/functools.py | 10 ++++++---- test-data/unit/check-functools.test | 21 +++++++++++++++++++++ 3 files changed, 30 insertions(+), 7 deletions(-) diff --git a/mypy/plugins/default.py b/mypy/plugins/default.py index 50e0e8cb4315..0ae95eb040db 100644 --- a/mypy/plugins/default.py +++ b/mypy/plugins/default.py @@ -96,7 +96,6 @@ def get_class_decorator_hook(self, fullname: str ) -> Optional[Callable[[ClassDefContext], None]]: from mypy.plugins import attrs from mypy.plugins import dataclasses - from mypy.plugins import functools if fullname in attrs.attr_class_makers: return attrs.attr_class_maker_callback @@ -118,17 +117,18 @@ def get_class_decorator_hook(self, fullname: str ) elif fullname in dataclasses.dataclass_makers: return dataclasses.dataclass_tag_callback - elif fullname in functools.functools_total_ordering_makers: - return functools.functools_total_ordering_maker_callback return None def get_class_decorator_hook_2(self, fullname: str ) -> Optional[Callable[[ClassDefContext], bool]]: from mypy.plugins import dataclasses + from mypy.plugins import functools if fullname in dataclasses.dataclass_makers: return dataclasses.dataclass_class_maker_callback + elif fullname in functools.functools_total_ordering_makers: + return functools.functools_total_ordering_maker_callback return None diff --git a/mypy/plugins/functools.py b/mypy/plugins/functools.py index 9e4d24283049..db10b7f1a262 100644 --- a/mypy/plugins/functools.py +++ b/mypy/plugins/functools.py @@ -26,25 +26,25 @@ class _MethodInfo(NamedTuple): def functools_total_ordering_maker_callback(ctx: mypy.plugin.ClassDefContext, - auto_attribs_default: bool = False) -> None: + auto_attribs_default: bool = False) -> bool: """Add dunder methods to classes decorated with functools.total_ordering.""" if ctx.api.options.python_version < (3,): # This plugin is not supported in Python 2 mode (it's a no-op). - return + return True comparison_methods = _analyze_class(ctx) if not comparison_methods: ctx.api.fail( 'No ordering operation defined when using "functools.total_ordering": < > <= >=', ctx.reason) - return + return True # prefer __lt__ to __le__ to __gt__ to __ge__ root = max(comparison_methods, key=lambda k: (comparison_methods[k] is None, k)) root_method = comparison_methods[root] if not root_method: # None of the defined comparison methods can be analysed - return + return True other_type = _find_other_type(root_method) bool_type = ctx.api.named_type('builtins.bool') @@ -61,6 +61,8 @@ def functools_total_ordering_maker_callback(ctx: mypy.plugin.ClassDefContext, args = [Argument(Var('other', other_type), other_type, None, ARG_POS)] add_method_to_class(ctx.api, ctx.cls, additional_op, args, ret_type) + return True + def _find_other_type(method: _MethodInfo) -> Type: """Find the type of the ``other`` argument in a comparison method.""" diff --git a/test-data/unit/check-functools.test b/test-data/unit/check-functools.test index 5f9159ab9c52..a2c6ba2eee05 100644 --- a/test-data/unit/check-functools.test +++ b/test-data/unit/check-functools.test @@ -132,3 +132,24 @@ from typing import TypeVar, Generic _T = TypeVar('_T') class cached_property(Generic[_T]): ... [builtins fixtures/property.pyi] + +[case testTotalOrderingWithForwardReference] +from typing import Generic, Any, TypeVar +import functools + +T = TypeVar("T", bound="C") + +@functools.total_ordering +class D(Generic[T]): + def __lt__(self, other: Any) -> bool: + ... + +class C: + pass + +def f(d: D[C]) -> None: + reveal_type(d.__gt__) # N: Revealed type is "def (other: Any) -> builtins.bool" + +d: D[int] # E: Type argument "int" of "D" must be a subtype of "C" +[builtins fixtures/ops.pyi] +[builtins fixtures/dict.pyi]