[clang] [clang-tools-extra] [Clang][C++29] Template pack indexing (PR #218738)

Corentin Jabot via cfe-commits cfe-commits at lists.llvm.org
Wed Aug 26 06:13:36 PDT 2026


https://github.com/cor3ntin updated https://github.com/llvm/llvm-project/pull/218738

>From ff87a746cbb4e3f3e86f3dfe697c4f4127ff78d4 Mon Sep 17 00:00:00 2001
From: Corentin Jabot <corentinjabot at gmail.com>
Date: Fri, 14 Aug 2026 15:47:15 +0200
Subject: [PATCH 1/4] [Clang][C++29] Template pack indexing.

This partially implement p3670r4 (https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2026/p3670r4.pdf)
I haven't implemented mangling yet, it part to limit the scope
of this change which is somewhat larger than I thought it would be.

This introduces a new uncommon template name storage kind
that stores a pattern and the expanded parameter, like we do
for types and expressions.

The rest is fairly mechanical.

The feature is backported to C++98 (for type template parameters).
Funnilly, the backport of pack indexing of types was never actually
tested in C++98 mode and did not work.
It should be fixed by this PR but I'll write tests for it
as a follow up.

I did my best to accommodate clang-tools-extra.
I wrote some tests to mirror some issues reported for pack indexing of
types, hopefully there is gonna be a shorter trail of bugs...
but because this touches type constraints, who knows.
---
 clang-tools-extra/clangd/DumpAST.cpp          |   6 +-
 clang-tools-extra/clangd/FindTarget.cpp       |  12 +-
 clang-tools-extra/clangd/Selection.cpp        |   5 +-
 .../clangd/SemanticHighlighting.cpp           |   1 +
 .../clangd/unittests/DumpASTTests.cpp         |  23 +
 .../clangd/unittests/FindTargetTests.cpp      |  21 +
 clang-tools-extra/modularize/Modularize.cpp   |  10 +-
 clang/docs/LanguageExtensions.md              |   1 +
 clang/docs/ReleaseNotes.md                    |   7 +
 clang/include/clang/AST/ASTConcept.h          |   2 +-
 clang/include/clang/AST/ASTContext.h          |   7 +
 clang/include/clang/AST/DependenceFlags.h     |   7 +
 clang/include/clang/AST/ExprCXX.h             |   8 +-
 clang/include/clang/AST/PropertiesBase.td     |  20 +
 clang/include/clang/AST/RecursiveASTVisitor.h |  12 +-
 clang/include/clang/AST/TemplateName.h        |  77 +++-
 .../include/clang/Basic/DiagnosticASTKinds.td |   1 +
 .../clang/Basic/DiagnosticSemaKinds.td        |   3 +
 clang/include/clang/ExtractAPI/API.h          |  30 +-
 clang/include/clang/Parse/Parser.h            |   7 +
 clang/include/clang/Sema/Sema.h               |  26 +-
 clang/lib/AST/ASTConcept.cpp                  |   5 +-
 clang/lib/AST/ASTContext.cpp                  |  41 +-
 clang/lib/AST/ASTDumper.cpp                   |   9 +-
 clang/lib/AST/ASTImporter.cpp                 |  21 +
 clang/lib/AST/ASTStructuralEquivalence.cpp    |   9 +
 clang/lib/AST/ASTTypeTraits.cpp               |   2 +-
 clang/lib/AST/ComputeDependence.cpp           |   4 +-
 clang/lib/AST/DeclPrinter.cpp                 |  18 +-
 clang/lib/AST/ItaniumMangle.cpp               |  30 ++
 clang/lib/AST/ODRHash.cpp                     |   6 +
 clang/lib/AST/StmtPrinter.cpp                 |   2 +-
 clang/lib/AST/TemplateBase.cpp                |  11 +-
 clang/lib/AST/TemplateName.cpp                | 160 ++++++-
 clang/lib/AST/TextNodeDumper.cpp              |  18 +-
 clang/lib/AST/Type.cpp                        |   4 +-
 clang/lib/AST/TypeLoc.cpp                     |   2 +-
 clang/lib/AST/TypePrinter.cpp                 |  14 +-
 clang/lib/ExtractAPI/DeclarationFragments.cpp |   8 +-
 clang/lib/Frontend/InitPreprocessor.cpp       |   2 +-
 clang/lib/Parse/ParseDeclCXX.cpp              |  82 ++++
 clang/lib/Parse/ParseExpr.cpp                 |   5 +-
 clang/lib/Parse/ParseExprCXX.cpp              |  77 ++--
 clang/lib/Parse/ParseTemplate.cpp             |  31 +-
 clang/lib/Sema/SemaCodeComplete.cpp           |   3 +-
 clang/lib/Sema/SemaExpr.cpp                   |   6 +
 clang/lib/Sema/SemaOverload.cpp               |   3 +-
 clang/lib/Sema/SemaTemplate.cpp               |  62 +--
 clang/lib/Sema/SemaTemplateDeduction.cpp      |   4 +-
 clang/lib/Sema/SemaTemplateInstantiate.cpp    |  22 +-
 clang/lib/Sema/SemaTemplateVariadic.cpp       |  68 +++
 clang/lib/Sema/SemaType.cpp                   |  32 +-
 clang/lib/Sema/TreeTransform.h                | 159 ++++++-
 .../Serialization/TemplateArgumentHasher.cpp  |   3 +
 .../AST/ast-dump-pack-indexing-template.cpp   |  56 +++
 .../AST/ast-print-pack-indexing-template.cpp  |  42 ++
 .../test/ExtractAPI/pack_indexing_concept.cpp |  33 ++
 clang/test/Lexer/cxx-features.cpp             |   2 +-
 .../test/Modules/pack-indexing-template.cppm  |  73 ++++
 clang/test/PCH/pack-indexing-template.cpp     |  67 +++
 .../Parser/cxx2d-pack-indexing-template.cpp   |  69 +++
 ...cxx2d-pack-indexing-template-ext-diags.cpp |  54 +++
 .../SemaCXX/cxx2d-pack-indexing-template.cpp  | 400 ++++++++++++++++++
 clang/tools/libclang/CIndex.cpp               |   4 +
 clang/www/cxx_status.html                     |   6 +-
 65 files changed, 1817 insertions(+), 198 deletions(-)
 create mode 100644 clang/test/AST/ast-dump-pack-indexing-template.cpp
 create mode 100644 clang/test/AST/ast-print-pack-indexing-template.cpp
 create mode 100644 clang/test/ExtractAPI/pack_indexing_concept.cpp
 create mode 100644 clang/test/Modules/pack-indexing-template.cppm
 create mode 100644 clang/test/PCH/pack-indexing-template.cpp
 create mode 100644 clang/test/Parser/cxx2d-pack-indexing-template.cpp
 create mode 100644 clang/test/SemaCXX/cxx2d-pack-indexing-template-ext-diags.cpp
 create mode 100644 clang/test/SemaCXX/cxx2d-pack-indexing-template.cpp

diff --git a/clang-tools-extra/clangd/DumpAST.cpp b/clang-tools-extra/clangd/DumpAST.cpp
index c3c35c51cf1ea..faa18fb7baa46 100644
--- a/clang-tools-extra/clangd/DumpAST.cpp
+++ b/clang-tools-extra/clangd/DumpAST.cpp
@@ -185,6 +185,7 @@ class DumpVisitor : public RecursiveASTVisitor<DumpVisitor> {
       TEMPLATE_KIND(SubstTemplateTemplateParmPack);
       TEMPLATE_KIND(UsingTemplate);
       TEMPLATE_KIND(DeducedTemplate);
+      TEMPLATE_KIND(PackIndexingTemplate);
 #undef TEMPLATE_KIND
     }
     llvm_unreachable("Unhandled NameKind enum");
@@ -303,7 +304,10 @@ class DumpVisitor : public RecursiveASTVisitor<DumpVisitor> {
     return CBS.isVirtual() ? "virtual" : "";
   }
   std::string getDetail(const ConceptReference *CR) {
-    return CR->getNamedConcept().getAsTemplateDecl()->getNameAsString();
+    TemplateName TN = CR->getNamedConcept();
+    if (const auto *TD = TN.getAsTemplateDecl())
+      return TD->getNameAsString();
+    return getDetail(TN);
   }
 
   /// Arcana is produced by TextNodeDumper, for the types it supports.
diff --git a/clang-tools-extra/clangd/FindTarget.cpp b/clang-tools-extra/clangd/FindTarget.cpp
index aea04e14b2cf8..142776d0dfaa7 100644
--- a/clang-tools-extra/clangd/FindTarget.cpp
+++ b/clang-tools-extra/clangd/FindTarget.cpp
@@ -128,6 +128,14 @@ bool shouldSkipTypedef(const TypedefNameDecl *TD) {
 //      template<class X> using pvec = vector<x*>; pvec<int> x;
 //    There's no Decl `pvec<int>`, we must choose `pvec<X>` or `vector<int*>`
 //    and both are lossy. We must know upfront what the caller ultimately wants.
+
+static const TemplateDecl *getReferencedConcept(const ConceptReference *CR) {
+  TemplateName TN = CR->getNamedConcept();
+  if (const TemplateDecl *TD = TN.getAsTemplateDecl())
+    return TD;
+  return TN.getAsTemplateTemplateParmDecl();
+}
+
 struct TargetFinder {
   using RelSet = DeclRelationSet;
   using Rel = DeclRelation;
@@ -519,7 +527,7 @@ struct TargetFinder {
   }
 
   void add(const ConceptReference *CR, RelSet Flags) {
-    add(CR->getNamedConcept().getAsTemplateDecl(), Flags);
+    add(getReferencedConcept(CR), Flags);
   }
 };
 
@@ -1081,7 +1089,7 @@ class ExplicitReferenceCollector
       return {ReferenceLoc{CR->getNestedNameSpecifierLoc(),
                            CR->getConceptNameLoc(),
                            /*IsDecl=*/false,
-                           {CR->getNamedConcept().getAsTemplateDecl()}}};
+                           {getReferencedConcept(CR)}}};
     if (const OffsetOfNode *OON = N.get<OffsetOfNode>()) {
       if (OON->getKind() == OffsetOfNode::Field)
         return {ReferenceLoc{NestedNameSpecifierLoc(),
diff --git a/clang-tools-extra/clangd/Selection.cpp b/clang-tools-extra/clangd/Selection.cpp
index 41d028cfcf4b5..ccd6ade47e6ef 100644
--- a/clang-tools-extra/clangd/Selection.cpp
+++ b/clang-tools-extra/clangd/Selection.cpp
@@ -733,7 +733,10 @@ class SelectionVisitor : public RecursiveASTVisitor<SelectionVisitor> {
     return traverseNode(E, [&] { return TraverseStmt(E->getSyntacticForm()); });
   }
   bool TraverseTypeConstraint(const TypeConstraint *C) {
-    if (auto *E = C->getImmediatelyDeclaredConstraint()) {
+    // A concept named through a template template parameter is not part of the
+    // immediately-declared constraint.
+    if (auto *E = C->getImmediatelyDeclaredConstraint();
+        E && !C->getNamedConcept().getAsTemplateTemplateParmDecl()) {
       // Technically this expression is 'implicit' and not traversed by the RAV.
       // However, the range is correct, so we visit expression to avoid adding
       // an extra kind to 'DynTypeNode' that hold 'TypeConstraint'.
diff --git a/clang-tools-extra/clangd/SemanticHighlighting.cpp b/clang-tools-extra/clangd/SemanticHighlighting.cpp
index d2d110b72011b..8948f1476e108 100644
--- a/clang-tools-extra/clangd/SemanticHighlighting.cpp
+++ b/clang-tools-extra/clangd/SemanticHighlighting.cpp
@@ -1024,6 +1024,7 @@ class CollectExtraHighlightings
     case TemplateName::SubstTemplateTemplateParmPack:
     case TemplateName::UsingTemplate:
     case TemplateName::DeducedTemplate:
+    case TemplateName::PackIndexingTemplate:
       // Names that could be resolved to a TemplateDecl are handled elsewhere.
       break;
     }
diff --git a/clang-tools-extra/clangd/unittests/DumpASTTests.cpp b/clang-tools-extra/clangd/unittests/DumpASTTests.cpp
index 1ea25d918dad6..bfdb156ceb6cb 100644
--- a/clang-tools-extra/clangd/unittests/DumpASTTests.cpp
+++ b/clang-tools-extra/clangd/unittests/DumpASTTests.cpp
@@ -227,6 +227,29 @@ TEST(DumpASTTests, UnbalancedBraces) {
   ASSERT_EQ(Node.range, Case.range("func"));
 }
 
+bool hasDetail(const ASTNode &Node, llvm::StringRef Detail) {
+  if (Node.detail == Detail)
+    return true;
+  for (const ASTNode &Child : Node.children)
+    if (hasDetail(Child, Detail))
+      return true;
+  return false;
+}
+
+TEST(DumpASTTests, PackIndexedConcept) {
+  auto TU = TestTU::withCode(R"cpp(
+template <template <class> concept... CC, CC...[0] T>
+void func(T);
+  )cpp");
+  TU.ExtraArgs = {"-std=c++2d"};
+  ParsedAST AST = TU.build();
+  const ASTNode Node = dumpAST(
+      DynTypedNode::create(*AST.getASTContext().getTranslationUnitDecl()),
+      AST.getTokens(), AST.getASTContext());
+
+  EXPECT_TRUE(hasDetail(Node, "CC...[0]"));
+}
+
 TEST(DumpASTTests, NestedTemplates) {
   // Test that we don't crash while trying to dump AST of a template function
   // with nested template names such as Foo<V>::template Bar<W>::Value.
diff --git a/clang-tools-extra/clangd/unittests/FindTargetTests.cpp b/clang-tools-extra/clangd/unittests/FindTargetTests.cpp
index 3377b017410b5..87febfef753a1 100644
--- a/clang-tools-extra/clangd/unittests/FindTargetTests.cpp
+++ b/clang-tools-extra/clangd/unittests/FindTargetTests.cpp
@@ -659,6 +659,27 @@ TEST_F(TargetDeclTest, Concept) {
                {"template <typename T, typename U> concept Fooable = true"});
 }
 
+TEST_F(TargetDeclTest, PackIndexedConcept) {
+  Flags.push_back("-std=c++2d");
+
+  // constrained-parameter
+  Code = R"cpp(
+    template <template <class> concept... CC>
+    struct S {
+      template <[[CC]]...[0] T>
+      void bar(T t);
+    };
+  )cpp";
+  EXPECT_DECLS("ConceptReference", {"template <class> concept ...CC"});
+
+  // constrained placeholder type
+  Code = R"cpp(
+    template <template <class> concept... CC>
+    void bar([[CC]]...[0] auto t);
+  )cpp";
+  EXPECT_DECLS("ConceptReference", {"template <class> concept ...CC"});
+}
+
 TEST_F(TargetDeclTest, Coroutine) {
   Flags.push_back("-std=c++20");
 
diff --git a/clang-tools-extra/modularize/Modularize.cpp b/clang-tools-extra/modularize/Modularize.cpp
index 5af50c71d1c16..be25f3daac798 100644
--- a/clang-tools-extra/modularize/Modularize.cpp
+++ b/clang-tools-extra/modularize/Modularize.cpp
@@ -544,7 +544,10 @@ class CollectEntitiesVisitor
   bool TraverseDeclarationNameInfo(DeclarationNameInfo NameInfo) {
     return true;
   }
-  bool TraverseTemplateName(TemplateName Template) { return true; }
+  bool TraverseTemplateName(TemplateName Template,
+                            bool TraverseQualifier = true) {
+    return true;
+  }
   bool TraverseTemplateArgument(const TemplateArgument &Arg) { return true; }
   bool TraverseTemplateArgumentLoc(const TemplateArgumentLoc &ArgLoc) {
     return true;
@@ -727,7 +730,10 @@ class CompileCheckVisitor
   bool TraverseDeclarationNameInfo(DeclarationNameInfo NameInfo) {
     return true;
   }
-  bool TraverseTemplateName(TemplateName Template) { return true; }
+  bool TraverseTemplateName(TemplateName Template,
+                            bool TraverseQualifier = true) {
+    return true;
+  }
   bool TraverseTemplateArgument(const TemplateArgument &Arg) { return true; }
   bool TraverseTemplateArgumentLoc(const TemplateArgumentLoc &ArgLoc) {
     return true;
diff --git a/clang/docs/LanguageExtensions.md b/clang/docs/LanguageExtensions.md
index 5e7f147f23259..6289a2ddc6c56 100644
--- a/clang/docs/LanguageExtensions.md
+++ b/clang/docs/LanguageExtensions.md
@@ -1787,6 +1787,7 @@ More information can be found [here](https://clang.llvm.org/docs/Modules.html).
 | Variadic Friends                              | \_\_cpp_variadic_friend            | C++26         | C++03         |
 | Trivial Relocatability                        | \_\_cpp_trivial_relocatability     | C++26         | C++03         |
 |``auto()`` cast                                | \_\_cpp_auto_cast                  | C++26         | C++03         |
+| Pack Indexing for Template Names              | \_\_cpp_pack_indexing >= 202606L   | C++2d         | C++03         |
 | Designated initializers (N494)                |                                    | C99           | C89           |
 | `_Complex` (N693)                             |                                    | C99           | C89, C++      |
 | `_Bool` (N815)                                |                                    | C99           | C89           |
diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index ca0dbfa2af229..6a342c78f66d5 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -136,6 +136,13 @@ features cannot lower the translation-unit ABI level;
   following new Unicode recommendations), applied as a DR to all C++ language
   modes.
 
+- Clang now supports [P3670R4](https://wg21.link/p3670r4) (Pack indexing for
+  template names), which allows a pack of templates to be indexed, as in
+  `TT...[0]<int>`. Like pack indexing of types and expressions, this is
+  available in all C++ language modes as an extension, controlled by
+  `-Wc++2d-extensions` and `-Wpre-c++2d-compat`, and `__cpp_pack_indexing` is
+  bumped to `202606L`.
+
 #### C++2c Feature Support
 
 - Added `__builtin_type_order` for compatibility with GCC as part of the
diff --git a/clang/include/clang/AST/ASTConcept.h b/clang/include/clang/AST/ASTConcept.h
index 9a65c32b5ca1e..62fe2c0f653d5 100644
--- a/clang/include/clang/AST/ASTConcept.h
+++ b/clang/include/clang/AST/ASTConcept.h
@@ -212,7 +212,7 @@ class ConceptReference {
 
   void print(llvm::raw_ostream &OS, const PrintingPolicy &Policy) const;
   void dump() const;
-  void dump(llvm::raw_ostream &) const;
+  void dump(llvm::raw_ostream &OS, const ASTContext &Context) const;
 };
 
 /// Models the abbreviated syntax to constrain a template type parameter:
diff --git a/clang/include/clang/AST/ASTContext.h b/clang/include/clang/AST/ASTContext.h
index 802f1ebf755b7..c2382d3d16ab4 100644
--- a/clang/include/clang/AST/ASTContext.h
+++ b/clang/include/clang/AST/ASTContext.h
@@ -311,6 +311,8 @@ class ASTContext : public RefCountedBase<ASTContext> {
     SubstTemplateTemplateParmPacks;
   mutable llvm::ContextualFoldingSet<DeducedTemplateStorage, ASTContext &>
       DeducedTemplates;
+  mutable llvm::ContextualFoldingSet<PackIndexingTemplateStorage, ASTContext &>
+      PackIndexingTemplates;
 
   mutable llvm::ContextualFoldingSet<ArrayParameterType, ASTContext &>
       ArrayParameterTypes;
@@ -2678,6 +2680,11 @@ class ASTContext : public RefCountedBase<ASTContext> {
                                                 unsigned Index,
                                                 bool Final) const;
 
+  TemplateName
+  getPackIndexingTemplateName(TemplateName Pattern, Expr *IndexExpr,
+                              bool FullySubstituted = false,
+                              ArrayRef<TemplateName> Expansions = {}) const;
+
   /// Represents a TemplateName which had some of its default arguments
   /// deduced. This both represents this default argument deduction as sugar,
   /// and provides the support for it's equivalences through canonicalization.
diff --git a/clang/include/clang/AST/DependenceFlags.h b/clang/include/clang/AST/DependenceFlags.h
index c4395259f0758..968fe8dd8c22b 100644
--- a/clang/include/clang/AST/DependenceFlags.h
+++ b/clang/include/clang/AST/DependenceFlags.h
@@ -259,6 +259,9 @@ inline ExprDependence toExprDependenceAsWritten(TypeDependence D) {
 inline ExprDependence toExprDependence(NestedNameSpecifierDependence D) {
   return Dependence(D).expr();
 }
+inline ExprDependence toExprDependence(TemplateNameDependence D) {
+  return Dependence(D).expr();
+}
 inline ExprDependence turnTypeToValueDependence(ExprDependence D) {
   // Type-dependent expressions are always be value-dependent, so we simply drop
   // type dependency.
@@ -315,6 +318,10 @@ toTemplateNameDependence(NestedNameSpecifierDependence D) {
   return Dependence(D).templateName();
 }
 
+inline TemplateNameDependence toTemplateNameDependence(ExprDependence D) {
+  return Dependence(D).templateName();
+}
+
 inline TemplateNameDependence
 toTemplateNameDependence(TemplateArgumentDependence D) {
   return Dependence(D).templateName();
diff --git a/clang/include/clang/AST/ExprCXX.h b/clang/include/clang/AST/ExprCXX.h
index bb790b1100e7f..7a2022adf09e4 100644
--- a/clang/include/clang/AST/ExprCXX.h
+++ b/clang/include/clang/AST/ExprCXX.h
@@ -3507,12 +3507,12 @@ class DependentTemplateIdExpr final
   TemplateName getTemplateName() const { return Name; }
 
   TemplateTemplateParmDecl *getParameter() const {
-    return cast<TemplateTemplateParmDecl>(Name.getAsTemplateDecl());
+    TemplateTemplateParmDecl *P = Name.getAsTemplateTemplateParmDecl();
+    assert(P && "A dependent template-id always names a template parameter");
+    return P;
   }
 
-  bool isConceptReference() const {
-    return getParameter()->templateParameterKind() == TNK_Concept_template;
-  }
+  bool isConceptReference() const { return Name.isConceptName(); }
 
   SourceLocation getLAngleLoc() const { return KWAndArgs.LAngleLoc; }
   SourceLocation getRAngleLoc() const { return KWAndArgs.RAngleLoc; }
diff --git a/clang/include/clang/AST/PropertiesBase.td b/clang/include/clang/AST/PropertiesBase.td
index d36cbbf7074b5..347f58d45ffa8 100644
--- a/clang/include/clang/AST/PropertiesBase.td
+++ b/clang/include/clang/AST/PropertiesBase.td
@@ -810,6 +810,26 @@ let Class = PropertyTypeCase<TemplateName, "DeducedTemplate"> in {
     return ctx.getDeducedTemplateName(underlying, {startPos, defaultArgs});
   }]>;
 }
+let Class = PropertyTypeCase<TemplateName, "PackIndexingTemplate"> in {
+  def : ReadHelper<[{
+    auto PI = node.getAsPackIndexingTemplate();
+  }]>;
+  def : Property<"pattern", TemplateName> {
+    let Read = [{ PI->getPattern() }];
+  }
+  def : Property<"indexExpression", ExprRef> {
+    let Read = [{ PI->getIndexExpr() }];
+  }
+  def : Property<"isFullySubstituted", Bool> {
+    let Read = [{ PI->isFullySubstituted() }];
+  }
+  def : Property<"expansions", Array<TemplateName>> {
+    let Read = [{ PI->getExpansions() }];
+  }
+  def : Creator<[{
+    return ctx.getPackIndexingTemplateName(pattern, indexExpression, isFullySubstituted, expansions);
+  }]>;
+}
 
 // Type cases for TemplateArgument.
 def : PropertyTypeKind<TemplateArgument, TemplateArgumentKind,
diff --git a/clang/include/clang/AST/RecursiveASTVisitor.h b/clang/include/clang/AST/RecursiveASTVisitor.h
index a933662f77273..ed230d50e92d2 100644
--- a/clang/include/clang/AST/RecursiveASTVisitor.h
+++ b/clang/include/clang/AST/RecursiveASTVisitor.h
@@ -540,7 +540,10 @@ bool RecursiveASTVisitor<Derived>::TraverseTypeConstraint(
     TRY_TO(TraverseConceptReference(C->getConceptReference()));
     return true;
   }
-  if (Expr *IDC = C->getImmediatelyDeclaredConstraint()) {
+  // A concept named through a template template parameter has no
+  // ConceptSpecializationExpr to carry its ConceptReference.
+  if (Expr *IDC = C->getImmediatelyDeclaredConstraint();
+      IDC && !C->getNamedConcept().getAsTemplateTemplateParmDecl()) {
     TRY_TO(TraverseStmt(IDC));
   } else {
     // Avoid traversing the ConceptReference in the TypeConstraint
@@ -882,6 +885,10 @@ bool RecursiveASTVisitor<Derived>::TraverseTemplateName(
     if (TraverseQualifier && QTN->getQualifier()) {
       TRY_TO(TraverseNestedNameSpecifier(QTN->getQualifier()));
     }
+  } else if (PackIndexingTemplateStorage *PI =
+                 Template.getAsPackIndexingTemplate()) {
+    TRY_TO(TraverseTemplateName(PI->getPattern(), TraverseQualifier));
+    TRY_TO(TraverseStmt(PI->getIndexExpr()));
   }
 
   return true;
@@ -2635,6 +2642,7 @@ DEF_TRAVERSE_STMT(CXXDependentScopeMemberExpr, {
 
 DEF_TRAVERSE_STMT(DependentTemplateIdExpr, {
   TRY_TO(TraverseDeclarationNameInfo(S->getNameInfo()));
+  TRY_TO(TraverseTemplateName(S->getTemplateName()));
   TRY_TO(TraverseTemplateArgumentLocsHelper(S->template_arguments().data(),
                                             S->getNumTemplateArgs()));
 })
@@ -2733,6 +2741,8 @@ bool RecursiveASTVisitor<Derived>::TraverseConceptReference(
     TRY_TO(VisitConceptReference(CR));
   TRY_TO(TraverseNestedNameSpecifierLoc(CR->getNestedNameSpecifierLoc()));
   TRY_TO(TraverseDeclarationNameInfo(CR->getConceptNameInfo()));
+  TRY_TO(TraverseTemplateName(CR->getNamedConcept(),
+                              /*TraverseQualifier=*/false));
   if (CR->hasExplicitTemplateArgs())
     TRY_TO(TraverseTemplateArgumentLocsHelper(
         CR->getTemplateArgsAsWritten()->getTemplateArgs(),
diff --git a/clang/include/clang/AST/TemplateName.h b/clang/include/clang/AST/TemplateName.h
index 47cfec8f61e6f..c1d790e6a9b26 100644
--- a/clang/include/clang/AST/TemplateName.h
+++ b/clang/include/clang/AST/TemplateName.h
@@ -22,6 +22,7 @@
 #include "llvm/ADT/PointerIntPair.h"
 #include "llvm/ADT/PointerUnion.h"
 #include "llvm/Support/PointerLikeTypeTraits.h"
+#include "llvm/Support/TrailingObjects.h"
 #include <cassert>
 #include <optional>
 
@@ -30,6 +31,7 @@ namespace clang {
 class ASTContext;
 class Decl;
 class DependentTemplateName;
+class Expr;
 class IdentifierInfo;
 class NamedDecl;
 class NestedNameSpecifier;
@@ -37,6 +39,7 @@ enum OverloadedOperatorKind : int;
 class OverloadedTemplateStorage;
 class AssumedTemplateStorage;
 class DeducedTemplateStorage;
+class PackIndexingTemplateStorage;
 struct PrintingPolicy;
 class QualifiedTemplateName;
 class SubstTemplateTemplateParmPackStorage;
@@ -55,7 +58,8 @@ class UncommonTemplateNameStorage {
     Assumed, // defined in DeclarationName.h
     Deduced,
     SubstTemplateTemplateParm,
-    SubstTemplateTemplateParmPack
+    SubstTemplateTemplateParmPack,
+    PackIndexing
   };
 
   struct BitsTag {
@@ -111,6 +115,12 @@ class UncommonTemplateNameStorage {
              ? reinterpret_cast<SubstTemplateTemplateParmPackStorage *>(this)
              : nullptr;
   }
+
+  PackIndexingTemplateStorage *getAsPackIndexingTemplate() {
+    return Bits.Kind == PackIndexing
+               ? reinterpret_cast<PackIndexingTemplateStorage *>(this)
+               : nullptr;
+  }
 };
 
 /// A structure for storing the information associated with an
@@ -269,6 +279,9 @@ class TemplateName {
     /// A template name that refers to another TemplateName with deduced default
     /// arguments.
     DeducedTemplate,
+
+    /// A pack-index-template-name.
+    PackIndexingTemplate,
   };
 
   TemplateName() = default;
@@ -281,6 +294,7 @@ class TemplateName {
   explicit TemplateName(DependentTemplateName *Dep);
   explicit TemplateName(UsingShadowDecl *Using);
   explicit TemplateName(DeducedTemplateStorage *Deduced);
+  explicit TemplateName(PackIndexingTemplateStorage *PackIndexing);
 
   /// Determine whether this template name is NULL.
   bool isNull() const;
@@ -297,6 +311,10 @@ class TemplateName {
   /// set of function templates, returns NULL.
   TemplateDecl *getAsTemplateDecl(bool IgnoreDeduced = false) const;
 
+  /// Retrieve the template template parameter that this template name refers
+  /// to, if any.
+  TemplateTemplateParmDecl *getAsTemplateTemplateParmDecl() const;
+
   /// Retrieves the underlying template name that
   /// this template name refers to, along with the
   /// deduced default arguments, if any.
@@ -354,6 +372,9 @@ class TemplateName {
   /// Retrieve the deduced template info, if any.
   DeducedTemplateStorage *getAsDeducedTemplateName() const;
 
+  /// Retrieve the pack-index-template-name storage, if any.
+  PackIndexingTemplateStorage *getAsPackIndexingTemplate() const;
+
   std::optional<TemplateName> desugar(bool IgnoreDeduced) const;
 
   TemplateName getUnderlying() const;
@@ -371,6 +392,10 @@ class TemplateName {
   /// unexpanded parameter pack (for C++0x variadic templates).
   bool containsUnexpandedParameterPack() const;
 
+  /// Determines whether this template name denotes a concept, or a template
+  /// template parameter denoting one.
+  bool isConceptName() const;
+
   enum class Qualified { None, AsWritten };
   /// Print the template name.
   ///
@@ -482,6 +507,56 @@ class DeducedTemplateStorage : public UncommonTemplateNameStorage,
                       TemplateName Underlying, const DefaultArguments &DefArgs);
 };
 
+/// A structure for storing a pack-index-template-name ([temp.names]).
+///
+/// C++29 [temp.names]p1:
+///   pack-index-template-name:
+///     simple-template-name ... [ constant-expression ]
+class PackIndexingTemplateStorage final
+    : public UncommonTemplateNameStorage,
+      public llvm::FoldingSetNode,
+      private llvm::TrailingObjects<PackIndexingTemplateStorage, TemplateName> {
+  friend class ASTContext;
+  friend TrailingObjects;
+
+  TemplateName Pattern;
+  Expr *IndexExpr;
+  bool FullySubstituted;
+
+  PackIndexingTemplateStorage(TemplateName Pattern, Expr *IndexExpr,
+                              bool FullySubstituted,
+                              ArrayRef<TemplateName> Expansions);
+
+public:
+  TemplateName getPattern() const { return Pattern; }
+
+  Expr *getIndexExpr() const { return IndexExpr; }
+
+  bool isFullySubstituted() const { return FullySubstituted; }
+
+  ArrayRef<TemplateName> getExpansions() const {
+    return getTrailingObjects(Bits.Data);
+  }
+
+  TemplateTemplateParmDecl *getParameterPack() const;
+
+  UnsignedOrNone getSelectedIndex() const;
+
+  TemplateName getSelectedTemplate() const;
+
+  bool expandsToEmptyPack() const {
+    return isFullySubstituted() && Bits.Data == 0;
+  }
+
+  TemplateNameDependence getDependence() const;
+
+  void Profile(llvm::FoldingSetNodeID &ID, const ASTContext &Context) const;
+
+  static void Profile(llvm::FoldingSetNodeID &ID, const ASTContext &Context,
+                      TemplateName Pattern, Expr *IndexExpr,
+                      bool FullySubstituted, ArrayRef<TemplateName> Expansions);
+};
+
 inline TemplateName TemplateName::getUnderlying() const {
   if (SubstTemplateTemplateParmStorage *subst
         = getAsSubstTemplateTemplateParm())
diff --git a/clang/include/clang/Basic/DiagnosticASTKinds.td b/clang/include/clang/Basic/DiagnosticASTKinds.td
index 3c39ccf51ab67..219d66de2fa61 100644
--- a/clang/include/clang/Basic/DiagnosticASTKinds.td
+++ b/clang/include/clang/Basic/DiagnosticASTKinds.td
@@ -1067,6 +1067,7 @@ def err_unsupported_itanium_mangling : Error<
     "|%UnnamedUnionNTTP{unnamed union non-type template parameter}"
     "|%RequiresExprWithSubstitutionFailure{requires-expression "
     "containing a substitution failure}"
+    "|%PackIndexTemplateName{pack indexing template name}"
     "}0">;
 
 def err_unsupported_itanium_expr_mangling : Error<
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 3a910c9c3f2b9..a98a07df48161 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -77,6 +77,9 @@ defm auto_expr : CXX23Compat<"'auto' as a functional-style cast is">;
 // C++26 compatibility with C++23 and earlier.
 defm decomp_decl_cond : CXX26Compat<"structured binding declaration in a condition is">;
 
+// C++29 compatibility with C++26 and earlier.
+defm pack_indexing_template : CXX29Compat<"pack indexing for template names is">;
+
 // Compatibility warnings duplicated across multiple language versions.
 foreach std = [14, 20, 23] in {
   defm cxx#std#_constexpr_body_invalid_stmt : CompatWarning<
diff --git a/clang/include/clang/ExtractAPI/API.h b/clang/include/clang/ExtractAPI/API.h
index d0d05fcab4af6..84bf152841884 100644
--- a/clang/include/clang/ExtractAPI/API.h
+++ b/clang/include/clang/ExtractAPI/API.h
@@ -37,6 +37,15 @@
 namespace clang {
 namespace extractapi {
 
+inline std::string getTypeConstraintSpelling(const TypeConstraint *TC,
+                                             const ASTContext &Context) {
+  std::string Name;
+  llvm::raw_string_ostream OS(Name);
+  TC->getNamedConcept().print(OS, Context.getPrintingPolicy(),
+                              TemplateName::Qualified::None);
+  return Name;
+}
+
 class Template {
   struct TemplateParameter {
     // "class", "typename", or concept name
@@ -71,11 +80,8 @@ class Template {
         continue;
       std::string Type;
       if (Param->hasTypeConstraint())
-        Type = Param->getTypeConstraint()
-                   ->getNamedConcept()
-                   .getAsTemplateDecl()
-                   ->getName()
-                   .str();
+        Type = getTypeConstraintSpelling(Param->getTypeConstraint(),
+                                         Param->getASTContext());
       else if (Param->wasDeclaredWithTypename())
         Type = "typename";
       else
@@ -93,11 +99,8 @@ class Template {
         continue;
       std::string Type;
       if (Param->hasTypeConstraint())
-        Type = Param->getTypeConstraint()
-                   ->getNamedConcept()
-                   .getAsTemplateDecl()
-                   ->getName()
-                   .str();
+        Type = getTypeConstraintSpelling(Param->getTypeConstraint(),
+                                         Param->getASTContext());
       else if (Param->wasDeclaredWithTypename())
         Type = "typename";
       else
@@ -115,11 +118,8 @@ class Template {
         continue;
       std::string Type;
       if (Param->hasTypeConstraint())
-        Type = Param->getTypeConstraint()
-                   ->getNamedConcept()
-                   .getAsTemplateDecl()
-                   ->getName()
-                   .str();
+        Type = getTypeConstraintSpelling(Param->getTypeConstraint(),
+                                         Param->getASTContext());
       else if (Param->wasDeclaredWithTypename())
         Type = "typename";
       else
diff --git a/clang/include/clang/Parse/Parser.h b/clang/include/clang/Parse/Parser.h
index 163aa483a84e3..ae91153e34e3a 100644
--- a/clang/include/clang/Parse/Parser.h
+++ b/clang/include/clang/Parse/Parser.h
@@ -3004,6 +3004,13 @@ class Parser : public CodeCompletionHandler {
                                            SourceLocation StartLoc,
                                            SourceLocation EndLoc);
 
+  TemplateNameKind isPackIndexingTemplateName(UnqualifiedId &Name,
+                                              TemplateTy &Template);
+
+  bool AnnotatePackIndexingTemplateName(CXXScopeSpec &SS, UnqualifiedId &Name,
+                                        TemplateTy Template,
+                                        TemplateNameKind TNK);
+
   /// Return true if the next token should be treated as a [[]] attribute,
   /// or as a keyword that behaves like one.  The former is only true if
   /// [[]] attributes are enabled, whereas the latter is true whenever
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index bad4430f78b55..4a01b6d7e6d99 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -11618,7 +11618,7 @@ class Sema final : public SemaBase {
   /// of arguments for the named concept).
   bool AttachTypeConstraint(NestedNameSpecifierLoc NS,
                             DeclarationNameInfo NameInfo,
-                            TemplateDecl *NamedConcept, NamedDecl *FoundDecl,
+                            TemplateName NamedConcept, NamedDecl *FoundDecl,
                             const TemplateArgumentListInfo *TemplateArgs,
                             TemplateTypeParmDecl *ConstrainedParameter,
                             SourceLocation EllipsisLoc);
@@ -11827,7 +11827,7 @@ class Sema final : public SemaBase {
                                 const TemplateArgumentListInfo *TemplateArgs);
 
   ExprResult CheckVarOrConceptTemplateTemplateId(
-      const DeclarationNameInfo &NameInfo, TemplateTemplateParmDecl *Template,
+      const DeclarationNameInfo &NameInfo, TemplateName Template,
       const TemplateArgumentListInfo *TemplateArgs);
 
   ExprResult
@@ -14686,6 +14686,15 @@ class Sema final : public SemaBase {
   void collectUnexpandedParameterPacks(
       QualType T, SmallVectorImpl<UnexpandedParameterPack> &Unexpanded);
 
+  /// Collect the set of unexpanded parameter packs within the given
+  /// template name.
+  ///
+  /// \param Template The template name that will be traversed to find
+  /// unexpanded parameter packs.
+  void collectUnexpandedParameterPacks(
+      TemplateName Template,
+      SmallVectorImpl<UnexpandedParameterPack> &Unexpanded);
+
   /// Collect the set of unexpanded parameter packs within the given
   /// type.
   ///
@@ -14900,6 +14909,19 @@ class Sema final : public SemaBase {
                                    ArrayRef<Expr *> ExpandedExprs = {},
                                    bool FullySubstituted = false);
 
+  TemplateName ActOnPackIndexingTemplateName(TemplateName Pattern,
+                                             SourceLocation NameLoc,
+                                             Expr *IndexExpr);
+
+  TemplateName
+  BuildPackIndexingTemplateName(TemplateName Pattern, Expr *IndexExpr,
+                                bool FullySubstituted = false,
+                                ArrayRef<TemplateName> Expansions = {});
+
+  TypeResult
+  ActOnPackIndexingDeducedTemplateSpecializationType(TemplateName Name,
+                                                     SourceLocation NameLoc);
+
   /// Handle a C++1z fold-expression: ( expr op ... op expr ).
   ExprResult ActOnCXXFoldExpr(Scope *S, SourceLocation LParenLoc, Expr *LHS,
                               tok::TokenKind Operator,
diff --git a/clang/lib/AST/ASTConcept.cpp b/clang/lib/AST/ASTConcept.cpp
index 6a90d214de7fd..2b04821a157da 100644
--- a/clang/lib/AST/ASTConcept.cpp
+++ b/clang/lib/AST/ASTConcept.cpp
@@ -96,7 +96,8 @@ ConceptReference::Create(const ASTContext &C, NestedNameSpecifierLoc NNS,
                          NamedDecl *FoundDecl, TemplateName NamedConcept,
                          const ASTTemplateArgumentListInfo *ArgsAsWritten) {
 
-  assert(NamedConcept.getKind() == TemplateName::Template);
+  assert(NamedConcept.isConceptName() &&
+         "concept reference does not name a concept");
 
   return new (C) ConceptReference(NNS, TemplateKWLoc, ConceptNameInfo,
                                   FoundDecl, NamedConcept, ArgsAsWritten);
@@ -112,7 +113,7 @@ SourceLocation ConceptReference::getBeginLoc() const {
 void ConceptReference::print(llvm::raw_ostream &OS,
                              const PrintingPolicy &Policy) const {
   NestedNameSpec.getNestedNameSpecifier().print(OS, Policy);
-  ConceptName.printName(OS, Policy);
+  NamedConcept.print(OS, Policy, TemplateName::Qualified::None);
   if (hasExplicitTemplateArgs()) {
     OS << "<";
     llvm::ListSeparator Sep(", ");
diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp
index 2be9d9430e1ec..df04410a0cd2f 100644
--- a/clang/lib/AST/ASTContext.cpp
+++ b/clang/lib/AST/ASTContext.cpp
@@ -940,8 +940,8 @@ ASTContext::ASTContext(LangOptions &LOpts, SourceManager &SM,
       AttributedTypes(this_()), DependentBitIntTypes(this_()),
       HLSLAttributedResourceTypes(this_()),
       SubstTemplateTemplateParmPacks(this_()), DeducedTemplates(this_()),
-      ArrayParameterTypes(this_()), CanonTemplateTemplateParms(this_()),
-      SourceMgr(SM), LangOpts(LOpts),
+      PackIndexingTemplates(this_()), ArrayParameterTypes(this_()),
+      CanonTemplateTemplateParms(this_()), SourceMgr(SM), LangOpts(LOpts),
       NoSanitizeL(new NoSanitizeList(LangOpts.NoSanitizeFiles, SM)),
       XRayFilter(new XRayFunctionFilter(LangOpts.XRayAlwaysInstrumentFiles,
                                         LangOpts.XRayNeverInstrumentFiles,
@@ -7405,6 +7405,10 @@ ASTContext::getNameForTemplate(TemplateName Name,
     DeducedTemplateStorage *DTS = Name.getAsDeducedTemplateName();
     return getNameForTemplate(DTS->getUnderlying(), NameLoc);
   }
+  case TemplateName::PackIndexingTemplate: {
+    PackIndexingTemplateStorage *PI = Name.getAsPackIndexingTemplate();
+    return getNameForTemplate(PI->getPattern(), NameLoc);
+  }
   }
 
   llvm_unreachable("bad template name kind!");
@@ -7472,6 +7476,16 @@ TemplateName ASTContext::getCanonicalTemplateName(TemplateName Name,
         canonArgPack, subst->getAssociatedDecl()->getCanonicalDecl(),
         subst->getIndex(), subst->getFinal());
   }
+
+  case TemplateName::PackIndexingTemplate: {
+    PackIndexingTemplateStorage *PI = Name.getAsPackIndexingTemplate();
+    SmallVector<TemplateName, 4> CanonExpansions;
+    for (TemplateName T : PI->getExpansions())
+      CanonExpansions.push_back(getCanonicalTemplateName(T, IgnoreDeduced));
+    return getPackIndexingTemplateName(
+        getCanonicalTemplateName(PI->getPattern(), IgnoreDeduced),
+        PI->getIndexExpr(), PI->isFullySubstituted(), CanonExpansions);
+  }
   case TemplateName::DeducedTemplate: {
     assert(IgnoreDeduced == false);
     DeducedTemplateStorage *DTS = Name.getAsDeducedTemplateName();
@@ -10654,6 +10668,29 @@ ASTContext::getDeducedTemplateName(TemplateName Underlying,
   return TemplateName(DTS);
 }
 
+TemplateName ASTContext::getPackIndexingTemplateName(
+    TemplateName Pattern, Expr *IndexExpr, bool FullySubstituted,
+    ArrayRef<TemplateName> Expansions) const {
+  auto &Self = const_cast<ASTContext &>(*this);
+  llvm::FoldingSetNodeID ID;
+  PackIndexingTemplateStorage::Profile(ID, Self, Pattern, IndexExpr,
+                                       FullySubstituted, Expansions);
+
+  void *InsertPos = nullptr;
+  PackIndexingTemplateStorage *PI =
+      PackIndexingTemplates.FindNodeOrInsertPos(ID, InsertPos);
+  if (!PI) {
+    void *Mem =
+        Allocate(PackIndexingTemplateStorage::totalSizeToAlloc<TemplateName>(
+                     Expansions.size()),
+                 alignof(PackIndexingTemplateStorage));
+    PI = new (Mem) PackIndexingTemplateStorage(Pattern, IndexExpr,
+                                               FullySubstituted, Expansions);
+    PackIndexingTemplates.InsertNode(PI, InsertPos);
+  }
+  return TemplateName(PI);
+}
+
 /// getFromTargetType - Given one of the integer types provided by
 /// TargetInfo, produce the corresponding type. The unsigned @p Type
 /// is actually a value of type @c TargetInfo::IntType.
diff --git a/clang/lib/AST/ASTDumper.cpp b/clang/lib/AST/ASTDumper.cpp
index f74fec07b6175..b4570b1223382 100644
--- a/clang/lib/AST/ASTDumper.cpp
+++ b/clang/lib/AST/ASTDumper.cpp
@@ -358,12 +358,13 @@ LLVM_DUMP_METHOD void APValue::dump(raw_ostream &OS,
 //===----------------------------------------------------------------------===//
 
 LLVM_DUMP_METHOD void ConceptReference::dump() const {
-  dump(llvm::errs());
+  ASTDumper P(llvm::errs(), /*ShowColors=*/false);
+  P.Visit(this);
 }
 
-LLVM_DUMP_METHOD void ConceptReference::dump(raw_ostream &OS) const {
-  auto &Ctx = getNamedConcept().getAsTemplateDecl()->getASTContext();
-  ASTDumper P(OS, Ctx, showColorsForStream(Ctx, OS));
+LLVM_DUMP_METHOD void ConceptReference::dump(raw_ostream &OS,
+                                             const ASTContext &Context) const {
+  ASTDumper P(OS, Context, showColorsForStream(Context, OS));
   P.Visit(this);
 }
 
diff --git a/clang/lib/AST/ASTImporter.cpp b/clang/lib/AST/ASTImporter.cpp
index e34b3b4025c5a..0ad4130ce252a 100644
--- a/clang/lib/AST/ASTImporter.cpp
+++ b/clang/lib/AST/ASTImporter.cpp
@@ -10332,6 +10332,27 @@ Expected<TemplateName> ASTImporter::Import(TemplateName From) {
         *ArgPackOrErr, *AssociatedDeclOrErr, SubstPack->getIndex(),
         SubstPack->getFinal());
   }
+  case TemplateName::PackIndexingTemplate: {
+    PackIndexingTemplateStorage *PI = From.getAsPackIndexingTemplate();
+    auto PatternOrErr = Import(PI->getPattern());
+    if (!PatternOrErr)
+      return PatternOrErr.takeError();
+
+    auto IndexExprOrErr = Import(PI->getIndexExpr());
+    if (!IndexExprOrErr)
+      return IndexExprOrErr.takeError();
+
+    SmallVector<TemplateName, 4> Expansions;
+    for (TemplateName T : PI->getExpansions()) {
+      auto ExpansionOrErr = Import(T);
+      if (!ExpansionOrErr)
+        return ExpansionOrErr.takeError();
+      Expansions.push_back(*ExpansionOrErr);
+    }
+
+    return ToContext.getPackIndexingTemplateName(
+        *PatternOrErr, *IndexExprOrErr, PI->isFullySubstituted(), Expansions);
+  }
   case TemplateName::UsingTemplate: {
     auto UsingOrError = Import(From.getAsUsingShadowDecl());
     if (!UsingOrError)
diff --git a/clang/lib/AST/ASTStructuralEquivalence.cpp b/clang/lib/AST/ASTStructuralEquivalence.cpp
index cce78485fad6e..029e33ae0db76 100644
--- a/clang/lib/AST/ASTStructuralEquivalence.cpp
+++ b/clang/lib/AST/ASTStructuralEquivalence.cpp
@@ -720,6 +720,15 @@ static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
            P1->getIndex() == P2->getIndex();
   }
 
+  case TemplateName::PackIndexingTemplate: {
+    PackIndexingTemplateStorage *P1 = N1.getAsPackIndexingTemplate(),
+                                *P2 = N2.getAsPackIndexingTemplate();
+    return IsStructurallyEquivalent(Context, P1->getPattern(),
+                                    P2->getPattern()) &&
+           IsStructurallyEquivalent(Context, P1->getIndexExpr(),
+                                    P2->getIndexExpr());
+  }
+
    case TemplateName::Template:
    case TemplateName::QualifiedTemplate:
    case TemplateName::SubstTemplateTemplateParm:
diff --git a/clang/lib/AST/ASTTypeTraits.cpp b/clang/lib/AST/ASTTypeTraits.cpp
index c717fd77d6ba6..27bd21fc75940 100644
--- a/clang/lib/AST/ASTTypeTraits.cpp
+++ b/clang/lib/AST/ASTTypeTraits.cpp
@@ -228,7 +228,7 @@ void DynTypedNode::dump(llvm::raw_ostream &OS,
   else if (const Type *T = get<Type>())
     T->dump(OS, Context);
   else if (const ConceptReference *C = get<ConceptReference>())
-    C->dump(OS);
+    C->dump(OS, Context);
   else if (const TypeLoc *TL = get<TypeLoc>())
     TL->dump(OS, Context);
   else
diff --git a/clang/lib/AST/ComputeDependence.cpp b/clang/lib/AST/ComputeDependence.cpp
index 0fc9da18b9394..7ea1c67ff7738 100644
--- a/clang/lib/AST/ComputeDependence.cpp
+++ b/clang/lib/AST/ComputeDependence.cpp
@@ -905,9 +905,7 @@ ExprDependence clang::computeDependence(CXXDependentScopeMemberExpr *E) {
 
 ExprDependence clang::computeDependence(DependentTemplateIdExpr *E) {
   auto D = ExprDependence::TypeValueInstantiation;
-  if (E->getTemplateName().getDependence() &
-      TemplateNameDependence::UnexpandedPack)
-    D |= ExprDependence::UnexpandedPack;
+  D |= toExprDependence(E->getTemplateName().getDependence());
   D |= getDependenceInExpr(E->getNameInfo());
   for (const auto &A : E->template_arguments())
     D |= toExprDependence(A.getArgument().getDependence());
diff --git a/clang/lib/AST/DeclPrinter.cpp b/clang/lib/AST/DeclPrinter.cpp
index 1312ffd080342..141ba50941685 100644
--- a/clang/lib/AST/DeclPrinter.cpp
+++ b/clang/lib/AST/DeclPrinter.cpp
@@ -1278,10 +1278,20 @@ void DeclPrinter::VisitTemplateDecl(const TemplateDecl *D) {
 
   if (const TemplateTemplateParmDecl *TTP =
         dyn_cast<TemplateTemplateParmDecl>(D)) {
-    if (TTP->wasDeclaredWithTypename())
-      Out << "typename";
-    else
-      Out << "class";
+    switch (TTP->templateParameterKind()) {
+    case TemplateNameKind::TNK_Concept_template:
+      Out << "concept";
+      break;
+    case TemplateNameKind::TNK_Var_template:
+      Out << "auto";
+      break;
+    default:
+      if (TTP->wasDeclaredWithTypename())
+        Out << "typename";
+      else
+        Out << "class";
+      break;
+    }
 
     if (TTP->isParameterPack())
       Out << " ...";
diff --git a/clang/lib/AST/ItaniumMangle.cpp b/clang/lib/AST/ItaniumMangle.cpp
index 7fb162a68fe88..2c38dbfac0cb2 100644
--- a/clang/lib/AST/ItaniumMangle.cpp
+++ b/clang/lib/AST/ItaniumMangle.cpp
@@ -537,6 +537,7 @@ class CXXNameMangler {
   void manglePrefix(QualType type);
   void mangleTemplatePrefix(GlobalDecl GD, bool NoFunction=false);
   void mangleTemplatePrefix(TemplateName Template);
+  void DiagnoseUnsupportedPackIndexTemplateName();
   const NamedDecl *getClosurePrefix(const Decl *ND);
   void mangleClosurePrefix(const NamedDecl *ND, bool NoFunction = false);
   bool mangleUnresolvedTypeOrSimpleId(QualType DestroyedType,
@@ -1266,6 +1267,12 @@ void CXXNameMangler::mangleFixedPointLiteral() {
       << UnsupportedItaniumManglingKind::FixedPointLiteral;
 }
 
+void CXXNameMangler::DiagnoseUnsupportedPackIndexTemplateName() {
+  DiagnosticsEngine &Diags = Context.getDiags();
+  Diags.Report(diag::err_unsupported_itanium_mangling)
+      << UnsupportedItaniumManglingKind::PackIndexTemplateName;
+}
+
 void CXXNameMangler::mangleNullPointer(QualType T) {
   //  <expr-primary> ::= L <type> 0 E
   Out << 'L';
@@ -2097,6 +2104,10 @@ void CXXNameMangler::mangleTemplateParameterList(
 void CXXNameMangler::mangleTypeConstraint(
     TemplateName Concept, ArrayRef<TemplateArgument> Arguments) {
   const TemplateDecl *TD = Concept.getAsTemplateDecl();
+  if (!TD) {
+    DiagnoseUnsupportedPackIndexTemplateName();
+    return;
+  }
   const DeclContext *DC = Context.getEffectiveDeclContext(TD);
   if (!Arguments.empty())
     mangleTemplateName(TD, Arguments);
@@ -2274,6 +2285,11 @@ void CXXNameMangler::mangleTemplatePrefix(TemplateName Template) {
   if (TemplateDecl *TD = Template.getAsTemplateDecl())
     return mangleTemplatePrefix(TD);
 
+  if (Template.getAsPackIndexingTemplate()) {
+    DiagnoseUnsupportedPackIndexTemplateName();
+    return;
+  }
+
   DependentTemplateName *Dependent = Template.getAsDependentTemplateName();
   assert(Dependent && "unexpected template name kind");
 
@@ -2431,6 +2447,11 @@ void CXXNameMangler::mangleType(TemplateName TN) {
     Out << "_SUBSTPACK_";
     break;
   }
+
+  case TemplateName::PackIndexingTemplate:
+    DiagnoseUnsupportedPackIndexTemplateName();
+    return;
+
   case TemplateName::DeducedTemplate:
     llvm_unreachable("Unexpected DeducedTemplate");
   }
@@ -2597,6 +2618,11 @@ bool CXXNameMangler::mangleUnresolvedTypeOrSimpleId(QualType Ty,
       Out << "_SUBSTPACK_";
       break;
     }
+
+    case TemplateName::PackIndexingTemplate:
+      DiagnoseUnsupportedPackIndexTemplateName();
+      return false;
+
     case TemplateName::UsingTemplate: {
       TemplateDecl *TD = TN.getAsTemplateDecl();
       assert(TD && !isa<TemplateTemplateParmDecl>(TD));
@@ -5321,6 +5347,10 @@ void CXXNameMangler::mangleExpression(const Expr *E, unsigned Arity,
   case Expr::DependentTemplateIdExprClass: {
     NotPrimaryExpr();
     const auto *DTI = cast<DependentTemplateIdExpr>(E);
+    if (DTI->getTemplateName().getAsPackIndexingTemplate()) {
+      DiagnoseUnsupportedPackIndexTemplateName();
+      break;
+    }
     mangleUnresolvedName(NestedNameSpecifier(), DTI->getName(),
                          DTI->template_arguments().data(),
                          DTI->getNumTemplateArgs(), Arity);
diff --git a/clang/lib/AST/ODRHash.cpp b/clang/lib/AST/ODRHash.cpp
index 386979d0f9ce2..9151fa2dae5f6 100644
--- a/clang/lib/AST/ODRHash.cpp
+++ b/clang/lib/AST/ODRHash.cpp
@@ -159,6 +159,12 @@ void ODRHash::AddTemplateName(TemplateName Name) {
     AddDependentTemplateName(*Name.getAsDependentTemplateName());
     break;
   }
+  case TemplateName::PackIndexingTemplate: {
+    PackIndexingTemplateStorage *PI = Name.getAsPackIndexingTemplate();
+    AddTemplateName(PI->getPattern());
+    AddStmt(PI->getIndexExpr());
+    break;
+  }
   // TODO: Support these cases.
   case TemplateName::OverloadedTemplate:
   case TemplateName::AssumedTemplate:
diff --git a/clang/lib/AST/StmtPrinter.cpp b/clang/lib/AST/StmtPrinter.cpp
index e957b03b7e898..31d670842ff87 100644
--- a/clang/lib/AST/StmtPrinter.cpp
+++ b/clang/lib/AST/StmtPrinter.cpp
@@ -2672,7 +2672,7 @@ void StmtPrinter::VisitCXXReflectExpr(CXXReflectExpr *S) {
 }
 
 void StmtPrinter::VisitDependentTemplateIdExpr(DependentTemplateIdExpr *Node) {
-  OS << Node->getNameInfo();
+  Node->getTemplateName().print(OS, Policy, TemplateName::Qualified::None);
   printTemplateArgumentList(OS, Node->template_arguments(), Policy,
                             Node->getParameter()->getTemplateParameters());
 }
diff --git a/clang/lib/AST/TemplateBase.cpp b/clang/lib/AST/TemplateBase.cpp
index 961b6acd022ef..1616e7e457dce 100644
--- a/clang/lib/AST/TemplateBase.cpp
+++ b/clang/lib/AST/TemplateBase.cpp
@@ -366,15 +366,8 @@ bool TemplateArgument::isPackExpansion() const {
 }
 
 bool TemplateArgument::isConceptOrConceptTemplateParameter() const {
-  if (getKind() != TemplateArgument::Template)
-    return false;
-
-  if (isa_and_nonnull<ConceptDecl>(getAsTemplate().getAsTemplateDecl()))
-    return true;
-  if (auto *TTP = llvm::dyn_cast_or_null<TemplateTemplateParmDecl>(
-          getAsTemplate().getAsTemplateDecl()))
-    return TTP->templateParameterKind() == TNK_Concept_template;
-  return false;
+  return getKind() == TemplateArgument::Template &&
+         getAsTemplate().isConceptName();
 }
 
 bool TemplateArgument::containsUnexpandedParameterPack() const {
diff --git a/clang/lib/AST/TemplateName.cpp b/clang/lib/AST/TemplateName.cpp
index 797a354c5d0fa..910c5a761dd77 100644
--- a/clang/lib/AST/TemplateName.cpp
+++ b/clang/lib/AST/TemplateName.cpp
@@ -16,6 +16,7 @@
 #include "clang/AST/DeclCXX.h"
 #include "clang/AST/DeclTemplate.h"
 #include "clang/AST/DependenceFlags.h"
+#include "clang/AST/Expr.h"
 #include "clang/AST/NestedNameSpecifier.h"
 #include "clang/AST/PrettyPrinter.h"
 #include "clang/AST/TemplateBase.h"
@@ -25,6 +26,7 @@
 #include "clang/Basic/OperatorKinds.h"
 #include "llvm/ADT/ArrayRef.h"
 #include "llvm/ADT/FoldingSet.h"
+#include "llvm/ADT/STLExtras.h"
 #include "llvm/Support/Compiler.h"
 #include "llvm/Support/raw_ostream.h"
 #include <cassert>
@@ -122,6 +124,95 @@ void SubstTemplateTemplateParmPackStorage::Profile(
   ID.AddBoolean(Final);
 }
 
+PackIndexingTemplateStorage::PackIndexingTemplateStorage(
+    TemplateName Pattern, Expr *IndexExpr, bool FullySubstituted,
+    ArrayRef<TemplateName> Expansions)
+    : UncommonTemplateNameStorage(PackIndexing, /*Index=*/0,
+                                  /*Data=*/Expansions.size()),
+      Pattern(Pattern), IndexExpr(IndexExpr),
+      FullySubstituted(FullySubstituted) {
+  llvm::uninitialized_copy(Expansions, getTrailingObjects());
+}
+
+TemplateTemplateParmDecl *
+PackIndexingTemplateStorage::getParameterPack() const {
+  if (SubstTemplateTemplateParmPackStorage *S =
+          Pattern.getAsSubstTemplateTemplateParmPack())
+    return S->getParameterPack();
+  return dyn_cast_if_present<TemplateTemplateParmDecl>(
+      Pattern.getAsTemplateDecl());
+}
+
+UnsignedOrNone PackIndexingTemplateStorage::getSelectedIndex() const {
+  if (IndexExpr->isInstantiationDependent())
+    return std::nullopt;
+  auto *CE = dyn_cast<ConstantExpr>(IndexExpr);
+  if (!CE)
+    return std::nullopt;
+  llvm::APSInt Index = CE->getResultAsAPSInt();
+  assert(Index.isNonNegative() && "Invalid index");
+  return static_cast<unsigned>(Index.getExtValue());
+}
+
+TemplateName PackIndexingTemplateStorage::getSelectedTemplate() const {
+  if (!isFullySubstituted())
+    return TemplateName();
+  UnsignedOrNone Index = getSelectedIndex();
+  ArrayRef<TemplateName> Expansions = getExpansions();
+  if (!Index || *Index >= Expansions.size())
+    return TemplateName();
+  return Expansions[*Index];
+}
+
+TemplateNameDependence PackIndexingTemplateStorage::getDependence() const {
+  TemplateNameDependence IndexD =
+      toTemplateNameDependence(IndexExpr->getDependence());
+
+  TemplateNameDependence D =
+      IndexD | (IndexExpr->isInstantiationDependent()
+                    ? TemplateNameDependence::DependentInstantiation
+                    : TemplateNameDependence::None);
+  if (ArrayRef<TemplateName> Expansions = getExpansions(); Expansions.empty())
+    D |= Pattern.getDependence() &
+         TemplateNameDependence::DependentInstantiation;
+  else
+    for (TemplateName T : Expansions)
+      D |= T.getDependence();
+
+  // C++29 [temp.names]p5:
+  //   A pack-index-template-name is a pack expansion.
+  if (!(IndexD & TemplateNameDependence::UnexpandedPack))
+    D &= ~TemplateNameDependence::UnexpandedPack;
+
+  // C++29 [temp.names]p3:
+  //   The simple-template-name P in a pack-index-template-name shall
+  //   denote a pack.
+  if (!Pattern.containsUnexpandedParameterPack())
+    D |= TemplateNameDependence::Error |
+         TemplateNameDependence::DependentInstantiation;
+
+  return D;
+}
+
+void PackIndexingTemplateStorage::Profile(llvm::FoldingSetNodeID &ID,
+                                          const ASTContext &Context) const {
+  Profile(ID, Context, Pattern, IndexExpr, isFullySubstituted(),
+          getExpansions());
+}
+
+void PackIndexingTemplateStorage::Profile(llvm::FoldingSetNodeID &ID,
+                                          const ASTContext &Context,
+                                          TemplateName Pattern, Expr *IndexExpr,
+                                          bool FullySubstituted,
+                                          ArrayRef<TemplateName> Expansions) {
+  Pattern.Profile(ID);
+  IndexExpr->Profile(ID, Context, /*Canonical=*/true);
+  ID.AddBoolean(FullySubstituted);
+  ID.AddInteger(Expansions.size());
+  for (TemplateName T : Expansions)
+    T.Profile(ID);
+}
+
 IdentifierOrOverloadedOperator::IdentifierOrOverloadedOperator(
     const IdentifierInfo *II)
     : PtrOrOp(reinterpret_cast<uintptr_t>(II)) {
@@ -165,6 +256,8 @@ TemplateName::TemplateName(DependentTemplateName *Dep) : Storage(Dep) {}
 TemplateName::TemplateName(UsingShadowDecl *Using) : Storage(Using) {}
 TemplateName::TemplateName(DeducedTemplateStorage *Deduced)
     : Storage(Deduced) {}
+TemplateName::TemplateName(PackIndexingTemplateStorage *PackIndexing)
+    : Storage(PackIndexing) {}
 
 bool TemplateName::isNull() const { return Storage.isNull(); }
 
@@ -191,6 +284,8 @@ TemplateName::NameKind TemplateName::getKind() const {
     return SubstTemplateTemplateParm;
   if (uncommon->getAsDeducedTemplateName())
     return DeducedTemplate;
+  if (uncommon->getAsPackIndexingTemplate())
+    return PackIndexingTemplate;
 
   assert(uncommon->getAsSubstTemplateTemplateParmPack() != nullptr);
   return SubstTemplateTemplateParmPack;
@@ -211,6 +306,14 @@ TemplateDecl *TemplateName::getAsTemplateDecl(bool IgnoreDeduced) const {
       dyn_cast_if_present<Decl *>(Name.Storage));
 }
 
+TemplateTemplateParmDecl *TemplateName::getAsTemplateTemplateParmDecl() const {
+  if (TemplateDecl *TD = getAsTemplateDecl())
+    return dyn_cast<TemplateTemplateParmDecl>(TD);
+  if (PackIndexingTemplateStorage *PI = getAsPackIndexingTemplate())
+    return PI->getParameterPack();
+  return nullptr;
+}
+
 std::pair<TemplateName, DefaultArguments>
 TemplateName::getTemplateDeclAndDefaultArgs() const {
   DefaultArguments DefArgs;
@@ -243,6 +346,11 @@ std::optional<TemplateName> TemplateName::desugar(bool IgnoreDeduced) const {
     return QTN->getUnderlyingTemplate();
   if (SubstTemplateTemplateParmStorage *S = getAsSubstTemplateTemplateParm())
     return S->getReplacement();
+  if (PackIndexingTemplateStorage *S = getAsPackIndexingTemplate()) {
+    if (TemplateName Selected = S->getSelectedTemplate(); !Selected.isNull())
+      return Selected;
+    return std::nullopt;
+  }
   if (IgnoreDeduced)
     if (DeducedTemplateStorage *S = getAsDeducedTemplateName())
       return S->getUnderlying();
@@ -300,7 +408,8 @@ TemplateName::getQualifierAndTemplateKeyword() const {
     if (QualifiedTemplateName *N = Cur->getAsQualifiedTemplateName())
       return {N->getQualifier(), N->hasTemplateKeyword()};
     if (Cur->getAsSubstTemplateTemplateParm() ||
-        Cur->getAsSubstTemplateTemplateParmPack())
+        Cur->getAsSubstTemplateTemplateParmPack() ||
+        Cur->getAsPackIndexingTemplate())
       break;
   }
   return {std::nullopt, false};
@@ -350,6 +459,14 @@ DeducedTemplateStorage *TemplateName::getAsDeducedTemplateName() const {
   return nullptr;
 }
 
+PackIndexingTemplateStorage *TemplateName::getAsPackIndexingTemplate() const {
+  if (UncommonTemplateNameStorage *Uncommon =
+          dyn_cast_if_present<UncommonTemplateNameStorage *>(Storage))
+    return Uncommon->getAsPackIndexingTemplate();
+
+  return nullptr;
+}
+
 TemplateNameDependence TemplateName::getDependence() const {
   switch (getKind()) {
   case NameKind::Template:
@@ -395,6 +512,8 @@ TemplateNameDependence TemplateName::getDependence() const {
       D |= toTemplateNameDependence(Arg.getDependence());
     return D;
   }
+  case NameKind::PackIndexingTemplate:
+    return getAsPackIndexingTemplate()->getDependence();
   case NameKind::AssumedTemplate:
     return TemplateNameDependence::DependentInstantiation;
   case NameKind::OverloadedTemplate:
@@ -415,6 +534,35 @@ bool TemplateName::containsUnexpandedParameterPack() const {
   return getDependence() & TemplateNameDependence::UnexpandedPack;
 }
 
+bool TemplateName::isConceptName() const {
+  auto namesConcept = [](const TemplateTemplateParmDecl *TTP) {
+    return TTP->templateParameterKind() == TNK_Concept_template;
+  };
+  switch (getKind()) {
+  case NameKind::Template:
+  case NameKind::UsingTemplate: {
+    const TemplateDecl *TD = getAsTemplateDecl();
+    if (const auto *TTP = dyn_cast<TemplateTemplateParmDecl>(TD))
+      return namesConcept(TTP);
+    return isa<ConceptDecl>(TD);
+  }
+  case NameKind::PackIndexingTemplate:
+    return getAsPackIndexingTemplate()->getPattern().isConceptName();
+  case NameKind::SubstTemplateTemplateParmPack:
+    return namesConcept(
+        getAsSubstTemplateTemplateParmPack()->getParameterPack());
+  case NameKind::QualifiedTemplate:
+  case NameKind::SubstTemplateTemplateParm:
+  case NameKind::DeducedTemplate:
+    return desugar(/*IgnoreDeduced=*/true)->isConceptName();
+  case NameKind::OverloadedTemplate:
+  case NameKind::AssumedTemplate:
+  case NameKind::DependentTemplate:
+    return false;
+  }
+  llvm_unreachable("Unknown TemplateName kind");
+}
+
 void TemplateName::print(raw_ostream &OS, const PrintingPolicy &Policy,
                          Qualified Qual) const {
   auto handleAnonymousTTP = [&](TemplateDecl *TD, raw_ostream &OS) {
@@ -479,6 +627,16 @@ void TemplateName::print(raw_ostream &OS, const PrintingPolicy &Policy,
   } else if (SubstTemplateTemplateParmStorage *subst =
                  getAsSubstTemplateTemplateParm()) {
     subst->getReplacement().print(OS, Policy, Qual);
+  } else if (PackIndexingTemplateStorage *PI = getAsPackIndexingTemplate()) {
+    if (TemplateName Selected = PI->getSelectedTemplate();
+        !Selected.isNull() && Policy.PrintAsCanonical) {
+      Selected.print(OS, Policy, Qual);
+      return;
+    }
+    PI->getPattern().print(OS, Policy, Qual);
+    OS << "...[";
+    PI->getIndexExpr()->printPretty(OS, nullptr, Policy);
+    OS << "]";
   } else if (SubstTemplateTemplateParmPackStorage *SubstPack =
                  getAsSubstTemplateTemplateParmPack())
     OS << *SubstPack->getParameterPack();
diff --git a/clang/lib/AST/TextNodeDumper.cpp b/clang/lib/AST/TextNodeDumper.cpp
index f58cc4f5761b7..89e01e9b563de 100644
--- a/clang/lib/AST/TextNodeDumper.cpp
+++ b/clang/lib/AST/TextNodeDumper.cpp
@@ -968,7 +968,7 @@ void TextNodeDumper::dumpBareConcept(TemplateName TN) {
 
   ColorScope Color(OS, ShowColors, ASTDumpColor::DeclName);
   OS << " '";
-  OS << TD->getDeclName();
+  TN.print(OS, PrintPolicy, TemplateName::Qualified::None);
   OS << '\'';
 }
 
@@ -1391,6 +1391,19 @@ void TextNodeDumper::dumpBareTemplateName(TemplateName TN) {
     });
     return;
   }
+  case TemplateName::PackIndexingTemplate: {
+    OS << " pack_indexing";
+    const PackIndexingTemplateStorage *PI = TN.getAsPackIndexingTemplate();
+    if (PI->isFullySubstituted())
+      OS << " fully_substituted";
+    if (UnsignedOrNone Index = PI->getSelectedIndex())
+      OS << " index " << *Index;
+    dumpTemplateName(PI->getPattern(), "pattern");
+    AddChild("index", [=] { Visit(PI->getIndexExpr()); });
+    for (TemplateName Expansion : PI->getExpansions())
+      dumpTemplateName(Expansion, "expansion");
+    return;
+  }
   // FIXME: Implement these.
   case TemplateName::OverloadedTemplate:
     OS << " overloaded";
@@ -1660,8 +1673,7 @@ void clang::TextNodeDumper::VisitDependentScopeDeclRefExpr(
 void clang::TextNodeDumper::VisitDependentTemplateIdExpr(
     const DependentTemplateIdExpr *Node) {
   OS << (Node->isConceptReference() ? " concept" : " variable template");
-  OS << ' ';
-  dumpBareTemplateName(Node->getTemplateName());
+  dumpTemplateName(Node->getTemplateName(), "name");
 }
 
 void TextNodeDumper::VisitUnresolvedLookupExpr(
diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp
index b92dec99613f7..f396c2a234006 100644
--- a/clang/lib/AST/Type.cpp
+++ b/clang/lib/AST/Type.cpp
@@ -5750,8 +5750,8 @@ AutoType::AutoType(DeducedKind DK, QualType DeducedAsTypeOrCanon,
   this->TypeConstraintConcept = TypeConstraintConcept;
   assert(!TypeConstraintConcept.isNull() || AutoTypeBits.NumArgs == 0);
   if (!TypeConstraintConcept.isNull()) {
-
-    assert(TypeConstraintConcept.getKind() == TemplateName::Template);
+    assert(TypeConstraintConcept.isConceptName() &&
+           "type-constraint does not name a concept");
 
     auto Dep = toTypeDependence(TypeConstraintConcept.getDependence());
 
diff --git a/clang/lib/AST/TypeLoc.cpp b/clang/lib/AST/TypeLoc.cpp
index 06406b805c402..682ce9ffacc30 100644
--- a/clang/lib/AST/TypeLoc.cpp
+++ b/clang/lib/AST/TypeLoc.cpp
@@ -768,7 +768,7 @@ static ConceptReference *createTrivialConceptReference(ASTContext &Context,
                                                        SourceLocation Loc,
                                                        const AutoType *AT) {
   DeclarationName ConceptName =
-      AT->getTypeConstraintConcept().getAsTemplateDecl()->getDeclName();
+      Context.getNameForTemplate(AT->getTypeConstraintConcept(), Loc).getName();
   DeclarationNameInfo DNI = DeclarationNameInfo(ConceptName, Loc, ConceptName);
   unsigned size = AT->getTypeConstraintArguments().size();
   llvm::SmallVector<TemplateArgumentLocInfo, 8> TALI(size);
diff --git a/clang/lib/AST/TypePrinter.cpp b/clang/lib/AST/TypePrinter.cpp
index 74644ac0da0a6..80d63434c28b1 100644
--- a/clang/lib/AST/TypePrinter.cpp
+++ b/clang/lib/AST/TypePrinter.cpp
@@ -1412,14 +1412,16 @@ void TypePrinter::printAutoBefore(const AutoType *T, raw_ostream &OS) {
     if (T->isConstrained()) {
       // FIXME: Track a TypeConstraint as type sugar, so that we can print the
       // type as it was written.
-      T->getTypeConstraintConcept().getAsTemplateDecl()->getDeclName().print(
-          OS, Policy);
+      TemplateName Concept = T->getTypeConstraintConcept();
+      Concept.print(OS, Policy, TemplateName::Qualified::None);
       auto Args = T->getTypeConstraintArguments();
-      if (!Args.empty())
+      if (!Args.empty()) {
+        const TemplateDecl *TD = Concept.getAsTemplateDecl();
+        if (!TD)
+          TD = Concept.getAsTemplateTemplateParmDecl();
         printTemplateArgumentList(OS, Args, Policy,
-                                  T->getTypeConstraintConcept()
-                                      .getAsTemplateDecl()
-                                      ->getTemplateParameters());
+                                  TD->getTemplateParameters());
+      }
       OS << ' ';
     }
     switch (T->getKeyword()) {
diff --git a/clang/lib/ExtractAPI/DeclarationFragments.cpp b/clang/lib/ExtractAPI/DeclarationFragments.cpp
index 47b4b8c864a89..150a05afb396f 100644
--- a/clang/lib/ExtractAPI/DeclarationFragments.cpp
+++ b/clang/lib/ExtractAPI/DeclarationFragments.cpp
@@ -1011,11 +1011,9 @@ DeclarationFragmentsBuilder::getFragmentsForTemplateParameters(
     if (const auto *TemplateParam =
             dyn_cast<TemplateTypeParmDecl>(ParameterArray[i])) {
       if (TemplateParam->hasTypeConstraint())
-        Fragments.append(TemplateParam->getTypeConstraint()
-                             ->getNamedConcept()
-                             .getAsTemplateDecl()
-                             ->getName()
-                             .str(),
+        Fragments.append(extractapi::getTypeConstraintSpelling(
+                             TemplateParam->getTypeConstraint(),
+                             TemplateParam->getASTContext()),
                          DeclarationFragments::FragmentKind::TypeIdentifier);
       else if (TemplateParam->wasDeclaredWithTypename())
         Fragments.append("typename",
diff --git a/clang/lib/Frontend/InitPreprocessor.cpp b/clang/lib/Frontend/InitPreprocessor.cpp
index 7cc481b595f36..f37d95cd02068 100644
--- a/clang/lib/Frontend/InitPreprocessor.cpp
+++ b/clang/lib/Frontend/InitPreprocessor.cpp
@@ -761,7 +761,7 @@ static void InitializeCPlusPlusFeatureTestMacros(const LangOptions &LangOpts,
   Builder.defineMacro("__cpp_placeholder_variables", "202306L");
 
   // C++26 features supported in earlier language modes.
-  Builder.defineMacro("__cpp_pack_indexing", "202311L");
+  Builder.defineMacro("__cpp_pack_indexing", "202606L");
   Builder.defineMacro("__cpp_deleted_function", "202403L");
   Builder.defineMacro("__cpp_variadic_friend", "202403L");
   Builder.defineMacro("__cpp_trivial_relocatability", "202502L");
diff --git a/clang/lib/Parse/ParseDeclCXX.cpp b/clang/lib/Parse/ParseDeclCXX.cpp
index 0bdea354c2406..468fa8cbadd75 100644
--- a/clang/lib/Parse/ParseDeclCXX.cpp
+++ b/clang/lib/Parse/ParseDeclCXX.cpp
@@ -1213,6 +1213,88 @@ SourceLocation Parser::ParsePackIndexingType(DeclSpec &DS) {
   return T.getCloseLocation();
 }
 
+TemplateNameKind Parser::isPackIndexingTemplateName(UnqualifiedId &Name,
+                                                    TemplateTy &Template) {
+  assert(Tok.is(tok::identifier) && NextToken().is(tok::ellipsis) &&
+         GetLookAheadToken(2).is(tok::l_square) && "expected 'identifier...['");
+
+  // C++29 [temp.names]p1:
+  //   pack-index-template-name:
+  //     simple-template-name ... [ constant-expression ]
+  Name.setIdentifier(Tok.getIdentifierInfo(), Tok.getLocation());
+  CXXScopeSpec EmptySS;
+  bool MemberOfUnknownSpecialization = false;
+  TemplateNameKind TNK = Actions.isTemplateName(
+      getCurScope(), EmptySS, /*hasTemplateKeyword=*/false, Name,
+      /*ObjectType=*/nullptr, /*EnteringContext=*/false, Template,
+      MemberOfUnknownSpecialization, /*AllowTypoCorrection=*/false);
+
+  if (TNK == TNK_Undeclared_template || !Template)
+    return TNK_Non_template;
+  return TNK;
+}
+
+bool Parser::AnnotatePackIndexingTemplateName(CXXScopeSpec &SS,
+                                              UnqualifiedId &Name,
+                                              TemplateTy Template,
+                                              TemplateNameKind TNK) {
+  assert(Tok.is(tok::identifier) && "expected a simple-template-name");
+  SourceLocation NameLoc = ConsumeToken();
+  ConsumeToken(); // the ellipsis
+
+  BalancedDelimiterTracker T(*this, tok::l_square);
+  if (T.consumeOpen())
+    return true;
+  ExprResult IndexExpr = ParseConstantExpression();
+  if (T.consumeClose() || IndexExpr.isInvalid())
+    return true;
+
+  TemplateName Indexed = Actions.ActOnPackIndexingTemplateName(
+      Template.get(), NameLoc, IndexExpr.get());
+  if (Indexed.isNull())
+    return true;
+  Template = TemplateTy::make(Indexed);
+
+  // C++29 [temp.names]p7:
+  //   A < is interpreted as the delimiter of a template-argument-list if
+  //   [...] it follows a pack-index-template-name.
+  if (Tok.is(tok::less))
+    return AnnotateTemplateIdToken(Template, TNK, SS,
+                                   /*TemplateKWLoc=*/SourceLocation(), Name,
+                                   /*AllowTypeAnnotation=*/false);
+
+  // Every token of the pack-index-template-name has been consumed,
+  // reinject the last token to produce an annotation.
+  if (PP.isBacktrackEnabled())
+    PP.RevertCachedTokens(1);
+  else
+    PP.EnterToken(Tok, /*IsReinject=*/true);
+
+  // C++29 [dcl.type.simple]p1:
+  //   A type specifier is a placeholder for a deduced class type if [...] it
+  //   is of the form typename pack-index-template-name.
+  if ((TNK == TNK_Type_template || TNK == TNK_Dependent_template_name) &&
+      getLangOpts().CPlusPlus17) {
+    TypeResult Type =
+        Actions.ActOnPackIndexingDeducedTemplateSpecializationType(Indexed,
+                                                                   NameLoc);
+    Tok.setKind(tok::annot_typename);
+    setTypeAnnotation(Tok, Type);
+  } else {
+    // A concept-name or a variable-template name.
+    Tok.setKind(tok::annot_template_id);
+    Tok.setAnnotationValue(TemplateIdAnnotation::Create(
+        /*TemplateKWLoc=*/SourceLocation(), NameLoc, Name.Identifier, OO_None,
+        Template, TNK, /*LAngleLoc=*/SourceLocation(),
+        /*RAngleLoc=*/SourceLocation(), /*TemplateArgs=*/{},
+        /*ArgsInvalid=*/false, TemplateIds));
+  }
+  Tok.setLocation(NameLoc);
+  Tok.setAnnotationEndLoc(T.getCloseLocation());
+  PP.AnnotateCachedTokens(Tok);
+  return false;
+}
+
 void Parser::AnnotateExistingIndexedTypeNamePack(ParsedType T,
                                                  SourceLocation StartLoc,
                                                  SourceLocation EndLoc) {
diff --git a/clang/lib/Parse/ParseExpr.cpp b/clang/lib/Parse/ParseExpr.cpp
index 87cd7a01451cf..524f1889e112c 100644
--- a/clang/lib/Parse/ParseExpr.cpp
+++ b/clang/lib/Parse/ParseExpr.cpp
@@ -902,8 +902,9 @@ Parser::ParseCastExpression(CastParseKind ParseKind, bool isAddressOfOperand,
         // Annotate the token and tail recurse.
         // If the token is not annotated, then it might be an expression pack
         // indexing
-        if (!TryAnnotateTypeOrScopeToken() &&
-            Tok.isOneOf(tok::annot_pack_indexing_type, tok::annot_cxxscope))
+        if (TryAnnotateTypeOrScopeToken())
+          return ExprError();
+        if (Tok.isAnnotation())
           return ParseCastExpression(ParseKind, isAddressOfOperand,
                                      CorrectionBehavior, isVectorLiteral,
                                      NotPrimaryExpression);
diff --git a/clang/lib/Parse/ParseExprCXX.cpp b/clang/lib/Parse/ParseExprCXX.cpp
index f9a0dcc7d53af..4a903155377d9 100644
--- a/clang/lib/Parse/ParseExprCXX.cpp
+++ b/clang/lib/Parse/ParseExprCXX.cpp
@@ -188,43 +188,54 @@ bool Parser::ParseOptionalCXXScopeSpecifier(
            GetLookAheadToken(1).is(tok::ellipsis) &&
            GetLookAheadToken(2).is(tok::l_square) &&
            !GetLookAheadToken(3).is(tok::r_square)) {
-    SourceLocation Start = Tok.getLocation();
-    DeclSpec DS(AttrFactory);
-    SourceLocation CCLoc;
-    SourceLocation EndLoc = ParsePackIndexingType(DS);
-    if (DS.getTypeSpecType() == DeclSpec::TST_error)
-      return false;
+    // C++29 [temp.names]p1:
+    //   pack-index-template-name:
+    //     simple-template-name ... [ constant-expression ]
+    UnqualifiedId TemplateName;
+    TemplateTy Template;
+    TemplateNameKind TNK = isPackIndexingTemplateName(TemplateName, Template);
+    if (TNK != TNK_Non_template) {
+      if (AnnotatePackIndexingTemplateName(SS, TemplateName, Template, TNK))
+        return true;
+    } else {
+      SourceLocation Start = Tok.getLocation();
+      DeclSpec DS(AttrFactory);
+      SourceLocation CCLoc;
+      SourceLocation EndLoc = ParsePackIndexingType(DS);
+      if (DS.getTypeSpecType() == DeclSpec::TST_error)
+        return false;
 
-    QualType Pattern = Sema::GetTypeFromParser(DS.getRepAsType());
-    QualType Type =
-        Actions.ActOnPackIndexingType(Pattern, DS.getPackIndexingExpr(),
-                                      DS.getBeginLoc(), DS.getEllipsisLoc());
+      QualType Pattern = Sema::GetTypeFromParser(DS.getRepAsType());
+      QualType Type =
+          Actions.ActOnPackIndexingType(Pattern, DS.getPackIndexingExpr(),
+                                        DS.getBeginLoc(), DS.getEllipsisLoc());
 
-    if (Type.isNull())
-      return false;
+      if (Type.isNull())
+        return false;
 
-    // C++ [cpp23.dcl.dcl-2]:
-    //   Previously, T...[n] would declare a pack of function parameters.
-    //   T...[n] is now a pack-index-specifier. [...] Valid C++ 2023 code that
-    //   declares a pack of parameters without specifying a declarator-id
-    //   becomes ill-formed.
-    //
-    // However, we still treat it as a pack indexing type because the use case
-    // is fairly rare, to ensure semantic consistency given that we have
-    // backported this feature to pre-C++26 modes.
-    if (!Tok.is(tok::coloncolon) && !getLangOpts().CPlusPlus26 &&
-        getCurScope()->isFunctionDeclarationScope())
-      Diag(Start, diag::warn_pre_cxx26_ambiguous_pack_indexing_type) << Type;
-
-    if (!TryConsumeToken(tok::coloncolon, CCLoc)) {
-      AnnotateExistingIndexedTypeNamePack(ParsedType::make(Type), Start,
-                                          EndLoc);
-      return false;
+      // C++ [cpp23.dcl.dcl-2]:
+      //   Previously, T...[n] would declare a pack of function parameters.
+      //   T...[n] is now a pack-index-specifier. [...] Valid C++ 2023 code
+      //   that declares a pack of parameters without specifying a
+      //   declarator-id becomes ill-formed.
+      //
+      // However, we still treat it as a pack indexing type because the use
+      // case is fairly rare, to ensure semantic consistency given that we have
+      // backported this feature to pre-C++26 modes.
+      if (!Tok.is(tok::coloncolon) && !getLangOpts().CPlusPlus26 &&
+          getCurScope()->isFunctionDeclarationScope())
+        Diag(Start, diag::warn_pre_cxx26_ambiguous_pack_indexing_type) << Type;
+
+      if (!TryConsumeToken(tok::coloncolon, CCLoc)) {
+        AnnotateExistingIndexedTypeNamePack(ParsedType::make(Type), Start,
+                                            EndLoc);
+        return false;
+      }
+      if (Actions.ActOnCXXNestedNameSpecifierIndexedPack(SS, DS, CCLoc,
+                                                         std::move(Type)))
+        SS.SetInvalid(SourceRange(Start, CCLoc));
+      HasScopeSpecifier = true;
     }
-    if (Actions.ActOnCXXNestedNameSpecifierIndexedPack(SS, DS, CCLoc,
-                                                       std::move(Type)))
-      SS.SetInvalid(SourceRange(Start, CCLoc));
-    HasScopeSpecifier = true;
   }
 
   // Preferred type might change when parsing qualifiers, we need the original.
diff --git a/clang/lib/Parse/ParseTemplate.cpp b/clang/lib/Parse/ParseTemplate.cpp
index 735a9bd1f9f1c..7a3629ddc19a9 100644
--- a/clang/lib/Parse/ParseTemplate.cpp
+++ b/clang/lib/Parse/ParseTemplate.cpp
@@ -1279,6 +1279,7 @@ ParsedTemplateArgument Parser::ParseTemplateTemplateArgument() {
     // We may have a (non-dependent) template name.
     TemplateTy Template;
     UnqualifiedId Name;
+    bool IsPackIndexingTemplateName = false;
     if (Tok.is(tok::annot_non_type)) {
       NamedDecl *ND = getNonTypeAnnotation(Tok);
       if (!isa<VarTemplateDecl>(ND))
@@ -1289,6 +1290,11 @@ ParsedTemplateArgument Parser::ParseTemplateTemplateArgument() {
       TemplateIdAnnotation *TemplateId = takeTemplateIdAnnotation(Tok);
       if (TemplateId->LAngleLoc.isValid())
         return Result;
+      if (TemplateId->Template &&
+          TemplateId->Template.get().getAsPackIndexingTemplate()) {
+        Template = TemplateId->Template;
+        IsPackIndexingTemplateName = true;
+      }
       Name.setIdentifier(TemplateId->Name, Tok.getLocation());
       ConsumeAnnotationToken();
     } else {
@@ -1299,18 +1305,23 @@ ParsedTemplateArgument Parser::ParseTemplateTemplateArgument() {
     TryConsumeToken(tok::ellipsis, EllipsisLoc);
 
     if (isEndOfTemplateArgument(Tok)) {
-      bool MemberOfUnknownSpecialization;
-      TemplateNameKind TNK = Actions.isTemplateName(
-          getCurScope(), SS,
-          /*hasTemplateKeyword=*/false, Name,
-          /*ObjectType=*/nullptr,
-          /*EnteringContext=*/false, Template, MemberOfUnknownSpecialization);
-      if (TNK == TNK_Dependent_template_name || TNK == TNK_Type_template ||
-          TNK == TNK_Var_template || TNK == TNK_Concept_template) {
-        // We have an id-expression that refers to a class template or
-        // (C++0x) alias template.
+      if (IsPackIndexingTemplateName) {
         Result = ParsedTemplateArgument(/*TemplateKwLoc=*/SourceLocation(), SS,
                                         Template, Name.StartLocation);
+      } else {
+        bool MemberOfUnknownSpecialization;
+        TemplateNameKind TNK = Actions.isTemplateName(
+            getCurScope(), SS,
+            /*hasTemplateKeyword=*/false, Name,
+            /*ObjectType=*/nullptr,
+            /*EnteringContext=*/false, Template, MemberOfUnknownSpecialization);
+        if (TNK == TNK_Dependent_template_name || TNK == TNK_Type_template ||
+            TNK == TNK_Var_template || TNK == TNK_Concept_template) {
+          // We have an id-expression that refers to a class template or
+          // (C++0x) alias template.
+          Result = ParsedTemplateArgument(/*TemplateKwLoc=*/SourceLocation(),
+                                          SS, Template, Name.StartLocation);
+        }
       }
     }
   }
diff --git a/clang/lib/Sema/SemaCodeComplete.cpp b/clang/lib/Sema/SemaCodeComplete.cpp
index 4b9b604d27b16..d3cb9b34a7609 100644
--- a/clang/lib/Sema/SemaCodeComplete.cpp
+++ b/clang/lib/Sema/SemaCodeComplete.cpp
@@ -5951,7 +5951,8 @@ class ConceptInfo {
   static QualType deduceType(const TypeConstraint &T) {
     // Assume a same_as<T> return type constraint is std::same_as or equivalent.
     // In this case the return type is T.
-    DeclarationName DN = T.getNamedConcept().getAsTemplateDecl()->getDeclName();
+    DeclarationName DN =
+        T.getConceptReference()->getConceptNameInfo().getName();
     if (DN.isIdentifier() && DN.getAsIdentifierInfo()->isStr("same_as"))
       if (const auto *Args = T.getTemplateArgsAsWritten())
         if (Args->getNumTemplateArgs() == 1) {
diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index 2b524a956ecc4..34f6ccdbc2fe6 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -2865,6 +2865,12 @@ ExprResult Sema::ActOnIdExpression(Scope *S, CXXScopeSpec &SS,
   IdentifierInfo *II = Name.getAsIdentifierInfo();
   SourceLocation NameLoc = NameInfo.getLoc();
 
+  if (Id.getKind() == UnqualifiedIdKind::IK_TemplateId &&
+      Id.TemplateId->Template)
+    if (TemplateName TN = Id.TemplateId->Template.get();
+        TN.getAsPackIndexingTemplate())
+      return CheckVarOrConceptTemplateTemplateId(NameInfo, TN, TemplateArgs);
+
   if (II && II->isEditorPlaceholder()) {
     // FIXME: When typed placeholders are supported we can create a typed
     // placeholder expression node.
diff --git a/clang/lib/Sema/SemaOverload.cpp b/clang/lib/Sema/SemaOverload.cpp
index 81bc6c68f377d..24b41a68db0fd 100644
--- a/clang/lib/Sema/SemaOverload.cpp
+++ b/clang/lib/Sema/SemaOverload.cpp
@@ -6496,7 +6496,8 @@ static ExprResult BuildConvertedConstantExpression(Sema &S, Expr *From,
                                                    NamedDecl *Dest,
                                                    APValue &PreNarrowingValue) {
   [[maybe_unused]] bool isCCEAllowedPreCXX11 =
-      (CCE == CCEKind::TempArgStrict || CCE == CCEKind::ExplicitBool);
+      (CCE == CCEKind::TempArgStrict || CCE == CCEKind::ExplicitBool ||
+       CCE == CCEKind::PackIndex);
   assert((S.getLangOpts().CPlusPlus11 || isCCEAllowedPreCXX11) &&
          "converted constant expression outside C++11 or TTP matching");
 
diff --git a/clang/lib/Sema/SemaTemplate.cpp b/clang/lib/Sema/SemaTemplate.cpp
index 64a05a8bc34e3..56d0c5a4d2fe9 100644
--- a/clang/lib/Sema/SemaTemplate.cpp
+++ b/clang/lib/Sema/SemaTemplate.cpp
@@ -1127,7 +1127,7 @@ bool Sema::CheckTypeConstraint(TemplateIdAnnotation *TypeConstr) {
   NamedDecl *CD = nullptr;
   bool IsTypeConcept = false;
   bool RequiresArguments = false;
-  if (auto *TTP = dyn_cast<TemplateTemplateParmDecl>(TN.getAsTemplateDecl())) {
+  if (auto *TTP = TN.getAsTemplateTemplateParmDecl()) {
     IsTypeConcept = TTP->isTypeConceptTemplateParam();
     RequiresArguments =
         TTP->getTemplateParameters()->getMinRequiredArguments() > 1;
@@ -1181,8 +1181,8 @@ bool Sema::BuildTypeConstraint(const CXXScopeSpec &SS,
     return true;
 
   TemplateName TN = TypeConstr->Template.get();
-  TemplateDecl *CD = cast<TemplateDecl>(TN.getAsTemplateDecl());
   UsingShadowDecl *USD = TN.getAsUsingShadowDecl();
+  TemplateDecl *CD = TN.getAsTemplateDecl();
 
   DeclarationNameInfo ConceptName(DeclarationName(TypeConstr->Name),
                                   TypeConstr->TemplateNameLoc);
@@ -1201,7 +1201,8 @@ bool Sema::BuildTypeConstraint(const CXXScopeSpec &SS,
   }
   return AttachTypeConstraint(
       SS.isSet() ? SS.getWithLocInContext(Context) : NestedNameSpecifierLoc(),
-      ConceptName, CD, /*FoundDecl=*/USD ? cast<NamedDecl>(USD) : CD,
+      ConceptName, TN,
+      /*FoundDecl=*/USD ? cast<NamedDecl>(USD) : cast_if_present<NamedDecl>(CD),
       TypeConstr->LAngleLoc.isValid() ? &TemplateArgs : nullptr,
       ConstrainedParameter, EllipsisLoc);
 }
@@ -1209,7 +1210,7 @@ bool Sema::BuildTypeConstraint(const CXXScopeSpec &SS,
 template <typename ArgumentLocAppender>
 static ExprResult formImmediatelyDeclaredConstraint(
     Sema &S, NestedNameSpecifierLoc NS, DeclarationNameInfo NameInfo,
-    NamedDecl *NamedConcept, NamedDecl *FoundDecl, SourceLocation LAngleLoc,
+    TemplateName NamedConcept, NamedDecl *FoundDecl, SourceLocation LAngleLoc,
     SourceLocation RAngleLoc, QualType ConstrainedType,
     SourceLocation ParamNameLoc, ArgumentLocAppender Appender,
     SourceLocation EllipsisLoc) {
@@ -1229,7 +1230,8 @@ static ExprResult formImmediatelyDeclaredConstraint(
   CXXScopeSpec SS;
   SS.Adopt(NS);
   ExprResult ImmediatelyDeclaredConstraint;
-  if (auto *CD = dyn_cast<ConceptDecl>(NamedConcept)) {
+  if (auto *CD =
+          dyn_cast_if_present<ConceptDecl>(NamedConcept.getAsTemplateDecl())) {
     ImmediatelyDeclaredConstraint = S.CheckConceptTemplateId(
         SS, /*TemplateKWLoc=*/SourceLocation(), NameInfo,
         /*FoundDecl=*/FoundDecl ? FoundDecl : CD, CD, &ConstraintArgs,
@@ -1239,9 +1241,8 @@ static ExprResult formImmediatelyDeclaredConstraint(
   // We have a template template parameter
   else {
     assert(SS.isEmpty() && "template parameter with a scope specifier?");
-    auto *CDT = dyn_cast<TemplateTemplateParmDecl>(NamedConcept);
-    ImmediatelyDeclaredConstraint =
-        S.CheckVarOrConceptTemplateTemplateId(NameInfo, CDT, &ConstraintArgs);
+    ImmediatelyDeclaredConstraint = S.CheckVarOrConceptTemplateTemplateId(
+        NameInfo, NamedConcept, &ConstraintArgs);
   }
   if (ImmediatelyDeclaredConstraint.isInvalid() || !EllipsisLoc.isValid())
     return ImmediatelyDeclaredConstraint;
@@ -1268,8 +1269,7 @@ static ExprResult formImmediatelyDeclaredConstraint(
 
 bool Sema::AttachTypeConstraint(NestedNameSpecifierLoc NS,
                                 DeclarationNameInfo NameInfo,
-                                TemplateDecl *NamedConcept,
-                                NamedDecl *FoundDecl,
+                                TemplateName NamedConcept, NamedDecl *FoundDecl,
                                 const TemplateArgumentListInfo *TemplateArgs,
                                 TemplateTypeParmDecl *ConstrainedParameter,
                                 SourceLocation EllipsisLoc) {
@@ -1296,13 +1296,12 @@ bool Sema::AttachTypeConstraint(NestedNameSpecifierLoc NS,
   if (ImmediatelyDeclaredConstraint.isInvalid())
     return true;
 
-  auto *CL =
-      ConceptReference::Create(Context, /*NNS=*/NS,
-                               /*TemplateKWLoc=*/SourceLocation{},
-                               /*ConceptNameInfo=*/NameInfo,
-                               /*FoundDecl=*/FoundDecl,
-                               /*NamedConcept=*/TemplateName(NamedConcept),
-                               /*ArgsWritten=*/ArgsAsWritten);
+  auto *CL = ConceptReference::Create(Context, /*NNS=*/NS,
+                                      /*TemplateKWLoc=*/SourceLocation{},
+                                      /*ConceptNameInfo=*/NameInfo,
+                                      /*FoundDecl=*/FoundDecl,
+                                      /*NamedConcept=*/NamedConcept,
+                                      /*ArgsWritten=*/ArgsAsWritten);
   ConstrainedParameter->setTypeConstraint(
       CL, ImmediatelyDeclaredConstraint.get(), std::nullopt);
   return false;
@@ -1331,7 +1330,7 @@ bool Sema::AttachTypeConstraint(AutoTypeLoc TL,
     return true;
   ExprResult ImmediatelyDeclaredConstraint = formImmediatelyDeclaredConstraint(
       *this, TL.getNestedNameSpecifierLoc(), TL.getConceptNameInfo(),
-      TL.getNamedConcept().getAsTemplateDecl(),
+      TL.getNamedConcept(),
       /*FoundDecl=*/TL.getFoundDecl(), TL.getLAngleLoc(), TL.getRAngleLoc(),
       BuildDecltypeType(Ref), OrigConstrainedParm->getLocation(),
       [&](TemplateArgumentListInfo &ConstraintArgs) {
@@ -3772,6 +3771,10 @@ QualType Sema::CheckTemplateIdType(ElaboratedTypeKeyword Keyword,
   if (!Template) {
     if (const auto *S = UnderlyingName.getAsSubstTemplateTemplateParmPack()) {
       Template = S->getParameterPack();
+    } else if (const auto *PI = UnderlyingName.getAsPackIndexingTemplate()) {
+      Template = PI->getParameterPack();
+      if (!Template)
+        Template = PI->getPattern().getAsTemplateDecl();
     } else if (const auto *DTN = UnderlyingName.getAsDependentTemplateName()) {
       if (DTN->getName().getIdentifier())
         // When building a template-id where the template-name is dependent,
@@ -4833,19 +4836,22 @@ ExprResult Sema::CheckVarTemplateId(
 }
 
 ExprResult Sema::CheckVarOrConceptTemplateTemplateId(
-    const DeclarationNameInfo &NameInfo, TemplateTemplateParmDecl *Template,
+    const DeclarationNameInfo &NameInfo, TemplateName Template,
     const TemplateArgumentListInfo *TemplateArgs) {
-  assert(Template && "A variable template id without template?");
+  TemplateTemplateParmDecl *Parameter =
+      Template.getAsTemplateTemplateParmDecl();
+  assert(Parameter && "A variable template id without template?");
 
-  if (Template->templateParameterKind() != TemplateNameKind::TNK_Var_template &&
-      Template->templateParameterKind() !=
+  if (Parameter->templateParameterKind() !=
+          TemplateNameKind::TNK_Var_template &&
+      Parameter->templateParameterKind() !=
           TemplateNameKind::TNK_Concept_template)
     return ExprResult();
 
   // Check that the template argument list is well-formed for this template.
   CheckTemplateArgumentInfo CTAI;
   if (CheckTemplateArgumentList(
-          Template, /*Template kw loc=*/{},
+          Parameter, /*Template kw loc=*/{},
           // FIXME: TemplateArgs will not be modified because
           // UpdateArgsWithConversions is false, however, we should
           // CheckTemplateArgumentList to be const-correct.
@@ -4854,8 +4860,8 @@ ExprResult Sema::CheckVarOrConceptTemplateTemplateId(
           /*UpdateArgsWithConversions=*/false))
     return true;
 
-  return DependentTemplateIdExpr::Create(getASTContext(), NameInfo,
-                                         TemplateName(Template), *TemplateArgs);
+  return DependentTemplateIdExpr::Create(getASTContext(), NameInfo, Template,
+                                         *TemplateArgs);
 }
 
 void Sema::diagnoseMissingTemplateArguments(TemplateName Name,
@@ -5000,8 +5006,8 @@ ExprResult Sema::BuildTemplateIdExpr(const CXXScopeSpec &SS,
     assert(TemplateKWLoc.isInvalid() &&
            "template keyword in front of a template parameter?");
     return CheckVarOrConceptTemplateTemplateId(
-        R.getLookupNameInfo(), R.getAsSingle<TemplateTemplateParmDecl>(),
-        TemplateArgs);
+        R.getLookupNameInfo(),
+        TemplateName(R.getAsSingle<TemplateTemplateParmDecl>()), TemplateArgs);
   }
 
   // Function templates
@@ -9316,7 +9322,7 @@ void Sema::CheckConceptRedefinition(ConceptDecl *NewDecl,
 }
 
 bool Sema::CheckConceptUseInDefinition(NamedDecl *Concept, SourceLocation Loc) {
-  if (auto *CE = llvm::dyn_cast<ConceptDecl>(Concept);
+  if (auto *CE = llvm::dyn_cast_if_present<ConceptDecl>(Concept);
       CE && !CE->isInvalidDecl() && !CE->hasDefinition()) {
     Diag(Loc, diag::err_recursive_concept) << CE;
     Diag(CE->getLocation(), diag::note_declared_at);
diff --git a/clang/lib/Sema/SemaTemplateDeduction.cpp b/clang/lib/Sema/SemaTemplateDeduction.cpp
index f177f0aa8645e..fd444e70b5192 100644
--- a/clang/lib/Sema/SemaTemplateDeduction.cpp
+++ b/clang/lib/Sema/SemaTemplateDeduction.cpp
@@ -247,8 +247,10 @@ getDeducedNTTParameterFromExpr(const Expr *E, unsigned Depth) {
       if (NTTP->getDepth() == Depth)
         return NTTP;
 
+  // A pack-index-template-name is not deducible.
   if (const auto *DTI = dyn_cast<DependentTemplateIdExpr>(E))
-    if (DTI->getParameter()->getDepth() == Depth)
+    if (!DTI->getTemplateName().getAsPackIndexingTemplate() &&
+        DTI->getParameter()->getDepth() == Depth)
       return DTI->getParameter();
 
   return nullptr;
diff --git a/clang/lib/Sema/SemaTemplateInstantiate.cpp b/clang/lib/Sema/SemaTemplateInstantiate.cpp
index 21781f7474e56..ffbe8bb0506bc 100644
--- a/clang/lib/Sema/SemaTemplateInstantiate.cpp
+++ b/clang/lib/Sema/SemaTemplateInstantiate.cpp
@@ -3158,7 +3158,7 @@ bool Sema::SubstTypeConstraint(
   }
   return AttachTypeConstraint(
       TC->getNestedNameSpecifierLoc(), TC->getConceptNameInfo(),
-      TC->getNamedConcept().getAsTemplateDecl(),
+      TC->getNamedConcept(),
       /*FoundDecl=*/TC->getConceptReference()->getFoundDecl(), &InstArgs, Inst,
       Inst->isParameterPack()
           ? cast<CXXFoldExpr>(TC->getImmediatelyDeclaredConstraint())
@@ -4649,14 +4649,22 @@ ExprResult Sema::SubstConceptTemplateArguments(
       TemplateTemplateParmDecl *TTP = E->getParameter();
       unsigned Depth = TTP->getDepth();
       unsigned Pos = TTP->getPosition();
-      ConceptDecl *ResolvedConcept = nullptr;
+      if (!MLTAL.hasTemplateArgument(Depth, Pos))
+        return E;
 
-      if (MLTAL.hasTemplateArgument(Depth, Pos)) {
-        TemplateArgument Arg = MLTAL(Depth, Pos);
-        assert(Arg.getKind() == TemplateArgument::Template);
-        ResolvedConcept =
-            dyn_cast<ConceptDecl>(Arg.getAsTemplate().getAsTemplateDecl());
+      TemplateArgument Arg = MLTAL(Depth, Pos);
+      if (PackIndexingTemplateStorage *PI =
+              E->getTemplateName().getAsPackIndexingTemplate()) {
+        UnsignedOrNone Index = PI->getSelectedIndex();
+        if (Arg.getKind() != TemplateArgument::Pack || !Index ||
+            *Index >= Arg.pack_size())
+          return E;
+        Arg = Arg.getPackAsArray()[*Index];
       }
+      if (Arg.getKind() != TemplateArgument::Template)
+        return E;
+      ConceptDecl *ResolvedConcept = dyn_cast_if_present<ConceptDecl>(
+          Arg.getAsTemplate().getAsTemplateDecl());
       if (!ResolvedConcept)
         return E;
 
diff --git a/clang/lib/Sema/SemaTemplateVariadic.cpp b/clang/lib/Sema/SemaTemplateVariadic.cpp
index a53500f2da497..83a81f2df69cf 100644
--- a/clang/lib/Sema/SemaTemplateVariadic.cpp
+++ b/clang/lib/Sema/SemaTemplateVariadic.cpp
@@ -145,6 +145,11 @@ class CollectUnexpandedParameterPacksVisitor
     /// Record occurrences of template template parameter packs.
     bool TraverseTemplateName(TemplateName Template,
                               bool TraverseQualifier = true) override {
+
+      if (PackIndexingTemplateStorage *PI =
+              Template.getAsPackIndexingTemplate())
+        return DynamicRecursiveASTVisitor::TraverseStmt(PI->getIndexExpr());
+
       if (auto *TTP = dyn_cast_or_null<TemplateTemplateParmDecl>(
               Template.getAsTemplateDecl())) {
         if (TTP->isParameterPack())
@@ -683,6 +688,13 @@ void Sema::collectUnexpandedParameterPacks(QualType T,
   CollectUnexpandedParameterPacksVisitor(Unexpanded).TraverseType(T);
 }
 
+void Sema::collectUnexpandedParameterPacks(
+    TemplateName Template,
+    SmallVectorImpl<UnexpandedParameterPack> &Unexpanded) {
+  CollectUnexpandedParameterPacksVisitor(Unexpanded)
+      .TraverseTemplateName(Template);
+}
+
 void Sema::collectUnexpandedParameterPacks(TypeLoc TL,
                    SmallVectorImpl<UnexpandedParameterPack> &Unexpanded) {
   CollectUnexpandedParameterPacksVisitor(Unexpanded).TraverseTypeLoc(TL);
@@ -1386,6 +1398,62 @@ ExprResult Sema::BuildPackIndexingExpr(Expr *PackExpression,
                                   ExpandedExprs, FullySubstituted);
 }
 
+TemplateName Sema::ActOnPackIndexingTemplateName(TemplateName Pattern,
+                                                 SourceLocation NameLoc,
+                                                 Expr *IndexExpr) {
+  if (Pattern.isNull() || !IndexExpr)
+    return TemplateName();
+
+  // C++29 [temp.names]p3:
+  //   The simple-template-name P in a pack-index-template-name shall denote a
+  //   pack.
+  bool DenotesPack = Pattern.containsUnexpandedParameterPack();
+  if (!DenotesPack)
+    Diag(NameLoc, diag::err_expected_name_of_pack) << Pattern;
+
+  TemplateName Name = BuildPackIndexingTemplateName(Pattern, IndexExpr);
+  if (!Name.isNull() && DenotesPack)
+    DiagCompat(NameLoc, diag_compat::pack_indexing_template);
+  return Name;
+}
+
+TemplateName
+Sema::BuildPackIndexingTemplateName(TemplateName Pattern, Expr *IndexExpr,
+                                    bool FullySubstituted,
+                                    ArrayRef<TemplateName> Expansions) {
+  if (!IndexExpr->isInstantiationDependent()) {
+    llvm::APSInt Value(Context.getIntWidth(Context.getSizeType()));
+    ExprResult Res = CheckConvertedConstantExpression(
+        IndexExpr, Context.getSizeType(), Value, CCEKind::PackIndex);
+    if (!Res.isUsable() || !Value.isRepresentableByInt64())
+      return TemplateName();
+
+    IndexExpr = Res.get();
+    uint64_t V = Value.getZExtValue();
+    if (FullySubstituted && V >= Expansions.size()) {
+      Diag(IndexExpr->getBeginLoc(), diag::err_pack_index_out_of_bound)
+          << V << Pattern << Expansions.size();
+      return TemplateName();
+    }
+  }
+
+  return Context.getPackIndexingTemplateName(Pattern, IndexExpr,
+                                             FullySubstituted, Expansions);
+}
+
+TypeResult Sema::ActOnPackIndexingDeducedTemplateSpecializationType(
+    TemplateName Name, SourceLocation NameLoc) {
+
+  QualType T = Context.getDeducedTemplateSpecializationType(
+      DeducedKind::Undeduced, QualType(), ElaboratedTypeKeyword::None, Name);
+  TypeLocBuilder TLB;
+  auto TL = TLB.push<DeducedTemplateSpecializationTypeLoc>(T);
+  TL.setElaboratedKeywordLoc(SourceLocation());
+  TL.setQualifierLoc(NestedNameSpecifierLoc());
+  TL.setNameLoc(NameLoc);
+  return CreateParsedType(T, TLB.getTypeSourceInfo(Context, T));
+}
+
 TemplateArgumentLoc Sema::getTemplateArgumentPackExpansionPattern(
     TemplateArgumentLoc OrigLoc, SourceLocation &Ellipsis,
     UnsignedOrNone &NumExpansions) const {
diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp
index 27197f36bcc5a..ad9204b12524b 100644
--- a/clang/lib/Sema/SemaType.cpp
+++ b/clang/lib/Sema/SemaType.cpp
@@ -1325,12 +1325,11 @@ static QualType ConvertDeclSpecToType(TypeProcessingState &state) {
                       ? AutoTypeKeyword::DecltypeAuto
                       : AutoTypeKeyword::Auto;
 
-    TemplateDecl *TypeConstraintConcept = nullptr;
+    TemplateName TypeConstraintConcept;
     llvm::SmallVector<TemplateArgument, 8> TemplateArgs;
     if (DS.isConstrainedAuto()) {
       if (TemplateIdAnnotation *TemplateId = DS.getRepAsTemplateId()) {
-        TypeConstraintConcept =
-            cast<TemplateDecl>(TemplateId->Template.get().getAsTemplateDecl());
+        TypeConstraintConcept = TemplateId->Template.get();
         TemplateArgumentListInfo TemplateArgsInfo;
         TemplateArgsInfo.setLAngleLoc(TemplateId->LAngleLoc);
         TemplateArgsInfo.setRAngleLoc(TemplateId->RAngleLoc);
@@ -1344,8 +1343,7 @@ static QualType ConvertDeclSpecToType(TypeProcessingState &state) {
       }
     }
     Result = S.Context.getAutoType(DeducedKind::Undeduced, QualType(), AutoKW,
-                                   TemplateName(TypeConstraintConcept),
-                                   TemplateArgs);
+                                   TypeConstraintConcept, TemplateArgs);
     break;
   }
 
@@ -3116,7 +3114,7 @@ InventTemplateParameter(TypeProcessingState &state, QualType T,
       if (!Invalid) {
         S.AttachTypeConstraint(
             AutoLoc.getNestedNameSpecifierLoc(), AutoLoc.getConceptNameInfo(),
-            AutoLoc.getNamedConcept().getAsTemplateDecl(),
+            AutoLoc.getNamedConcept(),
             /*FoundDecl=*/AutoLoc.getFoundDecl(),
             AutoLoc.hasExplicitTemplateArgs() ? &TAL : nullptr,
             InventedTemplateParam, D.getEllipsisLoc());
@@ -3144,15 +3142,16 @@ InventTemplateParameter(TypeProcessingState &state, QualType T,
         }
       }
       if (!Invalid) {
-        UsingShadowDecl *USD =
-            TemplateId->Template.get().getAsUsingShadowDecl();
-        TemplateDecl *CD = TemplateId->Template.get().getAsTemplateDecl();
+        TemplateName TN = TemplateId->Template.get();
+        UsingShadowDecl *USD = TN.getAsUsingShadowDecl();
+        TemplateDecl *CD = TN.getAsTemplateDecl();
         S.AttachTypeConstraint(
             D.getDeclSpec().getTypeSpecScope().getWithLocInContext(S.Context),
             DeclarationNameInfo(DeclarationName(TemplateId->Name),
                                 TemplateId->TemplateNameLoc),
-            CD,
-            /*FoundDecl=*/USD ? cast<NamedDecl>(USD) : CD,
+            TN,
+            /*FoundDecl=*/
+            USD ? cast<NamedDecl>(USD) : cast_if_present<NamedDecl>(CD),
             TemplateId->LAngleLoc.isValid() ? &TemplateArgsInfo : nullptr,
             InventedTemplateParam, D.getEllipsisLoc());
       }
@@ -4358,7 +4357,7 @@ static TypeSourceInfo *GetFullTypeForDeclarator(TypeProcessingState &state,
   // and at most one function declarator if this is a function declaration.
   // If T is a deduced class template specialization type, only parentheses
   // are allowed.
-  if (auto *DT = T->getAs<DeducedType>()) {
+  if (auto *DT = T->getAs<DeducedType>(); DT && !T->containsErrors()) {
     const AutoType *AT = T->getAs<AutoType>();
     bool IsClassTemplateDeduction = isa<DeducedTemplateSpecializationType>(DT);
     if ((AT && AT->isDecltypeAuto()) || IsClassTemplateDeduction) {
@@ -6140,12 +6139,9 @@ namespace {
                                            TemplateId->NumArgs);
         SemaRef.translateTemplateArguments(TemplateArgsPtr, TemplateArgsInfo);
       }
-      DeclarationNameInfo DNI =
-          DeclarationNameInfo(TL.getTypePtr()
-                                  ->getTypeConstraintConcept()
-                                  .getAsTemplateDecl()
-                                  ->getDeclName(),
-                              TemplateId->TemplateNameLoc);
+      DeclarationNameInfo DNI = Context.getNameForTemplate(
+          TL.getTypePtr()->getTypeConstraintConcept(),
+          TemplateId->TemplateNameLoc);
 
       NamedDecl *FoundDecl;
       if (auto TN = TemplateId->Template.get();
diff --git a/clang/lib/Sema/TreeTransform.h b/clang/lib/Sema/TreeTransform.h
index b0c836325a971..755f20ffe85f0 100644
--- a/clang/lib/Sema/TreeTransform.h
+++ b/clang/lib/Sema/TreeTransform.h
@@ -639,6 +639,9 @@ class TreeTransform {
                                      NamedDecl *FirstQualifierInScope = nullptr,
                                      bool AllowInjectedClassName = false);
 
+  TemplateName TransformConceptTemplateName(TemplateName Name,
+                                            SourceLocation NameLoc);
+
   /// Transform the given template argument.
   ///
   /// By default, this operation transforms the type, expression, or
@@ -1143,11 +1146,10 @@ class TreeTransform {
   /// By default, builds a new AutoType with the given deduced type.
   QualType RebuildAutoType(DeducedKind DK, QualType DeducedAsType,
                            AutoTypeKeyword Keyword,
-                           ConceptDecl *TypeConstraintConcept,
+                           TemplateName TypeConstraintConcept,
                            ArrayRef<TemplateArgument> TypeConstraintArgs) {
-    return SemaRef.Context.getAutoType(DK, DeducedAsType, Keyword,
-                                       TemplateName(TypeConstraintConcept),
-                                       TypeConstraintArgs);
+    return SemaRef.Context.getAutoType(
+        DK, DeducedAsType, Keyword, TypeConstraintConcept, TypeConstraintArgs);
   }
 
   /// By default, builds a new DeducedTemplateSpecializationType with the given
@@ -1355,6 +1357,18 @@ class TreeTransform {
         ArgPack, AssociatedDecl, Index, Final);
   }
 
+  /// Build a new pack-index-template-name ([temp.names]).
+  ///
+  /// By default, performs semantic analysis to build the new template name.
+  /// Subclasses may override this routine to provide different behavior.
+  TemplateName
+  RebuildPackIndexingTemplateName(TemplateName Pattern, Expr *IndexExpr,
+                                  bool FullySubstituted,
+                                  ArrayRef<TemplateName> Expansions = {}) {
+    return getSema().BuildPackIndexingTemplateName(
+        Pattern, IndexExpr, FullySubstituted, Expansions);
+  }
+
   /// Build a new compound statement.
   ///
   /// By default, performs semantic analysis to build the new statement.
@@ -4973,6 +4987,101 @@ TemplateName TreeTransform<Derived>::TransformTemplateName(
         S->getFinal());
   }
 
+  if (PackIndexingTemplateStorage *PI = Name.getAsPackIndexingTemplate()) {
+    assert(!QualifierLoc && "Unexpected qualified pack-index-template-name");
+
+    ExprResult IndexExpr;
+    {
+      EnterExpressionEvaluationContext ConstantContext(
+          SemaRef, Sema::ExpressionEvaluationContext::ConstantEvaluated);
+      IndexExpr = getDerived().TransformExpr(PI->getIndexExpr());
+      if (IndexExpr.isInvalid())
+        return TemplateName();
+    }
+
+    auto TransformOne = [&](TemplateName N) {
+      NestedNameSpecifierLoc NoQualifier;
+      return getDerived().TransformTemplateName(
+          NoQualifier, TemplateKWLoc, N, NameLoc, ObjectType,
+          FirstQualifierInScope, AllowInjectedClassName);
+    };
+
+    TemplateName Pattern = PI->getPattern();
+    SmallVector<TemplateName, 4> SubstitutedNames;
+    ArrayRef<TemplateName> Names = PI->getExpansions();
+
+    bool NotYetExpanded = Names.empty();
+    bool FullySubstituted = true;
+
+    if (Names.empty() && !PI->expandsToEmptyPack())
+      Names = ArrayRef(&Pattern, 1);
+
+    for (TemplateName N : Names) {
+      if (!N.containsUnexpandedParameterPack()) {
+        TemplateName Transformed = TransformOne(N);
+        if (Transformed.isNull())
+          return TemplateName();
+        SubstitutedNames.push_back(Transformed);
+        continue;
+      }
+
+      SmallVector<UnexpandedParameterPack, 2> Unexpanded;
+      getSema().collectUnexpandedParameterPacks(N, Unexpanded);
+      assert(!Unexpanded.empty() && "Pack expansion without parameter packs?");
+
+      bool ShouldExpand = true;
+      bool RetainExpansion = false;
+      UnsignedOrNone NumExpansions = std::nullopt;
+      if (getDerived().TryExpandParameterPacks(
+              NameLoc, SourceRange(), Unexpanded,
+              /*FailOnPackProducingTemplates=*/true, ShouldExpand,
+              RetainExpansion, NumExpansions))
+        return TemplateName();
+
+      if (!ShouldExpand) {
+        Sema::ArgPackSubstIndexRAII SubstIndex(getSema(), std::nullopt);
+        TemplateName Pack = TransformOne(N);
+        if (Pack.isNull())
+          return TemplateName();
+        if (NotYetExpanded) {
+          FullySubstituted = false;
+          return getDerived().RebuildPackIndexingTemplateName(
+              Pack, IndexExpr.get(), FullySubstituted);
+        }
+        SubstitutedNames.push_back(Pack);
+        continue;
+      }
+
+      for (unsigned I = 0; I != *NumExpansions; ++I) {
+        Sema::ArgPackSubstIndexRAII SubstIndex(getSema(), I);
+        TemplateName Out = TransformOne(N);
+        if (Out.isNull())
+          return TemplateName();
+        SubstitutedNames.push_back(Out);
+        FullySubstituted &= !Out.containsUnexpandedParameterPack();
+      }
+
+      // If we're supposed to retain a pack expansion, do so by temporarily
+      // forgetting the partially-substituted parameter pack.
+      if (RetainExpansion) {
+        FullySubstituted = false;
+        ForgetPartiallySubstitutedPackRAII Forget(getDerived());
+        TemplateName Out = TransformOne(N);
+        if (Out.isNull())
+          return TemplateName();
+        SubstitutedNames.push_back(Out);
+      }
+    }
+
+    Sema::ArgPackSubstIndexRAII SubstIndex(getSema(), std::nullopt);
+    TemplateName NewPattern = TransformOne(Pattern);
+    if (NewPattern.isNull())
+      return TemplateName();
+
+    return getDerived().RebuildPackIndexingTemplateName(
+        NewPattern, IndexExpr.get(), FullySubstituted, SubstitutedNames);
+  }
+
   assert(!Name.getAsDeducedTemplateName() &&
          "DeducedTemplateName should not escape partial ordering");
 
@@ -4996,6 +5105,15 @@ TemplateName TreeTransform<Derived>::TransformTemplateName(
   llvm_unreachable("overloaded function decl survived to here");
 }
 
+template <typename Derived>
+TemplateName
+TreeTransform<Derived>::TransformConceptTemplateName(TemplateName Name,
+                                                     SourceLocation NameLoc) {
+  NestedNameSpecifierLoc QualifierLoc;
+  return getDerived().TransformTemplateName(
+      QualifierLoc, /*TemplateKWLoc=*/SourceLocation(), Name, NameLoc);
+}
+
 template <typename Derived>
 TemplateArgument TreeTransform<Derived>::TransformNamedTemplateTemplateArgument(
     NestedNameSpecifierLoc &QualifierLoc, SourceLocation TemplateKeywordLoc,
@@ -7567,14 +7685,15 @@ QualType TreeTransform<Derived>::TransformAutoType(TypeLocBuilder &TLB,
       return QualType();
   }
 
-  ConceptDecl *NewCD = nullptr;
+  TemplateName NewCD;
   TemplateArgumentListInfo NewTemplateArgs;
   NestedNameSpecifierLoc NewNestedNameSpec;
   if (T->isConstrained()) {
     assert(TL.getConceptReference());
-    NewCD = cast_or_null<ConceptDecl>(getDerived().TransformDecl(
-        TL.getConceptNameLoc(),
-        T->getTypeConstraintConcept().getAsTemplateDecl()));
+    NewCD = getDerived().TransformConceptTemplateName(
+        T->getTypeConstraintConcept(), TL.getConceptNameLoc());
+    if (NewCD.isNull())
+      return QualType();
 
     NewTemplateArgs.setLAngleLoc(TL.getLAngleLoc());
     NewTemplateArgs.setRAngleLoc(TL.getRAngleLoc());
@@ -7614,10 +7733,11 @@ QualType TreeTransform<Derived>::TransformAutoType(TypeLocBuilder &TLB,
   NewTL.setConceptReference(nullptr);
 
   if (T->isConstrained()) {
-    DeclarationName ConceptName = TL.getTypePtr()
-                                      ->getTypeConstraintConcept()
-                                      .getAsTemplateDecl()
-                                      ->getDeclName();
+    DeclarationName ConceptName =
+        SemaRef.Context
+            .getNameForTemplate(TL.getTypePtr()->getTypeConstraintConcept(),
+                                TL.getConceptNameLoc())
+            .getName();
     DeclarationNameInfo DNI =
         DeclarationNameInfo(ConceptName, TL.getConceptNameLoc(), ConceptName);
     auto *CR = ConceptReference::Create(
@@ -16554,22 +16674,21 @@ template <typename Derived>
 ExprResult TreeTransform<Derived>::TransformDependentTemplateIdExpr(
     DependentTemplateIdExpr *E) {
 
-  NestedNameSpecifierLoc Loc;
-  TemplateName Name = getDerived().TransformTemplateName(
-      Loc, /*Template Keyword=*/SourceLocation(), E->getTemplateName(),
-      E->getNameLoc());
+  TemplateName Name = getDerived().TransformConceptTemplateName(
+      E->getTemplateName(), E->getNameLoc());
   if (Name.isNull())
     return ExprError();
 
-  TemplateDecl *TD = Name.getAsTemplateDecl();
-
-  assert(TD && "A dependent template id always refers to a template decl");
-
   TemplateArgumentListInfo TransArgs(E->getLAngleLoc(), E->getRAngleLoc());
   if (getDerived().TransformTemplateArguments(
           E->template_arguments().data(), E->getNumTemplateArgs(), TransArgs))
     return ExprError();
 
+  TemplateDecl *TD = Name.getAsTemplateDecl();
+  if (!TD)
+    return SemaRef.CheckVarOrConceptTemplateTemplateId(E->getNameInfo(), Name,
+                                                       &TransArgs);
+
   CXXScopeSpec SS;
 
   LookupResult R(SemaRef, E->getNameInfo(), Sema::LookupOrdinaryName);
diff --git a/clang/lib/Serialization/TemplateArgumentHasher.cpp b/clang/lib/Serialization/TemplateArgumentHasher.cpp
index 6b598d26c3098..a6d5ea6e0e523 100644
--- a/clang/lib/Serialization/TemplateArgumentHasher.cpp
+++ b/clang/lib/Serialization/TemplateArgumentHasher.cpp
@@ -113,6 +113,9 @@ void TemplateArgumentHasher::AddTemplateName(TemplateName Name) {
     AddTemplateName(QTN->getUnderlyingTemplate());
     break;
   }
+  case TemplateName::PackIndexingTemplate:
+    AddTemplateName(Name.getAsPackIndexingTemplate()->getPattern());
+    break;
   case TemplateName::OverloadedTemplate:
   case TemplateName::AssumedTemplate:
   case TemplateName::DependentTemplate:
diff --git a/clang/test/AST/ast-dump-pack-indexing-template.cpp b/clang/test/AST/ast-dump-pack-indexing-template.cpp
new file mode 100644
index 0000000000000..4fcbd90d16862
--- /dev/null
+++ b/clang/test/AST/ast-dump-pack-indexing-template.cpp
@@ -0,0 +1,56 @@
+// RUN: %clang_cc1 -std=c++2d -ast-dump -ast-dump-filter=Dump %s | FileCheck %s
+
+template <class T> struct A {};
+template <class T> struct B {};
+
+template <template <class> class... TT>
+struct DumpDependent {
+  using type = TT...[1]<int>;
+};
+
+// CHECK-LABEL: Dumping DumpDependent:
+// CHECK:      TypeAliasDecl {{.*}} type 'TT...[1]<int>'
+// CHECK-NEXT: `-TemplateSpecializationType {{.*}} 'TT...[1]<int>' dependent
+// CHECK-NEXT:   |-name: 'TT...[1]':'template-parameter-0-0...[1]' pack_indexing index 1
+// CHECK-NEXT:   | |-pattern: 'TT':'template-parameter-0-0'
+// CHECK-NEXT:   | | `-TemplateTemplateParmDecl {{.*}} depth 0 index 0 ... TT
+// CHECK-NEXT:   | `-index: ConstantExpr {{.*}} '__size_t':'unsigned long'
+// CHECK-NEXT:   |   `-value: Int 1
+// CHECK-NEXT:   `-TemplateArgument type 'int'
+
+using DumpSubstituted = DumpDependent<A, B>::type;
+
+// CHECK-LABEL: Dumping DumpSubstituted:
+// CHECK:      TypeAliasDecl {{.*}} DumpSubstituted 'DumpDependent<A, B>::type':'B<int>'
+// CHECK:      TemplateSpecializationType {{.*}} 'TT...[1]<int>' sugar
+// CHECK-NEXT: |-name: 'TT...[1]':'B' pack_indexing fully_substituted index 1
+
+template <class T> concept C = true;
+
+template <template <class> concept... CC>
+constexpr bool DumpConceptId = CC...[0]<int>;
+
+// CHECK-LABEL: Dumping DumpConceptId:
+// CHECK:      VarTemplateDecl {{.*}} DumpConceptId
+// CHECK:      DependentTemplateIdExpr {{.*}} concept
+// CHECK-NEXT: `-name: 'CC...[0]':'template-parameter-0-0...[0]' pack_indexing index 0
+// CHECK-NEXT:   |-pattern: 'CC':'template-parameter-0-0'
+// CHECK-NEXT:   | `-TemplateTemplateParmDecl {{.*}} depth 0 index 0 ... CC
+// CHECK-NEXT:   `-index: ConstantExpr {{.*}} '__size_t':'unsigned long'
+// CHECK-NEXT:     `-value: Int 0
+
+template <template <class> auto... VV>
+constexpr int DumpVariableTemplateId = VV...[1]<int>;
+
+// CHECK-LABEL: Dumping DumpVariableTemplateId:
+// CHECK:      DependentTemplateIdExpr {{.*}} variable template
+// CHECK-NEXT: `-name: 'VV...[1]':'template-parameter-0-0...[1]' pack_indexing index 1
+
+template <template <class> concept... CC>
+struct DumpTypeConstraint {
+  template <CC...[0] T>
+  static void f();
+};
+
+// CHECK-LABEL: Dumping DumpTypeConstraint:
+// CHECK: TemplateTypeParmDecl {{.*}} Concept {{.*}} 'CC...[0]' depth 1 index 0 T
diff --git a/clang/test/AST/ast-print-pack-indexing-template.cpp b/clang/test/AST/ast-print-pack-indexing-template.cpp
new file mode 100644
index 0000000000000..fddf0f46c7820
--- /dev/null
+++ b/clang/test/AST/ast-print-pack-indexing-template.cpp
@@ -0,0 +1,42 @@
+// RUN: %clang_cc1 -ast-print -std=c++2d %s | FileCheck %s
+
+template <unsigned N, template <class> class... TT>
+struct S {
+  using type = TT...[N]<int>;
+  using first = TT...[0]<int>;
+};
+
+// CHECK: template <unsigned int N, template <class> class ...TT> struct S {
+// CHECK-NEXT: using type = TT...[N]<int>;
+// CHECK-NEXT: using first = TT...[0]<int>;
+
+template <template <class> class... TT>
+TT...[0]<int> f(TT...[1]<int>);
+
+// CHECK: template <template <class> class ...TT> TT...[0]<int> f(TT...[1]<int>);
+
+template <template <class> concept... CC>
+struct Constrained {
+  template <CC...[0] T>
+  static void f();
+  static void g(CC...[1] auto);
+};
+
+// CHECK:      template <template <class> concept ...CC> struct Constrained {
+// CHECK-NEXT: template <CC...[0] T> static void f();
+// CHECK-NEXT: static void g(CC...[1] auto);
+
+template <template <class> concept... CC>
+constexpr bool concept_id = CC...[0]<int>;
+
+// CHECK: template <template <class> concept ...CC> constexpr bool concept_id = CC...[0]<int>;
+
+template <template <class> auto... VV>
+constexpr int variable_template_id = VV...[1]<int>;
+
+// CHECK: template <template <class> auto ...VV> constexpr int variable_template_id = VV...[1]<int>;
+
+template <template <class> concept... CC>
+void requires_clause() requires CC...[0]<int>;
+
+// CHECK: template <template <class> concept ...CC> void requires_clause() requires CC...[0]<int>;
diff --git a/clang/test/ExtractAPI/pack_indexing_concept.cpp b/clang/test/ExtractAPI/pack_indexing_concept.cpp
new file mode 100644
index 0000000000000..38443929937f9
--- /dev/null
+++ b/clang/test/ExtractAPI/pack_indexing_concept.cpp
@@ -0,0 +1,33 @@
+// RUN: rm -rf %t
+// RUN: %clang_cc1 -std=c++2d -extract-api --pretty-sgf --emit-sgf-symbol-labels-for-testing \
+// RUN:   --product-name=PackIndexing -triple arm64-apple-macosx -x c++-header %s \
+// RUN:   -o %t/pack-indexing.symbols.json -verify
+
+// RUN: FileCheck %s --input-file %t/pack-indexing.symbols.json --check-prefix FUNCTION
+
+template <template <class> concept... CC, CC...[0] T>
+void function(T);
+
+// expected-no-diagnostics
+
+//FUNCTION-LABEL: "!testLabel": "c:@FT@>2#pt>1#T#Tfunction#t0.1#v#"
+//FUNCTION:      "declarationFragments": [
+//FUNCTION:        "kind": "genericParameter",
+//FUNCTION-NEXT:   "spelling": "CC"
+//FUNCTION-NEXT: },
+//FUNCTION-NEXT: {
+//FUNCTION-NEXT:     "kind": "text",
+//FUNCTION-NEXT:     "spelling": ", "
+//FUNCTION-NEXT:   },
+//FUNCTION-NEXT:   {
+//FUNCTION-NEXT:     "kind": "typeIdentifier",
+//FUNCTION-NEXT:     "spelling": "CC...[0]"
+//FUNCTION-NEXT:   },
+//FUNCTION-NEXT:   {
+//FUNCTION-NEXT:     "kind": "text",
+//FUNCTION-NEXT:     "spelling": " "
+//FUNCTION-NEXT:   },
+//FUNCTION-NEXT:   {
+//FUNCTION-NEXT:     "kind": "genericParameter",
+//FUNCTION-NEXT:     "spelling": "T"
+//FUNCTION-NEXT:   },
diff --git a/clang/test/Lexer/cxx-features.cpp b/clang/test/Lexer/cxx-features.cpp
index b505f18dff14f..9845bea38d25b 100644
--- a/clang/test/Lexer/cxx-features.cpp
+++ b/clang/test/Lexer/cxx-features.cpp
@@ -46,7 +46,7 @@
 #error "wrong value for __cpp_deleted_function"
 #endif
 
-#if check(pack_indexing, 202311, 202311, 202311, 202311, 202311, 202311, 202311)
+#if check(pack_indexing, 202606, 202606, 202606, 202606, 202606, 202606, 202606)
 #error "wrong value for __cpp_pack_indexing"
 #endif
 
diff --git a/clang/test/Modules/pack-indexing-template.cppm b/clang/test/Modules/pack-indexing-template.cppm
new file mode 100644
index 0000000000000..c752cd9176c77
--- /dev/null
+++ b/clang/test/Modules/pack-indexing-template.cppm
@@ -0,0 +1,73 @@
+// RUN: rm -rf %t
+// RUN: split-file %s %t
+//
+// RUN: %clang_cc1 -std=c++2d -triple %itanium_abi_triple -emit-module-interface %t/a.cppm -o %t/a.pcm
+// RUN: %clang_cc1 -std=c++2d -triple %itanium_abi_triple -fmodule-file=a=%t/a.pcm -emit-llvm -o - %t/b.cpp | FileCheck %s
+// RUN: %clang_cc1 -std=c++2d -triple %itanium_abi_triple -fmodule-file=a=%t/a.pcm -fsyntax-only -verify %t/c.cpp
+
+//--- a.cppm
+export module a;
+
+template <class T> struct Wrapper {
+  using type = T;
+  T value;
+};
+template <class T> struct Other {};
+
+export template <unsigned I, template <class> class... TT>
+using Indexed = TT...[I]<int>;
+
+export template <template <class> class... TT>
+struct Holder {
+  using first = TT...[0]<int>;
+  typename TT...[0]<int>::type value;
+};
+
+export template <class T> concept Always = true;
+export template <class T> concept Never = false;
+export template <class T> constexpr int Var = 1;
+export template <class T> constexpr int Var2 = 2;
+
+export template <unsigned I, template <class> concept... CC>
+constexpr bool ConceptId = CC...[I]<int>;
+
+export template <unsigned I, template <class> auto... VV>
+constexpr int VariableTemplateId = VV...[I]<int>;
+
+export template <template <class> concept... CC>
+struct Constrained {
+  template <CC...[0] T>
+  static constexpr int f() { return 3; }
+  static constexpr int g(CC...[0] auto) { return 4; }
+};
+
+export Indexed<0, Wrapper, Other> a = {42};
+
+//--- b.cpp
+import a;
+
+int b() {
+  return a.value;
+}
+
+// CHECK: @_ZW1a1a = external global %struct.Wrapper
+// CHECK: define {{.*}}i32 @_Z1bv()
+
+//--- c.cpp
+// expected-no-diagnostics
+import a;
+
+template <class T> struct Local {
+  using type = T;
+};
+template <class T> struct Unused {};
+
+static_assert(__is_same(Indexed<1, Local, Unused>, Unused<int>));
+static_assert(__is_same(Holder<Local>::first, Local<int>));
+static_assert(__is_same(decltype(Holder<Local>::value), int));
+
+static_assert(ConceptId<0, Always, Never>);
+static_assert(!ConceptId<1, Always, Never>);
+static_assert(VariableTemplateId<1, Var, Var2> == 2);
+static_assert(Constrained<Always>::f<int>() == 3);
+static_assert(Constrained<Always>::g(0) == 4);
diff --git a/clang/test/PCH/pack-indexing-template.cpp b/clang/test/PCH/pack-indexing-template.cpp
new file mode 100644
index 0000000000000..fcb295a2b9ba4
--- /dev/null
+++ b/clang/test/PCH/pack-indexing-template.cpp
@@ -0,0 +1,67 @@
+// RUN: %clang_cc1 -std=c++2d -x c++-header %s -emit-pch -o %t.pch
+// RUN: %clang_cc1 -std=c++2d -x c++ /dev/null -include-pch %t.pch
+
+// RUN: %clang_cc1 -std=c++2d -x c++-header %s -emit-pch -fpch-instantiate-templates -o %t.pch
+// RUN: %clang_cc1 -std=c++2d -x c++ /dev/null -include-pch %t.pch
+
+template <class T> struct A {
+  using type = T;
+};
+template <class T> struct B {};
+template <template <class> class> struct Take {};
+
+template <unsigned I, template <class> class... TT>
+using Indexed = TT...[I]<int>;
+
+template <unsigned I, template <class> class... TT>
+using Nested = typename TT...[I]<int>::type;
+
+template <unsigned I, template <class> class... TT>
+using AsArgument = Take<TT...[I]>;
+
+template <template <class> class... TT>
+struct Base : TT...[0]<int> {};
+
+template <class T> struct Deduce {
+  Deduce(T);
+};
+template <template <class> class... TT>
+auto ctad() {
+  TT...[0] x{0};
+  return x;
+}
+
+template <class T> concept Always = true;
+template <class T> concept Never = false;
+template <class T> constexpr int Var = 1;
+template <class T> constexpr int Var2 = 2;
+
+template <unsigned I, template <class> concept... CC>
+constexpr bool ConceptId = CC...[I]<int>;
+
+template <unsigned I, template <class> auto... VV>
+constexpr int VariableTemplateId = VV...[I]<int>;
+
+template <template <class> concept... CC>
+struct Constrained {
+  template <CC...[0] T>
+  static constexpr int f() { return 3; }
+  static constexpr int g(CC...[0] auto) { return 4; }
+};
+
+template <template <class> concept... CC>
+constexpr int Requires() requires CC...[0]<int> { return 5; }
+
+void fn() {
+  static_assert(__is_same(Indexed<1, A, B>, B<int>));
+  static_assert(__is_same(Nested<0, A, B>, int));
+  static_assert(__is_same(AsArgument<1, A, B>, Take<B>));
+  static_assert(__is_base_of(A<int>, Base<A, B>));
+  static_assert(__is_same(decltype(ctad<Deduce>()), Deduce<int>));
+  static_assert(ConceptId<0, Always, Never>);
+  static_assert(!ConceptId<1, Always, Never>);
+  static_assert(VariableTemplateId<1, Var, Var2> == 2);
+  static_assert(Constrained<Always>::f<int>() == 3);
+  static_assert(Constrained<Always>::g(0) == 4);
+  static_assert(Requires<Always>() == 5);
+}
diff --git a/clang/test/Parser/cxx2d-pack-indexing-template.cpp b/clang/test/Parser/cxx2d-pack-indexing-template.cpp
new file mode 100644
index 0000000000000..b62cecf361451
--- /dev/null
+++ b/clang/test/Parser/cxx2d-pack-indexing-template.cpp
@@ -0,0 +1,69 @@
+// RUN: %clang_cc1 -std=c++2d -verify -fsyntax-only %s
+
+template <class> struct A {};
+template <class> struct B {};
+
+template <template <class> class... TT> // expected-note {{template is declared here}}
+struct Malformed {
+  using a = TT...[<int>;   // expected-error 2{{expected expression}} \
+                           // expected-error {{expected ']'}} \
+                           // expected-note {{to match this '['}} \
+                           // expected-error {{expected '(' for function-style cast or type construction}}
+
+  using b = TT...[0<int>;  // expected-error {{expected ']'}} \
+                           // expected-note {{to match this '['}} \
+                           // expected-error {{expected expression}} \
+                           // expected-error {{expected '(' for function-style cast or type construction}}
+
+  using c = TT...[]<int>;  // expected-error {{use of template template parameter 'TT' requires template arguments}} \
+                           // expected-error {{expected ';' after alias declaration}}
+};
+
+template <template <class> class... TT>
+struct S {
+  using type = TT...[0]<int>;
+};
+
+template <class T> struct WithMember { using type = T; };
+
+template <template <class> class... TT>
+using U = typename TT...[0]<int>::type;
+
+namespace ns { }
+template <template <class> class... TT>
+using Q = ns::TT...[0]<int>; // expected-error {{no type named 'TT' in namespace 'ns'}} \
+                             // expected-error {{expected ';' after alias declaration}}
+
+template <class T> concept Concept = true;
+template <class T> constexpr int Var = 1;
+
+template <template <class> concept... CC>
+constexpr bool concept_id() {
+  return CC...[0]<int>;
+}
+
+template <template <class> auto... VV>
+constexpr int variable_template_id() {
+  return VV...[0]<int>;
+}
+template <template <class> concept... CC>
+struct TypeConstraint {
+  template <CC...[0] T>
+  static constexpr int f() { return 2; }
+};
+template <template <class> concept... CC>
+struct MalformedConcept {
+  template <CC...[] T> // expected-error {{expected identifier}}
+  static void f(); // expected-error {{no candidate function template was found for dependent member function template specialization}} \
+                   // expected-warning {{explicit specialization cannot have a storage class}}
+};
+
+namespace not_a_template {
+template <class... T>
+using U = T...[0];
+static_assert(__is_same(U<int, long>, int));
+
+template <auto... V>
+constexpr auto W = V...[1];
+static_assert(W<1, 2, 3> == 2);
+}
diff --git a/clang/test/SemaCXX/cxx2d-pack-indexing-template-ext-diags.cpp b/clang/test/SemaCXX/cxx2d-pack-indexing-template-ext-diags.cpp
new file mode 100644
index 0000000000000..c29610c010533
--- /dev/null
+++ b/clang/test/SemaCXX/cxx2d-pack-indexing-template-ext-diags.cpp
@@ -0,0 +1,54 @@
+// RUN: %clang_cc1 -std=c++2d -verify=cxx29 -fsyntax-only -Wpre-c++2d-compat %s
+// RUN: %clang_cc1 -std=c++11 -verify=ext -fsyntax-only -Wc++2d-extensions %s
+// RUN: %clang_cc1 -std=c++98 -verify=ext -fsyntax-only -Wc++2d-extensions -Wno-c++11-extensions %s
+
+template <class T> struct A {};
+template <class T> struct B {};
+
+template <template <class> class... TT>
+struct S {
+  // cxx29-warning at +2 {{pack indexing for template names is incompatible with C++ standards before C++2d}}
+  // ext-warning at +1 {{pack indexing for template names is a C++2d extension}}
+  typedef TT...[0]<int> a;
+};
+
+#if __cplusplus > 202302L
+template <class T> concept Concept = true;
+
+template <template <class> concept... CC>
+struct Concepts {
+  // cxx29-warning at +1 {{pack indexing for template names is incompatible with C++ standards before C++2d}}
+  template <CC...[0] T>
+  static void a();
+};
+#endif
+
+template <class... T>
+struct Types {
+  // ext-warning at +1 {{pack indexing is a C++2c extension}}
+  typedef T...[0] a;
+};
+
+template <class T> struct Deduce { Deduce(T); };
+
+// ext-note at +1 {{template is declared here}}
+template <template <class> class... TT>
+void deduced_class_type() {
+  // cxx29-warning at +3 {{pack indexing for template names is incompatible with C++ standards before C++2d}}
+  // ext-warning at +2 {{pack indexing for template names is a C++2d extension}}
+  // ext-error at +1 {{too few template arguments for template template parameter 'TT'}}
+  TT...[0] x = 1;
+  (void)x;
+}
+
+void use() {
+  S<A, B> s;
+  (void)s;
+  Types<int, long> t;
+  (void)t;
+  deduced_class_type<Deduce>();
+#if __cplusplus > 202302L
+  Concepts<Concept> c;
+  (void)c;
+#endif
+}
diff --git a/clang/test/SemaCXX/cxx2d-pack-indexing-template.cpp b/clang/test/SemaCXX/cxx2d-pack-indexing-template.cpp
new file mode 100644
index 0000000000000..38be1ae5c7894
--- /dev/null
+++ b/clang/test/SemaCXX/cxx2d-pack-indexing-template.cpp
@@ -0,0 +1,400 @@
+// RUN: %clang_cc1 -std=c++2d -verify -fsyntax-only %s
+
+template <class T> struct A { using type = T; };
+template <class T> struct B { using type = T *; };
+template <class...> struct List {};
+
+namespace not_a_pack {
+template <template <class> class TT>
+using U = TT...[0]<int>; // expected-error {{'TT' does not refer to the name of a parameter pack}}
+
+using V = A...[0]<int>; // expected-error {{'A' does not refer to the name of a parameter pack}}
+
+template <template <class> concept CC>
+constexpr bool W = CC...[0]<int>; // expected-error {{'CC' does not refer to the name of a parameter pack}}
+
+template <template <class> auto VV>
+constexpr int X = VV...[0]<int>; // expected-error {{'VV' does not refer to the name of a parameter pack}}
+}
+
+namespace index {
+template <template <class> class... TT>
+struct S {
+  using ok = TT...[sizeof...(TT) - 1]<int>;
+};
+static_assert(__is_same(S<A, B>::ok, B<int>));
+
+template <template <class> class... TT>
+using OutOfBounds = TT...[2]<int>; // expected-error {{invalid index 2 for pack 'TT' of size 2}}
+using E1 = OutOfBounds<A, B>;      // expected-note {{in instantiation of template type alias 'OutOfBounds' requested here}}
+
+template <template <class> class... TT>
+using Negative = TT...[-1]<int>;
+// expected-error at -1 {{pack index evaluates to -1, which cannot be narrowed to type '__size_t'}}
+// expected-error at -2 {{expected ';' after alias declaration}}
+
+template <template <class> class... TT>
+using Narrowing = TT...[1.0]<int>;
+// expected-error at -1 {{conversion from 'double' to '__size_t' (aka 'unsigned long') is not allowed in a converted constant expression}}
+// expected-error at -2 {{expected ';' after alias declaration}}
+
+template <template <class> class... TT>
+using NonConstant = TT...[x]<int>;
+// expected-error at -1 {{use of undeclared identifier 'x'}}
+// expected-error at -2 {{expected ';' after alias declaration}}
+}
+
+namespace equivalence {
+template <template <class> class... TT>
+void same(TT...[0]<int>);
+template <template <class> class... TT>
+void same(TT...[0]<int>);
+
+template <unsigned N, template <class> class... TT>
+void dependent(TT...[N]<int>);
+template <unsigned N, template <class> class... TT>
+void dependent(TT...[N]<int>);
+
+template <template <class> class... TT>
+void different(TT...[0]<int>);
+template <template <class> class... TT>
+void different(TT...[1]<int>);
+
+void call() {
+  same<A, B>(A<int>{});
+  dependent<1, A, B>(B<int>{});
+  different<A, B>(A<int>{});
+  different<A, B>(B<int>{});
+}
+}
+
+namespace uses {
+template <class T> struct WithNested {
+  using type = T;
+  static constexpr int value = 1;
+};
+
+template <template <class> class... TT>
+struct Everywhere : TT...[0]<int> {
+  using alias = TT...[0]<int>;
+  template <class T> using tmpl_alias = TT...[0]<T>;
+  using nested = typename TT...[0]<int>::type;
+  static constexpr int v = TT...[0]<int>::value;
+  TT...[0]<int> member;
+  TT...[0]<int> fn(TT...[0]<int>);
+};
+
+static_assert(__is_base_of(WithNested<int>, Everywhere<WithNested>));
+static_assert(__is_same(Everywhere<WithNested>::alias, WithNested<int>));
+static_assert(__is_same(Everywhere<WithNested>::tmpl_alias<char>, WithNested<char>));
+static_assert(__is_same(Everywhere<WithNested>::nested, int));
+static_assert(Everywhere<WithNested>::v == 1);
+
+template <template <class> class> struct Take {};
+template <template <class> class... TT>
+using Arg = Take<TT...[1]>;
+static_assert(__is_same(Arg<A, B>, Take<B>));
+
+template <class T> struct Deduce {
+  Deduce(T);
+};
+template <template <class> class... TT>
+auto ctad() {
+  TT...[0] x{42};
+  return x;
+}
+static_assert(__is_same(decltype(ctad<Deduce>()), Deduce<int>));
+}
+
+namespace deduction_guides {
+template <class T> struct C { C(T); };
+
+template <template <class> class... TT>
+TT...[0](int) -> TT...[0]<int>;
+// expected-error at -1 {{expected unqualified-id}}
+// expected-error at -2 {{expected ')'}}
+// expected-note at -3 {{to match this '('}}
+
+template <template <class> class... TT>
+C(int) -> TT...[0]<int>;
+// expected-error at -1 {{deduced type 'TT...[0]<int>' of deduction guide is not written as a specialization of template 'C'}}
+}
+
+namespace pack_expansion {
+template <template <class> class... TT>
+struct S {
+  using one = TT...[0]<int>;
+  template <unsigned... Is>
+  using many = List<TT...[Is]<int>...>;
+};
+static_assert(__is_same(S<A, B>::many<1, 0>, List<B<int>, A<int>>));
+
+template <template <class> class... TT>
+struct Bad {
+  template <unsigned... Is>
+  using type = List<TT...[Is]<int>>; // expected-error {{declaration type contains unexpanded parameter pack 'Is'}}
+};
+}
+
+namespace partial_substitution {
+template <class T> struct Outer {
+  template <template <class> class... TT>
+  using inner = TT...[0]<T>;
+};
+static_assert(__is_same(Outer<int>::inner<A, B>, A<int>));
+
+template <template <class> class... TT>
+struct Nested {
+  template <class... Ts>
+  using apply = List<TT...[0]<Ts>...>;
+};
+static_assert(__is_same(Nested<A>::apply<int, long>, List<A<int>, A<long>>));
+}
+
+namespace empty_pack {
+template <template <class> class... TT>
+using U = TT...[0]<int>; // expected-error {{invalid index 0 for pack 'TT' of size 0}}
+using E = U<>;           // expected-note {{in instantiation of template type alias 'U' requested here}}
+}
+
+namespace kinds {
+template <class T> constexpr int Var = 1;
+template <class T> constexpr int Var2 = 2;
+template <class T> concept Always = true;
+template <class T> concept Never = false;
+template <class T, class U> concept Same = __is_same(T, U);
+
+template <template <class> auto V1> struct TakeVar {};
+template <template <class> concept C1> struct TakeConcept {};
+
+template <template <class> auto... VV>
+using AV = TakeVar<VV...[0]>;
+static_assert(__is_same(AV<Var>, TakeVar<Var>));
+
+template <template <class> concept... CC>
+using AC = TakeConcept<CC...[0]>;
+static_assert(__is_same(AC<Always>, TakeConcept<Always>));
+
+template <template <class> auto... VV>
+constexpr int use_var() {
+  return VV...[1]<int>;
+}
+static_assert(use_var<Var, Var2>() == 2);
+static_assert(use_var<Var2, Var>() == 1);
+
+template <template <class> concept... CC>
+constexpr bool use_concept() {
+  return CC...[0]<int>;
+}
+static_assert(use_concept<Always, Never>());
+static_assert(!use_concept<Never, Always>());
+
+template <template <class> concept... CC>
+struct Constrained {
+  template <CC...[0] T>
+  static constexpr int f() { return 1; }
+};
+static_assert(Constrained<Always, Never>::f<int>() == 1);
+
+template <template <class, class> concept... CC>
+struct ConstrainedWithArgs {
+  template <CC...[0]<int> T>
+  static constexpr int f() { return 2; }
+};
+static_assert(ConstrainedWithArgs<Same>::f<int>() == 2);
+
+template <template <class> concept... CC, CC...[0] T>
+constexpr int in_same_list(T) { return 3; }
+static_assert(in_same_list<Always>(0) == 3);
+
+template <template <class> concept... CC>
+constexpr int with_auto(CC...[0] auto x) { return 4; }
+static_assert(with_auto<Always>(0) == 4);
+
+template <template <class> concept... CC>
+constexpr int with_auto_var() {
+  CC...[0] auto x = 5;
+  return x;
+}
+static_assert(with_auto_var<Always>() == 5);
+
+template <template <class> concept... CC>
+constexpr auto with_auto_return() -> CC...[0] auto { return 6; }
+static_assert(with_auto_return<Always>() == 6);
+
+template <template <class> concept... CC>
+constexpr int with_requires() requires CC...[0]<int> { return 7; }
+static_assert(with_requires<Always>() == 7);
+
+template <unsigned N, template <class> auto... VV>
+constexpr int DependentVar = VV...[N]<int>;
+static_assert(DependentVar<0, Var, Var2> == 1);
+static_assert(DependentVar<1, Var, Var2> == 2);
+
+template <unsigned N, template <class> concept... CC>
+constexpr bool DependentConcept = CC...[N]<int>;
+static_assert(DependentConcept<0, Always, Never>);
+static_assert(!DependentConcept<1, Always, Never>);
+
+template <template <class> concept... CC>
+struct Expansion {
+  template <unsigned... Is>
+  static constexpr bool all() { return (CC...[Is]<int> && ...); }
+};
+static_assert(Expansion<Always, Always>::all<0, 1>());
+static_assert(!Expansion<Always, Never>::all<0, 1>());
+
+template <template <class> concept... CC>
+constexpr bool GH218035 = ((CC...[0]<int> == CC<int>) && ...);
+static_assert(GH218035<Always, Always>);
+static_assert(!GH218035<Always, Never>);
+static_assert(!GH218035<Never, Always>);
+
+template <template <class> concept... CC>
+struct GH218548 {
+  template <class... Ts>
+  static constexpr bool f() { return (CC...[sizeof(Ts) - 1]<int> && ...); }
+};
+static_assert(GH218548<Never, Always>::f<short>());
+static_assert(!GH218548<Never, Always>::f<char>());
+static_assert(!GH218548<Never, Always>::f<char, short>());
+}
+
+namespace kind_errors {
+template <class T> constexpr int Var = 1;
+template <class T> constexpr int Var2 = 2;
+template <class T> concept Always = true;
+template <class T> concept Big = sizeof(T) > 1;
+// expected-note at -1 2{{because 'sizeof(char) > 1' (1 > 1) evaluated to false}}
+
+template <template <class> concept... CC>
+void constrained_auto(CC...[1] auto x);
+// expected-note at -1 {{candidate template ignored: constraints not satisfied}}
+// expected-note at -2 {{because 'char' does not satisfy 'Big'}}
+
+void use_constrained_auto() {
+  constrained_auto<Always, Big>('c'); // expected-error {{no matching function for call to 'constrained_auto'}}
+  constrained_auto<Big, Always>('c');
+}
+
+template <template <class> concept... CC>
+struct Constrained {
+  template <CC...[0] T> // expected-note {{because 'char' does not satisfy 'Big'}}
+  static void f();      // expected-note {{candidate template ignored: constraints not satisfied [with T = char]}}
+};
+void use_constrained() {
+  Constrained<Big>::f<char>(); // expected-error {{no matching function for call to 'f'}}
+  Constrained<Big>::f<int>();
+}
+
+template <template <class> auto... VV>
+constexpr int VarOutOfBounds = VV...[2]<int>; // expected-error {{invalid index 2 for pack 'VV' of size 2}}
+constexpr int E1 = VarOutOfBounds<Var, Var2>; // expected-note {{in instantiation of variable template specialization 'kind_errors::VarOutOfBounds<kind_errors::Var, kind_errors::Var2>' requested here}}
+
+template <template <class> concept... CC>
+constexpr bool ConceptOutOfBounds = CC...[1]<int>; // expected-error {{invalid index 1 for pack 'CC' of size 1}}
+constexpr bool E2 = ConceptOutOfBounds<Always>;    // expected-note {{in instantiation of variable template specialization 'kind_errors::ConceptOutOfBounds<kind_errors::Always>' requested here}}
+
+template <template <class> concept... CC>
+struct ConstraintOutOfBounds {
+  // expected-note at +1 {{because substituted constraint expression is ill-formed: invalid index 1 for pack 'CC' of size 1}}
+  template <CC...[1] T>
+  static void f(); // expected-note {{candidate template ignored: constraints not satisfied [with T = int]}}
+};
+void use_out_of_bounds() {
+  ConstraintOutOfBounds<Always>::f<int>(); // expected-error {{no matching function for call to 'f'}}
+}
+
+template <auto> concept NonType = true;
+template <template <auto> concept... CC>
+struct NotATypeConcept {
+  template <CC...[0] T> // expected-error {{concept named in type constraint is not a type concept}}
+  static void f();
+};
+}
+
+namespace sfinae {
+template <class T> struct HasType {
+  using type = T;
+};
+template <class T> struct NoType {};
+
+template <template <class> class... TT>
+constexpr int f(typename TT...[0]<int>::type *) { return 1; }
+template <template <class> class... TT>
+constexpr int f(...) { return 2; }
+
+static_assert(f<HasType>(nullptr) == 1);
+static_assert(f<NoType>(nullptr) == 2);
+}
+
+namespace equivalence {
+template <class T> concept Always = true;
+
+template <template <class> concept... CC>
+void same(CC...[0] auto x) {} // expected-note {{previous definition is here}}
+template <template <class> concept... CC>
+void same(CC...[0] auto x) {} // expected-error {{redefinition of 'same'}}
+
+template <template <class> concept... CC>
+void different(CC...[0] auto x) {}
+template <template <class> concept... CC>
+void different(CC...[1] auto x) {}
+
+template <template <class> concept... CC>
+constexpr bool same_id = CC...[0]<int>; // expected-note {{previous definition is here}}
+template <template <class> concept... CC>
+constexpr bool same_id = CC...[0]<int>; // expected-error {{redefinition of 'same_id'}}
+
+template <template <class> concept... CC>
+constexpr bool different_id_a = CC...[0]<int>;
+template <template <class> concept... CC>
+constexpr bool different_id_b = CC...[1]<int>;
+
+template <class T> concept Never = false;
+
+template <template <class> concept... CC, class T> requires CC...[0]<T>
+constexpr int same_constraint(T) { return 1; } // expected-note {{previous definition is here}}
+template <template <class> concept... CC, class T> requires CC...[0]<T>
+constexpr int same_constraint(T) { return 2; } // expected-error {{redefinition of 'same_constraint'}}
+
+template <template <class> concept... CC, class T> requires CC...[0]<T>
+constexpr int different_constraint(T) { return 1; }
+template <template <class> concept... CC, class T> requires CC...[1]<T>
+constexpr int different_constraint(T) { return 2; }
+static_assert(different_constraint<Always, Never>(0) == 1);
+static_assert(different_constraint<Never, Always>(0) == 2);
+}
+
+namespace atomic_constraints {
+template <class T> concept Always = true;
+
+template <template <class> concept... CC>
+struct S {
+  template <class T> requires CC...[0]<T>
+  // expected-note at -1 {{similar constraint expression here}}
+  static constexpr int f() { return 1; } // expected-note {{candidate function [with T = int]}}
+  template <class T> requires CC...[0]<T> && Always<T>
+  // expected-note at -1 {{similar constraint expressions not considered equivalent; constraint expressions cannot be considered equivalent unless they originate from the same concept}}
+  static constexpr int f() { return 2; } // expected-note {{candidate function [with T = int]}}
+};
+constexpr int a = S<Always>::f<int>(); // expected-error {{call to 'f' is ambiguous}}
+
+template <template <class> concept... CC>
+struct T {
+  template <class T> requires CC...[0]<T>
+  static constexpr int f() { return 1; } // expected-note {{candidate function [with T = int]}}
+  template <class T> requires Always<T>
+  static constexpr int f() { return 2; } // expected-note {{candidate function [with T = int]}}
+};
+constexpr int b = T<Always>::f<int>(); // expected-error {{call to 'f' is ambiguous}}
+
+template <template <class> concept... CC>
+struct U {
+  template <class T> requires CC...[0]<T>
+  static constexpr int f() { return 1; } // expected-note {{candidate function [with T = int]}}
+  template <class T> requires CC...[1]<T>
+  static constexpr int f() { return 2; } // expected-note {{candidate function [with T = int]}}
+};
+constexpr int c = U<Always, Always>::f<int>(); // expected-error {{call to 'f' is ambiguous}}
+}
diff --git a/clang/tools/libclang/CIndex.cpp b/clang/tools/libclang/CIndex.cpp
index 2c5e799d12f18..a165924bebd2d 100644
--- a/clang/tools/libclang/CIndex.cpp
+++ b/clang/tools/libclang/CIndex.cpp
@@ -1503,6 +1503,10 @@ bool CursorVisitor::VisitTemplateName(TemplateName Name, SourceLocation NameLoc,
         Name.getAsSubstTemplateTemplateParmPack()->getParameterPack(), NameLoc,
         TU));
 
+  case TemplateName::PackIndexingTemplate:
+    return VisitTemplateName(Name.getAsPackIndexingTemplate()->getPattern(),
+                             NameLoc, NNS);
+
   case TemplateName::DeducedTemplate:
     llvm_unreachable("DeducedTemplate shouldn't appear in source");
   }
diff --git a/clang/www/cxx_status.html b/clang/www/cxx_status.html
index 16ebeb1052b79..4baaac74a0758 100755
--- a/clang/www/cxx_status.html
+++ b/clang/www/cxx_status.html
@@ -187,7 +187,11 @@ <h2 id="cxx29">C++2d implementation status</h2>
 <tr>
   <td>Pack indexing for template names</td>
   <td><a href="https://wg21.link/P3670R4">P3670R4</a></td>
-  <td class="none" align="center">No</td>
+  <td class="partial" align="center">
+    <details><summary>Clang 24 (Partial)</summary>
+      The mangling of a <i>pack-index-template-name</i> is not implemented.
+    </details>
+  </td>
 </tr>
 <tr>
   <td><code>#embed</code> offset parameter</td>

>From 9ecb5bab23e014b5521240f28ce7b6ac3d3c1f87 Mon Sep 17 00:00:00 2001
From: Corentin Jabot <corentinjabot at gmail.com>
Date: Wed, 26 Aug 2026 11:24:25 +0200
Subject: [PATCH 2/4] Address Eli and Younan's feedback

- Add tests with multiple expansions
- remove weird flag in run line
- remove feature test macro update
- remove unused headers
---
 .../clangd/unittests/DumpASTTests.cpp         |  8 ++++----
 clang/lib/AST/TemplateName.cpp                |  4 ----
 clang/lib/Frontend/InitPreprocessor.cpp       |  2 +-
 clang/lib/Sema/SemaTemplate.cpp               |  2 +-
 .../test/ExtractAPI/pack_indexing_concept.cpp |  3 +--
 clang/test/Lexer/cxx-features.cpp             |  2 +-
 .../SemaCXX/cxx2d-pack-indexing-template.cpp  | 19 ++++++++++++++++++-
 7 files changed, 26 insertions(+), 14 deletions(-)

diff --git a/clang-tools-extra/clangd/unittests/DumpASTTests.cpp b/clang-tools-extra/clangd/unittests/DumpASTTests.cpp
index bfdb156ceb6cb..f116fe56b90cd 100644
--- a/clang-tools-extra/clangd/unittests/DumpASTTests.cpp
+++ b/clang-tools-extra/clangd/unittests/DumpASTTests.cpp
@@ -10,6 +10,7 @@
 #include "DumpAST.h"
 #include "TestTU.h"
 #include "clang/AST/ASTTypeTraits.h"
+#include "llvm/ADT/STLExtras.h"
 #include "llvm/Support/ScopedPrinter.h"
 #include "gmock/gmock.h"
 #include "gtest/gtest.h"
@@ -230,10 +231,9 @@ TEST(DumpASTTests, UnbalancedBraces) {
 bool hasDetail(const ASTNode &Node, llvm::StringRef Detail) {
   if (Node.detail == Detail)
     return true;
-  for (const ASTNode &Child : Node.children)
-    if (hasDetail(Child, Detail))
-      return true;
-  return false;
+  return llvm::any_of(Node.children, [&Detail](const ASTNode &Child) {
+    return hasDetail(Child, Detail);
+  });
 }
 
 TEST(DumpASTTests, PackIndexedConcept) {
diff --git a/clang/lib/AST/TemplateName.cpp b/clang/lib/AST/TemplateName.cpp
index 910c5a761dd77..12c342652c851 100644
--- a/clang/lib/AST/TemplateName.cpp
+++ b/clang/lib/AST/TemplateName.cpp
@@ -16,8 +16,6 @@
 #include "clang/AST/DeclCXX.h"
 #include "clang/AST/DeclTemplate.h"
 #include "clang/AST/DependenceFlags.h"
-#include "clang/AST/Expr.h"
-#include "clang/AST/NestedNameSpecifier.h"
 #include "clang/AST/PrettyPrinter.h"
 #include "clang/AST/TemplateBase.h"
 #include "clang/Basic/Diagnostic.h"
@@ -26,8 +24,6 @@
 #include "clang/Basic/OperatorKinds.h"
 #include "llvm/ADT/ArrayRef.h"
 #include "llvm/ADT/FoldingSet.h"
-#include "llvm/ADT/STLExtras.h"
-#include "llvm/Support/Compiler.h"
 #include "llvm/Support/raw_ostream.h"
 #include <cassert>
 #include <optional>
diff --git a/clang/lib/Frontend/InitPreprocessor.cpp b/clang/lib/Frontend/InitPreprocessor.cpp
index f37d95cd02068..7cc481b595f36 100644
--- a/clang/lib/Frontend/InitPreprocessor.cpp
+++ b/clang/lib/Frontend/InitPreprocessor.cpp
@@ -761,7 +761,7 @@ static void InitializeCPlusPlusFeatureTestMacros(const LangOptions &LangOpts,
   Builder.defineMacro("__cpp_placeholder_variables", "202306L");
 
   // C++26 features supported in earlier language modes.
-  Builder.defineMacro("__cpp_pack_indexing", "202606L");
+  Builder.defineMacro("__cpp_pack_indexing", "202311L");
   Builder.defineMacro("__cpp_deleted_function", "202403L");
   Builder.defineMacro("__cpp_variadic_friend", "202403L");
   Builder.defineMacro("__cpp_trivial_relocatability", "202502L");
diff --git a/clang/lib/Sema/SemaTemplate.cpp b/clang/lib/Sema/SemaTemplate.cpp
index 56d0c5a4d2fe9..eb93a5e03b06f 100644
--- a/clang/lib/Sema/SemaTemplate.cpp
+++ b/clang/lib/Sema/SemaTemplate.cpp
@@ -9322,7 +9322,7 @@ void Sema::CheckConceptRedefinition(ConceptDecl *NewDecl,
 }
 
 bool Sema::CheckConceptUseInDefinition(NamedDecl *Concept, SourceLocation Loc) {
-  if (auto *CE = llvm::dyn_cast_if_present<ConceptDecl>(Concept);
+  if (auto *CE = llvm::dyn_cast<ConceptDecl>(Concept);
       CE && !CE->isInvalidDecl() && !CE->hasDefinition()) {
     Diag(Loc, diag::err_recursive_concept) << CE;
     Diag(CE->getLocation(), diag::note_declared_at);
diff --git a/clang/test/ExtractAPI/pack_indexing_concept.cpp b/clang/test/ExtractAPI/pack_indexing_concept.cpp
index 38443929937f9..b0575fc908275 100644
--- a/clang/test/ExtractAPI/pack_indexing_concept.cpp
+++ b/clang/test/ExtractAPI/pack_indexing_concept.cpp
@@ -1,7 +1,6 @@
 // RUN: rm -rf %t
 // RUN: %clang_cc1 -std=c++2d -extract-api --pretty-sgf --emit-sgf-symbol-labels-for-testing \
-// RUN:   --product-name=PackIndexing -triple arm64-apple-macosx -x c++-header %s \
-// RUN:   -o %t/pack-indexing.symbols.json -verify
+// RUN:  -triple arm64-apple-macosx -x c++-header %s -o %t/pack-indexing.symbols.json -verify
 
 // RUN: FileCheck %s --input-file %t/pack-indexing.symbols.json --check-prefix FUNCTION
 
diff --git a/clang/test/Lexer/cxx-features.cpp b/clang/test/Lexer/cxx-features.cpp
index 9845bea38d25b..b505f18dff14f 100644
--- a/clang/test/Lexer/cxx-features.cpp
+++ b/clang/test/Lexer/cxx-features.cpp
@@ -46,7 +46,7 @@
 #error "wrong value for __cpp_deleted_function"
 #endif
 
-#if check(pack_indexing, 202606, 202606, 202606, 202606, 202606, 202606, 202606)
+#if check(pack_indexing, 202311, 202311, 202311, 202311, 202311, 202311, 202311)
 #error "wrong value for __cpp_pack_indexing"
 #endif
 
diff --git a/clang/test/SemaCXX/cxx2d-pack-indexing-template.cpp b/clang/test/SemaCXX/cxx2d-pack-indexing-template.cpp
index 38be1ae5c7894..396fa66f7693c 100644
--- a/clang/test/SemaCXX/cxx2d-pack-indexing-template.cpp
+++ b/clang/test/SemaCXX/cxx2d-pack-indexing-template.cpp
@@ -1,4 +1,5 @@
-// RUN: %clang_cc1 -std=c++2d -verify -fsyntax-only %s
+// RUN: %clang_cc1 -std=c++2c -verify -fsyntax-only -triple x86_64-unknown-linux-gnu -Wno-c++2d-extensions %s
+// RUN: %clang_cc1 -std=c++2d -verify -fsyntax-only -triple x86_64-unknown-linux-gnu %s
 
 template <class T> struct A { using type = T; };
 template <class T> struct B { using type = T *; };
@@ -260,6 +261,22 @@ static_assert(!GH218548<Never, Always>::f<char>());
 static_assert(!GH218548<Never, Always>::f<char, short>());
 }
 
+template <template <typename> concept... C>
+void multiple_expansions() requires (C...[C<int>]<long long> && ...); // #multiple_expansions1
+
+template <template <typename> concept... C>
+void multiple_expansions_ok() requires (C...[C<long long>]<long long> || ...);
+template<typename T> concept X = sizeof(T) == 4; // #multiple_expansions2
+template<typename T> concept Y = sizeof(T) >= 4;
+void multiple_expansions_test() {
+  multiple_expansions<Y, X>();
+// expected-error at -1 {{no matching function for call to 'multiple_expansions'}}
+// expected-note@#multiple_expansions1 {{candidate template ignored: constraints not satisfied [with C = <Y, X>]}}
+// expected-note@#multiple_expansions1 {{because 'long long' does not satisfy 'X'}}
+// expected-note@#multiple_expansions2 {{because 'sizeof(long long) == 4' (8 == 4) evaluated to false}}
+  multiple_expansions_ok<Y, X>();
+}
+
 namespace kind_errors {
 template <class T> constexpr int Var = 1;
 template <class T> constexpr int Var2 = 2;

>From a29ebdacf92d7ad0844000a6d41c3c478fe3d7b4 Mon Sep 17 00:00:00 2001
From: Corentin Jabot <corentinjabot at gmail.com>
Date: Wed, 26 Aug 2026 14:32:28 +0200
Subject: [PATCH 3/4] address feedback

---
 clang/include/clang/AST/ExprCXX.h             |  4 ++-
 clang/include/clang/AST/TemplateBase.h        | 11 --------
 clang/include/clang/AST/TemplateName.h        | 28 ++++++++++++++++---
 clang/lib/AST/TemplateName.cpp                | 19 ++++++-------
 clang/lib/Parse/ParseDeclCXX.cpp              |  2 +-
 clang/lib/Parse/ParseExpr.cpp                 |  3 +-
 clang/lib/Sema/SemaTemplateVariadic.cpp       |  3 +-
 .../SemaCXX/cxx2d-pack-indexing-template.cpp  | 13 +++++++++
 8 files changed, 53 insertions(+), 30 deletions(-)

diff --git a/clang/include/clang/AST/ExprCXX.h b/clang/include/clang/AST/ExprCXX.h
index 7a2022adf09e4..1393da4da2721 100644
--- a/clang/include/clang/AST/ExprCXX.h
+++ b/clang/include/clang/AST/ExprCXX.h
@@ -3508,7 +3508,9 @@ class DependentTemplateIdExpr final
 
   TemplateTemplateParmDecl *getParameter() const {
     TemplateTemplateParmDecl *P = Name.getAsTemplateTemplateParmDecl();
-    assert(P && "A dependent template-id always names a template parameter");
+    assert(
+        P &&
+        "A dependent template-id always names a template template parameter");
     return P;
   }
 
diff --git a/clang/include/clang/AST/TemplateBase.h b/clang/include/clang/AST/TemplateBase.h
index 4d1e8beeea18d..5896cc721668d 100644
--- a/clang/include/clang/AST/TemplateBase.h
+++ b/clang/include/clang/AST/TemplateBase.h
@@ -35,17 +35,6 @@ namespace llvm {
 
 class FoldingSetNodeID;
 
-// Provide PointerLikeTypeTraits for clang::Expr*, this default one requires a
-// full definition of Expr, but this file only sees a forward del because of
-// the dependency.
-template <> struct PointerLikeTypeTraits<clang::Expr *> {
-  static inline void *getAsVoidPointer(clang::Expr *P) { return P; }
-  static inline clang::Expr *getFromVoidPointer(void *P) {
-    return static_cast<clang::Expr *>(P);
-  }
-  static constexpr int NumLowBitsAvailable = 2;
-};
-
 } // namespace llvm
 
 namespace clang {
diff --git a/clang/include/clang/AST/TemplateName.h b/clang/include/clang/AST/TemplateName.h
index c1d790e6a9b26..2ac87bd3cabaa 100644
--- a/clang/include/clang/AST/TemplateName.h
+++ b/clang/include/clang/AST/TemplateName.h
@@ -49,6 +49,25 @@ class TemplateDecl;
 class TemplateTemplateParmDecl;
 class UsingShadowDecl;
 
+} // namespace clang
+
+namespace llvm {
+
+// Provide PointerLikeTypeTraits for clang::Expr*, this default one requires a
+// full definition of Expr, but this file only sees a forward del because of
+// the dependency.
+template <> struct PointerLikeTypeTraits<clang::Expr *> {
+  static inline void *getAsVoidPointer(clang::Expr *P) { return P; }
+  static inline clang::Expr *getFromVoidPointer(void *P) {
+    return static_cast<clang::Expr *>(P);
+  }
+  static constexpr int NumLowBitsAvailable = 2;
+};
+
+} // namespace llvm
+
+namespace clang {
+
 /// Implementation class used to describe either a set of overloaded
 /// template names or an already-substituted template template parameter pack.
 class UncommonTemplateNameStorage {
@@ -520,8 +539,7 @@ class PackIndexingTemplateStorage final
   friend TrailingObjects;
 
   TemplateName Pattern;
-  Expr *IndexExpr;
-  bool FullySubstituted;
+  llvm::PointerIntPair<Expr *, 1> IndexAndIsFullySubstitited;
 
   PackIndexingTemplateStorage(TemplateName Pattern, Expr *IndexExpr,
                               bool FullySubstituted,
@@ -530,9 +548,11 @@ class PackIndexingTemplateStorage final
 public:
   TemplateName getPattern() const { return Pattern; }
 
-  Expr *getIndexExpr() const { return IndexExpr; }
+  Expr *getIndexExpr() const { return IndexAndIsFullySubstitited.getPointer(); }
 
-  bool isFullySubstituted() const { return FullySubstituted; }
+  bool isFullySubstituted() const {
+    return IndexAndIsFullySubstitited.getInt();
+  }
 
   ArrayRef<TemplateName> getExpansions() const {
     return getTrailingObjects(Bits.Data);
diff --git a/clang/lib/AST/TemplateName.cpp b/clang/lib/AST/TemplateName.cpp
index 12c342652c851..4cf58e66a082f 100644
--- a/clang/lib/AST/TemplateName.cpp
+++ b/clang/lib/AST/TemplateName.cpp
@@ -125,8 +125,8 @@ PackIndexingTemplateStorage::PackIndexingTemplateStorage(
     ArrayRef<TemplateName> Expansions)
     : UncommonTemplateNameStorage(PackIndexing, /*Index=*/0,
                                   /*Data=*/Expansions.size()),
-      Pattern(Pattern), IndexExpr(IndexExpr),
-      FullySubstituted(FullySubstituted) {
+      Pattern(Pattern),
+      IndexAndIsFullySubstitited(IndexExpr, FullySubstituted) {
   llvm::uninitialized_copy(Expansions, getTrailingObjects());
 }
 
@@ -140,9 +140,9 @@ PackIndexingTemplateStorage::getParameterPack() const {
 }
 
 UnsignedOrNone PackIndexingTemplateStorage::getSelectedIndex() const {
-  if (IndexExpr->isInstantiationDependent())
+  if (getIndexExpr()->isInstantiationDependent())
     return std::nullopt;
-  auto *CE = dyn_cast<ConstantExpr>(IndexExpr);
+  auto *CE = dyn_cast<ConstantExpr>(getIndexExpr());
   if (!CE)
     return std::nullopt;
   llvm::APSInt Index = CE->getResultAsAPSInt();
@@ -151,21 +151,20 @@ UnsignedOrNone PackIndexingTemplateStorage::getSelectedIndex() const {
 }
 
 TemplateName PackIndexingTemplateStorage::getSelectedTemplate() const {
-  if (!isFullySubstituted())
+  if (!isFullySubstituted() || getIndexExpr()->isInstantiationDependent())
     return TemplateName();
   UnsignedOrNone Index = getSelectedIndex();
   ArrayRef<TemplateName> Expansions = getExpansions();
-  if (!Index || *Index >= Expansions.size())
-    return TemplateName();
+  assert(Index && *Index < Expansions.size());
   return Expansions[*Index];
 }
 
 TemplateNameDependence PackIndexingTemplateStorage::getDependence() const {
   TemplateNameDependence IndexD =
-      toTemplateNameDependence(IndexExpr->getDependence());
+      toTemplateNameDependence(getIndexExpr()->getDependence());
 
   TemplateNameDependence D =
-      IndexD | (IndexExpr->isInstantiationDependent()
+      IndexD | (getIndexExpr()->isInstantiationDependent()
                     ? TemplateNameDependence::DependentInstantiation
                     : TemplateNameDependence::None);
   if (ArrayRef<TemplateName> Expansions = getExpansions(); Expansions.empty())
@@ -192,7 +191,7 @@ TemplateNameDependence PackIndexingTemplateStorage::getDependence() const {
 
 void PackIndexingTemplateStorage::Profile(llvm::FoldingSetNodeID &ID,
                                           const ASTContext &Context) const {
-  Profile(ID, Context, Pattern, IndexExpr, isFullySubstituted(),
+  Profile(ID, Context, Pattern, getIndexExpr(), isFullySubstituted(),
           getExpansions());
 }
 
diff --git a/clang/lib/Parse/ParseDeclCXX.cpp b/clang/lib/Parse/ParseDeclCXX.cpp
index 468fa8cbadd75..d701cbaa43bdd 100644
--- a/clang/lib/Parse/ParseDeclCXX.cpp
+++ b/clang/lib/Parse/ParseDeclCXX.cpp
@@ -1246,7 +1246,7 @@ bool Parser::AnnotatePackIndexingTemplateName(CXXScopeSpec &SS,
   if (T.consumeOpen())
     return true;
   ExprResult IndexExpr = ParseConstantExpression();
-  if (T.consumeClose() || IndexExpr.isInvalid())
+  if (T.consumeClose() || !IndexExpr.isUsable() || Template.get().isNull())
     return true;
 
   TemplateName Indexed = Actions.ActOnPackIndexingTemplateName(
diff --git a/clang/lib/Parse/ParseExpr.cpp b/clang/lib/Parse/ParseExpr.cpp
index 524f1889e112c..8f212fab4cdbf 100644
--- a/clang/lib/Parse/ParseExpr.cpp
+++ b/clang/lib/Parse/ParseExpr.cpp
@@ -904,7 +904,8 @@ Parser::ParseCastExpression(CastParseKind ParseKind, bool isAddressOfOperand,
         // indexing
         if (TryAnnotateTypeOrScopeToken())
           return ExprError();
-        if (Tok.isAnnotation())
+        if (Tok.isOneOf(tok::annot_cxxscope, tok::annot_pack_indexing_type,
+                        tok::annot_template_id))
           return ParseCastExpression(ParseKind, isAddressOfOperand,
                                      CorrectionBehavior, isVectorLiteral,
                                      NotPrimaryExpression);
diff --git a/clang/lib/Sema/SemaTemplateVariadic.cpp b/clang/lib/Sema/SemaTemplateVariadic.cpp
index 83a81f2df69cf..fb03591f70583 100644
--- a/clang/lib/Sema/SemaTemplateVariadic.cpp
+++ b/clang/lib/Sema/SemaTemplateVariadic.cpp
@@ -1401,8 +1401,7 @@ ExprResult Sema::BuildPackIndexingExpr(Expr *PackExpression,
 TemplateName Sema::ActOnPackIndexingTemplateName(TemplateName Pattern,
                                                  SourceLocation NameLoc,
                                                  Expr *IndexExpr) {
-  if (Pattern.isNull() || !IndexExpr)
-    return TemplateName();
+  assert(!Pattern.isNull() && IndexExpr);
 
   // C++29 [temp.names]p3:
   //   The simple-template-name P in a pack-index-template-name shall denote a
diff --git a/clang/test/SemaCXX/cxx2d-pack-indexing-template.cpp b/clang/test/SemaCXX/cxx2d-pack-indexing-template.cpp
index 396fa66f7693c..91253f86b8024 100644
--- a/clang/test/SemaCXX/cxx2d-pack-indexing-template.cpp
+++ b/clang/test/SemaCXX/cxx2d-pack-indexing-template.cpp
@@ -152,6 +152,19 @@ struct Nested {
 static_assert(__is_same(Nested<A>::apply<int, long>, List<A<int>, A<long>>));
 }
 
+namespace partial_substitution2 {
+template <template <typename> typename... TT>
+struct S {
+    using A = TT...[1]<int>;
+};
+
+template <typename>
+struct T;
+
+template <template <typename> typename... TT>
+using X = S<T, TT...>;
+}
+
 namespace empty_pack {
 template <template <class> class... TT>
 using U = TT...[0]<int>; // expected-error {{invalid index 0 for pack 'TT' of size 0}}

>From 228752fa54c5a3e7fea22f39691ac0c625675694 Mon Sep 17 00:00:00 2001
From: Corentin Jabot <corentinjabot at gmail.com>
Date: Wed, 26 Aug 2026 15:13:01 +0200
Subject: [PATCH 4/4] fix windows test

---
 clang/test/AST/ast-dump-pack-indexing-template.cpp | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/clang/test/AST/ast-dump-pack-indexing-template.cpp b/clang/test/AST/ast-dump-pack-indexing-template.cpp
index 4fcbd90d16862..c9cef1020c6b1 100644
--- a/clang/test/AST/ast-dump-pack-indexing-template.cpp
+++ b/clang/test/AST/ast-dump-pack-indexing-template.cpp
@@ -14,7 +14,7 @@ struct DumpDependent {
 // CHECK-NEXT:   |-name: 'TT...[1]':'template-parameter-0-0...[1]' pack_indexing index 1
 // CHECK-NEXT:   | |-pattern: 'TT':'template-parameter-0-0'
 // CHECK-NEXT:   | | `-TemplateTemplateParmDecl {{.*}} depth 0 index 0 ... TT
-// CHECK-NEXT:   | `-index: ConstantExpr {{.*}} '__size_t':'unsigned long'
+// CHECK-NEXT:   | `-index: ConstantExpr {{.*}} '__size_t':'{{.*}}'
 // CHECK-NEXT:   |   `-value: Int 1
 // CHECK-NEXT:   `-TemplateArgument type 'int'
 
@@ -36,7 +36,7 @@ constexpr bool DumpConceptId = CC...[0]<int>;
 // CHECK-NEXT: `-name: 'CC...[0]':'template-parameter-0-0...[0]' pack_indexing index 0
 // CHECK-NEXT:   |-pattern: 'CC':'template-parameter-0-0'
 // CHECK-NEXT:   | `-TemplateTemplateParmDecl {{.*}} depth 0 index 0 ... CC
-// CHECK-NEXT:   `-index: ConstantExpr {{.*}} '__size_t':'unsigned long'
+// CHECK-NEXT:   `-index: ConstantExpr {{.*}} '__size_t':'{{.*}}'
 // CHECK-NEXT:     `-value: Int 0
 
 template <template <class> auto... VV>



More information about the cfe-commits mailing list