https://github.com/vadikmironov created
https://github.com/llvm/llvm-project/pull/184022
## Summary
`classifyTokensBeforeFunctionName()` raw-lexes tokens between a function's
begin location and its name. When it encounters a macro identifier, it checks
`!MI || MI->isFunctionLike()` to bail out for
function-like macros that cannot be safely classified.
Builtin macros like `__has_feature`, `__has_builtin`, `__has_extension`, etc.
are registered via `RegisterBuiltinMacro()` as **object-like**
(`isFunctionLike() == false`), but they expect **function-like
syntax** (parenthesized arguments) when expanded by the preprocessor. The
existing filter missed them, allowing them to reach `classifyToken()` which
enters `{Tok, EOF}` into the preprocessor token stream.
`PP.Lex()` then tries to expand the builtin, expects an opening parenthesis,
finds EOF, and emits a spurious diagnostic:
error: missing '(' after '__has_feature' [clang-diagnostic-error]
This manifests in practice when clang-tidy processes C++20 code that includes
libc++ headers containing `__has_feature(address_sanitizer)` in `<string>` —
the check processes `basic_string` member functions
whose return-type token spans include the builtin macro.
## Root cause
| Step | What happens |
|------|-------------|
| 1 | `RegisterBuiltinMacro("__has_feature")` sets `isBuiltinMacro=true` but
**not** `isFunctionLike=true` |
| 2 | `classifyTokensBeforeFunctionName` encounters `__has_feature` as a raw
identifier |
| 3 | Filter checks `!MI \|\| MI->isFunctionLike()` — both false for builtins
→ passes through |
| 4 | `classifyToken()` enters `{__has_feature, EOF}` into preprocessor |
| 5 | `PP.Lex()` → `ExpandBuiltinMacro` → `EvaluateFeatureLikeBuiltinMacro`
expects `(` but gets `EOF` → error |
## Fix
Add `MI->isBuiltinMacro()` to the bail-out condition in
`classifyTokensBeforeFunctionName()`.
## Standalone reproducer
```cpp
// const forces classifyTokensBeforeFunctionName to be called (local
qualifier path).
// __has_feature in the raw lex span triggers the bug.
const decltype(__has_feature(cxx_constexpr)) bar() { return 1; }
clang-tidy --checks='-*,modernize-use-trailing-return-type' test.cpp --
-std=c++20
# Before fix: error: missing '(' after '__has_feature'
# After fix: warning (no error)
Test plan
- Added lit regression tests for __has_feature and __has_builtin in return
type expressions
- Verified RED→GREEN: test fails without the fix (producing the exact missing
'(' errors), passes with the fix
- All 6 existing use-trailing-return-type*.cpp lit tests pass
Fixes #168360
>From 4355d03af2244dd153ff60715c2079aed2d4fdcf Mon Sep 17 00:00:00 2001
From: Vadim Mironov <[email protected]>
Date: Sun, 1 Mar 2026 18:15:07 +0000
Subject: [PATCH] [clang-tidy] Fix spurious errors from builtin macros in
modernize-use-trailing-return-type
`classifyTokensBeforeFunctionName()` raw-lexes tokens between a
function's begin location and its name. When it encounters a macro
identifier, it checks `!MI || MI->isFunctionLike()` to bail out for
function-like macros that cannot be safely classified.
Builtin macros like `__has_feature`, `__has_builtin`, `__has_extension`,
etc. are registered via `RegisterBuiltinMacro()` as object-like
(`isFunctionLike() == false`), but they expect function-like syntax
(parenthesized arguments) when expanded by the preprocessor. The
existing filter missed them, allowing them to reach `classifyToken()`
which enters `{Tok, EOF}` into the preprocessor token stream.
`PP.Lex()` then tries to expand the builtin, expects an opening
parenthesis, finds EOF, and emits a spurious diagnostic:
error: missing '(' after '__has_feature'
This was reported as issue #168360, which was closed because LLVM 22+
happens to match fewer system-header functions (reducing the chance of
hitting the buggy path). The underlying defect remained, and any
function whose return-type tokens include a builtin macro can still
trigger it.
The fix adds `MI->isBuiltinMacro()` to the existing bail-out condition.
Fixes #168360
Co-Authored-By: Claude Opus 4.6 <[email protected]>
---
.../clang-tidy/modernize/UseTrailingReturnTypeCheck.cpp | 9 +++++++--
.../checkers/modernize/use-trailing-return-type.cpp | 9 +++++++++
2 files changed, 16 insertions(+), 2 deletions(-)
diff --git
a/clang-tools-extra/clang-tidy/modernize/UseTrailingReturnTypeCheck.cpp
b/clang-tools-extra/clang-tidy/modernize/UseTrailingReturnTypeCheck.cpp
index 8ff94709d2529..a09c5e82e8a08 100644
--- a/clang-tools-extra/clang-tidy/modernize/UseTrailingReturnTypeCheck.cpp
+++ b/clang-tools-extra/clang-tidy/modernize/UseTrailingReturnTypeCheck.cpp
@@ -270,8 +270,13 @@ classifyTokensBeforeFunctionName(const FunctionDecl &F,
const ASTContext &Ctx,
if (Info.hasMacroDefinition()) {
const MacroInfo *MI = PP->getMacroInfo(&Info);
- if (!MI || MI->isFunctionLike()) {
- // Cannot handle function style macros.
+ if (!MI || MI->isFunctionLike() || MI->isBuiltinMacro()) {
+ // Cannot handle function-like macros or builtin macros.
+ // Builtin macros like __has_feature, __has_builtin, etc. are
+ // registered as object-like macros but expect function-like syntax
+ // (parenthesized arguments) when expanded. Feeding them to
+ // classifyToken() would cause the preprocessor to emit spurious
+ // "missing '(' after '...'" errors.
return std::nullopt;
}
}
diff --git
a/clang-tools-extra/test/clang-tidy/checkers/modernize/use-trailing-return-type.cpp
b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-trailing-return-type.cpp
index 6c919409467d6..5f6e387ada423 100644
---
a/clang-tools-extra/test/clang-tidy/checkers/modernize/use-trailing-return-type.cpp
+++
b/clang-tools-extra/test/clang-tidy/checkers/modernize/use-trailing-return-type.cpp
@@ -487,6 +487,15 @@ decltype(COMMAND_LINE_INT{}) h21();
// CHECK-MESSAGES: :[[@LINE-1]]:30: warning: use a trailing return type for
this function [modernize-use-trailing-return-type]
// CHECK-FIXES: auto h21() -> decltype(COMMAND_LINE_INT{});
+// Builtin macros like __has_feature are registered as object-like macros but
+// require function-like syntax when expanded. Ensure the check does not cause
+// spurious "missing '(' after '__has_feature'" errors when they appear in the
+// raw lex span of a function return type.
+const decltype(__has_feature(cxx_constexpr)) h22();
+// CHECK-MESSAGES: :[[@LINE-1]]:46: warning: use a trailing return type for
this function [modernize-use-trailing-return-type]
+const decltype(__has_builtin(__builtin_expect)) h23();
+// CHECK-MESSAGES: :[[@LINE-1]]:49: warning: use a trailing return type for
this function [modernize-use-trailing-return-type]
+
//
// Name collisions
//
_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits