Skip to content

Support finding metaclasses from intersection-typed bases #3282

@jvdillon

Description

@jvdillon

When a metaclass __get__ returns Intersection[_T, type[SomeGeneric[TypeVar]]], dataclass_transform field inheritance breaks for child classes.

from __future__ import annotations
from typing import TYPE_CHECKING, Any, ClassVar, Generic, dataclass_transform
from typing_extensions import TypeVar
import dataclasses

if TYPE_CHECKING:
    from ty_extensions import Intersection

_T = TypeVar("_T")
_P = TypeVar("_P", default=Any)
_O = TypeVar("_O")

class Makeable(Generic[_P]):
    def make(self) -> _P: ...

class MyMeta(type):
    if TYPE_CHECKING:
        def __get__(cls: _T, obj: object, owner: type[_O]) -> Intersection[_T, type[Makeable[_O]]]:
            return cls

@dataclass_transform(kw_only_default=True)
class FigMeta(MyMeta):
    def __new__(mcls, name, bases, attrs, **kw):
        cls = super().__new__(mcls, name, bases, attrs)
        if "__slots__" not in cls.__dict__:
            cls = dataclasses.dataclass(cls, kw_only=True, slots=True)
        return cls

class Fig(Makeable[_P], metaclass=FigMeta):
    __slots__: ClassVar[tuple[str, ...]] = ()

class Parent:
    class Config(Fig):
        x: int = 0

class Child(Parent):
    class Config(Parent.Config):
        y: int = 1

Child.Config(x=1, y=2)  # warning: y is unknown-argument

Parent's field x is recognized. Child's own field y is not.

The trigger is specifically a TypeVar inside a parameterized generic inside type[] inside the Intersection. Replacing Makeable[_O] with any of these makes the warning disappear:

  • Makeable[Any]
  • Makeable (bare)
  • object

Expected behavior: Child class fields should be recognized regardless of what Intersection contains.

Workaround: moving @dataclass_transform from the metaclass to the base class avoids the issue:

# Instead of:
@dataclass_transform(kw_only_default=True)
class FigMeta(MyMeta): ...

class Fig(Makeable[_P], metaclass=FigMeta): ...

# Do:
class FigMeta(MyMeta): ...

@dataclass_transform(kw_only_default=True)
class Fig(Makeable[_P], metaclass=FigMeta): ...

Both placements are valid per PEP 681, but only the base class placement works when the metaclass has __get__ returning Intersection[_T, type[Generic[TypeVar]]].

ty version: 0.0.30

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions