You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Wrong comparison in type error for comparisons #22316
I can reproduce this on master (2.5.0+dev @ f501cd0):
deff(x: int|None) ->bool:
returnx>=0
main.py:2: error: Unsupported operand types for <= ("int" and "None") [operator]
main.py:2: note: Left operand is of type "int | None"
Root cause: the message is rendered in mypy/messages.py:646 (incompatible_argument, the elif name.startswith('"__') branch) purely from the name of the method that failed:
NoneType has no __ge__, so check_op_reversible falls back to the reflected call (mypy/checkexpr.py:4215, rev_op_name = operators.reverse_op_methods[op_name]) and the callee that actually fails is "__le__" of "int". The symbol is therefore rendered as <= and the operands are printed in reflected order; nothing maps the reflected method back to the operator the user actually wrote. The result also disagrees with CPython's runtime message ('>=' not supported between instances of 'NoneType' and 'int').
All four orderings are mirrored on master when the left operand lacks the forward method:
written
reported
x >= 0
<= ("int" and "None")
x <= 0
>= ("int" and "None")
x > 0
< ("int" and "None")
x < 0
> ("int" and "None")
(When the forward method exists the symbol is correct, e.g. x: int; s: str; x >= s -> >= ("int" and "str").)
I'd like to work on this. One design question first, because the mirrored rendering is baked into existing assertions, e.g. test-data/unit/check-incremental.test:3101-3103 (1 <= A(1) -> >=, 1 >= A(1) -> <=) and test-data/unit/check-plugin-attrs.test:208-210 (roughly 8-12 assertions total):
Option A: always report the operator as written, with operands in source order (x >= 0 -> Unsupported operand types for >= ...), and update those assertions.
Option B: keep the reflected rendering, but add a note saying the error comes from the reflected call.
My lean is A (it matches both the source text and CPython's message), but I'll follow your preference.
Activity
inchang-ing commentedon Oct 29, 2026
I can reproduce this on master (
2.5.0+dev@f501cd0):Root cause: the message is rendered in
mypy/messages.py:646(incompatible_argument, theelif name.startswith('"__')branch) purely from the name of the method that failed:NoneTypehas no__ge__, socheck_op_reversiblefalls back to the reflected call (mypy/checkexpr.py:4215,rev_op_name = operators.reverse_op_methods[op_name]) and the callee that actually fails is"__le__" of "int". The symbol is therefore rendered as<=and the operands are printed in reflected order; nothing maps the reflected method back to the operator the user actually wrote. The result also disagrees with CPython's runtime message ('>=' not supported between instances of 'NoneType' and 'int').All four orderings are mirrored on master when the left operand lacks the forward method:
x >= 0<= ("int" and "None")x <= 0>= ("int" and "None")x > 0< ("int" and "None")x < 0> ("int" and "None")(When the forward method exists the symbol is correct, e.g.
x: int; s: str; x >= s->>= ("int" and "str").)I'd like to work on this. One design question first, because the mirrored rendering is baked into existing assertions, e.g.
test-data/unit/check-incremental.test:3101-3103(1 <= A(1)->>=,1 >= A(1)-><=) andtest-data/unit/check-plugin-attrs.test:208-210(roughly 8-12 assertions total):x >= 0->Unsupported operand types for >= ...), and update those assertions.My lean is A (it matches both the source text and CPython's message), but I'll follow your preference.
Fix flipped operator in comparison error message