Skip to content

Commit 63d6e7c

Browse files
eurestigvanrossum
authored andcommitted
Treat divmod like a binary operator (#4585)
1 parent 17d1fd9 commit 63d6e7c

File tree

4 files changed

+74
-0
lines changed

4 files changed

+74
-0
lines changed

mypy/nodes.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1321,6 +1321,7 @@ def accept(self, visitor: ExpressionVisitor[T]) -> T:
13211321
'*': '__mul__',
13221322
'/': '__truediv__',
13231323
'%': '__mod__',
1324+
'divmod': '__divmod__',
13241325
'//': '__floordiv__',
13251326
'**': '__pow__',
13261327
'@': '__matmul__',
@@ -1356,6 +1357,7 @@ def accept(self, visitor: ExpressionVisitor[T]) -> T:
13561357
'__mul__': '__rmul__',
13571358
'__truediv__': '__rtruediv__',
13581359
'__mod__': '__rmod__',
1360+
'__divmod__': '__rdivmod__',
13591361
'__floordiv__': '__rfloordiv__',
13601362
'__pow__': '__rpow__',
13611363
'__matmul__': '__rmatmul__',

mypy/semanal.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3272,6 +3272,12 @@ def visit_call_expr(self, expr: CallExpr) -> None:
32723272
expr.analyzed.accept(self)
32733273
elif refers_to_fullname(expr.callee, 'builtins.dict'):
32743274
expr.analyzed = self.translate_dict_call(expr)
3275+
elif refers_to_fullname(expr.callee, 'builtins.divmod'):
3276+
if not self.check_fixed_args(expr, 2, 'divmod'):
3277+
return
3278+
expr.analyzed = OpExpr('divmod', expr.args[0], expr.args[1])
3279+
expr.analyzed.line = expr.line
3280+
expr.analyzed.accept(self)
32753281
else:
32763282
# Normal call expression.
32773283
for a in expr.args:

test-data/unit/check-expressions.test

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -705,6 +705,51 @@ tmp/m.py:7: error: Invalid index type "int" for "A"; expected type "str"
705705
tmp/m.py:8: error: Invalid index type "int" for "A"; expected type "str"
706706

707707

708+
[case testDivmod]
709+
from typing import Tuple, Union, SupportsInt
710+
_Decimal = Union[Decimal, int]
711+
class Decimal(SupportsInt):
712+
def __init__(self, int) -> None: ...
713+
def __divmod__(self, other: _Decimal) -> Tuple[Decimal, Decimal]: ...
714+
def __rdivmod__(self, other: _Decimal) -> Tuple[Decimal, Decimal]: ...
715+
716+
i = 8
717+
f = 8.0
718+
d = Decimal(8)
719+
720+
reveal_type(divmod(i, i)) # E: Revealed type is 'Tuple[builtins.int, builtins.int]'
721+
reveal_type(divmod(f, i)) # E: Revealed type is 'Tuple[builtins.float, builtins.float]'
722+
reveal_type(divmod(d, i)) # E: Revealed type is 'Tuple[__main__.Decimal, __main__.Decimal]'
723+
724+
reveal_type(divmod(i, f)) # E: Revealed type is 'Tuple[builtins.float, builtins.float]'
725+
reveal_type(divmod(f, f)) # E: Revealed type is 'Tuple[builtins.float, builtins.float]'
726+
divmod(d, f) # E: Unsupported operand types for divmod ("Decimal" and "float")
727+
728+
reveal_type(divmod(i, d)) # E: Revealed type is 'Tuple[__main__.Decimal, __main__.Decimal]'
729+
divmod(f, d) # E: Unsupported operand types for divmod ("float" and "Decimal")
730+
reveal_type(divmod(d, d)) # E: Revealed type is 'Tuple[__main__.Decimal, __main__.Decimal]'
731+
732+
# Now some bad calls
733+
divmod() # E: 'divmod' expects 2 arguments \
734+
# E: Too few arguments for "divmod"
735+
divmod(7) # E: 'divmod' expects 2 arguments \
736+
# E: Too few arguments for "divmod"
737+
divmod(7, 8, 9) # E: 'divmod' expects 2 arguments \
738+
# E: Too many arguments for "divmod"
739+
divmod(_x=7, _y=9) # E: 'divmod' must be called with 2 positional arguments
740+
741+
divmod('foo', 'foo') # E: Unsupported left operand type for divmod ("str")
742+
divmod(i, 'foo') # E: Unsupported operand types for divmod ("int" and "str")
743+
divmod(f, 'foo') # E: Unsupported operand types for divmod ("float" and "str")
744+
divmod(d, 'foo') # E: Unsupported operand types for divmod ("Decimal" and "str")
745+
746+
divmod('foo', i) # E: Unsupported operand types for divmod ("str" and "int")
747+
divmod('foo', f) # E: Unsupported operand types for divmod ("str" and "float")
748+
divmod('foo', d) # E: Unsupported operand types for divmod ("str" and "Decimal")
749+
750+
[builtins fixtures/divmod.pyi]
751+
752+
708753
-- Unary operators
709754
-- ---------------
710755

test-data/unit/fixtures/divmod.pyi

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
from typing import TypeVar, Tuple, SupportsInt
2+
class object:
3+
def __init__(self): pass
4+
5+
class int(SupportsInt):
6+
def __divmod__(self, other: int) -> Tuple[int, int]: pass
7+
def __rdivmod__(self, other: int) -> Tuple[int, int]: pass
8+
9+
class float(SupportsInt):
10+
def __divmod__(self, other: float) -> Tuple[float, float]: pass
11+
def __rdivmod__(self, other: float) -> Tuple[float, float]: pass
12+
13+
14+
class tuple: pass
15+
class function: pass
16+
class str: pass
17+
class type: pass
18+
class ellipsis: pass
19+
20+
_N = TypeVar('_N', int, float)
21+
def divmod(_x: _N, _y: _N) -> Tuple[_N, _N]: ...

0 commit comments

Comments
 (0)