microsoft / pyright

Static Type Checker for Python
Other
13.12k stars 1.4k forks source link

__init_subclass__ combined with @dataclass doesn't recognize the __init__ args #8710

Closed david-andrew closed 1 month ago

david-andrew commented 1 month ago

Describe the bug if you have some abstract base class that implements __init_subclass__ and in __init_subclass__ applies the dataclass decorator to the class, then in a subclass, pyright fails to identify the args in the constructor correctly. It should function identically to applying @dataclass to both the base and subclass.

Code or Screenshots

from abc import ABC
from dataclasses import dataclass

class MinimalExample(ABC):
    def __init_subclass__(cls):
        super().__init_subclass__()
        dataclass(cls)

class Example(MinimalExample):
    a: int
    b: str

e = Example(1, '2')

In this example, the error says: Pyright: Expected 0 positional arguments on the line e = Example(1, '2'), whereas it should be a valid initialization as the 2 dataclass arguments are provided. It works correctly if you just do:

from abc import ABC
from dataclasses import dataclass

@dataclass
class MinimalExample(ABC):
    ...

@dataclass
class Example(MinimalExample):
    a: int
    b: str

e = Example(1, '2')

VS Code extension or command-line I was running pyright as an extension to the zed editor, though I'm not sure how to get the version. I also ran the lastest pyright (1.1.375) directly from the terminal, and got the exact same results.

erictraut commented 1 month ago

What you're trying to do here won't work with static type checkers. The dataclass function performs a bunch of behind-the-scenes "magic" to synthesize methods, etc. To replicate this behavior type checkers require significant special casing, and this works only if you use @dataclass as a decorator on the class.

You may be able to use dataclass_transform to accomplish your goals.

Code sample in pyright playground

from abc import ABC
from dataclasses import dataclass, field
from typing import dataclass_transform

@dataclass_transform(field_specifiers=(field, ))
class MinimalExample(ABC):
    def __init_subclass__(cls):
        super().__init_subclass__()
        dataclass(cls)

class Example(MinimalExample):
    a: int
    b: str

e = Example(1, "2")

Closing the issue because pyright is working as intended here.

david-andrew commented 1 month ago

Looks like @dataclass_transform did the trick, thanks for the suggestion!