From 1960471fac784624f785a64a57ec3fec33e329dc Mon Sep 17 00:00:00 2001 From: Daniel Watkins Date: Wed, 30 Aug 2017 16:39:05 +0100 Subject: [PATCH 1/2] Refactor quoting of type strings to a separate method This allows callers of .bare_format (who need to perform post-processing before wrapping a type in quotes) to more easily match the quoting patterns used by other callers. --- mypy/messages.py | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/mypy/messages.py b/mypy/messages.py index a59d28678ab1..1689723ddb52 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -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\(length \d+\)$' + if (type_string in ['Module', 'overloaded function', '', ''] + 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. @@ -192,14 +202,7 @@ 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', '', ''] - 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: """ @@ -207,7 +210,8 @@ def format_bare(self, typ: Type, verbosity: int = 0) -> str: 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 @@ -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) From 0946af626d346e89be42d6dd588ee25367aef7b1 Mon Sep 17 00:00:00 2001 From: Daniel Watkins Date: Wed, 30 Aug 2017 16:51:15 +0100 Subject: [PATCH 2/2] Format many-itemed tuples and unions consistently and more clearly When the string representation of a tuple or union is too long to display, a shorter representation is used. This moves from two different representations to `` and ``, and consistently does not quote them. --- mypy/messages.py | 6 +++--- test-data/unit/check-tuples.test | 2 +- test-data/unit/check-unions.test | 19 +++++++++++++++++++ 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/mypy/messages.py b/mypy/messages.py index 1689723ddb52..032df17f40f1 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -185,7 +185,7 @@ def warn(self, msg: str, context: Context, file: Optional[str] = None, def quote_type_string(self, type_string: str) -> str: """Quotes a type representation for use in messages.""" - no_quote_regex = r'^tuple\(length \d+\)$' + no_quote_regex = r'^<(tuple|union): \d+ items>$' if (type_string in ['Module', 'overloaded function', '', ''] or re.match(no_quote_regex, type_string) is not None): # Messages are easier to read if these aren't quoted. We use a @@ -260,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 ''.format(len(items)) elif isinstance(typ, TypedDictType): # If the TypedDictType is named, return the name if not typ.is_anonymous(): @@ -288,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 ''.format(len(items)) elif isinstance(typ, NoneTyp): return 'None' elif isinstance(typ, AnyType): diff --git a/test-data/unit/check-tuples.test b/test-data/unit/check-tuples.test index ad435666fb22..14483d9ceea1 100644 --- a/test-data/unit/check-tuples.test +++ b/test-data/unit/check-tuples.test @@ -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 methods diff --git a/test-data/unit/check-unions.test b/test-data/unit/check-unions.test index 7dfd22557f9d..a43c42a4f970 100644 --- a/test-data/unit/check-unions.test +++ b/test-data/unit/check-unions.test @@ -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 ; expected "int"