Skip to content

Commit

Permalink
Detailed 'signature incompatible with supertype' for non-callables (#…
Browse files Browse the repository at this point in the history
…15263)

Previously the "signature incompatible with supertype" error only
included detailed comparison when both types are callables; now it will
compare in all cases.
  • Loading branch information
ikonst committed May 21, 2023
1 parent 0334ebc commit c2d02a3
Show file tree
Hide file tree
Showing 7 changed files with 156 additions and 34 deletions.
4 changes: 3 additions & 1 deletion mypy/checker.py
Expand Up @@ -1988,7 +1988,9 @@ def check_method_override_for_base_with_name(
# If the attribute is read-only, allow covariance
pass
else:
self.msg.signature_incompatible_with_supertype(defn.name, name, base.name, context)
self.msg.signature_incompatible_with_supertype(
defn.name, name, base.name, context, original=original_type, override=typ
)
return False

def bind_and_map_method(
Expand Down
41 changes: 31 additions & 10 deletions mypy/messages.py
Expand Up @@ -1136,13 +1136,18 @@ def signature_incompatible_with_supertype(
name_in_super: str,
supertype: str,
context: Context,
original: FunctionLike | None = None,
override: FunctionLike | None = None,
*,
original: ProperType,
override: ProperType,
) -> None:
code = codes.OVERRIDE
target = self.override_target(name, name_in_super, supertype)
self.fail(f'Signature of "{name}" incompatible with {target}', context, code=code)

original_str, override_str = format_type_distinctly(
original, override, options=self.options, bare=True
)

INCLUDE_DECORATOR = True # Include @classmethod and @staticmethod decorators, if any
ALLOW_DUPS = True # Allow duplicate notes, needed when signatures are duplicates
ALIGN_OFFSET = 1 # One space, to account for the difference between error and note
Expand All @@ -1152,13 +1157,10 @@ def signature_incompatible_with_supertype(
# note: def f(self) -> str
# note: Subclass:
# note: def f(self, x: str) -> None
if (
original is not None
and isinstance(original, (CallableType, Overloaded))
and override is not None
and isinstance(override, (CallableType, Overloaded))
):
self.note("Superclass:", context, offset=ALIGN_OFFSET + OFFSET, code=code)
self.note(
"Superclass:", context, offset=ALIGN_OFFSET + OFFSET, allow_dups=ALLOW_DUPS, code=code
)
if isinstance(original, (CallableType, Overloaded)):
self.pretty_callable_or_overload(
original,
context,
Expand All @@ -1167,8 +1169,19 @@ def signature_incompatible_with_supertype(
allow_dups=ALLOW_DUPS,
code=code,
)
else:
self.note(
original_str,
context,
offset=ALIGN_OFFSET + 2 * OFFSET,
allow_dups=ALLOW_DUPS,
code=code,
)

self.note("Subclass:", context, offset=ALIGN_OFFSET + OFFSET, code=code)
self.note(
"Subclass:", context, offset=ALIGN_OFFSET + OFFSET, allow_dups=ALLOW_DUPS, code=code
)
if isinstance(override, (CallableType, Overloaded)):
self.pretty_callable_or_overload(
override,
context,
Expand All @@ -1177,6 +1190,14 @@ def signature_incompatible_with_supertype(
allow_dups=ALLOW_DUPS,
code=code,
)
else:
self.note(
override_str,
context,
offset=ALIGN_OFFSET + 2 * OFFSET,
allow_dups=ALLOW_DUPS,
code=code,
)

def pretty_callable_or_overload(
self,
Expand Down
6 changes: 5 additions & 1 deletion test-data/unit/check-abstract.test
Expand Up @@ -790,7 +790,11 @@ class A(metaclass=ABCMeta):
def x(self) -> int: pass
class B(A):
@property
def x(self) -> str: return "no" # E: Signature of "x" incompatible with supertype "A"
def x(self) -> str: return "no" # E: Signature of "x" incompatible with supertype "A" \
# N: Superclass: \
# N: int \
# N: Subclass: \
# N: str
b = B()
b.x() # E: "str" not callable
[builtins fixtures/property.pyi]
Expand Down
115 changes: 97 additions & 18 deletions test-data/unit/check-classes.test
Expand Up @@ -115,7 +115,11 @@ class Base:
__hash__ = None

class Derived(Base):
def __hash__(self) -> int: # E: Signature of "__hash__" incompatible with supertype "Base"
def __hash__(self) -> int: # E: Signature of "__hash__" incompatible with supertype "Base" \
# N: Superclass: \
# N: None \
# N: Subclass: \
# N: def __hash__(self) -> int
pass

# Correct:
Expand Down Expand Up @@ -157,7 +161,11 @@ class Base:


class Derived(Base):
def partial_type(self) -> int: # E: Signature of "partial_type" incompatible with supertype "Base"
def partial_type(self) -> int: # E: Signature of "partial_type" incompatible with supertype "Base" \
# N: Superclass: \
# N: List[Any] \
# N: Subclass: \
# N: def partial_type(self) -> int
...


Expand Down Expand Up @@ -567,11 +575,45 @@ class A:

class B(A):
@dec
def f(self) -> int: pass # E: Signature of "f" incompatible with supertype "A"
def g(self) -> int: pass # E: Signature of "g" incompatible with supertype "A"
def f(self) -> int: pass # E: Signature of "f" incompatible with supertype "A" \
# N: Superclass: \
# N: def f(self) -> str \
# N: Subclass: \
# N: str
def g(self) -> int: pass # E: Signature of "g" incompatible with supertype "A" \
# N: Superclass: \
# N: str \
# N: Subclass: \
# N: def g(self) -> int
@dec
def h(self) -> str: pass

[case testOverrideIncompatibleWithMultipleSupertypes]
class A:
def f(self, *, a: int) -> None:
return

class B(A):
def f(self, *, b: int) -> None: # E: Signature of "f" incompatible with supertype "A" \
# N: Superclass: \
# N: def f(self, *, a: int) -> None \
# N: Subclass: \
# N: def f(self, *, b: int) -> None
return

class C(B):
def f(self, *, c: int) -> None: # E: Signature of "f" incompatible with supertype "B" \
# N: Superclass: \
# N: def f(self, *, b: int) -> None \
# N: Subclass: \
# N: def f(self, *, c: int) -> None \
# E: Signature of "f" incompatible with supertype "A" \
# N: Superclass: \
# N: def f(self, *, a: int) -> None \
# N: Subclass: \
# N: def f(self, *, c: int) -> None
return

[case testOverrideStaticMethodWithStaticMethod]
class A:
@staticmethod
Expand Down Expand Up @@ -4223,11 +4265,12 @@ class A:
def a(self) -> None: pass
b = 1
class B(A):
a = 1
def b(self) -> None: pass
[out]
main:5: error: Incompatible types in assignment (expression has type "int", base class "A" defined the type as "Callable[[A], None]")
main:6: error: Signature of "b" incompatible with supertype "A"
a = 1 # E: Incompatible types in assignment (expression has type "int", base class "A" defined the type as "Callable[[A], None]")
def b(self) -> None: pass # E: Signature of "b" incompatible with supertype "A" \
# N: Superclass: \
# N: int \
# N: Subclass: \
# N: def b(self) -> None

[case testVariableProperty]
class A:
Expand Down Expand Up @@ -6166,7 +6209,11 @@ import a
[file b.py]
import a
class Sub(a.Base):
def x(self) -> int: pass # E: Signature of "x" incompatible with supertype "Base"
def x(self) -> int: pass # E: Signature of "x" incompatible with supertype "Base" \
# N: Superclass: \
# N: int \
# N: Subclass: \
# N: def x(self) -> int

[file a.py]
import b
Expand All @@ -6182,7 +6229,11 @@ import a
import c
class Sub(a.Base):
@c.deco
def x(self) -> int: pass # E: Signature of "x" incompatible with supertype "Base"
def x(self) -> int: pass # E: Signature of "x" incompatible with supertype "Base" \
# N: Superclass: \
# N: int \
# N: Subclass: \
# N: def x(*Any, **Any) -> Tuple[int, int]

[file a.py]
import b
Expand All @@ -6204,7 +6255,11 @@ import a
import c
class Sub(a.Base):
@c.deco
def x(self) -> int: pass # E: Signature of "x" incompatible with supertype "Base"
def x(self) -> int: pass # E: Signature of "x" incompatible with supertype "Base" \
# N: Superclass: \
# N: int \
# N: Subclass: \
# N: def x(*Any, **Any) -> Tuple[int, int]

[file a.py]
import b
Expand Down Expand Up @@ -7687,13 +7742,29 @@ class Parent:
foobar = TypeVar("foobar")

class Child(Parent):
def foo(self, val: int) -> int: # E: Signature of "foo" incompatible with supertype "Parent"
def foo(self, val: int) -> int: # E: Signature of "foo" incompatible with supertype "Parent" \
# N: Superclass: \
# N: None \
# N: Subclass: \
# N: def foo(self, val: int) -> int
return val
def bar(self, val: str) -> str: # E: Signature of "bar" incompatible with supertype "Parent"
def bar(self, val: str) -> str: # E: Signature of "bar" incompatible with supertype "Parent" \
# N: Superclass: \
# N: None \
# N: Subclass: \
# N: def bar(self, val: str) -> str
return val
def baz(self, val: float) -> float: # E: Signature of "baz" incompatible with supertype "Parent"
def baz(self, val: float) -> float: # E: Signature of "baz" incompatible with supertype "Parent" \
# N: Superclass: \
# N: None \
# N: Subclass: \
# N: def baz(self, val: float) -> float
return val
def foobar(self) -> bool: # E: Signature of "foobar" incompatible with supertype "Parent"
def foobar(self) -> bool: # E: Signature of "foobar" incompatible with supertype "Parent" \
# N: Superclass: \
# N: None \
# N: Subclass: \
# N: def foobar(self) -> bool
return False

x: Parent.foo = lambda: 5
Expand Down Expand Up @@ -7761,7 +7832,11 @@ class B:
...
class C(B):
@property
def foo(self) -> int: # E: Signature of "foo" incompatible with supertype "B"
def foo(self) -> int: # E: Signature of "foo" incompatible with supertype "B" \
# N: Superclass: \
# N: def foo(self) -> int \
# N: Subclass: \
# N: int
...
[builtins fixtures/property.pyi]

Expand All @@ -7771,7 +7846,11 @@ class B:
def foo(self) -> int:
...
class C(B):
def foo(self) -> int: # E: Signature of "foo" incompatible with supertype "B"
def foo(self) -> int: # E: Signature of "foo" incompatible with supertype "B" \
# N: Superclass: \
# N: int \
# N: Subclass: \
# N: def foo(self) -> int
...
[builtins fixtures/property.pyi]

Expand Down
6 changes: 5 additions & 1 deletion test-data/unit/check-dataclasses.test
Expand Up @@ -198,7 +198,11 @@ class Base:
@dataclass
class BadDerived1(Base):
def foo(self) -> int: # E: Dataclass attribute may only be overridden by another attribute \
# E: Signature of "foo" incompatible with supertype "Base"
# E: Signature of "foo" incompatible with supertype "Base" \
# N: Superclass: \
# N: int \
# N: Subclass: \
# N: def foo(self) -> int
return 1

@dataclass
Expand Down
12 changes: 10 additions & 2 deletions test-data/unit/check-functions.test
Expand Up @@ -2848,7 +2848,11 @@ class C(A): # inverted order of decorators
class D(A):
@property
@override
def f(self) -> int: pass # E: Signature of "f" incompatible with supertype "A"
def f(self) -> int: pass # E: Signature of "f" incompatible with supertype "A" \
# N: Superclass: \
# N: str \
# N: Subclass: \
# N: int
[builtins fixtures/property.pyi]
[typing fixtures/typing-full.pyi]

Expand Down Expand Up @@ -2877,7 +2881,11 @@ class C(A):
def f(self, value: str) -> None: pass

class D(A):
@override # E: Signature of "f" incompatible with supertype "A"
@override # E: Signature of "f" incompatible with supertype "A" \
# N: Superclass: \
# N: str \
# N: Subclass: \
# N: int
@property
def f(self) -> int: pass

Expand Down
6 changes: 5 additions & 1 deletion test-data/unit/check-plugin-attrs.test
Expand Up @@ -1935,7 +1935,11 @@ class Sub(Base):
last_name: str

@property
def name(self) -> int: ... # E: Signature of "name" incompatible with supertype "Base"
def name(self) -> int: ... # E: Signature of "name" incompatible with supertype "Base" \
# N: Superclass: \
# N: str \
# N: Subclass: \
# N: int

# This matches runtime semantics
reveal_type(Sub) # N: Revealed type is "def (*, name: builtins.str, first_name: builtins.str, last_name: builtins.str) -> __main__.Sub"
Expand Down

0 comments on commit c2d02a3

Please sign in to comment.