basedpyright icon indicating copy to clipboard operation
basedpyright copied to clipboard

generics on frozen dataclasses should be inferred as covariant instead of invariant

Open DetachHead opened this issue 11 months ago • 5 comments

Description

https://github.com/microsoft/pyright/issues/9226

DetachHead avatar Apr 03 '25 00:04 DetachHead

Note: dataclasses now have a __replace__ method, so if you just do this naive generation:

@dataclass(frozen=True)
class Fruit[C]:
    name: str
    allowed_colors: Collection[C]

    # *pretend that this is synthesized*
    def __replace__(self, *, name: str = ..., allowed_colors: Collection[C] = ...) -> Self:
       ...

Then Fruit has to be invariant. So I think you will need to make it generic:

@dataclass(frozen=True)
class Fruit[C]:
    name: str
    allowed_colors: Collection[C]

    # *pretend that this is synthesized*
    def __replace__[C2=C](self, *, name: str = ..., allowed_colors: Collection[C2] = ...) -> Fruit[C2]:
       ...

Well... this is not allowed because Type parameter "C2" has a default type that refers to one or more type variables that are out of scope. So maybe it needs to be an overload?

@dataclass(frozen=True)
class Fruit[C]:
    name: str
    allowed_colors: Collection[C]

    # *pretend that this is synthesized*
    @overload
    def __replace__(self, *, name: str = ...) -> Fruit[C]: ...
    @overload
    def __replace__[C2](self, *, name: str = ..., allowed_colors: Collection[C2]) -> Fruit[C2]: ...

if there are N type variables, should this synthesize 2**N overloads?

decorator-factory avatar Oct 01 '25 23:10 decorator-factory

Also see: https://github.com/python/mypy/issues/19694

decorator-factory avatar Oct 01 '25 23:10 decorator-factory

relevant: #171

DetachHead avatar Oct 04 '25 03:10 DetachHead

@DetachHead I think this should be un-closed, because @dataclasses.dataclass-annotated classes still suffer from this issue

decorator-factory avatar Oct 11 '25 06:10 decorator-factory

oh true

DetachHead avatar Oct 11 '25 07:10 DetachHead