Skip to content

Commit 266a63f

Browse files
pkchgvanrossum
authored andcommitted
Treat non-inplace augmented assignment as a simple assignment (#3110)
Fixes #2098
1 parent 8e84c7d commit 266a63f

File tree

5 files changed

+149
-9
lines changed

5 files changed

+149
-9
lines changed

mypy/checker.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1939,14 +1939,18 @@ def visit_operator_assignment_stmt(self,
19391939
"""Type check an operator assignment statement, e.g. x += 1."""
19401940
lvalue_type = self.expr_checker.accept(s.lvalue)
19411941
inplace, method = infer_operator_assignment_method(lvalue_type, s.op)
1942-
rvalue_type, method_type = self.expr_checker.check_op(
1943-
method, lvalue_type, s.rvalue, s)
1944-
1945-
if isinstance(s.lvalue, IndexExpr) and not inplace:
1946-
self.check_indexed_assignment(s.lvalue, s.rvalue, s.rvalue)
1947-
else:
1942+
if inplace:
1943+
# There is __ifoo__, treat as x = x.__ifoo__(y)
1944+
rvalue_type, method_type = self.expr_checker.check_op(
1945+
method, lvalue_type, s.rvalue, s)
19481946
if not is_subtype(rvalue_type, lvalue_type):
19491947
self.msg.incompatible_operator_assignment(s.op, s)
1948+
else:
1949+
# There is no __ifoo__, treat as x = x <foo> y
1950+
expr = OpExpr(s.op, s.lvalue, s.rvalue)
1951+
expr.set_line(s)
1952+
self.check_assignment(lvalue=s.lvalue, rvalue=expr,
1953+
infer_lvalue_type=True, new_syntax=False)
19501954

19511955
def visit_assert_stmt(self, s: AssertStmt) -> None:
19521956
self.expr_checker.accept(s.expr)

test-data/unit/check-generics.test

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -309,7 +309,6 @@ class C:
309309
def __add__(self, o: 'C') -> 'C': pass
310310
[out]
311311
main:7: error: Unsupported operand types for + ("C" and "B")
312-
main:7: error: Incompatible types in assignment (expression has type "B", target has type "C")
313312
main:8: error: Invalid index type "C" for A[C]; expected type "B"
314313

315314
[case testOperatorAssignmentWithIndexLvalue2]

test-data/unit/check-statements.test

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,7 @@ class B:
226226
class C: pass
227227
[out]
228228
main:3: error: Unsupported operand types for + ("A" and "B")
229-
main:4: error: Result type of + incompatible in assignment
229+
main:4: error: Incompatible types in assignment (expression has type "C", variable has type "B")
230230
main:5: error: Unsupported left operand type for + ("C")
231231

232232
[case testMinusAssign]
@@ -246,7 +246,7 @@ class B:
246246
class C: pass
247247
[out]
248248
main:3: error: Unsupported operand types for - ("A" and "B")
249-
main:4: error: Result type of - incompatible in assignment
249+
main:4: error: Incompatible types in assignment (expression has type "C", variable has type "B")
250250
main:5: error: Unsupported left operand type for - ("C")
251251

252252
[case testMulAssign]
@@ -1514,3 +1514,46 @@ class A(): pass
15141514
class B(): pass
15151515
[out]
15161516
main:8: error: Incompatible types in assignment (expression has type "A", variable has type "B")
1517+
1518+
[case testAugmentedAssignmentIntFloat]
1519+
weight0 = 65.5
1520+
reveal_type(weight0) # E: Revealed type is 'builtins.float'
1521+
weight0 = 65
1522+
reveal_type(weight0) # E: Revealed type is 'builtins.int'
1523+
weight0 *= 'a' # E: Incompatible types in assignment (expression has type "str", variable has type "float")
1524+
weight0 *= 0.5
1525+
reveal_type(weight0) # E: Revealed type is 'builtins.float'
1526+
weight0 *= object() # E: Unsupported operand types for * ("float" and "object")
1527+
reveal_type(weight0) # E: Revealed type is 'builtins.float'
1528+
1529+
[builtins fixtures/float.pyi]
1530+
1531+
[case testAugmentedAssignmentIntFloatMember]
1532+
class A:
1533+
def __init__(self) -> None:
1534+
self.weight0 = 65.5
1535+
reveal_type(self.weight0) # E: Revealed type is 'builtins.float'
1536+
self.weight0 = 65
1537+
reveal_type(self.weight0) # E: Revealed type is 'builtins.int'
1538+
self.weight0 *= 'a' # E: Incompatible types in assignment (expression has type "str", variable has type "float")
1539+
self.weight0 *= 0.5
1540+
reveal_type(self.weight0) # E: Revealed type is 'builtins.float'
1541+
self.weight0 *= object() # E: Unsupported operand types for * ("float" and "object")
1542+
reveal_type(self.weight0) # E: Revealed type is 'builtins.float'
1543+
1544+
[builtins fixtures/float.pyi]
1545+
1546+
[case testAugmentedAssignmentIntFloatDict]
1547+
from typing import Dict
1548+
d = {'weight0': 65.5}
1549+
reveal_type(d['weight0']) # E: Revealed type is 'builtins.float*'
1550+
d['weight0'] = 65
1551+
reveal_type(d['weight0']) # E: Revealed type is 'builtins.float*'
1552+
d['weight0'] *= 'a' # E: Unsupported operand types for * ("float" and "str") # E: Incompatible types in assignment (expression has type "str", target has type "float")
1553+
d['weight0'] *= 0.5
1554+
reveal_type(d['weight0']) # E: Revealed type is 'builtins.float*'
1555+
d['weight0'] *= object() # E: Unsupported operand types for * ("float" and "object")
1556+
reveal_type(d['weight0']) # E: Revealed type is 'builtins.float*'
1557+
1558+
[builtins fixtures/floatdict.pyi]
1559+

test-data/unit/fixtures/float.pyi

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
Any = 0
2+
3+
class object:
4+
def __init__(self) -> None: pass
5+
6+
class type:
7+
def __init__(self, x: Any) -> None: pass
8+
9+
class str:
10+
def __add__(self, other: 'str') -> 'str': pass
11+
def __rmul__(self, n: int) -> str: ...
12+
13+
class bytes: pass
14+
15+
class tuple: pass
16+
class function: pass
17+
18+
class ellipsis: pass
19+
20+
21+
class int:
22+
def __float__(self) -> float: ...
23+
def __int__(self) -> int: ...
24+
def __mul__(self, x: int) -> int: ...
25+
def __rmul__(self, x: int) -> int: ...
26+
27+
class float:
28+
def __float__(self) -> float: ...
29+
def __int__(self) -> int: ...
30+
def __mul__(self, x: float) -> float: ...
31+
def __rmul__(self, x: float) -> float: ...

test-data/unit/fixtures/floatdict.pyi

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
from typing import TypeVar, Generic, Iterable, Iterator, Mapping, Tuple, overload, Optional, Union
2+
3+
T = TypeVar('T')
4+
KT = TypeVar('KT')
5+
VT = TypeVar('VT')
6+
7+
Any = 0
8+
9+
class object:
10+
def __init__(self) -> None: pass
11+
12+
class type:
13+
def __init__(self, x: Any) -> None: pass
14+
15+
class str:
16+
def __add__(self, other: 'str') -> 'str': pass
17+
def __rmul__(self, n: int) -> str: ...
18+
19+
class bytes: pass
20+
21+
class tuple: pass
22+
class function: pass
23+
24+
class ellipsis: pass
25+
26+
class list(Iterable[T], Generic[T]):
27+
@overload
28+
def __init__(self) -> None: pass
29+
@overload
30+
def __init__(self, x: Iterable[T]) -> None: pass
31+
def __iter__(self) -> Iterator[T]: pass
32+
def __add__(self, x: list[T]) -> list[T]: pass
33+
def __mul__(self, x: int) -> list[T]: pass
34+
def __getitem__(self, x: int) -> T: pass
35+
def append(self, x: T) -> None: pass
36+
def extend(self, x: Iterable[T]) -> None: pass
37+
38+
class dict(Iterable[KT], Mapping[KT, VT], Generic[KT, VT]):
39+
@overload
40+
def __init__(self, **kwargs: VT) -> None: pass
41+
@overload
42+
def __init__(self, arg: Iterable[Tuple[KT, VT]], **kwargs: VT) -> None: pass
43+
def __setitem__(self, k: KT, v: VT) -> None: pass
44+
def __getitem__(self, k: KT) -> VT: pass
45+
def __iter__(self) -> Iterator[KT]: pass
46+
def update(self, a: Mapping[KT, VT]) -> None: pass
47+
@overload
48+
def get(self, k: KT) -> Optional[VT]: pass
49+
@overload
50+
def get(self, k: KT, default: Union[KT, T]) -> Union[VT, T]: pass
51+
52+
53+
class int:
54+
def __float__(self) -> float: ...
55+
def __int__(self) -> int: ...
56+
def __mul__(self, x: int) -> int: ...
57+
def __rmul__(self, x: int) -> int: ...
58+
59+
class float:
60+
def __float__(self) -> float: ...
61+
def __int__(self) -> int: ...
62+
def __mul__(self, x: float) -> float: ...
63+
def __rmul__(self, x: float) -> float: ...

0 commit comments

Comments
 (0)