@@ -38,6 +38,125 @@ reveal_type(s1 > s2) # revealed: bool
3838reveal_type(s1 >= s2) # revealed: bool
3939```
4040
41+ ## Signature derived from source ordering method
42+
43+ When the source ordering method accepts a broader type (like ` object ` ) for its ` other ` parameter,
44+ the synthesized comparison methods should use the same signature. This allows comparisons with types
45+ other than the class itself:
46+
47+ ``` py
48+ from functools import total_ordering
49+
50+ @total_ordering
51+ class Comparable :
52+ def __init__ (self , value : int ):
53+ self .value = value
54+
55+ def __eq__ (self , other : object ) -> bool :
56+ if isinstance (other, Comparable):
57+ return self .value == other.value
58+ if isinstance (other, int ):
59+ return self .value == other
60+ return NotImplemented
61+
62+ def __lt__ (self , other : object ) -> bool :
63+ if isinstance (other, Comparable):
64+ return self .value < other.value
65+ if isinstance (other, int ):
66+ return self .value < other
67+ return NotImplemented
68+
69+ a = Comparable(10 )
70+ b = Comparable(20 )
71+
72+ # Comparisons with the same type work.
73+ reveal_type(a <= b) # revealed: bool
74+ reveal_type(a >= b) # revealed: bool
75+
76+ # Comparisons with `int` also work because `__lt__` accepts `object`.
77+ reveal_type(a <= 15 ) # revealed: bool
78+ reveal_type(a >= 5 ) # revealed: bool
79+ ```
80+
81+ ## Multiple ordering methods with different signatures
82+
83+ When multiple ordering methods are defined with different signatures, the decorator selects a "root"
84+ method using the priority order: ` __lt__ ` > ` __le__ ` > ` __gt__ ` > ` __ge__ ` . Synthesized methods use
85+ the signature from the highest-priority method. Methods that are explicitly defined are not
86+ overridden.
87+
88+ ``` py
89+ from functools import total_ordering
90+
91+ @total_ordering
92+ class MultiSig :
93+ def __init__ (self , value : int ):
94+ self .value = value
95+
96+ def __eq__ (self , other : object ) -> bool :
97+ return True
98+ # __lt__ accepts `object` (highest priority, used as root)
99+ def __lt__ (self , other : object ) -> bool :
100+ return True
101+ # __gt__ only accepts `MultiSig` (not overridden by decorator)
102+ def __gt__ (self , other : " MultiSig" ) -> bool :
103+ return True
104+
105+ a = MultiSig(10 )
106+ b = MultiSig(20 )
107+
108+ # __le__ and __ge__ are synthesized with __lt__'s signature (accepts `object`)
109+ reveal_type(a <= b) # revealed: bool
110+ reveal_type(a <= 15 ) # revealed: bool
111+ reveal_type(a >= b) # revealed: bool
112+ reveal_type(a >= 15 ) # revealed: bool
113+
114+ # __gt__ keeps its original signature (only accepts MultiSig)
115+ reveal_type(a > b) # revealed: bool
116+ a > 15 # error: [unsupported-operator]
117+ ```
118+
119+ ## Overloaded ordering method
120+
121+ When the source ordering method is overloaded, the synthesized comparison methods should preserve
122+ all overloads:
123+
124+ ``` py
125+ from functools import total_ordering
126+ from typing import overload
127+
128+ @total_ordering
129+ class Flexible :
130+ def __init__ (self , value : int ):
131+ self .value = value
132+
133+ def __eq__ (self , other : object ) -> bool :
134+ return True
135+
136+ @overload
137+ def __lt__ (self , other : " Flexible" ) -> bool : ...
138+ @overload
139+ def __lt__ (self , other : int ) -> bool : ...
140+ def __lt__ (self , other : " Flexible | int" ) -> bool :
141+ if isinstance (other, Flexible):
142+ return self .value < other.value
143+ return self .value < other
144+
145+ a = Flexible(10 )
146+ b = Flexible(20 )
147+
148+ # Synthesized __le__ preserves overloads from __lt__
149+ reveal_type(a <= b) # revealed: bool
150+ reveal_type(a <= 15 ) # revealed: bool
151+
152+ # Synthesized __ge__ also preserves overloads
153+ reveal_type(a >= b) # revealed: bool
154+ reveal_type(a >= 15 ) # revealed: bool
155+
156+ # But comparison with an unsupported type should still error
157+ a <= " string" # error: [unsupported-operator]
158+ ```
159+
41160## Using ` __gt__ ` as the root comparison method
42161
43162When a class defines ` __eq__ ` and ` __gt__ ` , the decorator synthesizes ` __lt__ ` , ` __le__ ` , and
@@ -127,6 +246,41 @@ reveal_type(c1 > c2) # revealed: bool
127246reveal_type(c1 >= c2) # revealed: bool
128247```
129248
249+ ## Method precedence with inheritance
250+
251+ The decorator always prefers ` __lt__ ` > ` __le__ ` > ` __gt__ ` > ` __ge__ ` , regardless of whether the
252+ method is defined locally or inherited. In this example, the inherited ` __lt__ ` takes precedence
253+ over the locally-defined ` __gt__ ` :
254+
255+ ``` py
256+ from functools import total_ordering
257+ from typing import Literal
258+
259+ class Base :
260+ def __lt__ (self , other : " Base" ) -> Literal[True ]:
261+ return True
262+
263+ @total_ordering
264+ class Child (Base ):
265+ # __gt__ is defined locally, but __lt__ (inherited) takes precedence
266+ def __gt__ (self , other : " Child" ) -> Literal[False ]:
267+ return False
268+
269+ c1 = Child()
270+ c2 = Child()
271+
272+ # __lt__ is inherited from Base
273+ reveal_type(c1 < c2) # revealed: Literal[True]
274+
275+ # __gt__ is defined locally on Child
276+ reveal_type(c1 > c2) # revealed: Literal[False]
277+
278+ # __le__ and __ge__ are synthesized from __lt__ (the highest-priority method),
279+ # even though __gt__ is defined locally on the class itself
280+ reveal_type(c1 <= c2) # revealed: bool
281+ reveal_type(c1 >= c2) # revealed: bool
282+ ```
283+
130284## Explicitly-defined methods are not overridden
131285
132286When a class explicitly defines multiple comparison methods, the decorator does not override them.
@@ -245,6 +399,79 @@ n1 <= n2 # error: [unsupported-operator]
245399n1 >= n2 # error: [unsupported-operator]
246400```
247401
402+ ## Non-bool return type
403+
404+ When the root ordering method returns a non-bool type (like ` int ` ), the synthesized methods return a
405+ union of that type and ` bool ` . This is because ` @total_ordering ` generates methods like:
406+
407+ ``` python
408+ def __le__ (self , other ):
409+ return self < other or self == other
410+ ```
411+
412+ If ` __lt__ ` returns ` int ` , then the synthesized ` __le__ ` could return either ` int ` (from
413+ ` self < other ` ) or ` bool ` (from ` self == other ` ). Since ` bool ` is a subtype of ` int ` , the union
414+ simplifies to ` int ` :
415+
416+ ``` py
417+ from functools import total_ordering
418+
419+ @total_ordering
420+ class IntReturn :
421+ def __init__ (self , value : int ):
422+ self .value = value
423+
424+ def __eq__ (self , other : object ) -> bool :
425+ if not isinstance (other, IntReturn):
426+ return NotImplemented
427+ return self .value == other.value
428+
429+ def __lt__ (self , other : " IntReturn" ) -> int :
430+ return self .value - other.value
431+
432+ a = IntReturn(10 )
433+ b = IntReturn(20 )
434+
435+ # User-defined __lt__ returns int.
436+ reveal_type(a < b) # revealed: int
437+
438+ # Synthesized methods return int (the union int | bool simplifies to int
439+ # because bool is a subtype of int in Python).
440+ reveal_type(a <= b) # revealed: int
441+ reveal_type(a > b) # revealed: int
442+ reveal_type(a >= b) # revealed: int
443+ ```
444+
445+ When the root method returns a type that is not a supertype of ` bool ` , the union is preserved:
446+
447+ ``` py
448+ from functools import total_ordering
449+
450+ @total_ordering
451+ class StrReturn :
452+ def __init__ (self , value : str ):
453+ self .value = value
454+
455+ def __eq__ (self , other : object ) -> bool :
456+ if not isinstance (other, StrReturn):
457+ return NotImplemented
458+ return self .value == other.value
459+
460+ def __lt__ (self , other : " StrReturn" ) -> str :
461+ return self .value
462+
463+ a = StrReturn(" a" )
464+ b = StrReturn(" b" )
465+
466+ # User-defined __lt__ returns str.
467+ reveal_type(a < b) # revealed: str
468+
469+ # Synthesized methods return str | bool.
470+ reveal_type(a <= b) # revealed: str | bool
471+ reveal_type(a > b) # revealed: str | bool
472+ reveal_type(a >= b) # revealed: str | bool
473+ ```
474+
248475## Function call form
249476
250477When ` total_ordering ` is called as a function (not as a decorator), the same validation is
0 commit comments