python - Is this a false positive [override] error? "Signature of (method) incompatible with supertype" - Stac

admin2025-05-02  0

Although the method signature in Sub is compatible with Super, mypy rejects the override: Signature of "method" incompatible with supertype "Super".

I'm using

  • python 3.13.1
  • mypy 1.14.0

First, I made following test.pyi.

from typing import overload

class Super:
    def method(self, arg:Other|Super)->Super: pass

class Sub(Super):
    @overload
    def method(self, arg:Other|Sub)->Sub: pass
    @overload
    def method(self, arg:Super)->Super: pass

class Other: pass

Then, when I ran mypy test.pyi in command line, mypy produced the following diagnostic:

test.pyi:7: error: Signature of "method" incompatible with supertype "Super"  [override]
test.pyi:7: note:      Superclass:
test.pyi:7: note:          def method(self, arg: Other | Super) -> Super
test.pyi:7: note:      Subclass:
test.pyi:7: note:          @overload
test.pyi:7: note:          def method(self, arg: Other | Sub) -> Sub
test.pyi:7: note:          @overload
test.pyi:7: note:          def method(self, arg: Super) -> Super
Found 1 error in 1 file (checked 1 source file)

I checked type of both Super.method and Sub.method's I/O, and found that there's no pattern that violates LSP (Liskov Substitution Principle).

Overloaded Sub.method can

  • input arg of type Other|Super (= Other|Sub + Super) and
  • output type Super (= Sub + Super ).

Above input and output type matches the signature of Super.method .

So, I have no idea to be Signature of "method" incompatible with supertype "Super".

Following is I/O table of method.

I\O Super.method Sub.method Compared to Super, Sub's return is: Adhering to LSP*
Other Super Sub narrower Yes
Sub Super Sub narrower Yes
Super Super Super the same Yes
Other|Sub Super Sub narrower Yes
Other|Super Super Super** the same Yes

Although the method signature in Sub is compatible with Super, mypy rejects the override: Signature of "method" incompatible with supertype "Super".

I'm using

  • python 3.13.1
  • mypy 1.14.0

First, I made following test.pyi.

from typing import overload

class Super:
    def method(self, arg:Other|Super)->Super: pass

class Sub(Super):
    @overload
    def method(self, arg:Other|Sub)->Sub: pass
    @overload
    def method(self, arg:Super)->Super: pass

class Other: pass

Then, when I ran mypy test.pyi in command line, mypy produced the following diagnostic:

test.pyi:7: error: Signature of "method" incompatible with supertype "Super"  [override]
test.pyi:7: note:      Superclass:
test.pyi:7: note:          def method(self, arg: Other | Super) -> Super
test.pyi:7: note:      Subclass:
test.pyi:7: note:          @overload
test.pyi:7: note:          def method(self, arg: Other | Sub) -> Sub
test.pyi:7: note:          @overload
test.pyi:7: note:          def method(self, arg: Super) -> Super
Found 1 error in 1 file (checked 1 source file)

I checked type of both Super.method and Sub.method's I/O, and found that there's no pattern that violates LSP (Liskov Substitution Principle).

Overloaded Sub.method can

  • input arg of type Other|Super (= Other|Sub + Super) and
  • output type Super (= Sub + Super ).

Above input and output type matches the signature of Super.method .

So, I have no idea to be Signature of "method" incompatible with supertype "Super".

Following is I/O table of method.

I\O Super.method Sub.method Compared to Super, Sub's return is: Adhering to LSP*
Other Super Sub narrower Yes
Sub Super Sub narrower Yes
Super Super Super the same Yes
Other|Sub Super Sub narrower Yes
Other|Super Super Super** the same Yes

*The LSP requires that the return type of a sub method be narrower or equal to the return type of the super method.

**Sub.method returns

  • Sub when Other input and
  • Super when Super input,

so it returns Sub|Super when Other|Super input.
Sub|Super means Super.

As you can see from the table above, there are no patterns that violate LSP.


So, I think that mypy error message Signature of "method" incompatible with supertype "Super" is incorrect.

Is my code wrong?

Also, if my code is not wrong and mypy's error message is wrong, where can I ask?


P.S. Quick way to hide the error.

Although it's not a complete solution, I found a simple way to hide the error.

from typing import overload

class Super:
    def method(self, arg:Other|Super)->Super: pass

class Sub(Super):
    @overload
    def method(self, arg:Other|Sub)->Sub: pass
    @overload
    def method(self, arg:Super)->Super: pass
    # Start of hiding error
    @overload
    def method( # type: ignore[overload-cannot-match]
               self, arg:Other|Super)->Super: pass
    # End of hiding error

class Other: pass

As a last overload, I added a Sub.method with the exact same signature as Super.method. However, when this issue is resolved in a future version, it will mean that the code I added will not be reached and we should get a [overload-cannot-match] error. Therefore, I added # type: ignore[overload-cannot-match] to ignore this error in advance.

(At first glance, it may seem like I am simply silencing errors with type: ignore, but this is not relevant to mypy as of now. This is merely a deterrent against future error.)

Share Improve this question edited Jan 2 at 5:13 平田智剛 asked Jan 2 at 1:09 平田智剛平田智剛 671 silver badge5 bronze badges 9
  • (let's wait for someone to confirm my comment - both mypy and pyright are mad at this override) Your code is safe and satisfies LSP. However, proving that in general case is really hard. Neither of two major typecheckers does that (pyright is more explicit: "No overload signature in override is compatible with base method"). Your table ignores the most interesting type: Other | Sub, the whole union. It can't be dispatched to either of signatures, so should resolve to a union of overloaded sigs. You can report this to mypy and pyright, but don't expect a quick fix - that's really hard. – STerliakov Commented Jan 2 at 1:28
  • Known for mypy, please don't report there: github.com/python/mypy/issues/12379. But I don't see such a ticket in pyright repo, maybe report it there: github.com/microsoft/pyright/issues – STerliakov Commented Jan 2 at 1:30
  • Thank you for your answer. If it has already been reported on github, Is it better to close this question? But I'm new to stackoverflow and don't know how to close questions. – 平田智剛 Commented Jan 2 at 1:49
  • 1 @dROOOze that was my first idea too. Mypy allows such calls by using a "union" signature, so that's fine - a union return type will be produced. mypy-play.net/… (if there is a matching overload for every union component, the return type is a simplified union of all matched overloads' return types) Do you also have a feeling that it didn't happen in early mypy versions?.. – STerliakov Commented Jan 2 at 3:14
  • 1 @STerliakov no, this definitely happened in previous versions (although I don't know how far back). I just forgot about this behaviour, and when it activated I didn't actually look in to where it came from. The union return behaviour of all overloads tended to always match the return type of my implementation functions, so I naively thought mypy was looking at the implementation when it couldn't figure out the overload choice, my bad! – dROOOze Commented Jan 2 at 3:20
 |  Show 4 more comments

1 Answer 1

Reset to default 4

As pointed out in a pyright ticket about this, the typing spec mentions this behaviour explicitly:

If a callable B is overloaded with two or more signatures, it is assignable to callable A if at least one of the overloaded signatures in B is assignable to A

There's a mypy ticket asking about the same problem.

However, your code is in fact safe, and the table in your post proves that. That's just a limitation of the specification and/or typecheckers.

As discussed in comments, it may seem like the problem is the union type itself (Other | Super can't be dispatched to either of overloads), but it isn't true: mypy uses union math in that case, and overloaded call return type is a union of return types of matched overloads if all union members can be dispatched to one of them. Here's the source where this magic happens, read the comments there if you're interested - the main checker path is documented well.

Now, given that your code doesn't typecheck only due to a typechecker/spec issue, you have several options:

  • # type: ignore[override] - why not? Your override is safe, just tell mypy to STFU.

  • Add a signature to match the spec requirements as in your last paragraph. But please don't add an unused ignore comment - there's a --warn-unused-ignores flag for mypy which is really useful. Don't add such ignore, just add a free-text comment explaining the problem and linking here or to the mypy issue.

  • Just extend the second signature. That's safe - overloads are tried in order, the first match wins (well, not exactly, but in simple cases without *args/**kwargs and ParamSpec that's true):

    class Sub(Super):
        @overload
        def method(self, arg: Other | Sub) -> Sub: ...
        @overload
        def method(self, arg: Other | Super) -> Super: ...
    

But I'd just go with an ignore comment and explain the problem:

class Sub(Super):
    # The override is safe, but doesn't conform to the spec.
    # https://github.com/python/mypy/issues/12379
    @overload  # type: ignore[override]
    def method(self, arg: Other | Sub) -> Sub: ...
    @overload
    def method(self, arg: Super) -> Super: ...
转载请注明原文地址:http://anycun.com/QandA/1746137970a92098.html