[clang] [Clang] Fix pack size handling when building alias deduction guides (PR #227964)

Akash Manna via cfe-commits cfe-commits at lists.llvm.org
Fri Oct 2 23:39:59 PDT 2026


https://github.com/akash-manna-sky updated https://github.com/llvm/llvm-project/pull/227964

>From cf02221a4f9e01c48fe6a9c9c2c4d818667c6de2 Mon Sep 17 00:00:00 2001
From: Akash Manna <akash.manna.mymail at gmail.com>
Date: Thu, 1 Oct 2026 11:28:03 +0530
Subject: [PATCH] [Clang] Fix pack size handling when building alias deduction
 guides

A pack that is rewritten to another pack is a one-element argument pack
holding an expansion of unknown length. Treat its size as unknown instead
of 1, expand constructor packs declared over a class pack of known size,
and refer to the rewritten pack directly when it is substituted without
being expanded.

Fixes #193217
---
 clang/docs/ReleaseNotes.md                    |  14 ++
 clang/include/clang/AST/TemplateBase.h        |   4 +
 clang/include/clang/Sema/Sema.h               |   9 ++
 clang/lib/AST/TemplateBase.cpp                |  26 +++
 clang/lib/Sema/SemaTemplateDeduction.cpp      |   5 +-
 clang/lib/Sema/SemaTemplateDeductionGuide.cpp | 149 +++++++++++++++--
 clang/lib/Sema/SemaTemplateInstantiate.cpp    | 111 ++++++++-----
 .../lib/Sema/SemaTemplateInstantiateDecl.cpp  |  20 +--
 clang/lib/Sema/SemaTemplateVariadic.cpp       | 152 +++++++++++-------
 clang/test/SemaCXX/ctad.cpp                   |  10 ++
 clang/test/SemaCXX/cxx20-ctad-type-alias.cpp  |  54 +++++++
 clang/test/SemaTemplate/pack-deduction.cpp    |   8 +
 12 files changed, 436 insertions(+), 126 deletions(-)

diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index 6afaa6675be7cd..245decca6d56ee 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -672,6 +672,20 @@ features cannot lower the translation-unit ABI level;
 - Fixed concept evaluation bugs where some declarations were not added to
   the current instantiation scope. (#GH198052)
 
+- Fixed crashes when building the deduction guides of an alias template from a
+  constructor whose own template parameter packs are declared over, or expanded
+  together with, a parameter pack of the class template that the alias template
+  specifies. (#GH193217)
+
+- Fixed an assertion failure when an empty pack is deduced for a template
+  parameter pack that was itself expanded from another pack, such as `Ns` in
+  `template <Ts... Ns>` in a member template of a specialization whose `Ts` is
+  empty.
+
+- Fixed a rejected-valid case in CTAD from a constructor template with a
+  parameter such as `tuple<pair<Ts, Us>...>`, whose nested pack expansion
+  names packs of both the class template and the constructor template.
+
 - Fixed a crash when a lambda parameter pack was given a default argument that
   is a pack expansion referencing an enclosing function's parameter pack (e.g.
   `[](Types... = args...) {}`). Clang now diagnoses the illegal default
diff --git a/clang/include/clang/AST/TemplateBase.h b/clang/include/clang/AST/TemplateBase.h
index f161f6561e742f..24e8b32f678b43 100644
--- a/clang/include/clang/AST/TemplateBase.h
+++ b/clang/include/clang/AST/TemplateBase.h
@@ -453,6 +453,10 @@ class TemplateArgument {
   /// the pattern of the pack expansion.
   TemplateArgument getPackExpansionPattern() const;
 
+  /// When the template argument is a pack expansion, returns the number of
+  /// expansions that it will produce, if known.
+  UnsignedOrNone getNumExpansions() const;
+
   /// Print this template argument to the given output stream.
   void print(const PrintingPolicy &Policy, raw_ostream &Out,
              bool IncludeType) const;
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 85ebe0cfdba1e8..badbb02bdd073e 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -14752,6 +14752,15 @@ class Sema final : public SemaBase {
   void collectUnexpandedParameterPacks(
       TypeLoc TL, SmallVectorImpl<UnexpandedParameterPack> &Unexpanded);
 
+  /// Collect the set of unexpanded parameter packs within the given
+  /// template parameter list.
+  ///
+  /// \param Params The template parameter list that will be traversed to find
+  /// unexpanded parameter packs.
+  void collectUnexpandedParameterPacks(
+      TemplateParameterList *Params,
+      SmallVectorImpl<UnexpandedParameterPack> &Unexpanded);
+
   /// Collect the set of unexpanded parameter packs within the given
   /// nested-name-specifier.
   ///
diff --git a/clang/lib/AST/TemplateBase.cpp b/clang/lib/AST/TemplateBase.cpp
index 9356bbadc7a48c..14dde2b9cb6d4d 100644
--- a/clang/lib/AST/TemplateBase.cpp
+++ b/clang/lib/AST/TemplateBase.cpp
@@ -536,6 +536,32 @@ TemplateArgument TemplateArgument::getPackExpansionPattern() const {
   llvm_unreachable("Invalid TemplateArgument Kind!");
 }
 
+UnsignedOrNone TemplateArgument::getNumExpansions() const {
+  assert(isPackExpansion());
+
+  switch (getKind()) {
+  case Type:
+    return getAsType()->castAs<PackExpansionType>()->getNumExpansions();
+
+  case Expression:
+    return cast<PackExpansionExpr>(getAsExpr())->getNumExpansions();
+
+  case TemplateExpansion:
+    return getNumTemplateExpansions();
+
+  case Declaration:
+  case Integral:
+  case StructuralValue:
+  case Pack:
+  case Null:
+  case Template:
+  case NullPtr:
+    return std::nullopt;
+  }
+
+  llvm_unreachable("Invalid TemplateArgument Kind!");
+}
+
 void TemplateArgument::print(const PrintingPolicy &Policy, raw_ostream &Out,
                              bool IncludeType) const {
 
diff --git a/clang/lib/Sema/SemaTemplateDeduction.cpp b/clang/lib/Sema/SemaTemplateDeduction.cpp
index 2257964a4310bb..b5724cdbc386d5 100644
--- a/clang/lib/Sema/SemaTemplateDeduction.cpp
+++ b/clang/lib/Sema/SemaTemplateDeduction.cpp
@@ -2999,8 +2999,9 @@ ConvertDeducedTemplateArgument(Sema &S, NamedDecl *Param,
     }
 
     // If the pack is empty, we still need to substitute into the parameter
-    // itself, in case that substitution fails.
-    if (SugaredPackedArgsBuilder.empty()) {
+    // itself, in case that substitution fails. An expanded parameter pack was
+    // already checked when it was expanded.
+    if (SugaredPackedArgsBuilder.empty() && !getExpandedPackSize(Param)) {
       LocalInstantiationScope Scope(S);
       MultiLevelTemplateArgumentList Args(Template, CTAI.SugaredConverted,
                                           /*Final=*/true);
diff --git a/clang/lib/Sema/SemaTemplateDeductionGuide.cpp b/clang/lib/Sema/SemaTemplateDeductionGuide.cpp
index fa0918ae30282b..a3241aeed5298f 100644
--- a/clang/lib/Sema/SemaTemplateDeductionGuide.cpp
+++ b/clang/lib/Sema/SemaTemplateDeductionGuide.cpp
@@ -337,16 +337,44 @@ transformTemplateParam(Sema &SemaRef, DeclContext *DC,
   return NewTTP;
 }
 
+// Whether all of the packs in `Unexpanded` have arguments of known size in
+// `Args`, as opposed to being rewritten to refer to another parameter pack.
+bool packsHaveKnownSize(ArrayRef<UnexpandedParameterPack> Unexpanded,
+                        const MultiLevelTemplateArgumentList &Args) {
+  if (Unexpanded.empty())
+    return false;
+  for (UnexpandedParameterPack UPP : Unexpanded) {
+    if (isa_and_present<VarDecl>(dyn_cast<NamedDecl *>(UPP.first)))
+      return false;
+    std::optional<std::pair<unsigned, unsigned>> DepthAndIndex =
+        getDepthAndIndex(UPP);
+    if (!DepthAndIndex)
+      return false;
+    auto [Depth, Index] = *DepthAndIndex;
+    if (Depth >= Args.getNumLevels() || !Args.hasTemplateArgument(Depth, Index))
+      return false;
+    const TemplateArgument &Arg = Args(Depth, Index);
+    if (Arg.getKind() != TemplateArgument::Pack ||
+        llvm::any_of(Arg.pack_elements(), [](const TemplateArgument &TA) {
+          return TA.isPackExpansion() && !TA.getNumExpansions();
+        }))
+      return false;
+  }
+  return true;
+}
+
 NonTypeTemplateParmDecl *
 transformTemplateParam(Sema &SemaRef, DeclContext *DC,
                        NonTypeTemplateParmDecl *TTP, unsigned NewDepth,
                        unsigned NewIndex,
                        MultiLevelTemplateArgumentList &Args) {
-  NonTypeTemplateParmDecl *NewTTP;
+  TypeSourceInfo *TSI = TTP->getTypeSourceInfo();
+  SmallVector<TypeSourceInfo *, 4> ExpandedTypeSourceInfos;
+  SmallVector<QualType, 4> ExpandedTypes;
+  bool IsExpandedParameterPack = false;
   if (TTP->isExpandedParameterPack()) {
-    SmallVector<TypeSourceInfo *, 4> ExpandedTypeSourceInfos(
-        TTP->getNumExpansionTypes());
-    SmallVector<QualType, 4> ExpandedTypes(TTP->getNumExpansionTypes());
+    ExpandedTypeSourceInfos.resize(TTP->getNumExpansionTypes());
+    ExpandedTypes.resize(TTP->getNumExpansionTypes());
     for (unsigned I = 0, N = TTP->getNumExpansionTypes(); I != N; ++I) {
       TypeSourceInfo *NewTSI =
           SemaRef.SubstType(TTP->getExpansionTypeSourceInfo(I), Args,
@@ -360,13 +388,55 @@ transformTemplateParam(Sema &SemaRef, DeclContext *DC,
       ExpandedTypeSourceInfos[I] = NewTSI;
       ExpandedTypes[I] = NewT;
     }
+    IsExpandedParameterPack = true;
+  } else if (TTP->isPackExpansion()) {
+    // If the packs named by the parameter's type have arguments of known size,
+    // expand the parameter as template instantiation would.
+    PackExpansionTypeLoc ExpansionTL =
+        TSI->getTypeLoc().castAs<PackExpansionTypeLoc>();
+    TypeLoc Pattern = ExpansionTL.getPatternLoc();
+    SmallVector<UnexpandedParameterPack, 2> Unexpanded;
+    SemaRef.collectUnexpandedParameterPacks(Pattern, Unexpanded);
+    if (packsHaveKnownSize(Unexpanded, Args)) {
+      bool Expand = true;
+      bool RetainExpansion = false;
+      UnsignedOrNone NumExpansions =
+          ExpansionTL.getTypePtr()->getNumExpansions();
+      if (SemaRef.CheckParameterPacksForExpansion(
+              ExpansionTL.getEllipsisLoc(), Pattern.getSourceRange(),
+              Unexpanded, Args, /*FailOnPackProducingTemplates=*/true, Expand,
+              RetainExpansion, NumExpansions))
+        return nullptr;
+      if (Expand) {
+        for (unsigned I = 0; I != *NumExpansions; ++I) {
+          Sema::ArgPackSubstIndexRAII SubstIndex(SemaRef, I);
+          TypeSourceInfo *NewTSI = SemaRef.SubstType(
+              Pattern, Args, TTP->getLocation(), TTP->getDeclName());
+          if (!NewTSI)
+            return nullptr;
+
+          QualType NewT = SemaRef.CheckNonTypeTemplateParameterType(
+              NewTSI, TTP->getLocation());
+          if (NewT.isNull())
+            return nullptr;
+
+          ExpandedTypeSourceInfos.push_back(NewTSI);
+          ExpandedTypes.push_back(NewT);
+        }
+        IsExpandedParameterPack = true;
+      }
+    }
+  }
+
+  NonTypeTemplateParmDecl *NewTTP;
+  if (IsExpandedParameterPack) {
     NewTTP = NonTypeTemplateParmDecl::Create(
         SemaRef.Context, DC, TTP->getBeginLoc(), TTP->getLocation(), NewDepth,
-        NewIndex, TTP->getIdentifier(), TTP->getType(),
-        TTP->getTypeSourceInfo(), ExpandedTypes, ExpandedTypeSourceInfos);
+        NewIndex, TTP->getIdentifier(), TTP->getType(), TSI, ExpandedTypes,
+        ExpandedTypeSourceInfos);
   } else {
-    TypeSourceInfo *NewTSI = SemaRef.SubstType(
-        TTP->getTypeSourceInfo(), Args, TTP->getLocation(), TTP->getDeclName());
+    TypeSourceInfo *NewTSI =
+        SemaRef.SubstType(TSI, Args, TTP->getLocation(), TTP->getDeclName());
     assert(NewTSI);
 
     QualType NewT =
@@ -378,11 +448,10 @@ transformTemplateParam(Sema &SemaRef, DeclContext *DC,
         NewIndex, TTP->getIdentifier(), NewT, TTP->isParameterPack(), NewTSI);
   }
 
-  if (TypeSourceInfo *TSI = TTP->getTypeSourceInfo();
-      AutoTypeLoc AutoLoc = TSI->getTypeLoc().getContainedAutoTypeLoc()) {
+  if (AutoTypeLoc AutoLoc = TSI->getTypeLoc().getContainedAutoTypeLoc()) {
     if (AutoLoc.isConstrained()) {
       SourceLocation EllipsisLoc;
-      if (TTP->isExpandedParameterPack())
+      if (IsExpandedParameterPack)
         EllipsisLoc =
             TSI->getTypeLoc().getAs<PackExpansionTypeLoc>().getEllipsisLoc();
       else if (auto *Constraint = dyn_cast_if_present<CXXFoldExpr>(
@@ -424,15 +493,53 @@ transformTemplateParam(Sema &SemaRef, DeclContext *DC,
                        TemplateTemplateParmDecl *TTP, unsigned NewDepth,
                        unsigned NewIndex, MultiLevelTemplateArgumentList &Args,
                        bool EvaluateConstraint) {
-  TemplateTemplateParmDecl *NewTTP;
+  SmallVector<TemplateParameterList *, 4> ExpandedTPLs;
+  bool IsExpandedParameterPack = false;
   if (TTP->isExpandedParameterPack()) {
-    SmallVector<TemplateParameterList *, 4> ExpandedTPLs(
-        TTP->getNumExpansionTemplateParameters());
+    ExpandedTPLs.resize(TTP->getNumExpansionTemplateParameters());
     for (unsigned I = 0, N = TTP->getNumExpansionTemplateParameters(); I != N;
-         ++I)
+         ++I) {
       ExpandedTPLs[I] = transformTemplateParameters(
           SemaRef, DC, TTP->getExpansionTemplateParameters(I), Args,
           NewDepth + 1, EvaluateConstraint);
+      if (!ExpandedTPLs[I])
+        return nullptr;
+    }
+    IsExpandedParameterPack = true;
+  } else if (TTP->isPackExpansion()) {
+    SmallVector<UnexpandedParameterPack, 2> Unexpanded;
+    SemaRef.collectUnexpandedParameterPacks(TTP->getTemplateParameters(),
+                                            Unexpanded);
+    if (packsHaveKnownSize(Unexpanded, Args)) {
+      bool Expand = true;
+      bool RetainExpansion = false;
+      UnsignedOrNone NumExpansions = std::nullopt;
+      if (SemaRef.CheckParameterPacksForExpansion(
+              TTP->getLocation(),
+              TTP->getTemplateParameters()->getSourceRange(), Unexpanded, Args,
+              /*FailOnPackProducingTemplates=*/true, Expand, RetainExpansion,
+              NumExpansions))
+        return nullptr;
+      if (Expand) {
+        for (unsigned I = 0; I != *NumExpansions; ++I) {
+          Sema::ArgPackSubstIndexRAII SubstIndex(SemaRef, I);
+          // The same template parameters are transformed for each expansion.
+          LocalInstantiationScope Scope(SemaRef,
+                                        /*CombineWithOuterScope=*/true);
+          TemplateParameterList *Expansion = transformTemplateParameters(
+              SemaRef, DC, TTP->getTemplateParameters(), Args, NewDepth + 1,
+              EvaluateConstraint);
+          if (!Expansion)
+            return nullptr;
+          ExpandedTPLs.push_back(Expansion);
+        }
+        IsExpandedParameterPack = true;
+      }
+    }
+  }
+
+  TemplateTemplateParmDecl *NewTTP;
+  if (IsExpandedParameterPack) {
     NewTTP = TemplateTemplateParmDecl::Create(
         SemaRef.Context, DC, TTP->getLocation(), NewDepth, NewIndex,
         TTP->getIdentifier(), TTP->templateParameterKind(),
@@ -442,6 +549,8 @@ transformTemplateParam(Sema &SemaRef, DeclContext *DC,
     TemplateParameterList *NewTPL =
         transformTemplateParameters(SemaRef, DC, TTP->getTemplateParameters(),
                                     Args, NewDepth + 1, EvaluateConstraint);
+    if (!NewTPL)
+      return nullptr;
     NewTTP = TemplateTemplateParmDecl::Create(
         SemaRef.Context, DC, TTP->getLocation(), NewDepth, NewIndex,
         TTP->isParameterPack(), TTP->getIdentifier(),
@@ -489,6 +598,8 @@ transformTemplateParameters(Sema &SemaRef, DeclContext *DC,
     Params[I] = transformTemplateParameter(SemaRef, DC, TPL->getParam(I), Args,
                                            /*NewIndex=*/I, NewDepth,
                                            EvaluateConstraint);
+    if (!Params[I])
+      return nullptr;
   }
   return TemplateParameterList::Create(
       SemaRef.Context, TPL->getTemplateLoc(), TPL->getLAngleLoc(), Params,
@@ -1028,6 +1139,8 @@ buildAssociatedConstraints(Sema &SemaRef, FunctionTemplateDecl *F,
     NamedDecl *NewParam = transformTemplateParameter(
         SemaRef, AliasTemplate->getDeclContext(), TP, Args,
         /*NewIndex=*/N++, getDepthAndIndex(TP).first + AdjustDepth);
+    if (!NewParam)
+      return nullptr;
 
     TemplateArgument NewTemplateArgument =
         Context.getInjectedTemplateArg(NewParam);
@@ -1054,6 +1167,8 @@ buildAssociatedConstraints(Sema &SemaRef, FunctionTemplateDecl *F,
       NamedDecl *NewParam = transformTemplateParameter(
           SemaRef, F->getDeclContext(), TP, Args,
           /*NewIndex=*/N++, getDepthAndIndex(TP).first + AdjustDepth);
+      if (!NewParam)
+        return nullptr;
       assert(TemplateArgsForBuildingRC[Index].isNull());
       TemplateArgsForBuildingRC[Index] =
           Context.getInjectedTemplateArg(NewParam);
@@ -1352,6 +1467,8 @@ CXXDeductionGuideDecl *BuildDeductionGuideForTypeAlias(
     NamedDecl *NewParam = transformTemplateParameter(
         SemaRef, AliasTemplate->getDeclContext(), TP, Args,
         /*NewIndex=*/FPrimeTemplateParams.size(), getDepthAndIndex(TP).first);
+    if (!NewParam)
+      return nullptr;
     FPrimeTemplateParams.push_back(NewParam);
 
     TemplateArgument NewTemplateArgument =
@@ -1428,6 +1545,8 @@ CXXDeductionGuideDecl *BuildDeductionGuideForTypeAlias(
     NamedDecl *NewParam = transformTemplateParameter(
         SemaRef, F->getDeclContext(), TP, Args, FPrimeTemplateParams.size(),
         getDepthAndIndex(TP).first);
+    if (!NewParam)
+      return nullptr;
     FPrimeTemplateParams.push_back(NewParam);
 
     assert(TemplateArgsForBuildingFPrime[FTemplateParamIdx].isNull() &&
diff --git a/clang/lib/Sema/SemaTemplateInstantiate.cpp b/clang/lib/Sema/SemaTemplateInstantiate.cpp
index 06ea12ca7bd73b..cb503aed1743a2 100644
--- a/clang/lib/Sema/SemaTemplateInstantiate.cpp
+++ b/clang/lib/Sema/SemaTemplateInstantiate.cpp
@@ -2123,6 +2123,20 @@ VarDecl *TemplateInstantiator::RebuildObjCExceptionDecl(VarDecl *ExceptionDecl,
   return Var;
 }
 
+/// If the given argument pack is a single pack expansion of unknown length,
+/// returns its pattern. A reference to the parameter pack that isn't being
+/// expanded is substituted by that pattern, as the enclosing pack expansion
+/// expands the packs it names.
+static std::optional<TemplateArgument>
+getLoneUnexpandedPackExpansionPattern(const TemplateArgument &Pack) {
+  if (Pack.pack_size() != 1)
+    return std::nullopt;
+  const TemplateArgument &Arg = *Pack.pack_begin();
+  if (!Arg.isPackExpansion() || Arg.getNumExpansions())
+    return std::nullopt;
+  return Arg.getPackExpansionPattern();
+}
+
 TemplateName TemplateInstantiator::TransformTemplateName(
     NestedNameSpecifierLoc &QualifierLoc, SourceLocation TemplateKWLoc,
     TemplateName Name, SourceLocation NameLoc, QualType ObjectType,
@@ -2161,15 +2175,20 @@ TemplateName TemplateInstantiator::TransformTemplateName(
                "Missing argument pack");
 
         if (!getSema().ArgPackSubstIndex) {
-          // We have the template argument pack to substitute, but we're not
-          // actually expanding the enclosing pack expansion yet. So, just
-          // keep the entire argument pack.
-          return getSema().Context.getSubstTemplateTemplateParmPack(
-              Arg, AssociatedDecl, TTP->getIndex(), Final);
+          std::optional<TemplateArgument> Pattern =
+              getLoneUnexpandedPackExpansionPattern(Arg);
+          if (!Pattern) {
+            // We have the template argument pack to substitute, but we're not
+            // actually expanding the enclosing pack expansion yet. So, just
+            // keep the entire argument pack.
+            return getSema().Context.getSubstTemplateTemplateParmPack(
+                Arg, AssociatedDecl, TTP->getIndex(), Final);
+          }
+          Arg = *Pattern;
+        } else {
+          PackIndex = SemaRef.getPackIndex(Arg);
+          Arg = SemaRef.getPackSubstitutedTemplateArgument(Arg);
         }
-
-        PackIndex = SemaRef.getPackIndex(Arg);
-        Arg = SemaRef.getPackSubstitutedTemplateArgument(Arg);
       }
 
       TemplateName Template = Arg.getAsTemplate();
@@ -2234,11 +2253,28 @@ TemplateInstantiator::TransformTemplateParmRefExpr(DeclRefExpr *E,
     return Arg.getAsExpr();
   }
 
-  QualType ParamType = NTTP->isExpandedParameterPack()
-                           ? NTTP->getExpansionType(*SemaRef.ArgPackSubstIndex)
-                       : NTTP->isParameterPack() && SemaRef.ArgPackSubstIndex
-                           ? NTTP->getType().getNonPackExpansionType()
-                           : NTTP->getType();
+  bool IsParameterPack =
+      NTTP->isParameterPack() ||
+      // In concept parameter mapping for fold expressions, packs that aren't
+      // expanded in place are treated as having non-pack dependency, so that
+      // a PackExpansionType won't prevent expanding the packs outside the
+      // TreeTransform. However, we still need to unpack the arguments during
+      // any template argument substitution, so we also check its FoundDecl.
+      (E->getFoundDecl() && E->getFoundDecl() != E->getDecl() &&
+       E->getFoundDecl()->isParameterPack());
+
+  std::optional<TemplateArgument> PackPattern;
+  if (IsParameterPack && !SemaRef.ArgPackSubstIndex) {
+    assert(Arg.getKind() == TemplateArgument::Pack && "Missing argument pack");
+    PackPattern = getLoneUnexpandedPackExpansionPattern(Arg);
+  }
+
+  QualType ParamType =
+      NTTP->isExpandedParameterPack()
+          ? NTTP->getExpansionType(*SemaRef.ArgPackSubstIndex)
+      : NTTP->isParameterPack() && (SemaRef.ArgPackSubstIndex || PackPattern)
+          ? NTTP->getType().getNonPackExpansionType()
+          : NTTP->getType();
   ParamType = SemaRef.SubstType(ParamType, TemplateArgs, E->getLocation(),
                                 NTTP->getDeclName());
   assert(!ParamType.isNull() && "Shouldn't substitute to an invalid type");
@@ -2246,17 +2282,12 @@ TemplateInstantiator::TransformTemplateParmRefExpr(DeclRefExpr *E,
   auto [AssociatedDecl, Final] =
       TemplateArgs.getAssociatedDecl(NTTP->getDepth());
   UnsignedOrNone PackIndex = std::nullopt;
-  if (NTTP->isParameterPack() ||
-      // In concept parameter mapping for fold expressions, packs that aren't
-      // expanded in place are treated as having non-pack dependency, so that
-      // a PackExpansionType won't prevent expanding the packs outside the
-      // TreeTransform. However, we still need to unpack the arguments during
-      // any template argument substitution, so we also check its FoundDecl.
-      (E->getFoundDecl() && E->getFoundDecl() != E->getDecl() &&
-       E->getFoundDecl()->isParameterPack())) {
+  if (IsParameterPack) {
     assert(Arg.getKind() == TemplateArgument::Pack && "Missing argument pack");
 
-    if (!getSema().ArgPackSubstIndex) {
+    if (PackPattern) {
+      Arg = *PackPattern;
+    } else if (!getSema().ArgPackSubstIndex) {
       // We have an argument pack, but we can't select a particular argument
       // out of it yet. Therefore, we'll build an expression to hold on to that
       // argument pack.
@@ -2266,9 +2297,10 @@ TemplateInstantiator::TransformTemplateParmRefExpr(DeclRefExpr *E,
       return new (SemaRef.Context) SubstNonTypeTemplateParmPackExpr(
           ExprType, ParamType->isReferenceType() ? VK_LValue : VK_PRValue,
           E->getLocation(), Arg, AssociatedDecl, NTTP->getPosition(), Final);
+    } else {
+      PackIndex = SemaRef.getPackIndex(Arg);
+      Arg = SemaRef.getPackSubstitutedTemplateArgument(Arg);
     }
-    PackIndex = SemaRef.getPackIndex(Arg);
-    Arg = SemaRef.getPackSubstitutedTemplateArgument(Arg);
   }
   return SemaRef.BuildSubstNonTypeTemplateParmExpr(
       AssociatedDecl, NTTP->getPosition(), ParamType, E->getLocation(), Arg,
@@ -2592,20 +2624,25 @@ TemplateInstantiator::TransformTemplateTypeParmType(TypeLocBuilder &TLB,
              "Missing argument pack");
 
       if (!getSema().ArgPackSubstIndex) {
-        // We have the template argument pack, but we're not expanding the
-        // enclosing pack expansion yet. Just save the template argument
-        // pack for later substitution.
-        QualType Result = getSema().Context.getSubstTemplateTypeParmPackType(
-            AssociatedDecl, T->getIndex(), Final, Arg);
-        SubstTemplateTypeParmPackTypeLoc NewTL
-          = TLB.push<SubstTemplateTypeParmPackTypeLoc>(Result);
-        NewTL.setNameLoc(TL.getNameLoc());
-        return Result;
+        std::optional<TemplateArgument> Pattern =
+            getLoneUnexpandedPackExpansionPattern(Arg);
+        if (!Pattern) {
+          // We have the template argument pack, but we're not expanding the
+          // enclosing pack expansion yet. Just save the template argument
+          // pack for later substitution.
+          QualType Result = getSema().Context.getSubstTemplateTypeParmPackType(
+              AssociatedDecl, T->getIndex(), Final, Arg);
+          SubstTemplateTypeParmPackTypeLoc NewTL =
+              TLB.push<SubstTemplateTypeParmPackTypeLoc>(Result);
+          NewTL.setNameLoc(TL.getNameLoc());
+          return Result;
+        }
+        Arg = *Pattern;
+      } else {
+        // PackIndex starts from last element.
+        PackIndex = SemaRef.getPackIndex(Arg);
+        Arg = SemaRef.getPackSubstitutedTemplateArgument(Arg);
       }
-
-      // PackIndex starts from last element.
-      PackIndex = SemaRef.getPackIndex(Arg);
-      Arg = SemaRef.getPackSubstitutedTemplateArgument(Arg);
     }
 
     assert(Arg.getKind() == TemplateArgument::Type &&
diff --git a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
index 7e29de5be2e970..b6ca3051fcc031 100644
--- a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
+++ b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
@@ -4037,22 +4037,6 @@ Decl *TemplateDeclInstantiator::VisitNonTypeTemplateParmDecl(
   return Param;
 }
 
-static void collectUnexpandedParameterPacks(
-    Sema &S,
-    TemplateParameterList *Params,
-    SmallVectorImpl<UnexpandedParameterPack> &Unexpanded) {
-  for (const auto &P : *Params) {
-    if (P->isTemplateParameterPack())
-      continue;
-    if (NonTypeTemplateParmDecl *NTTP = dyn_cast<NonTypeTemplateParmDecl>(P))
-      S.collectUnexpandedParameterPacks(NTTP->getTypeSourceInfo()->getTypeLoc(),
-                                        Unexpanded);
-    if (TemplateTemplateParmDecl *TTP = dyn_cast<TemplateTemplateParmDecl>(P))
-      collectUnexpandedParameterPacks(S, TTP->getTemplateParameters(),
-                                      Unexpanded);
-  }
-}
-
 Decl *
 TemplateDeclInstantiator::VisitTemplateTemplateParmDecl(
                                                   TemplateTemplateParmDecl *D) {
@@ -4085,8 +4069,8 @@ TemplateDeclInstantiator::VisitTemplateTemplateParmDecl(
     // template parameters. Determine whether we need to expand this parameter
     // pack into separate parameters.
     SmallVector<UnexpandedParameterPack, 2> Unexpanded;
-    collectUnexpandedParameterPacks(SemaRef, D->getTemplateParameters(),
-                                    Unexpanded);
+    SemaRef.collectUnexpandedParameterPacks(D->getTemplateParameters(),
+                                            Unexpanded);
 
     // Determine whether the set of unexpanded parameter packs can and should
     // be expanded.
diff --git a/clang/lib/Sema/SemaTemplateVariadic.cpp b/clang/lib/Sema/SemaTemplateVariadic.cpp
index 2d6e75bd77b402..83d72a3f19a993 100644
--- a/clang/lib/Sema/SemaTemplateVariadic.cpp
+++ b/clang/lib/Sema/SemaTemplateVariadic.cpp
@@ -688,6 +688,20 @@ void Sema::collectUnexpandedParameterPacks(QualType T,
   CollectUnexpandedParameterPacksVisitor(Unexpanded).TraverseType(T);
 }
 
+void Sema::collectUnexpandedParameterPacks(
+    TemplateParameterList *Params,
+    SmallVectorImpl<UnexpandedParameterPack> &Unexpanded) {
+  for (NamedDecl *P : *Params) {
+    if (P->isTemplateParameterPack())
+      continue;
+    if (auto *NTTP = dyn_cast<NonTypeTemplateParmDecl>(P))
+      collectUnexpandedParameterPacks(NTTP->getTypeSourceInfo()->getTypeLoc(),
+                                      Unexpanded);
+    else if (auto *TTP = dyn_cast<TemplateTemplateParmDecl>(P))
+      collectUnexpandedParameterPacks(TTP->getTemplateParameters(), Unexpanded);
+  }
+}
+
 void Sema::collectUnexpandedParameterPacks(
     TemplateName Template,
     SmallVectorImpl<UnexpandedParameterPack> &Unexpanded) {
@@ -863,6 +877,10 @@ bool Sema::CheckParameterPacksForExpansion(
   RetainExpansion = false;
   IdentifierLoc FirstPack;
   bool HaveFirstPack = false;
+  UnsignedOrNone OrigNumExpansions = NumExpansions;
+  // Set if NumExpansions comes from a pack with pack expansions of unknown
+  // length: the least number of arguments that pack expands to.
+  UnsignedOrNone LeastTentativeNumExpansions = std::nullopt;
   UnsignedOrNone NumPartialExpansions = std::nullopt;
   SourceLocation PartiallySubstitutedPackLoc;
   typedef LocalInstantiationScope::DeclArgumentPack DeclArgumentPack;
@@ -960,19 +978,7 @@ bool Sema::CheckParameterPacksForExpansion(
       NewPackSize = Pack.size();
       PendingPackExpansionSize =
           llvm::count_if(Pack, [](const TemplateArgument &TA) {
-            if (!TA.isPackExpansion())
-              return false;
-
-            if (TA.getKind() == TemplateArgument::Type)
-              return !TA.getAsType()
-                          ->castAs<PackExpansionType>()
-                          ->getNumExpansions();
-
-            if (TA.getKind() == TemplateArgument::Expression)
-              return !cast<PackExpansionExpr>(TA.getAsExpr())
-                          ->getNumExpansions();
-
-            return !TA.getNumTemplateExpansions();
+            return TA.isPackExpansion() && !TA.getNumExpansions();
           });
     }
 
@@ -1001,6 +1007,8 @@ bool Sema::CheckParameterPacksForExpansion(
       NumExpansions = NewPackSize;
       FirstPack = IdentifierLoc(ParmPack.second, Name);
       HaveFirstPack = true;
+      if (PendingPackExpansionSize)
+        LeastTentativeNumExpansions = NewPackSize - PendingPackExpansionSize;
       continue;
     }
 
@@ -1029,6 +1037,19 @@ bool Sema::CheckParameterPacksForExpansion(
       unsigned LeastNewPackSize = NewPackSize - PendingPackExpansionSize;
       if (PendingPackExpansionSize && LeastNewPackSize <= *NumExpansions) {
         ShouldExpand = false;
+        if (LeastTentativeNumExpansions)
+          LeastTentativeNumExpansions =
+              std::max(*LeastTentativeNumExpansions, LeastNewPackSize);
+        continue;
+      }
+      // Likewise if the pack NumExpansions comes from may still match this one.
+      if (LeastTentativeNumExpansions &&
+          *LeastTentativeNumExpansions <= NewPackSize) {
+        ShouldExpand = false;
+        if (!PendingPackExpansionSize) {
+          NumExpansions = NewPackSize;
+          LeastTentativeNumExpansions = std::nullopt;
+        }
         continue;
       }
       // C++0x [temp.variadic]p5:
@@ -1036,19 +1057,32 @@ bool Sema::CheckParameterPacksForExpansion(
       //   the same number of arguments specified.
       if (!Diagnose)
         ;
-      else if (HaveFirstPack)
+      else if (!HaveFirstPack)
+        Diag(EllipsisLoc, diag::err_pack_expansion_length_conflict_multilevel)
+            << Name << *NumExpansions << (LeastNewPackSize != NewPackSize)
+            << LeastNewPackSize << SourceRange(ParmPack.second);
+      else if (LeastTentativeNumExpansions)
+        Diag(EllipsisLoc, diag::err_pack_expansion_length_conflict)
+            << Name << FirstPack.getIdentifierInfo() << NewPackSize
+            << (*LeastTentativeNumExpansions != *NumExpansions)
+            << *LeastTentativeNumExpansions << SourceRange(ParmPack.second)
+            << SourceRange(FirstPack.getLoc());
+      else
         Diag(EllipsisLoc, diag::err_pack_expansion_length_conflict)
             << FirstPack.getIdentifierInfo() << Name << *NumExpansions
             << (LeastNewPackSize != NewPackSize) << LeastNewPackSize
             << SourceRange(FirstPack.getLoc()) << SourceRange(ParmPack.second);
-      else
-        Diag(EllipsisLoc, diag::err_pack_expansion_length_conflict_multilevel)
-            << Name << *NumExpansions << (LeastNewPackSize != NewPackSize)
-            << LeastNewPackSize << SourceRange(ParmPack.second);
       return true;
     }
+
+    if (!PendingPackExpansionSize)
+      LeastTentativeNumExpansions = std::nullopt;
   }
 
+  // Only report a tentative NumExpansions when expanding.
+  if (!ShouldExpand && LeastTentativeNumExpansions)
+    NumExpansions = OrigNumExpansions;
+
   // If we're performing a partial expansion but we also have a full expansion,
   // expand to the number of common arguments. For example, given:
   //
@@ -1080,59 +1114,69 @@ UnsignedOrNone Sema::getNumArgumentsInExpansionFromUnexpanded(
     llvm::ArrayRef<UnexpandedParameterPack> Unexpanded,
     const MultiLevelTemplateArgumentList &TemplateArgs) {
   UnsignedOrNone Result = std::nullopt;
+  bool ResultIsDefinite = false;
   for (unsigned I = 0, N = Unexpanded.size(); I != N; ++I) {
-    // Compute the depth and index for this parameter pack.
-    unsigned Depth;
-    unsigned Index;
+    unsigned Size;
+    bool SizeIsDefinite = true;
 
-    if (const TemplateTypeParmType *TTP =
-            dyn_cast<const TemplateTypeParmType *>(Unexpanded[I].first)) {
-      Depth = TTP->getDepth();
-      Index = TTP->getIndex();
-    } else if (auto *TST = dyn_cast<const TemplateSpecializationType *>(
-                   Unexpanded[I].first)) {
+    if (auto *TST =
+            dyn_cast<const TemplateSpecializationType *>(Unexpanded[I].first)) {
       // This is a dependent pack, we are not ready to expand it yet.
       assert(isPackProducingBuiltinTemplateName(TST->getTemplateName()));
       (void)TST;
       return std::nullopt;
     } else if (auto *PST = dyn_cast<const SubstBuiltinTemplatePackType *>(
                    Unexpanded[I].first)) {
-      assert((!Result || *Result == PST->getNumArgs()) &&
-             "inconsistent pack sizes");
-      Result = PST->getNumArgs();
-      continue;
-    } else {
-      NamedDecl *ND = cast<NamedDecl *>(Unexpanded[I].first);
-      if (isa<VarDecl>(ND)) {
-        // Function parameter pack or init-capture pack.
-        typedef LocalInstantiationScope::DeclArgumentPack DeclArgumentPack;
+      Size = PST->getNumArgs();
+    } else if (auto *ND = dyn_cast<NamedDecl *>(Unexpanded[I].first);
+               ND && isa<VarDecl>(ND)) {
+      // Function parameter pack or init-capture pack.
+      typedef LocalInstantiationScope::DeclArgumentPack DeclArgumentPack;
 
-        llvm::PointerUnion<Decl *, DeclArgumentPack *> *Instantiation =
-            CurrentInstantiationScope->findInstantiationOf(
-                cast<NamedDecl *>(Unexpanded[I].first));
-        if (isa<Decl *>(*Instantiation))
-          // The pattern refers to an unexpanded pack. We're not ready to expand
-          // this pack yet.
-          return std::nullopt;
+      llvm::PointerUnion<Decl *, DeclArgumentPack *> *Instantiation =
+          CurrentInstantiationScope->findInstantiationOf(ND);
+      if (isa<Decl *>(*Instantiation))
+        // The pattern refers to an unexpanded pack. We're not ready to expand
+        // this pack yet.
+        return std::nullopt;
 
-        unsigned Size = cast<DeclArgumentPack *>(*Instantiation)->size();
-        assert((!Result || *Result == Size) && "inconsistent pack sizes");
-        Result = Size;
-        continue;
+      Size = cast<DeclArgumentPack *>(*Instantiation)->size();
+    } else {
+      // Compute the depth and index for this parameter pack.
+      unsigned Depth;
+      unsigned Index;
+      if (const TemplateTypeParmType *TTP =
+              dyn_cast<const TemplateTypeParmType *>(Unexpanded[I].first)) {
+        Depth = TTP->getDepth();
+        Index = TTP->getIndex();
+      } else {
+        std::tie(Depth, Index) =
+            getDepthAndIndex(cast<NamedDecl *>(Unexpanded[I].first));
       }
 
-      std::tie(Depth, Index) = getDepthAndIndex(ND);
+      if (Depth >= TemplateArgs.getNumLevels() ||
+          !TemplateArgs.hasTemplateArgument(Depth, Index))
+        // The pattern refers to an unknown template argument. We're not ready
+        // to expand this pack yet.
+        return std::nullopt;
+
+      // Determine the size of the argument pack. It isn't definite if the
+      // pack contains pack expansions of unknown length.
+      ArrayRef<TemplateArgument> Pack =
+          TemplateArgs(Depth, Index).getPackAsArray();
+      Size = Pack.size();
+      SizeIsDefinite = llvm::none_of(Pack, [](const TemplateArgument &TA) {
+        return TA.isPackExpansion() && !TA.getNumExpansions();
+      });
     }
-    if (Depth >= TemplateArgs.getNumLevels() ||
-        !TemplateArgs.hasTemplateArgument(Depth, Index))
-      // The pattern refers to an unknown template argument. We're not ready to
-      // expand this pack yet.
+
+    // A pack of indefinite size may still match the others once substituted.
+    if (Result && *Result != Size && (!SizeIsDefinite || !ResultIsDefinite))
       return std::nullopt;
 
-    // Determine the size of the argument pack.
-    unsigned Size = TemplateArgs(Depth, Index).pack_size();
     assert((!Result || *Result == Size) && "inconsistent pack sizes");
     Result = Size;
+    ResultIsDefinite |= SizeIsDefinite;
   }
 
   return Result;
diff --git a/clang/test/SemaCXX/ctad.cpp b/clang/test/SemaCXX/ctad.cpp
index 6517e88a8a8312..1055d8c4c86e19 100644
--- a/clang/test/SemaCXX/ctad.cpp
+++ b/clang/test/SemaCXX/ctad.cpp
@@ -226,3 +226,13 @@ using AliasName = A<T, Ts...>;
 AliasName aa([](int){}, 0);
 
 }
+
+namespace NestedPackExpansionInConstructor {
+template <class...> struct tuple {};
+template <class, class> struct pair {};
+template <class... Ts> struct A {
+  template <class... Us> A(tuple<pair<Ts, Us>...>);
+};
+A a(tuple<pair<int, char>, pair<long, float>>{});
+static_assert(__is_same(decltype(a), A<int, long>));
+} // namespace NestedPackExpansionInConstructor
diff --git a/clang/test/SemaCXX/cxx20-ctad-type-alias.cpp b/clang/test/SemaCXX/cxx20-ctad-type-alias.cpp
index 78911cbaed67be..5b6ce1971c377a 100644
--- a/clang/test/SemaCXX/cxx20-ctad-type-alias.cpp
+++ b/clang/test/SemaCXX/cxx20-ctad-type-alias.cpp
@@ -629,3 +629,57 @@ template <typename T> using S3 = S2<T>; // expected-note {{candidate function no
                                         // expected-note {{cannot deduce template arguments for 'GH190517::S3' from 'GH190517::S1<char>'}}
 S3 foo(42); // expected-error {{no viable constructor or deduction guide for deduction of template arguments of 'S3'}}
 }
+
+namespace GH193217 {
+
+template <auto...> struct X {};
+
+template <typename T, typename... Ts> struct A {
+  template <Ts... Ns, T *...Ps> A(X<Ps...>, Ts (*...qs)[Ns]);
+};
+
+template <class T = int, class U = T> using AA = A<U>; // #GH193217_AA
+
+AA a{};
+// expected-error at -1 {{no viable constructor or deduction guide for deduction of template arguments of 'AA'}}
+// expected-note@#GH193217_AA 2{{candidate function template not viable}}
+// expected-note@#GH193217_AA 2{{implicit deduction guide declared as}}
+
+int *p;
+AA a1{X<&p>{}};
+static_assert(__is_same(decltype(a1), A<int *>));
+
+template <class T = int, class U = T> using AB = A<U, int>;
+int arr[3];
+AB b{X<&p>{}, &arr};
+static_assert(__is_same(decltype(b), A<int *, int>));
+
+template <class T = int, class U = T> using AC = A<U, int, long>;
+long arr2[4];
+AC c{X<&p>{}, &arr, &arr2};
+static_assert(__is_same(decltype(c), A<int *, int, long>));
+
+template <auto, class> struct Y {};
+template <class T, class... Ts> struct B {
+  template <Ts... Ns> B(T, Y<Ns, Ts>...);
+};
+template <class U> using BB = B<U>;
+BB bb{0};
+static_assert(__is_same(decltype(bb), B<int>));
+
+template <class, class> struct P {};
+template <class T, class... Ts> struct C {
+  template <class... Us> C(T, P<Ts, Us>...);
+};
+template <class U> using CC = C<U>;
+CC cc{0};
+static_assert(__is_same(decltype(cc), C<int>));
+
+template <class T, class... Ts> struct D {
+  template <template <Ts> class... TTs> D(T);
+};
+template <class U> using DD = D<U>;
+DD dd{0};
+static_assert(__is_same(decltype(dd), D<int>));
+
+} // namespace GH193217
diff --git a/clang/test/SemaTemplate/pack-deduction.cpp b/clang/test/SemaTemplate/pack-deduction.cpp
index b3104609994a4e..8b8db2889ce84f 100644
--- a/clang/test/SemaTemplate/pack-deduction.cpp
+++ b/clang/test/SemaTemplate/pack-deduction.cpp
@@ -258,3 +258,11 @@ template <int... Args> using Alias = Nttp<1, 2, 3>::B<Args...>;
 }
 
 }
+
+namespace ExpandedPackDeducedEmpty {
+template <int...> struct X {};
+template <typename... Ts> struct A {
+  template <int N, Ts... Ns> A(X<N>, X<Ns...>);
+};
+A<> a{X<1>{}, X<>{}};
+}



More information about the cfe-commits mailing list