Skip to content

Format many-itemed tuples and unions consistently and more clearly #3893

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Aug 31, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 18 additions & 13 deletions mypy/messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,16 @@ def warn(self, msg: str, context: Context, file: Optional[str] = None,
"""Report a warning message (unless disabled)."""
self.report(msg, context, 'warning', file=file, origin=origin)

def quote_type_string(self, type_string: str) -> str:
"""Quotes a type representation for use in messages."""
no_quote_regex = r'^<(tuple|union): \d+ items>$'
if (type_string in ['Module', 'overloaded function', '<nothing>', '<deleted>']
or re.match(no_quote_regex, type_string) is not None):
# Messages are easier to read if these aren't quoted. We use a
# regex to match strings with variable contents.
return type_string
return '"{}"'.format(type_string)

def format(self, typ: Type, verbosity: int = 0) -> str:
"""
Convert a type to a relatively short string suitable for error messages.
Expand All @@ -192,22 +202,16 @@ def format(self, typ: Type, verbosity: int = 0) -> str:
modification of the formatted string is required, callers should use
.format_bare.
"""
ret = self.format_bare(typ, verbosity)
no_quote_regex = r'^tuple\(length \d+\)$'
if (ret in ['Module', 'overloaded function', '<nothing>', '<deleted>']
or re.match(no_quote_regex, ret) is not None):
# Messages are easier to read if these aren't quoted. We use a
# regex to match strings with variable contents.
return ret
return '"{}"'.format(ret)
return self.quote_type_string(self.format_bare(typ, verbosity))

def format_bare(self, typ: Type, verbosity: int = 0) -> str:
"""
Convert a type to a relatively short string suitable for error messages.

This method will return an unquoted string. If a caller doesn't need to
perform post-processing on the string output, .format should be used
instead.
instead. (The caller may want to use .quote_type_string after
processing has happened, to maintain consistent quoting in messages.)
"""
if isinstance(typ, Instance):
itype = typ
Expand Down Expand Up @@ -256,7 +260,7 @@ def format_bare(self, typ: Type, verbosity: int = 0) -> str:
if len(s) < 400:
return s
else:
return 'tuple(length {})'.format(len(items))
return '<tuple: {} items>'.format(len(items))
elif isinstance(typ, TypedDictType):
# If the TypedDictType is named, return the name
if not typ.is_anonymous():
Expand Down Expand Up @@ -284,7 +288,7 @@ def format_bare(self, typ: Type, verbosity: int = 0) -> str:
if len(s) < 400:
return s
else:
return 'union type ({} items)'.format(len(items))
return '<union: {} items>'.format(len(items))
elif isinstance(typ, NoneTyp):
return 'None'
elif isinstance(typ, AnyType):
Expand Down Expand Up @@ -614,8 +618,9 @@ def incompatible_argument(self, n: int, m: int, callee: CallableType, arg_type:
arg_type_str = '*' + arg_type_str
elif arg_kind == ARG_STAR2:
arg_type_str = '**' + arg_type_str
msg = 'Argument {} {}has incompatible type "{}"; expected "{}"'.format(
n, target, arg_type_str, expected_type_str)
msg = 'Argument {} {}has incompatible type {}; expected {}'.format(
n, target, self.quote_type_string(arg_type_str),
self.quote_type_string(expected_type_str))
if isinstance(arg_type, Instance) and isinstance(expected_type, Instance):
notes = append_invariance_notes(notes, arg_type, expected_type)
self.fail(msg, context)
Expand Down
2 changes: 1 addition & 1 deletion test-data/unit/check-tuples.test
Original file line number Diff line number Diff line change
Expand Up @@ -579,7 +579,7 @@ class LongTypeName:
def __add__(self, x: 'LongTypeName') -> 'LongTypeName': pass
[builtins fixtures/tuple.pyi]
[out]
main:3: error: Unsupported operand types for + ("LongTypeName" and tuple(length 50))
main:3: error: Unsupported operand types for + ("LongTypeName" and <tuple: 50 items>)


-- Tuple methods
Expand Down
19 changes: 19 additions & 0 deletions test-data/unit/check-unions.test
Original file line number Diff line number Diff line change
Expand Up @@ -496,3 +496,22 @@ if bool():
reveal_type(x) # E: Revealed type is 'Any'
reveal_type(x) # E: Revealed type is 'Union[builtins.int, Any]'
[builtins fixtures/bool.pyi]

[case testLongUnionFormatting]
from typing import Any, Generic, TypeVar, Union

T = TypeVar('T')

class ExtremelyLongTypeNameWhichIsGenericSoWeCanUseItMultipleTimes(Generic[T]):
pass

x: Union[ExtremelyLongTypeNameWhichIsGenericSoWeCanUseItMultipleTimes[int],
ExtremelyLongTypeNameWhichIsGenericSoWeCanUseItMultipleTimes[object],
ExtremelyLongTypeNameWhichIsGenericSoWeCanUseItMultipleTimes[float],
ExtremelyLongTypeNameWhichIsGenericSoWeCanUseItMultipleTimes[str],
ExtremelyLongTypeNameWhichIsGenericSoWeCanUseItMultipleTimes[Any],
ExtremelyLongTypeNameWhichIsGenericSoWeCanUseItMultipleTimes[bytes]]

def takes_int(arg: int) -> None: pass

takes_int(x) # E: Argument 1 to "takes_int" has incompatible type <union: 6 items>; expected "int"