[clang] [clang][Sema] support block pointers as non-type template parameters (PR #191694)

via cfe-commits cfe-commits at lists.llvm.org
Sun Apr 12 02:21:40 PDT 2026


https://github.com/Serosh-commits created https://github.com/llvm/llvm-project/pull/191694

allow block pointers to be used as template parameters in C++20.
Add mangling for block literals in template arguments.
Let ODR hashing see block expressions so they don't cause a crash.
Fix a crash by making sure block names decay to pointers.
Allow matching block pointer types even if they have different const qualifiers.

>From 0a615a8f2e6b301485a65ee30f43bfe49276aefd Mon Sep 17 00:00:00 2001
From: Serosh-commits <janmejayapanda400 at gmail.com>
Date: Sat, 11 Apr 2026 21:15:22 +0530
Subject: [PATCH] [Clang][Sema] Support block pointers as non-type template
 parameters

Block pointer types were explicitly rejected as NTTPs with a diagnostic.
Since block pointers are scalar types, they satisfy the C++20 structural
type requirements and should be permitted as NTTPs, consistent with how
regular pointer types are handled.

This patch:
- Removes the explicit block pointer rejection in
  CheckNonTypeTemplateParameterType and adds block pointers to the
  allowed scalar type list, guarded by C++20 mode.
- Updates CheckTemplateArgument to handle BlockExpr as a valid LValue
  base for block pointer template arguments.
- Updates CheckTemplateArgumentAddressOfObjectOrFunction and
  CheckTemplateArgumentIsCompatibleWithParameter to handle block
  pointer types alongside regular pointer types.
- Performs DefaultLvalueConversion in BuildExpressionFromDeclTemplateArgument
  for block pointers to ensure we evaluate to an RValue, while
  skipping address-of since the expression already has the correct type.
- Add block pointer support to Itanium mangling for LValue template
  arguments by routing BlockExpr through mangleUnqualifiedBlock.
- Handle Expr-based LValue bases in ODRHash::AddStructuralValue to
  avoid assertion failures when hashing block pointer template args.
- Add qualification conversion support for block pointers during
  template argument deduction.
---
 clang/lib/AST/ItaniumMangle.cpp          | 12 ++++--
 clang/lib/AST/ODRHash.cpp                |  9 +++-
 clang/lib/Sema/SemaTemplate.cpp          | 33 +++++++++------
 clang/lib/Sema/SemaTemplateDeduction.cpp |  1 +
 clang/test/SemaCXX/nttp-blockpointer.cpp | 53 ++++++++++++++++++++++++
 5 files changed, 89 insertions(+), 19 deletions(-)
 create mode 100644 clang/test/SemaCXX/nttp-blockpointer.cpp

diff --git a/clang/lib/AST/ItaniumMangle.cpp b/clang/lib/AST/ItaniumMangle.cpp
index f58faa03bfa8c..af2b1b6ae0f20 100644
--- a/clang/lib/AST/ItaniumMangle.cpp
+++ b/clang/lib/AST/ItaniumMangle.cpp
@@ -5009,7 +5009,11 @@ void CXXNameMangler::mangleExpression(const Expr *E, unsigned Arity,
   }
 
   // FIXME: invent manglings for all these.
-  case Expr::BlockExprClass:
+  case Expr::BlockExprClass: {
+    NotPrimaryExpr();
+    mangleUnqualifiedBlock(cast<BlockExpr>(E)->getBlockDecl());
+    break;
+  }
   case Expr::ChooseExprClass:
   case Expr::CompoundLiteralExprClass:
   case Expr::ExtVectorElementExprClass:
@@ -6775,7 +6779,7 @@ void CXXNameMangler::mangleValueInTemplateArg(QualType T, const APValue &V,
 
   case APValue::LValue: {
     // Proposed in https://github.com/itanium-cxx-abi/cxx-abi/issues/47.
-    assert((T->isPointerOrReferenceType()) &&
+    assert((T->isPointerOrReferenceType() || T->isBlockPointerType()) &&
            "unexpected type for LValue template arg");
 
     if (V.isNullPointer()) {
@@ -6844,7 +6848,7 @@ void CXXNameMangler::mangleValueInTemplateArg(QualType T, const APValue &V,
           Out << "cv";
           mangleType(T);
         }
-        if (T->isPointerType())
+        if (T->isPointerType() || T->isBlockPointerType())
           Out << "ad";
         Out << "so";
         mangleType(T->isVoidPointerType()
@@ -6859,7 +6863,7 @@ void CXXNameMangler::mangleValueInTemplateArg(QualType T, const APValue &V,
           Out << "cv";
           mangleType(T);
         }
-        if (T->isPointerType()) {
+        if (T->isPointerType() || T->isBlockPointerType()) {
           NotPrimaryExpr();
           Out << "ad";
         }
diff --git a/clang/lib/AST/ODRHash.cpp b/clang/lib/AST/ODRHash.cpp
index 46a4e256ea3e5..0f883b240f30c 100644
--- a/clang/lib/AST/ODRHash.cpp
+++ b/clang/lib/AST/ODRHash.cpp
@@ -1270,8 +1270,13 @@ void ODRHash::AddStructuralValue(const APValue &Value) {
       break;
     }
 
-    assert(Base.is<const ValueDecl *>());
-    AddDecl(Base.get<const ValueDecl *>());
+    if (auto *VD = Base.dyn_cast<const ValueDecl *>()) {
+      AddDecl(VD);
+    } else if (auto *E = Base.dyn_cast<const Expr *>()) {
+      AddStmt(E);
+    } else {
+      llvm_unreachable("unexpected LValue base kind in structural value");
+    }
     ID.AddInteger(Value.getLValueOffset().getQuantity());
 
     bool OnePastTheEnd = Value.isLValueOnePastTheEnd();
diff --git a/clang/lib/Sema/SemaTemplate.cpp b/clang/lib/Sema/SemaTemplate.cpp
index c436b7018a2bd..2851c68c62c7e 100644
--- a/clang/lib/Sema/SemaTemplate.cpp
+++ b/clang/lib/Sema/SemaTemplate.cpp
@@ -1459,11 +1459,6 @@ QualType Sema::CheckNonTypeTemplateParameterType(QualType T,
     return QualType();
   }
 
-  if (T->isBlockPointerType()) {
-    Diag(Loc, diag::err_template_nontype_parm_bad_type) << T;
-    return QualType();
-  }
-
   // C++ [temp.param]p4:
   //
   // A non-type template-parameter shall have one of the following
@@ -1473,6 +1468,8 @@ QualType Sema::CheckNonTypeTemplateParameterType(QualType T,
   if (T->isIntegralOrEnumerationType() ||
       //   -- pointer to object or pointer to function,
       T->isPointerType() ||
+      //   -- block pointer type,
+      (getLangOpts().CPlusPlus20 && T->isBlockPointerType()) ||
       //   -- lvalue reference to object or lvalue reference to function,
       T->isLValueReferenceType() ||
       //   -- pointer to member,
@@ -6670,8 +6667,8 @@ CheckTemplateArgumentIsCompatibleWithParameter(Sema &S, NamedDecl *Param,
                                                QualType ParamType, Expr *ArgIn,
                                                Expr *Arg, QualType ArgType) {
   bool ObjCLifetimeConversion;
-  if (ParamType->isPointerType() &&
-      !ParamType->castAs<PointerType>()->getPointeeType()->isFunctionType() &&
+  if ((ParamType->isPointerType() || ParamType->isBlockPointerType()) &&
+      !ParamType->getPointeeType()->isFunctionType() &&
       S.IsQualificationConversion(ArgType, ParamType, false,
                                   ObjCLifetimeConversion)) {
     // For pointer-to-object types, qualification conversions are
@@ -6821,7 +6818,8 @@ static bool CheckTemplateArgumentAddressOfObjectOrFunction(
     Entity = CUE->getGuidDecl();
 
   // If our parameter has pointer type, check for a null template value.
-  if (ParamType->isPointerType() || ParamType->isNullPtrType()) {
+  if (ParamType->isPointerType() || ParamType->isBlockPointerType() ||
+      ParamType->isNullPtrType()) {
     switch (isNullPointerValueTemplateArgument(S, Param, ParamType, ArgIn,
                                                Entity)) {
     case NPV_NullPointer:
@@ -7355,17 +7353,19 @@ ExprResult Sema::CheckTemplateArgument(NamedDecl *Param, QualType ParamType,
     if (Value.isLValue()) {
       APValue::LValueBase Base = Value.getLValueBase();
       auto *VD = const_cast<ValueDecl *>(Base.dyn_cast<const ValueDecl *>());
+      auto *E = const_cast<Expr *>(Base.dyn_cast<const Expr *>());
       //   For a non-type template-parameter of pointer or reference type,
       //   the value of the constant expression shall not refer to
       assert(ParamType->isPointerOrReferenceType() ||
-             ParamType->isNullPtrType());
+             ParamType->isBlockPointerType() || ParamType->isNullPtrType());
       // -- a temporary object
       // -- a string literal
       // -- the result of a typeid expression, or
       // -- a predefined __func__ variable
       if (Base &&
           (!VD ||
-           isa<LifetimeExtendedTemporaryDecl, UnnamedGlobalConstantDecl>(VD))) {
+           isa<LifetimeExtendedTemporaryDecl, UnnamedGlobalConstantDecl>(VD)) &&
+          (!E || !isa<BlockExpr>(E))) {
         Diag(Arg->getBeginLoc(), diag::err_template_arg_not_decl_ref)
             << Arg->getSourceRange();
         return ExprError();
@@ -7663,13 +7663,16 @@ ExprResult Sema::CheckTemplateArgument(NamedDecl *Param, QualType ParamType,
     return Arg;
   }
 
-  if (ParamType->isPointerType()) {
+  if (ParamType->isPointerType() || ParamType->isBlockPointerType()) {
     //   -- for a non-type template-parameter of type pointer to
     //      object, qualification conversions (4.4) and the
     //      array-to-pointer conversion (4.2) are applied.
     // C++0x also allows a value of std::nullptr_t.
-    assert(ParamType->getPointeeType()->isIncompleteOrObjectType() &&
-           "Only object pointers allowed here");
+    if (ParamType->isPointerType())
+      assert(ParamType->getPointeeType()->isIncompleteOrObjectType() &&
+             "Only object pointers allowed here");
+    else
+      assert(ParamType->isBlockPointerType() && "Expected block pointer");
 
     if (CheckTemplateArgumentAddressOfObjectOrFunction(
             *this, Param, ParamType, Arg, SugaredConverted, CanonicalConverted))
@@ -7976,6 +7979,10 @@ ExprResult Sema::BuildExpressionFromDeclTemplateArgument(
     RefExpr = DefaultFunctionArrayConversion(RefExpr.get());
     if (RefExpr.isInvalid())
       return ExprError();
+  } else if (ParamType->isBlockPointerType()) {
+    RefExpr = DefaultLvalueConversion(RefExpr.get());
+    if (RefExpr.isInvalid())
+      return ExprError();
   } else if (ParamType->isPointerType() || ParamType->isMemberPointerType()) {
     // For any other pointer, take the address (or form a pointer-to-member).
     RefExpr = CreateBuiltinUnaryOp(Loc, UO_AddrOf, RefExpr.get());
diff --git a/clang/lib/Sema/SemaTemplateDeduction.cpp b/clang/lib/Sema/SemaTemplateDeduction.cpp
index c71c40526ccdc..66bbc7b1353e1 100644
--- a/clang/lib/Sema/SemaTemplateDeduction.cpp
+++ b/clang/lib/Sema/SemaTemplateDeduction.cpp
@@ -4986,6 +4986,7 @@ TemplateDeductionResult Sema::DeduceTemplateArguments(
   // both P and A are pointers or member pointers. In this case, we
   // just ignore cv-qualifiers completely).
   if ((P->isPointerType() && A->isPointerType()) ||
+      (P->isBlockPointerType() && A->isBlockPointerType()) ||
       (P->isMemberPointerType() && A->isMemberPointerType()))
     TDF |= TDF_IgnoreQualifiers;
 
diff --git a/clang/test/SemaCXX/nttp-blockpointer.cpp b/clang/test/SemaCXX/nttp-blockpointer.cpp
new file mode 100644
index 0000000000000..eb91f87e5bd5a
--- /dev/null
+++ b/clang/test/SemaCXX/nttp-blockpointer.cpp
@@ -0,0 +1,53 @@
+// RUN: %clang_cc1 -std=c++20 -fblocks -triple x86_64-apple-darwin %s -verify
+
+template<void (^B)()>
+struct A {
+  void call() { B(); }
+};
+
+constexpr void (^global_block)() = ^{};
+void test_global() {
+  A<global_block> a;
+  a.call();
+}
+
+void test_literal() {
+  A<^{}> a;
+  a.call();
+}
+
+void test_null() {
+  A<nullptr> a;
+}
+
+void test_capturing(int x) {
+  A<^{ (void)x; }> a; // expected-error {{non-type template argument is not a constant expression}}
+}
+
+template<void (^B)()>
+void deduce(A<B> a) {}
+
+void test_deduce() {
+  A<global_block> a;
+  deduce(a);
+}
+
+constexpr void (^another_block)() = ^{};
+static_assert(!__is_same(A<global_block>, A<another_block>));
+
+template<int (^B)(int)>
+struct BFunc {
+  int call(int x) { return B(x); }
+};
+
+void test_params() {
+  BFunc<^(int x) { return x + 1; }> b;
+  (void)b.call(1);
+}
+
+template<auto B>
+struct AutoBlock {};
+
+void test_auto() {
+  AutoBlock<global_block> a;
+}



More information about the cfe-commits mailing list