Skip to content

Return type of class decorator is ignored #16241

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
NeilGirdhar opened this issue Oct 9, 2023 · 6 comments
Closed

Return type of class decorator is ignored #16241

NeilGirdhar opened this issue Oct 9, 2023 · 6 comments
Labels
bug mypy got something wrong

Comments

@NeilGirdhar
Copy link
Contributor

NeilGirdhar commented Oct 9, 2023

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
@JelleZijlstra
Copy link
Member

Duplicate of #3135?

@NeilGirdhar
Copy link
Contributor Author

@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]

@NeilGirdhar
Copy link
Contributor Author

NeilGirdhar commented Oct 9, 2023

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]: ...

@erictraut
Copy link

This is covered by #11117.

@JelleZijlstra
Copy link
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.

@NeilGirdhar
Copy link
Contributor Author

NeilGirdhar commented Oct 9, 2023

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?

@JelleZijlstra JelleZijlstra closed this as not planned Won't fix, can't repro, duplicate, stale Oct 9, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug mypy got something wrong
Projects
None yet
Development

No branches or pull requests

3 participants