llvm / llvm/llvm-project

Behavior of `__has_builtin` is different when preprocessing vs. compiling and a typename shadows a builtin name

Open
#206,201 10 comments 0 reactions 0 assignees View on GitHub
clang:frontend
Dominant language
LLVM
Stars
40.5k
Forks
18.7k
PR merge metrics
PR metrics pending

Description

The behavior of `__has_builtin()` is different when running the preprocessor vs. compiling if a typename shadows a builtin name. Simplified repro is below.

This makes it impossible to compile a preprocessed file via `-x c++-cpp-output` if the source file uses `__has_builtin()` to detect a builtin whose name is shadowed by another typename. Unfortunately some libstdc++ typenames shadow clang builtins, e.g. `` declaring a `__make_unsigned` struct.

I discovered this while working on sccache-dist, which relies on compiling the preprocessor output on remote machines. This behavior causes distributed compilation to fail when libraries like [NVIDIA/CCCL](https://github.com/NVIDIA/cccl) use `__has_builtin` to [detect](https://github.com/NVIDIA/cccl/blob/2b4b3db72bb9a63dc137bf2331502d2a6cdcda15/libcudacxx/include/cuda/std/__cccl/builtin.h#L46-L68) and [use](https://github.com/NVIDIA/cccl/blob/2b4b3db72bb9a63dc137bf2331502d2a6cdcda15/libcudacxx/include/cuda/std/__type_traits/make_unsigned.h#L35-L54) clang builtins. The only workaround I can think is to define a macro when preprocessing that disables the builtin detection, but that feels gross.

### Example

```c++
// test.cpp
#include

#if __has_builtin(__make_unsigned)
template
using make_unsigned_t = __make_unsigned(Tp);
#else
using std::make_unsigned_t;
#endif

int main(void) {
auto x = make_unsigned_t{0};
return 0;
}
```

([godbolt](https://godbolt.org/z/YvcPEGnoq)) Compiling succeeds, but uses libstdc++'s `std::make_unsigned_t` instead of the builtin:
```shell
clang++-22 -x c++ -c test.cpp -o test.cpp.o
```

([godbolt](https://godbolt.org/z/dPG3KT8MT)) Preprocessing attempts to use the builtin, because `__has_builtin(__make_unsigned)` is true when preprocessing, but then produces a file that cannot be compiled:
```shell
$ clang++-22 -x c++ -E test.cpp -o test.cpp.ii
$ clang++-22 -x c++-cpp-output -c test.cpp.ii -o test.cpp.o
test.cpp:5:25: error: unknown type name '__make_unsigned'
5 | using make_unsigned_t = __make_unsigned(Tp);
| ^
test.cpp:11:14: error: use of undeclared identifier 'make_unsigned_t'; did you mean
'std::make_unsigned_t'?
11 | auto x = make_unsigned_t{0};
| ^~~~~~~~~~~~~~~
/usr/lib/gcc/x86_64-linux-gnu/11/../../../../include/c++/11/type_traits:1974:5: note:
'std::make_unsigned_t' declared here
11 | using make_unsigned_t = typename make_unsigned<_Tp>::type;
| ^
2 errors generated.
```

Contributor guide

Open the contributing guide

Research direction

Start with the test.cpp reproducer and run the two clang++ commands shown, comparing preprocessing with direct compilation. Trace how __has_builtin(__make_unsigned) is handled when __make_unsigned is shadowed by . Done means the preprocessed output follows the same builtin-detection behavior and compiles with -x c++-cpp-output.

Written by the indexing model from the issue text.

Assessment

Tech stack
cpp
Domain
compilers
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
46/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.