From aec90c26b8d20ed9f22c9add208f7850bae2cc04 Mon Sep 17 00:00:00 2001 From: Max Moroz Date: Sun, 2 Apr 2017 04:51:38 -0700 Subject: [PATCH 1/3] Treat non-inplace augmented assignment as a simple assignment --- mypy/checker.py | 10 ++++++++-- test-data/unit/check-statements.test | 7 +++++++ test-data/unit/fixtures/float.pyi | 30 ++++++++++++++++++++++++++++ 3 files changed, 45 insertions(+), 2 deletions(-) create mode 100644 test-data/unit/fixtures/float.pyi diff --git a/mypy/checker.py b/mypy/checker.py index 6c5ca121120e..620530852d11 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -1909,8 +1909,14 @@ def visit_operator_assignment_stmt(self, """Type check an operator assignment statement, e.g. x += 1.""" lvalue_type = self.expr_checker.accept(s.lvalue) inplace, method = infer_operator_assignment_method(lvalue_type, s.op) - rvalue_type, method_type = self.expr_checker.check_op( - method, lvalue_type, s.rvalue, s) + if inplace: + rvalue_type, method_type = self.expr_checker.check_op( + method, lvalue_type, s.rvalue, s) + else: + lvalue_type, index_lvalue, inferred = self.check_lvalue(s.lvalue) + expr = OpExpr(s.op, s.lvalue, s.rvalue) + expr.set_line(s) + rvalue_type = self.expr_checker.accept(expr, lvalue_type) if isinstance(s.lvalue, IndexExpr) and not inplace: self.check_indexed_assignment(s.lvalue, s.rvalue, s.rvalue) diff --git a/test-data/unit/check-statements.test b/test-data/unit/check-statements.test index 2ac3619c438c..9543b8175eba 100644 --- a/test-data/unit/check-statements.test +++ b/test-data/unit/check-statements.test @@ -1514,3 +1514,10 @@ class A(): pass class B(): pass [out] main:8: error: Incompatible types in assignment (expression has type "A", variable has type "B") + +[case testAugmentedAssignmentIntFloat] +weight0 = 65.5 +weight0 = 65 +weight0 *= 0.5 + +[builtins fixtures/float.pyi] \ No newline at end of file diff --git a/test-data/unit/fixtures/float.pyi b/test-data/unit/fixtures/float.pyi new file mode 100644 index 000000000000..2f5e5341b32f --- /dev/null +++ b/test-data/unit/fixtures/float.pyi @@ -0,0 +1,30 @@ +Any = 0 + +class object: + def __init__(self) -> None: pass + +class type: + def __init__(self, x: Any) -> None: pass + +class str: + def __add__(self, other: 'str') -> 'str': pass +class bytes: pass + +class tuple: pass +class function: pass + +class ellipsis: pass + + +class int: + def __float__(self) -> float: ... + def __int__(self) -> int: ... + def __mul__(self, x: int) -> int: ... + def __rmul__(self, x: int) -> int: ... + +class float: + def __float__(self) -> float: ... + def __int__(self) -> int: ... + def __mul__(self, x: float) -> float: ... + def __rmul__(self, x: float) -> float: ... + From e5af8b75229e5b77e19ea4c93c53370c340c5216 Mon Sep 17 00:00:00 2001 From: Max Moroz Date: Mon, 3 Apr 2017 23:01:39 -0700 Subject: [PATCH 2/3] Address Jukka CR; fix binder logic --- mypy/checker.py | 12 ++++-------- test-data/unit/check-generics.test | 1 - test-data/unit/check-statements.test | 11 ++++++++--- test-data/unit/fixtures/float.pyi | 3 ++- 4 files changed, 14 insertions(+), 13 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index 620530852d11..6451dea69733 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -1912,17 +1912,13 @@ def visit_operator_assignment_stmt(self, if inplace: rvalue_type, method_type = self.expr_checker.check_op( method, lvalue_type, s.rvalue, s) + if not is_subtype(rvalue_type, lvalue_type): + self.msg.incompatible_operator_assignment(s.op, s) else: - lvalue_type, index_lvalue, inferred = self.check_lvalue(s.lvalue) expr = OpExpr(s.op, s.lvalue, s.rvalue) expr.set_line(s) - rvalue_type = self.expr_checker.accept(expr, lvalue_type) - - if isinstance(s.lvalue, IndexExpr) and not inplace: - self.check_indexed_assignment(s.lvalue, s.rvalue, s.rvalue) - else: - if not is_subtype(rvalue_type, lvalue_type): - self.msg.incompatible_operator_assignment(s.op, s) + self.check_assignment(lvalue=s.lvalue, rvalue=expr, + infer_lvalue_type=True, new_syntax=False) def visit_assert_stmt(self, s: AssertStmt) -> None: self.expr_checker.accept(s.expr) diff --git a/test-data/unit/check-generics.test b/test-data/unit/check-generics.test index 7d035d0ed992..246f7ad90562 100644 --- a/test-data/unit/check-generics.test +++ b/test-data/unit/check-generics.test @@ -309,7 +309,6 @@ class C: def __add__(self, o: 'C') -> 'C': pass [out] main:7: error: Unsupported operand types for + ("C" and "B") -main:7: error: Incompatible types in assignment (expression has type "B", target has type "C") main:8: error: Invalid index type "C" for "A"; expected type "B" [case testOperatorAssignmentWithIndexLvalue2] diff --git a/test-data/unit/check-statements.test b/test-data/unit/check-statements.test index 9543b8175eba..23155985611a 100644 --- a/test-data/unit/check-statements.test +++ b/test-data/unit/check-statements.test @@ -226,7 +226,7 @@ class B: class C: pass [out] main:3: error: Unsupported operand types for + ("A" and "B") -main:4: error: Result type of + incompatible in assignment +main:4: error: Incompatible types in assignment (expression has type "C", variable has type "B") main:5: error: Unsupported left operand type for + ("C") [case testMinusAssign] @@ -246,7 +246,7 @@ class B: class C: pass [out] main:3: error: Unsupported operand types for - ("A" and "B") -main:4: error: Result type of - incompatible in assignment +main:4: error: Incompatible types in assignment (expression has type "C", variable has type "B") main:5: error: Unsupported left operand type for - ("C") [case testMulAssign] @@ -1517,7 +1517,12 @@ main:8: error: Incompatible types in assignment (expression has type "A", variab [case testAugmentedAssignmentIntFloat] weight0 = 65.5 +reveal_type(weight0) # E: Revealed type is 'builtins.float' weight0 = 65 +reveal_type(weight0) # E: Revealed type is 'builtins.int' +weight0 *= 'a' # E: Incompatible types in assignment (expression has type "str", variable has type "float") weight0 *= 0.5 +reveal_type(weight0) # E: Revealed type is 'builtins.float' +weight0 *= object() # E: Unsupported operand types for * ("float" and "object") -[builtins fixtures/float.pyi] \ No newline at end of file +[builtins fixtures/float.pyi] diff --git a/test-data/unit/fixtures/float.pyi b/test-data/unit/fixtures/float.pyi index 2f5e5341b32f..38bdc08a03c5 100644 --- a/test-data/unit/fixtures/float.pyi +++ b/test-data/unit/fixtures/float.pyi @@ -8,6 +8,8 @@ class type: class str: def __add__(self, other: 'str') -> 'str': pass + def __rmul__(self, n: int) -> str: ... + class bytes: pass class tuple: pass @@ -27,4 +29,3 @@ class float: def __int__(self) -> int: ... def __mul__(self, x: float) -> float: ... def __rmul__(self, x: float) -> float: ... - From 287f4156613be548b8de6e378c0bd1c01374f010 Mon Sep 17 00:00:00 2001 From: Max Moroz Date: Mon, 22 May 2017 14:25:42 -0700 Subject: [PATCH 3/3] Add comments and more tests --- mypy/checker.py | 2 + test-data/unit/check-statements.test | 31 +++++++++++++ test-data/unit/fixtures/floatdict.pyi | 63 +++++++++++++++++++++++++++ typeshed | 2 +- 4 files changed, 97 insertions(+), 1 deletion(-) create mode 100644 test-data/unit/fixtures/floatdict.pyi diff --git a/mypy/checker.py b/mypy/checker.py index 554dcdb6fa85..870c561852b6 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -1940,11 +1940,13 @@ def visit_operator_assignment_stmt(self, lvalue_type = self.expr_checker.accept(s.lvalue) inplace, method = infer_operator_assignment_method(lvalue_type, s.op) if inplace: + # There is __ifoo__, treat as x = x.__ifoo__(y) rvalue_type, method_type = self.expr_checker.check_op( method, lvalue_type, s.rvalue, s) if not is_subtype(rvalue_type, lvalue_type): self.msg.incompatible_operator_assignment(s.op, s) else: + # There is no __ifoo__, treat as x = x y expr = OpExpr(s.op, s.lvalue, s.rvalue) expr.set_line(s) self.check_assignment(lvalue=s.lvalue, rvalue=expr, diff --git a/test-data/unit/check-statements.test b/test-data/unit/check-statements.test index 144521463983..8c1f85b1d743 100644 --- a/test-data/unit/check-statements.test +++ b/test-data/unit/check-statements.test @@ -1524,5 +1524,36 @@ weight0 *= 'a' # E: Incompatible types in assignment (expression has type "str" weight0 *= 0.5 reveal_type(weight0) # E: Revealed type is 'builtins.float' weight0 *= object() # E: Unsupported operand types for * ("float" and "object") +reveal_type(weight0) # E: Revealed type is 'builtins.float' [builtins fixtures/float.pyi] + +[case testAugmentedAssignmentIntFloatMember] +class A: + def __init__(self) -> None: + self.weight0 = 65.5 + reveal_type(self.weight0) # E: Revealed type is 'builtins.float' + self.weight0 = 65 + reveal_type(self.weight0) # E: Revealed type is 'builtins.int' + self.weight0 *= 'a' # E: Incompatible types in assignment (expression has type "str", variable has type "float") + self.weight0 *= 0.5 + reveal_type(self.weight0) # E: Revealed type is 'builtins.float' + self.weight0 *= object() # E: Unsupported operand types for * ("float" and "object") + reveal_type(self.weight0) # E: Revealed type is 'builtins.float' + +[builtins fixtures/float.pyi] + +[case testAugmentedAssignmentIntFloatDict] +from typing import Dict +d = {'weight0': 65.5} +reveal_type(d['weight0']) # E: Revealed type is 'builtins.float*' +d['weight0'] = 65 +reveal_type(d['weight0']) # E: Revealed type is 'builtins.float*' +d['weight0'] *= 'a' # E: Unsupported operand types for * ("float" and "str") # E: Incompatible types in assignment (expression has type "str", target has type "float") +d['weight0'] *= 0.5 +reveal_type(d['weight0']) # E: Revealed type is 'builtins.float*' +d['weight0'] *= object() # E: Unsupported operand types for * ("float" and "object") +reveal_type(d['weight0']) # E: Revealed type is 'builtins.float*' + +[builtins fixtures/floatdict.pyi] + diff --git a/test-data/unit/fixtures/floatdict.pyi b/test-data/unit/fixtures/floatdict.pyi new file mode 100644 index 000000000000..9a34f8d369a0 --- /dev/null +++ b/test-data/unit/fixtures/floatdict.pyi @@ -0,0 +1,63 @@ +from typing import TypeVar, Generic, Iterable, Iterator, Mapping, Tuple, overload, Optional, Union + +T = TypeVar('T') +KT = TypeVar('KT') +VT = TypeVar('VT') + +Any = 0 + +class object: + def __init__(self) -> None: pass + +class type: + def __init__(self, x: Any) -> None: pass + +class str: + def __add__(self, other: 'str') -> 'str': pass + def __rmul__(self, n: int) -> str: ... + +class bytes: pass + +class tuple: pass +class function: pass + +class ellipsis: pass + +class list(Iterable[T], Generic[T]): + @overload + def __init__(self) -> None: pass + @overload + def __init__(self, x: Iterable[T]) -> None: pass + def __iter__(self) -> Iterator[T]: pass + def __add__(self, x: list[T]) -> list[T]: pass + def __mul__(self, x: int) -> list[T]: pass + def __getitem__(self, x: int) -> T: pass + def append(self, x: T) -> None: pass + def extend(self, x: Iterable[T]) -> None: pass + +class dict(Iterable[KT], Mapping[KT, VT], Generic[KT, VT]): + @overload + def __init__(self, **kwargs: VT) -> None: pass + @overload + def __init__(self, arg: Iterable[Tuple[KT, VT]], **kwargs: VT) -> None: pass + def __setitem__(self, k: KT, v: VT) -> None: pass + def __getitem__(self, k: KT) -> VT: pass + def __iter__(self) -> Iterator[KT]: pass + def update(self, a: Mapping[KT, VT]) -> None: pass + @overload + def get(self, k: KT) -> Optional[VT]: pass + @overload + def get(self, k: KT, default: Union[KT, T]) -> Union[VT, T]: pass + + +class int: + def __float__(self) -> float: ... + def __int__(self) -> int: ... + def __mul__(self, x: int) -> int: ... + def __rmul__(self, x: int) -> int: ... + +class float: + def __float__(self) -> float: ... + def __int__(self) -> int: ... + def __mul__(self, x: float) -> float: ... + def __rmul__(self, x: float) -> float: ... diff --git a/typeshed b/typeshed index 2d96eecd3008..c2e6a6f670f5 160000 --- a/typeshed +++ b/typeshed @@ -1 +1 @@ -Subproject commit 2d96eecd3008272ea656b031c9fba81f49832580 +Subproject commit c2e6a6f670f5d75c07eb31cf2509db302825b76d