mypy incorrectly complains about incompatible type error
Opened this issue · 2 comments
Bug Report
mypy is incorrectly complaining that the arguments to a function have an incompatible type, when used in the context of a class attribute.
In the example below, we are creating a library of fields (StringField
, IntegerField
, etc.), to be used in a runtime schema validation library. In addition, we create a OneOfField
that takes a list of sub-fields via the with_
function. Creating a OneOfField
works fine by itself, but when assigned to a class attribute, mypy incorrectly complains about a type error.
from typing import Generic, TypeVar, Union, assert_type, cast, overload, Self
InputType = TypeVar("InputType")
OutputType = TypeVar("OutputType")
class Field(Generic[InputType, OutputType]):
# We want both InputType and OutputType to be used in functions like the
# ones below, so they can't be marked covariant or contravariant.
def validate(self, input: Any) -> InputType:
return cast(InputType, input)
def render(self, input: InputType) -> OutputType:
return cast(OutputType, input)
def serialize(self, output: OutputType) -> str:
return ""
class StringField(Field[str, str]):
pass
class IntegerField(Field[int, int]):
pass
class BooleanField(Field[bool, bool]):
pass
def input_type(field: Field[InputType, OutputType]) -> InputType:
return cast(InputType, field)
OneOfInputType = TypeVar("OneOfInputType")
OneOfOutputType = TypeVar("OneOfOutputType")
AInputType = TypeVar("AInputType")
AOutputType = TypeVar("AOutputType")
BInputType = TypeVar("BInputType")
BOutputType = TypeVar("BOutputType")
CInputType = TypeVar("CInputType")
COutputType = TypeVar("COutputType")
class OneOfField(
Generic[OneOfInputType, OneOfOutputType],
Field[OneOfInputType, OneOfOutputType],
):
fields: list[Field]
def __init__(self) -> None:
super().__init__()
self.fields = []
@overload
def with_(
self,
a: Field[AInputType, AOutputType],
b: Field[BInputType, BOutputType],
) -> "OneOfField[Union[AInputType, BInputType], Union[AOutputType, BOutputType]]": ...
@overload
def with_(
self,
a: Field[AInputType, AOutputType],
b: Field[BInputType, BOutputType],
c: Field[CInputType, COutputType],
) -> "OneOfField[Union[AInputType, BInputType, CInputType], Union[AOutputType, BOutputType, COutputType]]": ...
def with_(
self,
a: Field[AInputType, AOutputType],
b: Field[BInputType, BOutputType],
c: Field[CInputType, COutputType] | None = None,
) -> "OneOfField":
if c is None:
self.fields = [a, b]
else:
self.fields = [a, b, c]
return self
def return_self(self) -> Self:
return self
# This works:
field = OneOfField().with_(StringField(), IntegerField())
assert_type(field, OneOfField[Union[str, int], Union[str, int]])
assert_type(input_type(field), Union[str, int])
# This doesn't work:
class MyModel:
# error: Argument 1 to "with_" of "OneOfField" has incompatible type "StringField"; expected "Field[str | int, str | int]" [arg-type]
# error: Argument 2 to "with_" of "OneOfField" has incompatible type "IntegerField"; expected "Field[str | int, str | int]" [arg-type]
foo: str | int = input_type(OneOfField().with_(StringField(), IntegerField()))
# This works?
class MyModel2:
foo: str | int = input_type(OneOfField().with_(StringField(), IntegerField()).return_self())
To Reproduce
https://gist.github.com/mypy-play/4619ce6d001d7c7c6994d2b6c912424a
This does not repro in Pyright: link
Expected Behavior
No errors.
Actual Behavior
mypy raises an invalid error saying the argument to with_
is invalid.
Your Environment
- Mypy version used: 1.13.0
- Mypy command-line flags: N/A
- Mypy configuration options from
mypy.ini
(and other config files): N/A - Python version used: 3.13
Smaller repro with one typevar (also excluding potential self
influence):
from typing import Any, Generic, TypeVar
_I = TypeVar("_I")
class Field(Generic[_I]): pass
class StringField(Field[str]): pass
class IntegerField(Field[int]): pass
_A = TypeVar("_A")
_B = TypeVar("_B")
class OneOfField(Field[_I]):
def __init__(
self: "OneOfField[_A | _B]",
a: Field[_A],
b: Field[_B],
) -> None:
pass
field: OneOfField[str|int] = OneOfField(StringField(), IntegerField()) # \
# E: Argument 1 to "OneOfField" has incompatible type "StringField"; expected "Field[str | int]" [arg-type] \
# E: Argument 2 to "OneOfField" has incompatible type "IntegerField"; expected "Field[str | int]" [arg-type]
Thanks for simplifying!