Skip to content

Commit 86ec658

Browse files
committed
Merged in jwalden/mypy/unary-plus (pull request #1)
Add support for modeling unary plus, including with respect to a custom __pos__
2 parents 5c34cdb + 5ca7147 commit 86ec658

File tree

10 files changed

+67
-5
lines changed

10 files changed

+67
-5
lines changed

mypy/checkexpr.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -850,7 +850,7 @@ def check_list_multiply(self, e: OpExpr) -> Type:
850850
return result
851851

852852
def visit_unary_expr(self, e: UnaryExpr) -> Type:
853-
"""Type check an unary operation ('not', '-' or '~')."""
853+
"""Type check an unary operation ('not', '-', '+' or '~')."""
854854
operand_type = self.accept(e.expr)
855855
op = e.op
856856
if op == 'not':
@@ -861,7 +861,13 @@ def visit_unary_expr(self, e: UnaryExpr) -> Type:
861861
operand_type, e)
862862
result, method_type = self.check_call(method_type, [], [], e)
863863
e.method_type = method_type
864-
elif op == '~':
864+
elif op == '+':
865+
method_type = self.analyse_external_member_access('__pos__',
866+
operand_type, e)
867+
result, method_type = self.check_call(method_type, [], [], e)
868+
e.method_type = method_type
869+
else:
870+
assert op == '~', "unhandled unary operator"
865871
method_type = self.analyse_external_member_access('__invert__',
866872
operand_type, e)
867873
result, method_type = self.check_call(method_type, [], [], e)

mypy/icode.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -639,10 +639,12 @@ def visit_unary_expr(self, e: UnaryExpr) -> int:
639639
else:
640640
if e.op == '-':
641641
method = '__neg__'
642+
elif e.op == '+':
643+
method = '__pos__'
642644
elif e.op == '~':
643645
method = '__invert__'
644646
else:
645-
raise NotImplementedError()
647+
raise NotImplementedError("unhandled op: " + e.op)
646648
inst = cast(Instance, operand_type) # TODO more flexible
647649
self.add(CallMethod(target, operand, method, inst.type, []))
648650
return target

mypy/messages.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,9 @@ def has_no_attr(self, typ: Type, member: str, context: Context) -> Type:
230230
elif member == '__neg__':
231231
self.fail('Unsupported operand type for unary - ({})'.format(
232232
self.format(typ)), context)
233+
elif member == '__pos__':
234+
self.fail('Unsupported operand type for unary + ({})'.format(
235+
self.format(typ)), context)
233236
elif member == '__invert__':
234237
self.fail('Unsupported operand type for ~ ({})'.format(
235238
self.format(typ)), context)

mypy/test/data/check-expressions.test

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -554,6 +554,22 @@ class B:
554554
main, line 3: Incompatible types in assignment
555555
main, line 4: Unsupported operand type for unary - ("B")
556556

557+
[case testUnaryPlus]
558+
from typing import Undefined
559+
a, b = Undefined, Undefined # type: (A, B)
560+
a = +a # Fail
561+
b = +b # Fail
562+
b = +a
563+
564+
class A:
565+
def __pos__(self) -> 'B':
566+
pass
567+
class B:
568+
pass
569+
[out]
570+
main, line 3: Incompatible types in assignment
571+
main, line 4: Unsupported operand type for unary + ("B")
572+
557573
[case testUnaryNot]
558574
from typing import Undefined
559575
a, b = Undefined, Undefined # type: (A, bool)

mypy/test/data/dyncheck-trans-basic.test

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1321,7 +1321,7 @@ x = x in a
13211321
a: A = Undefined
13221322
x = {Any <= int {A x} in a}
13231323

1324-
[case testUnaryOperator]
1324+
[case testUnaryMinusOperator]
13251325
from typing import Undefined, Any
13261326
class A:
13271327
def __neg__(self) -> bool: pass
@@ -1334,6 +1334,19 @@ x = -a
13341334
a: A = Undefined
13351335
x = {Any <= bool -a}
13361336

1337+
[case testUnaryPlusOperator]
1338+
from typing import Undefined, Any
1339+
class A:
1340+
def __pos__(self) -> bool: pass
1341+
x = Undefined # type: Any
1342+
a = Undefined # type: A
1343+
x = +a
1344+
[builtins fixtures/ops.py]
1345+
[out]
1346+
...
1347+
a: A = Undefined
1348+
x = {Any <= bool +a}
1349+
13371350
[case testTransformBinaryOpOperands]
13381351
from typing import Any, Undefined
13391352
class A:
@@ -1358,7 +1371,7 @@ a[f(1)]
13581371
a: A = Undefined
13591372
a[f({Any <= int 1})]
13601373

1361-
[case testTransformUnaryOperand]
1374+
[case testTransformUnaryMinusOperand]
13621375
from typing import Any
13631376
class A:
13641377
def __neg__(self) -> int: pass
@@ -1367,3 +1380,13 @@ def f(x: Any) -> A: pass
13671380
[out]
13681381
...
13691382
-f({Any <= int 1})
1383+
1384+
[case testTransformUnaryPlusOperand]
1385+
from typing import Any
1386+
class A:
1387+
def __pos__(self) -> int: pass
1388+
def f(x: Any) -> A: pass
1389+
+f(1)
1390+
[out]
1391+
...
1392+
+f({Any <= int 1})

mypy/test/data/fixtures/icodegen.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ def __add__(self, n: int) -> int: pass
1919
def __sub__(self, n: int) -> int: pass
2020
def __mul__(self, n: int) -> int: pass
2121
def __neg__(self) -> int: pass
22+
def __pos__(self) -> int: pass
2223
def __eq__(self, n: int) -> bool: pass
2324
def __ne__(self, n: int) -> bool: pass
2425
def __lt__(self, n: int) -> bool: pass

mypy/test/data/fixtures/ops.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ def __sub__(self, x: 'int') -> 'int': pass
2222
def __mul__(self, x: 'int') -> 'int': pass
2323
def __mod__(self, x: 'int') -> 'int': pass
2424
def __floordiv__(self, x: 'int') -> 'int': pass
25+
def __pos__(self) -> 'int': pass
2526
def __neg__(self) -> 'int': pass
2627
def __eq__(self, x: object) -> bool: pass
2728
def __ne__(self, x: object) -> bool: pass

mypy/test/data/icode-basic.test

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -531,15 +531,21 @@ def f:
531531
import typing
532532
class A:
533533
def __neg__(self) -> int: pass
534+
def __pos__(self) -> int: pass
534535
def __invert__(self) -> int: pass
535536
def f(a: A) -> int:
536537
return -a
537538
def g(a: A) -> int:
539+
return +a
540+
def h(a: A) -> int:
538541
return ~a
539542
[out]
540543
def f:
541544
r1 = r0.__neg__() [A]
542545
return r1
543546
def g:
547+
r1 = r0.__pos__() [A]
548+
return r1
549+
def h:
544550
r1 = r0.__invert__() [A]
545551
return r1

stubs/2.7/builtins.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ def __rlshift__(self, n: int) -> int: pass
101101
def __rrshift__(self, n: int) -> int: pass
102102

103103
def __neg__(self) -> int: pass
104+
def __pos__(self) -> int: pass
104105
def __invert__(self) -> int: pass
105106

106107
def __eq__(self, x: object) -> bool: pass
@@ -159,6 +160,7 @@ def __le__(self, x: float) -> bool: pass
159160
def __gt__(self, x: float) -> bool: pass
160161
def __ge__(self, x: float) -> bool: pass
161162
def __neg__(self) -> float: pass
163+
def __pos__(self) -> float: pass
162164

163165
def __str__(self) -> str: pass
164166
def __int__(self) -> int: pass

stubs/3.2/builtins.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ def __rlshift__(self, n: int) -> int: pass
114114
def __rrshift__(self, n: int) -> int: pass
115115

116116
def __neg__(self) -> int: pass
117+
def __pos__(self) -> int: pass
117118
def __invert__(self) -> int: pass
118119

119120
def __eq__(self, x: object) -> bool: pass
@@ -179,6 +180,7 @@ def __le__(self, x: float) -> bool: pass
179180
def __gt__(self, x: float) -> bool: pass
180181
def __ge__(self, x: float) -> bool: pass
181182
def __neg__(self) -> float: pass
183+
def __pos__(self) -> float: pass
182184

183185
def __str__(self) -> str: pass
184186
def __int__(self) -> int: pass

0 commit comments

Comments
 (0)