Repository: python/mypy

Issue:
Regression: Stub generator no longer generates `__members__` for enum class
<!--
  If you're new to mypy and you're not sure whether what you're experiencing is a mypy bug, please see the "Question and Help" form
  instead.
  Please also consider:

  - checking our common issues page: https://mypy.readthedocs.io/en/stable/common_issues.html
  - searching our issue tracker: https://github.com/python/mypy/issues to see if
    it's already been reported
  - asking on gitter chat: https://gitter.im/python/typing
-->

**Bug Report**

In mypy version 0.942, the stub generator used to create `__members__` fields like:

```python
class MyEnum:
    __members__: ClassVar[dict] = ...  # read-only
```

In our case, `MyEnum` is a C++ `enum class` exposed to Python via pybind11. The type annotation seemed to be correct, because at runtime, `__members__` does exist.

In mypy version 0.950 the `__members__` field is no longer annotated, which means that existing value code no longer type checks properly.

**To Reproduce**

- Create a temporary venv:

```sh
$ mkdir some_temporary_folder
$ cd some_temporary_folder
$ virtualenv ./tmp_venv -p /usr/bin/python3.8
$ . ./tmp_venv/bin/activate
$ pip install -U pip setuptools
$ pip install mypy==0.950 pybind11==2.9.0
```

- Create a file **`native_enum_test.cpp`** with content:

```c++
#include <cstddef>
#include <memory>
#include <pybind11/pybind11.h>

namespace py = pybind11;

enum class MyEnum { FOO, BAR };

PYBIND11_MODULE(native_enum_test, module) {
  pybind11::enum_<MyEnum>(module, "MyEnum", pybind11::arithmetic())
      .value("FOO", MyEnum::FOO)
      .value("BAR", MyEnum::BAR);
}
```

- Compile via:

```sh
$ c++ -O3 -Wall -shared -std=c++17 -fPIC $(python3 -m pybind11 --includes) native_enum_test.cpp -o native_enum_test.so
```

- Run the stub generator:

```sh
$ stubgen -p native_enum_test
```

**Expected Behavior**

As far as I can see, `__members__` should be generated by the stub generator.

Check against the previous mypy version: `pip install mypy==0.942` and re-running the stub generator produces:

<details>
<summary>Generator output mypy 0.942</summary>

```python
from typing import ClassVar

class MyEnum:
    __doc__: ClassVar[str] = ...  # read-only
    __members__: ClassVar[dict] = ...  # read-only
    BAR: ClassVar[MyEnum] = ...
    FOO: ClassVar[MyEnum] = ...
    __entries: ClassVar[dict] = ...
    def __init__(self, value: int) -> None: ...
    def __eq__(self, other: object) -> bool: ...
    def __ge__(self, other: object) -> bool: ...
    def __getstate__(self) -> int: ...
    def __gt__(self, other: object) -> bool: ...
    def __hash__(self) -> int: ...
    def __index__(self) -> int: ...
    def __int__(self) -> int: ...
    def __le__(self, other: object) -> bool: ...
    def __lt__(self, other: object) -> bool: ...
    def __ne__(self, other: object) -> bool: ...
    def __setstate__(self, state: int) -> None: ...
    @property
    def name(self) -> str: ...
    @property
    def value(self) -> int: ...
```

</details>

**Actual Behavior**

`__members__` is missing in the stub generator output.

<details>
<summary>Generator output mypy 0.950</summary>

```python
from typing import ClassVar

class MyEnum:
    BAR: ClassVar[MyEnum] = ...
    FOO: ClassVar[MyEnum] = ...
    __entries: ClassVar[dict] = ...
    def __init__(self, value: int) -> None: ...
    def __eq__(self, other: object) -> bool: ...
    def __ge__(self, other: object) -> bool: ...
    def __getstate__(self) -> int: ...
    def __gt__(self, other: object) -> bool: ...
    def __hash__(self) -> int: ...
    def __index__(self) -> int: ...
    def __int__(self) -> int: ...
    def __le__(self, other: object) -> bool: ...
    def __lt__(self, other: object) -> bool: ...
    def __ne__(self, other: object) -> bool: ...
    def __setstate__(self, state: int) -> None: ...
    @property
    def name(self) -> str: ...
    @property
    def value(self) -> int: ...
```

</details>

**Your Environment**

<!-- Include as many relevant details about the environment you experienced the bug in -->

- Mypy version used: 0.950
- Mypy command-line flags: not relevant, this is about the stub generator
- Mypy configuration options from `mypy.ini` (and other config files): not relevant, this is about the stub generator
- Python version used: 3.8.10
- Operating system and version: Ubuntu 20.04


Regression: Stub generator no longer generates `__members__` for enum class
<!--
  If you're new to mypy and you're not sure whether what you're experiencing is a mypy bug, please see the "Question and Help" form
  instead.
  Please also consider:

  - checking our common issues page: https://mypy.readthedocs.io/en/stable/common_issues.html
  - searching our issue tracker: https://github.com/python/mypy/issues to see if
    it's already been reported
  - asking on gitter chat: https://gitter.im/python/typing
-->

**Bug Report**

In mypy version 0.942, the stub generator used to create `__members__` fields like:

```python
class MyEnum:
    __members__: ClassVar[dict] = ...  # read-only
```

In our case, `MyEnum` is a C++ `enum class` exposed to Python via pybind11. The type annotation seemed to be correct, because at runtime, `__members__` does exist.

In mypy version 0.950 the `__members__` field is no longer annotated, which means that existing value code no longer type checks properly.

**To Reproduce**

- Create a temporary venv:

```sh
$ mkdir some_temporary_folder
$ cd some_temporary_folder
$ virtualenv ./tmp_venv -p /usr/bin/python3.8
$ . ./tmp_venv/bin/activate
$ pip install -U pip setuptools
$ pip install mypy==0.950 pybind11==2.9.0
```

- Create a file **`native_enum_test.cpp`** with content:

```c++
#include <cstddef>
#include <memory>
#include <pybind11/pybind11.h>

namespace py = pybind11;

enum class MyEnum { FOO, BAR };

PYBIND11_MODULE(native_enum_test, module) {
  pybind11::enum_<MyEnum>(module, "MyEnum", pybind11::arithmetic())
      .value("FOO", MyEnum::FOO)
      .value("BAR", MyEnum::BAR);
}
```

- Compile via:

```sh
$ c++ -O3 -Wall -shared -std=c++17 -fPIC $(python3 -m pybind11 --includes) native_enum_test.cpp -o native_enum_test.so
```

- Run the stub generator:

```sh
$ stubgen -p native_enum_test
```

**Expected Behavior**

As far as I can see, `__members__` should be generated by the stub generator.

Check against the previous mypy version: `pip install mypy==0.942` and re-running the stub generator produces:

<details>
<summary>Generator output mypy 0.942</summary>

```python
from typing import ClassVar

class MyEnum:
    __doc__: ClassVar[str] = ...  # read-only
    __members__: ClassVar[dict] = ...  # read-only
    BAR: ClassVar[MyEnum] = ...
    FOO: ClassVar[MyEnum] = ...
    __entries: ClassVar[dict] = ...
    def __init__(self, value: int) -> None: ...
    def __eq__(self, other: object) -> bool: ...
    def __ge__(self, other: object) -> bool: ...
    def __getstate__(self) -> int: ...
    def __gt__(self, other: object) -> bool: ...
    def __hash__(self) -> int: ...
    def __index__(self) -> int: ...
    def __int__(self) -> int: ...
    def __le__(self, other: object) -> bool: ...
    def __lt__(self, other: object) -> bool: ...
    def __ne__(self, other: object) -> bool: ...
    def __setstate__(self, state: int) -> None: ...
    @property
    def name(self) -> str: ...
    @property
    def value(self) -> int: ...
```

</details>

**Actual Behavior**

`__members__` is missing in the stub generator output.

<details>
<summary>Generator output mypy 0.950</summary>

```python
from typing import ClassVar

class MyEnum:
    BAR: ClassVar[MyEnum] = ...
    FOO: ClassVar[MyEnum] = ...
    __entries: ClassVar[dict] = ...
    def __init__(self, value: int) -> None: ...
    def __eq__(self, other: object) -> bool: ...
    def __ge__(self, other: object) -> bool: ...
    def __getstate__(self) -> int: ...
    def __gt__(self, other: object) -> bool: ...
    def __hash__(self) -> int: ...
    def __index__(self) -> int: ...
    def __int__(self) -> int: ...
    def __le__(self, other: object) -> bool: ...
    def __lt__(self, other: object) -> bool: ...
    def __ne__(self, other: object) -> bool: ...
    def __setstate__(self, state: int) -> None: ...
    @property
    def name(self) -> str: ...
    @property
    def value(self) -> int: ...
```

</details>

**Your Environment**

<!-- Include as many relevant details about the environment you experienced the bug in -->

- Mypy version used: 0.950
- Mypy command-line flags: not relevant, this is about the stub generator
- Mypy configuration options from `mypy.ini` (and other config files): not relevant, this is about the stub generator
- Python version used: 3.8.10
- Operating system and version: Ubuntu 20.04




Discussion:

Resolving patch:
diff --git a/mypy/stubgenc.py b/mypy/stubgenc.py
--- a/mypy/stubgenc.py
+++ b/mypy/stubgenc.py
@@ -288,7 +288,7 @@ def infer_prop_type(docstr: Optional[str]) -> Optional[str]:
             return None
 
     # Ignore special properties/attributes.
-    if name.startswith('__') and name.endswith('__'):
+    if is_skipped_attribute(name):
         return
 
     inferred = infer_prop_type(getattr(obj, '__doc__', None))


Test patch: