Closed Jacob-Friedberg closed 2 months ago
This issue extends to trying to access the enum by name. Does not highlight, does not show in the IntelliSense menu as an auto complete
This behavior is due to the way that nonmember
and member
are defined in the typeshed stubs. Here's the definition of nonmember
, for example.
class nonmember(Generic[_EnumMemberT]):
value: _EnumMemberT
def __init__(self, value: _EnumMemberT) -> None: ...
When @nonmember
is applied as a decorator to a class definition, it transforms that class into a nonmember
object. This object has no method random
, so an attempt to access this attribute will be seen as an error by a static type checker.
To fix this, the typeshed definition of nonmember
and member
would need to be updated. Their constructors should probably return the value they are passed.
class nonmember:
def __new__(cls, value: _EnumMemberT) -> _EnumMemberT: ...
Alternatively, a __get__
method could be added:
class nonmember(Generic[_EnumMemberT]):
value: _EnumMemberT
def __init__(self, value: _EnumMemberT) -> None: ...
def __get__(self, instance: Any, owner: type | None = None, /) -> _EnumMemberT: ...
If you would like to pursue one of these fixes, please file an issue and/or a PR in the typeshed project.
@erictraut
If you would like to pursue one of these fixes, please file an issue and/or a PR in the typeshed project.
I submitted an issue per your suggestion and the typeshed team has come to a conclusion:
-DaverBall
I'm not sure I agree that this is a typeshed issue, after all nonmember and member at runtime are just simple marker classes, with neither a custom new nor are they descriptors, so lying about that, while not particularly harmful, since they'll be stripped away in classes inheriting from Enum or using the EnumMeta metaclass anyways, it's still inaccurate. It would e.g. hide bugs where they're inappropriately used in other classes.
Enums require special-casing in order to be correctly supported by type checkers, and member/nonmember are part of that special casing, since it can override the default decision of whether or not that attribute is considered a member or not. So I'm not fully convinced it's worth lying about what these marker classes are.
That being said, it might make sense to make these markers generic, simply to give type checkers more flexibility at which point the special-casing is applied, i.e. change them to:
class member(Generic[_EnumMemberT]): value: _EnumMemberT def __init__(self, value: _EnumMemberT) -> None: ...
class nonmember(Generic[_EnumMemberT]): value: _EnumMemberT def init(self, value: _EnumMemberT) -> None: ...
> That would allow the special casing to happen at a purely semantic level after type inference, rather than at a syntactic level.
-srittau
> agree with -Daverball. The typeshed annotations follow the implementation exactly. This requires type checker special casing instead of typeshed hacks, although I'd be fine with making the classes generic, if that helps.
@erictraut
Im not too familiar with how pylance works internally to be able to answer their question about making the classes generic. Would making those two classes generic allow pylance/enable a feature to be written to pick up the decorated enum/inherited classes?
Could someone from the pylance team please transfer this issue to pyright? I'd like to think more about it given the feedback from the typeshed maintainers and explore possible solutions.
The "magic" performed by EnumMeta
is significant. This requires massive special-casing inside of static type checkers (at best) and makes it impossible or infeasible to statically type check certain enum-related features (at worse). Pyright's support for enum type checking already goes far beyond any other Python static type checker, but there are limits to what can be done.
This is addressed in pyright 1.1.372.
Environment data
Code Snippet
Expected behavior
Inherited functions should show up in highlighting and auto complete when either member/nonmember enum decorator is used.
Actual behavior
Inherited functions do not show up in IntelliSense or highlighting when the class contains an enum decorator such as member or nonmember.
I believe the default behavior for classes inside Enums is to become a member of that enum (effectively
@member
) until python 3.13 according to this deprecation warning: "DeprecationWarning: In 3.13 classes created inside an enum will not become a member. Use themember
decorator to keep the current behavior."The highlighting and IntelliSense work for the undecorated case (not the functionality I need).
Logs