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

Reply via email to