[clang] [clang-tools-extra] [Clang] support friend declarations with a dependent nested-name-specifier (PR #191268)

Oleksandr Tarasiuk via cfe-commits cfe-commits at lists.llvm.org
Fri Jun 5 04:34:44 PDT 2026


https://github.com/a-tarasyuk updated https://github.com/llvm/llvm-project/pull/191268

>From 7f3f8fcd0c2e4fef014b408d97288c17783e76f3 Mon Sep 17 00:00:00 2001
From: Oleksandr Tarasiuk <oleksandr.tarasiuk at outlook.com>
Date: Thu, 9 Apr 2026 18:04:29 +0300
Subject: [PATCH 01/38] [Clang] support friend declarations with a dependent
 nested-name-specifier

---
 .../clang/AST/ASTStructuralEquivalence.h      |   2 +
 clang/include/clang/AST/DeclFriend.h          | 117 ++----
 clang/include/clang/AST/DeclTemplate.h        |  67 ++--
 clang/include/clang/AST/RecursiveASTVisitor.h |   5 +-
 .../clang/Basic/DiagnosticSemaKinds.td        |   2 +
 clang/include/clang/Sema/Sema.h               |   3 +
 clang/include/clang/Sema/Template.h           |   2 +-
 clang/lib/AST/ASTImporter.cpp                 | 125 +++++-
 clang/lib/AST/ASTStructuralEquivalence.cpp    |  48 +++
 clang/lib/AST/DeclFriend.cpp                  |  64 +--
 clang/lib/AST/DeclPrinter.cpp                 |  17 +-
 clang/lib/AST/DeclTemplate.cpp                |  39 +-
 clang/lib/Sema/Sema.cpp                       |   5 +-
 clang/lib/Sema/SemaAccess.cpp                 | 379 +++++++++++++++---
 clang/lib/Sema/SemaDeclCXX.cpp                | 209 ++++++----
 clang/lib/Sema/SemaTemplate.cpp               |  14 +-
 clang/lib/Sema/SemaTemplateInstantiate.cpp    |  20 +-
 .../lib/Sema/SemaTemplateInstantiateDecl.cpp  | 102 +++--
 clang/lib/Serialization/ASTReaderDecl.cpp     |  16 +-
 clang/lib/Serialization/ASTWriterDecl.cpp     |  14 +-
 .../class.access/class.friend/p3-cxx0x.cpp    |   4 +-
 clang/test/CXX/drs/cwg18xx.cpp                |  11 +-
 clang/test/CXX/drs/cwg19xx.cpp                |   8 +-
 clang/test/CXX/drs/cwg6xx.cpp                 |  17 +-
 .../CXX/temp/temp.decls/temp.friend/p5.cpp    | 151 ++++++-
 .../CXX/temp/temp.decls/temp.friend/p6.cpp    |  24 ++
 .../SemaCXX/many-template-parameter-lists.cpp |   6 +-
 clang/test/SemaTemplate/GH71595.cpp           |   6 +-
 clang/test/SemaTemplate/ctad.cpp              |   9 +-
 29 files changed, 1050 insertions(+), 436 deletions(-)
 create mode 100644 clang/test/CXX/temp/temp.decls/temp.friend/p6.cpp

diff --git a/clang/include/clang/AST/ASTStructuralEquivalence.h b/clang/include/clang/AST/ASTStructuralEquivalence.h
index 6f82de1ae136d..89d7f8d6ba8ff 100644
--- a/clang/include/clang/AST/ASTStructuralEquivalence.h
+++ b/clang/include/clang/AST/ASTStructuralEquivalence.h
@@ -135,6 +135,8 @@ struct StructuralEquivalenceContext {
   /// \c VisitedDecls members) and can cause faulty equivalent results.
   bool IsEquivalent(Stmt *S1, Stmt *S2);
 
+  bool IsEquivalent(TemplateParameterList *TPL1, TemplateParameterList *TPL2);
+
   /// Find the index of the given anonymous struct/union within its
   /// context.
   ///
diff --git a/clang/include/clang/AST/DeclFriend.h b/clang/include/clang/AST/DeclFriend.h
index 1f8c210263677..2cd5a1af17fd8 100644
--- a/clang/include/clang/AST/DeclFriend.h
+++ b/clang/include/clang/AST/DeclFriend.h
@@ -15,20 +15,14 @@
 #define LLVM_CLANG_AST_DECLFRIEND_H
 
 #include "clang/AST/Decl.h"
-#include "clang/AST/DeclBase.h"
 #include "clang/AST/DeclCXX.h"
-#include "clang/AST/DeclTemplate.h"
-#include "clang/AST/ExternalASTSource.h"
 #include "clang/AST/TypeLoc.h"
 #include "clang/Basic/LLVM.h"
-#include "clang/Basic/SourceLocation.h"
 #include "llvm/ADT/ArrayRef.h"
 #include "llvm/ADT/PointerUnion.h"
 #include "llvm/Support/Casting.h"
 #include "llvm/Support/Compiler.h"
-#include "llvm/Support/TrailingObjects.h"
 #include <cassert>
-#include <iterator>
 
 namespace clang {
 
@@ -49,9 +43,7 @@ class ASTContext;
 /// @endcode
 ///
 /// The semantic context of a friend decl is its declaring class.
-class FriendDecl final
-    : public Decl,
-      private llvm::TrailingObjects<FriendDecl, TemplateParameterList *> {
+class FriendDecl : public Decl {
   LLVM_DECLARE_VIRTUAL_ANCHOR_FUNCTION();
 
 public:
@@ -61,46 +53,35 @@ class FriendDecl final
   friend class CXXRecordDecl;
   friend class CXXRecordDecl::friend_iterator;
 
-  // The declaration that's a friend of this class.
-  FriendUnion Friend;
-
-  // A pointer to the next friend in the sequence.
-  LazyDeclPtr NextFriend;
-
-  // Location of the 'friend' specifier.
-  SourceLocation FriendLoc;
-
   // Location of the '...', if present.
   SourceLocation EllipsisLoc;
 
+  SourceLocation FriendLoc;
+
   /// True if this 'friend' declaration is unsupported.  Eventually we
   /// will support every possible friend declaration, but for now we
   /// silently ignore some and set this flag to authorize all access.
   LLVM_PREFERRED_TYPE(bool)
   unsigned UnsupportedFriend : 1;
 
-  // The number of "outer" template parameter lists in non-templatic
-  // (currently unsupported) friend type declarations, such as
-  //     template <class T> friend class A<T>::B;
-  unsigned NumTPLists : 31;
-
-  FriendDecl(DeclContext *DC, SourceLocation L, FriendUnion Friend,
-             SourceLocation FriendL, SourceLocation EllipsisLoc,
-             ArrayRef<TemplateParameterList *> FriendTypeTPLists)
-      : Decl(Decl::Friend, DC, L), Friend(Friend), FriendLoc(FriendL),
-        EllipsisLoc(EllipsisLoc), UnsupportedFriend(false),
-        NumTPLists(FriendTypeTPLists.size()) {
-    llvm::copy(FriendTypeTPLists, getTrailingObjects());
-  }
+protected:
+  // The declaration that's a friend of this class.
+  FriendUnion Friend;
 
-  FriendDecl(EmptyShell Empty, unsigned NumFriendTypeTPLists)
-      : Decl(Decl::Friend, Empty), UnsupportedFriend(false),
-        NumTPLists(NumFriendTypeTPLists) {}
+  LazyDeclPtr NextFriend;
+
+  FriendDecl(Kind K, DeclContext *DC, SourceLocation L, FriendUnion Friend,
+             SourceLocation FriendL, SourceLocation EllipsisLoc = {})
+      : Decl(K, DC, L), EllipsisLoc(EllipsisLoc), FriendLoc(FriendL),
+        UnsupportedFriend(false), Friend(Friend), NextFriend() {}
+
+  FriendDecl(Kind K, EmptyShell Empty)
+      : Decl(K, Empty), UnsupportedFriend(false) {}
 
   FriendDecl *getNextFriend() {
-    if (!NextFriend.isOffset())
-      return cast_or_null<FriendDecl>(NextFriend.get(nullptr));
-    return getNextFriendSlowCase();
+    if (NextFriend.isOffset())
+      return getNextFriendSlowCase();
+    return cast_or_null<FriendDecl>(NextFriend.get(nullptr));
   }
 
   FriendDecl *getNextFriendSlowCase();
@@ -109,14 +90,11 @@ class FriendDecl final
   friend class ASTDeclReader;
   friend class ASTDeclWriter;
   friend class ASTNodeImporter;
-  friend TrailingObjects;
 
-  static FriendDecl *
-  Create(ASTContext &C, DeclContext *DC, SourceLocation L, FriendUnion Friend_,
-         SourceLocation FriendL, SourceLocation EllipsisLoc = {},
-         ArrayRef<TemplateParameterList *> FriendTypeTPLists = {});
-  static FriendDecl *CreateDeserialized(ASTContext &C, GlobalDeclID ID,
-                                        unsigned FriendTypeNumTPLists);
+  static FriendDecl *Create(ASTContext &C, DeclContext *DC, SourceLocation L,
+                            FriendUnion Friend_, SourceLocation FriendL,
+                            SourceLocation EllipsisLoc = {});
+  static FriendDecl *CreateDeserialized(ASTContext &C, GlobalDeclID ID);
 
   /// If this friend declaration names an (untemplated but possibly
   /// dependent) type, return the type; otherwise return null.  This
@@ -126,58 +104,18 @@ class FriendDecl final
     return Friend.dyn_cast<TypeSourceInfo*>();
   }
 
-  unsigned getFriendTypeNumTemplateParameterLists() const {
-    return NumTPLists;
-  }
-
-  TemplateParameterList *getFriendTypeTemplateParameterList(unsigned N) const {
-    return getTrailingObjects(NumTPLists)[N];
-  }
-
   /// If this friend declaration doesn't name a type, return the inner
   /// declaration.
   NamedDecl *getFriendDecl() const {
     return Friend.dyn_cast<NamedDecl *>();
   }
 
-  /// Retrieves the location of the 'friend' keyword.
-  SourceLocation getFriendLoc() const {
-    return FriendLoc;
-  }
-
   /// Retrieves the location of the '...', if present.
   SourceLocation getEllipsisLoc() const { return EllipsisLoc; }
 
-  /// Retrieves the source range for the friend declaration.
-  SourceRange getSourceRange() const override LLVM_READONLY {
-    if (TypeSourceInfo *TInfo = getFriendType()) {
-      SourceLocation StartL = (NumTPLists == 0)
-                                  ? getFriendLoc()
-                                  : getTrailingObjects()[0]->getTemplateLoc();
-      SourceLocation EndL = isPackExpansion() ? getEllipsisLoc()
-                                              : TInfo->getTypeLoc().getEndLoc();
-      return SourceRange(StartL, EndL);
-    }
-
-    if (isPackExpansion())
-      return SourceRange(getFriendLoc(), getEllipsisLoc());
-
-    if (NamedDecl *ND = getFriendDecl()) {
-      if (const auto *FD = dyn_cast<FunctionDecl>(ND))
-        return FD->getSourceRange();
-      if (const auto *FTD = dyn_cast<FunctionTemplateDecl>(ND))
-        return FTD->getSourceRange();
-      if (const auto *CTD = dyn_cast<ClassTemplateDecl>(ND))
-        return CTD->getSourceRange();
-      if (const auto *DD = dyn_cast<DeclaratorDecl>(ND)) {
-        if (DD->getOuterLocStart() != DD->getInnerLocStart())
-          return DD->getSourceRange();
-      }
-      return SourceRange(getFriendLoc(), ND->getEndLoc());
-    }
-
-    return SourceRange(getFriendLoc(), getLocation());
-  }
+  SourceLocation getFriendLoc() const { return FriendLoc; }
+
+  SourceRange getSourceRange() const override LLVM_READONLY;
 
   /// Determines if this friend kind is unsupported.
   bool isUnsupportedFriend() const {
@@ -191,9 +129,10 @@ class FriendDecl final
 
   // Implement isa/cast/dyncast/etc.
   static bool classof(const Decl *D) { return classofKind(D->getKind()); }
-  static bool classofKind(Kind K) { return K == Decl::Friend; }
+  static bool classofKind(Kind K) {
+    return K == Decl::Friend || K == Decl::FriendTemplate;
+  }
 };
-
 /// An iterator over the friend declarations of a class.
 class CXXRecordDecl::friend_iterator {
   friend class CXXRecordDecl;
diff --git a/clang/include/clang/AST/DeclTemplate.h b/clang/include/clang/AST/DeclTemplate.h
index a4a1bb9c13c79..1653c26910b8b 100644
--- a/clang/include/clang/AST/DeclTemplate.h
+++ b/clang/include/clang/AST/DeclTemplate.h
@@ -19,6 +19,7 @@
 #include "clang/AST/Decl.h"
 #include "clang/AST/DeclBase.h"
 #include "clang/AST/DeclCXX.h"
+#include "clang/AST/DeclFriend.h"
 #include "clang/AST/DeclarationName.h"
 #include "clang/AST/Redeclarable.h"
 #include "clang/AST/TemplateBase.h"
@@ -2473,42 +2474,43 @@ class ClassTemplateDecl : public RedeclarableTemplateDecl {
 ///
 /// \note This class is not currently in use.  All of the above
 /// will yield a FriendDecl, not a FriendTemplateDecl.
-class FriendTemplateDecl : public Decl {
-  virtual void anchor();
-
-public:
-  using FriendUnion = llvm::PointerUnion<NamedDecl *,TypeSourceInfo *>;
+class FriendTemplateDecl final
+    : public FriendDecl,
+      private llvm::TrailingObjects<FriendTemplateDecl,
+                                    TemplateParameterList *> {
+  void anchor() override;
 
 private:
-  // The number of template parameters;  always non-zero.
-  unsigned NumParams = 0;
+  unsigned NumTPLists : 31;
 
-  // The parameter list.
-  TemplateParameterList **Params = nullptr;
-
-  // The declaration that's a friend of this class.
-  FriendUnion Friend;
-
-  // Location of the 'friend' specifier.
-  SourceLocation FriendLoc;
-
-  FriendTemplateDecl(DeclContext *DC, SourceLocation Loc,
-                     TemplateParameterList **Params, unsigned NumParams,
-                     FriendUnion Friend, SourceLocation FriendLoc)
-      : Decl(Decl::FriendTemplate, DC, Loc), NumParams(NumParams),
-        Params(Params), Friend(Friend), FriendLoc(FriendLoc) {}
+  FriendTemplateDecl(DeclContext *DC, SourceLocation Loc, FriendUnion Friend,
+                     SourceLocation FriendLoc, SourceLocation EllipsisLoc,
+                     ArrayRef<TemplateParameterList *> FriendTypeTPLists)
+      : FriendDecl(Decl::FriendTemplate, DC, Loc, Friend, FriendLoc,
+                   EllipsisLoc),
+        NumTPLists(FriendTypeTPLists.size()) {
+    llvm::copy(FriendTypeTPLists, getTrailingObjects());
+  }
 
-  FriendTemplateDecl(EmptyShell Empty) : Decl(Decl::FriendTemplate, Empty) {}
+  FriendTemplateDecl(EmptyShell Empty, unsigned NumFriendTypeTPLists)
+      : FriendDecl(Decl::FriendTemplate, Empty),
+        NumTPLists(NumFriendTypeTPLists) {}
 
 public:
   friend class ASTDeclReader;
+  friend class ASTDeclWriter;
+  friend TrailingObjects;
 
   static FriendTemplateDecl *
   Create(ASTContext &Context, DeclContext *DC, SourceLocation Loc,
-         MutableArrayRef<TemplateParameterList *> Params, FriendUnion Friend,
-         SourceLocation FriendLoc);
+         FriendUnion Friend, SourceLocation FriendLoc,
+         ArrayRef<TemplateParameterList *> FriendTypeTPLists = {},
+         SourceLocation EllipsisLoc = {});
 
-  static FriendTemplateDecl *CreateDeserialized(ASTContext &C, GlobalDeclID ID);
+  static FriendTemplateDecl *CreateDeserialized(ASTContext &C, GlobalDeclID ID,
+                                                unsigned FriendTypeNumTPLists);
+
+  SourceRange getSourceRange() const override LLVM_READONLY;
 
   /// If this friend declaration names a templated type (or
   /// a dependent member type of a templated type), return that
@@ -2524,19 +2526,12 @@ class FriendTemplateDecl : public Decl {
     return Friend.dyn_cast<NamedDecl*>();
   }
 
-  /// Retrieves the location of the 'friend' keyword.
-  SourceLocation getFriendLoc() const {
-    return FriendLoc;
+  TemplateParameterList *getFriendTypeTemplateParameterList(unsigned N) const {
+    assert(N < NumTPLists);
+    return getTrailingObjects()[N];
   }
 
-  TemplateParameterList *getTemplateParameterList(unsigned i) const {
-    assert(i <= NumParams);
-    return Params[i];
-  }
-
-  unsigned getNumTemplateParameters() const {
-    return NumParams;
-  }
+  unsigned getFriendTypeNumTemplateParameterLists() const { return NumTPLists; }
 
   // Implement isa/cast/dyncast/etc.
   static bool classof(const Decl *D) { return classofKind(D->getKind()); }
diff --git a/clang/include/clang/AST/RecursiveASTVisitor.h b/clang/include/clang/AST/RecursiveASTVisitor.h
index ce6ad723191e0..c06ceec13b4dd 100644
--- a/clang/include/clang/AST/RecursiveASTVisitor.h
+++ b/clang/include/clang/AST/RecursiveASTVisitor.h
@@ -1727,8 +1727,9 @@ DEF_TRAVERSE_DECL(FriendTemplateDecl, {
     TRY_TO(TraverseTypeLoc(D->getFriendType()->getTypeLoc()));
   else
     TRY_TO(TraverseDecl(D->getFriendDecl()));
-  for (unsigned I = 0, E = D->getNumTemplateParameters(); I < E; ++I) {
-    TemplateParameterList *TPL = D->getTemplateParameterList(I);
+  for (unsigned I = 0, E = D->getFriendTypeNumTemplateParameterLists(); I < E;
+       ++I) {
+    TemplateParameterList *TPL = D->getFriendTypeTemplateParameterList(I);
     for (TemplateParameterList::iterator ITPL = TPL->begin(), ETPL = TPL->end();
          ITPL != ETPL; ++ITPL) {
       TRY_TO(TraverseDecl(*ITPL));
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 6d2fae551566f..9835c66c71816 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -1921,6 +1921,8 @@ def err_friend_template_decl_multiple_specifiers: Error<
   "a friend declaration that befriends a template must contain exactly one type-specifier">;
 def friend_template_decl_malformed_pack_expansion : Error<
   "friend declaration expands pack %0 that is declared it its own template parameter list">;
+def err_dependent_friend_not_member : Error<
+  "friend declaration does not name a member of a class template specialization">;
 
 def err_invalid_base_in_interface : Error<
   "interface type cannot inherit from "
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 760555d9c8b9b..544d892c1535c 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -12012,6 +12012,9 @@ class Sema final : public SemaBase {
   bool CheckMemberSpecialization(NamedDecl *Member, LookupResult &Previous);
   void CompleteMemberSpecialization(NamedDecl *Member, LookupResult &Previous);
 
+  bool CheckDependentFriend(SourceLocation Loc, NestedNameSpecifier NNS,
+                            TemplateParameterList *FPL);
+
   // Explicit instantiation of a class template specialization
   DeclResult ActOnExplicitInstantiation(
       Scope *S, SourceLocation ExternLoc, SourceLocation TemplateLoc,
diff --git a/clang/include/clang/Sema/Template.h b/clang/include/clang/Sema/Template.h
index b0170c21feb1a..62b1c16de82c1 100644
--- a/clang/include/clang/Sema/Template.h
+++ b/clang/include/clang/Sema/Template.h
@@ -715,7 +715,7 @@ enum class TemplateSubstitutionKind : char {
 
     // Helper functions for instantiating methods.
     TypeSourceInfo *SubstFunctionType(FunctionDecl *D,
-                             SmallVectorImpl<ParmVarDecl *> &Params);
+                                      SmallVectorImpl<ParmVarDecl *> &Params);
     bool InitFunctionInstantiation(FunctionDecl *New, FunctionDecl *Tmpl);
     bool InitMethodInstantiation(CXXMethodDecl *New, CXXMethodDecl *Tmpl);
 
diff --git a/clang/lib/AST/ASTImporter.cpp b/clang/lib/AST/ASTImporter.cpp
index 41ba98c53247d..782e4d710599c 100644
--- a/clang/lib/AST/ASTImporter.cpp
+++ b/clang/lib/AST/ASTImporter.cpp
@@ -537,6 +537,7 @@ namespace clang {
     ExpectedDecl VisitFieldDecl(FieldDecl *D);
     ExpectedDecl VisitIndirectFieldDecl(IndirectFieldDecl *D);
     ExpectedDecl VisitFriendDecl(FriendDecl *D);
+    ExpectedDecl VisitFriendTemplateDecl(FriendTemplateDecl *D);
     ExpectedDecl VisitObjCIvarDecl(ObjCIvarDecl *D);
     ExpectedDecl VisitVarDecl(VarDecl *D);
     ExpectedDecl VisitImplicitParamDecl(ImplicitParamDecl *D);
@@ -4541,10 +4542,40 @@ static bool IsEquivalentFriend(ASTImporter &Importer, FriendDecl *FD1,
       Importer.getToContext().getLangOpts(), FD1->getASTContext(),
       FD2->getASTContext(), NonEquivalentDecls,
       StructuralEquivalenceKind::Default,
-      /* StrictTypeSpelling = */ false, /* Complain = */ false);
+      /*StrictTypeSpelling=*/false, /*Complain=*/false);
   return Ctx.IsEquivalent(FD1, FD2);
 }
 
+static bool IsEquivalentFriend(ASTImporter &Importer, FriendTemplateDecl *FTD1,
+                               FriendTemplateDecl *FTD2) {
+  if (FTD1->getFriendTypeNumTemplateParameterLists() !=
+      FTD2->getFriendTypeNumTemplateParameterLists())
+    return false;
+
+  ASTImporter::NonEquivalentDeclSet NonEquivalentDecls;
+  StructuralEquivalenceContext Ctx(
+      Importer.getToContext().getLangOpts(), FTD1->getASTContext(),
+      FTD2->getASTContext(), NonEquivalentDecls,
+      StructuralEquivalenceKind::Default, /*StrictTypeSpelling=*/false,
+      /*Complain=*/false);
+
+  for (unsigned I = 0, N = FTD1->getFriendTypeNumTemplateParameterLists();
+       I != N; ++I) {
+    if (!Ctx.IsEquivalent(FTD1->getFriendTypeTemplateParameterList(I),
+                          FTD2->getFriendTypeTemplateParameterList(I)))
+      return false;
+  }
+
+  if ((!FTD1->getFriendType()) != (!FTD2->getFriendType()))
+    return false;
+
+  if (const TypeSourceInfo *TSI = FTD1->getFriendType())
+    return Importer.IsStructurallyEquivalent(
+        TSI->getType(), FTD2->getFriendType()->getType(), /*Complain=*/false);
+
+  return Ctx.IsEquivalent(FTD1, FTD2);
+}
+
 static FriendCountAndPosition getFriendCountAndPosition(ASTImporter &Importer,
                                                         FriendDecl *FD) {
   unsigned int FriendCount = 0;
@@ -4561,7 +4592,6 @@ static FriendCountAndPosition getFriendCountAndPosition(ASTImporter &Importer,
   }
 
   assert(FriendPosition && "Friend decl not found in own parent.");
-
   return {FriendCount, *FriendPosition};
 }
 
@@ -4576,9 +4606,11 @@ ExpectedDecl ASTNodeImporter::VisitFriendDecl(FriendDecl *D) {
   // We try to maintain order and count of redundant friend declarations.
   const auto *RD = cast<CXXRecordDecl>(DC);
   SmallVector<FriendDecl *, 2> ImportedEquivalentFriends;
-  for (FriendDecl *ImportedFriend : RD->friends())
-    if (IsEquivalentFriend(Importer, D, ImportedFriend))
-      ImportedEquivalentFriends.push_back(ImportedFriend);
+  for (FriendDecl *ImportedFriend : RD->friends()) {
+    if (ImportedFriend->getKind() == Decl::Friend &&
+        IsEquivalentFriend(Importer, D, cast<FriendDecl>(ImportedFriend)))
+      ImportedEquivalentFriends.push_back(cast<FriendDecl>(ImportedFriend));
+  }
 
   FriendCountAndPosition CountAndPosition =
       getFriendCountAndPosition(Importer, D);
@@ -4609,15 +4641,6 @@ ExpectedDecl ASTNodeImporter::VisitFriendDecl(FriendDecl *D) {
       return TSIOrErr.takeError();
   }
 
-  SmallVector<TemplateParameterList *, 1> ToTPLists(D->NumTPLists);
-  auto **FromTPLists = D->getTrailingObjects();
-  for (unsigned I = 0; I < D->NumTPLists; I++) {
-    if (auto ListOrErr = import(FromTPLists[I]))
-      ToTPLists[I] = *ListOrErr;
-    else
-      return ListOrErr.takeError();
-  }
-
   auto LocationOrErr = import(D->getLocation());
   if (!LocationOrErr)
     return LocationOrErr.takeError();
@@ -4631,7 +4654,7 @@ ExpectedDecl ASTNodeImporter::VisitFriendDecl(FriendDecl *D) {
   FriendDecl *FrD;
   if (GetImportedOrCreateDecl(FrD, D, Importer.getToContext(), DC,
                               *LocationOrErr, ToFU, *FriendLocOrErr,
-                              *EllipsisLocOrErr, ToTPLists))
+                              *EllipsisLocOrErr))
     return FrD;
 
   FrD->setAccess(D->getAccess());
@@ -4640,6 +4663,78 @@ ExpectedDecl ASTNodeImporter::VisitFriendDecl(FriendDecl *D) {
   return FrD;
 }
 
+ExpectedDecl ASTNodeImporter::VisitFriendTemplateDecl(FriendTemplateDecl *D) {
+  DeclContext *DC, *LexicalDC;
+  if (Error Err = ImportDeclContext(D, DC, LexicalDC))
+    return std::move(Err);
+
+  const auto *RD = cast<CXXRecordDecl>(DC);
+  SmallVector<FriendTemplateDecl *, 2> ImportedEquivalentFriends;
+  for (FriendDecl *ImportedFriend : RD->friends()) {
+    if (isa<FriendTemplateDecl>(ImportedFriend) &&
+        IsEquivalentFriend(Importer, D,
+                           cast<FriendTemplateDecl>(ImportedFriend)))
+      ImportedEquivalentFriends.push_back(
+          cast<FriendTemplateDecl>(ImportedFriend));
+  }
+
+  FriendCountAndPosition CountAndPosition =
+      getFriendCountAndPosition(Importer, D);
+  assert(ImportedEquivalentFriends.size() <= CountAndPosition.TotalCount &&
+         "Class with non-matching friends is imported, ODR check wrong?");
+
+  if (ImportedEquivalentFriends.size() == CountAndPosition.TotalCount)
+    return Importer.MapImported(
+        D, ImportedEquivalentFriends[CountAndPosition.IndexOfDecl]);
+
+  FriendTemplateDecl::FriendUnion ToFU;
+  if (NamedDecl *FriendD = D->getFriendDecl()) {
+    NamedDecl *ToFriendD;
+    if (Error Err = importInto(ToFriendD, FriendD))
+      return std::move(Err);
+    ToFU = ToFriendD;
+  } else {
+    if (auto TSIOrErr = import(D->getFriendType()))
+      ToFU = *TSIOrErr;
+    else
+      return TSIOrErr.takeError();
+  }
+
+  SmallVector<TemplateParameterList *, 1> ToParams(
+      D->getFriendTypeNumTemplateParameterLists());
+  for (unsigned I = 0, N = D->getFriendTypeNumTemplateParameterLists(); I != N;
+       ++I) {
+    if (auto ParamsOrErr = import(D->getFriendTypeTemplateParameterList(I)))
+      ToParams[I] = *ParamsOrErr;
+    else
+      return ParamsOrErr.takeError();
+  }
+
+  auto LocationOrErr = import(D->getLocation());
+  if (!LocationOrErr)
+    return LocationOrErr.takeError();
+
+  auto FriendLocOrErr = import(D->getFriendLoc());
+  if (!FriendLocOrErr)
+    return FriendLocOrErr.takeError();
+
+  auto EllipsisLocOrErr = import(D->getEllipsisLoc());
+  if (!EllipsisLocOrErr)
+    return EllipsisLocOrErr.takeError();
+
+  FriendTemplateDecl *FTD;
+  if (GetImportedOrCreateDecl(FTD, D, Importer.getToContext(), DC,
+                              *LocationOrErr, ToFU, *FriendLocOrErr, ToParams,
+                              *EllipsisLocOrErr))
+    return FTD;
+
+  FTD->setUnsupportedFriend(D->isUnsupportedFriend());
+  FTD->setAccess(D->getAccess());
+  FTD->setLexicalDeclContext(LexicalDC);
+  LexicalDC->addDeclInternal(FTD);
+  return FTD;
+}
+
 ExpectedDecl ASTNodeImporter::VisitObjCIvarDecl(ObjCIvarDecl *D) {
   // Import the major distinguishing characteristics of an ivar.
   DeclContext *DC, *LexicalDC;
diff --git a/clang/lib/AST/ASTStructuralEquivalence.cpp b/clang/lib/AST/ASTStructuralEquivalence.cpp
index 9d970651a9e65..8da7cca6665fb 100644
--- a/clang/lib/AST/ASTStructuralEquivalence.cpp
+++ b/clang/lib/AST/ASTStructuralEquivalence.cpp
@@ -2430,6 +2430,35 @@ static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
   return false;
 }
 
+static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
+                                     FriendTemplateDecl *FTD1,
+                                     FriendTemplateDecl *FTD2) {
+  if (FTD1->getFriendTypeNumTemplateParameterLists() !=
+      FTD2->getFriendTypeNumTemplateParameterLists())
+    return false;
+
+  for (unsigned I = 0, N = FTD1->getFriendTypeNumTemplateParameterLists();
+       I != N; ++I) {
+    if (!Context.IsEquivalent(FTD1->getFriendTypeTemplateParameterList(I),
+                              FTD2->getFriendTypeTemplateParameterList(I)))
+      return false;
+  }
+
+  if ((FTD1->getFriendType() && FTD2->getFriendDecl()) ||
+      (FTD1->getFriendDecl() && FTD2->getFriendType()))
+    return false;
+
+  if (FTD1->getFriendDecl() && FTD2->getFriendDecl())
+    return IsStructurallyEquivalent(Context, FTD1->getFriendDecl(),
+                                    FTD2->getFriendDecl());
+
+  if (FTD1->getFriendType() && FTD2->getFriendType())
+    return IsStructurallyEquivalent(Context, FTD1->getFriendType()->getType(),
+                                    FTD2->getFriendType()->getType());
+
+  return false;
+}
+
 static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
                                      TypedefNameDecl *D1, TypedefNameDecl *D2) {
   if (!IsStructurallyEquivalent(D1->getIdentifier(), D2->getIdentifier()))
@@ -2769,6 +2798,25 @@ bool StructuralEquivalenceContext::CheckCommonEquivalence(Decl *D1, Decl *D2) {
   return true;
 }
 
+bool StructuralEquivalenceContext::IsEquivalent(TemplateParameterList *TPL1,
+                                                TemplateParameterList *TPL2) {
+  if (TPL1 == TPL2)
+    return true;
+
+  if (!TPL1 || !TPL2)
+    return false;
+
+  if (TPL1->size() != TPL2->size())
+    return false;
+
+  for (unsigned I = 0, N = TPL1->size(); I != N; ++I) {
+    if (!IsEquivalent(TPL1->getParam(I), TPL2->getParam(I)))
+      return false;
+  }
+
+  return true;
+}
+
 bool StructuralEquivalenceContext::CheckKindSpecificEquivalence(
     Decl *D1, Decl *D2) {
 
diff --git a/clang/lib/AST/DeclFriend.cpp b/clang/lib/AST/DeclFriend.cpp
index 6bfc2eb62b284..aea0600441d25 100644
--- a/clang/lib/AST/DeclFriend.cpp
+++ b/clang/lib/AST/DeclFriend.cpp
@@ -13,11 +13,9 @@
 
 #include "clang/AST/DeclFriend.h"
 #include "clang/AST/ASTContext.h"
-#include "clang/AST/Decl.h"
-#include "clang/AST/DeclBase.h"
 #include "clang/AST/DeclCXX.h"
 #include "clang/AST/DeclTemplate.h"
-#include "clang/Basic/LLVM.h"
+#include "clang/AST/ExternalASTSource.h"
 #include <cassert>
 #include <cstddef>
 
@@ -25,16 +23,9 @@ using namespace clang;
 
 void FriendDecl::anchor() {}
 
-FriendDecl *FriendDecl::getNextFriendSlowCase() {
-  return cast_or_null<FriendDecl>(
-                           NextFriend.get(getASTContext().getExternalSource()));
-}
-
-FriendDecl *
-FriendDecl::Create(ASTContext &C, DeclContext *DC, SourceLocation L,
-                   FriendUnion Friend, SourceLocation FriendL,
-                   SourceLocation EllipsisLoc,
-                   ArrayRef<TemplateParameterList *> FriendTypeTPLists) {
+FriendDecl *FriendDecl::Create(ASTContext &C, DeclContext *DC, SourceLocation L,
+                               FriendUnion Friend, SourceLocation FriendL,
+                               SourceLocation EllipsisLoc) {
 #ifndef NDEBUG
   if (const auto *D = dyn_cast<NamedDecl *>(Friend)) {
     assert(isa<FunctionDecl>(D) ||
@@ -46,25 +37,22 @@ FriendDecl::Create(ASTContext &C, DeclContext *DC, SourceLocation L,
     // to the original declaration when instantiating members.
     assert(D->getFriendObjectKind() ||
            (cast<CXXRecordDecl>(DC)->getTemplateSpecializationKind()));
-    // These template parameters are for friend types only.
-    assert(FriendTypeTPLists.empty());
   }
 #endif
 
-  std::size_t Extra =
-      FriendDecl::additionalSizeToAlloc<TemplateParameterList *>(
-          FriendTypeTPLists.size());
-  auto *FD = new (C, DC, Extra)
-      FriendDecl(DC, L, Friend, FriendL, EllipsisLoc, FriendTypeTPLists);
+  auto *FD =
+      new (C, DC) FriendDecl(Decl::Friend, DC, L, Friend, FriendL, EllipsisLoc);
   cast<CXXRecordDecl>(DC)->pushFriendDecl(FD);
   return FD;
 }
 
-FriendDecl *FriendDecl::CreateDeserialized(ASTContext &C, GlobalDeclID ID,
-                                           unsigned FriendTypeNumTPLists) {
-  std::size_t Extra =
-      additionalSizeToAlloc<TemplateParameterList *>(FriendTypeNumTPLists);
-  return new (C, ID, Extra) FriendDecl(EmptyShell(), FriendTypeNumTPLists);
+FriendDecl *FriendDecl::CreateDeserialized(ASTContext &C, GlobalDeclID ID) {
+  return new (C, ID) FriendDecl(Decl::Friend, EmptyShell());
+}
+
+FriendDecl *FriendDecl::getNextFriendSlowCase() {
+  return cast_or_null<FriendDecl>(
+      NextFriend.get(getASTContext().getExternalSource()));
 }
 
 FriendDecl *CXXRecordDecl::getFirstFriend() const {
@@ -72,3 +60,29 @@ FriendDecl *CXXRecordDecl::getFirstFriend() const {
   Decl *First = data().FirstFriend.get(Source);
   return First ? cast<FriendDecl>(First) : nullptr;
 }
+
+SourceRange FriendDecl::getSourceRange() const {
+  if (TypeSourceInfo *TInfo = getFriendType()) {
+    SourceLocation EndL =
+        isPackExpansion() ? getEllipsisLoc() : TInfo->getTypeLoc().getEndLoc();
+    return SourceRange(getFriendLoc(), EndL);
+  }
+
+  if (isPackExpansion())
+    return SourceRange(getFriendLoc(), getEllipsisLoc());
+
+  if (NamedDecl *ND = getFriendDecl()) {
+    if (const auto *FD = dyn_cast<FunctionDecl>(ND))
+      return FD->getSourceRange();
+    if (const auto *FTD = dyn_cast<FunctionTemplateDecl>(ND))
+      return FTD->getSourceRange();
+    if (const auto *CTD = dyn_cast<ClassTemplateDecl>(ND))
+      return CTD->getSourceRange();
+    if (const auto *DD = dyn_cast<DeclaratorDecl>(ND)) {
+      if (DD->getOuterLocStart() != DD->getInnerLocStart())
+        return DD->getSourceRange();
+    }
+    return SourceRange(getFriendLoc(), ND->getEndLoc());
+  }
+  return SourceRange(getFriendLoc(), getLocation());
+}
diff --git a/clang/lib/AST/DeclPrinter.cpp b/clang/lib/AST/DeclPrinter.cpp
index 5e377a6c0c247..755f93e1ab22f 100644
--- a/clang/lib/AST/DeclPrinter.cpp
+++ b/clang/lib/AST/DeclPrinter.cpp
@@ -888,24 +888,17 @@ void DeclPrinter::VisitFunctionDecl(FunctionDecl *D) {
 
 void DeclPrinter::VisitFriendDecl(FriendDecl *D) {
   if (TypeSourceInfo *TSI = D->getFriendType()) {
-    unsigned NumTPLists = D->getFriendTypeNumTemplateParameterLists();
-    for (unsigned i = 0; i < NumTPLists; ++i)
-      printTemplateParameters(D->getFriendTypeTemplateParameterList(i));
     Out << "friend ";
     Out << TSI->getType().getAsString(Policy);
-  }
-  else if (FunctionDecl *FD =
-      dyn_cast<FunctionDecl>(D->getFriendDecl())) {
+  } else if (FunctionDecl *FD = dyn_cast<FunctionDecl>(D->getFriendDecl())) {
     Out << "friend ";
     VisitFunctionDecl(FD);
-  }
-  else if (FunctionTemplateDecl *FTD =
-           dyn_cast<FunctionTemplateDecl>(D->getFriendDecl())) {
+  } else if (FunctionTemplateDecl *FTD =
+                 dyn_cast<FunctionTemplateDecl>(D->getFriendDecl())) {
     Out << "friend ";
     VisitFunctionTemplateDecl(FTD);
-  }
-  else if (ClassTemplateDecl *CTD =
-           dyn_cast<ClassTemplateDecl>(D->getFriendDecl())) {
+  } else if (ClassTemplateDecl *CTD =
+                 dyn_cast<ClassTemplateDecl>(D->getFriendDecl())) {
     Out << "friend ";
     VisitRedeclarableTemplateDecl(CTD);
   }
diff --git a/clang/lib/AST/DeclTemplate.cpp b/clang/lib/AST/DeclTemplate.cpp
index 99d02fdc99e92..6c19c347d87fc 100644
--- a/clang/lib/AST/DeclTemplate.cpp
+++ b/clang/lib/AST/DeclTemplate.cpp
@@ -1231,21 +1231,34 @@ void FriendTemplateDecl::anchor() {}
 
 FriendTemplateDecl *
 FriendTemplateDecl::Create(ASTContext &Context, DeclContext *DC,
-                           SourceLocation L,
-                           MutableArrayRef<TemplateParameterList *> Params,
-                           FriendUnion Friend, SourceLocation FLoc) {
-  TemplateParameterList **TPL = nullptr;
-  if (!Params.empty()) {
-    TPL = new (Context) TemplateParameterList *[Params.size()];
-    llvm::copy(Params, TPL);
-  }
-  return new (Context, DC)
-      FriendTemplateDecl(DC, L, TPL, Params.size(), Friend, FLoc);
+                           SourceLocation Loc, FriendUnion Friend,
+                           SourceLocation FriendLoc,
+                           ArrayRef<TemplateParameterList *> FriendTypeTPLists,
+                           SourceLocation EllipsisLoc) {
+  std::size_t Extra =
+      FriendTemplateDecl::additionalSizeToAlloc<TemplateParameterList *>(
+          FriendTypeTPLists.size());
+  auto *FTD = new (Context, DC, Extra) FriendTemplateDecl(
+      DC, Loc, Friend, FriendLoc, EllipsisLoc, FriendTypeTPLists);
+  cast<CXXRecordDecl>(DC)->pushFriendDecl(FTD);
+  return FTD;
 }
 
-FriendTemplateDecl *FriendTemplateDecl::CreateDeserialized(ASTContext &C,
-                                                           GlobalDeclID ID) {
-  return new (C, ID) FriendTemplateDecl(EmptyShell());
+FriendTemplateDecl *
+FriendTemplateDecl::CreateDeserialized(ASTContext &C, GlobalDeclID ID,
+                                       unsigned NumFriendTypeTPLists) {
+  std::size_t Extra =
+      FriendTemplateDecl::additionalSizeToAlloc<TemplateParameterList *>(
+          NumFriendTypeTPLists);
+  return new (C, ID, Extra)
+      FriendTemplateDecl(EmptyShell(), NumFriendTypeTPLists);
+}
+
+SourceRange FriendTemplateDecl::getSourceRange() const {
+  SourceLocation Begin =
+      getFriendTypeTemplateParameterList(0)->getTemplateLoc();
+  SourceLocation End = FriendDecl::getSourceRange().getEnd();
+  return SourceRange(Begin, End);
 }
 
 //===----------------------------------------------------------------------===//
diff --git a/clang/lib/Sema/Sema.cpp b/clang/lib/Sema/Sema.cpp
index ef45d5842c795..27c99ef7691a6 100644
--- a/clang/lib/Sema/Sema.cpp
+++ b/clang/lib/Sema/Sema.cpp
@@ -1163,8 +1163,9 @@ static bool IsRecordFullyDefined(const CXXRecordDecl *RD,
   for (CXXRecordDecl::friend_iterator I = RD->friend_begin(),
                                       E = RD->friend_end();
        I != E && Complete; ++I) {
+    FriendDecl *Friend = *I;
     // Check if friend classes and methods are complete.
-    if (TypeSourceInfo *TSI = (*I)->getFriendType()) {
+    if (TypeSourceInfo *TSI = Friend->getFriendType()) {
       // Friend classes are available as the TypeSourceInfo of the FriendDecl.
       if (CXXRecordDecl *FriendD = TSI->getType()->getAsCXXRecordDecl())
         Complete = MethodsAndNestedClassesComplete(FriendD, MNCComplete);
@@ -1173,7 +1174,7 @@ static bool IsRecordFullyDefined(const CXXRecordDecl *RD,
     } else {
       // Friend functions are available through the NamedDecl of FriendDecl.
       if (const FunctionDecl *FD =
-          dyn_cast<FunctionDecl>((*I)->getFriendDecl()))
+              dyn_cast<FunctionDecl>(Friend->getFriendDecl()))
         Complete = FD->isDefined();
       else
         // This is a template friend, give up.
diff --git a/clang/lib/Sema/SemaAccess.cpp b/clang/lib/Sema/SemaAccess.cpp
index 17415b4185eff..c516aee3e62d6 100644
--- a/clang/lib/Sema/SemaAccess.cpp
+++ b/clang/lib/Sema/SemaAccess.cpp
@@ -21,6 +21,7 @@
 #include "clang/Sema/DelayedDiagnostic.h"
 #include "clang/Sema/Initialization.h"
 #include "clang/Sema/Lookup.h"
+#include "clang/Sema/Template.h"
 
 using namespace clang;
 using namespace sema;
@@ -274,6 +275,99 @@ struct AccessTarget : public AccessedEntity {
 
 }
 
+static bool CanDeduceTemplateArguments(Sema &S, TemplateParameterList *TPL,
+                                       ArrayRef<TemplateArgument> PatternArgs,
+                                       ArrayRef<TemplateArgument> Args,
+                                       SourceLocation Loc) {
+  if (PatternArgs.size() != Args.size())
+    return false;
+
+  auto Equal =
+      llvm::equal(PatternArgs, Args,
+                  [](const TemplateArgument &LHS, const TemplateArgument &RHS) {
+                    return LHS.structurallyEquals(RHS);
+                  });
+  if (Equal)
+    return true;
+
+  TemplateDeductionInfo Info(Loc);
+  SmallVector<DeducedTemplateArgument, 4> Deduced(TPL->size());
+  S.DeduceTemplateArguments(TPL, PatternArgs, Args, Info, Deduced,
+                            /*NumberOfArgumentsMustMatch=*/false);
+
+  for (const DeducedTemplateArgument &Arg : Deduced)
+    if (Arg.isNull())
+      return false;
+
+  return true;
+}
+
+static CanQual<FunctionProtoType>
+GetCanonicalFunctionProto(Sema &S, const FunctionDecl *FD) {
+  return S.Context.getCanonicalType(FD->getType())->getAs<FunctionProtoType>();
+}
+
+static const TemplateSpecializationType *
+TryGetTemplateSpecializationType(Sema &S, NestedNameSpecifier NNS) {
+  if (!NNS)
+    return nullptr;
+
+  QualType Ty(NNS.getAsType(), 0);
+  if (Ty.isNull())
+    return nullptr;
+
+  Ty = S.Context.getCanonicalType(Ty);
+  if (const auto *ICNT = Ty->getAs<InjectedClassNameType>())
+    Ty = ICNT->getDecl()->getCanonicalTemplateSpecializationType(S.Context);
+
+  return Ty->getAs<TemplateSpecializationType>();
+}
+
+static FunctionTemplateDecl *TryGetFunctionTemplateDecl(FunctionDecl *FD) {
+  if (auto *FTD = FD->getPrimaryTemplate())
+    return FTD->getCanonicalDecl();
+
+  if (auto *FTD = FD->getDescribedFunctionTemplate())
+    return FTD->getCanonicalDecl();
+
+  if (FunctionDecl *Pattern =
+          FD->getTemplateInstantiationPattern(/*ForDefinition=*/false)) {
+    if (auto *FTD = Pattern->getDescribedFunctionTemplate())
+      return FTD->getCanonicalDecl();
+    if (auto *FTD = Pattern->getPrimaryTemplate())
+      return FTD->getCanonicalDecl();
+  }
+
+  return nullptr;
+}
+
+static bool MatchesFriendContext(Sema &S, FunctionDecl *FD,
+                                 ClassTemplateDecl *FriendCTD,
+                                 ArrayRef<TemplateArgument> FriendArgs,
+                                 TemplateParameterList *FriendTPL,
+                                 SourceLocation Loc) {
+  const auto *RD = dyn_cast<CXXRecordDecl>(FD->getDeclContext());
+  if (!RD)
+    return false;
+
+  ClassTemplateDecl *ContextCTD = RD->getDescribedClassTemplate();
+  ArrayRef<TemplateArgument> ContextArgs;
+  if (ContextCTD) {
+    ContextArgs = ContextCTD->getInjectedTemplateArgs(S.Context);
+  } else {
+    const auto *CTSD = dyn_cast<ClassTemplateSpecializationDecl>(RD);
+    if (!CTSD)
+      return false;
+    ContextCTD = CTSD->getSpecializedTemplate();
+    ContextArgs = CTSD->getTemplateArgs().asArray();
+  }
+
+  if (ContextCTD->getCanonicalDecl() != FriendCTD->getCanonicalDecl())
+    return false;
+
+  return CanDeduceTemplateArguments(S, FriendTPL, FriendArgs, ContextArgs, Loc);
+}
+
 /// Checks whether one class might instantiate to the other.
 static bool MightInstantiateTo(const CXXRecordDecl *From,
                                const CXXRecordDecl *To) {
@@ -283,8 +377,12 @@ static bool MightInstantiateTo(const CXXRecordDecl *From,
 
   const DeclContext *FromDC = From->getDeclContext()->getPrimaryContext();
   const DeclContext *ToDC = To->getDeclContext()->getPrimaryContext();
-  if (FromDC == ToDC) return true;
-  if (FromDC->isFileContext() || ToDC->isFileContext()) return false;
+
+  if (FromDC == ToDC)
+    return true;
+
+  if (FromDC->isFileContext() || ToDC->isFileContext())
+    return false;
 
   // Be conservative.
   return true;
@@ -342,7 +440,6 @@ static AccessResult IsDerivedFromInclusive(const CXXRecordDecl *Derived,
   return OnFailure;
 }
 
-
 static bool MightInstantiateTo(Sema &S, DeclContext *Context,
                                DeclContext *Friend) {
   if (Friend == Context)
@@ -374,49 +471,67 @@ static bool MightInstantiateTo(Sema &S, CanQualType Context, CanQualType Friend)
   return true;
 }
 
-static bool MightInstantiateTo(Sema &S,
-                               FunctionDecl *Context,
-                               FunctionDecl *Friend) {
-  if (Context->getDeclName() != Friend->getDeclName())
+static bool MightInstantiateTo(Sema &S, CanQual<FunctionProtoType> Context,
+                               CanQual<FunctionProtoType> Friend) {
+  if (Friend.getQualifiers() != Context.getQualifiers())
+    return false;
+
+  if (Friend->getNumParams() != Context->getNumParams())
     return false;
 
-  if (!MightInstantiateTo(S,
-                          Context->getDeclContext(),
-                          Friend->getDeclContext()))
+  if (!MightInstantiateTo(S, Context->getReturnType(), Friend->getReturnType()))
+    return false;
+
+  for (unsigned I = 0, E = Friend->getNumParams(); I != E; ++I)
+    if (!MightInstantiateTo(S, Context->getParamType(I),
+                            Friend->getParamType(I)))
+      return false;
+
+  return true;
+}
+
+static bool MightInstantiateTo(Sema &S, DeclarationName Context,
+                               DeclarationName Friend) {
+  if (Context == Friend)
+    return true;
+
+  if (Context.getNameKind() != Friend.getNameKind())
     return false;
 
-  CanQual<FunctionProtoType> FriendTy
-    = S.Context.getCanonicalType(Friend->getType())
-         ->getAs<FunctionProtoType>();
-  CanQual<FunctionProtoType> ContextTy
-    = S.Context.getCanonicalType(Context->getType())
-         ->getAs<FunctionProtoType>();
+  switch (Context.getNameKind()) {
+  case DeclarationName::CXXConstructorName:
+  case DeclarationName::CXXDestructorName:
+  case DeclarationName::CXXConversionFunctionName:
+    return MightInstantiateTo(
+        S, S.Context.getCanonicalType(Context.getCXXNameType()),
+        S.Context.getCanonicalType(Friend.getCXXNameType()));
 
-  // There isn't any way that I know of to add qualifiers
-  // during instantiation.
-  if (FriendTy.getQualifiers() != ContextTy.getQualifiers())
+  default:
     return false;
+  }
+}
 
-  if (FriendTy->getNumParams() != ContextTy->getNumParams())
+static bool MightInstantiateTo(Sema &S, FunctionDecl *Context,
+                               FunctionDecl *Friend) {
+  if (!MightInstantiateTo(S, Context->getDeclName(), Friend->getDeclName()))
     return false;
 
-  if (!MightInstantiateTo(S, ContextTy->getReturnType(),
-                          FriendTy->getReturnType()))
+  DeclContext *ContextDC = Context->getDeclContext();
+  DeclContext *FriendDC = Friend->getDeclContext();
+
+  if (!FriendDC->isDependentContext() &&
+      !MightInstantiateTo(S, ContextDC, FriendDC))
     return false;
 
-  for (unsigned I = 0, E = FriendTy->getNumParams(); I != E; ++I)
-    if (!MightInstantiateTo(S, ContextTy->getParamType(I),
-                            FriendTy->getParamType(I)))
-      return false;
+  CanQual<FunctionProtoType> FriendTy = GetCanonicalFunctionProto(S, Friend);
+  CanQual<FunctionProtoType> ContextTy = GetCanonicalFunctionProto(S, Context);
 
-  return true;
+  return MightInstantiateTo(S, ContextTy, FriendTy);
 }
 
-static bool MightInstantiateTo(Sema &S,
-                               FunctionTemplateDecl *Context,
+static bool MightInstantiateTo(Sema &S, FunctionTemplateDecl *Context,
                                FunctionTemplateDecl *Friend) {
-  return MightInstantiateTo(S,
-                            Context->getTemplatedDecl(),
+  return MightInstantiateTo(S, Context->getTemplatedDecl(),
                             Friend->getTemplatedDecl());
 }
 
@@ -533,14 +648,10 @@ static AccessResult MatchesFriend(Sema &S,
   for (SmallVectorImpl<FunctionDecl*>::const_iterator
          I = EC.Functions.begin(), E = EC.Functions.end(); I != E; ++I) {
 
-    FunctionTemplateDecl *FTD = (*I)->getPrimaryTemplate();
-    if (!FTD)
-      FTD = (*I)->getDescribedFunctionTemplate();
+    FunctionTemplateDecl *FTD = TryGetFunctionTemplateDecl(*I);
     if (!FTD)
       continue;
 
-    FTD = FTD->getCanonicalDecl();
-
     if (Friend == FTD)
       return AR_accessible;
 
@@ -551,6 +662,166 @@ static AccessResult MatchesFriend(Sema &S,
   return OnFailure;
 }
 
+static AccessResult MatchesFriend(Sema &S, const EffectiveContext &EC,
+                                  NamedDecl *ND) {
+  ND = cast<NamedDecl>(ND->getCanonicalDecl());
+  if (ClassTemplateDecl *CTD = dyn_cast<ClassTemplateDecl>(ND))
+    return MatchesFriend(S, EC, CTD);
+
+  if (FunctionTemplateDecl *FTD = dyn_cast<FunctionTemplateDecl>(ND))
+    return MatchesFriend(S, EC, FTD);
+
+  if (CXXRecordDecl *RD = dyn_cast<CXXRecordDecl>(ND))
+    return MatchesFriend(S, EC, RD);
+
+  assert(isa<FunctionDecl>(ND) && "unknown friend decl kind");
+  return MatchesFriend(S, EC, cast<FunctionDecl>(ND));
+}
+
+static AccessResult MatchesFriend(Sema &S, const EffectiveContext &EC,
+                                  FriendTemplateDecl *FriendTD,
+                                  FunctionTemplateDecl *FriendFTD) {
+  AccessResult OnFailure = AR_inaccessible;
+  NestedNameSpecifier FriendNNS = FriendFTD->getTemplatedDecl()->getQualifier();
+  const auto *FriendTST = TryGetTemplateSpecializationType(S, FriendNNS);
+  if (!FriendTST)
+    return OnFailure;
+
+  auto *FriendCTD = dyn_cast<ClassTemplateDecl>(
+      FriendTST->getTemplateName().getAsTemplateDecl());
+  if (!FriendCTD)
+    return OnFailure;
+
+  TemplateParameterList *FriendTPL =
+      FriendTD->getFriendTypeTemplateParameterList(0);
+
+  if (!FriendTPL)
+    return OnFailure;
+
+  ArrayRef<TemplateArgument> FriendArgs = FriendTST->template_arguments();
+  SourceLocation FriendLoc = FriendTD->getLocation();
+
+  for (FunctionDecl *FD : EC.Functions) {
+    if (!MatchesFriendContext(S, FD, FriendCTD, FriendArgs, FriendTPL,
+                              FriendLoc))
+      continue;
+
+    FunctionTemplateDecl *ContextFTD = TryGetFunctionTemplateDecl(FD);
+    if (ContextFTD && MightInstantiateTo(S, FriendFTD, ContextFTD))
+      return AR_accessible;
+  }
+
+  return OnFailure;
+}
+
+static AccessResult MatchesFriend(Sema &S, const EffectiveContext &EC,
+                                  FriendTemplateDecl *FriendTD,
+                                  FunctionDecl *FriendFD) {
+  AccessResult OnFailure = AR_inaccessible;
+  NestedNameSpecifier FriendNNS = FriendFD->getQualifier();
+  const auto *FriendTST = TryGetTemplateSpecializationType(S, FriendNNS);
+  if (!FriendTST)
+    return OnFailure;
+
+  auto *FriendCTD = dyn_cast<ClassTemplateDecl>(
+      FriendTST->getTemplateName().getAsTemplateDecl());
+  if (!FriendCTD)
+    return OnFailure;
+
+  TemplateParameterList *FriendTPL =
+      FriendTD->getFriendTypeTemplateParameterList(0);
+  if (!FriendTPL)
+    return OnFailure;
+
+  CanQual<FunctionProtoType> FriendProto =
+      GetCanonicalFunctionProto(S, FriendFD);
+
+  ArrayRef<TemplateArgument> FriendArgs = FriendTST->template_arguments();
+  SourceLocation FriendLoc = FriendTD->getLocation();
+
+  for (FunctionDecl *FD : EC.Functions) {
+    if (!MightInstantiateTo(S, FD->getDeclName(), FriendFD->getDeclName()))
+      continue;
+
+    if (!MatchesFriendContext(S, FD, FriendCTD, FriendArgs, FriendTPL,
+                              FriendLoc))
+      continue;
+
+    CanQual<FunctionProtoType> ContextProto = GetCanonicalFunctionProto(S, FD);
+    if (MightInstantiateTo(S, ContextProto, FriendProto))
+      return AR_accessible;
+  }
+
+  return OnFailure;
+}
+
+static AccessResult MatchesFriend(Sema &S, const EffectiveContext &EC,
+                                  FriendTemplateDecl *FTD, NamedDecl *ND) {
+  if (auto *TD = dyn_cast<FunctionTemplateDecl>(ND))
+    return MatchesFriend(S, EC, FTD, TD);
+
+  if (auto *FD = dyn_cast<FunctionDecl>(ND))
+    return MatchesFriend(S, EC, FTD, FD);
+
+  return MatchesFriend(S, EC, ND);
+}
+
+static AccessResult MatchesFriend(Sema &S, const EffectiveContext &EC,
+                                  FriendTemplateDecl *FTD,
+                                  TypeSourceInfo *TSI) {
+  QualType TypeAsWritten = TSI->getType();
+  if (!TypeAsWritten->isDependentType())
+    return MatchesFriend(S, EC, S.Context.getCanonicalType(TypeAsWritten));
+
+  AccessResult OnFailure = EC.isDependent() ? AR_dependent : AR_inaccessible;
+  const auto *DNT = TypeAsWritten->getAs<DependentNameType>();
+  if (!DNT)
+    return OnFailure;
+
+  NestedNameSpecifier NNS = DNT->getQualifier();
+  if (!NNS)
+    return OnFailure;
+
+  const auto *T = NNS.getAsType();
+  if (!T)
+    return OnFailure;
+
+  const auto *TST =
+      S.Context.getCanonicalType(T)->getAsNonAliasTemplateSpecializationType();
+  if (!TST)
+    return OnFailure;
+
+  auto *CTD =
+      dyn_cast<ClassTemplateDecl>(TST->getTemplateName().getAsTemplateDecl());
+  if (!CTD)
+    return OnFailure;
+
+  TemplateParameterList *TPL = FTD->getFriendTypeTemplateParameterList(0);
+  if (!TPL)
+    return OnFailure;
+
+  for (CXXRecordDecl *RD : EC.Records) {
+    if (RD->getDeclName() != DNT->getIdentifier())
+      continue;
+
+    const auto *CTSD =
+        dyn_cast<ClassTemplateSpecializationDecl>(RD->getDeclContext());
+    if (!CTSD)
+      continue;
+
+    if (CTSD->getSpecializedTemplate()->getCanonicalDecl() !=
+        CTD->getCanonicalDecl())
+      continue;
+
+    if (CanDeduceTemplateArguments(S, TPL, TST->template_arguments(),
+                                   CTSD->getTemplateArgs().asArray(),
+                                   FTD->getLocation()))
+      return AR_accessible;
+  }
+
+  return OnFailure;
+}
+
 /// Determines whether the given friend declaration matches anything
 /// in the effective context.
 static AccessResult MatchesFriend(Sema &S,
@@ -561,25 +832,27 @@ static AccessResult MatchesFriend(Sema &S,
   if (FriendD->isInvalidDecl() || FriendD->isUnsupportedFriend())
     return AR_accessible;
 
+  if (NamedDecl *Friend = FriendD->getFriendDecl())
+    return MatchesFriend(S, EC, Friend);
+
   if (TypeSourceInfo *T = FriendD->getFriendType())
     return MatchesFriend(S, EC, T->getType()->getCanonicalTypeUnqualified());
 
-  NamedDecl *Friend
-    = cast<NamedDecl>(FriendD->getFriendDecl()->getCanonicalDecl());
-
-  // FIXME: declarations with dependent or templated scope.
+  return AR_inaccessible;
+}
 
-  if (isa<ClassTemplateDecl>(Friend))
-    return MatchesFriend(S, EC, cast<ClassTemplateDecl>(Friend));
+static AccessResult MatchesFriend(Sema &S, const EffectiveContext &EC,
+                                  FriendTemplateDecl *FTD) {
+  if (FTD->isInvalidDecl() || FTD->isUnsupportedFriend())
+    return AR_accessible;
 
-  if (isa<FunctionTemplateDecl>(Friend))
-    return MatchesFriend(S, EC, cast<FunctionTemplateDecl>(Friend));
+  if (NamedDecl *ND = FTD->getFriendDecl())
+    return MatchesFriend(S, EC, FTD, ND);
 
-  if (isa<CXXRecordDecl>(Friend))
-    return MatchesFriend(S, EC, cast<CXXRecordDecl>(Friend));
+  if (TypeSourceInfo *TSI = FTD->getFriendType())
+    return MatchesFriend(S, EC, FTD, TSI);
 
-  assert(isa<FunctionDecl>(Friend) && "unknown friend decl kind");
-  return MatchesFriend(S, EC, cast<FunctionDecl>(Friend));
+  return AR_inaccessible;
 }
 
 static AccessResult GetFriendKind(Sema &S,
@@ -588,8 +861,14 @@ static AccessResult GetFriendKind(Sema &S,
   AccessResult OnFailure = AR_inaccessible;
 
   // Okay, check friends.
-  for (auto *Friend : Class->friends()) {
-    switch (MatchesFriend(S, EC, Friend)) {
+  for (FriendDecl *Friend : Class->friends()) {
+    AccessResult AR;
+    if (auto *FTD = dyn_cast<FriendTemplateDecl>(Friend))
+      AR = MatchesFriend(S, EC, FTD);
+    else
+      AR = MatchesFriend(S, EC, cast<FriendDecl>(Friend));
+
+    switch (AR) {
     case AR_accessible:
       return AR_accessible;
 
diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index c1d3960e65ef6..6435dc250470d 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -58,6 +58,7 @@
 #include <set>
 
 using namespace clang;
+using namespace sema;
 
 //===----------------------------------------------------------------------===//
 // CheckDefaultArgumentVisitor
@@ -6211,8 +6212,8 @@ static void CheckAbstractClassUsage(AbstractUsageInfo &Info,
     if (D->isImplicit()) continue;
 
     // Step through friends to the befriended declaration.
-    if (auto *FD = dyn_cast<FriendDecl>(D)) {
-      D = FD->getFriendDecl();
+    if (D->getKind() == Decl::Friend) {
+      D = cast<FriendDecl>(D)->getFriendDecl();
       if (!D) continue;
     }
 
@@ -7330,9 +7331,9 @@ void Sema::CheckCompletedCXXClass(Scope *S, CXXRecordDecl *Record) {
 
       if (!isa<CXXDestructorDecl>(M))
         CompleteMemberFunction(M);
-    } else if (auto *F = dyn_cast<FriendDecl>(D)) {
+    } else if (D->getKind() == Decl::Friend) {
       CheckForDefaultedFunction(
-          dyn_cast_or_null<FunctionDecl>(F->getFriendDecl()));
+          dyn_cast_or_null<FunctionDecl>(cast<FriendDecl>(D)->getFriendDecl()));
     }
   }
 
@@ -18029,6 +18030,33 @@ Decl *Sema::BuildStaticAssertDeclaration(SourceLocation StaticAssertLoc,
   return Decl;
 }
 
+bool Sema::CheckDependentFriend(SourceLocation Loc, NestedNameSpecifier NNS,
+                                TemplateParameterList *FPL) {
+  if (!NNS || !FPL || FPL->size() == 0)
+    return false;
+
+  if (NNS.isDependent()) {
+    if (NNS.getKind() == NestedNameSpecifier::Kind::Type) {
+      QualType T(NNS.getCanonical().getAsType(), 0);
+      if (isa<PackIndexingType>(T))
+        return false;
+
+      if (const auto *TST = dyn_cast<TemplateSpecializationType>(T)) {
+        if (isa<ClassTemplateDecl>(TST->getTemplateName().getAsTemplateDecl()))
+          return false;
+      }
+
+      if (isa<InjectedClassNameType>(T))
+        return false;
+    }
+
+    Diag(Loc, diag::err_dependent_friend_not_member);
+    return true;
+  }
+
+  return false;
+}
+
 DeclResult Sema::ActOnTemplatedFriendTag(
     Scope *S, SourceLocation FriendLoc, unsigned TagSpec, SourceLocation TagLoc,
     CXXScopeSpec &SS, IdentifierInfo *Name, SourceLocation NameLoc,
@@ -18099,9 +18127,8 @@ DeclResult Sema::ActOnTemplatedFriendTag(
     if (T.isNull())
       return true;
 
-    FriendDecl *Friend =
-        FriendDecl::Create(Context, CurContext, NameLoc, TSI, FriendLoc,
-                           EllipsisLoc, TempParamLists);
+    FriendDecl *Friend = FriendDecl::Create(Context, CurContext, NameLoc, TSI,
+                                            FriendLoc, EllipsisLoc);
     Friend->setAccess(AS_public);
     CurContext->addDecl(Friend);
     return Friend;
@@ -18127,25 +18154,41 @@ DeclResult Sema::ActOnTemplatedFriendTag(
     }
   }
 
-  // Handle the case of a templated-scope friend class.  e.g.
-  //   template <class T> class A<T>::B;
-  // FIXME: we don't support these right now.
-  Diag(NameLoc, diag::warn_template_qualified_friend_unsupported)
-    << SS.getScopeRep() << SS.getRange() << cast<CXXRecordDecl>(CurContext);
+  NestedNameSpecifier NNS = SS.getScopeRep();
+  if (EllipsisLoc.isInvalid() &&
+      CheckDependentFriend(TagLoc, NNS, TempParamLists.front()))
+    return true;
+
   ElaboratedTypeKeyword ETK = TypeWithKeyword::getKeywordForTagTypeKind(Kind);
-  QualType T = Context.getDependentNameType(ETK, SS.getScopeRep(), Name);
+  QualType T = Context.getDependentNameType(ETK, NNS, Name);
   TypeSourceInfo *TSI = Context.CreateTypeSourceInfo(T);
+
   DependentNameTypeLoc TL = TSI->getTypeLoc().castAs<DependentNameTypeLoc>();
   TL.setElaboratedKeywordLoc(TagLoc);
   TL.setQualifierLoc(SS.getWithLocInContext(Context));
   TL.setNameLoc(NameLoc);
 
-  FriendDecl *Friend =
-      FriendDecl::Create(Context, CurContext, NameLoc, TSI, FriendLoc,
-                         EllipsisLoc, TempParamLists);
+  Decl *Friend;
+  if (TempParamLists.empty())
+    Friend = FriendDecl::Create(Context, CurContext, NameLoc, TSI, FriendLoc,
+                                EllipsisLoc);
+  else {
+    if (CheckTemplateDeclScope(S, TempParamLists.back()))
+      return true;
+
+    Friend = FriendTemplateDecl::Create(Context, CurContext, NameLoc, TSI,
+                                        FriendLoc, TempParamLists, EllipsisLoc);
+  }
+
+  if (EllipsisLoc.isValid() && NNS.isDependent()) {
+    Diag(NameLoc, diag::warn_template_qualified_friend_unsupported)
+        << SS.getScopeRep() << SS.getRange() << cast<CXXRecordDecl>(CurContext);
+    cast<FriendDecl>(Friend)->setUnsupportedFriend(true);
+  }
+
   Friend->setAccess(AS_public);
-  Friend->setUnsupportedFriend(true);
   CurContext->addDecl(Friend);
+
   return Friend;
 }
 
@@ -18246,11 +18289,14 @@ Decl *Sema::ActOnFriendTypeDecl(Scope *S, const DeclSpec &DS,
   // friend a member of an arbitrary specialization of your template).
 
   Decl *D;
-  if (!TempParams.empty())
+  if (!TempParams.empty()) {
+    if (CheckTemplateDeclScope(S, TempParams.back()))
+      return nullptr;
+
     // TODO: Support variadic friend template decls?
-    D = FriendTemplateDecl::Create(Context, CurContext, Loc, TempParams, TSI,
-                                   FriendLoc);
-  else
+    D = FriendTemplateDecl::Create(Context, CurContext, Loc, TSI, FriendLoc,
+                                   TempParams, EllipsisLoc);
+  } else
     D = FriendDecl::Create(Context, CurContext, TSI->getTypeLoc().getBeginLoc(),
                            TSI, FriendLoc, EllipsisLoc);
 
@@ -18437,6 +18483,11 @@ NamedDecl *Sema::ActOnFriendFunctionDecl(Scope *S, Declarator &D,
     assert(isa<CXXRecordDecl>(DC) && "friend declaration not in class?");
   }
 
+  if (TemplateParams.size() && SS.isValid() &&
+      CheckDependentFriend(NameInfo.getLoc(), SS.getScopeRep(),
+                           TemplateParams.front()))
+    return nullptr;
+
   if (!DC->isRecord()) {
     int DiagArg = -1;
     switch (D.getName().getKind()) {
@@ -18500,54 +18551,51 @@ NamedDecl *Sema::ActOnFriendFunctionDecl(Scope *S, Declarator &D,
       PushOnScopeChains(ND, EnclosingScope, /*AddToContext=*/ false);
   }
 
-  FriendDecl *FrD = FriendDecl::Create(Context, CurContext,
-                                       D.getIdentifierLoc(), ND,
-                                       DS.getFriendSpecLoc());
-  FrD->setAccess(AS_public);
-  CurContext->addDecl(FrD);
+  warnOnReservedIdentifier(ND);
 
-  if (ND->isInvalidDecl()) {
-    FrD->setInvalidDecl();
-  } else {
-    if (DC->isRecord()) CheckFriendAccess(ND);
+  if (ND->isInvalidDecl())
+    return ND;
 
-    FunctionDecl *FD;
-    if (FunctionTemplateDecl *FTD = dyn_cast<FunctionTemplateDecl>(ND))
-      FD = FTD->getTemplatedDecl();
-    else
-      FD = cast<FunctionDecl>(ND);
-
-    // C++ [class.friend]p6:
-    //   A function may be defined in a friend declaration of a class if and
-    //   only if the class is a non-local class, and the function name is
-    //   unqualified.
-    if (D.isFunctionDefinition()) {
-      // Qualified friend function definition.
-      if (SS.isNotEmpty()) {
-        // FIXME: We should only do this if the scope specifier names the
-        // innermost enclosing namespace; otherwise the fixit changes the
-        // meaning of the code.
-        SemaDiagnosticBuilder DB =
-            Diag(SS.getRange().getBegin(), diag::err_qualified_friend_def);
-
-        DB << SS.getScopeRep();
-        if (DC->isFileContext())
-          DB << FixItHint::CreateRemoval(SS.getRange());
-
-        // Friend function defined in a local class.
-      } else if (FunctionContainingLocalClass) {
-        Diag(NameInfo.getBeginLoc(), diag::err_friend_def_in_local_class);
-
-        // Per [basic.pre]p4, a template-id is not a name. Therefore, if we have
-        // a template-id, the function name is not unqualified because these is
-        // no name. While the wording requires some reading in-between the
-        // lines, GCC, MSVC, and EDG all consider a friend function
-        // specialization definitions to be de facto explicit specialization
-        // and diagnose them as such.
-      } else if (isTemplateId) {
-        Diag(NameInfo.getBeginLoc(), diag::err_friend_specialization_def);
-      }
+  if (DC->isRecord())
+    CheckFriendAccess(ND);
+
+  FunctionDecl *FD;
+  if (FunctionTemplateDecl *FTD = dyn_cast<FunctionTemplateDecl>(ND))
+    FD = FTD->getTemplatedDecl();
+  else
+    FD = cast<FunctionDecl>(ND);
+
+  // C++ [class.friend]p6:
+  //   A function may be defined in a friend declaration of a class if and
+  //   only if the class is a non-local class, and the function name is
+  //   unqualified.
+  if (D.isFunctionDefinition()) {
+    // Qualified friend function definition.
+    if (SS.isNotEmpty()) {
+      // FIXME: We should only do this if the scope specifier names the
+      // innermost enclosing namespace; otherwise the fixit changes the
+      // meaning of the code.
+      SemaDiagnosticBuilder DB =
+          Diag(SS.getRange().getBegin(), diag::err_qualified_friend_def);
+
+      DB << SS.getScopeRep();
+      if (DC->isFileContext())
+        DB << FixItHint::CreateRemoval(SS.getRange());
+
+      // Friend function defined in a local class.
+    } else if (FunctionContainingLocalClass) {
+      Diag(NameInfo.getBeginLoc(), diag::err_friend_def_in_local_class);
+
+      // Per [basic.pre]p4, a template-id is not a name. Therefore, if we have
+      // a template-id, the function name is not unqualified because these is
+      // no name. While the wording requires some reading in-between the
+      // lines, GCC, MSVC, and EDG all consider a friend function
+      // specialization definitions to be de facto explicit specialization
+      // and diagnose them as such.
+    } else if (isTemplateId) {
+      Diag(NameInfo.getBeginLoc(), diag::err_friend_specialization_def);
     }
+  }
 
     // C++11 [dcl.fct.default]p4: If a friend declaration specifies a
     // default argument expression, that declaration shall be a definition
@@ -18565,18 +18613,27 @@ NamedDecl *Sema::ActOnFriendFunctionDecl(Scope *S, Declarator &D,
         Diag(FD->getLocation(), diag::err_friend_decl_with_def_arg_must_be_def);
     }
 
-    // Mark templated-scope function declarations as unsupported.
-    if (FD->getNumTemplateParameterLists() && SS.isValid()) {
-      Diag(FD->getLocation(), diag::warn_template_qualified_friend_unsupported)
-        << SS.getScopeRep() << SS.getRange()
-        << cast<CXXRecordDecl>(CurContext);
-      FrD->setUnsupportedFriend(true);
-    }
-  }
+    unsigned NumTPLists = FD->getNumTemplateParameterLists();
+    Decl *Friend;
+    if (NumTPLists && SS.isValid()) {
+      SmallVector<TemplateParameterList *, 1> TPL(NumTPLists);
+      for (unsigned I = 0, N = NumTPLists; I != N; ++I)
+        TPL[I] = FD->getTemplateParameterList(I);
 
-  warnOnReservedIdentifier(ND);
+      if (CheckTemplateDeclScope(S, TPL.back()))
+        return nullptr;
 
-  return ND;
+      Friend =
+          FriendTemplateDecl::Create(Context, CurContext, D.getIdentifierLoc(),
+                                     ND, DS.getFriendSpecLoc(), TPL);
+    } else {
+      Friend = FriendDecl::Create(Context, CurContext, D.getIdentifierLoc(), ND,
+                                  DS.getFriendSpecLoc());
+    }
+    Friend->setAccess(AS_public);
+    CurContext->addDecl(Friend);
+
+    return ND;
 }
 
 void Sema::SetDeclDeleted(Decl *Dcl, SourceLocation DelLoc,
diff --git a/clang/lib/Sema/SemaTemplate.cpp b/clang/lib/Sema/SemaTemplate.cpp
index c436b7018a2bd..afd087afa048d 100644
--- a/clang/lib/Sema/SemaTemplate.cpp
+++ b/clang/lib/Sema/SemaTemplate.cpp
@@ -11266,14 +11266,11 @@ Sema::CheckTypenameType(ElaboratedTypeKeyword Keyword,
   return T;
 }
 
-/// Build the type that describes a C++ typename specifier,
-/// e.g., "typename T::type".
-QualType
-Sema::CheckTypenameType(ElaboratedTypeKeyword Keyword,
-                        SourceLocation KeywordLoc,
-                        NestedNameSpecifierLoc QualifierLoc,
-                        const IdentifierInfo &II,
-                        SourceLocation IILoc, bool DeducedTSTContext) {
+QualType Sema::CheckTypenameType(ElaboratedTypeKeyword Keyword,
+                                 SourceLocation KeywordLoc,
+                                 NestedNameSpecifierLoc QualifierLoc,
+                                 const IdentifierInfo &II, SourceLocation IILoc,
+                                 bool DeducedTSTContext) {
   assert((Keyword != ElaboratedTypeKeyword::None) == KeywordLoc.isValid());
 
   CXXScopeSpec SS;
@@ -11307,6 +11304,7 @@ Sema::CheckTypenameType(ElaboratedTypeKeyword Keyword,
     LookupQualifiedName(Result, Ctx, SS);
   else
     LookupName(Result, CurScope);
+
   unsigned DiagID = 0;
   Decl *Referenced = nullptr;
   switch (Result.getResultKind()) {
diff --git a/clang/lib/Sema/SemaTemplateInstantiate.cpp b/clang/lib/Sema/SemaTemplateInstantiate.cpp
index 5381a5a6f110d..3d9e9d1748c84 100644
--- a/clang/lib/Sema/SemaTemplateInstantiate.cpp
+++ b/clang/lib/Sema/SemaTemplateInstantiate.cpp
@@ -1321,6 +1321,7 @@ namespace {
     const MultiLevelTemplateArgumentList &TemplateArgs;
     SourceLocation Loc;
     DeclarationName Entity;
+
     // Whether to evaluate the C++20 constraints or simply substitute into them.
     bool EvaluateConstraints = true;
     // Whether Substitution was Incomplete, that is, we tried to substitute in
@@ -2831,8 +2832,7 @@ TemplateInstantiator::TransformNestedRequirement(
 
 TypeSourceInfo *Sema::SubstType(TypeSourceInfo *T,
                                 const MultiLevelTemplateArgumentList &Args,
-                                SourceLocation Loc,
-                                DeclarationName Entity,
+                                SourceLocation Loc, DeclarationName Entity,
                                 bool AllowDeducedTST) {
   assert(!CodeSynthesisContexts.empty() &&
          "Cannot perform an instantiation without some context on the "
@@ -2849,8 +2849,7 @@ TypeSourceInfo *Sema::SubstType(TypeSourceInfo *T,
 
 TypeSourceInfo *Sema::SubstType(TypeLoc TL,
                                 const MultiLevelTemplateArgumentList &Args,
-                                SourceLocation Loc,
-                                DeclarationName Entity) {
+                                SourceLocation Loc, DeclarationName Entity) {
   assert(!CodeSynthesisContexts.empty() &&
          "Cannot perform an instantiation without some context on the "
          "instantiation stack");
@@ -2922,13 +2921,10 @@ static bool NeedsInstantiationAsFunctionType(TypeSourceInfo *T) {
   return false;
 }
 
-TypeSourceInfo *Sema::SubstFunctionDeclType(TypeSourceInfo *T,
-                                const MultiLevelTemplateArgumentList &Args,
-                                SourceLocation Loc,
-                                DeclarationName Entity,
-                                CXXRecordDecl *ThisContext,
-                                Qualifiers ThisTypeQuals,
-                                bool EvaluateConstraints) {
+TypeSourceInfo *Sema::SubstFunctionDeclType(
+    TypeSourceInfo *T, const MultiLevelTemplateArgumentList &Args,
+    SourceLocation Loc, DeclarationName Entity, CXXRecordDecl *ThisContext,
+    Qualifiers ThisTypeQuals, bool EvaluateConstraints) {
   assert(!CodeSynthesisContexts.empty() &&
          "Cannot perform an instantiation without some context on the "
          "instantiation stack");
@@ -3147,7 +3143,7 @@ Sema::SubstParmVarDecl(ParmVarDecl *OldParm,
     }
   } else {
     NewTSI = SubstType(OldTSI, TemplateArgs, OldParm->getLocation(),
-                       OldParm->getDeclName());
+                       OldParm->getDeclName(), /*AllowDeducedTST=*/false);
   }
 
   if (!NewTSI)
diff --git a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
index 09c2482168ab7..0a33307ae535e 100644
--- a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
+++ b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
@@ -3180,29 +3180,6 @@ Decl *TemplateDeclInstantiator::VisitCXXMethodDecl(
     D->setTypeSourceInfo(TSI);
   }
 
-  SmallVector<ParmVarDecl *, 4> Params;
-  TypeSourceInfo *TInfo = SubstFunctionType(D, Params);
-  if (!TInfo)
-    return nullptr;
-  QualType T = adjustFunctionTypeForInstantiation(SemaRef.Context, D, TInfo);
-
-  if (TemplateParams && TemplateParams->size()) {
-    auto *LastParam =
-        dyn_cast<TemplateTypeParmDecl>(TemplateParams->asArray().back());
-    if (LastParam && LastParam->isImplicit() &&
-        LastParam->hasTypeConstraint()) {
-      // In abbreviated templates, the type-constraints of invented template
-      // type parameters are instantiated with the function type, invalidating
-      // the TemplateParameterList which relied on the template type parameter
-      // not having a type constraint. Recreate the TemplateParameterList with
-      // the updated parameter list.
-      TemplateParams = TemplateParameterList::Create(
-          SemaRef.Context, TemplateParams->getTemplateLoc(),
-          TemplateParams->getLAngleLoc(), TemplateParams->asArray(),
-          TemplateParams->getRAngleLoc(), TemplateParams->getRequiresClause());
-    }
-  }
-
   NestedNameSpecifierLoc QualifierLoc = D->getQualifierLoc();
   if (QualifierLoc) {
     QualifierLoc = SemaRef.SubstNestedNameSpecifierLoc(QualifierLoc,
@@ -3233,14 +3210,39 @@ Decl *TemplateDeclInstantiator::VisitCXXMethodDecl(
 
   DeclarationNameInfo NameInfo
     = SemaRef.SubstDeclarationNameInfo(D->getNameInfo(), TemplateArgs);
+  if (!NameInfo.getName())
+    return nullptr;
+
+  CXXMethodDecl *Method = nullptr;
+  SourceLocation StartLoc = D->getInnerLocStart();
+
+  SmallVector<ParmVarDecl *, 4> Params;
+  TypeSourceInfo *TInfo = SubstFunctionType(D, Params);
+  if (!TInfo)
+    return nullptr;
+
+  QualType T = adjustFunctionTypeForInstantiation(SemaRef.Context, D, TInfo);
+
+  if (TemplateParams && TemplateParams->size()) {
+    auto *LastParam =
+        dyn_cast<TemplateTypeParmDecl>(TemplateParams->asArray().back());
+    if (LastParam && LastParam->isImplicit() &&
+        LastParam->hasTypeConstraint()) {
+      // In abbreviated templates, the type-constraints of invented template
+      // type parameters are instantiated with the function type, invalidating
+      // the TemplateParameterList which relied on the template type parameter
+      // not having a type constraint. Recreate the TemplateParameterList with
+      // the updated parameter list.
+      TemplateParams = TemplateParameterList::Create(
+          SemaRef.Context, TemplateParams->getTemplateLoc(),
+          TemplateParams->getLAngleLoc(), TemplateParams->asArray(),
+          TemplateParams->getRAngleLoc(), TemplateParams->getRequiresClause());
+    }
+  }
 
   if (FunctionRewriteKind != RewriteKind::None)
     adjustForRewrite(FunctionRewriteKind, D, T, TInfo, NameInfo);
 
-  // Build the instantiated method declaration.
-  CXXMethodDecl *Method = nullptr;
-
-  SourceLocation StartLoc = D->getInnerLocStart();
   if (CXXConstructorDecl *Constructor = dyn_cast<CXXConstructorDecl>(D)) {
     Method = CXXConstructorDecl::Create(
         SemaRef.Context, Record, StartLoc, NameInfo, T, TInfo,
@@ -4712,12 +4714,36 @@ Decl *TemplateDeclInstantiator::VisitObjCAtDefsFieldDecl(ObjCAtDefsFieldDecl *D)
 }
 
 Decl *TemplateDeclInstantiator::VisitFriendTemplateDecl(FriendTemplateDecl *D) {
-  // FIXME: We need to be able to instantiate FriendTemplateDecls.
-  unsigned DiagID = SemaRef.getDiagnostics().getCustomDiagID(
-                                               DiagnosticsEngine::Error,
-                                               "cannot instantiate %0 yet");
-  SemaRef.Diag(D->getLocation(), DiagID)
-    << D->getDeclKindName();
+  unsigned NumTPLists = D->getFriendTypeNumTemplateParameterLists();
+  SmallVector<TemplateParameterList *, 1> TPL(NumTPLists);
+  for (unsigned I = 0, N = NumTPLists; I != N; ++I) {
+    TemplateParameterList *InstParams =
+        SubstTemplateParams(D->getFriendTypeTemplateParameterList(I));
+    if (!InstParams)
+      return nullptr;
+
+    TPL[I] = InstParams;
+  }
+
+  Decl *FTD = nullptr;
+  if (TypeSourceInfo *FT = D->getFriendType()) {
+    TypeSourceInfo *TSI = SemaRef.SubstType(FT, TemplateArgs, D->getLocation(),
+                                            DeclarationName());
+    if (TSI)
+      FTD = FriendTemplateDecl::Create(SemaRef.Context, Owner, D->getLocation(),
+                                       TSI, D->getFriendLoc(), TPL);
+  } else {
+    if (cast_or_null<NamedDecl>(SemaRef.FindInstantiatedDecl(
+            D->getLocation(), D->getFriendDecl(), TemplateArgs)))
+      FTD = FriendTemplateDecl::Create(SemaRef.Context, Owner, D->getLocation(),
+                                       D->getFriendDecl(), D->getFriendLoc(),
+                                       TPL);
+  }
+
+  if (FTD) {
+    FTD->setAccess(AS_public);
+    Owner->addDecl(FTD);
+  }
 
   return nullptr;
 }
@@ -5099,9 +5125,8 @@ TemplateDeclInstantiator::InstantiateVarTemplatePartialSpecialization(
   return InstPartialSpec;
 }
 
-TypeSourceInfo*
-TemplateDeclInstantiator::SubstFunctionType(FunctionDecl *D,
-                              SmallVectorImpl<ParmVarDecl *> &Params) {
+TypeSourceInfo *TemplateDeclInstantiator::SubstFunctionType(
+    FunctionDecl *D, SmallVectorImpl<ParmVarDecl *> &Params) {
   TypeSourceInfo *OldTInfo = D->getTypeSourceInfo();
   assert(OldTInfo && "substituting function without type source info");
   assert(Params.empty() && "parameter vector is non-empty at start");
@@ -5170,8 +5195,9 @@ TemplateDeclInstantiator::SubstFunctionType(FunctionDecl *D,
           continue;
         }
 
-        ParmVarDecl *Parm =
-            cast_or_null<ParmVarDecl>(VisitParmVarDecl(OldParam));
+        ParmVarDecl *Parm = SemaRef.SubstParmVarDecl(
+            OldParam, TemplateArgs, /*indexAdjustment=*/0, std::nullopt,
+            /*ExpectParameterPack=*/false, EvaluateConstraints);
         if (!Parm)
           return nullptr;
         Params.push_back(Parm);
diff --git a/clang/lib/Serialization/ASTReaderDecl.cpp b/clang/lib/Serialization/ASTReaderDecl.cpp
index 9033ea55bc5e2..320782f9733af 100644
--- a/clang/lib/Serialization/ASTReaderDecl.cpp
+++ b/clang/lib/Serialization/ASTReaderDecl.cpp
@@ -2397,8 +2397,6 @@ void ASTDeclReader::VisitFriendDecl(FriendDecl *D) {
     D->Friend = readDeclAs<NamedDecl>();
   else
     D->Friend = readTypeSourceInfo();
-  for (unsigned i = 0; i != D->NumTPLists; ++i)
-    D->getTrailingObjects()[i] = Record.readTemplateParameterList();
   D->NextFriend = readDeclID().getRawValue();
   D->UnsupportedFriend = (Record.readInt() != 0);
   D->FriendLoc = readSourceLocation();
@@ -2407,16 +2405,16 @@ void ASTDeclReader::VisitFriendDecl(FriendDecl *D) {
 
 void ASTDeclReader::VisitFriendTemplateDecl(FriendTemplateDecl *D) {
   VisitDecl(D);
-  unsigned NumParams = Record.readInt();
-  D->NumParams = NumParams;
-  D->Params = new (Reader.getContext()) TemplateParameterList *[NumParams];
-  for (unsigned i = 0; i != NumParams; ++i)
-    D->Params[i] = Record.readTemplateParameterList();
+  for (unsigned i = 0; i != D->NumTPLists; ++i)
+    D->getTrailingObjects()[i] = Record.readTemplateParameterList();
   if (Record.readInt()) // HasFriendDecl
     D->Friend = readDeclAs<NamedDecl>();
   else
     D->Friend = readTypeSourceInfo();
+  D->NextFriend = readDeclID().getRawValue();
+  D->UnsupportedFriend = (Record.readInt() != 0);
   D->FriendLoc = readSourceLocation();
+  D->EllipsisLoc = readSourceLocation();
 }
 
 void ASTDeclReader::VisitTemplateDecl(TemplateDecl *D) {
@@ -4044,10 +4042,10 @@ Decl *ASTReader::ReadDeclRecord(GlobalDeclID ID) {
     D = AccessSpecDecl::CreateDeserialized(Context, ID);
     break;
   case DECL_FRIEND:
-    D = FriendDecl::CreateDeserialized(Context, ID, Record.readInt());
+    D = FriendDecl::CreateDeserialized(Context, ID);
     break;
   case DECL_FRIEND_TEMPLATE:
-    D = FriendTemplateDecl::CreateDeserialized(Context, ID);
+    D = FriendTemplateDecl::CreateDeserialized(Context, ID, Record.readInt());
     break;
   case DECL_CLASS_TEMPLATE:
     D = ClassTemplateDecl::CreateDeserialized(Context, ID);
diff --git a/clang/lib/Serialization/ASTWriterDecl.cpp b/clang/lib/Serialization/ASTWriterDecl.cpp
index e415ac1e47862..287b763105b4c 100644
--- a/clang/lib/Serialization/ASTWriterDecl.cpp
+++ b/clang/lib/Serialization/ASTWriterDecl.cpp
@@ -1819,7 +1819,6 @@ void ASTDeclWriter::VisitAccessSpecDecl(AccessSpecDecl *D) {
 void ASTDeclWriter::VisitFriendDecl(FriendDecl *D) {
   // Record the number of friend type template parameter lists here
   // so as to simplify memory allocation during deserialization.
-  Record.push_back(D->NumTPLists);
   VisitDecl(D);
   bool hasFriendDecl = isa<NamedDecl *>(D->Friend);
   Record.push_back(hasFriendDecl);
@@ -1827,8 +1826,6 @@ void ASTDeclWriter::VisitFriendDecl(FriendDecl *D) {
     Record.AddDeclRef(D->getFriendDecl());
   else
     Record.AddTypeSourceInfo(D->getFriendType());
-  for (unsigned i = 0; i < D->NumTPLists; ++i)
-    Record.AddTemplateParameterList(D->getFriendTypeTemplateParameterList(i));
   Record.AddDeclRef(D->getNextFriend());
   Record.push_back(D->UnsupportedFriend);
   Record.AddSourceLocation(D->FriendLoc);
@@ -1838,15 +1835,18 @@ void ASTDeclWriter::VisitFriendDecl(FriendDecl *D) {
 
 void ASTDeclWriter::VisitFriendTemplateDecl(FriendTemplateDecl *D) {
   VisitDecl(D);
-  Record.push_back(D->getNumTemplateParameters());
-  for (unsigned i = 0, e = D->getNumTemplateParameters(); i != e; ++i)
-    Record.AddTemplateParameterList(D->getTemplateParameterList(i));
+  Record.push_back(D->NumTPLists);
+  for (unsigned i = 0, e = D->NumTPLists; i != e; ++i)
+    Record.AddTemplateParameterList(D->getFriendTypeTemplateParameterList(i));
   Record.push_back(D->getFriendDecl() != nullptr);
   if (D->getFriendDecl())
     Record.AddDeclRef(D->getFriendDecl());
   else
     Record.AddTypeSourceInfo(D->getFriendType());
-  Record.AddSourceLocation(D->getFriendLoc());
+  Record.AddDeclRef(D->getNextFriend());
+  Record.push_back(D->UnsupportedFriend);
+  Record.AddSourceLocation(D->FriendLoc);
+  Record.AddSourceLocation(D->EllipsisLoc);
   Code = serialization::DECL_FRIEND_TEMPLATE;
 }
 
diff --git a/clang/test/CXX/class.access/class.friend/p3-cxx0x.cpp b/clang/test/CXX/class.access/class.friend/p3-cxx0x.cpp
index f7216ea7eb7b0..6c55e81c58c18 100644
--- a/clang/test/CXX/class.access/class.friend/p3-cxx0x.cpp
+++ b/clang/test/CXX/class.access/class.friend/p3-cxx0x.cpp
@@ -36,7 +36,7 @@ class A {
 public:
   class foo {};
   static int y;
-  template <typename S> friend class B<S>::ty; // expected-warning {{dependent nested name specifier 'B<S>' for friend class declaration is not supported}}
+  template <typename S> friend class B<S>::ty;
 };
 
 template<typename T> class B { typedef int ty; };
@@ -74,7 +74,7 @@ struct {
       friend
 
   float;
-  template<typename T> friend class A<T>::foo; // expected-warning {{not supported}}
+  template<typename T> friend class A<T>::foo;
 } a;
 
 void testA() { (void)sizeof(A<int>); }
diff --git a/clang/test/CXX/drs/cwg18xx.cpp b/clang/test/CXX/drs/cwg18xx.cpp
index ebee9bc4c3e16..236423067dcb5 100644
--- a/clang/test/CXX/drs/cwg18xx.cpp
+++ b/clang/test/CXX/drs/cwg18xx.cpp
@@ -420,25 +420,20 @@ class C {
 
   template<class T>
   friend struct A<T>::B;
-  // expected-warning at -1 {{dependent nested name specifier 'A<T>' for friend class declaration is not supported; turning off access control for 'C'}}
 
   template<class T>
   friend void A<T>::f();
-  // expected-warning at -1 {{dependent nested name specifier 'A<T>' for friend class declaration is not supported; turning off access control for 'C'}}
 
-  // FIXME: this is ill-formed, because A<T>​::​D does not end with a simple-template-id
   template<class T>
   friend void A<T>::D::g();
-  // expected-warning at -1 {{dependent nested name specifier 'A<T>::D' for friend class declaration is not supported; turning off access control for 'C'}}
+  // expected-error at -1 {{friend declaration does not name a member of a class template specialization}}
 
   template<class T>
   friend int *A<T*>::h();
-  // expected-warning at -1 {{dependent nested name specifier 'A<T *>' for friend class declaration is not supported; turning off access control for 'C'}}
 
   template<class T>
   template<T U>
   friend T A<T>::i();
-  // expected-warning at -1 {{dependent nested name specifier 'A<T>' for friend class declaration is not supported; turning off access control for 'C'}}
 };
 
 C c;
@@ -450,11 +445,15 @@ void A<int>::B::e() { (void)c.private_int; }
 template<class T>
 void A<T>::f() { (void)c.private_int; }
 int A<int>::f() { (void)c.private_int; return 0; }
+// expected-error at -1 {{'private_int' is a private member of 'cwg1862::C'}}
+// expected-note at -30 {{implicitly declared private here}}
 
 // FIXME: both definition of 'D::g' are not friends, so they don't have access to 'private_int'
 template<class T>
 void A<T>::D::g() { (void)c.private_int; }
 void A<int>::D::g() { (void)c.private_int; }
+// expected-error at -1 {{'private_int' is a private member of 'cwg1862::C'}}
+// expected-note at -37 {{implicitly declared private here}}
 
 template<class T>
 T A<T>::h() { (void)c.private_int; }
diff --git a/clang/test/CXX/drs/cwg19xx.cpp b/clang/test/CXX/drs/cwg19xx.cpp
index 8162f9caa8f15..4f7031ddec602 100644
--- a/clang/test/CXX/drs/cwg19xx.cpp
+++ b/clang/test/CXX/drs/cwg19xx.cpp
@@ -102,18 +102,18 @@ template<typename T> struct A {
 };
 class X {
   static int x;
-  // FIXME: this is ill-formed, because A<T>::B::C does not end with a simple-template-id
   template <typename T>
   friend class A<T>::B::C;
-  // expected-warning at -1 {{dependent nested name specifier 'A<T>::B' for friend class declaration is not supported; turning off access control for 'X'}}
+  // expected-error at -1 {{friend declaration does not name a member of a class template specialization}}
 };
 template<> struct A<int> {
   typedef struct Q B;
 };
 struct Q {
   class C {
-    // FIXME: 'f' is not a friend, so 'X::x' is not accessible
     int f() { return X::x; }
+    // expected-error at -1 {{'x' is a private member of 'cwg1918::X'}}
+    // expected-note at -12 {{implicitly declared private here}}
   };
 };
 } // namespace cwg1918
@@ -170,7 +170,7 @@ class X {
   // FIXME: this is ill-formed, because A<T>::B::C does not end with a simple-template-id
   template <typename T>
   friend class A<T>::B::C;
-  // expected-warning at -1 {{dependent nested name specifier 'A<T>::B' for friend class declaration is not supported; turning off access control for 'X'}}
+  // expected-error at -1 {{friend declaration does not name a member of a class template specialization}}
 };
 } // namespace cwg1945
 
diff --git a/clang/test/CXX/drs/cwg6xx.cpp b/clang/test/CXX/drs/cwg6xx.cpp
index b7b2ebf700375..1fca7390c371d 100644
--- a/clang/test/CXX/drs/cwg6xx.cpp
+++ b/clang/test/CXX/drs/cwg6xx.cpp
@@ -409,24 +409,27 @@ namespace cwg638 { // cwg638: no
   class X {
     typedef int type;
     template<class T> friend struct A<T>::B;
-    // expected-warning at -1 {{dependent nested name specifier 'A<T>' for friend class declaration is not supported; turning off access control for 'X'}}
     template<class T> friend void A<T>::f();
-    // expected-warning at -1 {{dependent nested name specifier 'A<T>' for friend class declaration is not supported; turning off access control for 'X'}}
     template<class T> friend void A<T>::g();
-    // expected-warning at -1 {{dependent nested name specifier 'A<T>' for friend class declaration is not supported; turning off access control for 'X'}}
     template<class T> friend void A<T>::C::h();
-    // expected-warning at -1 {{dependent nested name specifier 'A<T>::C' for friend class declaration is not supported; turning off access control for 'X'}}
+    // expected-error at -1 {{friend declaration does not name a member of a class template specialization}}
   };
 
   template<> struct A<int> {
-    X::type a; // FIXME: private
+    X::type a;
+    // expected-error at -1 {{'type' is a private member of 'cwg638::X'}}
+    // expected-note at -11 {{implicitly declared private here}}
     struct B {
       X::type b; // ok
     };
-    int f() { X::type c; } // FIXME: private
+    int f() { X::type c; }
+    // expected-error at -1 {{'type' is a private member of 'cwg638::X'}}
+    // expected-note at -17 {{implicitly declared private here}}
     void g() { X::type d; } // ok
     struct D {
-      void h() { X::type e; } // FIXME: private
+      void h() { X::type e; }
+      // expected-error at -1 {{'type' is a private member of 'cwg638::X'}}
+      // expected-note at -22 {{implicitly declared private here}}
     };
   };
 } // namespace cwg638
diff --git a/clang/test/CXX/temp/temp.decls/temp.friend/p5.cpp b/clang/test/CXX/temp/temp.decls/temp.friend/p5.cpp
index a292d0de97a39..5b03aba342f8b 100644
--- a/clang/test/CXX/temp/temp.decls/temp.friend/p5.cpp
+++ b/clang/test/CXX/temp/temp.decls/temp.friend/p5.cpp
@@ -6,7 +6,7 @@ namespace test0 {
   };
 
   class B {
-    template <class T> friend class A<T>::Member; // expected-warning {{not supported}}
+    template <class T> friend class A<T>::Member;
     int n;
   };
 
@@ -19,7 +19,7 @@ namespace test1 {
 
   class C {
     static void foo();
-    template <class T> friend void A<T>::f(); // expected-warning {{not supported}}
+    template <class T> friend void A<T>::f();
   };
 
   template <class T> struct A {
@@ -35,25 +35,24 @@ namespace test1 {
   };
 }
 
-// FIXME: these should fail!
 namespace test2 {
   template <class T> struct A;
 
   class C {
-    static void foo();
-    template <class T> friend void A<T>::g(); // expected-warning {{not supported}}
+    static void foo(); // expected-note 3 {{implicitly declared private here}}
+    template <class T> friend void A<T>::g();
   };
 
   template <class T> struct A {
-    void f() { C::foo(); }
+    void f() { C::foo(); } // expected-error {{'foo' is a private member of 'test2::C'}}
   };
 
   template <class T> struct A<T*> {
-    void f() { C::foo(); }
+    void f() { C::foo(); } // expected-error {{'foo' is a private member of 'test2::C'}}
   };
 
   template <> struct A<char> {
-    void f() { C::foo(); }
+    void f() { C::foo(); } // expected-error {{'foo' is a private member of 'test2::C'}}
   };
 }
 
@@ -66,7 +65,7 @@ namespace test3 {
 
   template <class U> class C {
     int i;
-    template <class T> friend struct A<T>::Inner; // expected-warning {{not supported}}
+    template <class T> friend struct A<T>::Inner;
   };
 
   template <class T> int A<T>::Inner::foo() {
@@ -81,22 +80,148 @@ namespace test3 {
 namespace test4 {
   template <class T> struct X {
     template <class U> void operator+=(U);
-    
+
     template <class V>
     template <class U>
-    friend void X<V>::operator+=(U); // expected-warning {{not supported}}
+    friend void X<V>::operator+=(U);
   };
 
-  void test() {   
+  void test() {
     X<int>() += 1.0;
   }
 }
 
 namespace test5 {
   template<template <class> class T> struct A {
-    template<template <class> class U> friend void A<U>::foo(); // expected-warning {{not supported}}
+    template<template <class> class U> friend void A<U>::foo();
   };
 
   template <class> struct B {};
   template class A<B>;
 }
+
+namespace test6 {
+  template <class T> struct A {
+    struct B {
+      static int f();
+    };
+  };
+
+  struct C {
+    int n;
+    template <class T> friend struct A<T>::B;
+  };
+
+  template <class T> int A<T>::B::f() {
+    C c;
+    c.n = 0;
+    return 0;
+  }
+
+  int k = A<int>::B::f();
+}
+
+namespace test7 {
+  template <class T> struct A {
+    struct D {
+      void g();
+    };
+  };
+
+  struct C {
+    template <class T> friend void A<T>::D::g(); // expected-error {{friend declaration does not name a member of a class template specialization}}
+  };
+}
+
+namespace test8 {
+  template <class T> struct A {
+    T h();
+  };
+
+  template <> struct A<int> {
+    int h();
+  };
+
+  template <> struct A<float *> {
+    int *h();
+  };
+
+  class C {
+    int n; // expected-note {{implicitly declared private here}}
+    template <class T> friend int *A<T *>::h();
+  };
+
+  template <class T> T A<T>::h() {
+    return T();
+  }
+
+  int A<int>::h() {
+    C c;
+    c.n = 0; // expected-error {{'n' is a private member of 'test8::C'}}
+    return 0;
+  }
+
+  template <> int *A<int *>::h() {
+    C c;
+    c.n = 0;
+    return nullptr;
+  }
+
+  int *A<float *>::h() {
+    C c;
+    c.n = 0;
+    return nullptr;
+  }
+
+  int *t1 = A<int *>().h();
+  int *t2 = A<float *>().h();
+  int t3 = A<int>().h();
+}
+
+namespace test9 {
+  template <class T> struct A {
+    template <T U> T i();
+  };
+
+  template <> struct A<int> {
+    template <int U> int i();
+  };
+
+  struct C {
+    int n;
+    template <class T> template <T U> friend T A<T>::i();
+  };
+
+  template <class T> template <T U> T A<T>::i() {
+    C c;
+    c.n = 0;
+    return U;
+  }
+
+  template <int U> int A<int>::i() {
+    C c;
+    c.n = 0;
+    return U;
+  }
+
+  int x = A<int>().i<1>();
+}
+
+namespace test10 {
+  template <class T> struct A;
+  class C {
+    static void foo(); // expected-note {{implicitly declared private here}}
+    template <class T> friend void A<T>::f();
+  };
+
+  template <class T> struct A {
+    void f() { C::foo(); }
+  };
+
+  template <> struct A<int> {
+    int f() {
+      C::foo(); // expected-error {{'foo' is a private member of 'test10::C'}}
+      return 0;
+    }
+  };
+}
diff --git a/clang/test/CXX/temp/temp.decls/temp.friend/p6.cpp b/clang/test/CXX/temp/temp.decls/temp.friend/p6.cpp
new file mode 100644
index 0000000000000..67bb7e19b4aaf
--- /dev/null
+++ b/clang/test/CXX/temp/temp.decls/temp.friend/p6.cpp
@@ -0,0 +1,24 @@
+// RUN: %clang_cc1 -fsyntax-only -verify %s
+
+template <class T> struct A;
+template <class T> struct B {
+  void f();
+};
+
+void t1() {
+  struct S {
+    template <class T> friend void f(); // expected-error {{templates can only be declared in namespace or class scope}}
+  };
+}
+
+void t2() {
+  struct S {
+    template <class T> friend struct A; // expected-error {{templates cannot be declared inside of a local class}}
+  };
+}
+
+void t3() {
+  struct S {
+    template <class T> friend void B<T>::f(); // expected-error {{templates cannot be declared inside of a local class}}
+  };
+}
diff --git a/clang/test/SemaCXX/many-template-parameter-lists.cpp b/clang/test/SemaCXX/many-template-parameter-lists.cpp
index f98005c7e6fb5..cbd04db3301d6 100644
--- a/clang/test/SemaCXX/many-template-parameter-lists.cpp
+++ b/clang/test/SemaCXX/many-template-parameter-lists.cpp
@@ -5,7 +5,7 @@
 template <class T>
 struct X {
   template <class U>
-  struct A { // expected-note {{not-yet-instantiated member is declared here}}
+  struct A {
     template <class V>
     struct B {
       template <class W>
@@ -28,7 +28,9 @@ struct X {
   template <class X>
   template <class Y>
   template <class Z>
-  friend void A<U>::template B<V>::template C<W>::template D<X>::template E<Y>::operator+=(Z); // expected-warning {{not supported}} expected-error {{no member 'A' in 'X<int>'; it has not yet been instantiated}}
+  friend void A<U>::template B<V>::template C<W>::template D<X>::template E<Y>::operator+=(Z);
+  // expected-error at -1 {{no member 'operator+=' in 'X<int>'; it has not yet been instantiated}}
+  // expected-note at -2 {{not-yet-instantiated member is declared here}}
 };
 
 void test() {
diff --git a/clang/test/SemaTemplate/GH71595.cpp b/clang/test/SemaTemplate/GH71595.cpp
index daec9410e547a..637f00e886ef7 100644
--- a/clang/test/SemaTemplate/GH71595.cpp
+++ b/clang/test/SemaTemplate/GH71595.cpp
@@ -20,15 +20,15 @@ class temp {
     template<C<temp> T>
     friend void g(); // expected-error {{friend declaration with a constraint that depends on an enclosing template parameter must be a definition}}
 
-    temp();
+    temp(); // expected-note {{implicitly declared private here}}
 };
 
 template<C<temp<int>> T>
 void g() {
-    auto v = temp<T>();
+    auto v = temp<T>(); // expected-error {{calling a private constructor of class 'temp<int>'}}
 }
 
 void h() {
     f<int>();
-    g<int>();
+    g<int>(); // expected-note {{in instantiation of function template specialization 'g<int>' requested here}}
 }
diff --git a/clang/test/SemaTemplate/ctad.cpp b/clang/test/SemaTemplate/ctad.cpp
index 52ffef980fcdf..8822931f3a8dc 100644
--- a/clang/test/SemaTemplate/ctad.cpp
+++ b/clang/test/SemaTemplate/ctad.cpp
@@ -23,7 +23,8 @@ namespace Access {
   };
   template<typename T> struct D : B { // expected-note {{not viable}} \
                                          expected-note {{implicit deduction guide declared as 'template <typename T> D(Access::D<T>) -> Access::D<T>'}}
-    D(T, typename T::type); // expected-note {{private member}} \
+    D(T, typename T::type); // expected-error {{'type' is a private member of 'Access::Y'}} \
+                            // expected-note {{private member}} \
                             // expected-note {{implicit deduction guide declared as 'template <typename T> D(T, typename T::type) -> Access::D<T>'}}
   };
   D b = {B(), {}};
@@ -36,10 +37,10 @@ namespace Access {
   // Once we implement proper support for dependent nested name specifiers in
   // friends, this should still work.
   class Y {
-    template <typename T> friend D<T>::D(T, typename T::type); // expected-warning {{dependent nested name specifier}}
-    struct type {};
+    template <typename T> friend D<T>::D(T, typename T::type);
+    struct type {}; // expected-note {{implicitly declared private here}}
   };
-  D y = {Y(), {}};
+  D y = {Y(), {}}; // expected-note {{in instantiation of template class 'Access::D<Access::Y>' requested here}}
 
   class Z {
     template <typename T> friend class D;

>From 0f6736d979a58b2b6e36c76e111480f5fa602fc9 Mon Sep 17 00:00:00 2001
From: Oleksandr Tarasiuk <oleksandr.tarasiuk at outlook.com>
Date: Wed, 15 Apr 2026 10:11:14 +0300
Subject: [PATCH 02/38] remove unused comment

---
 clang/include/clang/AST/DeclTemplate.h | 3 ---
 1 file changed, 3 deletions(-)

diff --git a/clang/include/clang/AST/DeclTemplate.h b/clang/include/clang/AST/DeclTemplate.h
index 1653c26910b8b..ac77076175d91 100644
--- a/clang/include/clang/AST/DeclTemplate.h
+++ b/clang/include/clang/AST/DeclTemplate.h
@@ -2471,9 +2471,6 @@ class ClassTemplateDecl : public RedeclarableTemplateDecl {
 ///   template \<typename U> friend class Foo<T>::Nested; // friend template
 /// };
 /// \endcode
-///
-/// \note This class is not currently in use.  All of the above
-/// will yield a FriendDecl, not a FriendTemplateDecl.
 class FriendTemplateDecl final
     : public FriendDecl,
       private llvm::TrailingObjects<FriendTemplateDecl,

>From 06e0ff6953fa9d3186744b45dc80f21f29dfda76 Mon Sep 17 00:00:00 2001
From: Oleksandr Tarasiuk <oleksandr.tarasiuk at outlook.com>
Date: Wed, 15 Apr 2026 17:59:27 +0300
Subject: [PATCH 03/38] update tests

---
 clang/test/CXX/drs/cwg18xx.cpp                | 15 ++++++--------
 clang/test/CXX/drs/cwg19xx.cpp                | 13 +++++-------
 clang/test/CXX/drs/cwg6xx.cpp                 | 20 ++++++++-----------
 .../CXX/temp/temp.decls/temp.friend/p5.cpp    | 11 ++++++----
 .../SemaCXX/many-template-parameter-lists.cpp |  5 ++---
 5 files changed, 28 insertions(+), 36 deletions(-)

diff --git a/clang/test/CXX/drs/cwg18xx.cpp b/clang/test/CXX/drs/cwg18xx.cpp
index 236423067dcb5..d46bdffa26384 100644
--- a/clang/test/CXX/drs/cwg18xx.cpp
+++ b/clang/test/CXX/drs/cwg18xx.cpp
@@ -416,7 +416,7 @@ struct A<float*> {
 };
 
 class C {
-  int private_int;
+  int private_int; // #C_private_int
 
   template<class T>
   friend struct A<T>::B;
@@ -425,8 +425,7 @@ class C {
   friend void A<T>::f();
 
   template<class T>
-  friend void A<T>::D::g();
-  // expected-error at -1 {{friend declaration does not name a member of a class template specialization}}
+  friend void A<T>::D::g(); // expected-error {{friend declaration does not name a member of a class template specialization}}
 
   template<class T>
   friend int *A<T*>::h();
@@ -444,16 +443,14 @@ void A<int>::B::e() { (void)c.private_int; }
 
 template<class T>
 void A<T>::f() { (void)c.private_int; }
-int A<int>::f() { (void)c.private_int; return 0; }
-// expected-error at -1 {{'private_int' is a private member of 'cwg1862::C'}}
-// expected-note at -30 {{implicitly declared private here}}
+int A<int>::f() { (void)c.private_int; return 0; } // expected-error {{'private_int' is a private member of 'cwg1862::C'}} \
+                                                   // expected-note@#C_private_int {{implicitly declared private here}}
 
 // FIXME: both definition of 'D::g' are not friends, so they don't have access to 'private_int'
 template<class T>
 void A<T>::D::g() { (void)c.private_int; }
-void A<int>::D::g() { (void)c.private_int; }
-// expected-error at -1 {{'private_int' is a private member of 'cwg1862::C'}}
-// expected-note at -37 {{implicitly declared private here}}
+void A<int>::D::g() { (void)c.private_int; } // expected-error {{'private_int' is a private member of 'cwg1862::C'}} \
+                                             // expected-note@#C_private_int {{implicitly declared private here}}
 
 template<class T>
 T A<T>::h() { (void)c.private_int; }
diff --git a/clang/test/CXX/drs/cwg19xx.cpp b/clang/test/CXX/drs/cwg19xx.cpp
index 4f7031ddec602..4f20412d2ab39 100644
--- a/clang/test/CXX/drs/cwg19xx.cpp
+++ b/clang/test/CXX/drs/cwg19xx.cpp
@@ -101,19 +101,17 @@ template<typename T> struct A {
   };
 };
 class X {
-  static int x;
+  static int x; // #X_c
   template <typename T>
-  friend class A<T>::B::C;
-  // expected-error at -1 {{friend declaration does not name a member of a class template specialization}}
+  friend class A<T>::B::C; // expected-error {{friend declaration does not name a member of a class template specialization}}
 };
 template<> struct A<int> {
   typedef struct Q B;
 };
 struct Q {
   class C {
-    int f() { return X::x; }
-    // expected-error at -1 {{'x' is a private member of 'cwg1918::X'}}
-    // expected-note at -12 {{implicitly declared private here}}
+    int f() { return X::x; } // expected-error {{'x' is a private member of 'cwg1918::X'}} \
+                             // expected-note@#X_c {{implicitly declared private here}}
   };
 };
 } // namespace cwg1918
@@ -169,8 +167,7 @@ class X {
   static int x;
   // FIXME: this is ill-formed, because A<T>::B::C does not end with a simple-template-id
   template <typename T>
-  friend class A<T>::B::C;
-  // expected-error at -1 {{friend declaration does not name a member of a class template specialization}}
+  friend class A<T>::B::C; // expected-error {{friend declaration does not name a member of a class template specialization}}
 };
 } // namespace cwg1945
 
diff --git a/clang/test/CXX/drs/cwg6xx.cpp b/clang/test/CXX/drs/cwg6xx.cpp
index 1fca7390c371d..32f9c7314c59a 100644
--- a/clang/test/CXX/drs/cwg6xx.cpp
+++ b/clang/test/CXX/drs/cwg6xx.cpp
@@ -407,29 +407,25 @@ namespace cwg638 { // cwg638: no
   };
 
   class X {
-    typedef int type;
+    typedef int type; // #X_type
     template<class T> friend struct A<T>::B;
     template<class T> friend void A<T>::f();
     template<class T> friend void A<T>::g();
-    template<class T> friend void A<T>::C::h();
-    // expected-error at -1 {{friend declaration does not name a member of a class template specialization}}
+    template<class T> friend void A<T>::C::h(); // expected-error {{friend declaration does not name a member of a class template specialization}}
   };
 
   template<> struct A<int> {
-    X::type a;
-    // expected-error at -1 {{'type' is a private member of 'cwg638::X'}}
-    // expected-note at -11 {{implicitly declared private here}}
+    X::type a; // expected-error {{'type' is a private member of 'cwg638::X'}} \
+               // expected-note@#X_type {{implicitly declared private here}}
     struct B {
       X::type b; // ok
     };
-    int f() { X::type c; }
-    // expected-error at -1 {{'type' is a private member of 'cwg638::X'}}
-    // expected-note at -17 {{implicitly declared private here}}
+    int f() { X::type c; } // expected-error {{'type' is a private member of 'cwg638::X'}} \
+                           // expected-note@#X_type {{implicitly declared private here}}
     void g() { X::type d; } // ok
     struct D {
-      void h() { X::type e; }
-      // expected-error at -1 {{'type' is a private member of 'cwg638::X'}}
-      // expected-note at -22 {{implicitly declared private here}}
+      void h() { X::type e; } // expected-error {{'type' is a private member of 'cwg638::X'}}
+                              // expected-note@#X_type {{implicitly declared private here}}
     };
   };
 } // namespace cwg638
diff --git a/clang/test/CXX/temp/temp.decls/temp.friend/p5.cpp b/clang/test/CXX/temp/temp.decls/temp.friend/p5.cpp
index 5b03aba342f8b..94a4c0ceea0a9 100644
--- a/clang/test/CXX/temp/temp.decls/temp.friend/p5.cpp
+++ b/clang/test/CXX/temp/temp.decls/temp.friend/p5.cpp
@@ -39,20 +39,23 @@ namespace test2 {
   template <class T> struct A;
 
   class C {
-    static void foo(); // expected-note 3 {{implicitly declared private here}}
+    static void foo(); // #C_foo
     template <class T> friend void A<T>::g();
   };
 
   template <class T> struct A {
-    void f() { C::foo(); } // expected-error {{'foo' is a private member of 'test2::C'}}
+    void f() { C::foo(); } // expected-error {{'foo' is a private member of 'test2::C'}} \
+                           // expected-note@#C_foo {{implicitly declared private here}}
   };
 
   template <class T> struct A<T*> {
-    void f() { C::foo(); } // expected-error {{'foo' is a private member of 'test2::C'}}
+    void f() { C::foo(); } // expected-error {{'foo' is a private member of 'test2::C'}} \
+                           // expected-note@#C_foo {{implicitly declared private here}}
   };
 
   template <> struct A<char> {
-    void f() { C::foo(); } // expected-error {{'foo' is a private member of 'test2::C'}}
+    void f() { C::foo(); } // expected-error {{'foo' is a private member of 'test2::C'}} \
+                           // expected-note@#C_foo {{implicitly declared private here}}
   };
 }
 
diff --git a/clang/test/SemaCXX/many-template-parameter-lists.cpp b/clang/test/SemaCXX/many-template-parameter-lists.cpp
index cbd04db3301d6..3f44a9f82e9d7 100644
--- a/clang/test/SemaCXX/many-template-parameter-lists.cpp
+++ b/clang/test/SemaCXX/many-template-parameter-lists.cpp
@@ -28,9 +28,8 @@ struct X {
   template <class X>
   template <class Y>
   template <class Z>
-  friend void A<U>::template B<V>::template C<W>::template D<X>::template E<Y>::operator+=(Z);
-  // expected-error at -1 {{no member 'operator+=' in 'X<int>'; it has not yet been instantiated}}
-  // expected-note at -2 {{not-yet-instantiated member is declared here}}
+  friend void A<U>::template B<V>::template C<W>::template D<X>::template E<Y>::operator+=(Z); // expected-error {{no member 'operator+=' in 'X<int>'; it has not yet been instantiated}} \
+                                                                                               // expected-note {{not-yet-instantiated member is declared here}}
 };
 
 void test() {

>From 726716ea5430a622065f75640778456bb828f32c Mon Sep 17 00:00:00 2001
From: Oleksandr Tarasiuk <oleksandr.tarasiuk at outlook.com>
Date: Fri, 17 Apr 2026 00:07:27 +0300
Subject: [PATCH 04/38] use ArrayRef for FriendTemplateDecl parameter lists.
 finish deduction for friend access checks

---
 clang/include/clang/AST/DeclTemplate.h        |  8 +--
 clang/include/clang/AST/RecursiveASTVisitor.h |  4 +-
 clang/include/clang/Sema/Sema.h               |  6 ++
 clang/lib/AST/ASTImporter.cpp                 | 24 ++++----
 clang/lib/AST/ASTStructuralEquivalence.cpp    | 14 ++---
 clang/lib/AST/DeclTemplate.cpp                |  2 +-
 clang/lib/Sema/SemaAccess.cpp                 | 56 +++++++++++++------
 clang/lib/Sema/SemaTemplateDeduction.cpp      | 10 ++++
 .../lib/Sema/SemaTemplateInstantiateDecl.cpp  | 10 ++--
 clang/lib/Serialization/ASTWriterDecl.cpp     |  4 +-
 10 files changed, 87 insertions(+), 51 deletions(-)

diff --git a/clang/include/clang/AST/DeclTemplate.h b/clang/include/clang/AST/DeclTemplate.h
index ac77076175d91..24218cfb65da6 100644
--- a/clang/include/clang/AST/DeclTemplate.h
+++ b/clang/include/clang/AST/DeclTemplate.h
@@ -2523,13 +2523,11 @@ class FriendTemplateDecl final
     return Friend.dyn_cast<NamedDecl*>();
   }
 
-  TemplateParameterList *getFriendTypeTemplateParameterList(unsigned N) const {
-    assert(N < NumTPLists);
-    return getTrailingObjects()[N];
+  ArrayRef<TemplateParameterList *>
+  getFriendTypeTemplateParameterLists() const {
+    return ArrayRef(getTrailingObjects(), NumTPLists);
   }
 
-  unsigned getFriendTypeNumTemplateParameterLists() const { return NumTPLists; }
-
   // Implement isa/cast/dyncast/etc.
   static bool classof(const Decl *D) { return classofKind(D->getKind()); }
   static bool classofKind(Kind K) { return K == Decl::FriendTemplate; }
diff --git a/clang/include/clang/AST/RecursiveASTVisitor.h b/clang/include/clang/AST/RecursiveASTVisitor.h
index cd4fd5b598d4a..af72a71db9ffb 100644
--- a/clang/include/clang/AST/RecursiveASTVisitor.h
+++ b/clang/include/clang/AST/RecursiveASTVisitor.h
@@ -1727,9 +1727,7 @@ DEF_TRAVERSE_DECL(FriendTemplateDecl, {
     TRY_TO(TraverseTypeLoc(D->getFriendType()->getTypeLoc()));
   else
     TRY_TO(TraverseDecl(D->getFriendDecl()));
-  for (unsigned I = 0, E = D->getFriendTypeNumTemplateParameterLists(); I < E;
-       ++I) {
-    TemplateParameterList *TPL = D->getFriendTypeTemplateParameterList(I);
+  for (TemplateParameterList *TPL : D->getFriendTypeTemplateParameterLists()) {
     for (TemplateParameterList::iterator ITPL = TPL->begin(), ETPL = TPL->end();
          ITPL != ETPL; ++ITPL) {
       TRY_TO(TraverseDecl(*ITPL));
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 544d892c1535c..0cd1b29289e3d 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -12750,6 +12750,12 @@ class Sema final : public SemaBase {
             return false;
           });
 
+  TemplateDeductionResult FinishTemplateArgumentDeduction(
+      TemplateDecl *TD, TemplateParameterList *TPL,
+      ArrayRef<TemplateArgument> PatternArgs, ArrayRef<TemplateArgument> Args,
+      SmallVectorImpl<DeducedTemplateArgument> &Deduced,
+      sema::TemplateDeductionInfo &Info, bool CopyDeducedArgs);
+
   /// Perform template argument deduction from a function call
   /// (C++ [temp.deduct.call]).
   ///
diff --git a/clang/lib/AST/ASTImporter.cpp b/clang/lib/AST/ASTImporter.cpp
index 782e4d710599c..3667689d3d462 100644
--- a/clang/lib/AST/ASTImporter.cpp
+++ b/clang/lib/AST/ASTImporter.cpp
@@ -4548,8 +4548,11 @@ static bool IsEquivalentFriend(ASTImporter &Importer, FriendDecl *FD1,
 
 static bool IsEquivalentFriend(ASTImporter &Importer, FriendTemplateDecl *FTD1,
                                FriendTemplateDecl *FTD2) {
-  if (FTD1->getFriendTypeNumTemplateParameterLists() !=
-      FTD2->getFriendTypeNumTemplateParameterLists())
+  ArrayRef<TemplateParameterList *> TPL1 =
+      FTD1->getFriendTypeTemplateParameterLists();
+  ArrayRef<TemplateParameterList *> TPL2 =
+      FTD2->getFriendTypeTemplateParameterLists();
+  if (TPL1.size() != TPL2.size())
     return false;
 
   ASTImporter::NonEquivalentDeclSet NonEquivalentDecls;
@@ -4559,12 +4562,9 @@ static bool IsEquivalentFriend(ASTImporter &Importer, FriendTemplateDecl *FTD1,
       StructuralEquivalenceKind::Default, /*StrictTypeSpelling=*/false,
       /*Complain=*/false);
 
-  for (unsigned I = 0, N = FTD1->getFriendTypeNumTemplateParameterLists();
-       I != N; ++I) {
-    if (!Ctx.IsEquivalent(FTD1->getFriendTypeTemplateParameterList(I),
-                          FTD2->getFriendTypeTemplateParameterList(I)))
+  for (unsigned I = 0, N = TPL1.size(); I != N; ++I)
+    if (!Ctx.IsEquivalent(TPL1[I], TPL2[I]))
       return false;
-  }
 
   if ((!FTD1->getFriendType()) != (!FTD2->getFriendType()))
     return false;
@@ -4700,11 +4700,11 @@ ExpectedDecl ASTNodeImporter::VisitFriendTemplateDecl(FriendTemplateDecl *D) {
       return TSIOrErr.takeError();
   }
 
-  SmallVector<TemplateParameterList *, 1> ToParams(
-      D->getFriendTypeNumTemplateParameterLists());
-  for (unsigned I = 0, N = D->getFriendTypeNumTemplateParameterLists(); I != N;
-       ++I) {
-    if (auto ParamsOrErr = import(D->getFriendTypeTemplateParameterList(I)))
+  ArrayRef<TemplateParameterList *> TPLs =
+      D->getFriendTypeTemplateParameterLists();
+  SmallVector<TemplateParameterList *, 1> ToParams(TPLs.size());
+  for (unsigned I = 0, N = TPLs.size(); I != N; ++I) {
+    if (auto ParamsOrErr = import(TPLs[I]))
       ToParams[I] = *ParamsOrErr;
     else
       return ParamsOrErr.takeError();
diff --git a/clang/lib/AST/ASTStructuralEquivalence.cpp b/clang/lib/AST/ASTStructuralEquivalence.cpp
index 8da7cca6665fb..e85caf1e766fc 100644
--- a/clang/lib/AST/ASTStructuralEquivalence.cpp
+++ b/clang/lib/AST/ASTStructuralEquivalence.cpp
@@ -2433,16 +2433,16 @@ static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
 static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
                                      FriendTemplateDecl *FTD1,
                                      FriendTemplateDecl *FTD2) {
-  if (FTD1->getFriendTypeNumTemplateParameterLists() !=
-      FTD2->getFriendTypeNumTemplateParameterLists())
+  ArrayRef<TemplateParameterList *> TPL1 =
+      FTD1->getFriendTypeTemplateParameterLists();
+  ArrayRef<TemplateParameterList *> TPL2 =
+      FTD2->getFriendTypeTemplateParameterLists();
+  if (TPL1.size() != TPL2.size())
     return false;
 
-  for (unsigned I = 0, N = FTD1->getFriendTypeNumTemplateParameterLists();
-       I != N; ++I) {
-    if (!Context.IsEquivalent(FTD1->getFriendTypeTemplateParameterList(I),
-                              FTD2->getFriendTypeTemplateParameterList(I)))
+  for (unsigned I = 0, N = TPL1.size(); I != N; ++I)
+    if (!Context.IsEquivalent(TPL1[I], TPL2[I]))
       return false;
-  }
 
   if ((FTD1->getFriendType() && FTD2->getFriendDecl()) ||
       (FTD1->getFriendDecl() && FTD2->getFriendType()))
diff --git a/clang/lib/AST/DeclTemplate.cpp b/clang/lib/AST/DeclTemplate.cpp
index 6c19c347d87fc..623b62fc219ac 100644
--- a/clang/lib/AST/DeclTemplate.cpp
+++ b/clang/lib/AST/DeclTemplate.cpp
@@ -1256,7 +1256,7 @@ FriendTemplateDecl::CreateDeserialized(ASTContext &C, GlobalDeclID ID,
 
 SourceRange FriendTemplateDecl::getSourceRange() const {
   SourceLocation Begin =
-      getFriendTypeTemplateParameterList(0)->getTemplateLoc();
+      getFriendTypeTemplateParameterLists().front()->getTemplateLoc();
   SourceLocation End = FriendDecl::getSourceRange().getEnd();
   return SourceRange(Begin, End);
 }
diff --git a/clang/lib/Sema/SemaAccess.cpp b/clang/lib/Sema/SemaAccess.cpp
index c516aee3e62d6..91e84d62f27c9 100644
--- a/clang/lib/Sema/SemaAccess.cpp
+++ b/clang/lib/Sema/SemaAccess.cpp
@@ -19,6 +19,7 @@
 #include "clang/AST/ExprCXX.h"
 #include "clang/Basic/Specifiers.h"
 #include "clang/Sema/DelayedDiagnostic.h"
+#include "clang/Sema/EnterExpressionEvaluationContext.h"
 #include "clang/Sema/Initialization.h"
 #include "clang/Sema/Lookup.h"
 #include "clang/Sema/Template.h"
@@ -276,12 +277,10 @@ struct AccessTarget : public AccessedEntity {
 }
 
 static bool CanDeduceTemplateArguments(Sema &S, TemplateParameterList *TPL,
+                                       TemplateDecl *TD,
                                        ArrayRef<TemplateArgument> PatternArgs,
                                        ArrayRef<TemplateArgument> Args,
                                        SourceLocation Loc) {
-  if (PatternArgs.size() != Args.size())
-    return false;
-
   auto Equal =
       llvm::equal(PatternArgs, Args,
                   [](const TemplateArgument &LHS, const TemplateArgument &RHS) {
@@ -290,16 +289,28 @@ static bool CanDeduceTemplateArguments(Sema &S, TemplateParameterList *TPL,
   if (Equal)
     return true;
 
+  EnterExpressionEvaluationContext Unevaluated(
+      S, Sema::ExpressionEvaluationContext::Unevaluated);
   TemplateDeductionInfo Info(Loc);
+  Sema::SFINAETrap Trap(S, Info);
+  LocalInstantiationScope InstantiationScope(S);
   SmallVector<DeducedTemplateArgument, 4> Deduced(TPL->size());
-  S.DeduceTemplateArguments(TPL, PatternArgs, Args, Info, Deduced,
-                            /*NumberOfArgumentsMustMatch=*/false);
+  if (S.DeduceTemplateArguments(TPL, PatternArgs, Args, Info, Deduced,
+                                /*NumberOfArgumentsMustMatch=*/false) !=
+      TemplateDeductionResult::Success)
+    return false;
 
-  for (const DeducedTemplateArgument &Arg : Deduced)
-    if (Arg.isNull())
-      return false;
+  SmallVector<TemplateArgument, 4> DeducedArgs(Deduced.begin(), Deduced.end());
+  Sema::InstantiatingTemplate Inst(S, Info.getLocation(), TD, DeducedArgs);
+  if (Inst.isInvalid())
+    return false;
 
-  return true;
+  if (S.FinishTemplateArgumentDeduction(
+          TD, TPL, PatternArgs, Args, Deduced, Info,
+          /*CopyDeducedArgs=*/false) != TemplateDeductionResult::Success)
+    return false;
+
+  return !Trap.hasErrorOccurred();
 }
 
 static CanQual<FunctionProtoType>
@@ -365,7 +376,8 @@ static bool MatchesFriendContext(Sema &S, FunctionDecl *FD,
   if (ContextCTD->getCanonicalDecl() != FriendCTD->getCanonicalDecl())
     return false;
 
-  return CanDeduceTemplateArguments(S, FriendTPL, FriendArgs, ContextArgs, Loc);
+  return CanDeduceTemplateArguments(S, FriendTPL, FriendCTD, FriendArgs,
+                                    ContextArgs, Loc);
 }
 
 /// Checks whether one class might instantiate to the other.
@@ -692,9 +704,12 @@ static AccessResult MatchesFriend(Sema &S, const EffectiveContext &EC,
   if (!FriendCTD)
     return OnFailure;
 
-  TemplateParameterList *FriendTPL =
-      FriendTD->getFriendTypeTemplateParameterList(0);
+  ArrayRef<TemplateParameterList *> FriendTPLists =
+      FriendTD->getFriendTypeTemplateParameterLists();
+  if (FriendTPLists.empty())
+    return OnFailure;
 
+  TemplateParameterList *FriendTPL = FriendTPLists.front();
   if (!FriendTPL)
     return OnFailure;
 
@@ -728,8 +743,12 @@ static AccessResult MatchesFriend(Sema &S, const EffectiveContext &EC,
   if (!FriendCTD)
     return OnFailure;
 
-  TemplateParameterList *FriendTPL =
-      FriendTD->getFriendTypeTemplateParameterList(0);
+  ArrayRef<TemplateParameterList *> FriendTPLists =
+      FriendTD->getFriendTypeTemplateParameterLists();
+  if (FriendTPLists.empty())
+    return OnFailure;
+
+  TemplateParameterList *FriendTPL = FriendTPLists.front();
   if (!FriendTPL)
     return OnFailure;
 
@@ -796,7 +815,12 @@ static AccessResult MatchesFriend(Sema &S, const EffectiveContext &EC,
   if (!CTD)
     return OnFailure;
 
-  TemplateParameterList *TPL = FTD->getFriendTypeTemplateParameterList(0);
+  ArrayRef<TemplateParameterList *> FriendTPLists =
+      FTD->getFriendTypeTemplateParameterLists();
+  if (FriendTPLists.empty())
+    return OnFailure;
+
+  TemplateParameterList *TPL = FriendTPLists.front();
   if (!TPL)
     return OnFailure;
 
@@ -813,7 +837,7 @@ static AccessResult MatchesFriend(Sema &S, const EffectiveContext &EC,
         CTD->getCanonicalDecl())
       continue;
 
-    if (CanDeduceTemplateArguments(S, TPL, TST->template_arguments(),
+    if (CanDeduceTemplateArguments(S, TPL, CTD, TST->template_arguments(),
                                    CTSD->getTemplateArgs().asArray(),
                                    FTD->getLocation()))
       return AR_accessible;
diff --git a/clang/lib/Sema/SemaTemplateDeduction.cpp b/clang/lib/Sema/SemaTemplateDeduction.cpp
index c71c40526ccdc..a8303d615b5e5 100644
--- a/clang/lib/Sema/SemaTemplateDeduction.cpp
+++ b/clang/lib/Sema/SemaTemplateDeduction.cpp
@@ -4108,6 +4108,16 @@ TemplateDeductionResult Sema::FinishTemplateArgumentDeduction(
   return TemplateDeductionResult::Success;
 }
 
+TemplateDeductionResult Sema::FinishTemplateArgumentDeduction(
+    TemplateDecl *TD, TemplateParameterList *TPL,
+    ArrayRef<TemplateArgument> PatternArgs, ArrayRef<TemplateArgument> Args,
+    SmallVectorImpl<DeducedTemplateArgument> &Deduced,
+    sema::TemplateDeductionInfo &Info, bool CopyDeducedArgs) {
+  return ::FinishTemplateArgumentDeduction(
+      *this, TD, TPL, TD, /*PartialOrdering=*/false, PatternArgs, Args, Deduced,
+      Info, CopyDeducedArgs);
+}
+
 /// Gets the type of a function for template-argument-deducton
 /// purposes when it's considered as part of an overload set.
 static QualType GetTypeOfFunction(Sema &S, const OverloadExpr::FindResult &R,
diff --git a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
index 0a33307ae535e..5a8944d7902f3 100644
--- a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
+++ b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
@@ -4714,11 +4714,11 @@ Decl *TemplateDeclInstantiator::VisitObjCAtDefsFieldDecl(ObjCAtDefsFieldDecl *D)
 }
 
 Decl *TemplateDeclInstantiator::VisitFriendTemplateDecl(FriendTemplateDecl *D) {
-  unsigned NumTPLists = D->getFriendTypeNumTemplateParameterLists();
-  SmallVector<TemplateParameterList *, 1> TPL(NumTPLists);
-  for (unsigned I = 0, N = NumTPLists; I != N; ++I) {
-    TemplateParameterList *InstParams =
-        SubstTemplateParams(D->getFriendTypeTemplateParameterList(I));
+  ArrayRef<TemplateParameterList *> TPLists =
+      D->getFriendTypeTemplateParameterLists();
+  SmallVector<TemplateParameterList *, 1> TPL(TPLists.size());
+  for (unsigned I = 0, N = TPLists.size(); I != N; ++I) {
+    TemplateParameterList *InstParams = SubstTemplateParams(TPLists[I]);
     if (!InstParams)
       return nullptr;
 
diff --git a/clang/lib/Serialization/ASTWriterDecl.cpp b/clang/lib/Serialization/ASTWriterDecl.cpp
index 287b763105b4c..33b8c5bb23e11 100644
--- a/clang/lib/Serialization/ASTWriterDecl.cpp
+++ b/clang/lib/Serialization/ASTWriterDecl.cpp
@@ -1836,8 +1836,8 @@ void ASTDeclWriter::VisitFriendDecl(FriendDecl *D) {
 void ASTDeclWriter::VisitFriendTemplateDecl(FriendTemplateDecl *D) {
   VisitDecl(D);
   Record.push_back(D->NumTPLists);
-  for (unsigned i = 0, e = D->NumTPLists; i != e; ++i)
-    Record.AddTemplateParameterList(D->getFriendTypeTemplateParameterList(i));
+  for (TemplateParameterList *TPL : D->getFriendTypeTemplateParameterLists())
+    Record.AddTemplateParameterList(TPL);
   Record.push_back(D->getFriendDecl() != nullptr);
   if (D->getFriendDecl())
     Record.AddDeclRef(D->getFriendDecl());

>From ab8d8f596d3637c7525e0812212fc37a25e2233e Mon Sep 17 00:00:00 2001
From: Oleksandr Tarasiuk <oleksandr.tarasiuk at outlook.com>
Date: Thu, 23 Apr 2026 10:26:06 +0300
Subject: [PATCH 05/38] fix tests formatting

---
 clang/test/CXX/drs/cwg18xx.cpp | 15 +++++++++------
 clang/test/CXX/drs/cwg19xx.cpp | 13 ++++++++-----
 clang/test/CXX/drs/cwg6xx.cpp  | 20 ++++++++++++--------
 3 files changed, 29 insertions(+), 19 deletions(-)

diff --git a/clang/test/CXX/drs/cwg18xx.cpp b/clang/test/CXX/drs/cwg18xx.cpp
index d46bdffa26384..d72e2089039ca 100644
--- a/clang/test/CXX/drs/cwg18xx.cpp
+++ b/clang/test/CXX/drs/cwg18xx.cpp
@@ -416,7 +416,7 @@ struct A<float*> {
 };
 
 class C {
-  int private_int; // #C_private_int
+  int private_int; // #cwg1862-C-private_int
 
   template<class T>
   friend struct A<T>::B;
@@ -425,7 +425,8 @@ class C {
   friend void A<T>::f();
 
   template<class T>
-  friend void A<T>::D::g(); // expected-error {{friend declaration does not name a member of a class template specialization}}
+  friend void A<T>::D::g();
+  // expected-error at -1 {{friend declaration does not name a member of a class template specialization}}
 
   template<class T>
   friend int *A<T*>::h();
@@ -443,14 +444,16 @@ void A<int>::B::e() { (void)c.private_int; }
 
 template<class T>
 void A<T>::f() { (void)c.private_int; }
-int A<int>::f() { (void)c.private_int; return 0; } // expected-error {{'private_int' is a private member of 'cwg1862::C'}} \
-                                                   // expected-note@#C_private_int {{implicitly declared private here}}
+int A<int>::f() { (void)c.private_int; return 0; }
+// expected-error at -1 {{'private_int' is a private member of 'cwg1862::C'}}
+//   expected-note@#cwg1862-C-private_int {{implicitly declared private here}}
 
 // FIXME: both definition of 'D::g' are not friends, so they don't have access to 'private_int'
 template<class T>
 void A<T>::D::g() { (void)c.private_int; }
-void A<int>::D::g() { (void)c.private_int; } // expected-error {{'private_int' is a private member of 'cwg1862::C'}} \
-                                             // expected-note@#C_private_int {{implicitly declared private here}}
+void A<int>::D::g() { (void)c.private_int; }
+// expected-error at -1 {{'private_int' is a private member of 'cwg1862::C'}}
+//   expected-note@#cwg1862-C-private_int {{implicitly declared private here}}
 
 template<class T>
 T A<T>::h() { (void)c.private_int; }
diff --git a/clang/test/CXX/drs/cwg19xx.cpp b/clang/test/CXX/drs/cwg19xx.cpp
index 4f20412d2ab39..7317c8beedaaa 100644
--- a/clang/test/CXX/drs/cwg19xx.cpp
+++ b/clang/test/CXX/drs/cwg19xx.cpp
@@ -101,17 +101,19 @@ template<typename T> struct A {
   };
 };
 class X {
-  static int x; // #X_c
+  static int x; // #cwg1918-X-x
   template <typename T>
-  friend class A<T>::B::C; // expected-error {{friend declaration does not name a member of a class template specialization}}
+  friend class A<T>::B::C;
+  // expected-error at -1 {{friend declaration does not name a member of a class template specialization}}
 };
 template<> struct A<int> {
   typedef struct Q B;
 };
 struct Q {
   class C {
-    int f() { return X::x; } // expected-error {{'x' is a private member of 'cwg1918::X'}} \
-                             // expected-note@#X_c {{implicitly declared private here}}
+    int f() { return X::x; }
+    // expected-error at -1 {{'x' is a private member of 'cwg1918::X'}}
+    //   expected-note@#cwg1918-X-x {{implicitly declared private here}}
   };
 };
 } // namespace cwg1918
@@ -167,7 +169,8 @@ class X {
   static int x;
   // FIXME: this is ill-formed, because A<T>::B::C does not end with a simple-template-id
   template <typename T>
-  friend class A<T>::B::C; // expected-error {{friend declaration does not name a member of a class template specialization}}
+  friend class A<T>::B::C;
+  // expected-error at -1 {{friend declaration does not name a member of a class template specialization}}
 };
 } // namespace cwg1945
 
diff --git a/clang/test/CXX/drs/cwg6xx.cpp b/clang/test/CXX/drs/cwg6xx.cpp
index 32f9c7314c59a..a61f161e5ed62 100644
--- a/clang/test/CXX/drs/cwg6xx.cpp
+++ b/clang/test/CXX/drs/cwg6xx.cpp
@@ -407,25 +407,29 @@ namespace cwg638 { // cwg638: no
   };
 
   class X {
-    typedef int type; // #X_type
+    typedef int type; // #cwg638-X-type
     template<class T> friend struct A<T>::B;
     template<class T> friend void A<T>::f();
     template<class T> friend void A<T>::g();
-    template<class T> friend void A<T>::C::h(); // expected-error {{friend declaration does not name a member of a class template specialization}}
+    template<class T> friend void A<T>::C::h();
+    // expected-error at -1 {{friend declaration does not name a member of a class template specialization}}
   };
 
   template<> struct A<int> {
-    X::type a; // expected-error {{'type' is a private member of 'cwg638::X'}} \
-               // expected-note@#X_type {{implicitly declared private here}}
+    X::type a;
+    // expected-error at -1 {{'type' is a private member of 'cwg638::X'}}
+    //   expected-note@#cwg638-X-type {{implicitly declared private here}}
     struct B {
       X::type b; // ok
     };
-    int f() { X::type c; } // expected-error {{'type' is a private member of 'cwg638::X'}} \
-                           // expected-note@#X_type {{implicitly declared private here}}
+    int f() { X::type c; }
+    // expected-error at -1 {{'type' is a private member of 'cwg638::X'}}
+    //   expected-note@#cwg638-X-type {{implicitly declared private here}}
     void g() { X::type d; } // ok
     struct D {
-      void h() { X::type e; } // expected-error {{'type' is a private member of 'cwg638::X'}}
-                              // expected-note@#X_type {{implicitly declared private here}}
+      void h() { X::type e; }
+      // expected-error at -1 {{'type' is a private member of 'cwg638::X'}}
+      //   expected-note@#cwg638-X-type {{implicitly declared private here}}
     };
   };
 } // namespace cwg638

>From 1ed9754477f44fcca9c34dcb85273988c2c5df19 Mon Sep 17 00:00:00 2001
From: Oleksandr Tarasiuk <oleksandr.tarasiuk at outlook.com>
Date: Thu, 23 Apr 2026 11:14:12 +0300
Subject: [PATCH 06/38] fix tests formatting

---
 .../CXX/temp/temp.decls/temp.friend/p5.cpp    | 32 ++++++++++++-------
 .../CXX/temp/temp.decls/temp.friend/p6.cpp    |  9 ++++--
 .../SemaCXX/many-template-parameter-lists.cpp |  8 +++--
 clang/test/SemaTemplate/GH71595.cpp           | 12 ++++---
 clang/test/SemaTemplate/ctad.cpp              | 23 +++++++------
 5 files changed, 53 insertions(+), 31 deletions(-)

diff --git a/clang/test/CXX/temp/temp.decls/temp.friend/p5.cpp b/clang/test/CXX/temp/temp.decls/temp.friend/p5.cpp
index 94a4c0ceea0a9..c3c8c3668d91e 100644
--- a/clang/test/CXX/temp/temp.decls/temp.friend/p5.cpp
+++ b/clang/test/CXX/temp/temp.decls/temp.friend/p5.cpp
@@ -39,23 +39,26 @@ namespace test2 {
   template <class T> struct A;
 
   class C {
-    static void foo(); // #C_foo
+    static void foo(); // #test2-C-foo
     template <class T> friend void A<T>::g();
   };
 
   template <class T> struct A {
-    void f() { C::foo(); } // expected-error {{'foo' is a private member of 'test2::C'}} \
-                           // expected-note@#C_foo {{implicitly declared private here}}
+    void f() { C::foo(); }
+    // expected-error at -1 {{'foo' is a private member of 'test2::C'}}
+    //   expected-note@#test2-C-foo {{implicitly declared private here}}
   };
 
   template <class T> struct A<T*> {
-    void f() { C::foo(); } // expected-error {{'foo' is a private member of 'test2::C'}} \
-                           // expected-note@#C_foo {{implicitly declared private here}}
+    void f() { C::foo(); }
+    // expected-error at -1 {{'foo' is a private member of 'test2::C'}}
+    //   expected-note@#test2-C-foo {{implicitly declared private here}}
   };
 
   template <> struct A<char> {
-    void f() { C::foo(); } // expected-error {{'foo' is a private member of 'test2::C'}} \
-                           // expected-note@#C_foo {{implicitly declared private here}}
+    void f() { C::foo(); }
+    // expected-error at -1 {{'foo' is a private member of 'test2::C'}}
+    //   expected-note@#test2-C-foo {{implicitly declared private here}}
   };
 }
 
@@ -132,7 +135,8 @@ namespace test7 {
   };
 
   struct C {
-    template <class T> friend void A<T>::D::g(); // expected-error {{friend declaration does not name a member of a class template specialization}}
+    template <class T> friend void A<T>::D::g();
+    // expected-error at -1 {{friend declaration does not name a member of a class template specialization}}
   };
 }
 
@@ -150,7 +154,7 @@ namespace test8 {
   };
 
   class C {
-    int n; // expected-note {{implicitly declared private here}}
+    int n; // #test8-C-n
     template <class T> friend int *A<T *>::h();
   };
 
@@ -160,7 +164,9 @@ namespace test8 {
 
   int A<int>::h() {
     C c;
-    c.n = 0; // expected-error {{'n' is a private member of 'test8::C'}}
+    c.n = 0;
+    // expected-error at -1 {{'n' is a private member of 'test8::C'}}
+    //   expected-note@#test8-C-n {{implicitly declared private here}}
     return 0;
   }
 
@@ -213,7 +219,7 @@ namespace test9 {
 namespace test10 {
   template <class T> struct A;
   class C {
-    static void foo(); // expected-note {{implicitly declared private here}}
+    static void foo(); // #test10-C-foo
     template <class T> friend void A<T>::f();
   };
 
@@ -223,7 +229,9 @@ namespace test10 {
 
   template <> struct A<int> {
     int f() {
-      C::foo(); // expected-error {{'foo' is a private member of 'test10::C'}}
+      C::foo();
+      // expected-error at -1 {{'foo' is a private member of 'test10::C'}}
+      //   expected-note@#test10-C-foo {{implicitly declared private here}}
       return 0;
     }
   };
diff --git a/clang/test/CXX/temp/temp.decls/temp.friend/p6.cpp b/clang/test/CXX/temp/temp.decls/temp.friend/p6.cpp
index 67bb7e19b4aaf..f60f3dd42445c 100644
--- a/clang/test/CXX/temp/temp.decls/temp.friend/p6.cpp
+++ b/clang/test/CXX/temp/temp.decls/temp.friend/p6.cpp
@@ -7,18 +7,21 @@ template <class T> struct B {
 
 void t1() {
   struct S {
-    template <class T> friend void f(); // expected-error {{templates can only be declared in namespace or class scope}}
+    template <class T> friend void f();
+    // expected-error at -1 {{templates can only be declared in namespace or class scope}}
   };
 }
 
 void t2() {
   struct S {
-    template <class T> friend struct A; // expected-error {{templates cannot be declared inside of a local class}}
+    template <class T> friend struct A;
+    // expected-error at -1 {{templates cannot be declared inside of a local class}}
   };
 }
 
 void t3() {
   struct S {
-    template <class T> friend void B<T>::f(); // expected-error {{templates cannot be declared inside of a local class}}
+    template <class T> friend void B<T>::f();
+    // expected-error at -1 {{templates cannot be declared inside of a local class}}
   };
 }
diff --git a/clang/test/SemaCXX/many-template-parameter-lists.cpp b/clang/test/SemaCXX/many-template-parameter-lists.cpp
index 3f44a9f82e9d7..ad6de9ee5b25e 100644
--- a/clang/test/SemaCXX/many-template-parameter-lists.cpp
+++ b/clang/test/SemaCXX/many-template-parameter-lists.cpp
@@ -28,10 +28,12 @@ struct X {
   template <class X>
   template <class Y>
   template <class Z>
-  friend void A<U>::template B<V>::template C<W>::template D<X>::template E<Y>::operator+=(Z); // expected-error {{no member 'operator+=' in 'X<int>'; it has not yet been instantiated}} \
-                                                                                               // expected-note {{not-yet-instantiated member is declared here}}
+  friend void A<U>::template B<V>::template C<W>::template D<X>::template E<Y>::operator+=(Z); // #X-friend-operator-plus-eq
+  // expected-error at -1 {{no member 'operator+=' in 'X<int>'; it has not yet been instantiated}}
+  //   expected-note@#X-friend-operator-plus-eq {{not-yet-instantiated member is declared here}}
 };
 
 void test() {
-  X<int>::A<int>::B<int>::C<int>::D<int>::E<int>() += 1.0; // expected-note {{in instantiation of template class 'X<int>' requested here}}
+  X<int>::A<int>::B<int>::C<int>::D<int>::E<int>() += 1.0;
+  // expected-note at -1 {{in instantiation of template class 'X<int>' requested here}}
 }
diff --git a/clang/test/SemaTemplate/GH71595.cpp b/clang/test/SemaTemplate/GH71595.cpp
index 637f00e886ef7..c5c5d13036233 100644
--- a/clang/test/SemaTemplate/GH71595.cpp
+++ b/clang/test/SemaTemplate/GH71595.cpp
@@ -18,17 +18,21 @@ void f() {
 template<class A>
 class temp {
     template<C<temp> T>
-    friend void g(); // expected-error {{friend declaration with a constraint that depends on an enclosing template parameter must be a definition}}
+    friend void g();
+    // expected-error at -1 {{friend declaration with a constraint that depends on an enclosing template parameter must be a definition}}
 
-    temp(); // expected-note {{implicitly declared private here}}
+    temp(); // #temp-ctor
 };
 
 template<C<temp<int>> T>
 void g() {
-    auto v = temp<T>(); // expected-error {{calling a private constructor of class 'temp<int>'}}
+    auto v = temp<T>();
+    // expected-error at -1 {{calling a private constructor of class 'temp<int>'}}
+    //   expected-note@#temp-ctor {{implicitly declared private here}}
 }
 
 void h() {
     f<int>();
-    g<int>(); // expected-note {{in instantiation of function template specialization 'g<int>' requested here}}
+    g<int>();
+    // expected-note at -1 {{in instantiation of function template specialization 'g<int>' requested here}}
 }
diff --git a/clang/test/SemaTemplate/ctad.cpp b/clang/test/SemaTemplate/ctad.cpp
index 8822931f3a8dc..0f713faf0d8dc 100644
--- a/clang/test/SemaTemplate/ctad.cpp
+++ b/clang/test/SemaTemplate/ctad.cpp
@@ -19,28 +19,33 @@ namespace pr41427 {
 namespace Access {
   struct B {
   protected:
-    struct type {};
+    struct type {}; // #Access-B-type
   };
-  template<typename T> struct D : B { // expected-note {{not viable}} \
-                                         expected-note {{implicit deduction guide declared as 'template <typename T> D(Access::D<T>) -> Access::D<T>'}}
-    D(T, typename T::type); // expected-error {{'type' is a private member of 'Access::Y'}} \
-                            // expected-note {{private member}} \
-                            // expected-note {{implicit deduction guide declared as 'template <typename T> D(T, typename T::type) -> Access::D<T>'}}
+  template<typename T> struct D : B { // #Access-D
+    D(T, typename T::type); // #Access-D-ctor
+    // expected-error at -1 {{'type' is a private member of 'Access::Y'}}
+    //   expected-note@#Access-Y-type {{implicitly declared private here}}
+    //   expected-note@#Access-D-ctor {{implicit deduction guide declared as 'template <typename T> D(T, typename T::type) -> Access::D<T>'}}
   };
   D b = {B(), {}};
 
   class X {
     using type = int;
   };
-  D x = {X(), {}}; // expected-error {{no viable constructor or deduction guide}}
+  D x = {X(), {}};
+  // expected-error at -1 {{no viable constructor or deduction guide}}
+  //   expected-note@#Access-D {{implicit deduction guide declared as 'template <typename T> D(Access::D<T>) -> Access::D<T>'}}
+  //   expected-note@#Access-D {{candidate function template not viable: requires 1 argument, but 2 were provided}}
+  //   expected-note@#Access-D-ctor {{candidate template ignored: substitution failure [with T = X]: 'type' is a private member of 'Access::X'}}
 
   // Once we implement proper support for dependent nested name specifiers in
   // friends, this should still work.
   class Y {
     template <typename T> friend D<T>::D(T, typename T::type);
-    struct type {}; // expected-note {{implicitly declared private here}}
+    struct type {}; // #Access-Y-type
   };
-  D y = {Y(), {}}; // expected-note {{in instantiation of template class 'Access::D<Access::Y>' requested here}}
+  D y = {Y(), {}};
+  // expected-note at -1 {{in instantiation of template class 'Access::D<Access::Y>' requested here}}
 
   class Z {
     template <typename T> friend class D;

>From a47fba8549ab7559fb51b6d89e126d8ee32dbece Mon Sep 17 00:00:00 2001
From: Oleksandr Tarasiuk <oleksandr.tarasiuk at outlook.com>
Date: Thu, 23 Apr 2026 18:15:43 +0300
Subject: [PATCH 07/38] update release notes

---
 clang/docs/ReleaseNotes.rst | 1 +
 1 file changed, 1 insertion(+)

diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 8d60450f54669..79f65b9ce92a9 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -131,6 +131,7 @@ C++ Language Changes
 --------------------
 
 - ``__is_trivially_equality_comparable`` no longer returns false for all enum types. (#GH132672)
+- Clang now supports friend declarations with a dependent nested name specifier. (#GH104057)
 
 C++2c Feature Support
 ^^^^^^^^^^^^^^^^^^^^^

>From afe8dc24a413f7b6a0983fa35d0c244f8ef597bb Mon Sep 17 00:00:00 2001
From: Oleksandr Tarasiuk <oleksandr.tarasiuk at outlook.com>
Date: Wed, 29 Apr 2026 15:48:35 +0300
Subject: [PATCH 08/38] fix formatting

---
 clang/lib/AST/DeclPrinter.cpp | 14 +++++++++-----
 1 file changed, 9 insertions(+), 5 deletions(-)

diff --git a/clang/lib/AST/DeclPrinter.cpp b/clang/lib/AST/DeclPrinter.cpp
index b84745382a976..d9f4ebbe9bb63 100644
--- a/clang/lib/AST/DeclPrinter.cpp
+++ b/clang/lib/AST/DeclPrinter.cpp
@@ -890,15 +890,19 @@ void DeclPrinter::VisitFriendDecl(FriendDecl *D) {
   if (TypeSourceInfo *TSI = D->getFriendType()) {
     Out << "friend ";
     Out << TSI->getType().getAsString(Policy);
-  } else if (FunctionDecl *FD = dyn_cast<FunctionDecl>(D->getFriendDecl())) {
+  }
+  else if (FunctionDecl *FD =
+      dyn_cast<FunctionDecl>(D->getFriendDecl())) {
     Out << "friend ";
     VisitFunctionDecl(FD);
-  } else if (FunctionTemplateDecl *FTD =
-                 dyn_cast<FunctionTemplateDecl>(D->getFriendDecl())) {
+  }
+  else if (FunctionTemplateDecl *FTD =
+           dyn_cast<FunctionTemplateDecl>(D->getFriendDecl())) {
     Out << "friend ";
     VisitFunctionTemplateDecl(FTD);
-  } else if (ClassTemplateDecl *CTD =
-                 dyn_cast<ClassTemplateDecl>(D->getFriendDecl())) {
+  }
+  else if (ClassTemplateDecl *CTD =
+           dyn_cast<ClassTemplateDecl>(D->getFriendDecl())) {
     Out << "friend ";
     VisitRedeclarableTemplateDecl(CTD);
   }

>From ad032bc8e618dc9be9fd65b61479fb3b3c57b0da Mon Sep 17 00:00:00 2001
From: Oleksandr Tarasiuk <oleksandr.tarasiuk at outlook.com>
Date: Wed, 29 Apr 2026 15:49:20 +0300
Subject: [PATCH 09/38] combine conditions

---
 clang/lib/AST/ASTStructuralEquivalence.cpp | 5 +----
 1 file changed, 1 insertion(+), 4 deletions(-)

diff --git a/clang/lib/AST/ASTStructuralEquivalence.cpp b/clang/lib/AST/ASTStructuralEquivalence.cpp
index e85caf1e766fc..4b2663420b80a 100644
--- a/clang/lib/AST/ASTStructuralEquivalence.cpp
+++ b/clang/lib/AST/ASTStructuralEquivalence.cpp
@@ -2803,10 +2803,7 @@ bool StructuralEquivalenceContext::IsEquivalent(TemplateParameterList *TPL1,
   if (TPL1 == TPL2)
     return true;
 
-  if (!TPL1 || !TPL2)
-    return false;
-
-  if (TPL1->size() != TPL2->size())
+  if (!TPL1 || !TPL2 || TPL1->size() != TPL2->size())
     return false;
 
   for (unsigned I = 0, N = TPL1->size(); I != N; ++I) {

>From 291ccff3cad08bcce72a1aad463eb8548bd821aa Mon Sep 17 00:00:00 2001
From: Oleksandr Tarasiuk <oleksandr.tarasiuk at outlook.com>
Date: Wed, 29 Apr 2026 15:51:02 +0300
Subject: [PATCH 10/38] remove redundant parameter list loop

---
 clang/lib/Sema/SemaDeclCXX.cpp | 10 +++-------
 1 file changed, 3 insertions(+), 7 deletions(-)

diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index 31ae89aad0851..5a25e0a112922 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -58,7 +58,6 @@
 #include <set>
 
 using namespace clang;
-using namespace sema;
 
 //===----------------------------------------------------------------------===//
 // CheckDefaultArgumentVisitor
@@ -18660,13 +18659,9 @@ NamedDecl *Sema::ActOnFriendFunctionDecl(Scope *S, Declarator &D,
         Diag(FD->getLocation(), diag::err_friend_decl_with_def_arg_must_be_def);
     }
 
-    unsigned NumTPLists = FD->getNumTemplateParameterLists();
+    ArrayRef<TemplateParameterList *> TPL = FD->getTemplateParameterLists();
     Decl *Friend;
-    if (NumTPLists && SS.isValid()) {
-      SmallVector<TemplateParameterList *, 1> TPL(NumTPLists);
-      for (unsigned I = 0, N = NumTPLists; I != N; ++I)
-        TPL[I] = FD->getTemplateParameterList(I);
-
+    if (TPL.size() > 0 && SS.isValid()) {
       if (CheckTemplateDeclScope(S, TPL.back()))
         return nullptr;
 
@@ -18677,6 +18672,7 @@ NamedDecl *Sema::ActOnFriendFunctionDecl(Scope *S, Declarator &D,
       Friend = FriendDecl::Create(Context, CurContext, D.getIdentifierLoc(), ND,
                                   DS.getFriendSpecLoc());
     }
+
     Friend->setAccess(AS_public);
     CurContext->addDecl(Friend);
 

>From 66c0c4851e84fe841b4898e2f80065fa5adcb8d4 Mon Sep 17 00:00:00 2001
From: Oleksandr Tarasiuk <oleksandr.tarasiuk at outlook.com>
Date: Wed, 29 Apr 2026 15:56:22 +0300
Subject: [PATCH 11/38] fix formatting

---
 clang/lib/Sema/SemaTemplateInstantiate.cpp | 20 ++++++++++++--------
 1 file changed, 12 insertions(+), 8 deletions(-)

diff --git a/clang/lib/Sema/SemaTemplateInstantiate.cpp b/clang/lib/Sema/SemaTemplateInstantiate.cpp
index 096b0d562469d..59a3b01e963ca 100644
--- a/clang/lib/Sema/SemaTemplateInstantiate.cpp
+++ b/clang/lib/Sema/SemaTemplateInstantiate.cpp
@@ -1321,7 +1321,6 @@ namespace {
     const MultiLevelTemplateArgumentList &TemplateArgs;
     SourceLocation Loc;
     DeclarationName Entity;
-
     // Whether to evaluate the C++20 constraints or simply substitute into them.
     bool EvaluateConstraints = true;
     // Whether Substitution was Incomplete, that is, we tried to substitute in
@@ -2832,7 +2831,8 @@ TemplateInstantiator::TransformNestedRequirement(
 
 TypeSourceInfo *Sema::SubstType(TypeSourceInfo *T,
                                 const MultiLevelTemplateArgumentList &Args,
-                                SourceLocation Loc, DeclarationName Entity,
+                                SourceLocation Loc,
+                                DeclarationName Entity,
                                 bool AllowDeducedTST) {
   assert(!CodeSynthesisContexts.empty() &&
          "Cannot perform an instantiation without some context on the "
@@ -2849,7 +2849,8 @@ TypeSourceInfo *Sema::SubstType(TypeSourceInfo *T,
 
 TypeSourceInfo *Sema::SubstType(TypeLoc TL,
                                 const MultiLevelTemplateArgumentList &Args,
-                                SourceLocation Loc, DeclarationName Entity) {
+                                SourceLocation Loc,
+                                DeclarationName Entity) {
   assert(!CodeSynthesisContexts.empty() &&
          "Cannot perform an instantiation without some context on the "
          "instantiation stack");
@@ -2921,10 +2922,13 @@ static bool NeedsInstantiationAsFunctionType(TypeSourceInfo *T) {
   return false;
 }
 
-TypeSourceInfo *Sema::SubstFunctionDeclType(
-    TypeSourceInfo *T, const MultiLevelTemplateArgumentList &Args,
-    SourceLocation Loc, DeclarationName Entity, CXXRecordDecl *ThisContext,
-    Qualifiers ThisTypeQuals, bool EvaluateConstraints) {
+TypeSourceInfo *Sema::SubstFunctionDeclType(TypeSourceInfo *T,
+                                const MultiLevelTemplateArgumentList &Args,
+                                SourceLocation Loc,
+                                DeclarationName Entity,
+                                CXXRecordDecl *ThisContext,
+                                Qualifiers ThisTypeQuals,
+                                bool EvaluateConstraints) {
   assert(!CodeSynthesisContexts.empty() &&
          "Cannot perform an instantiation without some context on the "
          "instantiation stack");
@@ -3149,7 +3153,7 @@ Sema::SubstParmVarDecl(ParmVarDecl *OldParm,
     }
   } else {
     NewTSI = SubstType(OldTSI, TemplateArgs, OldParm->getLocation(),
-                       OldParm->getDeclName(), /*AllowDeducedTST=*/false);
+                       OldParm->getDeclName());
   }
 
   if (!NewTSI)

>From 38bd8183bc83edec8708c7f266a0100adf2676de Mon Sep 17 00:00:00 2001
From: Oleksandr Tarasiuk <oleksandr.tarasiuk at outlook.com>
Date: Wed, 29 Apr 2026 16:01:43 +0300
Subject: [PATCH 12/38] add overload comment

---
 clang/include/clang/Sema/Sema.h | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 506419d85ee72..c045c93fd93ba 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -12759,6 +12759,9 @@ class Sema final : public SemaBase {
             return false;
           });
 
+  /// Finish template argument deduction for a template declaration, checking
+  /// the deduced template arguments for completeness and forming the deduced
+  /// template argument list.
   TemplateDeductionResult FinishTemplateArgumentDeduction(
       TemplateDecl *TD, TemplateParameterList *TPL,
       ArrayRef<TemplateArgument> PatternArgs, ArrayRef<TemplateArgument> Args,

>From dc55c13d9ef0f60193413e7193b6c5f498614d42 Mon Sep 17 00:00:00 2001
From: Oleksandr Tarasiuk <oleksandr.tarasiuk at outlook.com>
Date: Wed, 29 Apr 2026 16:06:07 +0300
Subject: [PATCH 13/38] handle pack-indexed qualifiers in dependent friend
 declarations

---
 clang/lib/Sema/SemaDeclCXX.cpp | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index 5a25e0a112922..751a81a65b1a1 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -18068,8 +18068,9 @@ bool Sema::CheckDependentFriend(SourceLocation Loc, NestedNameSpecifier NNS,
   if (NNS.isDependent()) {
     if (NNS.getKind() == NestedNameSpecifier::Kind::Type) {
       QualType T(NNS.getCanonical().getAsType(), 0);
-      if (isa<PackIndexingType>(T))
-        return false;
+
+      if (const auto *PIT = dyn_cast<PackIndexingType>(T))
+        T = PIT->getPattern();
 
       if (const auto *TST = dyn_cast<TemplateSpecializationType>(T)) {
         if (isa<ClassTemplateDecl>(TST->getTemplateName().getAsTemplateDecl()))

>From 6234a6fec5c4f2eeec817ef3d5598a627df1250a Mon Sep 17 00:00:00 2001
From: Oleksandr Tarasiuk <oleksandr.tarasiuk at outlook.com>
Date: Wed, 29 Apr 2026 16:59:48 +0300
Subject: [PATCH 14/38] revert reorder changes

---
 .../lib/Sema/SemaTemplateInstantiateDecl.cpp  | 61 +++++++++----------
 clang/test/CXX/drs/cwg28xx.cpp                |  1 +
 2 files changed, 31 insertions(+), 31 deletions(-)

diff --git a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
index 0c672fa8e168d..ed3ed8e437327 100644
--- a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
+++ b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
@@ -3193,6 +3193,29 @@ Decl *TemplateDeclInstantiator::VisitCXXMethodDecl(
     D->setTypeSourceInfo(TSI);
   }
 
+  SmallVector<ParmVarDecl *, 4> Params;
+  TypeSourceInfo *TInfo = SubstFunctionType(D, Params);
+  if (!TInfo)
+    return nullptr;
+  QualType T = adjustFunctionTypeForInstantiation(SemaRef.Context, D, TInfo);
+
+  if (TemplateParams && TemplateParams->size()) {
+    auto *LastParam =
+        dyn_cast<TemplateTypeParmDecl>(TemplateParams->asArray().back());
+    if (LastParam && LastParam->isImplicit() &&
+        LastParam->hasTypeConstraint()) {
+      // In abbreviated templates, the type-constraints of invented template
+      // type parameters are instantiated with the function type, invalidating
+      // the TemplateParameterList which relied on the template type parameter
+      // not having a type constraint. Recreate the TemplateParameterList with
+      // the updated parameter list.
+      TemplateParams = TemplateParameterList::Create(
+          SemaRef.Context, TemplateParams->getTemplateLoc(),
+          TemplateParams->getLAngleLoc(), TemplateParams->asArray(),
+          TemplateParams->getRAngleLoc(), TemplateParams->getRequiresClause());
+    }
+  }
+
   NestedNameSpecifierLoc QualifierLoc = D->getQualifierLoc();
   if (QualifierLoc) {
     QualifierLoc = SemaRef.SubstNestedNameSpecifierLoc(QualifierLoc,
@@ -3223,39 +3246,14 @@ Decl *TemplateDeclInstantiator::VisitCXXMethodDecl(
 
   DeclarationNameInfo NameInfo
     = SemaRef.SubstDeclarationNameInfo(D->getNameInfo(), TemplateArgs);
-  if (!NameInfo.getName())
-    return nullptr;
-
-  CXXMethodDecl *Method = nullptr;
-  SourceLocation StartLoc = D->getInnerLocStart();
-
-  SmallVector<ParmVarDecl *, 4> Params;
-  TypeSourceInfo *TInfo = SubstFunctionType(D, Params);
-  if (!TInfo)
-    return nullptr;
-
-  QualType T = adjustFunctionTypeForInstantiation(SemaRef.Context, D, TInfo);
-
-  if (TemplateParams && TemplateParams->size()) {
-    auto *LastParam =
-        dyn_cast<TemplateTypeParmDecl>(TemplateParams->asArray().back());
-    if (LastParam && LastParam->isImplicit() &&
-        LastParam->hasTypeConstraint()) {
-      // In abbreviated templates, the type-constraints of invented template
-      // type parameters are instantiated with the function type, invalidating
-      // the TemplateParameterList which relied on the template type parameter
-      // not having a type constraint. Recreate the TemplateParameterList with
-      // the updated parameter list.
-      TemplateParams = TemplateParameterList::Create(
-          SemaRef.Context, TemplateParams->getTemplateLoc(),
-          TemplateParams->getLAngleLoc(), TemplateParams->asArray(),
-          TemplateParams->getRAngleLoc(), TemplateParams->getRequiresClause());
-    }
-  }
 
   if (FunctionRewriteKind != RewriteKind::None)
     adjustForRewrite(FunctionRewriteKind, D, T, TInfo, NameInfo);
 
+  // Build the instantiated method declaration.
+  CXXMethodDecl *Method = nullptr;
+
+  SourceLocation StartLoc = D->getInnerLocStart();
   if (CXXConstructorDecl *Constructor = dyn_cast<CXXConstructorDecl>(D)) {
     Method = CXXConstructorDecl::Create(
         SemaRef.Context, Record, StartLoc, NameInfo, T, TInfo,
@@ -5136,8 +5134,9 @@ TemplateDeclInstantiator::InstantiateVarTemplatePartialSpecialization(
   return InstPartialSpec;
 }
 
-TypeSourceInfo *TemplateDeclInstantiator::SubstFunctionType(
-    FunctionDecl *D, SmallVectorImpl<ParmVarDecl *> &Params) {
+TypeSourceInfo*
+TemplateDeclInstantiator::SubstFunctionType(FunctionDecl *D,
+                              SmallVectorImpl<ParmVarDecl *> &Params) {
   TypeSourceInfo *OldTInfo = D->getTypeSourceInfo();
   assert(OldTInfo && "substituting function without type source info");
   assert(Params.empty() && "parameter vector is non-empty at start");
diff --git a/clang/test/CXX/drs/cwg28xx.cpp b/clang/test/CXX/drs/cwg28xx.cpp
index 02c8f30249683..6876cfa12ff51 100644
--- a/clang/test/CXX/drs/cwg28xx.cpp
+++ b/clang/test/CXX/drs/cwg28xx.cpp
@@ -183,6 +183,7 @@ struct A {
   friend void Ts...[0]::f();
   template<typename U>
   friend void Ts...[0]::g();
+  // since-cxx26-error at -1 {{friend declaration does not name a member of a class template specialization}}
 
   friend struct Ts...[0]::B;
   // FIXME: The index of the pack-index-specifier is printed as a memory address in the diagnostic.

>From 4a1cb61071f833c9278776b129c97b06d73d5f4b Mon Sep 17 00:00:00 2001
From: Oleksandr Tarasiuk <oleksandr.tarasiuk at outlook.com>
Date: Wed, 29 Apr 2026 17:19:33 +0300
Subject: [PATCH 15/38] add additional tests

---
 clang/test/SemaTemplate/concepts-friends.cpp | 32 ++++++++++++++++++++
 1 file changed, 32 insertions(+)

diff --git a/clang/test/SemaTemplate/concepts-friends.cpp b/clang/test/SemaTemplate/concepts-friends.cpp
index 11287aa773b1b..0dcfdd097e2d4 100644
--- a/clang/test/SemaTemplate/concepts-friends.cpp
+++ b/clang/test/SemaTemplate/concepts-friends.cpp
@@ -566,3 +566,35 @@ struct Test {
 };
 
 }
+
+namespace DependentFriends {
+template <class T> concept X = requires { typename T::type; }; // #DependentFriends_X
+
+struct A {
+  using type = int;
+};
+struct B {};
+
+template <class T> struct C {
+  static void f()
+    requires X<T>; // #DependentFriends_C_f
+};
+
+class D {
+  static int n;
+  template <X T> friend void C<T>::f();
+};
+
+template <class T>
+void C<T>::f() requires X<T> {
+  D::n = 0;
+}
+
+void test() {
+  C<A>::f();
+  C<B>::f();
+  // expected-error at -1 {{invalid reference to function 'f': constraints not satisfied}}
+  //   expected-note@#DependentFriends_C_f {{because 'DependentFriends::B' does not satisfy 'X'}}
+  //   expected-note@#DependentFriends_X {{because 'typename T::type' would be invalid: no type named 'type' in 'DependentFriends::B'}}
+}
+}

>From d1ffffd15e45840262ffd37a7866e988d8dbbc5e Mon Sep 17 00:00:00 2001
From: Oleksandr Tarasiuk <oleksandr.tarasiuk at outlook.com>
Date: Wed, 29 Apr 2026 17:29:53 +0300
Subject: [PATCH 16/38] fix formatting

---
 clang/lib/AST/DeclPrinter.cpp | 14 +++++---------
 1 file changed, 5 insertions(+), 9 deletions(-)

diff --git a/clang/lib/AST/DeclPrinter.cpp b/clang/lib/AST/DeclPrinter.cpp
index d9f4ebbe9bb63..b84745382a976 100644
--- a/clang/lib/AST/DeclPrinter.cpp
+++ b/clang/lib/AST/DeclPrinter.cpp
@@ -890,19 +890,15 @@ void DeclPrinter::VisitFriendDecl(FriendDecl *D) {
   if (TypeSourceInfo *TSI = D->getFriendType()) {
     Out << "friend ";
     Out << TSI->getType().getAsString(Policy);
-  }
-  else if (FunctionDecl *FD =
-      dyn_cast<FunctionDecl>(D->getFriendDecl())) {
+  } else if (FunctionDecl *FD = dyn_cast<FunctionDecl>(D->getFriendDecl())) {
     Out << "friend ";
     VisitFunctionDecl(FD);
-  }
-  else if (FunctionTemplateDecl *FTD =
-           dyn_cast<FunctionTemplateDecl>(D->getFriendDecl())) {
+  } else if (FunctionTemplateDecl *FTD =
+                 dyn_cast<FunctionTemplateDecl>(D->getFriendDecl())) {
     Out << "friend ";
     VisitFunctionTemplateDecl(FTD);
-  }
-  else if (ClassTemplateDecl *CTD =
-           dyn_cast<ClassTemplateDecl>(D->getFriendDecl())) {
+  } else if (ClassTemplateDecl *CTD =
+                 dyn_cast<ClassTemplateDecl>(D->getFriendDecl())) {
     Out << "friend ";
     VisitRedeclarableTemplateDecl(CTD);
   }

>From 7a0ad25525b48ffe5a6624616579c89608029f6c Mon Sep 17 00:00:00 2001
From: Oleksandr Tarasiuk <oleksandr.tarasiuk at outlook.com>
Date: Wed, 29 Apr 2026 21:01:37 +0300
Subject: [PATCH 17/38] handle requires clause in tpl params list

---
 clang/lib/AST/ASTStructuralEquivalence.cpp | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/clang/lib/AST/ASTStructuralEquivalence.cpp b/clang/lib/AST/ASTStructuralEquivalence.cpp
index 4b2663420b80a..aa09ea7efcf92 100644
--- a/clang/lib/AST/ASTStructuralEquivalence.cpp
+++ b/clang/lib/AST/ASTStructuralEquivalence.cpp
@@ -2279,7 +2279,8 @@ static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
       return false;
   }
 
-  return true;
+  return IsStructurallyEquivalent(Context, Params1->getRequiresClause(),
+                                  Params2->getRequiresClause());
 }
 
 static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
@@ -2811,7 +2812,7 @@ bool StructuralEquivalenceContext::IsEquivalent(TemplateParameterList *TPL1,
       return false;
   }
 
-  return true;
+  return IsEquivalent(TPL1->getRequiresClause(), TPL2->getRequiresClause());
 }
 
 bool StructuralEquivalenceContext::CheckKindSpecificEquivalence(

>From 600949e57d4aabf6c163ba458b8abe22fef2b6c4 Mon Sep 17 00:00:00 2001
From: Oleksandr Tarasiuk <oleksandr.tarasiuk at outlook.com>
Date: Wed, 29 Apr 2026 21:05:32 +0300
Subject: [PATCH 18/38] wrap FinishTemplateArgumentDeduction into
 runWithSufficientStackSpace

---
 clang/lib/Sema/SemaAccess.cpp | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/clang/lib/Sema/SemaAccess.cpp b/clang/lib/Sema/SemaAccess.cpp
index 91e84d62f27c9..d124acdb8c864 100644
--- a/clang/lib/Sema/SemaAccess.cpp
+++ b/clang/lib/Sema/SemaAccess.cpp
@@ -305,12 +305,12 @@ static bool CanDeduceTemplateArguments(Sema &S, TemplateParameterList *TPL,
   if (Inst.isInvalid())
     return false;
 
-  if (S.FinishTemplateArgumentDeduction(
-          TD, TPL, PatternArgs, Args, Deduced, Info,
-          /*CopyDeducedArgs=*/false) != TemplateDeductionResult::Success)
-    return false;
-
-  return !Trap.hasErrorOccurred();
+  TemplateDeductionResult Result;
+  S.runWithSufficientStackSpace(Info.getLocation(), [&] {
+    Result = S.FinishTemplateArgumentDeduction(
+        TD, TPL, PatternArgs, Args, Deduced, Info, /*CopyDeducedArgs=*/false);
+  });
+  return Result == TemplateDeductionResult::Success && !Trap.hasErrorOccurred();
 }
 
 static CanQual<FunctionProtoType>

>From cac422f588b8e0c6b7e9ca13823c89d50a90a6c8 Mon Sep 17 00:00:00 2001
From: Oleksandr Tarasiuk <oleksandr.tarasiuk at outlook.com>
Date: Thu, 30 Apr 2026 14:22:07 +0300
Subject: [PATCH 19/38] update FriendTemplate in  DeclNodes to inherit from
 Friend

---
 clang/include/clang/AST/DeclFriend.h   |  2 +-
 clang/include/clang/Basic/DeclNodes.td |  2 +-
 clang/lib/AST/ASTImporter.cpp          | 13 ++++++-------
 clang/lib/Sema/SemaAccess.cpp          |  2 +-
 clang/lib/Sema/SemaDeclCXX.cpp         |  6 +++---
 5 files changed, 12 insertions(+), 13 deletions(-)

diff --git a/clang/include/clang/AST/DeclFriend.h b/clang/include/clang/AST/DeclFriend.h
index 2cd5a1af17fd8..16fe6219bdbb0 100644
--- a/clang/include/clang/AST/DeclFriend.h
+++ b/clang/include/clang/AST/DeclFriend.h
@@ -130,7 +130,7 @@ class FriendDecl : public Decl {
   // Implement isa/cast/dyncast/etc.
   static bool classof(const Decl *D) { return classofKind(D->getKind()); }
   static bool classofKind(Kind K) {
-    return K == Decl::Friend || K == Decl::FriendTemplate;
+    return K >= firstFriend && K <= lastFriend;
   }
 };
 /// An iterator over the friend declarations of a class.
diff --git a/clang/include/clang/Basic/DeclNodes.td b/clang/include/clang/Basic/DeclNodes.td
index ffb58b43812dc..c55a3d68184f4 100644
--- a/clang/include/clang/Basic/DeclNodes.td
+++ b/clang/include/clang/Basic/DeclNodes.td
@@ -99,7 +99,7 @@ def FileScopeAsm : DeclNode<Decl>;
 def TopLevelStmt : DeclNode<Decl>, DeclContext;
 def AccessSpec : DeclNode<Decl>;
 def Friend : DeclNode<Decl>;
-def FriendTemplate : DeclNode<Decl>;
+def FriendTemplate : DeclNode<Friend>;
 def StaticAssert : DeclNode<Decl>;
 def ExplicitInstantiation : DeclNode<Decl>;
 def Block : DeclNode<Decl, "blocks">, DeclContext;
diff --git a/clang/lib/AST/ASTImporter.cpp b/clang/lib/AST/ASTImporter.cpp
index 305d4f16a41f5..4228904c39a05 100644
--- a/clang/lib/AST/ASTImporter.cpp
+++ b/clang/lib/AST/ASTImporter.cpp
@@ -4608,8 +4608,8 @@ ExpectedDecl ASTNodeImporter::VisitFriendDecl(FriendDecl *D) {
   SmallVector<FriendDecl *, 2> ImportedEquivalentFriends;
   for (FriendDecl *ImportedFriend : RD->friends()) {
     if (ImportedFriend->getKind() == Decl::Friend &&
-        IsEquivalentFriend(Importer, D, cast<FriendDecl>(ImportedFriend)))
-      ImportedEquivalentFriends.push_back(cast<FriendDecl>(ImportedFriend));
+        IsEquivalentFriend(Importer, D, ImportedFriend))
+      ImportedEquivalentFriends.push_back(ImportedFriend);
   }
 
   FriendCountAndPosition CountAndPosition =
@@ -4671,11 +4671,10 @@ ExpectedDecl ASTNodeImporter::VisitFriendTemplateDecl(FriendTemplateDecl *D) {
   const auto *RD = cast<CXXRecordDecl>(DC);
   SmallVector<FriendTemplateDecl *, 2> ImportedEquivalentFriends;
   for (FriendDecl *ImportedFriend : RD->friends()) {
-    if (isa<FriendTemplateDecl>(ImportedFriend) &&
-        IsEquivalentFriend(Importer, D,
-                           cast<FriendTemplateDecl>(ImportedFriend)))
-      ImportedEquivalentFriends.push_back(
-          cast<FriendTemplateDecl>(ImportedFriend));
+    auto *ImportedFriendTemplate = dyn_cast<FriendTemplateDecl>(ImportedFriend);
+    if (ImportedFriendTemplate &&
+        IsEquivalentFriend(Importer, D, ImportedFriendTemplate))
+      ImportedEquivalentFriends.push_back(ImportedFriendTemplate);
   }
 
   FriendCountAndPosition CountAndPosition =
diff --git a/clang/lib/Sema/SemaAccess.cpp b/clang/lib/Sema/SemaAccess.cpp
index d124acdb8c864..7c28708783b99 100644
--- a/clang/lib/Sema/SemaAccess.cpp
+++ b/clang/lib/Sema/SemaAccess.cpp
@@ -890,7 +890,7 @@ static AccessResult GetFriendKind(Sema &S,
     if (auto *FTD = dyn_cast<FriendTemplateDecl>(Friend))
       AR = MatchesFriend(S, EC, FTD);
     else
-      AR = MatchesFriend(S, EC, cast<FriendDecl>(Friend));
+      AR = MatchesFriend(S, EC, Friend);
 
     switch (AR) {
     case AR_accessible:
diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index 751a81a65b1a1..f69c28967c0bb 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -18199,7 +18199,7 @@ DeclResult Sema::ActOnTemplatedFriendTag(
   TL.setQualifierLoc(SS.getWithLocInContext(Context));
   TL.setNameLoc(NameLoc);
 
-  Decl *Friend;
+  FriendDecl *Friend;
   if (TempParamLists.empty())
     Friend = FriendDecl::Create(Context, CurContext, NameLoc, TSI, FriendLoc,
                                 EllipsisLoc);
@@ -18214,7 +18214,7 @@ DeclResult Sema::ActOnTemplatedFriendTag(
   if (EllipsisLoc.isValid() && NNS.isDependent()) {
     Diag(NameLoc, diag::warn_template_qualified_friend_unsupported)
         << SS.getScopeRep() << SS.getRange() << cast<CXXRecordDecl>(CurContext);
-    cast<FriendDecl>(Friend)->setUnsupportedFriend(true);
+    Friend->setUnsupportedFriend(true);
   }
 
   Friend->setAccess(AS_public);
@@ -18661,7 +18661,7 @@ NamedDecl *Sema::ActOnFriendFunctionDecl(Scope *S, Declarator &D,
     }
 
     ArrayRef<TemplateParameterList *> TPL = FD->getTemplateParameterLists();
-    Decl *Friend;
+    FriendDecl *Friend;
     if (TPL.size() > 0 && SS.isValid()) {
       if (CheckTemplateDeclScope(S, TPL.back()))
         return nullptr;

>From 76c27e076a7abe1a4a96e9d9d6e8ee67fe55c704 Mon Sep 17 00:00:00 2001
From: Oleksandr Tarasiuk <oleksandr.tarasiuk at outlook.com>
Date: Thu, 30 Apr 2026 16:58:23 +0300
Subject: [PATCH 20/38] remove unsupported friend flag

---
 clang/include/clang/AST/DeclFriend.h          |  19 +--
 clang/include/clang/Basic/DiagnosticGroups.td |   1 -
 .../clang/Basic/DiagnosticSemaKinds.td        |  10 --
 clang/include/clang/Sema/Template.h           |   9 ++
 clang/lib/AST/ASTImporter.cpp                 |   1 -
 clang/lib/Sema/SemaAccess.cpp                 |   7 +-
 clang/lib/Sema/SemaDeclCXX.cpp                |  20 +--
 clang/lib/Sema/SemaTemplate.cpp               |   9 +-
 .../lib/Sema/SemaTemplateInstantiateDecl.cpp  | 147 ++++++++++--------
 clang/lib/Serialization/ASTReaderDecl.cpp     |   2 -
 clang/lib/Serialization/ASTWriterDecl.cpp     |   2 -
 clang/test/CXX/drs/cwg28xx.cpp                |   2 +-
 clang/test/Parser/cxx2c-variadic-friends.cpp  |   6 +-
 clang/test/SemaTemplate/friend-template.cpp   |   9 +-
 14 files changed, 112 insertions(+), 132 deletions(-)

diff --git a/clang/include/clang/AST/DeclFriend.h b/clang/include/clang/AST/DeclFriend.h
index 16fe6219bdbb0..4f0b865dbfbc7 100644
--- a/clang/include/clang/AST/DeclFriend.h
+++ b/clang/include/clang/AST/DeclFriend.h
@@ -58,12 +58,6 @@ class FriendDecl : public Decl {
 
   SourceLocation FriendLoc;
 
-  /// True if this 'friend' declaration is unsupported.  Eventually we
-  /// will support every possible friend declaration, but for now we
-  /// silently ignore some and set this flag to authorize all access.
-  LLVM_PREFERRED_TYPE(bool)
-  unsigned UnsupportedFriend : 1;
-
 protected:
   // The declaration that's a friend of this class.
   FriendUnion Friend;
@@ -73,10 +67,9 @@ class FriendDecl : public Decl {
   FriendDecl(Kind K, DeclContext *DC, SourceLocation L, FriendUnion Friend,
              SourceLocation FriendL, SourceLocation EllipsisLoc = {})
       : Decl(K, DC, L), EllipsisLoc(EllipsisLoc), FriendLoc(FriendL),
-        UnsupportedFriend(false), Friend(Friend), NextFriend() {}
+        Friend(Friend), NextFriend() {}
 
-  FriendDecl(Kind K, EmptyShell Empty)
-      : Decl(K, Empty), UnsupportedFriend(false) {}
+  FriendDecl(Kind K, EmptyShell Empty) : Decl(K, Empty) {}
 
   FriendDecl *getNextFriend() {
     if (NextFriend.isOffset())
@@ -117,14 +110,6 @@ class FriendDecl : public Decl {
 
   SourceRange getSourceRange() const override LLVM_READONLY;
 
-  /// Determines if this friend kind is unsupported.
-  bool isUnsupportedFriend() const {
-    return UnsupportedFriend;
-  }
-  void setUnsupportedFriend(bool Unsupported) {
-    UnsupportedFriend = Unsupported;
-  }
-
   bool isPackExpansion() const { return EllipsisLoc.isValid(); }
 
   // Implement isa/cast/dyncast/etc.
diff --git a/clang/include/clang/Basic/DiagnosticGroups.td b/clang/include/clang/Basic/DiagnosticGroups.td
index 74f9fab14d82c..93af83301655b 100644
--- a/clang/include/clang/Basic/DiagnosticGroups.td
+++ b/clang/include/clang/Basic/DiagnosticGroups.td
@@ -1142,7 +1142,6 @@ def Attributes : DiagGroup<"attributes", [UnknownAttributes,
 def UnknownSanitizers : DiagGroup<"unknown-sanitizers">;
 def UnnamedTypeTemplateArgs : DiagGroup<"unnamed-type-template-args",
                                         [CXX98CompatUnnamedTypeTemplateArgs]>;
-def UnsupportedFriend : DiagGroup<"unsupported-friend">;
 def UnusedArgument : DiagGroup<"unused-argument">;
 def UnusedCommandLineArgument : DiagGroup<"unused-command-line-argument">;
 def IgnoredOptimizationArgument : DiagGroup<"ignored-optimization-argument">;
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 1a0c3eb361a66..f5c93412abf76 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -1908,16 +1908,6 @@ def err_friend_not_first_in_declaration : Error<
   "'friend' must appear first in a non-function declaration">;
 def err_using_decl_friend : Error<
   "cannot befriend target of using declaration">;
-def warn_template_qualified_friend_unsupported
-    : Warning<
-          "dependent nested name specifier %0 for friend class declaration is "
-          "not supported; turning off access control for %1">,
-      InGroup<UnsupportedFriend>;
-def warn_template_qualified_friend_ignored
-    : Warning<"dependent nested name specifier %0 for friend template "
-              "declaration is "
-              "not supported; ignoring this friend declaration">,
-      InGroup<UnsupportedFriend>;
 def ext_friend_tag_redecl_outside_namespace : ExtWarn<
   "unqualified friend declaration referring to type outside of the nearest "
   "enclosing namespace is a Microsoft extension; add a nested name specifier">,
diff --git a/clang/include/clang/Sema/Template.h b/clang/include/clang/Sema/Template.h
index 62b1c16de82c1..42febace0a671 100644
--- a/clang/include/clang/Sema/Template.h
+++ b/clang/include/clang/Sema/Template.h
@@ -724,6 +724,10 @@ enum class TemplateSubstitutionKind : char {
     TemplateParameterList *
       SubstTemplateParams(TemplateParameterList *List);
 
+    bool SubstTemplateParameterLists(
+        ArrayRef<TemplateParameterList *> TPL,
+        SmallVectorImpl<TemplateParameterList *> &InstTPL);
+
     bool SubstQualifier(const DeclaratorDecl *OldDecl,
                         DeclaratorDecl *NewDecl);
     bool SubstQualifier(const TagDecl *OldDecl,
@@ -734,6 +738,11 @@ enum class TemplateSubstitutionKind : char {
         ArrayRef<TemplateArgument> Converted,
         VarTemplateSpecializationDecl *PrevDecl = nullptr);
 
+    template <typename FriendTy>
+    bool
+    InstantiateFriendPackExpansion(FriendTy *D, TypeSourceInfo *TSI,
+                                   ArrayRef<TemplateParameterList *> TPL = {});
+
     Decl *InstantiateTypedefNameDecl(TypedefNameDecl *D, bool IsTypeAlias);
     Decl *InstantiateTypeAliasTemplateDecl(TypeAliasTemplateDecl *D);
     ClassTemplatePartialSpecializationDecl *
diff --git a/clang/lib/AST/ASTImporter.cpp b/clang/lib/AST/ASTImporter.cpp
index 4228904c39a05..5533cacac129c 100644
--- a/clang/lib/AST/ASTImporter.cpp
+++ b/clang/lib/AST/ASTImporter.cpp
@@ -4727,7 +4727,6 @@ ExpectedDecl ASTNodeImporter::VisitFriendTemplateDecl(FriendTemplateDecl *D) {
                               *EllipsisLocOrErr))
     return FTD;
 
-  FTD->setUnsupportedFriend(D->isUnsupportedFriend());
   FTD->setAccess(D->getAccess());
   FTD->setLexicalDeclContext(LexicalDC);
   LexicalDC->addDeclInternal(FTD);
diff --git a/clang/lib/Sema/SemaAccess.cpp b/clang/lib/Sema/SemaAccess.cpp
index 7c28708783b99..0a67b2b9d8f6b 100644
--- a/clang/lib/Sema/SemaAccess.cpp
+++ b/clang/lib/Sema/SemaAccess.cpp
@@ -851,9 +851,8 @@ static AccessResult MatchesFriend(Sema &S, const EffectiveContext &EC,
 static AccessResult MatchesFriend(Sema &S,
                                   const EffectiveContext &EC,
                                   FriendDecl *FriendD) {
-  // Whitelist accesses if there's an invalid or unsupported friend
-  // declaration.
-  if (FriendD->isInvalidDecl() || FriendD->isUnsupportedFriend())
+  // Whitelist accesses if there's an invalid friend declaration.
+  if (FriendD->isInvalidDecl())
     return AR_accessible;
 
   if (NamedDecl *Friend = FriendD->getFriendDecl())
@@ -867,7 +866,7 @@ static AccessResult MatchesFriend(Sema &S,
 
 static AccessResult MatchesFriend(Sema &S, const EffectiveContext &EC,
                                   FriendTemplateDecl *FTD) {
-  if (FTD->isInvalidDecl() || FTD->isUnsupportedFriend())
+  if (FTD->isInvalidDecl())
     return AR_accessible;
 
   if (NamedDecl *ND = FTD->getFriendDecl())
diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index f69c28967c0bb..d9657afc260cd 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -18103,16 +18103,16 @@ DeclResult Sema::ActOnTemplatedFriendTag(
               TagLoc, NameLoc, SS, nullptr, TempParamLists, /*friend*/ true,
               IsMemberSpecialization, Invalid)) {
     if (TemplateParams->size() > 0) {
-      // This is a declaration of a class template.
       if (Invalid)
         return true;
 
-      return CheckClassTemplate(S, TagSpec, TagUseKind::Friend, TagLoc, SS,
-                                Name, NameLoc, Attr, TemplateParams, AS_public,
-                                /*ModulePrivateLoc=*/SourceLocation(),
-                                FriendLoc, TempParamLists.size() - 1,
-                                TempParamLists.data())
-          .get();
+      if (SS.isEmpty() || !SS.getScopeRep().isDependent()) {
+        DeclResult Result = CheckClassTemplate(
+            S, TagSpec, TagUseKind::Friend, TagLoc, SS, Name, NameLoc, Attr,
+            TemplateParams, AS_public, /*ModulePrivateLoc=*/SourceLocation(),
+            FriendLoc, TempParamLists.size() - 1, TempParamLists.data());
+        return Result.get();
+      }
     } else {
       // The "template<>" header is extraneous.
       Diag(TemplateParams->getTemplateLoc(), diag::err_template_tag_noparams)
@@ -18211,12 +18211,6 @@ DeclResult Sema::ActOnTemplatedFriendTag(
                                         FriendLoc, TempParamLists, EllipsisLoc);
   }
 
-  if (EllipsisLoc.isValid() && NNS.isDependent()) {
-    Diag(NameLoc, diag::warn_template_qualified_friend_unsupported)
-        << SS.getScopeRep() << SS.getRange() << cast<CXXRecordDecl>(CurContext);
-    Friend->setUnsupportedFriend(true);
-  }
-
   Friend->setAccess(AS_public);
   CurContext->addDecl(Friend);
 
diff --git a/clang/lib/Sema/SemaTemplate.cpp b/clang/lib/Sema/SemaTemplate.cpp
index 1f5d12c2cc068..ef8ab574eb0d2 100644
--- a/clang/lib/Sema/SemaTemplate.cpp
+++ b/clang/lib/Sema/SemaTemplate.cpp
@@ -1955,14 +1955,9 @@ DeclResult Sema::CheckClassTemplate(
   if (SS.isNotEmpty() && !SS.isInvalid()) {
     SemanticContext = computeDeclContext(SS, true);
     if (!SemanticContext) {
-      // FIXME: Horrible, horrible hack! We can't currently represent this
-      // in the AST, and historically we have just ignored such friend
-      // class templates, so don't complain here.
-      Diag(NameLoc, TUK == TagUseKind::Friend
-                        ? diag::warn_template_qualified_friend_ignored
-                        : diag::err_template_qualified_declarator_no_match)
+      Diag(NameLoc, diag::err_template_qualified_declarator_no_match)
           << SS.getScopeRep() << SS.getRange();
-      return TUK != TagUseKind::Friend;
+      return true;
     }
 
     if (RequireCompleteDeclContext(SS, SemanticContext))
diff --git a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
index ed3ed8e437327..a4fde32e41552 100644
--- a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
+++ b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
@@ -2001,68 +2001,69 @@ Decl *TemplateDeclInstantiator::VisitIndirectFieldDecl(IndirectFieldDecl *D) {
   return IndirectField;
 }
 
-Decl *TemplateDeclInstantiator::VisitFriendDecl(FriendDecl *D) {
-  // Handle friend type expressions by simply substituting template
-  // parameters into the pattern type and checking the result.
-  if (TypeSourceInfo *Ty = D->getFriendType()) {
-    TypeSourceInfo *InstTy;
-    // If this is an unsupported friend, don't bother substituting template
-    // arguments into it. The actual type referred to won't be used by any
-    // parts of Clang, and may not be valid for instantiating. Just use the
-    // same info for the instantiated friend.
-    if (D->isUnsupportedFriend()) {
-      InstTy = Ty;
-    } else {
-      if (D->isPackExpansion()) {
-        SmallVector<UnexpandedParameterPack, 2> Unexpanded;
-        SemaRef.collectUnexpandedParameterPacks(Ty->getTypeLoc(), Unexpanded);
-        assert(!Unexpanded.empty() && "Pack expansion without packs");
-
-        bool ShouldExpand = true;
-        bool RetainExpansion = false;
-        UnsignedOrNone NumExpansions = std::nullopt;
-        if (SemaRef.CheckParameterPacksForExpansion(
-                D->getEllipsisLoc(), D->getSourceRange(), Unexpanded,
-                TemplateArgs, /*FailOnPackProducingTemplates=*/true,
-                ShouldExpand, RetainExpansion, NumExpansions))
-          return nullptr;
+template <typename FriendTy>
+bool TemplateDeclInstantiator::InstantiateFriendPackExpansion(
+    FriendTy *D, TypeSourceInfo *TSI, ArrayRef<TemplateParameterList *> TPL) {
+  SmallVector<UnexpandedParameterPack, 2> Unexpanded;
+  SemaRef.collectUnexpandedParameterPacks(TSI->getTypeLoc(), Unexpanded);
+  assert(!Unexpanded.empty() && "Pack expansion without packs");
 
-        assert(!RetainExpansion &&
-               "should never retain an expansion for a variadic friend decl");
-
-        if (ShouldExpand) {
-          SmallVector<FriendDecl *> Decls;
-          for (unsigned I = 0; I != *NumExpansions; I++) {
-            Sema::ArgPackSubstIndexRAII SubstIndex(SemaRef, I);
-            TypeSourceInfo *TSI = SemaRef.SubstType(
-                Ty, TemplateArgs, D->getEllipsisLoc(), DeclarationName());
-            if (!TSI)
-              return nullptr;
-
-            auto FD =
-                FriendDecl::Create(SemaRef.Context, Owner, D->getLocation(),
-                                   TSI, D->getFriendLoc());
-
-            FD->setAccess(AS_public);
-            Owner->addDecl(FD);
-            Decls.push_back(FD);
-          }
+  bool ShouldExpand = true;
+  bool RetainExpansion = false;
+  UnsignedOrNone NumExpansions = std::nullopt;
+  if (SemaRef.CheckParameterPacksForExpansion(
+          D->getEllipsisLoc(), D->getSourceRange(), Unexpanded, TemplateArgs,
+          /*FailOnPackProducingTemplates=*/true, ShouldExpand, RetainExpansion,
+          NumExpansions))
+    return true;
 
-          // Just drop this node; we have no use for it anymore.
-          return nullptr;
-        }
-      }
+  assert(!RetainExpansion &&
+         "should never retain an expansion for a friend declaration");
+
+  if (!ShouldExpand)
+    return false;
+
+  for (unsigned I = 0; I != *NumExpansions; I++) {
+    Sema::ArgPackSubstIndexRAII SubstIndex(SemaRef, I);
+    SmallVector<TemplateParameterList *, 1> InstTPL;
+    if (SubstTemplateParameterLists(TPL, InstTPL))
+      return true;
+
+    TypeSourceInfo *InstTy = SemaRef.SubstType(
+        TSI, TemplateArgs, D->getEllipsisLoc(), DeclarationName());
+    if (!InstTy)
+      return true;
 
-      InstTy = SemaRef.SubstType(Ty, TemplateArgs, D->getLocation(),
-                                 DeclarationName());
+    FriendDecl *FD;
+    if (isa<FriendTemplateDecl>(D))
+      FD = FriendTemplateDecl::Create(SemaRef.Context, Owner, D->getLocation(),
+                                      InstTy, D->getFriendLoc(), InstTPL);
+    else {
+      assert(InstTPL.empty() && "unexpected template parameter lists");
+      FD = FriendDecl::Create(SemaRef.Context, Owner, D->getLocation(), InstTy,
+                              D->getFriendLoc());
     }
+
+    FD->setAccess(AS_public);
+    Owner->addDecl(FD);
+  }
+
+  return true;
+}
+
+Decl *TemplateDeclInstantiator::VisitFriendDecl(FriendDecl *D) {
+  if (TypeSourceInfo *Ty = D->getFriendType()) {
+    if (D->isPackExpansion() && InstantiateFriendPackExpansion(D, Ty))
+      return nullptr;
+
+    TypeSourceInfo *InstTy = SemaRef.SubstType(
+        Ty, TemplateArgs, D->getLocation(), DeclarationName());
     if (!InstTy)
       return nullptr;
 
     FriendDecl *FD = FriendDecl::Create(
         SemaRef.Context, Owner, D->getLocation(), InstTy, D->getFriendLoc());
     FD->setAccess(AS_public);
-    FD->setUnsupportedFriend(D->isUnsupportedFriend());
     Owner->addDecl(FD);
     return FD;
   }
@@ -2081,7 +2082,6 @@ Decl *TemplateDeclInstantiator::VisitFriendDecl(FriendDecl *D) {
     FriendDecl::Create(SemaRef.Context, Owner, D->getLocation(),
                        cast<NamedDecl>(NewND), D->getFriendLoc());
   FD->setAccess(AS_public);
-  FD->setUnsupportedFriend(D->isUnsupportedFriend());
   Owner->addDecl(FD);
   return FD;
 }
@@ -4725,23 +4725,27 @@ Decl *TemplateDeclInstantiator::VisitObjCAtDefsFieldDecl(ObjCAtDefsFieldDecl *D)
 Decl *TemplateDeclInstantiator::VisitFriendTemplateDecl(FriendTemplateDecl *D) {
   ArrayRef<TemplateParameterList *> TPLists =
       D->getFriendTypeTemplateParameterLists();
-  SmallVector<TemplateParameterList *, 1> TPL(TPLists.size());
-  for (unsigned I = 0, N = TPLists.size(); I != N; ++I) {
-    TemplateParameterList *InstParams = SubstTemplateParams(TPLists[I]);
-    if (!InstParams)
-      return nullptr;
-
-    TPL[I] = InstParams;
-  }
 
   Decl *FTD = nullptr;
   if (TypeSourceInfo *FT = D->getFriendType()) {
-    TypeSourceInfo *TSI = SemaRef.SubstType(FT, TemplateArgs, D->getLocation(),
-                                            DeclarationName());
-    if (TSI)
+    if (D->isPackExpansion() && InstantiateFriendPackExpansion(D, FT, TPLists))
+      return nullptr;
+
+    SmallVector<TemplateParameterList *, 1> TPL;
+    if (SubstTemplateParameterLists(TPLists, TPL))
+      return nullptr;
+
+    TypeSourceInfo *InstTy = SemaRef.SubstType(
+        FT, TemplateArgs, D->getLocation(), DeclarationName());
+    if (InstTy) {
       FTD = FriendTemplateDecl::Create(SemaRef.Context, Owner, D->getLocation(),
-                                       TSI, D->getFriendLoc(), TPL);
+                                       InstTy, D->getFriendLoc(), TPL);
+    }
   } else {
+    SmallVector<TemplateParameterList *, 1> TPL;
+    if (SubstTemplateParameterLists(TPLists, TPL))
+      return nullptr;
+
     if (cast_or_null<NamedDecl>(SemaRef.FindInstantiatedDecl(
             D->getLocation(), D->getFriendDecl(), TemplateArgs)))
       FTD = FriendTemplateDecl::Create(SemaRef.Context, Owner, D->getLocation(),
@@ -4892,6 +4896,19 @@ TemplateDeclInstantiator::SubstTemplateParams(TemplateParameterList *L) {
   return InstL;
 }
 
+bool TemplateDeclInstantiator::SubstTemplateParameterLists(
+    ArrayRef<TemplateParameterList *> TPL,
+    SmallVectorImpl<TemplateParameterList *> &InstTPL) {
+  for (TemplateParameterList *L : TPL) {
+    TemplateParameterList *InstParams = SubstTemplateParams(L);
+    if (!InstParams)
+      return true;
+
+    InstTPL.push_back(InstParams);
+  }
+  return false;
+}
+
 TemplateParameterList *
 Sema::SubstTemplateParams(TemplateParameterList *Params, DeclContext *Owner,
                           const MultiLevelTemplateArgumentList &TemplateArgs,
diff --git a/clang/lib/Serialization/ASTReaderDecl.cpp b/clang/lib/Serialization/ASTReaderDecl.cpp
index 0896bae6abc28..cbcd92c9cc810 100644
--- a/clang/lib/Serialization/ASTReaderDecl.cpp
+++ b/clang/lib/Serialization/ASTReaderDecl.cpp
@@ -2399,7 +2399,6 @@ void ASTDeclReader::VisitFriendDecl(FriendDecl *D) {
   else
     D->Friend = readTypeSourceInfo();
   D->NextFriend = readDeclID().getRawValue();
-  D->UnsupportedFriend = (Record.readInt() != 0);
   D->FriendLoc = readSourceLocation();
   D->EllipsisLoc = readSourceLocation();
 }
@@ -2413,7 +2412,6 @@ void ASTDeclReader::VisitFriendTemplateDecl(FriendTemplateDecl *D) {
   else
     D->Friend = readTypeSourceInfo();
   D->NextFriend = readDeclID().getRawValue();
-  D->UnsupportedFriend = (Record.readInt() != 0);
   D->FriendLoc = readSourceLocation();
   D->EllipsisLoc = readSourceLocation();
 }
diff --git a/clang/lib/Serialization/ASTWriterDecl.cpp b/clang/lib/Serialization/ASTWriterDecl.cpp
index c67467e3d6b01..4a6fdba869480 100644
--- a/clang/lib/Serialization/ASTWriterDecl.cpp
+++ b/clang/lib/Serialization/ASTWriterDecl.cpp
@@ -1832,7 +1832,6 @@ void ASTDeclWriter::VisitFriendDecl(FriendDecl *D) {
   else
     Record.AddTypeSourceInfo(D->getFriendType());
   Record.AddDeclRef(D->getNextFriend());
-  Record.push_back(D->UnsupportedFriend);
   Record.AddSourceLocation(D->FriendLoc);
   Record.AddSourceLocation(D->EllipsisLoc);
   Code = serialization::DECL_FRIEND;
@@ -1849,7 +1848,6 @@ void ASTDeclWriter::VisitFriendTemplateDecl(FriendTemplateDecl *D) {
   else
     Record.AddTypeSourceInfo(D->getFriendType());
   Record.AddDeclRef(D->getNextFriend());
-  Record.push_back(D->UnsupportedFriend);
   Record.AddSourceLocation(D->FriendLoc);
   Record.AddSourceLocation(D->EllipsisLoc);
   Code = serialization::DECL_FRIEND_TEMPLATE;
diff --git a/clang/test/CXX/drs/cwg28xx.cpp b/clang/test/CXX/drs/cwg28xx.cpp
index 6876cfa12ff51..e574a5f48c507 100644
--- a/clang/test/CXX/drs/cwg28xx.cpp
+++ b/clang/test/CXX/drs/cwg28xx.cpp
@@ -189,7 +189,7 @@ struct A {
   // FIXME: The index of the pack-index-specifier is printed as a memory address in the diagnostic.
   template<typename U>
   friend struct Ts...[0]::C;
-  // since-cxx26-warning at -1 {{dependent nested name specifier 'Ts...[0]' for friend template declaration is not supported; ignoring this friend declaration}}
+  // since-cxx26-error at -1 {{friend declaration does not name a member of a class template specialization}}
 };
 
 #endif
diff --git a/clang/test/Parser/cxx2c-variadic-friends.cpp b/clang/test/Parser/cxx2c-variadic-friends.cpp
index 621ae912c1ac9..23451e1a80a12 100644
--- a/clang/test/Parser/cxx2c-variadic-friends.cpp
+++ b/clang/test/Parser/cxx2c-variadic-friends.cpp
@@ -56,13 +56,11 @@ struct VS {
   template<bool... Bs>
   friend class E<Bs>::Nested...; // expected-error {{friend declaration expands pack 'Bs' that is declared it its own template parameter list}}
 
-  // FIXME: Both of these should be valid, but we can't handle these at
-  // the moment because the NNS is dependent.
   template<class ...T>
-  friend class TS<Ts>::Nested...; // expected-warning {{dependent nested name specifier 'TS<Ts>' for friend template declaration is not supported; ignoring this friend declaration}}
+  friend class TS<Ts>::Nested...;
 
   template<class T>
-  friend class D<T, Ts>::Nested...; // expected-warning {{dependent nested name specifier 'D<T, Ts>' for friend class declaration is not supported; turning off access control for 'VS'}}
+  friend class D<T, Ts>::Nested...;
 };
 
 namespace length_mismatch {
diff --git a/clang/test/SemaTemplate/friend-template.cpp b/clang/test/SemaTemplate/friend-template.cpp
index 2b5a226c3b33c..811a5c352b885 100644
--- a/clang/test/SemaTemplate/friend-template.cpp
+++ b/clang/test/SemaTemplate/friend-template.cpp
@@ -235,20 +235,19 @@ namespace rdar11147355 {
   template <class T>
   struct A {
     template <class U> class B;
-    template <class S> template <class U> friend class A<S>::B; // expected-warning {{dependent nested name specifier 'A<S>' for friend template declaration is not supported; ignoring this friend declaration}}
+    template <class S> template <class U> friend class A<S>::B;
   private:
-    int n; // expected-note {{here}}
+    int n;
   };
 
   template <class S> template <class U> class A<S>::B {
   public:
-    // FIXME: This should be permitted.
-    int f(A<S*> a) { return a.n; } // expected-error {{private}}
+    int f(A<S*> a) { return a.n; }
   };
 
   A<double>::B<double>  ab;
   A<double*> a;
-  int k = ab.f(a); // expected-note {{instantiation of}}
+  int k = ab.f(a);
 }
 
 namespace RedeclUnrelated {

>From 6a2425d3c72f080c62fde1e681234b1ca9beda78 Mon Sep 17 00:00:00 2001
From: Oleksandr Tarasiuk <oleksandr.tarasiuk at outlook.com>
Date: Thu, 30 Apr 2026 19:49:04 +0300
Subject: [PATCH 21/38] remove unsupported friend

---
 clang-tools-extra/clang-doc/Serialize.cpp | 3 ---
 1 file changed, 3 deletions(-)

diff --git a/clang-tools-extra/clang-doc/Serialize.cpp b/clang-tools-extra/clang-doc/Serialize.cpp
index ef7fed6cc8501..e8538aa84a7f2 100644
--- a/clang-tools-extra/clang-doc/Serialize.cpp
+++ b/clang-tools-extra/clang-doc/Serialize.cpp
@@ -996,9 +996,6 @@ void Serializer::parseFriends(RecordInfo &RI, const CXXRecordDecl *D) {
     return;
 
   for (const FriendDecl *FD : D->friends()) {
-    if (FD->isUnsupportedFriend())
-      continue;
-
     FriendInfo F(InfoType::IT_friend, getUSRForDecl(FD));
     const auto *ActualDecl = FD->getFriendDecl();
     if (!ActualDecl) {

>From 2e33fcd98bfc9a5d08463795b214bb46ef3f1772 Mon Sep 17 00:00:00 2001
From: Oleksandr Tarasiuk <oleksandr.tarasiuk at outlook.com>
Date: Fri, 1 May 2026 00:21:41 +0300
Subject: [PATCH 22/38] add support class template friends with dependent
 qualifiers

---
 clang/lib/Sema/SemaAccess.cpp                 | 77 +++++++++++++++----
 .../lib/Sema/SemaTemplateInstantiateDecl.cpp  | 28 ++++++-
 2 files changed, 85 insertions(+), 20 deletions(-)

diff --git a/clang/lib/Sema/SemaAccess.cpp b/clang/lib/Sema/SemaAccess.cpp
index 0a67b2b9d8f6b..1b53349a1b175 100644
--- a/clang/lib/Sema/SemaAccess.cpp
+++ b/clang/lib/Sema/SemaAccess.cpp
@@ -319,7 +319,8 @@ GetCanonicalFunctionProto(Sema &S, const FunctionDecl *FD) {
 }
 
 static const TemplateSpecializationType *
-TryGetTemplateSpecializationType(Sema &S, NestedNameSpecifier NNS) {
+GetCanonicalQualifierTemplateSpecializationType(Sema &S,
+                                                NestedNameSpecifier NNS) {
   if (!NNS)
     return nullptr;
 
@@ -352,12 +353,12 @@ static FunctionTemplateDecl *TryGetFunctionTemplateDecl(FunctionDecl *FD) {
   return nullptr;
 }
 
-static bool MatchesFriendContext(Sema &S, FunctionDecl *FD,
+static bool MatchesFriendContext(Sema &S, DeclContext *DC,
                                  ClassTemplateDecl *FriendCTD,
                                  ArrayRef<TemplateArgument> FriendArgs,
                                  TemplateParameterList *FriendTPL,
                                  SourceLocation Loc) {
-  const auto *RD = dyn_cast<CXXRecordDecl>(FD->getDeclContext());
+  const auto *RD = dyn_cast<CXXRecordDecl>(DC);
   if (!RD)
     return false;
 
@@ -690,12 +691,56 @@ static AccessResult MatchesFriend(Sema &S, const EffectiveContext &EC,
   return MatchesFriend(S, EC, cast<FunctionDecl>(ND));
 }
 
+static AccessResult MatchesFriend(Sema &S, const EffectiveContext &EC,
+                                  FriendTemplateDecl *FriendTD,
+                                  ClassTemplateDecl *FriendCTD) {
+  AccessResult OnFailure = AR_inaccessible;
+  const auto *FriendTST = GetCanonicalQualifierTemplateSpecializationType(
+      S, FriendCTD->getTemplatedDecl()->getQualifier());
+  if (!FriendTST)
+    return MatchesFriend(S, EC, FriendCTD);
+
+  auto *FriendContextCTD = dyn_cast<ClassTemplateDecl>(
+      FriendTST->getTemplateName().getAsTemplateDecl());
+  if (!FriendContextCTD)
+    return OnFailure;
+
+  ArrayRef<TemplateParameterList *> FriendTPLists =
+      FriendTD->getFriendTypeTemplateParameterLists();
+  if (FriendTPLists.empty())
+    return OnFailure;
+
+  TemplateParameterList *FriendTPL = FriendTPLists.front();
+  if (!FriendTPL)
+    return OnFailure;
+
+  ArrayRef<TemplateArgument> FriendArgs = FriendTST->template_arguments();
+  for (CXXRecordDecl *RD : EC.Records) {
+    ClassTemplateDecl *ContextCTD = nullptr;
+    if (auto *CTSD = dyn_cast<ClassTemplateSpecializationDecl>(RD))
+      ContextCTD = CTSD->getSpecializedTemplate();
+    else
+      ContextCTD = RD->getDescribedClassTemplate();
+
+    if (!ContextCTD)
+      continue;
+
+    if (MightInstantiateTo(ContextCTD->getTemplatedDecl(),
+                           FriendCTD->getTemplatedDecl()) &&
+        MatchesFriendContext(S, RD->getDeclContext(), FriendContextCTD,
+                             FriendArgs, FriendTPL, FriendTD->getLocation()))
+      return AR_accessible;
+  }
+
+  return OnFailure;
+}
+
 static AccessResult MatchesFriend(Sema &S, const EffectiveContext &EC,
                                   FriendTemplateDecl *FriendTD,
                                   FunctionTemplateDecl *FriendFTD) {
   AccessResult OnFailure = AR_inaccessible;
-  NestedNameSpecifier FriendNNS = FriendFTD->getTemplatedDecl()->getQualifier();
-  const auto *FriendTST = TryGetTemplateSpecializationType(S, FriendNNS);
+  const auto *FriendTST = GetCanonicalQualifierTemplateSpecializationType(
+      S, FriendFTD->getTemplatedDecl()->getQualifier());
   if (!FriendTST)
     return OnFailure;
 
@@ -717,8 +762,8 @@ static AccessResult MatchesFriend(Sema &S, const EffectiveContext &EC,
   SourceLocation FriendLoc = FriendTD->getLocation();
 
   for (FunctionDecl *FD : EC.Functions) {
-    if (!MatchesFriendContext(S, FD, FriendCTD, FriendArgs, FriendTPL,
-                              FriendLoc))
+    if (!MatchesFriendContext(S, FD->getDeclContext(), FriendCTD, FriendArgs,
+                              FriendTPL, FriendLoc))
       continue;
 
     FunctionTemplateDecl *ContextFTD = TryGetFunctionTemplateDecl(FD);
@@ -733,8 +778,8 @@ static AccessResult MatchesFriend(Sema &S, const EffectiveContext &EC,
                                   FriendTemplateDecl *FriendTD,
                                   FunctionDecl *FriendFD) {
   AccessResult OnFailure = AR_inaccessible;
-  NestedNameSpecifier FriendNNS = FriendFD->getQualifier();
-  const auto *FriendTST = TryGetTemplateSpecializationType(S, FriendNNS);
+  const auto *FriendTST = GetCanonicalQualifierTemplateSpecializationType(
+      S, FriendFD->getQualifier());
   if (!FriendTST)
     return OnFailure;
 
@@ -762,8 +807,8 @@ static AccessResult MatchesFriend(Sema &S, const EffectiveContext &EC,
     if (!MightInstantiateTo(S, FD->getDeclName(), FriendFD->getDeclName()))
       continue;
 
-    if (!MatchesFriendContext(S, FD, FriendCTD, FriendArgs, FriendTPL,
-                              FriendLoc))
+    if (!MatchesFriendContext(S, FD->getDeclContext(), FriendCTD, FriendArgs,
+                              FriendTPL, FriendLoc))
       continue;
 
     CanQual<FunctionProtoType> ContextProto = GetCanonicalFunctionProto(S, FD);
@@ -776,6 +821,9 @@ static AccessResult MatchesFriend(Sema &S, const EffectiveContext &EC,
 
 static AccessResult MatchesFriend(Sema &S, const EffectiveContext &EC,
                                   FriendTemplateDecl *FTD, NamedDecl *ND) {
+  if (auto *CTD = dyn_cast<ClassTemplateDecl>(ND))
+    return MatchesFriend(S, EC, FTD, CTD);
+
   if (auto *TD = dyn_cast<FunctionTemplateDecl>(ND))
     return MatchesFriend(S, EC, FTD, TD);
 
@@ -801,12 +849,7 @@ static AccessResult MatchesFriend(Sema &S, const EffectiveContext &EC,
   if (!NNS)
     return OnFailure;
 
-  const auto *T = NNS.getAsType();
-  if (!T)
-    return OnFailure;
-
-  const auto *TST =
-      S.Context.getCanonicalType(T)->getAsNonAliasTemplateSpecializationType();
+  const auto *TST = GetCanonicalQualifierTemplateSpecializationType(S, NNS);
   if (!TST)
     return OnFailure;
 
diff --git a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
index a4fde32e41552..32de665297205 100644
--- a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
+++ b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
@@ -4735,9 +4735,31 @@ Decl *TemplateDeclInstantiator::VisitFriendTemplateDecl(FriendTemplateDecl *D) {
     if (SubstTemplateParameterLists(TPLists, TPL))
       return nullptr;
 
-    TypeSourceInfo *InstTy = SemaRef.SubstType(
-        FT, TemplateArgs, D->getLocation(), DeclarationName());
-    if (InstTy) {
+    ClassTemplateDecl *CTD = nullptr;
+    if (auto DNT = FT->getTypeLoc().getAs<DependentNameTypeLoc>()) {
+      NestedNameSpecifierLoc QualifierLoc = SemaRef.SubstNestedNameSpecifierLoc(
+          DNT.getQualifierLoc(), TemplateArgs);
+      if (QualifierLoc) {
+        CXXScopeSpec SS;
+        SS.Adopt(QualifierLoc);
+
+        DeclContext *DC =
+            SemaRef.computeDeclContext(SS, /*EnteringContext=*/true);
+        if (DC) {
+          LookupResult Result(SemaRef, DNT.getTypePtr()->getIdentifier(),
+                              DNT.getNameLoc(), Sema::LookupOrdinaryName,
+                              SemaRef.forRedeclarationInCurContext());
+          SemaRef.LookupQualifiedName(Result, DC);
+          CTD = Result.getAsSingle<ClassTemplateDecl>();
+        }
+      }
+    }
+
+    if (CTD) {
+      FTD = FriendTemplateDecl::Create(SemaRef.Context, Owner, D->getLocation(),
+                                       CTD, D->getFriendLoc(), TPL);
+    } else if (TypeSourceInfo *InstTy = SemaRef.SubstType(
+                   FT, TemplateArgs, D->getLocation(), DeclarationName())) {
       FTD = FriendTemplateDecl::Create(SemaRef.Context, Owner, D->getLocation(),
                                        InstTy, D->getFriendLoc(), TPL);
     }

>From 4843b85d794c0f591949dfc1bf839cb66406dbb2 Mon Sep 17 00:00:00 2001
From: Oleksandr Tarasiuk <oleksandr.tarasiuk at outlook.com>
Date: Fri, 1 May 2026 08:31:53 +0300
Subject: [PATCH 23/38] fix friend template serialization

---
 clang/lib/Serialization/ASTWriterDecl.cpp | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/clang/lib/Serialization/ASTWriterDecl.cpp b/clang/lib/Serialization/ASTWriterDecl.cpp
index 4a6fdba869480..6e96fc6021696 100644
--- a/clang/lib/Serialization/ASTWriterDecl.cpp
+++ b/clang/lib/Serialization/ASTWriterDecl.cpp
@@ -1822,8 +1822,6 @@ void ASTDeclWriter::VisitAccessSpecDecl(AccessSpecDecl *D) {
 }
 
 void ASTDeclWriter::VisitFriendDecl(FriendDecl *D) {
-  // Record the number of friend type template parameter lists here
-  // so as to simplify memory allocation during deserialization.
   VisitDecl(D);
   bool hasFriendDecl = isa<NamedDecl *>(D->Friend);
   Record.push_back(hasFriendDecl);
@@ -1838,8 +1836,10 @@ void ASTDeclWriter::VisitFriendDecl(FriendDecl *D) {
 }
 
 void ASTDeclWriter::VisitFriendTemplateDecl(FriendTemplateDecl *D) {
-  VisitDecl(D);
+  // Record the number of friend type template parameter lists here
+  // so as to simplify memory allocation during deserialization.
   Record.push_back(D->NumTPLists);
+  VisitDecl(D);
   for (TemplateParameterList *TPL : D->getFriendTypeTemplateParameterLists())
     Record.AddTemplateParameterList(TPL);
   Record.push_back(D->getFriendDecl() != nullptr);

>From 4b98963f0bab293c1d25f50b8dff96877a60b7c0 Mon Sep 17 00:00:00 2001
From: Oleksandr Tarasiuk <oleksandr.tarasiuk at outlook.com>
Date: Sat, 2 May 2026 10:18:00 +0300
Subject: [PATCH 24/38] handle failed friends template deduction

---
 clang/lib/Sema/SemaAccess.cpp                 | 199 ++++++++++++------
 .../CXX/temp/temp.decls/temp.friend/p5.cpp    |   3 +-
 clang/test/SemaTemplate/friend-template.cpp   |  25 +++
 3 files changed, 163 insertions(+), 64 deletions(-)

diff --git a/clang/lib/Sema/SemaAccess.cpp b/clang/lib/Sema/SemaAccess.cpp
index 1b53349a1b175..2c11521ddf211 100644
--- a/clang/lib/Sema/SemaAccess.cpp
+++ b/clang/lib/Sema/SemaAccess.cpp
@@ -23,6 +23,7 @@
 #include "clang/Sema/Initialization.h"
 #include "clang/Sema/Lookup.h"
 #include "clang/Sema/Template.h"
+#include "clang/Sema/TemplateDeduction.h"
 
 using namespace clang;
 using namespace sema;
@@ -276,11 +277,32 @@ struct AccessTarget : public AccessedEntity {
 
 }
 
+static void AddFriendTemplateDeductionCandidate(
+    Sema &S, TemplateDecl *TD, Decl *Declaration, TemplateDeductionInfo &Info,
+    TemplateDeductionResult Result, TemplateSpecCandidateSet *FailedTSC) {
+  if (!FailedTSC)
+    return;
+
+  const Decl *CanonicalDeclaration = Declaration->getCanonicalDecl();
+  for (TemplateSpecCandidate &Candidate : *FailedTSC) {
+    if (!Candidate.Specialization)
+      continue;
+
+    if (Candidate.Specialization->getCanonicalDecl() == CanonicalDeclaration)
+      return;
+  }
+
+  FailedTSC->addCandidate().set(
+      DeclAccessPair::make(TD, AS_public), Declaration,
+      MakeDeductionFailureInfo(S.Context, Result, Info));
+}
+
 static bool CanDeduceTemplateArguments(Sema &S, TemplateParameterList *TPL,
                                        TemplateDecl *TD,
                                        ArrayRef<TemplateArgument> PatternArgs,
                                        ArrayRef<TemplateArgument> Args,
-                                       SourceLocation Loc) {
+                                       SourceLocation Loc,
+                                       TemplateSpecCandidateSet *FailedTSC) {
   auto Equal =
       llvm::equal(PatternArgs, Args,
                   [](const TemplateArgument &LHS, const TemplateArgument &RHS) {
@@ -295,22 +317,41 @@ static bool CanDeduceTemplateArguments(Sema &S, TemplateParameterList *TPL,
   Sema::SFINAETrap Trap(S, Info);
   LocalInstantiationScope InstantiationScope(S);
   SmallVector<DeducedTemplateArgument, 4> Deduced(TPL->size());
-  if (S.DeduceTemplateArguments(TPL, PatternArgs, Args, Info, Deduced,
-                                /*NumberOfArgumentsMustMatch=*/false) !=
-      TemplateDeductionResult::Success)
+  TemplateDeductionResult DeductionResult =
+      S.DeduceTemplateArguments(TPL, PatternArgs, Args, Info, Deduced,
+                                /*NumberOfArgumentsMustMatch=*/false);
+  if (DeductionResult != TemplateDeductionResult::Success) {
+    AddFriendTemplateDeductionCandidate(S, TD, TD->getTemplatedDecl(), Info,
+                                        DeductionResult, FailedTSC);
     return false;
+  }
 
   SmallVector<TemplateArgument, 4> DeducedArgs(Deduced.begin(), Deduced.end());
   Sema::InstantiatingTemplate Inst(S, Info.getLocation(), TD, DeducedArgs);
-  if (Inst.isInvalid())
+  if (Inst.isInvalid()) {
+    AddFriendTemplateDeductionCandidate(
+        S, TD, TD->getTemplatedDecl(), Info,
+        TemplateDeductionResult::InstantiationDepth, FailedTSC);
     return false;
+  }
 
   TemplateDeductionResult Result;
   S.runWithSufficientStackSpace(Info.getLocation(), [&] {
     Result = S.FinishTemplateArgumentDeduction(
         TD, TPL, PatternArgs, Args, Deduced, Info, /*CopyDeducedArgs=*/false);
   });
-  return Result == TemplateDeductionResult::Success && !Trap.hasErrorOccurred();
+
+  if (Result != TemplateDeductionResult::Success || Trap.hasErrorOccurred()) {
+    TemplateDeductionResult Failure =
+        Result != TemplateDeductionResult::Success
+            ? Result
+            : TemplateDeductionResult::SubstitutionFailure;
+    AddFriendTemplateDeductionCandidate(S, TD, TD->getTemplatedDecl(), Info,
+                                        Failure, FailedTSC);
+    return false;
+  }
+
+  return true;
 }
 
 static CanQual<FunctionProtoType>
@@ -328,7 +369,6 @@ GetCanonicalQualifierTemplateSpecializationType(Sema &S,
   if (Ty.isNull())
     return nullptr;
 
-  Ty = S.Context.getCanonicalType(Ty);
   if (const auto *ICNT = Ty->getAs<InjectedClassNameType>())
     Ty = ICNT->getDecl()->getCanonicalTemplateSpecializationType(S.Context);
 
@@ -357,7 +397,8 @@ static bool MatchesFriendContext(Sema &S, DeclContext *DC,
                                  ClassTemplateDecl *FriendCTD,
                                  ArrayRef<TemplateArgument> FriendArgs,
                                  TemplateParameterList *FriendTPL,
-                                 SourceLocation Loc) {
+                                 SourceLocation Loc,
+                                 TemplateSpecCandidateSet *FailedTSC) {
   const auto *RD = dyn_cast<CXXRecordDecl>(DC);
   if (!RD)
     return false;
@@ -378,7 +419,7 @@ static bool MatchesFriendContext(Sema &S, DeclContext *DC,
     return false;
 
   return CanDeduceTemplateArguments(S, FriendTPL, FriendCTD, FriendArgs,
-                                    ContextArgs, Loc);
+                                    ContextArgs, Loc, FailedTSC);
 }
 
 /// Checks whether one class might instantiate to the other.
@@ -693,7 +734,8 @@ static AccessResult MatchesFriend(Sema &S, const EffectiveContext &EC,
 
 static AccessResult MatchesFriend(Sema &S, const EffectiveContext &EC,
                                   FriendTemplateDecl *FriendTD,
-                                  ClassTemplateDecl *FriendCTD) {
+                                  ClassTemplateDecl *FriendCTD,
+                                  TemplateSpecCandidateSet *FailedTSC) {
   AccessResult OnFailure = AR_inaccessible;
   const auto *FriendTST = GetCanonicalQualifierTemplateSpecializationType(
       S, FriendCTD->getTemplatedDecl()->getQualifier());
@@ -728,7 +770,8 @@ static AccessResult MatchesFriend(Sema &S, const EffectiveContext &EC,
     if (MightInstantiateTo(ContextCTD->getTemplatedDecl(),
                            FriendCTD->getTemplatedDecl()) &&
         MatchesFriendContext(S, RD->getDeclContext(), FriendContextCTD,
-                             FriendArgs, FriendTPL, FriendTD->getLocation()))
+                             FriendArgs, FriendTPL, FriendTD->getLocation(),
+                             FailedTSC))
       return AR_accessible;
   }
 
@@ -737,7 +780,8 @@ static AccessResult MatchesFriend(Sema &S, const EffectiveContext &EC,
 
 static AccessResult MatchesFriend(Sema &S, const EffectiveContext &EC,
                                   FriendTemplateDecl *FriendTD,
-                                  FunctionTemplateDecl *FriendFTD) {
+                                  FunctionTemplateDecl *FriendFTD,
+                                  TemplateSpecCandidateSet *FailedTSC) {
   AccessResult OnFailure = AR_inaccessible;
   const auto *FriendTST = GetCanonicalQualifierTemplateSpecializationType(
       S, FriendFTD->getTemplatedDecl()->getQualifier());
@@ -763,7 +807,7 @@ static AccessResult MatchesFriend(Sema &S, const EffectiveContext &EC,
 
   for (FunctionDecl *FD : EC.Functions) {
     if (!MatchesFriendContext(S, FD->getDeclContext(), FriendCTD, FriendArgs,
-                              FriendTPL, FriendLoc))
+                              FriendTPL, FriendLoc, FailedTSC))
       continue;
 
     FunctionTemplateDecl *ContextFTD = TryGetFunctionTemplateDecl(FD);
@@ -776,7 +820,8 @@ static AccessResult MatchesFriend(Sema &S, const EffectiveContext &EC,
 
 static AccessResult MatchesFriend(Sema &S, const EffectiveContext &EC,
                                   FriendTemplateDecl *FriendTD,
-                                  FunctionDecl *FriendFD) {
+                                  FunctionDecl *FriendFD,
+                                  TemplateSpecCandidateSet *FailedTSC) {
   AccessResult OnFailure = AR_inaccessible;
   const auto *FriendTST = GetCanonicalQualifierTemplateSpecializationType(
       S, FriendFD->getQualifier());
@@ -808,7 +853,7 @@ static AccessResult MatchesFriend(Sema &S, const EffectiveContext &EC,
       continue;
 
     if (!MatchesFriendContext(S, FD->getDeclContext(), FriendCTD, FriendArgs,
-                              FriendTPL, FriendLoc))
+                              FriendTPL, FriendLoc, FailedTSC))
       continue;
 
     CanQual<FunctionProtoType> ContextProto = GetCanonicalFunctionProto(S, FD);
@@ -820,22 +865,23 @@ static AccessResult MatchesFriend(Sema &S, const EffectiveContext &EC,
 }
 
 static AccessResult MatchesFriend(Sema &S, const EffectiveContext &EC,
-                                  FriendTemplateDecl *FTD, NamedDecl *ND) {
+                                  FriendTemplateDecl *FTD, NamedDecl *ND,
+                                  TemplateSpecCandidateSet *FailedTSC) {
   if (auto *CTD = dyn_cast<ClassTemplateDecl>(ND))
-    return MatchesFriend(S, EC, FTD, CTD);
+    return MatchesFriend(S, EC, FTD, CTD, FailedTSC);
 
   if (auto *TD = dyn_cast<FunctionTemplateDecl>(ND))
-    return MatchesFriend(S, EC, FTD, TD);
+    return MatchesFriend(S, EC, FTD, TD, FailedTSC);
 
   if (auto *FD = dyn_cast<FunctionDecl>(ND))
-    return MatchesFriend(S, EC, FTD, FD);
+    return MatchesFriend(S, EC, FTD, FD, FailedTSC);
 
   return MatchesFriend(S, EC, ND);
 }
 
 static AccessResult MatchesFriend(Sema &S, const EffectiveContext &EC,
-                                  FriendTemplateDecl *FTD,
-                                  TypeSourceInfo *TSI) {
+                                  FriendTemplateDecl *FTD, TypeSourceInfo *TSI,
+                                  TemplateSpecCandidateSet *FailedTSC) {
   QualType TypeAsWritten = TSI->getType();
   if (!TypeAsWritten->isDependentType())
     return MatchesFriend(S, EC, S.Context.getCanonicalType(TypeAsWritten));
@@ -882,7 +928,7 @@ static AccessResult MatchesFriend(Sema &S, const EffectiveContext &EC,
 
     if (CanDeduceTemplateArguments(S, TPL, CTD, TST->template_arguments(),
                                    CTSD->getTemplateArgs().asArray(),
-                                   FTD->getLocation()))
+                                   FTD->getLocation(), FailedTSC))
       return AR_accessible;
   }
 
@@ -908,29 +954,30 @@ static AccessResult MatchesFriend(Sema &S,
 }
 
 static AccessResult MatchesFriend(Sema &S, const EffectiveContext &EC,
-                                  FriendTemplateDecl *FTD) {
+                                  FriendTemplateDecl *FTD,
+                                  TemplateSpecCandidateSet *FailedTSC) {
   if (FTD->isInvalidDecl())
     return AR_accessible;
 
   if (NamedDecl *ND = FTD->getFriendDecl())
-    return MatchesFriend(S, EC, FTD, ND);
+    return MatchesFriend(S, EC, FTD, ND, FailedTSC);
 
   if (TypeSourceInfo *TSI = FTD->getFriendType())
-    return MatchesFriend(S, EC, FTD, TSI);
+    return MatchesFriend(S, EC, FTD, TSI, FailedTSC);
 
   return AR_inaccessible;
 }
 
-static AccessResult GetFriendKind(Sema &S,
-                                  const EffectiveContext &EC,
-                                  const CXXRecordDecl *Class) {
+static AccessResult GetFriendKind(Sema &S, const EffectiveContext &EC,
+                                  const CXXRecordDecl *Class,
+                                  TemplateSpecCandidateSet *FailedTSC) {
   AccessResult OnFailure = AR_inaccessible;
 
   // Okay, check friends.
   for (FriendDecl *Friend : Class->friends()) {
     AccessResult AR;
     if (auto *FTD = dyn_cast<FriendTemplateDecl>(Friend))
-      AR = MatchesFriend(S, EC, FTD);
+      AR = MatchesFriend(S, EC, FTD, FailedTSC);
     else
       AR = MatchesFriend(S, EC, Friend);
 
@@ -958,6 +1005,7 @@ namespace {
 struct ProtectedFriendContext {
   Sema &S;
   const EffectiveContext &EC;
+  TemplateSpecCandidateSet *FailedTSC;
   const CXXRecordDecl *NamingClass;
   bool CheckDependent;
   bool EverDependent;
@@ -967,18 +1015,19 @@ struct ProtectedFriendContext {
 
   ProtectedFriendContext(Sema &S, const EffectiveContext &EC,
                          const CXXRecordDecl *InstanceContext,
-                         const CXXRecordDecl *NamingClass)
-    : S(S), EC(EC), NamingClass(NamingClass),
-      CheckDependent(InstanceContext->isDependentContext() ||
-                     NamingClass->isDependentContext()),
-      EverDependent(false) {}
+                         const CXXRecordDecl *NamingClass,
+                         TemplateSpecCandidateSet *FailedTSC)
+      : S(S), EC(EC), FailedTSC(FailedTSC), NamingClass(NamingClass),
+        CheckDependent(InstanceContext->isDependentContext() ||
+                       NamingClass->isDependentContext()),
+        EverDependent(false) {}
 
   /// Check classes in the current path for friendship, starting at
   /// the given index.
   bool checkFriendshipAlongPath(unsigned I) {
     assert(I < CurPath.size());
     for (unsigned E = CurPath.size(); I != E; ++I) {
-      switch (GetFriendKind(S, EC, CurPath[I])) {
+      switch (GetFriendKind(S, EC, CurPath[I], FailedTSC)) {
       case AR_accessible:   return true;
       case AR_inaccessible: continue;
       case AR_dependent:    EverDependent = true; continue;
@@ -1065,9 +1114,9 @@ struct ProtectedFriendContext {
 ///     because the original target might have been more accessible
 ///     because of crazy subclassing.
 /// So we don't implement that.
-static AccessResult GetProtectedFriendKind(Sema &S, const EffectiveContext &EC,
-                                           const CXXRecordDecl *InstanceContext,
-                                           const CXXRecordDecl *NamingClass) {
+static AccessResult GetProtectedFriendKind(
+    Sema &S, const EffectiveContext &EC, const CXXRecordDecl *InstanceContext,
+    const CXXRecordDecl *NamingClass, TemplateSpecCandidateSet *FailedTSC) {
   assert(InstanceContext == nullptr ||
          InstanceContext->getCanonicalDecl() == InstanceContext);
   assert(NamingClass->getCanonicalDecl() == NamingClass);
@@ -1075,19 +1124,20 @@ static AccessResult GetProtectedFriendKind(Sema &S, const EffectiveContext &EC,
   // If we don't have an instance context, our constraints give us
   // that NamingClass <= P <= NamingClass, i.e. P == NamingClass.
   // This is just the usual friendship check.
-  if (!InstanceContext) return GetFriendKind(S, EC, NamingClass);
+  if (!InstanceContext)
+    return GetFriendKind(S, EC, NamingClass, FailedTSC);
 
-  ProtectedFriendContext PRC(S, EC, InstanceContext, NamingClass);
+  ProtectedFriendContext PRC(S, EC, InstanceContext, NamingClass, FailedTSC);
   if (PRC.findFriendship(InstanceContext)) return AR_accessible;
   if (PRC.EverDependent) return AR_dependent;
   return AR_inaccessible;
 }
 
-static AccessResult HasAccess(Sema &S,
-                              const EffectiveContext &EC,
+static AccessResult HasAccess(Sema &S, const EffectiveContext &EC,
                               const CXXRecordDecl *NamingClass,
                               AccessSpecifier Access,
-                              const AccessTarget &Target) {
+                              const AccessTarget &Target,
+                              TemplateSpecCandidateSet *FailedTSC) {
   assert(NamingClass->getCanonicalDecl() == NamingClass &&
          "declaration should be canonicalized before being passed here");
 
@@ -1207,7 +1257,8 @@ static AccessResult HasAccess(Sema &S,
       if (!InstanceContext) return AR_dependent;
     }
 
-    switch (GetProtectedFriendKind(S, EC, InstanceContext, NamingClass)) {
+    switch (GetProtectedFriendKind(S, EC, InstanceContext, NamingClass,
+                                   FailedTSC)) {
     case AR_accessible: return AR_accessible;
     case AR_inaccessible: return OnFailure;
     case AR_dependent: return AR_dependent;
@@ -1215,7 +1266,7 @@ static AccessResult HasAccess(Sema &S,
     llvm_unreachable("impossible friendship kind");
   }
 
-  switch (GetFriendKind(S, EC, NamingClass)) {
+  switch (GetFriendKind(S, EC, NamingClass, FailedTSC)) {
   case AR_accessible: return AR_accessible;
   case AR_inaccessible: return OnFailure;
   case AR_dependent: return AR_dependent;
@@ -1328,7 +1379,8 @@ static CXXBasePath *FindBestPath(Sema &S,
       AccessSpecifier BaseAccess = I->Base->getAccessSpecifier();
       PathAccess = std::max(PathAccess, BaseAccess);
 
-      switch (HasAccess(S, EC, NC, PathAccess, Target)) {
+      switch (HasAccess(S, EC, NC, PathAccess, Target,
+                        /*FailedTSC=*/nullptr)) {
       case AR_inaccessible: break;
       case AR_accessible:
         PathAccess = AS_public;
@@ -1524,7 +1576,8 @@ static void DiagnoseAccessPath(Sema &S,
     accessSoFar = D->getAccess();
     const CXXRecordDecl *declaringClass = entity.getDeclaringClass();
 
-    switch (HasAccess(S, EC, declaringClass, accessSoFar, entity)) {
+    switch (HasAccess(S, EC, declaringClass, accessSoFar, entity,
+                      /*FailedTSC=*/nullptr)) {
     // If the declaration is accessible when named in its declaring
     // class, then we must be constrained by the path.
     case AR_accessible:
@@ -1567,7 +1620,8 @@ static void DiagnoseAccessPath(Sema &S,
       accessSoFar = baseAccess;
     }
 
-    switch (HasAccess(S, EC, derivingClass, accessSoFar, entity)) {
+    switch (HasAccess(S, EC, derivingClass, accessSoFar, entity,
+                      /*FailedTSC=*/nullptr)) {
     case AR_inaccessible: break;
     case AR_accessible:
       accessSoFar = AS_public;
@@ -1671,9 +1725,9 @@ static bool IsMicrosoftUsingDeclarationAccessBug(Sema& S,
 
 /// Determines whether the accessed entity is accessible.  Public members
 /// have been weeded out by this point.
-static AccessResult IsAccessible(Sema &S,
-                                 const EffectiveContext &EC,
-                                 AccessTarget &Entity) {
+static AccessResult IsAccessible(Sema &S, const EffectiveContext &EC,
+                                 AccessTarget &Entity,
+                                 TemplateSpecCandidateSet *FailedTSC) {
   // Determine the actual naming class.
   const CXXRecordDecl *NamingClass = Entity.getEffectiveNamingClass();
 
@@ -1685,7 +1739,8 @@ static AccessResult IsAccessible(Sema &S,
   // which don't require [M4] or [B4]. These are by far the most
   // common forms of privileged access.
   if (UnprivilegedAccess != AS_none) {
-    switch (HasAccess(S, EC, NamingClass, UnprivilegedAccess, Entity)) {
+    switch (
+        HasAccess(S, EC, NamingClass, UnprivilegedAccess, Entity, FailedTSC)) {
     case AR_dependent:
       // This is actually an interesting policy decision.  We don't
       // *have* to delay immediately here: we can do the full access
@@ -1714,7 +1769,7 @@ static AccessResult IsAccessible(Sema &S,
     const CXXRecordDecl *DeclaringClass = Entity.getDeclaringClass();
 
     FinalAccess = Target->getAccess();
-    switch (HasAccess(S, EC, DeclaringClass, FinalAccess, Entity)) {
+    switch (HasAccess(S, EC, DeclaringClass, FinalAccess, Entity, FailedTSC)) {
     case AR_accessible:
       // Target is accessible at EC when named in its declaring class.
       // We can now hill-climb and simply check whether the declaring
@@ -1766,25 +1821,30 @@ static void DelayDependentAccess(Sema &S,
                               Entity.getDiag());
 }
 
-/// Checks access to an entity from the given effective context.
-static AccessResult CheckEffectiveAccess(Sema &S,
-                                         const EffectiveContext &EC,
+static AccessResult CheckEffectiveAccess(Sema &S, const EffectiveContext &EC,
                                          SourceLocation Loc,
-                                         AccessTarget &Entity) {
-  assert(Entity.getAccess() != AS_public && "called for public access!");
+                                         AccessTarget &Entity,
+                                         TemplateSpecCandidateSet *FailedTSC) {
+  assert((Entity.isQuiet() || FailedTSC) &&
+         "non-quiet access check requires a candidate set");
 
-  switch (IsAccessible(S, EC, Entity)) {
+  switch (IsAccessible(S, EC, Entity, FailedTSC)) {
   case AR_dependent:
     DelayDependentAccess(S, EC, Loc, Entity);
     return AR_dependent;
 
-  case AR_inaccessible:
+  case AR_inaccessible: {
     if (S.getLangOpts().MSVCCompat &&
         IsMicrosoftUsingDeclarationAccessBug(S, Loc, Entity))
       return AR_accessible;
-    if (!Entity.isQuiet())
-      DiagnoseBadAccess(S, Loc, EC, Entity);
+
+    if (Entity.isQuiet())
+      return AR_inaccessible;
+
+    DiagnoseBadAccess(S, Loc, EC, Entity);
+    FailedTSC->NoteCandidates(S, Loc);
     return AR_inaccessible;
+  }
 
   case AR_accessible:
     return AR_accessible;
@@ -1794,6 +1854,18 @@ static AccessResult CheckEffectiveAccess(Sema &S,
   llvm_unreachable("invalid access result");
 }
 
+static AccessResult CheckEffectiveAccess(Sema &S, const EffectiveContext &EC,
+                                         SourceLocation Loc,
+                                         AccessTarget &Entity) {
+  assert(Entity.getAccess() != AS_public && "called for public access!");
+
+  if (Entity.isQuiet())
+    return CheckEffectiveAccess(S, EC, Loc, Entity, /*FailedTSC=*/nullptr);
+
+  TemplateSpecCandidateSet FailedTSC(Loc);
+  return CheckEffectiveAccess(S, EC, Loc, Entity, &FailedTSC);
+}
+
 static Sema::AccessResult CheckAccess(Sema &S, SourceLocation Loc,
                                       AccessTarget &Entity) {
   // If the access path is public, it's accessible everywhere.
@@ -2278,7 +2350,8 @@ bool Sema::IsSimplyAccessible(NamedDecl *Target, CXXRecordDecl *NamingClass,
     AccessTarget Entity(Context, AccessedEntity::Member, NamingClass,
                         DeclAccessPair::make(Target, AS_none), BaseType);
     EffectiveContext EC(CurContext);
-    return ::IsAccessible(*this, EC, Entity) != ::AR_inaccessible;
+    return ::IsAccessible(*this, EC, Entity, /*FailedTSC=*/nullptr) !=
+           ::AR_inaccessible;
   }
 
   if (ObjCIvarDecl *Ivar = dyn_cast<ObjCIvarDecl>(Target)) {
diff --git a/clang/test/CXX/temp/temp.decls/temp.friend/p5.cpp b/clang/test/CXX/temp/temp.decls/temp.friend/p5.cpp
index c3c8c3668d91e..46f8139d4451f 100644
--- a/clang/test/CXX/temp/temp.decls/temp.friend/p5.cpp
+++ b/clang/test/CXX/temp/temp.decls/temp.friend/p5.cpp
@@ -141,7 +141,7 @@ namespace test7 {
 }
 
 namespace test8 {
-  template <class T> struct A {
+  template <class T> struct A { // #test8-A
     T h();
   };
 
@@ -167,6 +167,7 @@ namespace test8 {
     c.n = 0;
     // expected-error at -1 {{'n' is a private member of 'test8::C'}}
     //   expected-note@#test8-C-n {{implicitly declared private here}}
+    //   expected-note@#test8-A {{candidate template ignored: could not match 'T *' against 'int'}}
     return 0;
   }
 
diff --git a/clang/test/SemaTemplate/friend-template.cpp b/clang/test/SemaTemplate/friend-template.cpp
index 811a5c352b885..3555bb2eb8cb3 100644
--- a/clang/test/SemaTemplate/friend-template.cpp
+++ b/clang/test/SemaTemplate/friend-template.cpp
@@ -337,3 +337,28 @@ class Foo {
   bool aux;
 };
 }
+
+namespace GH104057 {
+template <class T>
+struct A { // #GH104057-A
+  template <class> struct B;
+
+private:
+  static void f(); // #GH104057-A-f
+  template <class U> friend struct A<U *>::B;
+};
+
+template <class T>
+template <class U> struct A<T>::B {
+  static void g() {
+    A<int>::f();
+    // expected-error at -1 {{'f' is a private member of 'GH104057::A<int>'}}
+    //   expected-note@#GH104057-A-f {{declared private here}}
+    //   expected-note@#GH104057-A {{candidate template ignored: could not match 'U *' against 'double'}}
+  }
+};
+
+void test() {
+  A<double>::B<int>::g(); // expected-note {{in instantiation of member function 'GH104057::A<double>::B<int>::g' requested here}}
+}
+}

>From d4918a8d7d1b0d9c94962523203be22e07f1eedd Mon Sep 17 00:00:00 2001
From: Oleksandr Tarasiuk <oleksandr.tarasiuk at outlook.com>
Date: Sat, 2 May 2026 12:52:41 +0300
Subject: [PATCH 25/38] cleanup

---
 clang/lib/Sema/SemaAccess.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/clang/lib/Sema/SemaAccess.cpp b/clang/lib/Sema/SemaAccess.cpp
index 2c11521ddf211..78ab1cdb6cc60 100644
--- a/clang/lib/Sema/SemaAccess.cpp
+++ b/clang/lib/Sema/SemaAccess.cpp
@@ -313,7 +313,7 @@ static bool CanDeduceTemplateArguments(Sema &S, TemplateParameterList *TPL,
 
   EnterExpressionEvaluationContext Unevaluated(
       S, Sema::ExpressionEvaluationContext::Unevaluated);
-  TemplateDeductionInfo Info(Loc);
+  TemplateDeductionInfo Info(FailedTSC ? FailedTSC->getLocation() : Loc);
   Sema::SFINAETrap Trap(S, Info);
   LocalInstantiationScope InstantiationScope(S);
   SmallVector<DeducedTemplateArgument, 4> Deduced(TPL->size());

>From 08209a79689e176346dfee9def64c8b601500d0d Mon Sep 17 00:00:00 2001
From: Oleksandr Tarasiuk <oleksandr.tarasiuk at outlook.com>
Date: Thu, 14 May 2026 23:35:35 +0300
Subject: [PATCH 26/38] preserve TemplateName in FriendTemplateDecl

---
 clang/include/clang/AST/DeclTemplate.h        | 18 +++++++--
 clang/include/clang/AST/RecursiveASTVisitor.h | 13 ++++--
 clang/lib/AST/ASTImporter.cpp                 | 40 +++++++++++++------
 clang/lib/AST/ASTStructuralEquivalence.cpp    | 27 ++++++++-----
 clang/lib/AST/DeclTemplate.cpp                | 16 ++++++++
 clang/lib/AST/ODRHash.cpp                     | 12 ++++++
 clang/lib/Sema/SemaAccess.cpp                 | 30 +++++++++++++-
 .../lib/Sema/SemaTemplateInstantiateDecl.cpp  | 35 ++++++++++++----
 clang/lib/Serialization/ASTReaderDecl.cpp     | 13 ++++--
 clang/lib/Serialization/ASTWriterDecl.cpp     | 17 +++++---
 10 files changed, 175 insertions(+), 46 deletions(-)

diff --git a/clang/include/clang/AST/DeclTemplate.h b/clang/include/clang/AST/DeclTemplate.h
index ff962f73a8148..999f5ba2f4c1c 100644
--- a/clang/include/clang/AST/DeclTemplate.h
+++ b/clang/include/clang/AST/DeclTemplate.h
@@ -2479,13 +2479,15 @@ class FriendTemplateDecl final
 
 private:
   unsigned NumTPLists : 31;
+  TemplateName Template;
 
   FriendTemplateDecl(DeclContext *DC, SourceLocation Loc, FriendUnion Friend,
                      SourceLocation FriendLoc, SourceLocation EllipsisLoc,
-                     ArrayRef<TemplateParameterList *> FriendTypeTPLists)
+                     ArrayRef<TemplateParameterList *> FriendTypeTPLists,
+                     TemplateName Template = {})
       : FriendDecl(Decl::FriendTemplate, DC, Loc, Friend, FriendLoc,
                    EllipsisLoc),
-        NumTPLists(FriendTypeTPLists.size()) {
+        NumTPLists(FriendTypeTPLists.size()), Template(Template) {
     llvm::copy(FriendTypeTPLists, getTrailingObjects());
   }
 
@@ -2504,6 +2506,12 @@ class FriendTemplateDecl final
          ArrayRef<TemplateParameterList *> FriendTypeTPLists = {},
          SourceLocation EllipsisLoc = {});
 
+  static FriendTemplateDecl *
+  Create(ASTContext &Context, DeclContext *DC, SourceLocation Loc,
+         TemplateName Template, SourceLocation FriendLoc,
+         ArrayRef<TemplateParameterList *> FriendTypeTPLists = {},
+         SourceLocation EllipsisLoc = {});
+
   static FriendTemplateDecl *CreateDeserialized(ASTContext &C, GlobalDeclID ID,
                                                 unsigned FriendTypeNumTPLists);
 
@@ -2520,9 +2528,13 @@ class FriendTemplateDecl final
   /// a member function of a templated type), return that type;
   /// otherwise return null.
   NamedDecl *getFriendDecl() const {
-    return Friend.dyn_cast<NamedDecl*>();
+    if (TemplateDecl *TD = Template.getAsTemplateDecl())
+      return TD;
+    return Friend.dyn_cast<NamedDecl *>();
   }
 
+  TemplateName getFriendTemplateName() const { return Template; }
+
   ArrayRef<TemplateParameterList *>
   getFriendTypeTemplateParameterLists() const {
     return ArrayRef(getTrailingObjects(), NumTPLists);
diff --git a/clang/include/clang/AST/RecursiveASTVisitor.h b/clang/include/clang/AST/RecursiveASTVisitor.h
index 75c985f9d173b..dd3d0ec5841c6 100644
--- a/clang/include/clang/AST/RecursiveASTVisitor.h
+++ b/clang/include/clang/AST/RecursiveASTVisitor.h
@@ -1733,10 +1733,15 @@ DEF_TRAVERSE_DECL(FriendDecl, {
 })
 
 DEF_TRAVERSE_DECL(FriendTemplateDecl, {
-  if (D->getFriendType())
-    TRY_TO(TraverseTypeLoc(D->getFriendType()->getTypeLoc()));
-  else
-    TRY_TO(TraverseDecl(D->getFriendDecl()));
+  TemplateName Template = D->getFriendTemplateName();
+  if (Template.isNull()) {
+    if (D->getFriendType())
+      TRY_TO(TraverseTypeLoc(D->getFriendType()->getTypeLoc()));
+    else
+      TRY_TO(TraverseDecl(D->getFriendDecl()));
+  } else {
+    TRY_TO(TraverseTemplateName(Template));
+  }
   for (TemplateParameterList *TPL : D->getFriendTypeTemplateParameterLists()) {
     for (TemplateParameterList::iterator ITPL = TPL->begin(), ETPL = TPL->end();
          ITPL != ETPL; ++ITPL) {
diff --git a/clang/lib/AST/ASTImporter.cpp b/clang/lib/AST/ASTImporter.cpp
index 5533cacac129c..045e4b088c4b6 100644
--- a/clang/lib/AST/ASTImporter.cpp
+++ b/clang/lib/AST/ASTImporter.cpp
@@ -4687,16 +4687,25 @@ ExpectedDecl ASTNodeImporter::VisitFriendTemplateDecl(FriendTemplateDecl *D) {
         D, ImportedEquivalentFriends[CountAndPosition.IndexOfDecl]);
 
   FriendTemplateDecl::FriendUnion ToFU;
-  if (NamedDecl *FriendD = D->getFriendDecl()) {
-    NamedDecl *ToFriendD;
-    if (Error Err = importInto(ToFriendD, FriendD))
-      return std::move(Err);
-    ToFU = ToFriendD;
+  TemplateName ToTemplate;
+  TemplateName FromTemplate = D->getFriendTemplateName();
+  if (FromTemplate.isNull()) {
+    if (NamedDecl *FriendD = D->getFriendDecl()) {
+      NamedDecl *ToFriendD;
+      if (Error Err = importInto(ToFriendD, FriendD))
+        return std::move(Err);
+      ToFU = ToFriendD;
+    } else {
+      if (auto TSIOrErr = import(D->getFriendType()))
+        ToFU = *TSIOrErr;
+      else
+        return TSIOrErr.takeError();
+    }
   } else {
-    if (auto TSIOrErr = import(D->getFriendType()))
-      ToFU = *TSIOrErr;
+    if (auto TemplateOrErr = import(FromTemplate))
+      ToTemplate = *TemplateOrErr;
     else
-      return TSIOrErr.takeError();
+      return TemplateOrErr.takeError();
   }
 
   ArrayRef<TemplateParameterList *> TPLs =
@@ -4722,10 +4731,17 @@ ExpectedDecl ASTNodeImporter::VisitFriendTemplateDecl(FriendTemplateDecl *D) {
     return EllipsisLocOrErr.takeError();
 
   FriendTemplateDecl *FTD;
-  if (GetImportedOrCreateDecl(FTD, D, Importer.getToContext(), DC,
-                              *LocationOrErr, ToFU, *FriendLocOrErr, ToParams,
-                              *EllipsisLocOrErr))
-    return FTD;
+  if (ToTemplate.isNull()) {
+    if (GetImportedOrCreateDecl(FTD, D, Importer.getToContext(), DC,
+                                *LocationOrErr, ToFU, *FriendLocOrErr, ToParams,
+                                *EllipsisLocOrErr))
+      return FTD;
+  } else {
+    if (GetImportedOrCreateDecl(FTD, D, Importer.getToContext(), DC,
+                                *LocationOrErr, ToTemplate, *FriendLocOrErr,
+                                ToParams, *EllipsisLocOrErr))
+      return FTD;
+  }
 
   FTD->setAccess(D->getAccess());
   FTD->setLexicalDeclContext(LexicalDC);
diff --git a/clang/lib/AST/ASTStructuralEquivalence.cpp b/clang/lib/AST/ASTStructuralEquivalence.cpp
index aa09ea7efcf92..82baed3e31cc0 100644
--- a/clang/lib/AST/ASTStructuralEquivalence.cpp
+++ b/clang/lib/AST/ASTStructuralEquivalence.cpp
@@ -2445,19 +2445,28 @@ static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
     if (!Context.IsEquivalent(TPL1[I], TPL2[I]))
       return false;
 
-  if ((FTD1->getFriendType() && FTD2->getFriendDecl()) ||
-      (FTD1->getFriendDecl() && FTD2->getFriendType()))
+  TemplateName TN1 = FTD1->getFriendTemplateName();
+  TemplateName TN2 = FTD2->getFriendTemplateName();
+  if (TN1.isNull() != TN2.isNull())
     return false;
 
-  if (FTD1->getFriendDecl() && FTD2->getFriendDecl())
-    return IsStructurallyEquivalent(Context, FTD1->getFriendDecl(),
-                                    FTD2->getFriendDecl());
+  if (TN1.isNull()) {
+    if ((FTD1->getFriendType() && FTD2->getFriendDecl()) ||
+        (FTD1->getFriendDecl() && FTD2->getFriendType()))
+      return false;
 
-  if (FTD1->getFriendType() && FTD2->getFriendType())
-    return IsStructurallyEquivalent(Context, FTD1->getFriendType()->getType(),
-                                    FTD2->getFriendType()->getType());
+    if (FTD1->getFriendDecl() && FTD2->getFriendDecl())
+      return IsStructurallyEquivalent(Context, FTD1->getFriendDecl(),
+                                      FTD2->getFriendDecl());
 
-  return false;
+    if (FTD1->getFriendType() && FTD2->getFriendType())
+      return IsStructurallyEquivalent(Context, FTD1->getFriendType()->getType(),
+                                      FTD2->getFriendType()->getType());
+
+    return false;
+  }
+
+  return IsStructurallyEquivalent(Context, TN1, TN2);
 }
 
 static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
diff --git a/clang/lib/AST/DeclTemplate.cpp b/clang/lib/AST/DeclTemplate.cpp
index 5bc2d6c6dbd6c..8aa1479520dec 100644
--- a/clang/lib/AST/DeclTemplate.cpp
+++ b/clang/lib/AST/DeclTemplate.cpp
@@ -1250,6 +1250,22 @@ FriendTemplateDecl::Create(ASTContext &Context, DeclContext *DC,
   return FTD;
 }
 
+FriendTemplateDecl *
+FriendTemplateDecl::Create(ASTContext &Context, DeclContext *DC,
+                           SourceLocation Loc, TemplateName Template,
+                           SourceLocation FriendLoc,
+                           ArrayRef<TemplateParameterList *> FriendTypeTPLists,
+                           SourceLocation EllipsisLoc) {
+  std::size_t Extra =
+      FriendTemplateDecl::additionalSizeToAlloc<TemplateParameterList *>(
+          FriendTypeTPLists.size());
+  auto *FTD = new (Context, DC, Extra)
+      FriendTemplateDecl(DC, Loc, FriendUnion(), FriendLoc, EllipsisLoc,
+                         FriendTypeTPLists, Template);
+  cast<CXXRecordDecl>(DC)->pushFriendDecl(FTD);
+  return FTD;
+}
+
 FriendTemplateDecl *
 FriendTemplateDecl::CreateDeserialized(ASTContext &C, GlobalDeclID ID,
                                        unsigned NumFriendTypeTPLists) {
diff --git a/clang/lib/AST/ODRHash.cpp b/clang/lib/AST/ODRHash.cpp
index 46a4e256ea3e5..a22ab006640f3 100644
--- a/clang/lib/AST/ODRHash.cpp
+++ b/clang/lib/AST/ODRHash.cpp
@@ -164,7 +164,9 @@ void ODRHash::AddTemplateName(TemplateName Name) {
   case TemplateName::AssumedTemplate:
   case TemplateName::SubstTemplateTemplateParm:
   case TemplateName::SubstTemplateTemplateParmPack:
+    break;
   case TemplateName::UsingTemplate:
+    AddDecl(Name.getAsUsingShadowDecl()->getTargetDecl());
     break;
   case TemplateName::DeducedTemplate:
     llvm_unreachable("Unexpected DeducedTemplate");
@@ -473,6 +475,16 @@ class ODRDeclVisitor : public ConstDeclVisitor<ODRDeclVisitor> {
     Hash.AddBoolean(D->isPackExpansion());
   }
 
+  void VisitFriendTemplateDecl(const FriendTemplateDecl *D) {
+    TemplateName TN = D->getFriendTemplateName();
+    Hash.AddBoolean(TN.isNull());
+    if (TN.isNull()) {
+      VisitFriendDecl(D);
+    } else {
+      Hash.AddTemplateName(TN);
+    }
+  }
+
   void VisitTemplateTypeParmDecl(const TemplateTypeParmDecl *D) {
     // Only care about default arguments as part of the definition.
     const bool hasDefaultArgument =
diff --git a/clang/lib/Sema/SemaAccess.cpp b/clang/lib/Sema/SemaAccess.cpp
index 78ab1cdb6cc60..3e4af456c8f67 100644
--- a/clang/lib/Sema/SemaAccess.cpp
+++ b/clang/lib/Sema/SemaAccess.cpp
@@ -735,10 +735,11 @@ static AccessResult MatchesFriend(Sema &S, const EffectiveContext &EC,
 static AccessResult MatchesFriend(Sema &S, const EffectiveContext &EC,
                                   FriendTemplateDecl *FriendTD,
                                   ClassTemplateDecl *FriendCTD,
+                                  NestedNameSpecifier Qualifier,
                                   TemplateSpecCandidateSet *FailedTSC) {
   AccessResult OnFailure = AR_inaccessible;
-  const auto *FriendTST = GetCanonicalQualifierTemplateSpecializationType(
-      S, FriendCTD->getTemplatedDecl()->getQualifier());
+  const auto *FriendTST =
+      GetCanonicalQualifierTemplateSpecializationType(S, Qualifier);
   if (!FriendTST)
     return MatchesFriend(S, EC, FriendCTD);
 
@@ -778,6 +779,26 @@ static AccessResult MatchesFriend(Sema &S, const EffectiveContext &EC,
   return OnFailure;
 }
 
+static AccessResult MatchesFriend(Sema &S, const EffectiveContext &EC,
+                                  FriendTemplateDecl *FriendTD,
+                                  TemplateName Template,
+                                  ClassTemplateDecl *FriendCTD,
+                                  TemplateSpecCandidateSet *FailedTSC) {
+  NestedNameSpecifier Qualifier = Template.getQualifier();
+  if (Template.getAsUsingShadowDecl())
+    Qualifier = FriendCTD->getTemplatedDecl()->getQualifier();
+  return MatchesFriend(S, EC, FriendTD, FriendCTD, Qualifier, FailedTSC);
+}
+
+static AccessResult MatchesFriend(Sema &S, const EffectiveContext &EC,
+                                  FriendTemplateDecl *FriendTD,
+                                  ClassTemplateDecl *FriendCTD,
+                                  TemplateSpecCandidateSet *FailedTSC) {
+  return MatchesFriend(S, EC, FriendTD, FriendCTD,
+                       FriendCTD->getTemplatedDecl()->getQualifier(),
+                       FailedTSC);
+}
+
 static AccessResult MatchesFriend(Sema &S, const EffectiveContext &EC,
                                   FriendTemplateDecl *FriendTD,
                                   FunctionTemplateDecl *FriendFTD,
@@ -867,6 +888,11 @@ static AccessResult MatchesFriend(Sema &S, const EffectiveContext &EC,
 static AccessResult MatchesFriend(Sema &S, const EffectiveContext &EC,
                                   FriendTemplateDecl *FTD, NamedDecl *ND,
                                   TemplateSpecCandidateSet *FailedTSC) {
+  TemplateName Template = FTD->getFriendTemplateName();
+  if (auto *CTD =
+          dyn_cast_if_present<ClassTemplateDecl>(Template.getAsTemplateDecl()))
+    return MatchesFriend(S, EC, FTD, Template, CTD, FailedTSC);
+
   if (auto *CTD = dyn_cast<ClassTemplateDecl>(ND))
     return MatchesFriend(S, EC, FTD, CTD, FailedTSC);
 
diff --git a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
index 32de665297205..314d4fd951329 100644
--- a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
+++ b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
@@ -4735,7 +4735,7 @@ Decl *TemplateDeclInstantiator::VisitFriendTemplateDecl(FriendTemplateDecl *D) {
     if (SubstTemplateParameterLists(TPLists, TPL))
       return nullptr;
 
-    ClassTemplateDecl *CTD = nullptr;
+    TemplateName Template;
     if (auto DNT = FT->getTypeLoc().getAs<DependentNameTypeLoc>()) {
       NestedNameSpecifierLoc QualifierLoc = SemaRef.SubstNestedNameSpecifierLoc(
           DNT.getQualifierLoc(), TemplateArgs);
@@ -4745,23 +4745,42 @@ Decl *TemplateDeclInstantiator::VisitFriendTemplateDecl(FriendTemplateDecl *D) {
 
         DeclContext *DC =
             SemaRef.computeDeclContext(SS, /*EnteringContext=*/true);
+        if (DC && !DC->isDependentContext() &&
+            SemaRef.RequireCompleteDeclContext(SS, DC))
+          DC = nullptr;
         if (DC) {
           LookupResult Result(SemaRef, DNT.getTypePtr()->getIdentifier(),
                               DNT.getNameLoc(), Sema::LookupOrdinaryName,
                               SemaRef.forRedeclarationInCurContext());
           SemaRef.LookupQualifiedName(Result, DC);
-          CTD = Result.getAsSingle<ClassTemplateDecl>();
+          if (Result.getResultKind() == LookupResultKind::Found) {
+            NamedDecl *FoundD = *Result.begin();
+            UsingShadowDecl *FoundUsingShadow =
+                dyn_cast<UsingShadowDecl>(FoundD);
+            NamedDecl *Underlying =
+                FoundUsingShadow ? FoundUsingShadow->getTargetDecl() : FoundD;
+            if (auto *CTD = dyn_cast<ClassTemplateDecl>(Underlying)) {
+              Template = FoundUsingShadow ? TemplateName(FoundUsingShadow)
+                                          : TemplateName(CTD);
+              Template = SemaRef.Context.getQualifiedTemplateName(
+                  QualifierLoc.getNestedNameSpecifier(),
+                  /*TemplateKeyword=*/false, Template);
+            }
+          }
         }
       }
     }
 
-    if (CTD) {
-      FTD = FriendTemplateDecl::Create(SemaRef.Context, Owner, D->getLocation(),
-                                       CTD, D->getFriendLoc(), TPL);
-    } else if (TypeSourceInfo *InstTy = SemaRef.SubstType(
-                   FT, TemplateArgs, D->getLocation(), DeclarationName())) {
-      FTD = FriendTemplateDecl::Create(SemaRef.Context, Owner, D->getLocation(),
+    if (Template.isNull()) {
+      if (TypeSourceInfo *InstTy = SemaRef.SubstType(
+              FT, TemplateArgs, D->getLocation(), DeclarationName())) {
+        FTD =
+            FriendTemplateDecl::Create(SemaRef.Context, Owner, D->getLocation(),
                                        InstTy, D->getFriendLoc(), TPL);
+      }
+    } else {
+      FTD = FriendTemplateDecl::Create(SemaRef.Context, Owner, D->getLocation(),
+                                       Template, D->getFriendLoc(), TPL);
     }
   } else {
     SmallVector<TemplateParameterList *, 1> TPL;
diff --git a/clang/lib/Serialization/ASTReaderDecl.cpp b/clang/lib/Serialization/ASTReaderDecl.cpp
index cbcd92c9cc810..d10d67708dca1 100644
--- a/clang/lib/Serialization/ASTReaderDecl.cpp
+++ b/clang/lib/Serialization/ASTReaderDecl.cpp
@@ -2407,10 +2407,17 @@ void ASTDeclReader::VisitFriendTemplateDecl(FriendTemplateDecl *D) {
   VisitDecl(D);
   for (unsigned i = 0; i != D->NumTPLists; ++i)
     D->getTrailingObjects()[i] = Record.readTemplateParameterList();
-  if (Record.readInt()) // HasFriendDecl
-    D->Friend = readDeclAs<NamedDecl>();
-  else
+  switch (Record.readInt()) {
+  case 0:
     D->Friend = readTypeSourceInfo();
+    break;
+  case 1:
+    D->Friend = readDeclAs<NamedDecl>();
+    break;
+  case 2:
+    D->Template = Record.readTemplateName();
+    break;
+  }
   D->NextFriend = readDeclID().getRawValue();
   D->FriendLoc = readSourceLocation();
   D->EllipsisLoc = readSourceLocation();
diff --git a/clang/lib/Serialization/ASTWriterDecl.cpp b/clang/lib/Serialization/ASTWriterDecl.cpp
index 6e96fc6021696..8201af47d504f 100644
--- a/clang/lib/Serialization/ASTWriterDecl.cpp
+++ b/clang/lib/Serialization/ASTWriterDecl.cpp
@@ -1842,11 +1842,18 @@ void ASTDeclWriter::VisitFriendTemplateDecl(FriendTemplateDecl *D) {
   VisitDecl(D);
   for (TemplateParameterList *TPL : D->getFriendTypeTemplateParameterLists())
     Record.AddTemplateParameterList(TPL);
-  Record.push_back(D->getFriendDecl() != nullptr);
-  if (D->getFriendDecl())
-    Record.AddDeclRef(D->getFriendDecl());
-  else
-    Record.AddTypeSourceInfo(D->getFriendType());
+  if (D->Template.isNull()) {
+    if (D->getFriendDecl()) {
+      Record.push_back(1);
+      Record.AddDeclRef(D->getFriendDecl());
+    } else {
+      Record.push_back(0);
+      Record.AddTypeSourceInfo(D->getFriendType());
+    }
+  } else {
+    Record.push_back(2);
+    Record.AddTemplateName(D->Template);
+  }
   Record.AddDeclRef(D->getNextFriend());
   Record.AddSourceLocation(D->FriendLoc);
   Record.AddSourceLocation(D->EllipsisLoc);

>From bf05d27e16ac83824af6d30ff7fd95af827cd39b Mon Sep 17 00:00:00 2001
From: Oleksandr Tarasiuk <oleksandr.tarasiuk at outlook.com>
Date: Thu, 14 May 2026 23:38:54 +0300
Subject: [PATCH 27/38] update args comments

---
 clang/lib/Sema/SemaTemplateInstantiateDecl.cpp | 3 ++-
 clang/lib/Serialization/ASTReaderDecl.cpp      | 3 ++-
 2 files changed, 4 insertions(+), 2 deletions(-)

diff --git a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
index 314d4fd951329..643d3dc88a0d1 100644
--- a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
+++ b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
@@ -5264,7 +5264,8 @@ TemplateDeclInstantiator::SubstFunctionType(FunctionDecl *D,
         }
 
         ParmVarDecl *Parm = SemaRef.SubstParmVarDecl(
-            OldParam, TemplateArgs, /*indexAdjustment=*/0, std::nullopt,
+            OldParam, TemplateArgs, /*indexAdjustment=*/0,
+            /*NumExpansions=*/std::nullopt,
             /*ExpectParameterPack=*/false, EvaluateConstraints);
         if (!Parm)
           return nullptr;
diff --git a/clang/lib/Serialization/ASTReaderDecl.cpp b/clang/lib/Serialization/ASTReaderDecl.cpp
index d10d67708dca1..293c3557f1a5d 100644
--- a/clang/lib/Serialization/ASTReaderDecl.cpp
+++ b/clang/lib/Serialization/ASTReaderDecl.cpp
@@ -4077,7 +4077,8 @@ Decl *ASTReader::ReadDeclRecord(GlobalDeclID ID) {
     D = FriendDecl::CreateDeserialized(Context, ID);
     break;
   case DECL_FRIEND_TEMPLATE:
-    D = FriendTemplateDecl::CreateDeserialized(Context, ID, Record.readInt());
+    D = FriendTemplateDecl::CreateDeserialized(Context, ID,
+                                               /*NumTPLists=*/Record.readInt());
     break;
   case DECL_CLASS_TEMPLATE:
     D = ClassTemplateDecl::CreateDeserialized(Context, ID);

>From 6078de60d6d7efe9db0283b6260d5429678b6820 Mon Sep 17 00:00:00 2001
From: Oleksandr Tarasiuk <oleksandr.tarasiuk at outlook.com>
Date: Thu, 14 May 2026 23:43:12 +0300
Subject: [PATCH 28/38] add local instantiation scope

---
 clang/lib/Sema/SemaTemplateInstantiateDecl.cpp | 1 +
 1 file changed, 1 insertion(+)

diff --git a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
index 643d3dc88a0d1..0f811a7db0ccf 100644
--- a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
+++ b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
@@ -4940,6 +4940,7 @@ TemplateDeclInstantiator::SubstTemplateParams(TemplateParameterList *L) {
 bool TemplateDeclInstantiator::SubstTemplateParameterLists(
     ArrayRef<TemplateParameterList *> TPL,
     SmallVectorImpl<TemplateParameterList *> &InstTPL) {
+  LocalInstantiationScope Scope(SemaRef, /*CombineWithOuterScope=*/true);
   for (TemplateParameterList *L : TPL) {
     TemplateParameterList *InstParams = SubstTemplateParams(L);
     if (!InstParams)

>From eb4f5290934ddb22714ef0f73e84dd331598f949 Mon Sep 17 00:00:00 2001
From: Oleksandr Tarasiuk <oleksandr.tarasiuk at outlook.com>
Date: Thu, 14 May 2026 23:48:26 +0300
Subject: [PATCH 29/38] refactor CheckDependentFriend

---
 clang/lib/Sema/SemaDeclCXX.cpp | 26 +++++++++++---------------
 1 file changed, 11 insertions(+), 15 deletions(-)

diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index 30f8f12016f6f..a3a6de6e872c2 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -18062,30 +18062,26 @@ Decl *Sema::BuildStaticAssertDeclaration(SourceLocation StaticAssertLoc,
 
 bool Sema::CheckDependentFriend(SourceLocation Loc, NestedNameSpecifier NNS,
                                 TemplateParameterList *FPL) {
-  if (!NNS || !FPL || FPL->size() == 0)
+  if (!NNS.isDependent() || !FPL || FPL->size() == 0)
     return false;
 
-  if (NNS.isDependent()) {
-    if (NNS.getKind() == NestedNameSpecifier::Kind::Type) {
-      QualType T(NNS.getCanonical().getAsType(), 0);
+  if (NNS.getKind() == NestedNameSpecifier::Kind::Type) {
+    QualType T(NNS.getCanonical().getAsType(), 0);
 
-      if (const auto *PIT = dyn_cast<PackIndexingType>(T))
-        T = PIT->getPattern();
+    if (const auto *PIT = dyn_cast<PackIndexingType>(T))
+      T = PIT->getPattern();
 
-      if (const auto *TST = dyn_cast<TemplateSpecializationType>(T)) {
-        if (isa<ClassTemplateDecl>(TST->getTemplateName().getAsTemplateDecl()))
-          return false;
-      }
-
-      if (isa<InjectedClassNameType>(T))
+    if (const auto *TST = dyn_cast<TemplateSpecializationType>(T)) {
+      if (isa<ClassTemplateDecl>(TST->getTemplateName().getAsTemplateDecl()))
         return false;
     }
 
-    Diag(Loc, diag::err_dependent_friend_not_member);
-    return true;
+    if (isa<InjectedClassNameType>(T))
+      return false;
   }
 
-  return false;
+  Diag(Loc, diag::err_dependent_friend_not_member);
+  return true;
 }
 
 DeclResult Sema::ActOnTemplatedFriendTag(

>From 2c7a2ff0d17482416ded63a19ffa5bca88abad22 Mon Sep 17 00:00:00 2001
From: Oleksandr Tarasiuk <oleksandr.tarasiuk at outlook.com>
Date: Thu, 14 May 2026 23:57:55 +0300
Subject: [PATCH 30/38] fix dependent friend check for dependent template names

---
 clang/lib/Sema/SemaDeclCXX.cpp | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp
index a3a6de6e872c2..e4232f9221a1c 100644
--- a/clang/lib/Sema/SemaDeclCXX.cpp
+++ b/clang/lib/Sema/SemaDeclCXX.cpp
@@ -18072,7 +18072,8 @@ bool Sema::CheckDependentFriend(SourceLocation Loc, NestedNameSpecifier NNS,
       T = PIT->getPattern();
 
     if (const auto *TST = dyn_cast<TemplateSpecializationType>(T)) {
-      if (isa<ClassTemplateDecl>(TST->getTemplateName().getAsTemplateDecl()))
+      if (isa_and_nonnull<ClassTemplateDecl>(
+              TST->getTemplateName().getAsTemplateDecl()))
         return false;
     }
 

>From 68adfc1af674289d5960319fbd24b6d64226fe79 Mon Sep 17 00:00:00 2001
From: Oleksandr Tarasiuk <oleksandr.tarasiuk at outlook.com>
Date: Fri, 15 May 2026 00:16:14 +0300
Subject: [PATCH 31/38] refine friend template qualifier handling

---
 clang/lib/Sema/SemaAccess.cpp | 23 +++++++++++++++--------
 1 file changed, 15 insertions(+), 8 deletions(-)

diff --git a/clang/lib/Sema/SemaAccess.cpp b/clang/lib/Sema/SemaAccess.cpp
index 3e4af456c8f67..6a39e7416f2d1 100644
--- a/clang/lib/Sema/SemaAccess.cpp
+++ b/clang/lib/Sema/SemaAccess.cpp
@@ -360,8 +360,7 @@ GetCanonicalFunctionProto(Sema &S, const FunctionDecl *FD) {
 }
 
 static const TemplateSpecializationType *
-GetCanonicalQualifierTemplateSpecializationType(Sema &S,
-                                                NestedNameSpecifier NNS) {
+GetQualifierClassTemplateSpecializationType(Sema &S, NestedNameSpecifier NNS) {
   if (!NNS)
     return nullptr;
 
@@ -372,7 +371,15 @@ GetCanonicalQualifierTemplateSpecializationType(Sema &S,
   if (const auto *ICNT = Ty->getAs<InjectedClassNameType>())
     Ty = ICNT->getDecl()->getCanonicalTemplateSpecializationType(S.Context);
 
-  return Ty->getAs<TemplateSpecializationType>();
+  const auto *TST = Ty->getAsNonAliasTemplateSpecializationType();
+  if (!TST)
+    return nullptr;
+
+  if (!isa_and_nonnull<ClassTemplateDecl>(
+          TST->getTemplateName().getAsTemplateDecl()))
+    return nullptr;
+
+  return TST;
 }
 
 static FunctionTemplateDecl *TryGetFunctionTemplateDecl(FunctionDecl *FD) {
@@ -739,7 +746,7 @@ static AccessResult MatchesFriend(Sema &S, const EffectiveContext &EC,
                                   TemplateSpecCandidateSet *FailedTSC) {
   AccessResult OnFailure = AR_inaccessible;
   const auto *FriendTST =
-      GetCanonicalQualifierTemplateSpecializationType(S, Qualifier);
+      GetQualifierClassTemplateSpecializationType(S, Qualifier);
   if (!FriendTST)
     return MatchesFriend(S, EC, FriendCTD);
 
@@ -804,7 +811,7 @@ static AccessResult MatchesFriend(Sema &S, const EffectiveContext &EC,
                                   FunctionTemplateDecl *FriendFTD,
                                   TemplateSpecCandidateSet *FailedTSC) {
   AccessResult OnFailure = AR_inaccessible;
-  const auto *FriendTST = GetCanonicalQualifierTemplateSpecializationType(
+  const auto *FriendTST = GetQualifierClassTemplateSpecializationType(
       S, FriendFTD->getTemplatedDecl()->getQualifier());
   if (!FriendTST)
     return OnFailure;
@@ -844,8 +851,8 @@ static AccessResult MatchesFriend(Sema &S, const EffectiveContext &EC,
                                   FunctionDecl *FriendFD,
                                   TemplateSpecCandidateSet *FailedTSC) {
   AccessResult OnFailure = AR_inaccessible;
-  const auto *FriendTST = GetCanonicalQualifierTemplateSpecializationType(
-      S, FriendFD->getQualifier());
+  const auto *FriendTST =
+      GetQualifierClassTemplateSpecializationType(S, FriendFD->getQualifier());
   if (!FriendTST)
     return OnFailure;
 
@@ -921,7 +928,7 @@ static AccessResult MatchesFriend(Sema &S, const EffectiveContext &EC,
   if (!NNS)
     return OnFailure;
 
-  const auto *TST = GetCanonicalQualifierTemplateSpecializationType(S, NNS);
+  const auto *TST = GetQualifierClassTemplateSpecializationType(S, NNS);
   if (!TST)
     return OnFailure;
 

>From 94f24a570262d5c9e8b031b943794ff487813a92 Mon Sep 17 00:00:00 2001
From: Oleksandr Tarasiuk <oleksandr.tarasiuk at outlook.com>
Date: Fri, 15 May 2026 00:49:53 +0300
Subject: [PATCH 32/38] improve diagnostics

---
 .../clang/Basic/DiagnosticSemaKinds.td        |  5 ++++
 clang/include/clang/Sema/TemplateDeduction.h  | 17 ++++++++++---
 clang/lib/Sema/SemaAccess.cpp                 |  4 +++-
 clang/lib/Sema/SemaOverload.cpp               | 24 ++++++++++++-------
 .../CXX/temp/temp.decls/temp.friend/p5.cpp    |  2 +-
 clang/test/SemaTemplate/friend-template.cpp   |  2 +-
 6 files changed, 40 insertions(+), 14 deletions(-)

diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index f5c93412abf76..a7a875b08588a 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -5264,11 +5264,16 @@ def note_ovl_candidate_deduced_mismatch : Note<
     "adjusted type of %select{|element of }4argument}1,2%3">;
 def note_ovl_candidate_non_deduced_mismatch : Note<
     "candidate template ignored: could not match %diff{$ against $|types}0,1">;
+def note_friend_template_non_deduced_mismatch : Note<
+    "candidate friend template ignored: could not match "
+    "%diff{$ against $|types}0,1">;
 // This note is needed because the above note would sometimes print two
 // different types with the same name.  Remove this note when the above note
 // can handle that case properly.
 def note_ovl_candidate_non_deduced_mismatch_qualified : Note<
     "candidate template ignored: could not match %q0 against %q1">;
+def note_friend_template_non_deduced_mismatch_qualified : Note<
+    "candidate friend template ignored: could not match %q0 against %q1">;
 
 // Note that we don't treat templates differently for this diagnostic.
 def note_ovl_candidate_arity : Note<"candidate "
diff --git a/clang/include/clang/Sema/TemplateDeduction.h b/clang/include/clang/Sema/TemplateDeduction.h
index 39c909d73f565..dd9fe46c9bf8a 100644
--- a/clang/include/clang/Sema/TemplateDeduction.h
+++ b/clang/include/clang/Sema/TemplateDeduction.h
@@ -311,6 +311,11 @@ struct DeductionFailureInfo {
   }
 };
 
+enum class TemplateSpecCandidateSetKind {
+  Normal,
+  FriendTemplate,
+};
+
 /// TemplateSpecCandidate - This is a generalization of OverloadCandidate
 /// which keeps track of template argument deduction failure info, when
 /// handling explicit specializations (and instantiations) of templates
@@ -337,7 +342,8 @@ struct TemplateSpecCandidate {
   }
 
   /// Diagnose a template argument deduction failure.
-  void NoteDeductionFailure(Sema &S, bool ForTakingAddress);
+  void NoteDeductionFailure(Sema &S, bool ForTakingAddress,
+                            TemplateSpecCandidateSetKind CandidateSetKind);
 };
 
 /// TemplateSpecCandidateSet - A set of generalized overload candidates,
@@ -353,11 +359,16 @@ class TemplateSpecCandidateSet {
   // attribute on parameters.
   bool ForTakingAddress;
 
+  TemplateSpecCandidateSetKind CandidateSetKind;
+
   void destroyCandidates();
 
 public:
-  TemplateSpecCandidateSet(SourceLocation Loc, bool ForTakingAddress = false)
-      : Loc(Loc), ForTakingAddress(ForTakingAddress) {}
+  TemplateSpecCandidateSet(SourceLocation Loc, bool ForTakingAddress = false,
+                           TemplateSpecCandidateSetKind CandidateSetKind =
+                               TemplateSpecCandidateSetKind::Normal)
+      : Loc(Loc), ForTakingAddress(ForTakingAddress),
+        CandidateSetKind(CandidateSetKind) {}
   TemplateSpecCandidateSet(const TemplateSpecCandidateSet &) = delete;
   TemplateSpecCandidateSet &
   operator=(const TemplateSpecCandidateSet &) = delete;
diff --git a/clang/lib/Sema/SemaAccess.cpp b/clang/lib/Sema/SemaAccess.cpp
index 6a39e7416f2d1..ad6aac1c956ed 100644
--- a/clang/lib/Sema/SemaAccess.cpp
+++ b/clang/lib/Sema/SemaAccess.cpp
@@ -1895,7 +1895,9 @@ static AccessResult CheckEffectiveAccess(Sema &S, const EffectiveContext &EC,
   if (Entity.isQuiet())
     return CheckEffectiveAccess(S, EC, Loc, Entity, /*FailedTSC=*/nullptr);
 
-  TemplateSpecCandidateSet FailedTSC(Loc);
+  TemplateSpecCandidateSet FailedTSC(
+      Loc, /*ForTakingAddress=*/false,
+      TemplateSpecCandidateSetKind::FriendTemplate);
   return CheckEffectiveAccess(S, EC, Loc, Entity, &FailedTSC);
 }
 
diff --git a/clang/lib/Sema/SemaOverload.cpp b/clang/lib/Sema/SemaOverload.cpp
index 96c4ce489fe04..097807b0e6602 100644
--- a/clang/lib/Sema/SemaOverload.cpp
+++ b/clang/lib/Sema/SemaOverload.cpp
@@ -12371,8 +12371,9 @@ static TemplateDecl *getDescribedTemplate(Decl *Templated) {
 /// Diagnose a failed template-argument deduction.
 static void DiagnoseBadDeduction(Sema &S, NamedDecl *Found, Decl *Templated,
                                  DeductionFailureInfo &DeductionFailure,
-                                 unsigned NumArgs,
-                                 bool TakingCandidateAddress) {
+                                 unsigned NumArgs, bool TakingCandidateAddress,
+                                 TemplateSpecCandidateSetKind CandidateSetKind =
+                                     TemplateSpecCandidateSetKind::Normal) {
   TemplateParameter Param = DeductionFailure.getTemplateParameter();
   NamedDecl *ParamD;
   (ParamD = Param.dyn_cast<TemplateTypeParmDecl*>()) ||
@@ -12620,7 +12621,10 @@ static void DiagnoseBadDeduction(Sema &S, NamedDecl *Found, Decl *Templated,
           //    name for types, not decls.
           // Ideally, this should folded into the diagnostic printer.
           S.Diag(Templated->getLocation(),
-                 diag::note_ovl_candidate_non_deduced_mismatch_qualified)
+                 CandidateSetKind ==
+                         TemplateSpecCandidateSetKind::FriendTemplate
+                     ? diag::note_friend_template_non_deduced_mismatch_qualified
+                     : diag::note_ovl_candidate_non_deduced_mismatch_qualified)
               << FirstTN.getAsTemplateDecl() << SecondTN.getAsTemplateDecl();
           return;
         }
@@ -12636,7 +12640,9 @@ static void DiagnoseBadDeduction(Sema &S, NamedDecl *Found, Decl *Templated,
     // diagnostic that mentions 'auto' and lambda in addition to
     // (or instead of?) the canonical template type parameters.
     S.Diag(Templated->getLocation(),
-           diag::note_ovl_candidate_non_deduced_mismatch)
+           CandidateSetKind == TemplateSpecCandidateSetKind::FriendTemplate
+               ? diag::note_friend_template_non_deduced_mismatch
+               : diag::note_ovl_candidate_non_deduced_mismatch)
         << FirstTA << SecondTA;
     return;
   }
@@ -13565,10 +13571,12 @@ struct CompareTemplateSpecCandidatesForDisplay {
 /// Diagnose a template argument deduction failure.
 /// We are treating these failures as overload failures due to bad
 /// deductions.
-void TemplateSpecCandidate::NoteDeductionFailure(Sema &S,
-                                                 bool ForTakingAddress) {
+void TemplateSpecCandidate::NoteDeductionFailure(
+    Sema &S, bool ForTakingAddress,
+    TemplateSpecCandidateSetKind CandidateSetKind) {
   DiagnoseBadDeduction(S, FoundDecl, Specialization, // pattern
-                       DeductionFailure, /*NumArgs=*/0, ForTakingAddress);
+                       DeductionFailure, /*NumArgs=*/0, ForTakingAddress,
+                       CandidateSetKind);
 }
 
 void TemplateSpecCandidateSet::destroyCandidates() {
@@ -13620,7 +13628,7 @@ void TemplateSpecCandidateSet::NoteCandidates(Sema &S, SourceLocation Loc) {
 
     assert(Cand->Specialization &&
            "Non-matching built-in candidates are not added to Cands.");
-    Cand->NoteDeductionFailure(S, ForTakingAddress);
+    Cand->NoteDeductionFailure(S, ForTakingAddress, CandidateSetKind);
   }
 
   if (I != E)
diff --git a/clang/test/CXX/temp/temp.decls/temp.friend/p5.cpp b/clang/test/CXX/temp/temp.decls/temp.friend/p5.cpp
index 46f8139d4451f..684d03ab82ced 100644
--- a/clang/test/CXX/temp/temp.decls/temp.friend/p5.cpp
+++ b/clang/test/CXX/temp/temp.decls/temp.friend/p5.cpp
@@ -167,7 +167,7 @@ namespace test8 {
     c.n = 0;
     // expected-error at -1 {{'n' is a private member of 'test8::C'}}
     //   expected-note@#test8-C-n {{implicitly declared private here}}
-    //   expected-note@#test8-A {{candidate template ignored: could not match 'T *' against 'int'}}
+    //   expected-note@#test8-A {{candidate friend template ignored: could not match 'T *' against 'int'}}
     return 0;
   }
 
diff --git a/clang/test/SemaTemplate/friend-template.cpp b/clang/test/SemaTemplate/friend-template.cpp
index 3555bb2eb8cb3..4a8f23a56dd22 100644
--- a/clang/test/SemaTemplate/friend-template.cpp
+++ b/clang/test/SemaTemplate/friend-template.cpp
@@ -354,7 +354,7 @@ template <class U> struct A<T>::B {
     A<int>::f();
     // expected-error at -1 {{'f' is a private member of 'GH104057::A<int>'}}
     //   expected-note@#GH104057-A-f {{declared private here}}
-    //   expected-note@#GH104057-A {{candidate template ignored: could not match 'U *' against 'double'}}
+    //   expected-note@#GH104057-A {{candidate friend template ignored: could not match 'U *' against 'double'}}
   }
 };
 

>From df7ec0f4780f6286a9baa4fdc947a2e0eda69ecf Mon Sep 17 00:00:00 2001
From: Oleksandr Tarasiuk <oleksandr.tarasiuk at outlook.com>
Date: Fri, 15 May 2026 01:00:46 +0300
Subject: [PATCH 33/38] add additional tests

---
 .../CXX/temp/temp.decls/temp.friend/p5.cpp    | 52 +++++++++++++++++++
 1 file changed, 52 insertions(+)

diff --git a/clang/test/CXX/temp/temp.decls/temp.friend/p5.cpp b/clang/test/CXX/temp/temp.decls/temp.friend/p5.cpp
index 684d03ab82ced..7baebae666f11 100644
--- a/clang/test/CXX/temp/temp.decls/temp.friend/p5.cpp
+++ b/clang/test/CXX/temp/temp.decls/temp.friend/p5.cpp
@@ -237,3 +237,55 @@ namespace test10 {
     }
   };
 }
+
+namespace test11 {
+  template <class> struct C;
+  template <class T> struct A {
+    template <class> struct B;
+  };
+  template <class T> struct D : A<T> {
+    using A<T>::B;
+  };
+
+  template <class T> struct C {
+    int n;
+    template <class U> friend struct D<T>::B;
+  };
+
+  template <> template <class U> struct A<int>::B {
+    static int f(C<int> &c) {
+      c.n = 0;
+      return 0;
+    }
+  };
+
+  int x = A<int>::B<void>::f(*new C<int>);
+}
+
+namespace test12 {
+  template <class T> struct A {
+    template <T> struct B {
+      static int f();
+    };
+  };
+
+  template <class T> struct C {
+    int n;
+    template <class U> template <U V> friend struct A<U>::B;
+  };
+
+  template <class T> template <T V> int A<T>::B<V>::f() {
+    C<T> c;
+    c.n = 0;
+    return 0;
+  }
+
+  int x = A<int>::B<0>::f();
+}
+
+namespace test13 {
+template <typename T> struct S {
+  template <typename> friend class T::template X<int>::Y;
+  // expected-error at -1 {{friend declaration does not name a member of a class template specialization}}
+};
+}

>From a93ba613195a6b12a5f0dade538119d9f6d91b97 Mon Sep 17 00:00:00 2001
From: Oleksandr Tarasiuk <oleksandr.tarasiuk at outlook.com>
Date: Fri, 15 May 2026 09:10:52 +0300
Subject: [PATCH 34/38] cleanup

---
 clang/lib/Sema/SemaAccess.cpp | 11 ++++-------
 1 file changed, 4 insertions(+), 7 deletions(-)

diff --git a/clang/lib/Sema/SemaAccess.cpp b/clang/lib/Sema/SemaAccess.cpp
index ad6aac1c956ed..02414dae5938d 100644
--- a/clang/lib/Sema/SemaAccess.cpp
+++ b/clang/lib/Sema/SemaAccess.cpp
@@ -372,14 +372,11 @@ GetQualifierClassTemplateSpecializationType(Sema &S, NestedNameSpecifier NNS) {
     Ty = ICNT->getDecl()->getCanonicalTemplateSpecializationType(S.Context);
 
   const auto *TST = Ty->getAsNonAliasTemplateSpecializationType();
-  if (!TST)
-    return nullptr;
+  if (TST && isa_and_nonnull<ClassTemplateDecl>(
+                 TST->getTemplateName().getAsTemplateDecl()))
+    return TST;
 
-  if (!isa_and_nonnull<ClassTemplateDecl>(
-          TST->getTemplateName().getAsTemplateDecl()))
-    return nullptr;
-
-  return TST;
+  return nullptr;
 }
 
 static FunctionTemplateDecl *TryGetFunctionTemplateDecl(FunctionDecl *FD) {

>From 40fba25564766fb47dd5088a4529988a0375b036 Mon Sep 17 00:00:00 2001
From: Oleksandr Tarasiuk <oleksandr.tarasiuk at outlook.com>
Date: Fri, 15 May 2026 09:34:46 +0300
Subject: [PATCH 35/38] simplify friend template lookup handling

---
 .../lib/Sema/SemaTemplateInstantiateDecl.cpp  | 21 +++++++------------
 1 file changed, 8 insertions(+), 13 deletions(-)

diff --git a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
index 04a7804182240..714711342d76f 100644
--- a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
+++ b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
@@ -4755,19 +4755,14 @@ Decl *TemplateDeclInstantiator::VisitFriendTemplateDecl(FriendTemplateDecl *D) {
                               DNT.getNameLoc(), Sema::LookupOrdinaryName,
                               SemaRef.forRedeclarationInCurContext());
           SemaRef.LookupQualifiedName(Result, DC);
-          if (Result.getResultKind() == LookupResultKind::Found) {
-            NamedDecl *FoundD = *Result.begin();
-            UsingShadowDecl *FoundUsingShadow =
-                dyn_cast<UsingShadowDecl>(FoundD);
-            NamedDecl *Underlying =
-                FoundUsingShadow ? FoundUsingShadow->getTargetDecl() : FoundD;
-            if (auto *CTD = dyn_cast<ClassTemplateDecl>(Underlying)) {
-              Template = FoundUsingShadow ? TemplateName(FoundUsingShadow)
-                                          : TemplateName(CTD);
-              Template = SemaRef.Context.getQualifiedTemplateName(
-                  QualifierLoc.getNestedNameSpecifier(),
-                  /*TemplateKeyword=*/false, Template);
-            }
+          if (auto *CTD = Result.getAsSingle<ClassTemplateDecl>()) {
+            auto *FoundUsingShadow =
+                dyn_cast<UsingShadowDecl>(Result.getRepresentativeDecl());
+            Template = FoundUsingShadow ? TemplateName(FoundUsingShadow)
+                                        : TemplateName(CTD);
+            Template = SemaRef.Context.getQualifiedTemplateName(
+                QualifierLoc.getNestedNameSpecifier(),
+                /*TemplateKeyword=*/false, Template);
           }
         }
       }

>From 644fc60cca0a467415bb983c0e3d9f332279db45 Mon Sep 17 00:00:00 2001
From: Oleksandr Tarasiuk <oleksandr.tarasiuk at outlook.com>
Date: Fri, 15 May 2026 10:56:41 +0300
Subject: [PATCH 36/38] handle qualified friend template access

---
 clang/lib/Sema/SemaAccess.cpp                 | 47 +++++++++++++++++--
 .../CXX/temp/temp.decls/temp.friend/p5.cpp    | 22 +++++++--
 2 files changed, 60 insertions(+), 9 deletions(-)

diff --git a/clang/lib/Sema/SemaAccess.cpp b/clang/lib/Sema/SemaAccess.cpp
index 02414dae5938d..a261c84aa6f0f 100644
--- a/clang/lib/Sema/SemaAccess.cpp
+++ b/clang/lib/Sema/SemaAccess.cpp
@@ -426,6 +426,41 @@ static bool MatchesFriendContext(Sema &S, DeclContext *DC,
                                     ContextArgs, Loc, FailedTSC);
 }
 
+static bool MatchesFriendContext(Sema &S, DeclContext *DC,
+                                 ClassTemplateDecl *FriendCTD,
+                                 DeclContext *FriendDC,
+                                 ArrayRef<TemplateArgument> FriendArgs,
+                                 TemplateParameterList *FriendTPL,
+                                 SourceLocation Loc,
+                                 TemplateSpecCandidateSet *FailedTSC) {
+  auto GetClassTemplateContext =
+      [](const DeclContext *DC,
+         ClassTemplateDecl *Template) -> const CXXRecordDecl * {
+    const auto *RD = dyn_cast<CXXRecordDecl>(DC);
+    if (RD) {
+      ClassTemplateDecl *CTD = RD->getDescribedClassTemplate();
+      if (const auto *CTSD = dyn_cast<ClassTemplateSpecializationDecl>(RD))
+        CTD = CTSD->getSpecializedTemplate();
+
+      if (CTD && CTD->getCanonicalDecl() == Template->getCanonicalDecl())
+        return RD;
+    }
+    return nullptr;
+  };
+
+  if (const auto *FriendRecord = dyn_cast<CXXRecordDecl>(FriendDC)) {
+    const CXXRecordDecl *FriendContext =
+        GetClassTemplateContext(FriendRecord->getDeclContext(), FriendCTD);
+    const CXXRecordDecl *Context = GetClassTemplateContext(DC, FriendCTD);
+    if (FriendContext && Context &&
+        FriendContext->getCanonicalDecl() == Context->getCanonicalDecl())
+      return true;
+  }
+
+  return MatchesFriendContext(S, DC, FriendCTD, FriendArgs, FriendTPL, Loc,
+                              FailedTSC);
+}
+
 /// Checks whether one class might instantiate to the other.
 static bool MightInstantiateTo(const CXXRecordDecl *From,
                                const CXXRecordDecl *To) {
@@ -772,11 +807,13 @@ static AccessResult MatchesFriend(Sema &S, const EffectiveContext &EC,
     if (!ContextCTD)
       continue;
 
-    if (MightInstantiateTo(ContextCTD->getTemplatedDecl(),
-                           FriendCTD->getTemplatedDecl()) &&
-        MatchesFriendContext(S, RD->getDeclContext(), FriendContextCTD,
-                             FriendArgs, FriendTPL, FriendTD->getLocation(),
-                             FailedTSC))
+    if (!MightInstantiateTo(ContextCTD->getTemplatedDecl(),
+                            FriendCTD->getTemplatedDecl()))
+      continue;
+
+    if (MatchesFriendContext(S, RD->getDeclContext(), FriendContextCTD,
+                             FriendTD->getDeclContext(), FriendArgs, FriendTPL,
+                             FriendTD->getLocation(), FailedTSC))
       return AR_accessible;
   }
 
diff --git a/clang/test/CXX/temp/temp.decls/temp.friend/p5.cpp b/clang/test/CXX/temp/temp.decls/temp.friend/p5.cpp
index 7baebae666f11..ec766b909d0f7 100644
--- a/clang/test/CXX/temp/temp.decls/temp.friend/p5.cpp
+++ b/clang/test/CXX/temp/temp.decls/temp.friend/p5.cpp
@@ -284,8 +284,22 @@ namespace test12 {
 }
 
 namespace test13 {
-template <typename T> struct S {
-  template <typename> friend class T::template X<int>::Y;
-  // expected-error at -1 {{friend declaration does not name a member of a class template specialization}}
-};
+  template <typename T> struct S {
+    template <typename> friend class T::template X<int>::Y;
+    // expected-error at -1 {{friend declaration does not name a member of a class template specialization}}
+  };
+}
+
+namespace test14 {
+  template <class T> struct A {
+    template <bool V> struct B {
+      static int f(B<false> &x) { return x.n; }
+
+    private:
+      int n;
+      template <bool> friend struct A<T>::B;
+    };
+  };
+
+  int x = A<int>::B<true>::f(*new A<int>::B<false>);
 }

>From 91f7995fb753ea0d393fe77d1fb2d0e70a55e104 Mon Sep 17 00:00:00 2001
From: Oleksandr Tarasiuk <oleksandr.tarasiuk at outlook.com>
Date: Tue, 26 May 2026 11:56:56 +0300
Subject: [PATCH 37/38] reuse structural equivalence for templated friends

---
 clang/lib/AST/ASTImporter.cpp              | 39 ++--------------------
 clang/lib/AST/ASTStructuralEquivalence.cpp | 15 +++++----
 2 files changed, 10 insertions(+), 44 deletions(-)

diff --git a/clang/lib/AST/ASTImporter.cpp b/clang/lib/AST/ASTImporter.cpp
index ba2ccb4ce3b77..01e3f7ece5baf 100644
--- a/clang/lib/AST/ASTImporter.cpp
+++ b/clang/lib/AST/ASTImporter.cpp
@@ -4531,13 +4531,9 @@ struct FriendCountAndPosition {
 
 static bool IsEquivalentFriend(ASTImporter &Importer, FriendDecl *FD1,
                                FriendDecl *FD2) {
-  if ((!FD1->getFriendType()) != (!FD2->getFriendType()))
+  if (FD1->getKind() != FD2->getKind())
     return false;
 
-  if (const TypeSourceInfo *TSI = FD1->getFriendType())
-    return Importer.IsStructurallyEquivalent(
-        TSI->getType(), FD2->getFriendType()->getType(), /*Complain=*/false);
-
   ASTImporter::NonEquivalentDeclSet NonEquivalentDecls;
   StructuralEquivalenceContext Ctx(
       Importer.getToContext().getLangOpts(), FD1->getASTContext(),
@@ -4547,36 +4543,6 @@ static bool IsEquivalentFriend(ASTImporter &Importer, FriendDecl *FD1,
   return Ctx.IsEquivalent(FD1, FD2);
 }
 
-static bool IsEquivalentFriend(ASTImporter &Importer, FriendTemplateDecl *FTD1,
-                               FriendTemplateDecl *FTD2) {
-  ArrayRef<TemplateParameterList *> TPL1 =
-      FTD1->getFriendTypeTemplateParameterLists();
-  ArrayRef<TemplateParameterList *> TPL2 =
-      FTD2->getFriendTypeTemplateParameterLists();
-  if (TPL1.size() != TPL2.size())
-    return false;
-
-  ASTImporter::NonEquivalentDeclSet NonEquivalentDecls;
-  StructuralEquivalenceContext Ctx(
-      Importer.getToContext().getLangOpts(), FTD1->getASTContext(),
-      FTD2->getASTContext(), NonEquivalentDecls,
-      StructuralEquivalenceKind::Default, /*StrictTypeSpelling=*/false,
-      /*Complain=*/false);
-
-  for (unsigned I = 0, N = TPL1.size(); I != N; ++I)
-    if (!Ctx.IsEquivalent(TPL1[I], TPL2[I]))
-      return false;
-
-  if ((!FTD1->getFriendType()) != (!FTD2->getFriendType()))
-    return false;
-
-  if (const TypeSourceInfo *TSI = FTD1->getFriendType())
-    return Importer.IsStructurallyEquivalent(
-        TSI->getType(), FTD2->getFriendType()->getType(), /*Complain=*/false);
-
-  return Ctx.IsEquivalent(FTD1, FTD2);
-}
-
 static FriendCountAndPosition getFriendCountAndPosition(ASTImporter &Importer,
                                                         FriendDecl *FD) {
   unsigned int FriendCount = 0;
@@ -4607,11 +4573,10 @@ ExpectedDecl ASTNodeImporter::VisitFriendDecl(FriendDecl *D) {
   // We try to maintain order and count of redundant friend declarations.
   const auto *RD = cast<CXXRecordDecl>(DC);
   SmallVector<FriendDecl *, 2> ImportedEquivalentFriends;
-  for (FriendDecl *ImportedFriend : RD->friends()) {
+  for (FriendDecl *ImportedFriend : RD->friends())
     if (ImportedFriend->getKind() == Decl::Friend &&
         IsEquivalentFriend(Importer, D, ImportedFriend))
       ImportedEquivalentFriends.push_back(ImportedFriend);
-  }
 
   FriendCountAndPosition CountAndPosition =
       getFriendCountAndPosition(Importer, D);
diff --git a/clang/lib/AST/ASTStructuralEquivalence.cpp b/clang/lib/AST/ASTStructuralEquivalence.cpp
index 82baed3e31cc0..db0ed0e3662a6 100644
--- a/clang/lib/AST/ASTStructuralEquivalence.cpp
+++ b/clang/lib/AST/ASTStructuralEquivalence.cpp
@@ -2442,7 +2442,7 @@ static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
     return false;
 
   for (unsigned I = 0, N = TPL1.size(); I != N; ++I)
-    if (!Context.IsEquivalent(TPL1[I], TPL2[I]))
+    if (!IsStructurallyEquivalent(Context, TPL1[I], TPL2[I]))
       return false;
 
   TemplateName TN1 = FTD1->getFriendTemplateName();
@@ -2810,18 +2810,19 @@ bool StructuralEquivalenceContext::CheckCommonEquivalence(Decl *D1, Decl *D2) {
 
 bool StructuralEquivalenceContext::IsEquivalent(TemplateParameterList *TPL1,
                                                 TemplateParameterList *TPL2) {
+  assert(DeclsToCheck.empty());
+  assert(VisitedDecls.empty());
+
   if (TPL1 == TPL2)
     return true;
 
-  if (!TPL1 || !TPL2 || TPL1->size() != TPL2->size())
+  if (!TPL1 || !TPL2)
     return false;
 
-  for (unsigned I = 0, N = TPL1->size(); I != N; ++I) {
-    if (!IsEquivalent(TPL1->getParam(I), TPL2->getParam(I)))
-      return false;
-  }
+  if (!::IsStructurallyEquivalent(*this, TPL1, TPL2))
+    return false;
 
-  return IsEquivalent(TPL1->getRequiresClause(), TPL2->getRequiresClause());
+  return !Finish();
 }
 
 bool StructuralEquivalenceContext::CheckKindSpecificEquivalence(

>From 62144c3597a28508a42a4239902693df7427b07f Mon Sep 17 00:00:00 2001
From: Oleksandr Tarasiuk <oleksandr.tarasiuk at outlook.com>
Date: Tue, 26 May 2026 11:57:48 +0300
Subject: [PATCH 38/38] add templated friend visitor to decl printer

---
 clang/lib/AST/DeclPrinter.cpp | 19 +++++++++++++++++++
 clang/lib/AST/ODRHash.cpp     |  3 +++
 2 files changed, 22 insertions(+)

diff --git a/clang/lib/AST/DeclPrinter.cpp b/clang/lib/AST/DeclPrinter.cpp
index b84745382a976..7980dd38211d8 100644
--- a/clang/lib/AST/DeclPrinter.cpp
+++ b/clang/lib/AST/DeclPrinter.cpp
@@ -70,6 +70,7 @@ namespace {
     void VisitEmptyDecl(EmptyDecl *D);
     void VisitFunctionDecl(FunctionDecl *D);
     void VisitFriendDecl(FriendDecl *D);
+    void VisitFriendTemplateDecl(FriendTemplateDecl *D);
     void VisitFieldDecl(FieldDecl *D);
     void VisitVarDecl(VarDecl *D);
     void VisitLabelDecl(LabelDecl *D);
@@ -907,6 +908,24 @@ void DeclPrinter::VisitFriendDecl(FriendDecl *D) {
     Out << "...";
 }
 
+void DeclPrinter::VisitFriendTemplateDecl(FriendTemplateDecl *D) {
+  for (TemplateParameterList *TPL : D->getFriendTypeTemplateParameterLists())
+    printTemplateParameters(TPL);
+
+  TemplateName TN = D->getFriendTemplateName();
+  if (TN.isNull()) {
+    VisitFriendDecl(D);
+  } else {
+    Out << "friend ";
+    TN.print(Out, Policy,
+             Policy.SuppressScope ? TemplateName::Qualified::None
+                                  : TemplateName::Qualified::AsWritten);
+
+    if (D->isPackExpansion())
+      Out << "...";
+  }
+}
+
 void DeclPrinter::VisitFieldDecl(FieldDecl *D) {
   prettyPrintPragmas(D);
   // FIXME: add printing of pragma attributes if required.
diff --git a/clang/lib/AST/ODRHash.cpp b/clang/lib/AST/ODRHash.cpp
index a22ab006640f3..b85204083cca9 100644
--- a/clang/lib/AST/ODRHash.cpp
+++ b/clang/lib/AST/ODRHash.cpp
@@ -476,6 +476,9 @@ class ODRDeclVisitor : public ConstDeclVisitor<ODRDeclVisitor> {
   }
 
   void VisitFriendTemplateDecl(const FriendTemplateDecl *D) {
+    for (TemplateParameterList *TPL : D->getFriendTypeTemplateParameterLists())
+      Hash.AddTemplateParameterList(TPL);
+
     TemplateName TN = D->getFriendTemplateName();
     Hash.AddBoolean(TN.isNull());
     if (TN.isNull()) {



More information about the cfe-commits mailing list