microsoft/pyright

Parameter 2 type mismatch: base parameter is type "Any", override parameter is type "Any" with interface methods

Closed this issue · 4 comments

This is a somewhat niche problem, but I have come across it in practice twice already. It seems like the issue is caused by unimplemented methods in the base class which have type annotations being implemented in sub-classes. The resulting error looks like this:

Method "foo" overrides class "A" in an incompatible manner
  Parameter 2 type mismatch: base parameter is type "Any", override parameter is type "Any"

Adding the "Any" annotation doesn't seem to help either.

Sample code which causes the issue

from typing import Any, Callable

class A(object):
    def _foo_unimplemented(self, input: Any) -> None:
        raise NotImplementedError
    foo: Callable[..., Any] = _foo_unimplemented

class B(A):
    def foo(self, obj: Any):
        pass
    # Result: Method "foo" overrides class "A" in an incompatible manner.  Parameter 2 type mismatch: base parameter is type "Any", override parameter is type "Any"

# Real world examples
import tornado.web
class RunsHandler(tornado.web.RequestHandler):
    def initialize(self, con: Any):
        self._con = con
    # Result: Method "initialize" overrides class "RequestHandler" in an incompatible manner. Parameter 2 type mismatch: base parameter is type "Any", override parameter is type "Any"

import torch.nn
class Model(torch.nn.Module):
    def forward(self, data: Any):
        return data
    # Result: Method "forward" overrides class "Module" in an incompatible manner. Parameter 2 type mismatch: base parameter is type "Any", override parameter is type "Any"

class Model(torch.nn.Module):
    def forward(self, data):
        return data
    # Result: Method "forward" overrides class "Module" in an incompatible manner. Parameter 2 type mismatch: base parameter is type "Any", override parameter is type "Unknown"

The current behavior is correct, or at least as intended. The intention behind the "reportIncompatibleMethodOverride" check is to detect cases where a base class is making assumptions that a subclass is violating. In your example above, A.foo is annotated as a callable type that accepts an arbitrary number of parameters (both positional and keyword) and returns any type. That implies that class A may call this function with any number of parameters. But B.foo is attempting to override it with a method that accepts only one positional parameter (obj) and no keyword parameters. If A calls it with any other combination of parameters, the program will crash at runtime. So this is exactly the sort of inconsistency that "reportIncompatibleMethodOverride" was designed to detect.

I think you have a few options here:

  1. Change the signature of foo in the base class so it reflects the fact that it accepts only one positional parameter.
  2. Change the signature of foo in the subclass so it accepts *args and **kwargs.
  3. If you don't want this check to be performed, you can disable it globally for your project. Or you can disable it for a specific file by using a comment (# pyright: reportIncompatibleMethodOverride=false).

Hello. I also encountered this issue and tried to address it, but option 2 is not working. What should I do?

from typing import Any, Callable

class A(object):
    def _foo_unimplemented(self, input: Any) -> None:
        raise NotImplementedError

    foo: Callable[..., Any] = _foo_unimplemented


class B(A):
    def foo(self):
        pass
    # Result:
    # Method "foo" overrides class "A" in an incompatible manner
    # Positional parameter count mismatch; base method has 2, but override has 1
    # Parameter "args" is missing in override (reportIncompatibleMethodOverride)


class C(A):
    def foo(self, *args: Any):
        pass
    # Result:
    # Method "foo" overrides class "A" in an incompatible manner
    # Positional parameter count mismatch; base method has 2, but override has 2 (reportIncompatibleMethodOverride)


class D(A):
    def foo(self, *args: Any, **kwargs: Any):
        pass
    # Result:
    # Method "foo" overrides class "A" in an incompatible manner
    # Positional parameter count mismatch; base method has 2, but override has 3 (reportIncompatibleMethodOverride)

Yeah, I would expect option 2 to work in this case. It doesn't because of the self parameter that's present in the override but is not in the base. However, the ... should be treated specially in this case.

I've made this change, and it will be included in the next release.

This is addressed in pyright 1.1.306, which I just published. It will also be included in a future release of pylance.