https://github.com/kumarak created 
https://github.com/llvm/llvm-project/pull/210524

## Summary
Fix an assertion failure when Clang classifies a built-in call with 
type-dependent arguments before template instantiation,  e.g., while deducing 
an `auto` non-type template parameter:

  For example:
  ```cpp
  template <auto> struct S {};

  template <typename T>
  using Alias = S<__builtin_constant_p(T::x)>;
```
```
Assertion failed: (isa<T>(CanonicalType)), function castAs, file TypeBase.h, 
line 9349.
 #9  clang::Type::castAs<clang::FunctionType>() const
 #10 clang::CallExpr::getCallReturnType(clang::ASTContext const&) const
 #11 ClassifyInternal(clang::ASTContext&, clang::Expr const*)
 #12 clang::Expr::ClassifyImpl(clang::ASTContext&, clang::SourceLocation*) const
 #13 clang::Sema::DeduceAutoType(...)
 #14 clang::Sema::CheckTemplateArgument(...)
```

## Cause
Built-in references initially have the BuiltinFn placeholder type. For a 
non-dependent call, `BuildResolvedCallExpr()` applies `CK_BuiltinFnToFnPtr`, 
converting the callee to its function-pointer type. 

When a call has type-dependent arguments, `BuildCallExpr()` postpones semantic 
analysis until instantiation. The callee, therefore, retains its BuiltinFn 
placeholder type. Deducing the auto non-type template parameter classifies the 
dependent call through `Expr::ClassifyImpl()`. This calls 
`CallExpr::getCallReturnType()`, which previously did not handle BuiltinFn and 
attempted to cast the placeholder to FunctionType, triggering an assertion.

The issue also affects ordinary builtins such as `__builtin_ffs` and is not 
specific to` __builtin_constant_p`.

 ## Fix
Handle `BuiltinFn` alongside the dependent and `Overload` callee cases in 
`CallExpr::getCallReturnType()` and return `DependentTy`. This matches the type 
`BuildCallExpr()` gives the `CallExpr` itself, so `getCallReturnType()` and 
`getType()` now agree on such calls.

Built-in resolution and built-in-specific type checking still occur as usual 
during template instantiation.

Added coverage for dependent calls and successful instantiation of 
`__builtin_constant_p` and `__builtin_ffs`,  a non-dependent control case, and 
getCallReturnType() for a BuiltinFn callee.

>From fbf1e67f353cd7ed02002b9d690303ceef8e44b8 Mon Sep 17 00:00:00 2001
From: AkshayK <[email protected]>
Date: Sat, 18 Jul 2026 10:14:42 -0400
Subject: [PATCH] [Clang] Fix crash when classifying a dependent call to a
 builtin

A call to a builtin with dependent arguments takes the dependent path
in Sema::BuildCallExpr, so its callee is not rewritten with
CK_BuiltinFnToFnPtr until instantiation and keeps the BuiltinFn
placeholder type. CallExpr::getCallReturnType had no case for this
placeholder and fell through to castAs<FunctionType>, asserting on
valid code such as:

  template <auto> struct S {};
  template <typename T>
  using Alias = S<__builtin_constant_p(T::x)>;

where the call is classified via Expr::ClassifyImpl while deducing the
auto non-type template parameter.

Return DependentTy for BuiltinFn callees, mirroring the existing
overload, bound-member, and record-callee cases.
---
 clang/docs/ReleaseNotes.md                    |  3 ++
 clang/lib/AST/Expr.cpp                        |  4 +-
 clang/test/SemaCXX/builtin-dependent-call.cpp | 18 ++++++++
 clang/unittests/Tooling/SourceCodeTest.cpp    | 42 +++++++++++++++++++
 4 files changed, 66 insertions(+), 1 deletion(-)
 create mode 100644 clang/test/SemaCXX/builtin-dependent-call.cpp

diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index a39bc2dfc5b6d..8e910a697845c 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -302,6 +302,9 @@ latest release, please see the [Clang Web 
Site](https://clang.llvm.org) or the
 
 #### Bug Fixes to Compiler Builtins
 
+- Fixed a crash when classifying a call to a builtin with dependent arguments,
+  such as when the call is used as an `auto` non-type template argument.
+
 #### Bug Fixes to Attribute Support
 
 - The `counted_by`/`counted_by_or_null` diagnostic that rejects a pointer whose
diff --git a/clang/lib/AST/Expr.cpp b/clang/lib/AST/Expr.cpp
index 0bb9c6aa01c39..9a9a76e265f6a 100644
--- a/clang/lib/AST/Expr.cpp
+++ b/clang/lib/AST/Expr.cpp
@@ -1628,7 +1628,9 @@ QualType CallExpr::getCallReturnType(const ASTContext 
&Ctx) const {
     // dependent call to the call operator of that type.
     return Ctx.DependentTy;
   } else if (CalleeType->isDependentType() ||
-             CalleeType->isSpecificPlaceholderType(BuiltinType::Overload)) {
+             CalleeType->isSpecificPlaceholderType(BuiltinType::Overload) ||
+             CalleeType->isSpecificPlaceholderType(BuiltinType::BuiltinFn)) {
+    // Dependent builtin calls keep their placeholder until instantiation.
     return Ctx.DependentTy;
   }
 
diff --git a/clang/test/SemaCXX/builtin-dependent-call.cpp 
b/clang/test/SemaCXX/builtin-dependent-call.cpp
new file mode 100644
index 0000000000000..e84bb78cfea29
--- /dev/null
+++ b/clang/test/SemaCXX/builtin-dependent-call.cpp
@@ -0,0 +1,18 @@
+// RUN: %clang_cc1 -std=c++17 -fsyntax-only -verify %s
+// expected-no-diagnostics
+
+// Dependent builtin calls used to assert during auto NTTP deduction.
+
+template <auto> struct S {};
+
+template <typename T> using Alias = S<__builtin_constant_p(T::x)>;
+template <typename T> using Alias2 = S<__builtin_ffs(T::x)>;
+
+struct HasX {
+  static constexpr int x = 42;
+};
+
+using Inst = Alias<HasX>;
+using Inst2 = Alias2<HasX>;
+
+using Control = S<__builtin_constant_p(3)>;
diff --git a/clang/unittests/Tooling/SourceCodeTest.cpp 
b/clang/unittests/Tooling/SourceCodeTest.cpp
index d0eba43b0bb1c..db454945286ad 100644
--- a/clang/unittests/Tooling/SourceCodeTest.cpp
+++ b/clang/unittests/Tooling/SourceCodeTest.cpp
@@ -824,4 +824,46 @@ void f2() {
   Visitor.runOver(Code.code(), CallsVisitor::Lang_CXX14);
 }
 
+TEST(SourceCodeTest, GetCallReturnType_DependentBuiltinCall) {
+  // Dependent builtin calls keep their BuiltinFn placeholder until
+  // instantiation. The alias also covers the parse-time regression.
+  llvm::Annotations Code{R"cpp(
+template <auto> struct S {};
+
+template <typename T>
+using Alias = S<__builtin_constant_p(T::x)>;
+
+template <typename T>
+void templ(const T &t) {
+  $test1[[__builtin_constant_p(t.x)]];
+  $test2[[__builtin_ffs(t.x)]];
+}
+)cpp"};
+
+  llvm::Annotations::Range R1 = Code.range("test1");
+  llvm::Annotations::Range R2 = Code.range("test2");
+
+  bool SawBuiltinFnCallee = false;
+  CallsVisitor Visitor;
+  Visitor.OnCall = [&R1, &R2, &SawBuiltinFnCallee](CallExpr *Expr,
+                                                   ASTContext *Context) {
+    unsigned Begin = Context->getSourceManager().getFileOffset(
+        Expr->getSourceRange().getBegin());
+    unsigned End = Context->getSourceManager().getFileOffset(
+        Expr->getSourceRange().getEnd());
+    llvm::Annotations::Range R{Begin, End + 1};
+
+    QualType CalleeType = Expr->getCallee()->getType();
+    if (R == R1 || R == R2) {
+      SawBuiltinFnCallee = true;
+      ASSERT_FALSE(CalleeType->isDependentType());
+      ASSERT_TRUE(
+          CalleeType->isSpecificPlaceholderType(BuiltinType::BuiltinFn));
+      EXPECT_EQ(Expr->getCallReturnType(*Context), Context->DependentTy);
+    }
+  };
+  Visitor.runOver(Code.code(), CallsVisitor::Lang_CXX17);
+  EXPECT_TRUE(SawBuiltinFnCallee);
+}
+
 } // end anonymous namespace

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to