Skip to content

@dataclass_transform metaclass + __new__ -> "Base" collapses subclasses to base #3552

@jurasic-pf

Description

@jurasic-pf

Describe the Bug

When a class with a @dataclass_transform()-decorated metaclass declares def __new__(cls, ...) -> "Base", pyrefly types every subclass instance as the base class instead of the concrete subclass.

This results in the following problems:

  • Subclass-specific methods (including __call__) are not visible, the expression subclass_instance(...) raises not-callable.
  • Functions parameterised on an intermediate abstract subclass reject calls with concrete subclasses (bad-argument-type).
  • Attributes defined only on the subclass are reported as missing.

I hit this when using equinox which uses this pattern in its base class:

@dataclass_transform(field_specifiers=(...))
class Meta(BaseMeta):
    def __call__(cls, *args: object, **kwargs: object): ...

class Module(Hashable, metaclass=Meta):
    def __new__(cls, *args: object, **kwargs: object) -> "Module":  # <-- here
        ...

Pyrefly typed every Subclass(...) as Module, generating dozens of spurious not-callable / bad-argument-type errors in unrelated files.

Minimal Reproducer

from typing_extensions import dataclass_transform

@dataclass_transform()
class Meta(type):
    pass

class Base(metaclass=Meta):
    def __new__(cls, *args: object, **kwargs: object) -> "Base":
        return super().__new__(cls)

class Greeter(Base):
    name: str
    def __call__(self, greeting: str) -> str:
        return self.name

g = Greeter(name="world")
out: str = g("hello")   # pyrefly: Expected a callable, got `Base`

Pyrefly output (1.0.0)

ERROR Expected a callable, got `Base` [not-callable]

Pyright output

0 errors

Notes

  • Removing @dataclass_transform() makes pyright and pyrefly agree (both say Base). The bug is in the interaction between dataclass_transform and the literal-string -> "Base" return on __new__.

Sandbox Link

https://pyrefly.org/sandbox/?project=N4IgZglgNgpgziAXKOBDAdgEwEYHsAeAdAA4CeS4ATrgLYAEALqcROgOYD6M%2BDM6cEXPzoQaxXJQZ1MqBqgDGUVHDgcGlDHDASaAHXT6AAjLmLlq9Zu2UaACgCU%2BsyroBZGHNtNiMe4n10gXTE5vpOSi4AQsowtjQeChFwALzucn4BQZgwYHQcHOgwAO75topwADR0AFSolGxwiHS42ABWMPIMVdXVANZFdQ1NLe2d9nQAtAB8dLog0XAwc-7oQWt0lB4ArpSrcFs%2BlA6E%2BYUlHGVQcI4G6M5wdADimx4wRwu%2BK2voqPFNcOpMoFsrl8vJUFAoKVFlAwFU2C8GKw2P91ONpnQAZQvutApsGDs9jBYYQfvEwuw6Mknoi3rYyTBknMihIoJg5jdcFsGKjKFS6GxbHMABbEqC4DlBADEwVImzAUFITQAovgfJ0YJg6Kg6ODIahsLB4bgpAADD6mkAVEBkeWKwgMWhQCgygAKcpyisxGBwBF1QkgbB2skE6EI%2BhlAGUYDA6MKGAxiI0APTJ22e0iECRsZN8ZOYXDyODJ%2BQBiBBjRIoTJujWbUAN1Q0ANsH96EDwarq1wxC7cHD6DIDGFQgm9beAiE-LmAGZCABGABMc30AG03tRKHAALr6LboUTiSSaiaYCCbToQcf8gDk5fQEhgN-0D4YE02AEctueT70YKQJgUeR4AeakbwGXZn3QEAAF9rQUJFxwAMWgGAKDQLA8CIMhYKAA

(Only applicable for extension issues) IDE Information

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions