Skip to content

Strict equality not working between string and uuid #13632

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

Open
sk- opened this issue Mar 13, 2025 · 10 comments
Open

Strict equality not working between string and uuid #13632

sk- opened this issue Mar 13, 2025 · 10 comments

Comments

@sk-
Copy link
Contributor

sk- commented Mar 13, 2025

Bug Report

--strict-equality seems to only work in very reduced cases. In particular, it is not working when comparing strings and uuids.

To Reproduce

import uuid
from typing import TYPE_CHECKING, reveal_type

a = uuid.uuid4()
b = str(a)

if TYPE_CHECKING:
  reveal_type(a)
  reveal_type(b)
print(a == b)

c = "c"
d = 500
print(c == d)

https://mypy-play.net/?mypy=latest&python=3.12&flags=show-error-codes%2Cstrict-equality&gist=6c8273316b52930967ad0f8062c57aa3

Expected Behavior

The comparison in line 10 should raise a comparison overlap error. Something like

test_strict_equality.py:10: error: Non-overlapping equality check (left operand type: "str", right operand type: "uuid.UUID")  [comparison-overlap]

Actual Behavior

Mypy does not flag the string/UUID comparison as incompatible

test_strict_equality.py:8: note: Revealed type is "uuid.UUID"
test_strict_equality.py:9: note: Revealed type is "builtins.str"
test_strict_equality.py:14: error: Non-overlapping equality check (left operand type: "str", right operand type: "int")  [comparison-overlap]
Found 1 error in 1 file (checked 1 source file)

Your Environment

  • Mypy version used: 1.15.0
  • Mypy command-line flags: --strict-equality --show-error-codes
  • Mypy configuration options from mypy.ini (and other config files):
  • Python version used: 3.12.9
@sterliakov
Copy link

It only works when there's no custom __eq__ on types of both sides:

https://github.com/python/mypy/blob/e37d92d6c2d1de92e74c365ee1240c67c94c24b3/mypy/checkexpr.py#L3760-L3761

Any type can compare equal to anything (def __eq__(self, other): return True), and it isn't recognizeable just by looking at __eq__ signature. uuid.UUID has custom __eq__ defined in typeshed.

However, AFAIC that method only compares equal to other UUIDs, so perhaps it should be excluded from typeshed?

@sterliakov
Copy link

No, current typeshed consistently adds __eq__ to all classes that redefine it, so changing this for UUID would be unprecedented.

@sterliakov sterliakov closed this as not planned Won't fix, can't repro, duplicate, stale Mar 14, 2025
@sk-
Copy link
Contributor Author

sk- commented Mar 14, 2025

@sterliakov This is really unfortunate, because the code is special casing base types. That's why it works for strings and lists, as both the typings for strings and lists also have a custom declared __eq__ method. It can be be seen in this playground that mypy correctly reports a non overlapping error when checking them for equality.

I would argue against the decision of closing this issue, because uuid.UUID is a type from the standard library and could be easily special cased, just like builtin types.

If this is in fact is the intended behavior, could you please add a note to the documentation https://mypy.readthedocs.io/en/stable/command_line.html#cmdoption-mypy-strict-equality.

@sterliakov
Copy link

I'm slightly against introducing more special cases for stdlib like this - mypy code is fairly non-trivial, and this issue can be solved at the stubs/typeshed level more efficiently in checker-agnostic fashion. Pyright suffers from exactly the same problem now.

Perhaps you could raise this issue (slightly rephrasing to make it clear that custom __eq__ is harmful for uuid.UUID typing) in python/typeshed instead? Since we know that UUIDs only compare equal to other UUIDs, we may lie in stubs and omit custom UUID.__eq__ entirely. @hauntsaninja I'm not perfectly familiar with typeshed guidelines - is such an omission feasible?

@hauntsaninja hauntsaninja reopened this Mar 15, 2025
@hauntsaninja hauntsaninja transferred this issue from python/mypy Mar 16, 2025
@srittau
Copy link
Collaborator

srittau commented Mar 16, 2025

typeshed's annotation seem correct to me:

def __eq__(self, other: object) -> bool: ...

Annotating UUID.__eq__ explicitly in typeshed is necessary, since the argument is kwargs-or-positional as opposed to object.__eq__, which is positional-only.

I'm against making changes to typeshed that cause the stubs to divert from the implementation just to satisfy a particular type checker's - fairly arbitrary - rules.

@Akuli
Copy link
Collaborator

Akuli commented Mar 17, 2025

I personally think that deleting UUID.__eq__ would make sense due to "practicality beats purity". It would make mypy behave better while not making the situation worse on other type checkers.

@srittau
Copy link
Collaborator

srittau commented Mar 17, 2025

It would make the maintenance situation worse for typeshed, as we'd have to manage another stubtest allowlist entry. It also introduces a lie, which is might have other unforseen consequences in the future – for example for non-typechecker related issues. So this is still a -1 for me.

@sterliakov
Copy link

sterliakov commented Mar 25, 2025

just to satisfy a particular type checker's - fairly arbitrary - rules.

I would agree with this in general, but this case is less obvious. Both mypy and pyright (so an overwhelming majority, I believe, at least until red knot goes live) use the same heuristic: comparison of unrelated types emits a warning unless either side defines custom (not inherited from object) __eq__ method and does not belong to a short list of well-known builtins/typing primitives. IMO this justifies minor stubs-vs-runtime disagreement, especially given that UUID.__eq__ has wider type in stubs, so this would only mark explicit calls of some_uuid.__eq__(other=something) as false positives. This statement is probably perverse enough to just ignore and recommend using == or at least calling __eq__ with positional arg?

The benefits of such a change outweigh the inconvenience: I was also hit by string == uuid comparisons a couple of times, a mypy diagnostic would have helped detect it before running the test suite.

@srittau
Copy link
Collaborator

srittau commented Mar 25, 2025

We could change the annotations to the more correct:

@overload
def __eq__(self, other: UUID) -> bool: ...
@overload
def __eq__(self, other: object) -> NotImplementedType: ...

This is not only more correct, it would also allow type checkers to still implement the strict equality check – this time based on actual types, instead of faulty heuristics. I'm not sure any type checkers do this, at the moment.

@sterliakov
Copy link

sterliakov commented Mar 27, 2025

We could change the annotations to the more correct:

I'd be happy to see this implemented everywhere, consistently. If we apply this approach to all __eq__ (at least in the standard library), such a check should be easy possible to implement in mypy. Now we only check for method's presence, but can check an overloaded call instead. However, there's no precedent of using NotImplementedType in type hints for comparison methods AFAIC - now the consensus is "assume bool, ignore NotImplemented returns there". A change this big certainly needs a Discourse thread, and probably a typing spec update as well.

(as a small note, I'd prefer using NotImplemented directly in annotation, similarly to how we use None over NoneType - that's a reasonable exception for language-level singleton)

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

No branches or pull requests

5 participants