Skip to content

Commit 92e3b3e

Browse files
elazarggvanrossum
authored andcommitted
Display full type of left operand in error messages (#3147)
Remember the bound type of the self argument, allowing more consistent error messages. For example, previously left and right operands are displayed differntly: ``` # tmp.py class A: def __add__(self, other: A): return self a = A() (a, a) + a a + (a, a) ``` Errors: ``` tmp.py:3: error: Unsupported operand types for + ("tuple" and "A") tmp.py:5: error: Unsupported operand types for + ("A" and "Tuple[A, A]") ``` Left and right operands were not treated the same way. The new output is: ``` tmp.py:4: error: Unsupported operand types for + ("Tuple[A, A]" and "A") tmp.py:5: error: Unsupported operand types for + ("A" and "Tuple[A, A]") ```
1 parent 95c30f5 commit 92e3b3e

9 files changed

+34
-18
lines changed

mypy/checkmember.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -632,11 +632,14 @@ def expand(target: Type) -> Type:
632632
arg_types = func.arg_types[1:]
633633
ret_type = func.ret_type
634634
variables = func.variables
635+
if isinstance(original_type, CallableType) and original_type.is_type_obj():
636+
original_type = TypeType(original_type.ret_type)
635637
res = func.copy_modified(arg_types=arg_types,
636638
arg_kinds=func.arg_kinds[1:],
637639
arg_names=func.arg_names[1:],
638640
variables=variables,
639-
ret_type=ret_type)
641+
ret_type=ret_type,
642+
bound_args=[original_type])
640643
return cast(F, res)
641644

642645

mypy/messages.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -485,7 +485,10 @@ def incompatible_argument(self, n: int, m: int, callee: CallableType, arg_type:
485485
target = ''
486486
if callee.name:
487487
name = callee.name
488-
base = extract_type(name)
488+
if callee.bound_args and callee.bound_args[0] is not None:
489+
base = self.format(callee.bound_args[0])
490+
else:
491+
base = extract_type(name)
489492

490493
for op, method in op_methods.items():
491494
for variant in method, '__r' + method[2:]:

mypy/types.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -534,6 +534,8 @@ class CallableType(FunctionLike):
534534
# Was this callable generated by analyzing Type[...] instantiation?
535535
from_type_type = False # type: bool
536536

537+
bound_args = None # type: List[Type]
538+
537539
def __init__(self,
538540
arg_types: List[Type],
539541
arg_kinds: List[int],
@@ -550,6 +552,7 @@ def __init__(self,
550552
is_classmethod_class: bool = False,
551553
special_sig: Optional[str] = None,
552554
from_type_type: bool = False,
555+
bound_args: List[Type] = None,
553556
) -> None:
554557
if variables is None:
555558
variables = []
@@ -571,6 +574,7 @@ def __init__(self,
571574
self.is_classmethod_class = is_classmethod_class
572575
self.special_sig = special_sig
573576
self.from_type_type = from_type_type
577+
self.bound_args = bound_args or []
574578
super().__init__(line, column)
575579

576580
def copy_modified(self,
@@ -586,7 +590,8 @@ def copy_modified(self,
586590
column: int = _dummy,
587591
is_ellipsis_args: bool = _dummy,
588592
special_sig: Optional[str] = _dummy,
589-
from_type_type: bool = _dummy) -> 'CallableType':
593+
from_type_type: bool = _dummy,
594+
bound_args: List[Type] = _dummy) -> 'CallableType':
590595
return CallableType(
591596
arg_types=arg_types if arg_types is not _dummy else self.arg_types,
592597
arg_kinds=arg_kinds if arg_kinds is not _dummy else self.arg_kinds,
@@ -604,6 +609,7 @@ def copy_modified(self,
604609
is_classmethod_class=self.is_classmethod_class,
605610
special_sig=special_sig if special_sig is not _dummy else self.special_sig,
606611
from_type_type=from_type_type if from_type_type is not _dummy else self.from_type_type,
612+
bound_args=bound_args if bound_args is not _dummy else self.bound_args,
607613
)
608614

609615
def is_type_obj(self) -> bool:
@@ -739,6 +745,8 @@ def serialize(self) -> JsonDict:
739745
'is_ellipsis_args': self.is_ellipsis_args,
740746
'implicit': self.implicit,
741747
'is_classmethod_class': self.is_classmethod_class,
748+
'bound_args': [(None if t is None else t.serialize())
749+
for t in self.bound_args],
742750
}
743751

744752
@classmethod
@@ -756,6 +764,8 @@ def deserialize(cls, data: JsonDict) -> 'CallableType':
756764
is_ellipsis_args=data['is_ellipsis_args'],
757765
implicit=data['implicit'],
758766
is_classmethod_class=data['is_classmethod_class'],
767+
bound_args=[(None if t is None else deserialize_type(t))
768+
for t in data['bound_args']],
759769
)
760770

761771

test-data/unit/check-classes.test

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3038,7 +3038,7 @@ class Concrete(metaclass=Meta):
30383038
pass
30393039

30403040
reveal_type(Concrete + X()) # E: Revealed type is 'builtins.str'
3041-
Concrete + "hello" # E: Unsupported operand types for + ("Meta" and "str")
3041+
Concrete + "hello" # E: Unsupported operand types for + (Type[Concrete] and "str")
30423042

30433043
[case testMetaclassGetitem]
30443044
class M(type):

test-data/unit/check-generics.test

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -284,9 +284,9 @@ class B: pass
284284
class C: pass
285285
[out]
286286
main:8: error: Incompatible types in assignment (expression has type "C", variable has type "B")
287-
main:9: error: Unsupported operand types for + ("A" and "C")
287+
main:9: error: Unsupported operand types for + (A[B, C] and "C")
288288
main:10: error: Incompatible types in assignment (expression has type "B", variable has type "C")
289-
main:11: error: Invalid index type "B" for "A"; expected type "C"
289+
main:11: error: Invalid index type "B" for A[B, C]; expected type "C"
290290

291291
[case testOperatorAssignmentWithIndexLvalue1]
292292
from typing import TypeVar, Generic
@@ -310,7 +310,7 @@ class C:
310310
[out]
311311
main:7: error: Unsupported operand types for + ("C" and "B")
312312
main:7: error: Incompatible types in assignment (expression has type "B", target has type "C")
313-
main:8: error: Invalid index type "C" for "A"; expected type "B"
313+
main:8: error: Invalid index type "C" for A[C]; expected type "B"
314314

315315
[case testOperatorAssignmentWithIndexLvalue2]
316316
from typing import TypeVar, Generic
@@ -331,9 +331,9 @@ class B: pass
331331
class C:
332332
def __add__(self, o: 'C') -> 'C': pass
333333
[out]
334-
main:7: error: Invalid index type "B" for "A"; expected type "C"
335-
main:8: error: Invalid index type "C" for "A"; expected type "B"
336-
main:9: error: Invalid index type "B" for "A"; expected type "C"
334+
main:7: error: Invalid index type "B" for A[C]; expected type "C"
335+
main:8: error: Invalid index type "C" for A[C]; expected type "B"
336+
main:9: error: Invalid index type "B" for A[C]; expected type "C"
337337

338338

339339
-- Nested generic types

test-data/unit/check-isinstance.test

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -679,7 +679,7 @@ while bool():
679679
x + 'a'
680680
break
681681
x + [1]
682-
x + 'a' # E: Unsupported operand types for + ("list" and "str")
682+
x + 'a' # E: Unsupported operand types for + (List[int] and "str")
683683
x + [1] # E: Unsupported operand types for + (likely involving Union)
684684

685685
[builtins fixtures/isinstancelist.pyi]

test-data/unit/check-newsyntax.test

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ from typing import Dict, Any
2929
d: Dict[int, str] = {}
3030
d[42] = 'ab'
3131
d[42] = 42 # E: Incompatible types in assignment (expression has type "int", target has type "str")
32-
d['ab'] = 'ab' # E: Invalid index type "str" for "dict"; expected type "int"
32+
d['ab'] = 'ab' # E: Invalid index type "str" for Dict[int, str]; expected type "int"
3333
[builtins fixtures/dict.pyi]
3434
[out]
3535

test-data/unit/check-optional.test

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,7 @@ x = {None: None}
196196
x["bar"] = 1
197197
[builtins fixtures/dict.pyi]
198198
[out]
199-
main:2: error: Invalid index type "str" for "dict"; expected type None
199+
main:2: error: Invalid index type "str" for Dict[None, None]; expected type None
200200
main:2: error: Incompatible types in assignment (expression has type "int", target has type None)
201201

202202
[case testInferNonOptionalDictType]

test-data/unit/pythoneval.test

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -577,7 +577,7 @@ print(tuple(a))
577577
import typing
578578
[1] + iter([2, 3])
579579
[out]
580-
_program.py:2: error: Unsupported operand types for + ("list" and Iterator[int])
580+
_program.py:2: error: Unsupported operand types for + (List[int] and Iterator[int])
581581

582582
[case testInferHeterogeneousListOfIterables]
583583
from typing import Sequence
@@ -1095,10 +1095,10 @@ MyDDict(dict)['0']
10951095
MyDDict(dict)[0]
10961096
[out]
10971097
_program.py:6: error: Argument 1 to "defaultdict" has incompatible type List[_T]; expected Callable[[], str]
1098-
_program.py:9: error: Invalid index type "str" for "dict"; expected type "int"
1098+
_program.py:9: error: Invalid index type "str" for defaultdict[int, str]; expected type "int"
10991099
_program.py:9: error: Incompatible types in assignment (expression has type "int", target has type "str")
11001100
_program.py:19: error: Dict entry 0 has incompatible type "str": List[<uninhabited>]
1101-
_program.py:23: error: Invalid index type "str" for "dict"; expected type "int"
1101+
_program.py:23: error: Invalid index type "str" for MyDDict[Dict[_KT, _VT]]; expected type "int"
11021102

11031103
[case testNoSubcriptionOfStdlibCollections]
11041104
import collections
@@ -1120,7 +1120,7 @@ def f(d: collections.defaultdict[int, str]) -> None:
11201120
_program.py:5: error: "defaultdict" is not subscriptable
11211121
_program.py:6: error: "Counter" is not subscriptable
11221122
_program.py:9: error: "defaultdict" is not subscriptable
1123-
_program.py:12: error: Invalid index type "int" for "dict"; expected type "str"
1123+
_program.py:12: error: Invalid index type "int" for defaultdict[str, int]; expected type "str"
11241124
_program.py:14: error: "defaultdict" is not subscriptable, use "typing.DefaultDict" instead
11251125

11261126
[case testCollectionsAliases]
@@ -1148,7 +1148,7 @@ reveal_type(o6)
11481148

11491149
[out]
11501150
_testCollectionsAliases.py:5: error: Revealed type is 'collections.Counter[builtins.int]'
1151-
_testCollectionsAliases.py:6: error: Invalid index type "str" for "dict"; expected type "int"
1151+
_testCollectionsAliases.py:6: error: Invalid index type "str" for Counter[int]; expected type "int"
11521152
_testCollectionsAliases.py:9: error: Revealed type is 'collections.ChainMap[builtins.int, builtins.str]'
11531153
_testCollectionsAliases.py:12: error: Revealed type is 'collections.deque[builtins.int]'
11541154
_testCollectionsAliases.py:15: error: Revealed type is 'collections.Counter[builtins.int*]'

0 commit comments

Comments
 (0)