-
-
Notifications
You must be signed in to change notification settings - Fork 2.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
False positive: signature incompatible with supertype #13106
Comments
I came across this issue while looking for a different thing entirely, but IMO, I don't think this is actually a bug in mypy at all. At least not from a technical standpoint. You see, in To make this pattern more convenient, I would suggest some sort of As an alternative to overloads, you could probably make use of a StrOrInt = TypeVar('StrOrInt', str, int)
class Interface(metaclass=ABCMeta):
@abstractmethod
def foo(self, a: StrOrInt) -> StrOrInt:
pass
class Implementation(Interface):
def foo(self, a: StrOrInt) -> StrOrInt:
return a It's not the most elegant thing, especially if |
I thought I had tried that early on in my original use case which is why I didn't try it here again. Thanks for pointing this out. Still, mypy doesn't make it easy to figure out what is not compatible 😞 |
What about this case?: from __future__ import annotations
from typing import overload, Generic, TypeVar
T = TypeVar("T")
class Interface(Generic[T]):
@overload
def foo(self: Interface[None]) -> int:
...
@overload
def foo(self, a: T) -> int:
...
def foo(self, a: T | None = None) -> str | int:
return 1
class Implementation(Interface[int]):
def foo(self, a: int) -> int:
return 2 Here the other branch of the overload don't apply to the subclass, but Mypy still complains. |
Edit: To be clear, the fact that you are ignoring the input param and returning a static integer doesn't matter. MyPy only cares about your type signatures, the same as your users who are (hopefully) reading your documentation. Edit2: I misread the code, see: #13106 (comment) |
But that is not true! |
Can I though? >>> Implementation().foo()
TypeError Traceback (most recent call last)
Input In [2], in <cell line: 24>()
22 def foo(self, a: int) -> int:
23 return 2
---> 24 Implementation().foo()
TypeError: Implementation.foo() missing 1 required positional argument: 'a' You need to actually make it optional: class Implementation(Interface[int]):
def foo(self, a: int | None = None) -> int:
return 2 This passes MyPy no problem. I do realize I misread your original code as having |
Not for |
Ah, but here's the trick: class Implementation(Interface[None]):
def foo(self) -> int:
return 2 will still fail with: a.py:22: error: Signature of "foo" incompatible with supertype "Interface"
a.py:22: note: Superclass:
a.py:22: note: @overload
a.py:22: note: def foo(self) -> int
a.py:22: note: @overload
a.py:22: note: def foo(self, a: None) -> int
a.py:22: note: Subclass:
a.py:22: note: def foo(self) -> int
Found 1 error in 1 file (checked 1 source file) Why? Because This passes: class Implementation(Interface[None]):
def foo(self, a: None = None) -> int:
return 2 |
Maybe I am not explaining myself: the problem is that Mypy complains even when I inherit from |
Now I'm seeing it. I would say the problem there is you have an overload that only applies to one specific type of subclass, which smells like an anti-pattern to me. Personally I wouldn't see this as a bug but rather an unsupported feature. Do you have an example of this idea in another language with strong typing? |
No, I don't have an example. I had this problem in the context of typing scikit-learn |
I see, that makes sense. Just to make sure we're on the same page, you're trying to make TransformerMixin generic? So in the scenario where |
Yes, I am the maintainer of scikit-fda, a library that depends on scikit-learn. As scikit-learn is not typed, I am subclassing some classes in order to provide typing in my own library: class TransformerMixin( # noqa: D101
ABC,
Generic[Input, Output, Target],
sklearn.base.TransformerMixin, # type: ignore[misc]
):
@overload
def fit(
self: TransformerNoTarget,
X: Input,
) -> TransformerNoTarget:
pass
@overload
def fit(
self: SelfType,
X: Input,
y: Target,
) -> SelfType:
pass
def fit( # noqa: D102
self: SelfType,
X: Input,
y: Target | None = None,
) -> SelfType:
return self
@overload
def fit_transform(
self: TransformerNoTarget,
X: Input,
) -> Output:
pass
@overload
def fit_transform(
self,
X: Input,
y: Target,
) -> Output:
pass
def fit_transform( # noqa: D102
self,
X: Input,
y: Target | None = None,
**fit_params: Any,
) -> Output:
if y is None:
return self.fit( # type: ignore[no-any-return]
X,
**fit_params,
).transform(X)
return self.fit( # type: ignore[no-any-return]
X,
y,
**fit_params,
).transform(X) |
While I see how your approach would be more convenient to your users with only a single mixin class, I think having class TransformerMixin( # noqa: D101
ABC,
Generic[Input, Output, Target],
sklearn.base.TransformerMixin, # type: ignore[misc]
):
def fit(
self: SelfType,
X: Input,
y: Target,
) -> SelfType:
return self
def fit_transform( # noqa: D102
self,
X: Input,
y: Target,
**fit_params: Any,
) -> Output:
return self.fit( # type: ignore[no-any-return]
X,
**fit_params,
).transform(X)
class TransformerNoTarget(TransformerMixin[Input, Output, None]):
@overload
def fit(
self: SelfType,
X: Input,
) -> SelfType:
pass
@overload
def fit(
self: SelfType,
X: Input,
y: None = None,
) -> SelfType:
pass
def fit( # noqa: D102
self: SelfType,
X: Input,
y: None = None,
) -> SelfType:
return self
@overload
def fit_transform(
self: SelfType,
X: Input,
**fit_params: Any,
) -> Output:
pass
@overload
def fit_transform(
self,
X: Input,
y: None = None,
**fit_params: Any,
) -> Output:
pass
def fit_transform( # noqa: D102
self,
X: Input,
y: None = None,
**fit_params: Any,
) -> Output:
return super().fit(X, y, **fit_params) The above code passes MyPy and allows a |
Well, that introduces additional problems:
|
In order to explore this more, I tried to code up the same example in Java and immediately ran into an issue: // Interface.java
public interface Interface<T> {
public Integer foo();
public Integer foo(T a);
}
// Implementation.java
public class Implementation implements Interface<Integer> {
public Integer foo(Integer a) { return a; }
} There's no way to provide a type for As expected, this throws an error because we didn't implement the version that doesn't take any parameters: $ javac Implementation.java
Implementation.java:1: error: Implementation is not abstract and does not override abstract method foo() in Interface
public class Implementation implements Interface<Integer> {
^
1 error I could be missing something as my Java isn't the best, but I'm pretty sure there is no physical way to specify this kind of behavior at the pure typing level. The fact that we can provide a type for I would say that Also, just to be clear, I'm not a MyPy dev. I've just been using MyPy for a few years and just happened to have the answer to the original question and misread yours thinking it was a lot simpler than it is haha. |
That is an accepted and documented way to do that: https://mypy.readthedocs.io/en/stable/more_types.html?highlight=Advanced%20uses#restricted-methods-in-generic-classes |
Well then, looks like I missed some parts of the docs. In that case, I'm pretty sure we're dealing with a bug and you should open a dedicated ticket. Specifically on inheriting overloaded, restricted methods from generic classes. |
Bug Report
Mypy falsely reports an overridden method to be incompatible with the supertype.
To Reproduce
Create the following file:
Actual Behavior
If I replace the union with
str
(and force the last line to returnstr
), the error goes away. Pyright does not report any problems with this code either way. This isn't aboutABCMeta
or abstract methods either – if I remove all that, the error is still reported (but then pyright would complain thatInterface.foo()
does not return anything – which strangely mypy does not).Your Environment
mypy.ini
(and other config files): (none)The text was updated successfully, but these errors were encountered: