Skip to content

Return type of class decorator is ignored #16241

Closed as not planned
Closed as not planned

Description

@NeilGirdhar

MyPy seems to ignore the return type of class decorators. @AlexWaygood 's suggested I create a minimal repro of this problem:

from typing import Any

class T:
    y: int

def some_decorator(cls: type[Any]) -> type[T]:
    cls.y = 1
    return cls

@some_decorator
class X:
    a: int

def f(x: X) -> None:
    print(x.y)  # No attribute!

This makes it impossible to define a dataclass transform that produces ordinary dataclasses:

import dataclasses
from dataclasses import replace
from typing import Any, ClassVar, Protocol, dataclass_transform


class DataclassInstance(Protocol):
    __dataclass_fields__: ClassVar[dict[str, dataclasses.Field[Any]]]


@dataclass_transform()
def dataclass(cls: type[Any]) -> type[DataclassInstance]:
    return dataclasses.dataclass(cls)

@dataclass
class X:
    a: int

def f(x: X) -> X:
    return replace(x, a=1)  # Error: dataclass expected

Activity

  1. JelleZijlstra commented on Oct 9, 2023

    @JelleZijlstra
    Member

    Duplicate of #3135?

  2. NeilGirdhar commented on Oct 9, 2023

    @NeilGirdhar
    ContributorAuthor

    @JelleZijlstra Not exactly. If I write it the other way X = some_decorator(X), then I get:

    m.py:12: error: Cannot assign to a type  [misc]
    m.py:12: error: Incompatible types in assignment (expression has type "type[T]", variable has type "type[X]")  [assignment]
    m.py:15: error: "X" has no attribute "y"  [attr-defined]
  3. NeilGirdhar commented on Oct 9, 2023

    @NeilGirdhar
    ContributorAuthor

    I can see that the decorator is rightly skipping these checks, but the decorator's return type seems to be ignored. I think it should be assigned to the name it decorates (in this case, X).

    In an ideal world, I would be able to annotate the decorator:

    def dataclass[T](cls: type[T]) -> type[T] & type[DataclassInstance]: ...
  4. erictraut commented on Oct 9, 2023

    @erictraut

    This is covered by #11117.

  5. JelleZijlstra commented on Oct 9, 2023

    @JelleZijlstra
    Member

    Not exactly. If I write it the other way X = some_decorator(X), then I get:

    Those errors look correct. After you define a class, mypy won't let you reassign its name.

    I can see that the decorator is rightly skipping these checks, but the decorator's return type seems to be ignored. I think it should be assigned to the name it decorates (in this case, X).

    That's #3135, right? In any case I don't think mypy is misinterpreting your code. If you annotate your decorator with @dataclass_transform, you assert that it returns the same class with dataclass-like semantics. The return type annotation doesn't matter there.

    If your request is that mypy infers an intersection type in this particular case, that seems out of scope until intersections are actually part of the type system.

  6. NeilGirdhar commented on Oct 9, 2023

    @NeilGirdhar
    ContributorAuthor

    Those errors look correct. After you define a class, mypy won't let you reassign its name.

    Yeah, both of these checks do make sense. No argument there.

    That's #3135, right?

    Yes, sorry I didn't see this comment, which covers my case.

    In any case I don't think mypy is misinterpreting your code. If you annotate your decorator with @dataclass_transform, you assert that it returns the same class with dataclass-like semantics. The return type annotation doesn't matter there.

    It depends on what you mean by "dataclass-like semantics". My problem is that @dataclass_transform does not (and should not) assert that it returns the same class, but inheriting from DataclassLike—that is, a so-decorated class won't be passable to fields and replace. I initially thought so too, but this comment explained it.

    If your request is that mypy infers an intersection type in this particular case, that seems out of scope until intersections are actually part of the type system.

    Yes, I know. I'm just pointing that out so that we don't debate about the lost type information (type[T]) from applying the decorator.

    This is covered by #11117.

    Yes, exactly right, thanks.

    In any case, I guess we can close this as a duplicate of #3135. Seems to me that fixing that should fix the two examples I gave above?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugmypy got something wrong

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions