Skip to content

Commit

Permalink
Add expected type in incompatible arg errors (#3515)
Browse files Browse the repository at this point in the history
* Add expected type in incompatible arg errors

* Update pythoneval tests

* Use format instead of format_simple

* Use is_same_type for dict incompatibility checks

* Add tests for dict's invariance

* Revert "Use is_same_type for dict incompatibility checks"

This reverts commit 5a0903d.

* Add test for dict's verbosity

* Duplicate test for dict keys as well
  • Loading branch information
miedzinski authored and ilevkivskyi committed Jul 11, 2017
1 parent b13699a commit c8bfb65
Show file tree
Hide file tree
Showing 17 changed files with 113 additions and 55 deletions.
54 changes: 42 additions & 12 deletions mypy/messages.py
Expand Up @@ -543,29 +543,59 @@ def incompatible_argument(self, n: int, m: int, callee: CallableType, arg_type:
if callee.name == '<list>':
name = callee.name[1:-1]
n -= 1
msg = '{} item {} has incompatible type {}'.format(
name.title(), n, self.format(arg_type))
actual_type_str, expected_type_str = self.format_distinctly(arg_type,
callee.arg_types[0])
msg = '{} item {} has incompatible type {}; expected {}'.format(
name.title(), n, actual_type_str, expected_type_str)
elif callee.name == '<dict>':
name = callee.name[1:-1]
n -= 1
key_type, value_type = cast(TupleType, arg_type).items
msg = '{} entry {} has incompatible type {}: {}'.format(
name.title(), n, self.format(key_type), self.format(value_type))
expected_key_type, expected_value_type = cast(TupleType, callee.arg_types[0]).items

# don't increase verbosity unless there is need to do so
from mypy.subtypes import is_subtype
if is_subtype(key_type, expected_key_type):
key_type_str = self.format(key_type)
expected_key_type_str = self.format(expected_key_type)
else:
key_type_str, expected_key_type_str = self.format_distinctly(
key_type, expected_key_type)
if is_subtype(value_type, expected_value_type):
value_type_str = self.format(value_type)
expected_value_type_str = self.format(expected_value_type)
else:
value_type_str, expected_value_type_str = self.format_distinctly(
value_type, expected_value_type)

msg = '{} entry {} has incompatible type {}: {}; expected {}: {}'.format(
name.title(), n, key_type_str, value_type_str,
expected_key_type_str, expected_value_type_str)
elif callee.name == '<list-comprehension>':
msg = 'List comprehension has incompatible type List[{}]'.format(
strip_quotes(self.format(arg_type)))
actual_type_str, expected_type_str = map(strip_quotes,
self.format_distinctly(arg_type,
callee.arg_types[0]))
msg = 'List comprehension has incompatible type List[{}]; expected List[{}]'.format(
actual_type_str, expected_type_str)
elif callee.name == '<set-comprehension>':
msg = 'Set comprehension has incompatible type Set[{}]'.format(
strip_quotes(self.format(arg_type)))
actual_type_str, expected_type_str = map(strip_quotes,
self.format_distinctly(arg_type,
callee.arg_types[0]))
msg = 'Set comprehension has incompatible type Set[{}]; expected Set[{}]'.format(
actual_type_str, expected_type_str)
elif callee.name == '<dictionary-comprehension>':
actual_type_str, expected_type_str = self.format_distinctly(arg_type,
callee.arg_types[n - 1])
msg = ('{} expression in dictionary comprehension has incompatible type {}; '
'expected type {}').format(
'Key' if n == 1 else 'Value',
self.format(arg_type),
self.format(callee.arg_types[n - 1]))
actual_type_str,
expected_type_str)
elif callee.name == '<generator>':
msg = 'Generator has incompatible item type {}'.format(
self.format(arg_type))
actual_type_str, expected_type_str = self.format_distinctly(arg_type,
callee.arg_types[0])
msg = 'Generator has incompatible item type {}; expected {}'.format(
actual_type_str, expected_type_str)
else:
try:
expected_type = callee.arg_types[m - 1]
Expand Down
2 changes: 1 addition & 1 deletion test-data/unit/check-class-namedtuple.test
Expand Up @@ -416,7 +416,7 @@ class Parameterized(NamedTuple):
z: List[int] = []

reveal_type(Parameterized(1)) # E: Revealed type is 'Tuple[builtins.int, builtins.list[builtins.int], builtins.list[builtins.int], fallback=__main__.Parameterized]'
Parameterized(1, ['not an int']) # E: List item 0 has incompatible type "str"
Parameterized(1, ['not an int']) # E: List item 0 has incompatible type "str"; expected "int"

class Default:
pass
Expand Down
2 changes: 1 addition & 1 deletion test-data/unit/check-classes.test
Expand Up @@ -2847,7 +2847,7 @@ class B(A[int]):
b = ['']
[builtins fixtures/list.pyi]
[out]
main:8: error: List item 0 has incompatible type "str"
main:8: error: List item 0 has incompatible type "str"; expected "int"

[case testVariableMethod]
class A:
Expand Down
2 changes: 1 addition & 1 deletion test-data/unit/check-classvar.test
Expand Up @@ -151,7 +151,7 @@ A().x.append(1)
A().x.append('')
[builtins fixtures/list.pyi]
[out]
main:4: error: List item 0 has incompatible type "str"
main:4: error: List item 0 has incompatible type "str"; expected "int"
main:6: error: Argument 1 to "append" of "list" has incompatible type "str"; expected "int"

[case testClassVarWithUnion]
Expand Down
54 changes: 41 additions & 13 deletions test-data/unit/check-expressions.test
Expand Up @@ -1231,7 +1231,7 @@ fun(lambda: (yield from [1])) # E: Incompatible types in "yield from" (actual t
from typing import List
a = None # type: List[A]
a = [x for x in a]
b = [x for x in a] # type: List[B] # E: List comprehension has incompatible type List[A]
b = [x for x in a] # type: List[B] # E: List comprehension has incompatible type List[A]; expected List[B]
class A: pass
class B: pass
[builtins fixtures/for.pyi]
Expand All @@ -1240,7 +1240,7 @@ class B: pass
from typing import List, Tuple
l = None # type: List[Tuple[A, Tuple[A, B]]]
a = [a2 for a1, (a2, b1) in l] # type: List[A]
b = [a2 for a1, (a2, b1) in l] # type: List[B] # E: List comprehension has incompatible type List[A]
b = [a2 for a1, (a2, b1) in l] # type: List[B] # E: List comprehension has incompatible type List[A]; expected List[B]
class A: pass
class B: pass
[builtins fixtures/for.pyi]
Expand All @@ -1259,7 +1259,7 @@ from typing import List
a = None # type: List[A]
b = None # type: List[B]
b = [f(x) for x in a]
a = [f(x) for x in a] # E: List comprehension has incompatible type List[B]
a = [f(x) for x in a] # E: List comprehension has incompatible type List[B]; expected List[A]
([f(x) for x in b]) # E: Argument 1 to "f" has incompatible type "B"; expected "A"
class A: pass
class B: pass
Expand All @@ -1285,7 +1285,7 @@ from typing import List
class A:
a = None # type: List[A]
a = [x for x in a]
b = [x for x in a] # type: List[B] # E: List comprehension has incompatible type List[A]
b = [x for x in a] # type: List[B] # E: List comprehension has incompatible type List[A]; expected List[B]
class B: pass
[builtins fixtures/for.pyi]
[out]
Expand All @@ -1299,7 +1299,7 @@ class B: pass
from typing import Set
a = None # type: Set[A]
a = {x for x in a}
b = {x for x in a} # type: Set[B] # E: Set comprehension has incompatible type Set[A]
b = {x for x in a} # type: Set[B] # E: Set comprehension has incompatible type Set[A]; expected Set[B]
class A: pass
class B: pass
[builtins fixtures/set.pyi]
Expand Down Expand Up @@ -1351,15 +1351,15 @@ from typing import Iterator
a = None # type: Iterator[int]
a = (x for x in a)
b = None # type: Iterator[str]
b = (x for x in a) # E: Generator has incompatible item type "int"
b = (x for x in a) # E: Generator has incompatible item type "int"; expected "str"
[builtins fixtures/for.pyi]

[case testGeneratorIncompatibleErrorMessage]
from typing import Callable, Iterator, List

a = [] # type: List[Callable[[], str]]
b = None # type: Iterator[Callable[[], int]]
b = (x for x in a) # E: Generator has incompatible item type Callable[[], str]
b = (x for x in a) # E: Generator has incompatible item type Callable[[], str]; expected Callable[[], int]
[builtins fixtures/list.pyi]

-- Conditional expressions
Expand Down Expand Up @@ -1394,7 +1394,7 @@ y = '' # E: Incompatible types in assignment (expression has type "str", variabl
import typing
x = [1] if bool() else []
x = [1]
x = ['x'] # E: List item 0 has incompatible type "str"
x = ['x'] # E: List item 0 has incompatible type "str"; expected "int"
[builtins fixtures/list.pyi]


Expand Down Expand Up @@ -1550,8 +1550,8 @@ def g() -> Iterator[int]:
[case testDictWithKeywordArgsOnly]
from typing import Dict, Any
d1 = dict(a=1, b=2) # type: Dict[str, int]
d2 = dict(a=1, b='') # type: Dict[str, int] # E: Dict entry 1 has incompatible type "str": "str"
d3 = dict(a=1) # type: Dict[int, int] # E: Dict entry 0 has incompatible type "str": "int"
d2 = dict(a=1, b='') # type: Dict[str, int] # E: Dict entry 1 has incompatible type "str": "str"; expected "str": "int"
d3 = dict(a=1) # type: Dict[int, int] # E: Dict entry 0 has incompatible type "str": "int"; expected "int": "int"
d4 = dict(a=1, b=1)
d4.xyz # E: Dict[str, int] has no attribute "xyz"
d5 = dict(a=1, b='') # type: Dict[str, Any]
Expand All @@ -1568,7 +1568,7 @@ dict(undefined) # E: Name 'undefined' is not defined
from typing import Dict
d = dict([(1, 'x'), (2, 'y')])
d() # E: Dict[int, str] not callable
d2 = dict([(1, 'x')]) # type: Dict[str, str] # E: List item 0 has incompatible type "Tuple[int, str]"
d2 = dict([(1, 'x')]) # type: Dict[str, str] # E: List item 0 has incompatible type "Tuple[int, str]"; expected "Tuple[str, str]"
[builtins fixtures/dict.pyi]

[case testDictFromIterableAndKeywordArg]
Expand Down Expand Up @@ -1701,7 +1701,7 @@ b = {'z': 26, **a}
c = {**b}
d = {**a, **b, 'c': 3}
e = {1: 'a', **a} # E: Argument 1 to "update" of "dict" has incompatible type Dict[str, int]; expected Mapping[int, str]
f = {**b} # type: Dict[int, int] # E: List item 0 has incompatible type Dict[str, int]
f = {**b} # type: Dict[int, int] # E: List item 0 has incompatible type Dict[str, int]; expected Mapping[int, int]
[builtins fixtures/dict.pyi]

[case testDictIncompatibleTypeErrorMessage]
Expand All @@ -1710,11 +1710,39 @@ from typing import Dict, Callable
def things() -> int:
return 42

stuff: Dict[int, Callable[[], str]] = { # E: Dict entry 0 has incompatible type "int": Callable[[], int]
stuff: Dict[int, Callable[[], str]] = { # E: Dict entry 0 has incompatible type "int": Callable[[], int]; expected "int": Callable[[], str]
1: things
}
[builtins fixtures/dict.pyi]

[case testDictIncompatibleKeyVerbosity]
from typing import Dict
import mod

class A: ...
class B(A): ...

d: Dict[A, B] = {A(): mod.B()} # E: Dict entry 0 has incompatible type "A": "mod.B"; expected "A": "__main__.B"

[file mod.py]
class B: ...

[builtins fixtures/dict.pyi]

[case testDictIncompatibleValueVerbosity]
from typing import Dict
import mod

class A: ...
class B(A): ...

d: Dict[B, A] = {mod.B(): A()} # E: Dict entry 0 has incompatible type "mod.B": "A"; expected "__main__.B": "A"

[file mod.py]
class B: ...

[builtins fixtures/dict.pyi]

-- Type checker default plugin
-- ---------------------------

Expand Down
4 changes: 2 additions & 2 deletions test-data/unit/check-functions.test
Expand Up @@ -335,10 +335,10 @@ def f(x: C) -> C: pass
from typing import Any, Callable, List
def f(fields: List[Callable[[Any], Any]]): pass
class C: pass
f([C]) # E: List item 0 has incompatible type Type[C]
f([C]) # E: List item 0 has incompatible type Type[C]; expected Callable[[Any], Any]
class D:
def __init__(self, a, b): pass
f([D]) # E: List item 0 has incompatible type Type[D]
f([D]) # E: List item 0 has incompatible type Type[D]; expected Callable[[Any], Any]
[builtins fixtures/list.pyi]

[case testSubtypingTypeTypeAsCallable]
Expand Down
4 changes: 2 additions & 2 deletions test-data/unit/check-generics.test
Expand Up @@ -1108,7 +1108,7 @@ from m import Alias

n = Alias[int]([1])
reveal_type(n) # E: Revealed type is 'm.Node[builtins.list*[builtins.int]]'
bad = Alias[str]([1]) # E: List item 0 has incompatible type "int"
bad = Alias[str]([1]) # E: List item 0 has incompatible type "int"; expected "str"

n2 = Alias([1]) # Same as Node[List[Any]]
reveal_type(n2) # E: Revealed type is 'm.Node[builtins.list*[Any]]'
Expand Down Expand Up @@ -1390,7 +1390,7 @@ def f(a: List[A]) -> A: pass
def f(a: B) -> B: pass

b = f([a]) # E: Incompatible types in assignment (expression has type "A", variable has type "B")
a = f([b]) # E: List item 0 has incompatible type "B"
a = f([b]) # E: List item 0 has incompatible type "B"; expected "A"
a = f(b) # E: Incompatible types in assignment (expression has type "B", variable has type "A")

a = f([a])
Expand Down
22 changes: 11 additions & 11 deletions test-data/unit/check-inference-context.test
Expand Up @@ -350,8 +350,8 @@ ao = None # type: List[object]
a = None # type: A
b = None # type: B

aa = [b] # E: List item 0 has incompatible type "B"
ab = [a] # E: List item 0 has incompatible type "A"
aa = [b] # E: List item 0 has incompatible type "B"; expected "A"
ab = [a] # E: List item 0 has incompatible type "A"; expected "B"

aa = [a]
ab = [b]
Expand All @@ -371,8 +371,8 @@ ao = None # type: List[object]
a = None # type: A
b = None # type: B

ab = [b, a] # E: List item 1 has incompatible type "A"
ab = [a, b] # E: List item 0 has incompatible type "A"
ab = [b, a] # E: List item 1 has incompatible type "A"; expected "B"
ab = [a, b] # E: List item 0 has incompatible type "A"; expected "B"

aa = [a, b, a]
ao = [a, b]
Expand All @@ -387,7 +387,7 @@ def f() -> None:
a = [] # E: Need type annotation for variable
b = [None]
c = [B()]
c = [object()] # E: List item 0 has incompatible type "object"
c = [object()] # E: List item 0 has incompatible type "object"; expected "B"
c = [B()]
class B: pass
[builtins fixtures/list.pyi]
Expand All @@ -401,8 +401,8 @@ ab = None # type: List[B]
b = None # type: B
o = None # type: object

aao = [[o], ab] # E: List item 1 has incompatible type List[B]
aab = [[], [o]] # E: List item 0 has incompatible type "object"
aao = [[o], ab] # E: List item 1 has incompatible type List[B]; expected List[object]
aab = [[], [o]] # E: List item 0 has incompatible type "object"; expected "B"

aao = [[None], [b], [], [o]]
aab = [[None], [b], []]
Expand Down Expand Up @@ -529,7 +529,7 @@ class set(Generic[t]):
def __init__(self, iterable: Iterable[t]) -> None: pass
b = bool()
l = set([b])
l = set([object()]) # E: List item 0 has incompatible type "object"
l = set([object()]) # E: List item 0 has incompatible type "object"; expected "bool"
[builtins fixtures/for.pyi]


Expand Down Expand Up @@ -564,7 +564,7 @@ class B:
from typing import List, Callable
f = None # type: Callable[[], List[A]]
f = lambda: []
f = lambda: [B()] # E: List item 0 has incompatible type "B"
f = lambda: [B()] # E: List item 0 has incompatible type "B"; expected "A"
class A: pass
class B: pass
[builtins fixtures/list.pyi]
Expand Down Expand Up @@ -743,14 +743,14 @@ class A:
a = A()
a.x = []
a.x = [1]
a.x = [''] # E: List item 0 has incompatible type "str"
a.x = [''] # E: List item 0 has incompatible type "str"; expected "int"
[builtins fixtures/list.pyi]

[case testListMultiplyInContext]
from typing import List
a = None # type: List[int]
a = [None] * 3
a = [''] * 3 # E: List item 0 has incompatible type "str"
a = [''] * 3 # E: List item 0 has incompatible type "str"; expected "int"
[builtins fixtures/list.pyi]

[case testUnionTypeContext]
Expand Down
2 changes: 1 addition & 1 deletion test-data/unit/check-modules.test
Expand Up @@ -1360,7 +1360,7 @@ def do_something(dic: Row) -> None:
def do_another() -> Row:
return {}

do_something({'good': 'bad'}) # E: Dict entry 0 has incompatible type "str": "str"
do_something({'good': 'bad'}) # E: Dict entry 0 has incompatible type "str": "str"; expected "str": "int"
reveal_type(do_another()) # E: Revealed type is 'builtins.dict[builtins.str, builtins.int]'

[file ex2a.py]
Expand Down
2 changes: 1 addition & 1 deletion test-data/unit/check-newtype.test
Expand Up @@ -55,7 +55,7 @@ from typing import NewType, List
UserId = NewType('UserId', int)
IdList = NewType('IdList', List[UserId])

bad1 = IdList([1]) # E: List item 0 has incompatible type "int"
bad1 = IdList([1]) # E: List item 0 has incompatible type "int"; expected "UserId"

foo = IdList([])
foo.append(3) # E: Argument 1 to "append" of "list" has incompatible type "int"; expected "UserId"
Expand Down
4 changes: 2 additions & 2 deletions test-data/unit/check-optional.test
Expand Up @@ -455,7 +455,7 @@ reveal_type(l) # E: Revealed type is 'builtins.list[typing.Generator*[builtins.
[builtins fixtures/list.pyi]

[case testNoneListTernary]
x = [None] if "" else [1] # E: List item 0 has incompatible type "int"
x = [None] if "" else [1] # E: List item 0 has incompatible type "int"; expected None
[builtins fixtures/list.pyi]

[case testListIncompatibleErrorMessage]
Expand All @@ -465,7 +465,7 @@ def foo(l: List[Callable[[], str]]) -> None: pass
def f() -> int:
return 42

foo([f]) # E: List item 0 has incompatible type Callable[[], int]
foo([f]) # E: List item 0 has incompatible type Callable[[], int]; expected Callable[[], str]
[builtins fixtures/list.pyi]

[case testInferEqualsNotOptional]
Expand Down
2 changes: 1 addition & 1 deletion test-data/unit/check-overloading.test
Expand Up @@ -901,7 +901,7 @@ def f(x: int, y: List[int] = None) -> int: pass
def f(x: int, y: List[str] = None) -> int: pass
f(y=[1], x=0)() # E: "int" not callable
f(y=[''], x=0)() # E: "int" not callable
a = f(y=[['']], x=0) # E: List item 0 has incompatible type List[str]
a = f(y=[['']], x=0) # E: List item 0 has incompatible type List[str]; expected "int"
a() # E: "int" not callable
[builtins fixtures/list.pyi]

Expand Down

1 comment on commit c8bfb65

@gvanrossum
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Quick note on the commit message: core mypy devs, please edit the commit message when merging a multi-commit PR, so the commit message doesn't contain the local history from the committer and the review history from the PR. Those are not useful when reading through history of master (and in this case I was confused by the mention of something that was reverted -- it was a previous local commit).

Please sign in to comment.