Skip to content
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

Class decorators are not type checked #3135

Open
JelleZijlstra opened this issue Apr 5, 2017 · 9 comments
Open

Class decorators are not type checked #3135

JelleZijlstra opened this issue Apr 5, 2017 · 9 comments
Labels
bug mypy got something wrong priority-1-normal

Comments

@JelleZijlstra
Copy link
Member

decorator: int = 3

@decorator  # no error
class A:
    pass

class B:
    pass
NewB = decorator(B)  # E: "int" not callable

Mypy doesn't appear to do any checking of class decorators. Applying the decorator manually works.

@JelleZijlstra
Copy link
Member Author

Guido and I found out about this in python/typeshed#1136 (comment). Looks like this can be fixed somewhere around analyze_class_decorator in semanal.py.

@ilevkivskyi
Copy link
Member

A use case reported in #3483

from typing import Type

class A:
    pass

def deco(cls) -> Type[A]:
    return A

@deco
class B:
    pass

def func(cls: Type[A]):
    assert cls is A

func(B)

might be non-trivial to implement. I am adding this example here, so that we will not forget about it.

@euresti
Copy link
Contributor

euresti commented Feb 3, 2018

Another possibly easier to solve problem is that the functions called are not type-checked

from typing import TypeVar, Callable
_C = TypeVar('_C', bound=type)
def blah(x:int=17) -> Callable[[_C], _C]: ...

@blah(y=18)
class A:
    pass

blah(y=18)

Or even more fun

x=17

@x(huh=17)
class B:
    pass

x(huh=17)

euresti added a commit to euresti/mypy that referenced this issue Feb 5, 2018
This does the bare minimum, it checks that the calls are well-type.
It does not check whether applying the decorator makes sense.

Helps with python#3135
ilevkivskyi pushed a commit that referenced this issue Feb 9, 2018
It checks that the calls are well-type and that applying the decorators works.
It does not check/apply the end result.

Helps with #3135
@reinhrst
Copy link

I'm thinking how this could/should be used in combination with Protocols to deal with class decorators adding some methods to a class

class FooBar(Protocol):
   def bar(self) -> int:
     return 1

T = TypeVar("T", bound=Type)
def add_bar(cls: T) -> Intersection[FooBar, T]:
   def bar(self) -> int:
       return 2
  cls.bar = bar

@add_bar
class Foo: pass

Foo().bar()

Now this depends on Intersection which was described in PEP483, but not implemented; I don't see another way of doing this cleanly right now.

@erezsh
Copy link

erezsh commented Jul 15, 2022

Are there plans to fix this? It's strange that mypy doesn't support a core syntax.

@rileyjohngibbs
Copy link

It looks like some of these examples (e.g. the x=17 example above) may have been fixed, so in the interest of having a current, non-working example:

from typing import Any

def foo(_: Any) -> int:
    return 5

@foo
class Foo:
    pass

def test_foo() -> None:
    assert Foo + 7 == 12  # Does not pass typechecking

The error for the final line is: error: Unsupported operand types for + ("Type[Foo]" and "int") [operator].

Function decorators seem to work fine though:

def qux(func: Callable[[], str]) -> Callable[[], int]:
    def wrap() -> int:
        return int(func())
    return wrap

@qux
def five() -> str:
    return "5"

def test_qux() -> None:
    assert five() + 10 == 15  # Passes typechecking

@DetachHead
Copy link
Contributor

It looks like some of these examples (e.g. the x=17 example above) may have been fixed

yeah, class decorators are now typechecked, but the issue now is that they are unable to change the type of the class they're decorating. see #11117 (comment)

@erezsh
Copy link

erezsh commented Oct 10, 2023

yeah, class decorators are now typechecked, but the issue now is that they are unable to change the type of the class they're decorating. see #11117 (comment)

Imho, this is important functionality.

For example, it could be useful for implementers of alternative dataclasses, such as pydantic or sqlmodel. Especially once support for intersection gets into mypy. I believe it would be much better than the current approach, that states:

Mypy does not yet recognize aliases of dataclasses.dataclass, and will probably never recognize dynamically computed decorators.

(taken from https://mypy.readthedocs.io/en/stable/additional_features.html)

It would also obsolete the hacky and limited typing_extensions.dataclass_transform.

Just my two (hopefully correct) cents.

@NeilGirdhar
Copy link
Contributor

It would also obsolete the hacky and limited typing_extensions.dataclass_transform.

I don't think it would. The dataclass_transform marks that a class has fields made with field-makers. Whereas an annotated decorator can tell the type checker that a class can be passed to dataclasses functions like fields and replace.

These are separate concepts. You can have classes with fields that can't be passed to dataclass functions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug mypy got something wrong priority-1-normal
Projects
None yet
Development

No branches or pull requests

8 participants