Repository: sphinx-doc/sphinx

Issue:
`autosummary` ignores `__all__` when it is empty
### Describe the bug

With `__all__ = []` in a module, `autosummary` takes everything as a member instead of nothing, even with `autosummary_ignore_module_all = False`. In addition, weird members like `__builtins__`, `__file__` and `__package__` are showed as members.

```python
"""Dummy module"""

__all__ = []

def component():
    """Dummy component"""

    pass
```

![image](https://github.com/sphinx-doc/sphinx/assets/37352336/d73ebe4d-ffeb-4a98-9628-31c42c39a455)



### How to Reproduce

I provide a minimal reproducing example in https://github.com/francois-rozet/sphinx-bug

```
git clone https://github.com/francois-rozet/sphinx-bug
cd sphinx-bug
pip install -e .
pip install sphinx
sphinx-build . html
open html/api/dummy.html
```

### Environment Information

```text
Platform:              linux; (Linux-6.5.0-0.deb12.4-amd64-x86_64-with-glibc2.38)
Python version:        3.9.18 | packaged by conda-forge | (main, Aug 30 2023, 03:49:32) 
[GCC 12.3.0])
Python implementation: CPython
Sphinx version:        7.3.7
Docutils version:      0.19
Jinja2 version:        3.1.2
Pygments version:      2.17.2
```


### Sphinx extensions

```python
extensions = [
    'sphinx.ext.autodoc',
    'sphinx.ext.autosummary',
]
```


### Additional context

_No response_

Hints from issue comments:
@francois-rozet: Note that if `dummy/__init__.py` is empty

```python
"""Dummy module"""

# __all__ = []

# def component():
#     """Dummy component"""

#     pass

```

the weird members (`__builtins__`, `__cached__`, ...) still appear.

![image](https://github.com/sphinx-doc/sphinx/assets/37352336/57db3e1b-015c-49a7-865c-7d21f3cf6444)
@vpratz: Thanks for the nice bug report and the repository for reproduction. I could reproduce the bug, the problem was a small check that has gone wrong. I have supplied a fix, maybe one of the maintainers can review it.

Solution patch:
diff --git a/CHANGES.rst b/CHANGES.rst
index 8f856b1f620..69c4ada5cc1 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -54,6 +54,8 @@ Features added
 Bugs fixed
 ----------
 
+* #12463: autosummary: Respect an empty module ``__all__``.
+  Patch by Valentin Pratz
 * #13060: HTML Search: use ``Map`` to store per-file term scores.
   Patch by James Addison
 * #13130: LaTeX docs: ``pdflatex`` index creation may fail for index entries
diff --git a/sphinx/ext/autosummary/generate.py b/sphinx/ext/autosummary/generate.py
index 48d50a19135..617fc495be8 100644
--- a/sphinx/ext/autosummary/generate.py
+++ b/sphinx/ext/autosummary/generate.py
@@ -275,7 +275,11 @@ def members_of(obj: Any, conf: Config) -> Sequence[str]:
     if conf.autosummary_ignore_module_all:
         return dir(obj)
     else:
-        return getall(obj) or dir(obj)
+        if (obj___all__ := getall(obj)) is not None:
+            # return __all__, even if empty.
+            return obj___all__
+        # if __all__ is not set, return dir(obj)
+        return dir(obj)
 
 
 def generate_autosummary_content(


Test patch:
diff --git a/tests/roots/test-ext-autosummary-module_empty_all/autosummary_dummy_package_empty_all/__init__.py b/tests/roots/test-ext-autosummary-module_empty_all/autosummary_dummy_package_empty_all/__init__.py
new file mode 100644
index 00000000000..ea9b7835485
--- /dev/null
+++ b/tests/roots/test-ext-autosummary-module_empty_all/autosummary_dummy_package_empty_all/__init__.py
@@ -0,0 +1 @@
+__all__ = ()
diff --git a/tests/roots/test-ext-autosummary-module_empty_all/conf.py b/tests/roots/test-ext-autosummary-module_empty_all/conf.py
new file mode 100644
index 00000000000..9062c5cebcb
--- /dev/null
+++ b/tests/roots/test-ext-autosummary-module_empty_all/conf.py
@@ -0,0 +1,11 @@
+import sys
+from pathlib import Path
+
+sys.path.insert(0, str(Path.cwd().resolve()))
+
+extensions = ['sphinx.ext.autodoc', 'sphinx.ext.autosummary']
+autodoc_default_options = {'members': True}
+autosummary_ignore_module_all = False
+autosummary_imorted_members = False
+
+templates_path = ['templates']
diff --git a/tests/roots/test-ext-autosummary-module_empty_all/index.rst b/tests/roots/test-ext-autosummary-module_empty_all/index.rst
new file mode 100644
index 00000000000..41afaf3783e
--- /dev/null
+++ b/tests/roots/test-ext-autosummary-module_empty_all/index.rst
@@ -0,0 +1,8 @@
+test-ext-autosummary-module_all
+===============================
+
+.. autosummary::
+   :toctree: generated
+   :recursive:
+
+   autosummary_dummy_package_empty_all
diff --git a/tests/roots/test-ext-autosummary-module_empty_all/templates/autosummary/module.rst b/tests/roots/test-ext-autosummary-module_empty_all/templates/autosummary/module.rst
new file mode 100644
index 00000000000..c7803af698d
--- /dev/null
+++ b/tests/roots/test-ext-autosummary-module_empty_all/templates/autosummary/module.rst
@@ -0,0 +1,13 @@
+{{ fullname | escape | underline}}
+
+.. automodule:: {{ fullname }}
+
+   {% block members %}
+   Summary
+   -------
+   .. autosummary::
+
+   {% for item in members %}
+      {{ item }}
+   {%- endfor %}
+   {% endblock %}
diff --git a/tests/test_extensions/test_ext_autosummary.py b/tests/test_extensions/test_ext_autosummary.py
index 901edb29915..c80753bef13 100644
--- a/tests/test_extensions/test_ext_autosummary.py
+++ b/tests/test_extensions/test_ext_autosummary.py
@@ -826,9 +826,8 @@ def test_autosummary_module_all(app):
         app.build()
         # generated/foo is generated successfully
         assert app.env.get_doctree('generated/autosummary_dummy_package_all')
-        module = (
-            app.srcdir / 'generated' / 'autosummary_dummy_package_all.rst'
-        ).read_text(encoding='utf8')
+        path = app.srcdir / 'generated' / 'autosummary_dummy_package_all.rst'
+        module = path.read_text(encoding='utf8')
         assert '   .. autosummary::\n   \n      PublicBar\n   \n' in module
         assert (
             '   .. autosummary::\n   \n      public_foo\n      public_baz\n   \n'
@@ -840,6 +839,30 @@ def test_autosummary_module_all(app):
         sys.modules.pop('autosummary_dummy_package_all', None)
 
 
+@pytest.mark.sphinx('dummy', testroot='ext-autosummary-module_empty_all')
+def test_autosummary_module_empty_all(app):
+    try:
+        app.build()
+        # generated/foo is generated successfully
+        assert app.env.get_doctree('generated/autosummary_dummy_package_empty_all')
+        path = app.srcdir / 'generated' / 'autosummary_dummy_package_empty_all.rst'
+        module = path.read_text(encoding='utf8')
+        assert '.. automodule:: autosummary_dummy_package_empty_all' in module
+        # for __all__ = (), the output should not contain any variables
+        assert '__all__' not in module
+        assert '__builtins__' not in module
+        assert '__cached__' not in module
+        assert '__doc__' not in module
+        assert '__file__' not in module
+        assert '__loader__' not in module
+        assert '__name__' not in module
+        assert '__package__' not in module
+        assert '__path__' not in module
+        assert '__spec__' not in module
+    finally:
+        sys.modules.pop('autosummary_dummy_package_all', None)
+
+
 @pytest.mark.sphinx(
     'html',
     testroot='ext-autodoc',