Skip to content

False-positive errors w.r.t. max() #11588

Closed

Description

@AlexWaygood

Bug Report

Mypy raises false-positive errors if you pass an object that only has a __gt__ method to max().

To Reproduce

Consider the following class definitions:

class OnlyKnowsDunderLT:
    def __init__(self, n):
        self.n = n
    def __lt__(self, other):
        return self.n < other.n
    def __repr__(self):
        return f'OnlyKnowsDunderLT(n={self.n})'
 
class OnlyKnowsDunderGT:
    def __init__(self, n):
        self.n = n
    def __gt__(self, other):
        return self.n > other.n
    def __repr__(self):
        return f'OnlyKnowsDunderGT(n={self.n})'

Here's how they behave with max() at runtime in Python 3.10:

>>> max(OnlyKnowsDunderLT(5), OnlyKnowsDunderLT(6))
OnlyKnowsDunderLT(n=6)
>>> max(OnlyKnowsDunderGT(3), OnlyKnowsDunderGT(8))
OnlyKnowsDunderGT(n=8)
>>> max(OnlyKnowsDunderLT(3), OnlyKnowsDunderGT(9))
OnlyKnowsDunderGT(n=9)

Expected Behavior

Mypy should raise no errors for code such as this, as it works fine at runtime and is perfectly type-safe.

Actual Behavior

Mypy raises errors for the following snippet of code, a typed equivalent to the untyped code above:

from typing import Protocol

class HasN(Protocol):
    n: int

class OnlyKnowsDunderLT:
    def __init__(self, n: int) -> None:
        self.n = n
    def __lt__(self, other: HasN) -> bool:
        return self.n < other.n
    def __repr__(self) -> str:
        return f'OnlyKnowsDunderLT(n={self.n})'
 
class OnlyKnowsDunderGT:
    def __init__(self, n: int) -> None:
        self.n = n
    def __gt__(self, other: HasN) -> bool:
        return self.n > other.n
    def __repr__(self) -> str:
        return f'OnlyKnowsDunderGT(n={self.n})'

max(OnlyKnowsDunderLT(5), OnlyKnowsDunderLT(6))
max(OnlyKnowsDunderGT(3), OnlyKnowsDunderGT(8)) # error: No overload variant of "max" matches argument types "OnlyKnowsDunderGT", "OnlyKnowsDunderGT"  [call-overload]
max(OnlyKnowsDunderLT(3), OnlyKnowsDunderGT(9)) # error: No overload variant of "max" matches argument types "OnlyKnowsDunderLT", "OnlyKnowsDunderGT"  [call-overload]

Over at typeshed, the stub for max() has just been changed. max() is now annotated as requiring arguments to have a __gt__ method rather than a __lt__ method, as this more accurately reflects the C code for max(). However, the only effect of this change is to "flip" the errors that mypy reports: it will now error out on the class that only has a __lt__ method, instead of the class that only has a __gt__ method.

One potential solution to this problem could be to further revise the typeshed stub. However, I have been advised by @srittau that this would be a suboptimal solution, as __gt__/__lt__ probably need to be special-cased by mypy and other type-checkers to some extent.

Your Environment

  • Mypy version used: 0.910
  • Mypy command-line flags: --strict, --show-error-codes (the same errors occur without the --strict flag)
  • Mypy configuration options from mypy.ini (and other config files): None
  • Python version used: 3.10

Activity

  1. AlexWaygood commented on Dec 14, 2021

    @AlexWaygood
    MemberAuthor

    Issue fixed by python/typeshed#6583 🥳

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugmypy got something wrong

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions