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

Unable to use typing.Self with Enums #18345

Open
youtux opened this issue Dec 27, 2024 · 7 comments
Open

Unable to use typing.Self with Enums #18345

youtux opened this issue Dec 27, 2024 · 7 comments

Comments

@youtux
Copy link

youtux commented Dec 27, 2024

Bug Report

I expect to be able to use typing.Self for classmethods of an Enum subclass, but mypy fails the type check for such methods.

To Reproduce
https://mypy-play.net/?mypy=master&python=3.12&gist=9f529a65422ee83aba0312c91e74a935

from enum import *
from typing import *

class Foo(Enum):
    BAR = "bar"

    @classmethod
    def get_bar(cls) -> Self:
        return cls.BAR

Expected Behavior
Type checking should pass.

Actual Behavior
I get the following error:

main.py:9: error: Incompatible return value type (got "Foo", expected "Self")  [return-value]
Found 1 error in 1 file (checked 1 source file)

Your Environment

  • Mypy version used: 1.13.0, 1.14.0, master branch
  • Mypy command-line flags:
  • Mypy configuration options from mypy.ini (and other config files):
  • Python version used: 3.12, 3.13
@youtux youtux added the bug mypy got something wrong label Dec 27, 2024
@erictraut
Copy link

I think mypy's behavior is correct here. See this pyright issue for details.

@youtux
Copy link
Author

youtux commented Dec 27, 2024

I mean, Foo.BAR is an instance of Foo, so why shouldn't I be able to use Self to describe it, and avoid repetition?

In my real world use case I have an enum with many class methods that return Self or list[Self], so it would be quite a repetition to mention Foo instead in all these places.

@erictraut
Copy link

why shouldn't I be able to use Self to describe it?

Self doesn't mean "any instance of the containing class", and it's not intended to be a shortcut to specify that class in an annotation. Self has a very specific meaning and intended use in the typing spec. It is a type variable whose type has an upper bound of the containing class. It is also the type of the self or cls parameter. For more details, refer to this section of the typing spec.

An annotation of Self in your example indicates that the method will return an instance of whatever class was passed for the cls parameter. If your intent is for the method to return an instance of Foo, the method should be annotated as such.

@brianschubert
Copy link
Collaborator

In this specific case, it's not possible to subclass Foo (right?), so I think it would be valid for mypy to assume that Self always gets bound to Foo. Mypy already makes a similar assumption when Self appears in the body of class that is annotated with @final.

@brianschubert brianschubert added feature topic-enum and removed bug mypy got something wrong labels Dec 27, 2024
@AlexWaygood
Copy link
Member

In this specific case, it's not possible to subclass Foo (right?), so I think it would be valid for mypy to assume that Self always gets bound to Foo. Mypy already makes a similar assumption when Self appears in the body of class that is annotated with @final.

I made the same argument in #16558 (comment), but others argued that doing this would require a change to the typing spec. I still think it would be a useful improvement that would make life more ergonomic for users of enums without any detriment to type safety (but I haven't got the time/energy to push for a change to the spec on this point right now, if it does indeed require a chnage to the spec)

@AlexWaygood AlexWaygood added the topic-self-types Types for self label Dec 28, 2024
@youtux
Copy link
Author

youtux commented Dec 30, 2024

I found a workaround that will let me use Self: https://mypy-play.net/?mypy=master&python=3.12&gist=35585f6a10b359f79d7e660d547aaa5c

from enum import *
from typing import *

T = TypeVar("T")

class _FooImpl:
    @classmethod
    def get_bar(cls) -> Self:
        return cls.BAR  # type: ignore

class Foo(_FooImpl, Enum):
    BAR = "bar"


reveal_type(Foo.get_bar())

although it would be nicer if we could just define the get_bar function in the actual Foo enum class, as this workaround really destroys the reading flow.

@youtux
Copy link
Author

youtux commented Dec 30, 2024

plus, it requires to use a # type: ignore, which kind of defeats the purpose

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

No branches or pull requests

4 participants