Skip to content

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

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
AlexWaygood opened this issue Nov 21, 2021 · 1 comment
Closed

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

AlexWaygood opened this issue Nov 21, 2021 · 1 comment
Labels
bug mypy got something wrong

Comments

@AlexWaygood
Copy link
Member

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
@AlexWaygood
Copy link
Member Author

Issue fixed by python/typeshed#6583 🥳

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug mypy got something wrong
Projects
None yet
Development

No branches or pull requests

1 participant