llvm / llvm/llvm-project

[Clang][Modules] explicit specifier lost on template conversion operators after PCM serialization

Open
#188,907 2 comments 0 reactions 0 assignees View on GitHub
clang:frontend clang:modules miscompilation
Dominant language
LLVM
Stars
40.5k
Forks
18.7k
PR merge metrics
PR metrics pending

Description

## Description

Template conversion operators declared `explicit` lose their `explicit` specifier after PCM serialization/deserialization through C++ modules. This causes `std::is_convertible_v` to return `true` for conversions that should only be available via `static_cast`.

The bug only affects a specific subset of conversions: cross-direction, same-constness conversions in a bidirectional iterator implementation. Other conversions (same-direction, or cross-direction+const) correctly retain their `explicit` specifier.

## Precise diagnosis

The conversion operators have 8 template parameters with complex SFINAE conditions via `std::enable_if_t`. After importing through a deep module chain (10+ PCM layers), the `explicit` specifier is lost on specific overloads:

| Conversion | is_convertible_v | static_cast | Status |
|---|---|---|---|
| FwdNC -> FwdC (same direction) | false | works | OK (explicit retained) |
| FwdNC -> RevNC (cross direction) | **true** | works | **BUG: explicit lost** |
| FwdNC -> RevC (cross direction + const) | false | works | OK (explicit retained) |
| RevNC -> RevC (same direction) | false | works | OK (explicit retained) |

The `explicit` specifier IS lost (not a missing conversion -- `static_cast` still works, but implicit conversion is now allowed). This pattern repeats symmetrically for all non-const cross-direction conversions (Rev->Fwd also affected).

## Source location

The conversion operators are at:
```cpp
// type/integer/fundamental/offset/abstract/iterator/implementation.hpp:1364
template
explicit operator NewSelf() { /* ... */ }
```

## Reproducer status

A self-contained minimal reproducer has not been achieved despite extensive attempts. The bug requires the full project module graph depth (10+ PCM layers through a CRTP/Aspect/PID/Context/Stack injection chain).

Tested and confirmed NOT to trigger the bug:
- Single module with same template structure (up to 8 params)
- Module partition -> primary re-export chain with same structure
- 2-level module re-export chain with same structure
- Adding CRTP Self, BaseInt, StaticCastable chain
- Various combinations of trait lookup patterns

The bug specifically requires the depth of the module serialization chain, suggesting the `explicit` specifier is corrupted or dropped during multi-layer PCM serialization/deserialization.

## How to verify

With the project, a diagnostic test confirms:
```cpp
// is_convertible_v should be false (explicit prevents implicit conversion)
// static_cast should work (conversion exists)
static_assert(!std::is_convertible_v); // FAILS: is_convertible_v=true
static_assert(std::is_constructible_v); // OK: static_cast works
```

## Environment

- **Clang version**: 23.0.0git (commit 3392ec8cf17ce90fe0859ae31e86cf48f8fa185d)
- **OS**: macOS 15.4 (Darwin 25.3.0), ARM64

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.