From e0c008540c8f668e22c93c35f44c9cfdf8371a7a Mon Sep 17 00:00:00 2001 From: Mark Koh Date: Mon, 22 May 2017 14:41:42 -0700 Subject: [PATCH 1/8] Added signature information to incompatible signature failure. --- mypy/checker.py | 2 +- mypy/messages.py | 11 ++++++---- mypy/types.py | 54 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 62 insertions(+), 5 deletions(-) diff --git a/mypy/checker.py b/mypy/checker.py index c65f9563afdd..95ec2b6de5da 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -1067,7 +1067,7 @@ def erase_override(t: Type) -> Type: if not emitted_msg: # Fall back to generic incompatibility message. self.msg.signature_incompatible_with_supertype( - name, name_in_super, supertype, node) + name, name_in_super, supertype, node, original, override) def visit_class_def(self, defn: ClassDef) -> None: """Type check a class definition.""" diff --git a/mypy/messages.py b/mypy/messages.py index 5c2f5d16fdc7..bb4866dba488 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -6,7 +6,7 @@ import re import difflib -from typing import cast, List, Dict, Any, Sequence, Iterable, Tuple +from typing import cast, List, Dict, Any, Sequence, Iterable, Tuple, Optional from mypy.erasetype import erase_type from mypy.errors import Errors @@ -677,10 +677,13 @@ def incompatible_operator_assignment(self, op: str, def signature_incompatible_with_supertype( self, name: str, name_in_super: str, supertype: str, - context: Context) -> None: + context: Context, original: Optional[FunctionLike] = None, override: Optional[FunctionLike] = None) -> None: target = self.override_target(name, name_in_super, supertype) - self.fail('Signature of "{}" incompatible with {}'.format( - name, target), context) + self.fail('Signature of "{}" incompatible with {}'.format(name, target), context) + if original is not None: + self.note('Signature of "{}" in superclass: "{}"'.format(name, original.pretty_str()), context) + if override is not None: + self.note('Signature of "{}" in subclass: "{}"'.format(name, override.pretty_str()), context) def argument_incompatible_with_supertype( self, arg_num: int, name: str, name_in_supertype: str, diff --git a/mypy/types.py b/mypy/types.py index c17bb063f5fb..7d198a7abddf 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -56,6 +56,9 @@ def accept(self, visitor: 'TypeVisitor[T]') -> T: def __repr__(self) -> str: return self.accept(TypeStrVisitor()) + def pretty_str(self) -> str: + return self.accept(PrettyTypeStrVisitor()) + def serialize(self) -> Union[JsonDict, str]: raise NotImplementedError('Cannot serialize {} instance'.format(self.__class__.__name__)) @@ -1546,6 +1549,57 @@ def keywords_str(self, a: Iterable[Tuple[str, Type]]) -> str: ]) +class PrettyTypeStrVisitor(TypeStrVisitor): + """Overloaded Visitor for pretty-printing types into user-faceing strings. + + Attempts to (but usually doesnt) match original formatting + + Notes: + - Represent unbound types as Foo? or Foo?[...]. + - Represent the NoneTyp type as None. + """ + def visit_none_type(self, t: NoneTyp) -> str: + # Note: This isn't discernable from the None value (for readiblity) + return "None" + + def visit_callable_type(self, t: CallableType) -> str: + s = '' + bare_asterisk = False + for i in range(len(t.arg_types)): + if s != '': + s += ', ' + if t.arg_kinds[i] in (ARG_NAMED, ARG_NAMED_OPT) and not bare_asterisk: + s += '*, ' + bare_asterisk = True + if t.arg_kinds[i] == ARG_STAR: + s += '*' + if t.arg_kinds[i] == ARG_STAR2: + s += '**' + name = t.arg_names[i] + if name: + s += name + ': ' + s += t.arg_types[i].accept(self) + if t.arg_kinds[i] in (ARG_OPT, ARG_NAMED_OPT): + s += ' =' + + if t.definition is not None and t.definition.name() is not None: + # If we got a "special arg" (i.e: self, cls, etc...), prepend it to the arg list + if t.arg_names != t.definition.arg_names and len(t.definition.arg_names) > 0: + special_arg = t.definition.arg_names[0] + if s != '': + s = ', ' + s + s = special_arg + s + s = '{}({})'.format(t.definition.name(), s) + else: + s = '({})'.format(s) + + s += ' -> {}'.format(t.ret_type.accept(self)) + + if t.variables: + s = '{} {}'.format(t.variables, s) + + return 'def {}'.format(s) + class TypeQuery(SyntheticTypeVisitor[T]): """Visitor for performing queries of types. From f68fe5cfc58339a52454ba55f7f800605418167e Mon Sep 17 00:00:00 2001 From: Mark Koh Date: Mon, 22 May 2017 17:16:46 -0700 Subject: [PATCH 2/8] Fixed incompatible signature tests. --- mypy/messages.py | 2 +- mypy/types.py | 8 ++++--- test-data/unit/check-abstract.test | 6 +++-- test-data/unit/check-classes.test | 25 ++++++++++++++++++--- test-data/unit/check-dynamic-typing.test | 10 +++++++-- test-data/unit/check-functions.test | 14 +++++++++--- test-data/unit/check-generic-subtyping.test | 7 +++++- test-data/unit/check-overloading.test | 4 ++++ 8 files changed, 61 insertions(+), 15 deletions(-) diff --git a/mypy/messages.py b/mypy/messages.py index bb4866dba488..0fe779149d95 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -683,7 +683,7 @@ def signature_incompatible_with_supertype( if original is not None: self.note('Signature of "{}" in superclass: "{}"'.format(name, original.pretty_str()), context) if override is not None: - self.note('Signature of "{}" in subclass: "{}"'.format(name, override.pretty_str()), context) + self.note('Signature of "{}" in subclass: "{}"'.format(name, override.pretty_str()), context) def argument_incompatible_with_supertype( self, arg_num: int, name: str, name_in_supertype: str, diff --git a/mypy/types.py b/mypy/types.py index 7d198a7abddf..f466c5b0f359 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -1383,7 +1383,7 @@ def visit_type_type(self, t: TypeType) -> Type: class TypeStrVisitor(SyntheticTypeVisitor[str]): - """Visitor for pretty-printing types into strings. + """Visitor for printing types into human-readable strings. This is mostly for debugging/testing. @@ -1550,9 +1550,9 @@ def keywords_str(self, a: Iterable[Tuple[str, Type]]) -> str: class PrettyTypeStrVisitor(TypeStrVisitor): - """Overloaded Visitor for pretty-printing types into user-faceing strings. + """Overloaded Visitor for pretty-printing types into strings. - Attempts to (but usually doesnt) match original formatting + Does not preserve original formatting. Notes: - Represent unbound types as Foo? or Foo?[...]. @@ -1566,6 +1566,8 @@ def visit_callable_type(self, t: CallableType) -> str: s = '' bare_asterisk = False for i in range(len(t.arg_types)): + if len(t.arg_types) > 5: + s += '\n' if s != '': s += ', ' if t.arg_kinds[i] in (ARG_NAMED, ARG_NAMED_OPT) and not bare_asterisk: diff --git a/test-data/unit/check-abstract.test b/test-data/unit/check-abstract.test index 2de9d129436b..382a92916a30 100644 --- a/test-data/unit/check-abstract.test +++ b/test-data/unit/check-abstract.test @@ -406,9 +406,11 @@ class I(metaclass=ABCMeta): def g(self, x): pass class A(I): def f(self, x): pass - def g(self, x, y) -> None: pass \ - # E: Signature of "g" incompatible with supertype "I" + def g(self, x, y) -> None: pass [out] +main:10: error: Signature of "g" incompatible with supertype "I" +main:10: note: Signature of "g" in superclass: "def (x: Any) -> Any" +main:10: note: Signature of "g" in subclass: "def g(self, x: Any, y: Any) -> None" [case testAbstractClassWithAllDynamicTypes2] from abc import abstractmethod, ABCMeta diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index a257fa0375f0..225394dcc6b9 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -249,7 +249,11 @@ class B(A): def g(self, x: A) -> A: pass # Fail [out] main:6: error: Signature of "f" incompatible with supertype "A" +main:6: note: Signature of "f" in superclass: "def f(self, x: __main__.A) -> None" +main:6: note: Signature of "f" in subclass: "def f(self, x: __main__.A, y: __main__.A) -> None" main:7: error: Signature of "g" incompatible with supertype "A" +main:7: note: Signature of "g" in superclass: "def g(self, x: __main__.A, y: __main__.B) -> __main__.A" +main:7: note: Signature of "g" in subclass: "def g(self, x: __main__.A) -> __main__.A" [case testMethodOverridingAcrossDeepInheritanceHierarchy1] import typing @@ -856,7 +860,7 @@ class A: def g(self) -> None: pass class B(A): - def f(self) -> None: pass # E: Signature of "f" incompatible with supertype "A" + def f(self) -> None: pass @classmethod def g(cls) -> None: pass @@ -864,6 +868,10 @@ class B(A): class C(A): @staticmethod def f() -> None: pass +[out] +main:8: error: Signature of "f" incompatible with supertype "A" +main:8: note: Signature of "f" in superclass: "def f(cls) -> None" +main:8: note: Signature of "f" in subclass: "def f(self) -> None" [builtins fixtures/classmethod.pyi] -- Properties @@ -1343,11 +1351,14 @@ from typing import overload class A: def __add__(self, x: int) -> int: pass class B(A): - @overload # E: Signature of "__add__" incompatible with supertype "A" + @overload def __add__(self, x: int) -> int: pass @overload def __add__(self, x: str) -> str: pass [out] +tmp/foo.pyi:5: error: Signature of "__add__" incompatible with supertype "A" +tmp/foo.pyi:5: note: Signature of "__add__" in superclass: "def __add__(self, builtins.int) -> builtins.int" +tmp/foo.pyi:5: note: Signature of "__add__" in subclass: "Overload(def __add__(self, builtins.int) -> builtins.int, def __add__(self, builtins.str) -> builtins.str)" [case testOperatorMethodOverrideWideningArgumentType] import typing @@ -1418,6 +1429,8 @@ class B(A): def __add__(self, x: type) -> A: pass [out] tmp/foo.pyi:8: error: Signature of "__add__" incompatible with supertype "A" +tmp/foo.pyi:8: note: Signature of "__add__" in superclass: "Overload(def __add__(self, builtins.int) -> foo.A, def __add__(self, builtins.str) -> foo.A)" +tmp/foo.pyi:8: note: Signature of "__add__" in subclass: "Overload(def __add__(self, builtins.int) -> foo.A, def __add__(self, builtins.str) -> foo.A, def __add__(self, builtins.type) -> foo.A)" [case testOverloadedOperatorMethodOverrideWithSwitchedItemOrder] from foo import * @@ -1435,6 +1448,8 @@ class B(A): def __add__(self, x: 'B') -> 'B': pass [out] tmp/foo.pyi:8: error: Signature of "__add__" incompatible with supertype "A" +tmp/foo.pyi:8: note: Signature of "__add__" in superclass: "Overload(def __add__(self, foo.B) -> foo.B, def __add__(self, foo.A) -> foo.A)" +tmp/foo.pyi:8: note: Signature of "__add__" in subclass: "Overload(def __add__(self, foo.A) -> foo.A, def __add__(self, foo.B) -> foo.B)" [case testReverseOperatorMethodArgumentType] from typing import Any @@ -2904,13 +2919,17 @@ class A: def c() -> None: pass class B(A): - def a(self) -> None: pass # E: Signature of "a" incompatible with supertype "A" + def a(self) -> None: pass @classmethod def b(cls) -> None: pass @staticmethod def c() -> None: pass +[out] +main:11: error: Signature of "a" incompatible with supertype "A" +main:11: note: Signature of "a" in superclass: "def a() -> None" +main:11: note: Signature of "a" in subclass: "def a(self) -> None" [builtins fixtures/classmethod.pyi] [case testTempNode] diff --git a/test-data/unit/check-dynamic-typing.test b/test-data/unit/check-dynamic-typing.test index 68a174e3cc81..8531d1dce721 100644 --- a/test-data/unit/check-dynamic-typing.test +++ b/test-data/unit/check-dynamic-typing.test @@ -640,18 +640,24 @@ import typing class B: def f(self, x, y): pass class A(B): - def f(self, x: 'A') -> None: # E: Signature of "f" incompatible with supertype "B" + def f(self, x: 'A') -> None: pass [out] +main:5: error: Signature of "f" incompatible with supertype "B" +main:5: note: Signature of "f" in superclass: "def (x: Any, y: Any) -> Any" +main:5: note: Signature of "f" in subclass: "def f(self, x: __main__.A) -> None" [case testInvalidOverrideArgumentCountWithImplicitSignature3] import typing class B: def f(self, x: A) -> None: pass class A(B): - def f(self, x, y) -> None: # E: Signature of "f" incompatible with supertype "B" + def f(self, x, y) -> None: x() [out] +main:5: error: Signature of "f" incompatible with supertype "B" +main:5: note: Signature of "f" in superclass: "def f(self, x: __main__.A) -> None" +main:5: note: Signature of "f" in subclass: "def f(self, x: Any, y: Any) -> None" -- Don't complain about too few/many arguments in dynamic functions diff --git a/test-data/unit/check-functions.test b/test-data/unit/check-functions.test index 8ac35e052659..940fd69597fc 100644 --- a/test-data/unit/check-functions.test +++ b/test-data/unit/check-functions.test @@ -31,7 +31,12 @@ class B(A): def f(self, *, b: str, a: int) -> None: pass class C(A): - def f(self, *, b: int, a: str) -> None: pass # E: Signature of "f" incompatible with supertype "A" + def f(self, *, b: int, a: str) -> None: pass + +[out] +main:10: error: Signature of "f" incompatible with supertype "A" +main:10: note: Signature of "f" in superclass: "def f(self, *, a: builtins.int, b: builtins.str) -> None" +main:10: note: Signature of "f" in subclass: "def f(self, *, b: builtins.int, a: builtins.str) -> None" [case testPositionalOverridingArgumentNameInsensitivity] import typing @@ -53,8 +58,11 @@ class A(object): def f(self, a: int, b: str) -> None: pass class B(A): - def f(self, b: int, a: str) -> None: pass # E: Signature of "f" incompatible with supertype "A" - + def f(self, b: int, a: str) -> None: pass +[out] +main:7: error: Signature of "f" incompatible with supertype "A" +main:7: note: Signature of "f" in superclass: "def f(self, a: builtins.int, b: builtins.str) -> None" +main:7: note: Signature of "f" in subclass: "def f(self, b: builtins.int, a: builtins.str) -> None" [case testSubtypingFunctionTypes] from typing import Callable diff --git a/test-data/unit/check-generic-subtyping.test b/test-data/unit/check-generic-subtyping.test index 6cae196bbe80..212c3849e666 100644 --- a/test-data/unit/check-generic-subtyping.test +++ b/test-data/unit/check-generic-subtyping.test @@ -245,9 +245,12 @@ class A: class B(A): def f(self, x: List[S], y: List[T]) -> None: pass class C(A): - def f(self, x: List[T], y: List[T]) -> None: pass # E: Signature of "f" incompatible with supertype "A" + def f(self, x: List[T], y: List[T]) -> None: pass [builtins fixtures/list.pyi] [out] +main:11: error: Signature of "f" incompatible with supertype "A" +main:11: note: Signature of "f" in superclass: "def [T, S] f(self, x: builtins.list[T`-1], y: builtins.list[S`-2]) -> None" +main:11: note: Signature of "f" in subclass: "def [T] f(self, x: builtins.list[T`-1], y: builtins.list[T`-1]) -> None" [case testOverrideGenericMethodInNonGenericClassGeneralize] from typing import TypeVar @@ -267,6 +270,8 @@ class D(A): [out] main:12: error: Argument 2 of "f" incompatible with supertype "A" main:14: error: Signature of "f" incompatible with supertype "A" +main:14: note: Signature of "f" in superclass: "def [S] f(self, x: builtins.int, y: S`-1) -> None" +main:14: note: Signature of "f" in subclass: "def [T1 <: builtins.str, S] f(self, x: T1`-1, y: S`-2) -> None" -- Inheritance from generic types with implicit dynamic supertype diff --git a/test-data/unit/check-overloading.test b/test-data/unit/check-overloading.test index 69289fae18c1..38eee241649f 100644 --- a/test-data/unit/check-overloading.test +++ b/test-data/unit/check-overloading.test @@ -787,7 +787,11 @@ class D(A): def f(self, x: str) -> str: return '' [out] tmp/foo.pyi:12: error: Signature of "f" incompatible with supertype "A" +tmp/foo.pyi:12: note: Signature of "f" in superclass: "Overload(def f(self, x: builtins.int) -> builtins.int, def f(self, x: builtins.str) -> builtins.str)" +tmp/foo.pyi:12: note: Signature of "f" in subclass: "Overload(def f(self, x: foo.IntSub) -> builtins.int, def f(self, x: builtins.str) -> builtins.str)" tmp/foo.pyi:17: error: Signature of "f" incompatible with supertype "A" +tmp/foo.pyi:17: note: Signature of "f" in superclass: "Overload(def f(self, x: builtins.int) -> builtins.int, def f(self, x: builtins.str) -> builtins.str)" +tmp/foo.pyi:17: note: Signature of "f" in subclass: "Overload(def f(self, x: builtins.int) -> builtins.int, def f(self, x: foo.StrSub) -> builtins.str)" [case testOverloadingAndDucktypeCompatibility] from foo import * From fb9ff7e2bb1275682ca748c36b180ae6e60c24b6 Mon Sep 17 00:00:00 2001 From: Mark Koh Date: Mon, 22 May 2017 17:55:00 -0700 Subject: [PATCH 3/8] Fix tests and linting. --- mypy/messages.py | 11 ++++++++--- mypy/types.py | 7 +++++-- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/mypy/messages.py b/mypy/messages.py index 0fe779149d95..78c0b87b6a99 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -677,13 +677,18 @@ def incompatible_operator_assignment(self, op: str, def signature_incompatible_with_supertype( self, name: str, name_in_super: str, supertype: str, - context: Context, original: Optional[FunctionLike] = None, override: Optional[FunctionLike] = None) -> None: + context: Context, original: Optional[FunctionLike] = None, + override: Optional[FunctionLike] = None) -> None: target = self.override_target(name, name_in_super, supertype) self.fail('Signature of "{}" incompatible with {}'.format(name, target), context) if original is not None: - self.note('Signature of "{}" in superclass: "{}"'.format(name, original.pretty_str()), context) + self.note('Signature of "{}" in superclass: "{}"'.format(name, + original.pretty_str()), + context) if override is not None: - self.note('Signature of "{}" in subclass: "{}"'.format(name, override.pretty_str()), context) + self.note('Signature of "{}" in subclass: "{}"'.format(name, + override.pretty_str()), + context) def argument_incompatible_with_supertype( self, arg_num: int, name: str, name_in_supertype: str, diff --git a/mypy/types.py b/mypy/types.py index f466c5b0f359..171c1c116d4d 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -1586,8 +1586,10 @@ def visit_callable_type(self, t: CallableType) -> str: if t.definition is not None and t.definition.name() is not None: # If we got a "special arg" (i.e: self, cls, etc...), prepend it to the arg list - if t.arg_names != t.definition.arg_names and len(t.definition.arg_names) > 0: - special_arg = t.definition.arg_names[0] + definition_args = getattr(t.definition, 'arg_names') + if definition_args and t.arg_names != definition_args \ + and len(definition_args) > 0: + special_arg = definition_args[0] if s != '': s = ', ' + s s = special_arg + s @@ -1602,6 +1604,7 @@ def visit_callable_type(self, t: CallableType) -> str: return 'def {}'.format(s) + class TypeQuery(SyntheticTypeVisitor[T]): """Visitor for performing queries of types. From b2ed7aeb3be7f47b0e511bbf653d31463c4b45a1 Mon Sep 17 00:00:00 2001 From: Mark Koh Date: Mon, 22 May 2017 18:31:32 -0700 Subject: [PATCH 4/8] Add fancy newline formatting for Jukka --- mypy/messages.py | 31 +++++++++++++++++-------------- mypy/types.py | 6 ++++++ 2 files changed, 23 insertions(+), 14 deletions(-) diff --git a/mypy/messages.py b/mypy/messages.py index 78c0b87b6a99..ff893e42fb5d 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -154,28 +154,29 @@ def is_errors(self) -> bool: return self.errors.is_errors() def report(self, msg: str, context: Context, severity: str, - file: str = None, origin: Context = None) -> None: + file: str = None, origin: Context = None, strip: bool = True) -> None: """Report an error or note (unless disabled).""" if self.disable_count <= 0: self.errors.report(context.get_line() if context else -1, context.get_column() if context else -1, - msg.strip(), severity=severity, file=file, + msg.strip() if strip else msg, + severity=severity, file=file, origin_line=origin.get_line() if origin else None) def fail(self, msg: str, context: Context, file: str = None, - origin: Context = None) -> None: + origin: Context = None, strip: bool = True) -> None: """Report an error message (unless disabled).""" - self.report(msg, context, 'error', file=file, origin=origin) + self.report(msg, context, 'error', file=file, origin=origin, strip=strip) def note(self, msg: str, context: Context, file: str = None, - origin: Context = None) -> None: + origin: Context = None, strip: bool = True) -> None: """Report a note (unless disabled).""" - self.report(msg, context, 'note', file=file, origin=origin) + self.report(msg, context, 'note', file=file, origin=origin, strip=strip) def warn(self, msg: str, context: Context, file: str = None, - origin: Context = None) -> None: + origin: Context = None, strip: bool = True) -> None: """Report a warning message (unless disabled).""" - self.report(msg, context, 'warning', file=file, origin=origin) + self.report(msg, context, 'warning', file=file, origin=origin, strip=strip) def format(self, typ: Type, verbosity: int = 0) -> str: """Convert a type to a relatively short string that is suitable for error messages. @@ -682,13 +683,15 @@ def signature_incompatible_with_supertype( target = self.override_target(name, name_in_super, supertype) self.fail('Signature of "{}" incompatible with {}'.format(name, target), context) if original is not None: - self.note('Signature of "{}" in superclass: "{}"'.format(name, - original.pretty_str()), - context) + pretty_str = original.pretty_str().split('\n') + self.note('Signature of "{}" in superclass:'.format(name), context) + for line in pretty_str: + self.note(' {}'.format(line), context, strip=False) if override is not None: - self.note('Signature of "{}" in subclass: "{}"'.format(name, - override.pretty_str()), - context) + pretty_str = override.pretty_str().split('\n') + self.note('Signature of "{}" in subclass:'.format(name), context) + for line in pretty_str: + self.note(' {}'.format(line), context, strip=False) def argument_incompatible_with_supertype( self, arg_num: int, name: str, name_in_supertype: str, diff --git a/mypy/types.py b/mypy/types.py index 171c1c116d4d..81cc29c1cee8 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -1562,6 +1562,12 @@ def visit_none_type(self, t: NoneTyp) -> str: # Note: This isn't discernable from the None value (for readiblity) return "None" + def visit_overloaded(self, t: Overloaded) -> str: + a = [] + for i in t.items(): + a.append(i.accept(self)) + return '\n@overload\n'.join(a) + def visit_callable_type(self, t: CallableType) -> str: s = '' bare_asterisk = False From 0caae8e9c299a1359f3e249134cc53f60abdb202 Mon Sep 17 00:00:00 2001 From: Mark Koh Date: Tue, 23 May 2017 16:04:51 -0700 Subject: [PATCH 5/8] Improve output and add allow_dups. --- mypy/errors.py | 40 ++++++++++++++++++++-------------- mypy/messages.py | 56 ++++++++++++++++++++++++++++++++---------------- mypy/types.py | 4 ++-- 3 files changed, 64 insertions(+), 36 deletions(-) diff --git a/mypy/errors.py b/mypy/errors.py index 6648784be310..1364356ced93 100644 --- a/mypy/errors.py +++ b/mypy/errors.py @@ -50,6 +50,9 @@ class ErrorInfo: # Only report this particular messages once per program. only_once = False + # Do not remove duplicate copies of this message (ignored if only_once is True) + allow_dups = False + # Fine-grained incremental target where this was reported target = None # type: Optional[str] @@ -65,6 +68,7 @@ def __init__(self, message: str, blocker: bool, only_once: bool, + allow_dups: bool, origin: Tuple[str, int] = None, target: str = None) -> None: self.import_ctx = import_ctx @@ -78,6 +82,7 @@ def __init__(self, self.message = message self.blocker = blocker self.only_once = only_once + self.allow_dups = allow_dups self.origin = origin or (file, line) self.target = target @@ -253,7 +258,7 @@ def set_import_context(self, ctx: List[Tuple[str, int]]) -> None: def report(self, line: int, column: int, message: str, blocker: bool = False, severity: str = 'error', file: str = None, only_once: bool = False, - origin_line: int = None) -> None: + origin_line: int = None, allow_dups: bool = False) -> None: """Report message at the given line using the current error context. Args: @@ -264,6 +269,7 @@ def report(self, line: int, column: int, message: str, blocker: bool = False, file: if non-None, override current file as context only_once: if True, only report this exact message once per build origin_line: if non-None, override current context as origin + allow_dups: if True, do not remove duplicate copies of this message (ignored if only_once is True) """ type = self.type_name[-1] # type: Optional[str] if len(self.function_or_member) > 2: @@ -274,7 +280,7 @@ def report(self, line: int, column: int, message: str, blocker: bool = False, self.function_or_member[-1], line, column, severity, message, blocker, only_once, origin=(self.file, origin_line) if origin_line else None, - target=self.current_target()) + target=self.current_target(), allow_dups=allow_dups) self.add_error_info(info) def add_error_info(self, info: ErrorInfo) -> None: @@ -338,7 +344,7 @@ def messages(self) -> List[str]: a = [] # type: List[str] errors = self.render_messages(self.sort_messages(self.error_info)) errors = self.remove_duplicates(errors) - for file, line, column, severity, message in errors: + for file, line, column, severity, message, allow_dups in errors: s = '' if file is not None: if self.show_column_numbers and line is not None and line >= 0 \ @@ -363,7 +369,7 @@ def targets(self) -> Set[str]: if info.target) def render_messages(self, errors: List[ErrorInfo]) -> List[Tuple[Optional[str], int, int, - str, str]]: + str, str, bool]]: """Translate the messages into a sequence of tuples. Each tuple is of form (path, line, col, message. The rendered @@ -371,7 +377,7 @@ def render_messages(self, errors: List[ErrorInfo]) -> List[Tuple[Optional[str], item may be None. If the line item is negative, the line number is not defined for the tuple. """ - result = [] # type: List[Tuple[Optional[str], int, int, str, str]] + result = [] # type: List[Tuple[Optional[str], int, int, str, str, bool]] # (path, line, column, severity, message) prev_import_context = [] # type: List[Tuple[str, int]] @@ -429,7 +435,7 @@ def render_messages(self, errors: List[ErrorInfo]) -> List[Tuple[Optional[str], result.append((file, -1, -1, 'note', 'In class "{}":'.format(e.type))) - result.append((file, e.line, e.column, e.severity, e.message)) + result.append((file, e.line, e.column, e.severity, e.message, e.allow_dups)) prev_import_context = e.import_ctx prev_function_or_member = e.function_or_member @@ -460,21 +466,23 @@ def sort_messages(self, errors: List[ErrorInfo]) -> List[ErrorInfo]: result.extend(a) return result - def remove_duplicates(self, errors: List[Tuple[Optional[str], int, int, str, str]] - ) -> List[Tuple[Optional[str], int, int, str, str]]: + def remove_duplicates(self, errors: List[Tuple[Optional[str], int, int, str, str, bool]] + ) -> List[Tuple[Optional[str], int, int, str, str, bool]]: """Remove duplicates from a sorted error list.""" - res = [] # type: List[Tuple[Optional[str], int, int, str, str]] + res = [] # type: List[Tuple[Optional[str], int, int, str, str, bool]] i = 0 while i < len(errors): dup = False j = i - 1 - while (j >= 0 and errors[j][0] == errors[i][0] and - errors[j][1] == errors[i][1]): - if (errors[j][3] == errors[i][3] and - errors[j][4] == errors[i][4]): # ignore column - dup = True - break - j -= 1 + # Find duplicates for this error (unless we're allowing duplicates) + if not errors[i][5]: + while (j >= 0 and errors[j][0] == errors[i][0] and + errors[j][1] == errors[i][1]): + if (errors[j][3] == errors[i][3] and + errors[j][4] == errors[i][4]): # ignore column + dup = True + break + j -= 1 if not dup: res.append(errors[i]) i += 1 diff --git a/mypy/messages.py b/mypy/messages.py index ff893e42fb5d..7713c7cd9dc4 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -154,29 +154,34 @@ def is_errors(self) -> bool: return self.errors.is_errors() def report(self, msg: str, context: Context, severity: str, - file: str = None, origin: Context = None, strip: bool = True) -> None: + file: str = None, origin: Context = None, strip: bool = True, + allow_dups: bool = False) -> None: """Report an error or note (unless disabled).""" if self.disable_count <= 0: self.errors.report(context.get_line() if context else -1, context.get_column() if context else -1, msg.strip() if strip else msg, severity=severity, file=file, - origin_line=origin.get_line() if origin else None) + origin_line=origin.get_line() if origin else None, + allow_dups=allow_dups) def fail(self, msg: str, context: Context, file: str = None, - origin: Context = None, strip: bool = True) -> None: + origin: Context = None, strip: bool = True, allow_dups: bool = False) -> None: """Report an error message (unless disabled).""" - self.report(msg, context, 'error', file=file, origin=origin, strip=strip) + self.report(msg, context, 'error', file=file, origin=origin, strip=strip, + allow_dups=allow_dups) def note(self, msg: str, context: Context, file: str = None, - origin: Context = None, strip: bool = True) -> None: + origin: Context = None, strip: bool = True, allow_dups: bool = False) -> None: """Report a note (unless disabled).""" - self.report(msg, context, 'note', file=file, origin=origin, strip=strip) + self.report(msg, context, 'note', file=file, origin=origin, strip=strip, + allow_dups=allow_dups) def warn(self, msg: str, context: Context, file: str = None, - origin: Context = None, strip: bool = True) -> None: + origin: Context = None, strip: bool = True, allow_dups: bool = False) -> None: """Report a warning message (unless disabled).""" - self.report(msg, context, 'warning', file=file, origin=origin, strip=strip) + self.report(msg, context, 'warning', file=file, origin=origin, strip=strip, + allow_dups=allow_dups) def format(self, typ: Type, verbosity: int = 0) -> str: """Convert a type to a relatively short string that is suitable for error messages. @@ -354,6 +359,25 @@ def format_distinctly(self, type1: Type, type2: Type) -> Tuple[str, str]: return (str1, str2) return (str1, str2) + def note_signature(self, defn: Optional[FunctionLike], context: Context) -> None: + """ + Given a FunctionLike object, pretty print its signature across multiple lines + and truncate it if there are lots of overloads + """ + if defn is not None: + max_overload_lines = 6 # Print up to 3 overloads, then truncate + pretty_str = defn.pretty_str().split('\n') + func_name = defn.name() if callable(defn.name) else defn.name + # TODO: Add filename and line number to method message + self.note(' Method {}:'.format(func_name), context, strip=False) + for line in pretty_str[:max_overload_lines]: + self.note(' {}'.format(line), context, strip=False, allow_dups=True) + if len(pretty_str) > max_overload_lines: + # Each overload takes up 2 lines because of "@overload" + additional_overrides = int((len(pretty_str) - max_overload_lines)/2) + self.note(' '.format(additional_overrides), + context, strip=False, allow_dups=True) + # # Specific operations # @@ -681,17 +705,13 @@ def signature_incompatible_with_supertype( context: Context, original: Optional[FunctionLike] = None, override: Optional[FunctionLike] = None) -> None: target = self.override_target(name, name_in_super, supertype) + + # Question: should this be at the top of this file in all caps? self.fail('Signature of "{}" incompatible with {}'.format(name, target), context) - if original is not None: - pretty_str = original.pretty_str().split('\n') - self.note('Signature of "{}" in superclass:'.format(name), context) - for line in pretty_str: - self.note(' {}'.format(line), context, strip=False) - if override is not None: - pretty_str = override.pretty_str().split('\n') - self.note('Signature of "{}" in subclass:'.format(name), context) - for line in pretty_str: - self.note(' {}'.format(line), context, strip=False) + + # Print the original and overridden methods nicely + self.note_signature(original, context) + self.note_signature(override, context) def argument_incompatible_with_supertype( self, arg_num: int, name: str, name_in_supertype: str, diff --git a/mypy/types.py b/mypy/types.py index 81cc29c1cee8..3b17d48771f5 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -1565,8 +1565,8 @@ def visit_none_type(self, t: NoneTyp) -> str: def visit_overloaded(self, t: Overloaded) -> str: a = [] for i in t.items(): - a.append(i.accept(self)) - return '\n@overload\n'.join(a) + a.append('@overload\n' + i.accept(self)) + return '\n'.join(a) def visit_callable_type(self, t: CallableType) -> str: s = '' From 452146a1c0d4a49a068c70a04c91de4dd797568a Mon Sep 17 00:00:00 2001 From: Mark Koh Date: Tue, 23 May 2017 16:42:28 -0700 Subject: [PATCH 6/8] Fix test outputs and error tuple rendering. --- mypy/errors.py | 16 ++-- test-data/unit/check-abstract.test | 6 +- test-data/unit/check-classes.test | 93 +++++++++++++++++---- test-data/unit/check-dynamic-typing.test | 12 ++- test-data/unit/check-functions.test | 12 ++- test-data/unit/check-generic-subtyping.test | 12 ++- test-data/unit/check-overloading.test | 24 +++++- 7 files changed, 135 insertions(+), 40 deletions(-) diff --git a/mypy/errors.py b/mypy/errors.py index 1364356ced93..59a8a024838e 100644 --- a/mypy/errors.py +++ b/mypy/errors.py @@ -306,7 +306,7 @@ def generate_unused_ignore_notes(self) -> None: # Don't use report since add_error_info will ignore the error! info = ErrorInfo(self.import_context(), file, self.current_module(), None, None, line, -1, 'note', "unused 'type: ignore' comment", - False, False) + False, False, False) self.error_info.append(info) def is_typeshed_file(self, file: str) -> bool: @@ -403,7 +403,7 @@ def render_messages(self, errors: List[ErrorInfo]) -> List[Tuple[Optional[str], # Remove prefix to ignore from path (if present) to # simplify path. path = remove_path_prefix(path, self.ignore_prefix) - result.append((None, -1, -1, 'note', fmt.format(path, line))) + result.append((None, -1, -1, 'note', fmt.format(path, line), e.allow_dups)) i -= 1 file = self.simplify_path(e.file) @@ -415,25 +415,25 @@ def render_messages(self, errors: List[ErrorInfo]) -> List[Tuple[Optional[str], e.type != prev_type): if e.function_or_member is None: if e.type is None: - result.append((file, -1, -1, 'note', 'At top level:')) + result.append((file, -1, -1, 'note', 'At top level:', e.allow_dups)) else: result.append((file, -1, -1, 'note', 'In class "{}":'.format( - e.type))) + e.type), e.allow_dups)) else: if e.type is None: result.append((file, -1, -1, 'note', 'In function "{}":'.format( - e.function_or_member))) + e.function_or_member), e.allow_dups)) else: result.append((file, -1, -1, 'note', 'In member "{}" of class "{}":'.format( - e.function_or_member, e.type))) + e.function_or_member, e.type), e.allow_dups)) elif e.type != prev_type: if e.type is None: - result.append((file, -1, -1, 'note', 'At top level:')) + result.append((file, -1, -1, 'note', 'At top level:', e.allow_dups)) else: result.append((file, -1, -1, 'note', - 'In class "{}":'.format(e.type))) + 'In class "{}":'.format(e.type), e.allow_dups)) result.append((file, e.line, e.column, e.severity, e.message, e.allow_dups)) diff --git a/test-data/unit/check-abstract.test b/test-data/unit/check-abstract.test index 382a92916a30..ec3d339f0cb9 100644 --- a/test-data/unit/check-abstract.test +++ b/test-data/unit/check-abstract.test @@ -409,8 +409,10 @@ class A(I): def g(self, x, y) -> None: pass [out] main:10: error: Signature of "g" incompatible with supertype "I" -main:10: note: Signature of "g" in superclass: "def (x: Any) -> Any" -main:10: note: Signature of "g" in subclass: "def g(self, x: Any, y: Any) -> None" +main:10: note: Method "g" of "I": +main:10: note: def (x: Any) -> Any +main:10: note: Method "g" of "A": +main:10: note: def g(self, x: Any, y: Any) -> None [case testAbstractClassWithAllDynamicTypes2] from abc import abstractmethod, ABCMeta diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index 225394dcc6b9..bc2db45ffeba 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -249,11 +249,15 @@ class B(A): def g(self, x: A) -> A: pass # Fail [out] main:6: error: Signature of "f" incompatible with supertype "A" -main:6: note: Signature of "f" in superclass: "def f(self, x: __main__.A) -> None" -main:6: note: Signature of "f" in subclass: "def f(self, x: __main__.A, y: __main__.A) -> None" +main:6: note: Method "f" of "A": +main:6: note: def f(self, x: __main__.A) -> None +main:6: note: Method "f" of "B": +main:6: note: def f(self, x: __main__.A, y: __main__.A) -> None main:7: error: Signature of "g" incompatible with supertype "A" -main:7: note: Signature of "g" in superclass: "def g(self, x: __main__.A, y: __main__.B) -> __main__.A" -main:7: note: Signature of "g" in subclass: "def g(self, x: __main__.A) -> __main__.A" +main:7: note: Method "g" of "A": +main:7: note: def g(self, x: __main__.A, y: __main__.B) -> __main__.A +main:7: note: Method "g" of "B": +main:7: note: def g(self, x: __main__.A) -> __main__.A [case testMethodOverridingAcrossDeepInheritanceHierarchy1] import typing @@ -870,8 +874,11 @@ class C(A): def f() -> None: pass [out] main:8: error: Signature of "f" incompatible with supertype "A" -main:8: note: Signature of "f" in superclass: "def f(cls) -> None" -main:8: note: Signature of "f" in subclass: "def f(self) -> None" +main:8: note: Method "f" of "A": +main:8: note: def f(cls) -> None +main:8: note: Method "f" of "B": +main:8: note: def f(self) -> None + [builtins fixtures/classmethod.pyi] -- Properties @@ -1357,8 +1364,13 @@ class B(A): def __add__(self, x: str) -> str: pass [out] tmp/foo.pyi:5: error: Signature of "__add__" incompatible with supertype "A" -tmp/foo.pyi:5: note: Signature of "__add__" in superclass: "def __add__(self, builtins.int) -> builtins.int" -tmp/foo.pyi:5: note: Signature of "__add__" in subclass: "Overload(def __add__(self, builtins.int) -> builtins.int, def __add__(self, builtins.str) -> builtins.str)" +tmp/foo.pyi:5: note: Method "__add__" of "A": +tmp/foo.pyi:5: note: def __add__(self, builtins.int) -> builtins.int +tmp/foo.pyi:5: note: Method "__add__" of "B": +tmp/foo.pyi:5: note: @overload +tmp/foo.pyi:5: note: def __add__(self, builtins.int) -> builtins.int +tmp/foo.pyi:5: note: @overload +tmp/foo.pyi:5: note: def __add__(self, builtins.str) -> builtins.str [case testOperatorMethodOverrideWideningArgumentType] import typing @@ -1429,8 +1441,18 @@ class B(A): def __add__(self, x: type) -> A: pass [out] tmp/foo.pyi:8: error: Signature of "__add__" incompatible with supertype "A" -tmp/foo.pyi:8: note: Signature of "__add__" in superclass: "Overload(def __add__(self, builtins.int) -> foo.A, def __add__(self, builtins.str) -> foo.A)" -tmp/foo.pyi:8: note: Signature of "__add__" in subclass: "Overload(def __add__(self, builtins.int) -> foo.A, def __add__(self, builtins.str) -> foo.A, def __add__(self, builtins.type) -> foo.A)" +tmp/foo.pyi:8: note: Method "__add__" of "A": +tmp/foo.pyi:8: note: @overload +tmp/foo.pyi:8: note: def __add__(self, builtins.int) -> foo.A +tmp/foo.pyi:8: note: @overload +tmp/foo.pyi:8: note: def __add__(self, builtins.str) -> foo.A +tmp/foo.pyi:8: note: Method "__add__" of "B": +tmp/foo.pyi:8: note: @overload +tmp/foo.pyi:8: note: def __add__(self, builtins.int) -> foo.A +tmp/foo.pyi:8: note: @overload +tmp/foo.pyi:8: note: def __add__(self, builtins.str) -> foo.A +tmp/foo.pyi:8: note: @overload +tmp/foo.pyi:8: note: def __add__(self, builtins.type) -> foo.A [case testOverloadedOperatorMethodOverrideWithSwitchedItemOrder] from foo import * @@ -1448,8 +1470,48 @@ class B(A): def __add__(self, x: 'B') -> 'B': pass [out] tmp/foo.pyi:8: error: Signature of "__add__" incompatible with supertype "A" -tmp/foo.pyi:8: note: Signature of "__add__" in superclass: "Overload(def __add__(self, foo.B) -> foo.B, def __add__(self, foo.A) -> foo.A)" -tmp/foo.pyi:8: note: Signature of "__add__" in subclass: "Overload(def __add__(self, foo.A) -> foo.A, def __add__(self, foo.B) -> foo.B)" +tmp/foo.pyi:8: note: Method "__add__" of "A": +tmp/foo.pyi:8: note: @overload +tmp/foo.pyi:8: note: def __add__(self, foo.B) -> foo.B +tmp/foo.pyi:8: note: @overload +tmp/foo.pyi:8: note: def __add__(self, foo.A) -> foo.A +tmp/foo.pyi:8: note: Method "__add__" of "B": +tmp/foo.pyi:8: note: @overload +tmp/foo.pyi:8: note: def __add__(self, foo.A) -> foo.A +tmp/foo.pyi:8: note: @overload +tmp/foo.pyi:8: note: def __add__(self, foo.B) -> foo.B + +[case testOverloadedOperatorsMultilineOutput] +from foo import * +[file foo.pyi] +from typing import overload, Any +class A: + @overload + def __add__(self, x: int) -> 'B': pass +class B(A): + @overload + def __add__(self, x: str) -> 'A': pass + @overload + def __add__(self, x: int) -> 'A': pass + @overload + def __add__(self, x: str) -> 'A': pass + @overload + def __add__(self, x: int) -> 'A': pass + @overload + def __add__(self, x: str) -> 'A': pass +[out] +tmp/foo.pyi:3: error: Single overload definition, multiple required +tmp/foo.pyi:6: error: Signature of "__add__" incompatible with supertype "A" +tmp/foo.pyi:6: note: Method "__add__" of "A": +tmp/foo.pyi:6: note: def __add__(self, builtins.int) -> foo.B +tmp/foo.pyi:6: note: Method "__add__" of "B": +tmp/foo.pyi:6: note: @overload +tmp/foo.pyi:6: note: def __add__(self, builtins.str) -> foo.A +tmp/foo.pyi:6: note: @overload +tmp/foo.pyi:6: note: def __add__(self, builtins.int) -> foo.A +tmp/foo.pyi:6: note: @overload +tmp/foo.pyi:6: note: def __add__(self, builtins.str) -> foo.A +tmp/foo.pyi:6: note: [case testReverseOperatorMethodArgumentType] from typing import Any @@ -2928,8 +2990,11 @@ class B(A): def c() -> None: pass [out] main:11: error: Signature of "a" incompatible with supertype "A" -main:11: note: Signature of "a" in superclass: "def a() -> None" -main:11: note: Signature of "a" in subclass: "def a(self) -> None" +main:11: note: Method "a" of "A": +main:11: note: def a() -> None +main:11: note: Method "a" of "B": +main:11: note: def a(self) -> None + [builtins fixtures/classmethod.pyi] [case testTempNode] diff --git a/test-data/unit/check-dynamic-typing.test b/test-data/unit/check-dynamic-typing.test index 8531d1dce721..892240095be1 100644 --- a/test-data/unit/check-dynamic-typing.test +++ b/test-data/unit/check-dynamic-typing.test @@ -644,8 +644,10 @@ class A(B): pass [out] main:5: error: Signature of "f" incompatible with supertype "B" -main:5: note: Signature of "f" in superclass: "def (x: Any, y: Any) -> Any" -main:5: note: Signature of "f" in subclass: "def f(self, x: __main__.A) -> None" +main:5: note: Method "f": +main:5: note: def (x: Any, y: Any) -> Any +main:5: note: Method "f" of "A": +main:5: note: def f(self, x: __main__.A) -> None [case testInvalidOverrideArgumentCountWithImplicitSignature3] import typing @@ -656,8 +658,10 @@ class A(B): x() [out] main:5: error: Signature of "f" incompatible with supertype "B" -main:5: note: Signature of "f" in superclass: "def f(self, x: __main__.A) -> None" -main:5: note: Signature of "f" in subclass: "def f(self, x: Any, y: Any) -> None" +main:5: note: Method "f" of "B": +main:5: note: def f(self, x: __main__.A) -> None +main:5: note: Method "f" of "A": +main:5: note: def f(self, x: Any, y: Any) -> None -- Don't complain about too few/many arguments in dynamic functions diff --git a/test-data/unit/check-functions.test b/test-data/unit/check-functions.test index 940fd69597fc..51d854108119 100644 --- a/test-data/unit/check-functions.test +++ b/test-data/unit/check-functions.test @@ -35,8 +35,10 @@ class C(A): [out] main:10: error: Signature of "f" incompatible with supertype "A" -main:10: note: Signature of "f" in superclass: "def f(self, *, a: builtins.int, b: builtins.str) -> None" -main:10: note: Signature of "f" in subclass: "def f(self, *, b: builtins.int, a: builtins.str) -> None" +main:10: note: Method "f" of "A": +main:10: note: def f(self, *, a: builtins.int, b: builtins.str) -> None +main:10: note: Method "f" of "C": +main:10: note: def f(self, *, b: builtins.int, a: builtins.str) -> None [case testPositionalOverridingArgumentNameInsensitivity] import typing @@ -61,8 +63,10 @@ class B(A): def f(self, b: int, a: str) -> None: pass [out] main:7: error: Signature of "f" incompatible with supertype "A" -main:7: note: Signature of "f" in superclass: "def f(self, a: builtins.int, b: builtins.str) -> None" -main:7: note: Signature of "f" in subclass: "def f(self, b: builtins.int, a: builtins.str) -> None" +main:7: note: Method "f" of "A": +main:7: note: def f(self, a: builtins.int, b: builtins.str) -> None +main:7: note: Method "f" of "B": +main:7: note: def f(self, b: builtins.int, a: builtins.str) -> None [case testSubtypingFunctionTypes] from typing import Callable diff --git a/test-data/unit/check-generic-subtyping.test b/test-data/unit/check-generic-subtyping.test index 212c3849e666..f282f01fb7a8 100644 --- a/test-data/unit/check-generic-subtyping.test +++ b/test-data/unit/check-generic-subtyping.test @@ -249,8 +249,10 @@ class C(A): [builtins fixtures/list.pyi] [out] main:11: error: Signature of "f" incompatible with supertype "A" -main:11: note: Signature of "f" in superclass: "def [T, S] f(self, x: builtins.list[T`-1], y: builtins.list[S`-2]) -> None" -main:11: note: Signature of "f" in subclass: "def [T] f(self, x: builtins.list[T`-1], y: builtins.list[T`-1]) -> None" +main:11: note: Method "f" of "A": +main:11: note: def [T, S] f(self, x: builtins.list[T`-1], y: builtins.list[S`-2]) -> None +main:11: note: Method "f" of "C": +main:11: note: def [T] f(self, x: builtins.list[T`-1], y: builtins.list[T`-1]) -> None [case testOverrideGenericMethodInNonGenericClassGeneralize] from typing import TypeVar @@ -270,8 +272,10 @@ class D(A): [out] main:12: error: Argument 2 of "f" incompatible with supertype "A" main:14: error: Signature of "f" incompatible with supertype "A" -main:14: note: Signature of "f" in superclass: "def [S] f(self, x: builtins.int, y: S`-1) -> None" -main:14: note: Signature of "f" in subclass: "def [T1 <: builtins.str, S] f(self, x: T1`-1, y: S`-2) -> None" +main:14: note: Method "f" of "A": +main:14: note: def [S] f(self, x: builtins.int, y: S`-1) -> None +main:14: note: Method "f" of "D": +main:14: note: def [T1 <: builtins.str, S] f(self, x: T1`-1, y: S`-2) -> None -- Inheritance from generic types with implicit dynamic supertype diff --git a/test-data/unit/check-overloading.test b/test-data/unit/check-overloading.test index 38eee241649f..fce43b950071 100644 --- a/test-data/unit/check-overloading.test +++ b/test-data/unit/check-overloading.test @@ -787,11 +787,27 @@ class D(A): def f(self, x: str) -> str: return '' [out] tmp/foo.pyi:12: error: Signature of "f" incompatible with supertype "A" -tmp/foo.pyi:12: note: Signature of "f" in superclass: "Overload(def f(self, x: builtins.int) -> builtins.int, def f(self, x: builtins.str) -> builtins.str)" -tmp/foo.pyi:12: note: Signature of "f" in subclass: "Overload(def f(self, x: foo.IntSub) -> builtins.int, def f(self, x: builtins.str) -> builtins.str)" +tmp/foo.pyi:12: note: Method "f" of "A": +tmp/foo.pyi:12: note: @overload +tmp/foo.pyi:12: note: def f(self, x: builtins.int) -> builtins.int +tmp/foo.pyi:12: note: @overload +tmp/foo.pyi:12: note: def f(self, x: builtins.str) -> builtins.str +tmp/foo.pyi:12: note: Method "f" of "B": +tmp/foo.pyi:12: note: @overload +tmp/foo.pyi:12: note: def f(self, x: foo.IntSub) -> builtins.int +tmp/foo.pyi:12: note: @overload +tmp/foo.pyi:12: note: def f(self, x: builtins.str) -> builtins.str tmp/foo.pyi:17: error: Signature of "f" incompatible with supertype "A" -tmp/foo.pyi:17: note: Signature of "f" in superclass: "Overload(def f(self, x: builtins.int) -> builtins.int, def f(self, x: builtins.str) -> builtins.str)" -tmp/foo.pyi:17: note: Signature of "f" in subclass: "Overload(def f(self, x: builtins.int) -> builtins.int, def f(self, x: foo.StrSub) -> builtins.str)" +tmp/foo.pyi:17: note: Method "f" of "A": +tmp/foo.pyi:17: note: @overload +tmp/foo.pyi:17: note: def f(self, x: builtins.int) -> builtins.int +tmp/foo.pyi:17: note: @overload +tmp/foo.pyi:17: note: def f(self, x: builtins.str) -> builtins.str +tmp/foo.pyi:17: note: Method "f" of "C": +tmp/foo.pyi:17: note: @overload +tmp/foo.pyi:17: note: def f(self, x: builtins.int) -> builtins.int +tmp/foo.pyi:17: note: @overload +tmp/foo.pyi:17: note: def f(self, x: foo.StrSub) -> builtins.str [case testOverloadingAndDucktypeCompatibility] from foo import * From cba607e5c25992aba5b03d08e818d01bbbaa1ddb Mon Sep 17 00:00:00 2001 From: Mark Koh Date: Tue, 23 May 2017 17:05:01 -0700 Subject: [PATCH 7/8] Fix ALL the tests! --- mypy/errors.py | 2 +- mypy/messages.py | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/mypy/errors.py b/mypy/errors.py index 59a8a024838e..f59ee05c9aaa 100644 --- a/mypy/errors.py +++ b/mypy/errors.py @@ -269,7 +269,7 @@ def report(self, line: int, column: int, message: str, blocker: bool = False, file: if non-None, override current file as context only_once: if True, only report this exact message once per build origin_line: if non-None, override current context as origin - allow_dups: if True, do not remove duplicate copies of this message (ignored if only_once is True) + allow_dups: if True, don't remove duplicate copies of this (ignored if only_once) """ type = self.type_name[-1] # type: Optional[str] if len(self.function_or_member) > 2: diff --git a/mypy/messages.py b/mypy/messages.py index 7713c7cd9dc4..d4dd21d785aa 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -367,14 +367,15 @@ def note_signature(self, defn: Optional[FunctionLike], context: Context) -> None if defn is not None: max_overload_lines = 6 # Print up to 3 overloads, then truncate pretty_str = defn.pretty_str().split('\n') - func_name = defn.name() if callable(defn.name) else defn.name + # func_name = defn.name() if callable(getattr(defn, 'name')) else getattr(defn, 'name') + func_name = defn.get_name() # TODO: Add filename and line number to method message self.note(' Method {}:'.format(func_name), context, strip=False) for line in pretty_str[:max_overload_lines]: self.note(' {}'.format(line), context, strip=False, allow_dups=True) if len(pretty_str) > max_overload_lines: # Each overload takes up 2 lines because of "@overload" - additional_overrides = int((len(pretty_str) - max_overload_lines)/2) + additional_overrides = int((len(pretty_str) - max_overload_lines) / 2) self.note(' '.format(additional_overrides), context, strip=False, allow_dups=True) From c6f8a1352dbec652dc22c63349753e53a6a550de Mon Sep 17 00:00:00 2001 From: Mark Koh Date: Fri, 26 May 2017 18:34:19 -0400 Subject: [PATCH 8/8] Update message style to use Subclass and Superclass. --- mypy/messages.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/mypy/messages.py b/mypy/messages.py index d4dd21d785aa..ffa8497ac267 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -359,7 +359,7 @@ def format_distinctly(self, type1: Type, type2: Type) -> Tuple[str, str]: return (str1, str2) return (str1, str2) - def note_signature(self, defn: Optional[FunctionLike], context: Context) -> None: + def note_signature(self, annotation: str, defn: Optional[FunctionLike], context: Context) -> None: """ Given a FunctionLike object, pretty print its signature across multiple lines and truncate it if there are lots of overloads @@ -367,12 +367,10 @@ def note_signature(self, defn: Optional[FunctionLike], context: Context) -> None if defn is not None: max_overload_lines = 6 # Print up to 3 overloads, then truncate pretty_str = defn.pretty_str().split('\n') - # func_name = defn.name() if callable(getattr(defn, 'name')) else getattr(defn, 'name') - func_name = defn.get_name() # TODO: Add filename and line number to method message - self.note(' Method {}:'.format(func_name), context, strip=False) + self.note(' {}:'.format(annotation), context, strip=False) for line in pretty_str[:max_overload_lines]: - self.note(' {}'.format(line), context, strip=False, allow_dups=True) + self.note(' {}'.format(line), context, strip=False, allow_dups=True) if len(pretty_str) > max_overload_lines: # Each overload takes up 2 lines because of "@overload" additional_overrides = int((len(pretty_str) - max_overload_lines) / 2) @@ -711,8 +709,8 @@ def signature_incompatible_with_supertype( self.fail('Signature of "{}" incompatible with {}'.format(name, target), context) # Print the original and overridden methods nicely - self.note_signature(original, context) - self.note_signature(override, context) + self.note_signature('Superclass', original, context) + self.note_signature('Subclass', override, context) def argument_incompatible_with_supertype( self, arg_num: int, name: str, name_in_supertype: str,