Skip to content

Commit 46f4e23

Browse files
committed
Improve verbose formatting to disambiguate types of the same name.
Previously, foo.Bar and baz.Bar would both be formatted as "Bar" in error messages, which causes confusing errors like the following: error: Argument 2 to "quux" has incompatible type "Bar"; expected "Bar"
1 parent b32ab6a commit 46f4e23

File tree

3 files changed

+30
-18
lines changed

3 files changed

+30
-18
lines changed

mypy/checker.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1916,11 +1916,12 @@ def check_subtype(self, subtype: Type, supertype: Type, context: Context,
19161916
self.msg.does_not_return_value(subtype, context)
19171917
else:
19181918
extra_info = [] # type: List[str]
1919-
if subtype_label is not None:
1920-
extra_info.append(subtype_label + ' ' + self.msg.format(subtype, verbose=True))
1921-
if supertype_label is not None:
1922-
extra_info.append(supertype_label + ' ' + self.msg.format(supertype,
1923-
verbose=True))
1919+
if subtype_label is not None or supertype_label is not None:
1920+
subtype_str, supertype_str = self.msg.format_distinctly(subtype, supertype)
1921+
if subtype_label is not None:
1922+
extra_info.append(subtype_label + ' ' + subtype_str)
1923+
if supertype_label is not None:
1924+
extra_info.append(supertype_label + ' ' + supertype_str)
19241925
if extra_info:
19251926
msg += ' (' + ', '.join(extra_info) + ')'
19261927
self.fail(msg, context)

mypy/messages.py

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import re
77
import difflib
88

9-
from typing import cast, List, Dict, Any, Sequence, Iterable
9+
from typing import cast, List, Dict, Any, Sequence, Iterable, Tuple
1010

1111
from mypy.errors import Errors
1212
from mypy.types import (
@@ -136,12 +136,12 @@ def note(self, msg: str, context: Context, file: str = None) -> None:
136136
"""Report an error message (unless disabled)."""
137137
self.report(msg, context, 'note', file=file)
138138

139-
def format(self, typ: Type, verbose: bool = False) -> str:
139+
def format(self, typ: Type, verbosity: int = 0) -> str:
140140
"""Convert a type to a relatively short string that is suitable for error messages.
141141
142142
Mostly behave like format_simple below, but never return an empty string.
143143
"""
144-
s = self.format_simple(typ)
144+
s = self.format_simple(typ, verbosity)
145145
if s != '':
146146
# If format_simple returns a non-trivial result, use that.
147147
return s
@@ -152,7 +152,7 @@ def format(self, typ: Type, verbose: bool = False) -> str:
152152
# return type (this always works).
153153
itype = cast(Instance, func.items()[0].ret_type)
154154
result = self.format(itype)
155-
if verbose:
155+
if verbosity >= 1:
156156
# In some contexts we want to be explicit about the distinction
157157
# between type X and the type of type object X.
158158
result += ' (type object)'
@@ -172,7 +172,7 @@ def format(self, typ: Type, verbose: bool = False) -> str:
172172
# Default case; we simply have to return something meaningful here.
173173
return 'object'
174174

175-
def format_simple(self, typ: Type) -> str:
175+
def format_simple(self, typ: Type, verbosity: int = 0) -> str:
176176
"""Convert simple types to string that is suitable for error messages.
177177
178178
Return "" for complex types. Try to keep the length of the result
@@ -187,7 +187,10 @@ def format_simple(self, typ: Type) -> str:
187187
if isinstance(typ, Instance):
188188
itype = cast(Instance, typ)
189189
# Get the short name of the type.
190-
base_str = itype.type.name()
190+
if verbosity >= 2:
191+
base_str = itype.type.fullname()
192+
else:
193+
base_str = itype.type.name()
191194
if itype.args == []:
192195
# No type arguments. Place the type name in quotes to avoid
193196
# potential for confusion: otherwise, the type name could be
@@ -252,6 +255,19 @@ def format_simple(self, typ: Type) -> str:
252255
# message.
253256
return ''
254257

258+
def format_distinctly(self, type1: Type, type2: Type) -> Tuple[str, str]:
259+
"""Jointly format a pair of types to distinct strings.
260+
261+
Increase the verbosity of the type strings until they become distinct.
262+
"""
263+
verbosity = 0
264+
for verbosity in range(3):
265+
str1 = self.format(type1, verbosity=verbosity)
266+
str2 = self.format(type2, verbosity=verbosity)
267+
if str1 != str2:
268+
return (str1, str2)
269+
return (str1, str2)
270+
255271
#
256272
# Specific operations
257273
#
@@ -432,12 +448,7 @@ def incompatible_argument(self, n: int, m: int, callee: CallableType, arg_type:
432448
expected_type = callee.arg_types[m - 1]
433449
except IndexError: # Varargs callees
434450
expected_type = callee.arg_types[-1]
435-
arg_type_str = self.format(arg_type)
436-
expected_type_str = self.format(expected_type)
437-
# If type names turn out ambiguous, attempt to disambiguate.
438-
if arg_type_str == expected_type_str:
439-
arg_type_str = self.format(arg_type, verbose=True)
440-
expected_type_str = self.format(expected_type, verbose=True)
451+
arg_type_str, expected_type_str = self.format_distinctly(arg_type, expected_type)
441452
msg = 'Argument {} {}has incompatible type {}; expected {}'.format(
442453
n, target, arg_type_str, expected_type_str)
443454
self.fail(msg, context)

mypy/test/data/check-dynamic-typing.test

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -485,7 +485,7 @@ class A:
485485
def __init__(self, a, b): pass
486486
[out]
487487
main:6: error: Too few arguments for "A"
488-
main:7: error: Incompatible types in assignment (expression has type "A" (type object), variable has type Callable[[A], A])
488+
main:7: error: Incompatible types in assignment (expression has type "A", variable has type Callable[[A], A])
489489

490490
[case testUsingImplicitTypeObjectWithIs]
491491

0 commit comments

Comments
 (0)