[clang] [clang] Fix an accepts-invalid related to concepts and parameter packs (PR #227494)

Nico Weber via cfe-commits cfe-commits at lists.llvm.org
Sun Oct 4 18:41:11 PDT 2026


https://github.com/nico updated https://github.com/llvm/llvm-project/pull/227494

>From 1cffd99626c0b52f7723abf1d3e1cff2969ea9da Mon Sep 17 00:00:00 2001
From: Nico Weber <thakis at chromium.org>
Date: Tue, 29 Sep 2026 12:01:43 -0700
Subject: [PATCH] [clang] Fix an accepts-invalid related to concepts and
 parameter packs

HashParameterMapping computes the keys of
UnsubstitutedConstraintSatisfactionCache. In a fold expression, it hashes
the current element for every use of the expanded pack. But in
P...[sizeof(P)], P refers to the whole pack, not the current element. So two
checks with the same element at the same position got the same key even if
the indexed pack differs, and the second one used the first one's result.
This accepted e.g.

    template <class T> concept two_bytes = sizeof(T) == 2;
    template <typename... P>
    void f() requires(two_bytes<P...[sizeof(P)]> && ...) {}

    void g() {
      f<char, short, short>();
      f<char, int, short, short, short>(); // P...[sizeof(char)] is int
    }

(without the first call, the second one is rejected). This is a
regression exposed by #218577 (which fixed #218548), even though the underlying
bug is older.

As fix, hash the whole pack for the pattern of a PackIndexingType.
---
 clang/lib/Sema/SemaConcept.cpp          |  9 ++++++
 clang/test/SemaCXX/cxx2c-fold-exprs.cpp | 40 +++++++++++++++++++++++--
 2 files changed, 46 insertions(+), 3 deletions(-)

diff --git a/clang/lib/Sema/SemaConcept.cpp b/clang/lib/Sema/SemaConcept.cpp
index 9ef76b758d5fe16..05c44c0c1ce7422 100644
--- a/clang/lib/Sema/SemaConcept.cpp
+++ b/clang/lib/Sema/SemaConcept.cpp
@@ -404,6 +404,15 @@ class HashParameterMapping : public RecursiveASTVisitor<HashParameterMapping> {
     return inherited::TraverseType(TT->desugar());
   }
 
+  bool TraversePackIndexingType(PackIndexingType *T, bool TraverseQualifier) {
+    {
+      Sema::ArgPackSubstIndexRAII _(SemaRef, std::nullopt);
+      if (!TraverseType(T->getPattern()))
+        return false;
+    }
+    return TraverseStmt(T->getIndexExpr());
+  }
+
   bool TraverseDecl(Decl *D) {
     if (auto *VD = dyn_cast<ValueDecl>(D)) {
       if (auto *Var = dyn_cast<VarDecl>(VD))
diff --git a/clang/test/SemaCXX/cxx2c-fold-exprs.cpp b/clang/test/SemaCXX/cxx2c-fold-exprs.cpp
index b4f763f7fdb7d5f..f0819ed04881b34 100644
--- a/clang/test/SemaCXX/cxx2c-fold-exprs.cpp
+++ b/clang/test/SemaCXX/cxx2c-fold-exprs.cpp
@@ -677,7 +677,7 @@ void g() { f<long long, float>(); }
 namespace GH218548 {
 
 template <class T>
-concept same_as_impl = sizeof(T) == 2;
+concept same_as_impl = sizeof(T) == 2; // #GH218548_same_as_impl
 template <typename... P>
 void f() requires(same_as_impl<P...[sizeof(P)]> && ...) // #GH218548_f
 {}
@@ -687,8 +687,42 @@ void g() {
   f<char, int, short>();
   // expected-error at -1 {{no matching function}}
   // expected-note@#GH218548_f {{constraints not satisfied}}
-  // expected-note@#GH218548_f {{does not satisfy 'same_as_impl'}}
-  // expected-note@#GH218548_f {{invalid index}}
+  // expected-note@#GH218548_f {{because 'int' does not satisfy 'same_as_impl'}}
+  // expected-note@#GH218548_same_as_impl {{because 'sizeof(int) == 2' (4 == 2) evaluated to false}}
+}
+
+}
+
+namespace GH218548_cache {
+
+// The cached satisfaction of an element must depend on the whole pack that
+// P...[sizeof(P)] indexes: the first element's P...[sizeof(P)] is short in the
+// first call but int in the second.
+template <class T>
+concept two_bytes = sizeof(T) == 2; // #GH218548_cache_two_bytes
+template <typename... P>
+void f() requires(two_bytes<P...[sizeof(P)]> && ...) // #GH218548_cache_f
+{}
+template <class T>
+concept wrapped = two_bytes<T>; // #GH218548_cache_wrapped
+template <typename... P>
+void g() requires(wrapped<P...[sizeof(P)]> && ...) // #GH218548_cache_g
+{}
+void h() {
+  f<char, short, short>();
+  f<char, int, short, short, short>();
+  // expected-error at -1 {{no matching function}}
+  // expected-note@#GH218548_cache_f {{constraints not satisfied}}
+  // expected-note@#GH218548_cache_f {{because 'int' does not satisfy 'two_bytes'}}
+  // expected-note@#GH218548_cache_two_bytes {{because 'sizeof(int) == 2' (4 == 2) evaluated to false}}
+
+  g<char, short, short>();
+  g<char, int, short, short, short>();
+  // expected-error at -1 {{no matching function}}
+  // expected-note@#GH218548_cache_g {{constraints not satisfied}}
+  // expected-note@#GH218548_cache_g {{because 'int' does not satisfy 'wrapped'}}
+  // expected-note@#GH218548_cache_wrapped {{because 'int' does not satisfy 'two_bytes'}}
+  // expected-note@#GH218548_cache_two_bytes {{because 'sizeof(int) == 2' (4 == 2) evaluated to false}}
 }
 
 }



More information about the cfe-commits mailing list