Repository: python/mypy

Issue:
(🐞) `Enum.name` is `Any` in 3.11 / support `enum.property`
```py
from enum import Enum

value: Enum

reveal_type(value)
reveal_type(value.name)
```
```
> mypy --python-version 3.10 test.py
test.py:5: note: Revealed type is "enum.Enum"
test.py:6: note: Revealed type is "str"
Success: no issues found in 1 source file

> mypy --python-version 3.11 test.py
test.py:5: note: Revealed type is "enum.Enum"
test.py:6: note: Revealed type is "Any"
Success: no issues found in 1 source file

```

in 3.11 `name` is decorated with `enum.property` which is a subtype of `types.DynamicClassAttribute` (aka, `builtins.property`)

from https://github.com/python/typeshed/issues/7564
Related #12220


Discussion:
To paraphase in a way others won't need to dig throuhg typeshed, typeshed does this:

```
if sys.version_info >= (3, 11):
    class property(types.DynamicClassAttribute):
        ...
    _magic_enum_attr = property
else:
    _magic_enum_attr = types.DynamicClassAttribute
```
And `types.DynamicClassAttribute` is an alias for `builtins.property`.
It might be worth pointing out that mypy doesn't get the behaviour precisely correct on Python <=3.10 either:

```python
from enum import Enum

class Foo(Enum):
    BAR = 'bar'
    BAZ = 'baz'

reveal_type(Foo.name)  # No error from mypy, raises AttributeError at runtime
reveal_type(Foo.value)  # No error from mypy, raises AttributeError at runtime
```

On Python <= 3.10, `Enum.name` and `Enum.value` are special-cased by the `enum` module to allow this magical behaviour, where `name` and `value` are read-only properties that raise `AttributeError` if accessed from the class rather than an instance. This is to allow enumerations where members have `name` or `value` as their names:

```python
>>> from enum import Enum
>>> class Dictionary(Enum):
...     name = 'a word used to identify something'
...     animal = 'a living creature'
...
>>> Dictionary.name
<Dictionary.name: 'a word used to identify something'>
>>> Dictionary.name.name
'name'
```

In Python 3.11, `name` and `value` are no longer special-cased by the `enum` module. Instead, the new `enum.property` class generalises this behaviour, so that users can create their own "magic enum properties":

```python
>>> import enum
>>> class CustomEnum(enum.Enum):
...     @enum.property
...     def foo(self):
...         return 'MAGIC'
...
>>> class MyEnum(CustomEnum):
...     foo = 'foo'
...
>>> MyEnum.foo
<MyEnum.foo: 'foo'>
>>> MyEnum.foo.foo
'MAGIC'
```
Here's the dedicated issue regarding the incorrect behaviour on <=3.10: #4654
Hope this is proper place to report, as I have experienced another issue related to `Enum.name` behaviour after upgrading my codebase to Python 3.11 (and latest mypy 0.991).

With Python 3.10 following code checked OK,

```py
from enum import Enum, unique


@unique
class SortEnum(Enum):
     name = "name"
     name_desc = "-name"
```

```
❯ pipx run mypy==0.991 --python-version=3.10 test.py
Success: no issues found in 1 source file
```

But with Python 3.11 it throwing,

```
❯ pipx run mypy==0.991 --python-version=3.11 test.py
test.py:6:12: error: Incompatible types in assignment (expression has type "str", base class "Enum" defined the type as "Callable[[Enum], str]")  [assignment]
Found 1 error in 1 file (checked 1 source file)
```

As a quick workaround it might be possible to rename `SortEnum.name` to `SortEnum.name_asc`, and in meantime looking forward to proper fix 🙏 

Resolving patch:
diff --git a/mypy/semanal.py b/mypy/semanal.py
--- a/mypy/semanal.py
+++ b/mypy/semanal.py
@@ -1448,7 +1448,13 @@ def visit_decorator(self, dec: Decorator) -> None:
                 dec.var.is_classmethod = True
                 self.check_decorated_function_is_method("classmethod", dec)
             elif refers_to_fullname(
-                d, ("builtins.property", "abc.abstractproperty", "functools.cached_property")
+                d,
+                (
+                    "builtins.property",
+                    "abc.abstractproperty",
+                    "functools.cached_property",
+                    "enum.property",
+                ),
             ):
                 removed.append(i)
                 dec.func.is_property = True


Test patch:
diff --git a/test-data/unit/pythoneval.test b/test-data/unit/pythoneval.test
--- a/test-data/unit/pythoneval.test
+++ b/test-data/unit/pythoneval.test
@@ -1712,3 +1712,26 @@ A = Type[int] | str
 B: TypeAlias = Type[int] | str
 [out]
 m.pyi:5: note: Revealed type is "typing._SpecialForm"
+
+[case testEnumNameWorkCorrectlyOn311]
+# flags: --python-version 3.11
+import enum
+
+class E(enum.Enum):
+    X = 1
+    Y = 2
+    @enum.property
+    def foo(self) -> int: ...
+
+e: E
+reveal_type(e.name)
+reveal_type(e.value)
+reveal_type(E.X.name)
+reveal_type(e.foo)
+reveal_type(E.Y.foo)
+[out]
+_testEnumNameWorkCorrectlyOn311.py:11: note: Revealed type is "builtins.str"
+_testEnumNameWorkCorrectlyOn311.py:12: note: Revealed type is "Union[Literal[1]?, Literal[2]?]"
+_testEnumNameWorkCorrectlyOn311.py:13: note: Revealed type is "Literal['X']?"
+_testEnumNameWorkCorrectlyOn311.py:14: note: Revealed type is "builtins.int"
+_testEnumNameWorkCorrectlyOn311.py:15: note: Revealed type is "builtins.int"