Skip to content

chore: add an include-what-you-use preset and apply its findings - #6123

Open
henryiii wants to merge 4 commits into
pybind:masterfrom
henryiii:iwyu
Open

chore: add an include-what-you-use preset and apply its findings#6123
henryiii wants to merge 4 commits into
pybind:masterfrom
henryiii:iwyu

Conversation

@henryiii

@henryiii henryiii commented Aug 2, 2026

Copy link
Copy Markdown
Collaborator

This is a pretty straightforward Claupy of CLIUtils/CLI11#1418. CI will have to test Windows and other variations of compilers and platforms, I did a linux and macOS locally.

iwyu crashes on one of our headers, and segfaults on another, by the way.

🤖 AI text below 🤖

Description

Adds an iwyu preset and applies what it found, following CLIUtils/CLI11#1418.

The preset runs include-what-you-use over the test build through
tools/iwyu.sh, which asks IWYU to also check each pybind11 header. Two
headers crash IWYU 0.26 while it analyzes them and are skipped: pybind11.h
(assertion: "There should be a redecl specifying the default arg") and
cast.h (segmentation fault). Nothing enforces the result: the build always
succeeds and IWYU writes to stderr.

tools/iwyu.imp keeps the report readable. It maps each CPython header that
Python.h includes back to <Python.h> (headers such as <datetime.h> that
Python.h does not include stay direct), libc++ detail headers and the C
headers libstdc++ reports to the standard C++ header, and Eigen src/
internals to the module headers. IWYU pragma: export comments on
detail/common.h and conduit/wrap_include_python_h.h teach IWYU the
convention that detail/common.h is how a header gets <Python.h>.

The second commit applies the additions, so each header names the pybind11
and standard headers it uses instead of taking them transitively. The third
applies the removals that macOS (libc++) and Debian (libstdc++) runs both
report, after checking the preprocessor branches IWYU did not compile; the
commit message records the rejected findings and why (Py_GIL_DISABLED and
PYPY_VERSION branches, C++20 branches, forward declarations that carry
default template arguments, and the one-include contract of the feature
headers).

Beyond CI, verified with the full test suite on macOS, a free-threaded 3.14t
build for the Py_GIL_DISABLED branches, and a g++ -Werror build on Debian
trixie.

Suggested changelog entry:

  • Headers now include what they use, and an iwyu CMake preset is available to keep them that way.

The iwyu preset runs include-what-you-use over the test build with
tools/iwyu.sh, which asks IWYU to also check each pybind11 header. Two
headers crash IWYU 0.26 and are skipped: pybind11.h and cast.h. The
mapping file tools/iwyu.imp names <Python.h> for the CPython headers it
includes, and the standard C++ header for libc++ detail headers and
libstdc++ C headers. IWYU export pragmas on detail/common.h and
conduit/wrap_include_python_h.h teach IWYU that detail/common.h is the
project convention for <Python.h> and the namespace macros.

Assisted-by: ClaudeCode:claude-fable-5
Apply the additions from the iwyu preset report: each header now names
the pybind11 and standard headers it uses instead of taking them
transitively, so no include is load-bearing by accident. One removal is
included: detail/init.h does not use class.h, and pybind11.h includes
class.h itself.

The remaining reported removals are deferred. Removals of standard
headers need agreement from a libstdc++ (Linux) run, and removals of
pybind11.h from the feature headers (chrono, complex, operators,
warnings, embed) would break the documented contract that one include
is enough.

Assisted-by: ClaudeCode:claude-fable-5
Apply the removals that the macOS (libc++) and Linux (libstdc++) IWYU
runs both report, after a check of the preprocessor branches IWYU did
not compile. Verified with the full test suite on macOS, a
free-threaded (3.14t) build for the Py_GIL_DISABLED branches, and a
g++ -Werror build on Debian.

Rejected findings, kept for the record:

- detail/internals.h <thread> and <limits>: used in the Py_GIL_DISABLED
  constructor branch.
- detail/cpp_conduit.h internals.h: get_internals() is used in the
  PYPY_VERSION branch.
- typing.h <algorithm>: std::copy_n is used in the C++20 branch.
- The forward declarations of op_ (attr.h) and the collectors and arg_v
  (pytypes.h): they carry default template arguments that the
  definitions elsewhere do not repeat.
- eigen/matrix.h <Eigen/SparseCore>: the sparse casters use it.
- pybind11.h in the feature headers: the documented one-include
  contract.

Assisted-by: ClaudeCode:claude-fable-5
@henryiii

henryiii commented Aug 2, 2026

Copy link
Copy Markdown
Collaborator Author

I have to take this out of draft to run the full CI. (Edit: looks fine)

@henryiii
henryiii marked this pull request as ready for review August 2, 2026 13:27
@rwgk

rwgk commented Aug 3, 2026

Copy link
Copy Markdown
Collaborator

The 1. finding below makes me wonder: could it be better to drop C++11 and C++14 support first? I think that will eliminate quite a few preprocessor branches.


Findings (codex gpt-5.6-sol)

  1. Blocking: unconditional C++17 library headers break supported C++11 toolchains.

    These headers were added unconditionally:

    • <string_view> in include/pybind11/detail/type_caster_base.h
    • <string_view> in include/pybind11/stl.h
    • <optional> in include/pybind11/stl_bind.h

    All string_view usage is already guarded by PYBIND11_HAS_STRING_VIEW, and stl_bind.h does not use std::optional at all. GCC 4.8 is still explicitly accepted by pybind11 and does not provide either header. C++11 CI jobs using modern standard libraries may not expose this regression because the header files happen to exist there even in C++11 mode.

    Recommended fix:

    • Guard both <string_view> includes with PYBIND11_HAS_STRING_VIEW.
    • Remove <optional> from stl_bind.h.
  2. The CPython mapping is not valid across all Python versions supported by pybind11.

    tools/iwyu.imp was generated from CPython 3.14, but the preset selects the contributor's python3 through the inherited venv preset.

    CPython 3.9 and 3.10 had providers such as code.h, genobject.h, funcobject.h, and cellobject.h at the top level rather than under cpython/. Newer CPython versions continue changing the layout. On an affected version, IWYU can recommend a forbidden direct CPython include instead of <Python.h>.

    I recommend mapping the union of headers included by Python.h across all supported CPython versions. Pinning this preset to Python 3.14 would be simpler, but would also prevent it from analyzing Python-version-specific paths.

  3. Eigen/Core is now included outside the intended warning-suppression block.

    include/pybind11/eigen/tensor.h now includes <Eigen/Core> before the warning push. Previously, Eigen/Core arrived through the unsupported Tensor header while the suppression block was active.

    This can expose Eigen warnings as consumer -Werror failures, particularly with MSVC or MinGW. <Eigen/Core> should be moved into the existing suppression block, immediately before <unsupported/Eigen/CXX11/Tensor>.

  4. The two core headers skipped by IWYU still depend on transitive standard-library includes.

    Because IWYU crashes while analyzing pybind11.h and cast.h, those headers need a small manual audit:

    • pybind11.h directly uses std::forward_list and std::type_index, but includes neither <forward_list> nor <typeindex>.
    • cast.h directly uses std::type_index, but does not include <typeindex>.

    Those types currently arrive through other pybind11 headers, which is precisely the load-bearing transitive dependency this work is intended to eliminate.

  5. detail/typeid.h expands an existing violation of the Python-header ordering requirement.

    detail/typeid.h now adds <memory>, <string>, and <typeinfo> before common.h, and therefore before Python.h. There was already a standard header before common.h, but this PR expands the problem while making the header more self-contained.

    common.h should be moved immediately below #pragma once, ahead of all standard-library headers.

  6. There are a few nonblocking tooling and documentation issues.

    • The documented recipe does not mention that inheriting the venv preset requires uv.
    • “The build always succeeds” is inaccurate. IWYU recommendations do not fail the build, but a missing executable, IWYU crash, invalid mapping, or compilation error does.
    • Since IWYU advice is written to stderr, an output-capture example should use 2>&1 if it is intended to capture both progress and advice.
    • The wrapper's two globs cover every current header, but not a future header nested more than one directory below include/pybind11/, despite the comment saying new headers are covered automatically.
    • The .sh wrapper makes the preset effectively Unix-only. The documentation should describe it as macOS/Linux-only unless native Windows support is intended.

The export pragmas, shell quoting, standard-library mappings, and audited include removals otherwise look sound. I found no ABI or object-layout change.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants