Skip to content

Commit

Permalink
Fix crash related to functools.total_ordering and forward reference (#…
Browse files Browse the repository at this point in the history
…12767)

Run the plugin in a later pass to avoid placeholder nodes.

Fixes #11728.
  • Loading branch information
JukkaL committed May 11, 2022
1 parent 03901ef commit 8faf44a
Show file tree
Hide file tree
Showing 3 changed files with 30 additions and 7 deletions.
6 changes: 3 additions & 3 deletions mypy/plugins/default.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

Expand Down
10 changes: 6 additions & 4 deletions mypy/plugins/functools.py
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand All @@ -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."""
Expand Down
21 changes: 21 additions & 0 deletions test-data/unit/check-functools.test
Original file line number Diff line number Diff line change
Expand Up @@ -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]

0 comments on commit 8faf44a

Please sign in to comment.