[clang] [clang-tools-extra] [Clang][AST] Introduce `ExplicitInstantiationDecl` to preserve source info and fix diagnostic locations (PR #191658)

via cfe-commits cfe-commits at lists.llvm.org
Mon Apr 13 00:46:58 PDT 2026


https://github.com/16bit-ykiko updated https://github.com/llvm/llvm-project/pull/191658

>From 15a609e9782ee3637244f0c6677d19eeda5a7b1b Mon Sep 17 00:00:00 2001
From: ykiko <ykikoykikoykiko at gmail.com>
Date: Sun, 12 Apr 2026 03:02:08 +0800
Subject: [PATCH 01/15] [clang] Add ExplicitInstantiationDecl to preserve
 source info for explicit template instantiations

---
 clang/include/clang/AST/ASTNodeTraverser.h    |  11 ++
 clang/include/clang/AST/DeclTemplate.h        | 116 ++++++++++++
 clang/include/clang/AST/JSONNodeDumper.h      |   1 +
 clang/include/clang/AST/RecursiveASTVisitor.h |  16 +-
 clang/include/clang/AST/TextNodeDumper.h      |   1 +
 clang/include/clang/Basic/DeclNodes.td        |   1 +
 .../include/clang/Serialization/ASTBitCodes.h |   5 +-
 clang/lib/AST/DeclTemplate.cpp                |  20 +++
 clang/lib/AST/JSONNodeDumper.cpp              |  26 +++
 clang/lib/AST/TextNodeDumper.cpp              |  14 ++
 clang/lib/CIR/CodeGen/CIRGenDecl.cpp          |   1 +
 clang/lib/CIR/CodeGen/CIRGenModule.cpp        |   1 +
 clang/lib/CodeGen/CGDecl.cpp                  |   1 +
 clang/lib/CodeGen/CodeGenModule.cpp           |   1 +
 clang/lib/Sema/SemaTemplate.cpp               | 102 +++++++++--
 .../lib/Sema/SemaTemplateInstantiateDecl.cpp  |   8 +
 clang/lib/Serialization/ASTCommon.cpp         |   1 +
 clang/lib/Serialization/ASTReaderDecl.cpp     |  20 +++
 clang/lib/Serialization/ASTWriterDecl.cpp     |  18 ++
 clang/lib/Tooling/Syntax/BuildTree.cpp        |   7 +
 clang/test/AST/ast-dump-templates-pattern.cpp |  32 +++-
 clang/test/AST/ast-dump-templates.cpp         |  62 +++++--
 .../explicit-instantiation-source-info.cpp    | 166 ++++++++++++++++++
 .../explicit-instantiation-diag-location.cpp  |  31 ++++
 clang/tools/libclang/CIndex.cpp               |   2 +
 25 files changed, 628 insertions(+), 36 deletions(-)
 create mode 100644 clang/test/AST/explicit-instantiation-source-info.cpp
 create mode 100644 clang/test/SemaTemplate/explicit-instantiation-diag-location.cpp

diff --git a/clang/include/clang/AST/ASTNodeTraverser.h b/clang/include/clang/AST/ASTNodeTraverser.h
index 3be24ff868c2d..39a6864bf169e 100644
--- a/clang/include/clang/AST/ASTNodeTraverser.h
+++ b/clang/include/clang/AST/ASTNodeTraverser.h
@@ -683,6 +683,17 @@ class ASTNodeTraverser
     Visit(D->getMessage());
   }
 
+  void VisitExplicitInstantiationDecl(const ExplicitInstantiationDecl *D) {
+    // The specialization is already elsewhere in the AST; don't re-traverse it.
+    // Traverse source-location sub-nodes: template arguments and type-as-written.
+    if (const auto *ArgsAsWritten = D->getTemplateArgsAsWritten())
+      for (unsigned I = 0, E = ArgsAsWritten->NumTemplateArgs; I != E; ++I)
+        Visit((*ArgsAsWritten)[I].getArgument(),
+              (*ArgsAsWritten)[I].getSourceRange());
+    if (TypeSourceInfo *TSI = D->getTypeAsWritten())
+      Visit(TSI->getTypeLoc());
+  }
+
   void VisitFunctionTemplateDecl(const FunctionTemplateDecl *D) {
     dumpTemplateDecl(D);
   }
diff --git a/clang/include/clang/AST/DeclTemplate.h b/clang/include/clang/AST/DeclTemplate.h
index a4a1bb9c13c79..b470b520531f1 100644
--- a/clang/include/clang/AST/DeclTemplate.h
+++ b/clang/include/clang/AST/DeclTemplate.h
@@ -3405,6 +3405,122 @@ getReplacedTemplateParameter(Decl *D, unsigned Index);
 /// If we have an implicit instantiation, adjust 'D' to refer to template.
 const Decl &adjustDeclToTemplate(const Decl &D);
 
+/// Represents an explicit instantiation of a template entity in source code.
+///
+/// This node records source location information for an explicit instantiation
+/// statement. It does not participate in name lookup (inherits from Decl, not
+/// NamedDecl), and does not affect code generation. The underlying
+/// specialization decl (FunctionDecl, VarDecl, CXXRecordDecl, etc.) continues
+/// to handle all semantic and codegen responsibilities.
+///
+/// \code
+///   template void ns::foo<int>(int);        // function template
+///   extern template struct ns::S<int>;      // class template (extern)
+///   template int ns::bar<int>;              // variable template
+///   template void ns::S<int>::method(int);  // member function
+/// \endcode
+class ExplicitInstantiationDecl : public Decl {
+  /// The underlying specialization being explicitly instantiated.
+  NamedDecl *Specialization = nullptr;
+
+  /// The source range of the entire explicit instantiation statement.
+  SourceRange Range;
+
+  /// Location of the 'extern' keyword (invalid if not extern template).
+  SourceLocation ExternLoc;
+
+  /// Location of the struct/class/union keyword (for class template and
+  /// nested class instantiations; invalid otherwise).
+  SourceLocation TagKWLoc;
+
+  /// Nested name specifier with source locations (e.g., ns::S<int>::).
+  NestedNameSpecifierLoc QualifierLoc;
+
+  /// Template arguments as written (e.g., <int>). Null if the template
+  /// arguments were deduced.
+  const ASTTemplateArgumentListInfo *TemplateArgsAsWritten = nullptr;
+
+  /// Location of the entity name (e.g., 'foo' in 'template void ns::foo<int>(int)').
+  SourceLocation NameLoc;
+
+  /// Type source info for the declaration type:
+  ///   - Function templates / member functions: FunctionProtoTypeLoc with
+  ///     return type location and parameter type locations.
+  ///   - Variable templates / static data members: the declared type.
+  ///   - Class templates / nested classes: null.
+  TypeSourceInfo *TypeAsWritten = nullptr;
+
+  /// Whether this is a declaration (extern template) or definition (template).
+  LLVM_PREFERRED_TYPE(TemplateSpecializationKind)
+  unsigned TSK : 3;
+
+  ExplicitInstantiationDecl(DeclContext *DC, SourceRange Range,
+                            NamedDecl *Specialization,
+                            SourceLocation ExternLoc,
+                            SourceLocation TemplateLoc,
+                            SourceLocation TagKWLoc,
+                            NestedNameSpecifierLoc QualifierLoc,
+                            const ASTTemplateArgumentListInfo *ArgsAsWritten,
+                            SourceLocation NameLoc,
+                            TypeSourceInfo *TypeAsWritten,
+                            TemplateSpecializationKind TSK)
+      : Decl(ExplicitInstantiation, DC, TemplateLoc),
+        Specialization(Specialization), Range(Range), ExternLoc(ExternLoc),
+        TagKWLoc(TagKWLoc), QualifierLoc(QualifierLoc),
+        TemplateArgsAsWritten(ArgsAsWritten), NameLoc(NameLoc),
+        TypeAsWritten(TypeAsWritten), TSK(TSK) {
+    assert((TSK == TSK_ExplicitInstantiationDeclaration) == ExternLoc.isValid()
+           && "ExternLoc should be valid iff TSK is a declaration");
+  }
+
+  ExplicitInstantiationDecl(EmptyShell Empty)
+      : Decl(ExplicitInstantiation, Empty), TSK(TSK_Undeclared) {}
+
+  virtual void anchor();
+
+public:
+  friend class ASTDeclReader;
+  friend class ASTDeclWriter;
+
+  static ExplicitInstantiationDecl *
+  Create(ASTContext &C, DeclContext *DC, SourceRange Range,
+         NamedDecl *Specialization, SourceLocation ExternLoc,
+         SourceLocation TemplateLoc, SourceLocation TagKWLoc,
+         NestedNameSpecifierLoc QualifierLoc,
+         const ASTTemplateArgumentListInfo *ArgsAsWritten,
+         SourceLocation NameLoc, TypeSourceInfo *TypeAsWritten,
+         TemplateSpecializationKind TSK);
+
+  static ExplicitInstantiationDecl *CreateDeserialized(ASTContext &C,
+                                                       GlobalDeclID ID);
+
+  NamedDecl *getSpecialization() const { return Specialization; }
+
+  SourceRange getSourceRange() const override LLVM_READONLY { return Range; }
+
+  SourceLocation getExternLoc() const { return ExternLoc; }
+  SourceLocation getTemplateLoc() const { return getLocation(); }
+  SourceLocation getTagKWLoc() const { return TagKWLoc; }
+  SourceLocation getNameLoc() const { return NameLoc; }
+
+  NestedNameSpecifierLoc getQualifierLoc() const { return QualifierLoc; }
+
+  const ASTTemplateArgumentListInfo *getTemplateArgsAsWritten() const {
+    return TemplateArgsAsWritten;
+  }
+
+  TypeSourceInfo *getTypeAsWritten() const { return TypeAsWritten; }
+
+  TemplateSpecializationKind getTemplateSpecializationKind() const {
+    return static_cast<TemplateSpecializationKind>(TSK);
+  }
+
+  bool isExternTemplate() const { return ExternLoc.isValid(); }
+
+  static bool classof(const Decl *D) { return classofKind(D->getKind()); }
+  static bool classofKind(Kind K) { return K == ExplicitInstantiation; }
+};
+
 } // namespace clang
 
 #endif // LLVM_CLANG_AST_DECLTEMPLATE_H
diff --git a/clang/include/clang/AST/JSONNodeDumper.h b/clang/include/clang/AST/JSONNodeDumper.h
index 69dbdbbdb3ecd..4e8d1649bbf8b 100644
--- a/clang/include/clang/AST/JSONNodeDumper.h
+++ b/clang/include/clang/AST/JSONNodeDumper.h
@@ -268,6 +268,7 @@ class JSONNodeDumper
   void VisitLinkageSpecDecl(const LinkageSpecDecl *LSD);
   void VisitAccessSpecDecl(const AccessSpecDecl *ASD);
   void VisitFriendDecl(const FriendDecl *FD);
+  void VisitExplicitInstantiationDecl(const ExplicitInstantiationDecl *D);
 
   void VisitObjCIvarDecl(const ObjCIvarDecl *D);
   void VisitObjCMethodDecl(const ObjCMethodDecl *D);
diff --git a/clang/include/clang/AST/RecursiveASTVisitor.h b/clang/include/clang/AST/RecursiveASTVisitor.h
index ce6ad723191e0..37bcfb5b2ed76 100644
--- a/clang/include/clang/AST/RecursiveASTVisitor.h
+++ b/clang/include/clang/AST/RecursiveASTVisitor.h
@@ -1748,6 +1748,16 @@ DEF_TRAVERSE_DECL(StaticAssertDecl, {
   TRY_TO(TraverseStmt(D->getMessage()));
 })
 
+DEF_TRAVERSE_DECL(ExplicitInstantiationDecl, {
+  if (D->getQualifierLoc())
+    TRY_TO(TraverseNestedNameSpecifierLoc(D->getQualifierLoc()));
+  if (const auto *ArgsAsWritten = D->getTemplateArgsAsWritten())
+    for (unsigned I = 0, E = ArgsAsWritten->NumTemplateArgs; I != E; ++I)
+      TRY_TO(TraverseTemplateArgumentLoc((*ArgsAsWritten)[I]));
+  if (TypeSourceInfo *TSI = D->getTypeAsWritten())
+    TRY_TO(TraverseTypeLoc(TSI->getTypeLoc()));
+})
+
 DEF_TRAVERSE_DECL(TranslationUnitDecl, {
   // Code in an unnamed namespace shows up automatically in
   // decls_begin()/decls_end().  Thus we don't need to recurse on
@@ -2027,8 +2037,10 @@ bool RecursiveASTVisitor<Derived>::TraverseTemplateInstantiations(
         TRY_TO(TraverseDecl(RD));
         break;
 
-      // FIXME: For now traverse explicit instantiations here. Change that
-      // once they are represented as dedicated nodes in the AST.
+      // Unlike class/variable template specializations, function template
+      // specializations are not independent children of the DeclContext —
+      // they are only reachable via FunctionTemplateDecl::specializations().
+      // We must traverse them here so visitors can see the instantiated body.
       case TSK_ExplicitInstantiationDeclaration:
       case TSK_ExplicitInstantiationDefinition:
         TRY_TO(TraverseDecl(RD));
diff --git a/clang/include/clang/AST/TextNodeDumper.h b/clang/include/clang/AST/TextNodeDumper.h
index 32e83ebb5c8eb..6b7ac6fac8111 100644
--- a/clang/include/clang/AST/TextNodeDumper.h
+++ b/clang/include/clang/AST/TextNodeDumper.h
@@ -395,6 +395,7 @@ class TextNodeDumper
   void VisitLinkageSpecDecl(const LinkageSpecDecl *D);
   void VisitAccessSpecDecl(const AccessSpecDecl *D);
   void VisitFriendDecl(const FriendDecl *D);
+  void VisitExplicitInstantiationDecl(const ExplicitInstantiationDecl *D);
   void VisitObjCIvarDecl(const ObjCIvarDecl *D);
   void VisitObjCMethodDecl(const ObjCMethodDecl *D);
   void VisitObjCTypeParamDecl(const ObjCTypeParamDecl *D);
diff --git a/clang/include/clang/Basic/DeclNodes.td b/clang/include/clang/Basic/DeclNodes.td
index 04311055bb600..ffb58b43812dc 100644
--- a/clang/include/clang/Basic/DeclNodes.td
+++ b/clang/include/clang/Basic/DeclNodes.td
@@ -101,6 +101,7 @@ def AccessSpec : DeclNode<Decl>;
 def Friend : DeclNode<Decl>;
 def FriendTemplate : DeclNode<Decl>;
 def StaticAssert : DeclNode<Decl>;
+def ExplicitInstantiation : DeclNode<Decl>;
 def Block : DeclNode<Decl, "blocks">, DeclContext;
 def OutlinedFunction : DeclNode<Decl>, DeclContext;
 def Captured : DeclNode<Decl>, DeclContext;
diff --git a/clang/include/clang/Serialization/ASTBitCodes.h b/clang/include/clang/Serialization/ASTBitCodes.h
index 783cd82895a90..499bd772ae126 100644
--- a/clang/include/clang/Serialization/ASTBitCodes.h
+++ b/clang/include/clang/Serialization/ASTBitCodes.h
@@ -1543,7 +1543,10 @@ enum DeclCode {
   // An OpenACCRoutineDecl record.
   DECL_OPENACC_ROUTINE,
 
-  DECL_LAST = DECL_OPENACC_ROUTINE
+  /// An ExplicitInstantiationDecl record.
+  DECL_EXPLICIT_INSTANTIATION,
+
+  DECL_LAST = DECL_EXPLICIT_INSTANTIATION
 };
 
 /// Record codes for each kind of statement or expression.
diff --git a/clang/lib/AST/DeclTemplate.cpp b/clang/lib/AST/DeclTemplate.cpp
index 99d02fdc99e92..76e3de5394d86 100644
--- a/clang/lib/AST/DeclTemplate.cpp
+++ b/clang/lib/AST/DeclTemplate.cpp
@@ -1784,3 +1784,23 @@ const Decl &clang::adjustDeclToTemplate(const Decl &D) {
   // FIXME: Adjust alias templates?
   return D;
 }
+
+void ExplicitInstantiationDecl::anchor() {}
+
+ExplicitInstantiationDecl *ExplicitInstantiationDecl::Create(
+    ASTContext &C, DeclContext *DC, SourceRange Range,
+    NamedDecl *Specialization, SourceLocation ExternLoc,
+    SourceLocation TemplateLoc, SourceLocation TagKWLoc,
+    NestedNameSpecifierLoc QualifierLoc,
+    const ASTTemplateArgumentListInfo *ArgsAsWritten,
+    SourceLocation NameLoc, TypeSourceInfo *TypeAsWritten,
+    TemplateSpecializationKind TSK) {
+  return new (C, DC) ExplicitInstantiationDecl(
+      DC, Range, Specialization, ExternLoc, TemplateLoc, TagKWLoc,
+      QualifierLoc, ArgsAsWritten, NameLoc, TypeAsWritten, TSK);
+}
+
+ExplicitInstantiationDecl *
+ExplicitInstantiationDecl::CreateDeserialized(ASTContext &C, GlobalDeclID ID) {
+  return new (C, ID) ExplicitInstantiationDecl(EmptyShell());
+}
diff --git a/clang/lib/AST/JSONNodeDumper.cpp b/clang/lib/AST/JSONNodeDumper.cpp
index 3138f95e6a83b..8373dd8e373e0 100644
--- a/clang/lib/AST/JSONNodeDumper.cpp
+++ b/clang/lib/AST/JSONNodeDumper.cpp
@@ -1119,6 +1119,32 @@ void JSONNodeDumper::VisitAccessSpecDecl(const AccessSpecDecl *ASD) {
   JOS.attribute("access", createAccessSpecifier(ASD->getAccess()));
 }
 
+void JSONNodeDumper::VisitExplicitInstantiationDecl(
+    const ExplicitInstantiationDecl *D) {
+  attributeOnlyIfTrue("isExternTemplate", D->isExternTemplate());
+  if (D->getSpecialization())
+    JOS.attribute("specializationDeclId",
+                  createPointerRepresentation(D->getSpecialization()));
+  switch (D->getTemplateSpecializationKind()) {
+  case TSK_Undeclared:
+    break;
+  case TSK_ImplicitInstantiation:
+    JOS.attribute("templateSpecializationKind", "implicit_instantiation");
+    break;
+  case TSK_ExplicitSpecialization:
+    JOS.attribute("templateSpecializationKind", "explicit_specialization");
+    break;
+  case TSK_ExplicitInstantiationDeclaration:
+    JOS.attribute("templateSpecializationKind",
+                  "explicit_instantiation_declaration");
+    break;
+  case TSK_ExplicitInstantiationDefinition:
+    JOS.attribute("templateSpecializationKind",
+                  "explicit_instantiation_definition");
+    break;
+  }
+}
+
 void JSONNodeDumper::VisitFriendDecl(const FriendDecl *FD) {
   if (const TypeSourceInfo *T = FD->getFriendType())
     JOS.attribute("type", createQualType(T->getType()));
diff --git a/clang/lib/AST/TextNodeDumper.cpp b/clang/lib/AST/TextNodeDumper.cpp
index 250ec8b666e05..108d77527f276 100644
--- a/clang/lib/AST/TextNodeDumper.cpp
+++ b/clang/lib/AST/TextNodeDumper.cpp
@@ -2936,6 +2936,20 @@ void TextNodeDumper::VisitAccessSpecDecl(const AccessSpecDecl *D) {
   dumpAccessSpecifier(D->getAccess());
 }
 
+void TextNodeDumper::VisitExplicitInstantiationDecl(
+    const ExplicitInstantiationDecl *D) {
+  dumpTemplateSpecializationKind(D->getTemplateSpecializationKind());
+  if (D->isExternTemplate())
+    OS << " extern";
+  OS << " template";
+  if (D->getQualifierLoc())
+    dumpNestedNameSpecifier(D->getQualifierLoc().getNestedNameSpecifier());
+  if (const NamedDecl *Spec = D->getSpecialization()) {
+    OS << " '" << Spec->getDeclName() << "'";
+    dumpDeclRef(Spec);
+  }
+}
+
 void TextNodeDumper::VisitFriendDecl(const FriendDecl *D) {
   if (TypeSourceInfo *T = D->getFriendType())
     dumpType(T->getType());
diff --git a/clang/lib/CIR/CodeGen/CIRGenDecl.cpp b/clang/lib/CIR/CodeGen/CIRGenDecl.cpp
index b96b822609c10..3e2d4035fef15 100644
--- a/clang/lib/CIR/CodeGen/CIRGenDecl.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenDecl.cpp
@@ -831,6 +831,7 @@ void CIRGenFunction::emitDecl(const Decl &d, bool evaluateConditionDecl) {
 
   case Decl::Function:     // void X();
   case Decl::EnumConstant: // enum ? { X = ? }
+  case Decl::ExplicitInstantiation:
   case Decl::StaticAssert: // static_assert(X, ""); [C++0x]
   case Decl::Label:        // __label__ x;
   case Decl::Import:
diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.cpp b/clang/lib/CIR/CodeGen/CIRGenModule.cpp
index 2037b92e2b5d1..abe5834af46ee 100644
--- a/clang/lib/CIR/CodeGen/CIRGenModule.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenModule.cpp
@@ -2031,6 +2031,7 @@ void CIRGenModule::emitTopLevelDecl(Decl *decl) {
   case Decl::Concept:
   case Decl::CXXDeductionGuide:
   case Decl::Empty:
+  case Decl::ExplicitInstantiation:
   case Decl::FunctionTemplate:
   case Decl::StaticAssert:
   case Decl::TypeAliasTemplate:
diff --git a/clang/lib/CodeGen/CGDecl.cpp b/clang/lib/CodeGen/CGDecl.cpp
index 748362105cb02..419b3c477e7b2 100644
--- a/clang/lib/CodeGen/CGDecl.cpp
+++ b/clang/lib/CodeGen/CGDecl.cpp
@@ -125,6 +125,7 @@ void CodeGenFunction::EmitDecl(const Decl &D, bool EvaluateConditionDecl) {
   case Decl::Function:     // void X();
   case Decl::EnumConstant: // enum ? { X = ? }
   case Decl::StaticAssert: // static_assert(X, ""); [C++0x]
+  case Decl::ExplicitInstantiation:
   case Decl::Label:        // __label__ x;
   case Decl::Import:
   case Decl::MSGuid:    // __declspec(uuid("..."))
diff --git a/clang/lib/CodeGen/CodeGenModule.cpp b/clang/lib/CodeGen/CodeGenModule.cpp
index 68a403e02968d..6a1dcfd55f4f4 100644
--- a/clang/lib/CodeGen/CodeGenModule.cpp
+++ b/clang/lib/CodeGen/CodeGenModule.cpp
@@ -7777,6 +7777,7 @@ void CodeGenModule::EmitTopLevelDecl(Decl *D) {
     break;
 
   case Decl::StaticAssert:
+  case Decl::ExplicitInstantiation:
     // Nothing to do.
     break;
 
diff --git a/clang/lib/Sema/SemaTemplate.cpp b/clang/lib/Sema/SemaTemplate.cpp
index c436b7018a2bd..66fc2b6fc81e3 100644
--- a/clang/lib/Sema/SemaTemplate.cpp
+++ b/clang/lib/Sema/SemaTemplate.cpp
@@ -9314,10 +9314,41 @@ static void StripImplicitInstantiation(NamedDecl *D, bool MinGW) {
     FD->setInlineSpecified(false);
 }
 
+/// Create an ExplicitInstantiationDecl to record source-location info for an
+/// explicit template instantiation statement, and add it to \p CurContext.
+static void addExplicitInstantiationDecl(
+    ASTContext &Context, DeclContext *CurContext, const CXXScopeSpec &SS,
+    SourceLocation EndLoc, NamedDecl *Spec, SourceLocation ExternLoc,
+    SourceLocation TemplateLoc, SourceLocation TagKWLoc,
+    const ASTTemplateArgumentListInfo *ArgsAsWritten,
+    SourceLocation NameLoc, TypeSourceInfo *TypeAsWritten,
+    TemplateSpecializationKind TSK) {
+  NestedNameSpecifierLoc QualifierLoc;
+  if (SS.isNotEmpty())
+    QualifierLoc = SS.getWithLocInContext(Context);
+  SourceLocation BeginLoc = ExternLoc.isValid() ? ExternLoc : TemplateLoc;
+  auto *EID = ExplicitInstantiationDecl::Create(
+      Context, CurContext, SourceRange(BeginLoc, EndLoc), Spec, ExternLoc,
+      TemplateLoc, TagKWLoc, QualifierLoc, ArgsAsWritten, NameLoc,
+      TypeAsWritten, TSK);
+  CurContext->addDecl(EID);
+}
+
 /// Compute the diagnostic location for an explicit instantiation
 //  declaration or definition.
 static SourceLocation DiagLocForExplicitInstantiation(
     NamedDecl* D, SourceLocation PointOfInstantiation) {
+  // Search for an ExplicitInstantiationDecl that references D. Per
+  // [temp.explicit], explicit instantiations must appear in an enclosing
+  // namespace of the template, so the EID is in D's DeclContext or an ancestor.
+  for (DeclContext *DC = D->getDeclContext(); DC; DC = DC->getParent()) {
+    for (auto *Decl : DC->decls()) {
+      if (auto *EID = dyn_cast<ExplicitInstantiationDecl>(Decl))
+        if (EID->getSpecialization() == D)
+          return EID->getTemplateLoc();
+    }
+  }
+
   // Explicit instantiations following a specialization have no effect and
   // hence no PointOfInstantiation. In that case, walk decl backwards
   // until a valid name loc is found.
@@ -10406,6 +10437,11 @@ DeclResult Sema::ActOnExplicitInstantiation(
   if (HasNoEffect) {
     // Set the template specialization kind.
     Specialization->setTemplateSpecializationKind(TSK);
+
+    addExplicitInstantiationDecl(
+        Context, CurContext, SS, RAngleLoc, Specialization, ExternLoc,
+        TemplateLoc, KWLoc, Specialization->getTemplateArgsAsWritten(),
+        TemplateNameLoc, nullptr, TSK);
     return Specialization;
   }
 
@@ -10495,6 +10531,10 @@ DeclResult Sema::ActOnExplicitInstantiation(
     Specialization->setTemplateSpecializationKind(TSK);
   }
 
+  addExplicitInstantiationDecl(
+      Context, CurContext, SS, RAngleLoc, Specialization, ExternLoc,
+      TemplateLoc, KWLoc, Specialization->getTemplateArgsAsWritten(),
+      TemplateNameLoc, nullptr, TSK);
   return Specialization;
 }
 
@@ -10569,8 +10609,12 @@ Sema::ActOnExplicitInstantiation(Scope *S, SourceLocation ExternLoc,
                                              MSInfo->getPointOfInstantiation(),
                                                HasNoEffect))
       return true;
-    if (HasNoEffect)
+    if (HasNoEffect) {
+      addExplicitInstantiationDecl(Context, CurContext, SS, NameLoc, Record,
+                                   ExternLoc, TemplateLoc, KWLoc, nullptr,
+                                   NameLoc, nullptr, TSK);
       return TagD;
+    }
   }
 
   CXXRecordDecl *RecordDef
@@ -10606,10 +10650,9 @@ Sema::ActOnExplicitInstantiation(Scope *S, SourceLocation ExternLoc,
   if (TSK == TSK_ExplicitInstantiationDefinition)
     MarkVTableUsed(NameLoc, RecordDef, true);
 
-  // FIXME: We don't have any representation for explicit instantiations of
-  // member classes. Such a representation is not needed for compilation, but it
-  // should be available for clients that want to see all of the declarations in
-  // the source code.
+  addExplicitInstantiationDecl(Context, CurContext, SS, NameLoc, Record,
+                               ExternLoc, TemplateLoc, KWLoc, nullptr,
+                               NameLoc, nullptr, TSK);
   return TagD;
 }
 
@@ -10803,7 +10846,7 @@ DeclResult Sema::ActOnExplicitInstantiation(Scope *S,
     if (!HasNoEffect) {
       // Instantiate static data member or variable template.
       Prev->setTemplateSpecializationKind(TSK, D.getIdentifierLoc());
-      if (auto *VTSD = dyn_cast<VarTemplatePartialSpecializationDecl>(Prev)) {
+      if (auto *VTSD = dyn_cast<VarTemplateSpecializationDecl>(Prev)) {
         VTSD->setExternKeywordLoc(ExternLoc);
         VTSD->setTemplateKeywordLoc(TemplateLoc);
       }
@@ -10827,8 +10870,18 @@ DeclResult Sema::ActOnExplicitInstantiation(Scope *S,
       return true;
     }
 
-    // FIXME: Create an ExplicitInstantiation node?
-    return (Decl*) nullptr;
+    {
+      const ASTTemplateArgumentListInfo *ArgsAsWritten = nullptr;
+      if (auto *VTSD = dyn_cast<VarTemplateSpecializationDecl>(Prev))
+        ArgsAsWritten = VTSD->getTemplateArgsAsWritten();
+      addExplicitInstantiationDecl(
+          Context, CurContext, D.getCXXScopeSpec(),
+          D.getSourceRange().getEnd(), Prev, ExternLoc, TemplateLoc,
+          SourceLocation(), ArgsAsWritten, D.getIdentifierLoc(), T, TSK);
+      // Don't return the EID to the Parser — doing so would trigger
+      // unrelated semantic actions (e.g. access checks via FinalizeDeclaration).
+      return (Decl *)nullptr;
+    }
   }
 
   // If the declarator is a template-id, translate the parser's template
@@ -11005,10 +11058,19 @@ DeclResult Sema::ActOnExplicitInstantiation(Scope *S,
                                                HasNoEffect))
       return true;
 
-    // FIXME: We may still want to build some representation of this
-    // explicit specialization.
-    if (HasNoEffect)
-      return (Decl*) nullptr;
+    if (HasNoEffect) {
+      const ASTTemplateArgumentListInfo *ArgsAsWritten = nullptr;
+      if (HasExplicitTemplateArgs)
+        ArgsAsWritten =
+            ASTTemplateArgumentListInfo::Create(Context, TemplateArgs);
+      addExplicitInstantiationDecl(
+          Context, CurContext, D.getCXXScopeSpec(),
+          D.getSourceRange().getEnd(), Specialization, ExternLoc, TemplateLoc,
+          SourceLocation(), ArgsAsWritten, D.getIdentifierLoc(), T, TSK);
+      // Don't return the EID to the Parser — doing so would trigger
+      // unrelated semantic actions (e.g. access checks via FinalizeDeclaration).
+      return (Decl *)nullptr;
+    }
   }
 
   // HACK: libc++ has a bug where it attempts to explicitly instantiate the
@@ -11036,7 +11098,6 @@ DeclResult Sema::ActOnExplicitInstantiation(Scope *S,
     TSK = TSK_ExplicitInstantiationDeclaration;
 
   Specialization->setTemplateSpecializationKind(TSK, D.getIdentifierLoc());
-
   if (Specialization->isDefined()) {
     // Let the ASTConsumer know that this function has been explicitly
     // instantiated now, and its linkage might have changed.
@@ -11065,8 +11126,19 @@ DeclResult Sema::ActOnExplicitInstantiation(Scope *S,
               : Specialization->getInstantiatedFromMemberFunction(),
       D.getIdentifierLoc(), D.getCXXScopeSpec().isSet(), TSK);
 
-  // FIXME: Create some kind of ExplicitInstantiationDecl here.
-  return (Decl*) nullptr;
+  {
+    const ASTTemplateArgumentListInfo *ArgsAsWritten = nullptr;
+    if (HasExplicitTemplateArgs)
+      ArgsAsWritten =
+          ASTTemplateArgumentListInfo::Create(Context, TemplateArgs);
+    addExplicitInstantiationDecl(
+        Context, CurContext, D.getCXXScopeSpec(),
+        D.getSourceRange().getEnd(), Specialization, ExternLoc, TemplateLoc,
+        SourceLocation(), ArgsAsWritten, D.getIdentifierLoc(), T, TSK);
+    // Don't return the EID to the Parser — doing so would trigger
+    // unrelated semantic actions (e.g. access checks via FinalizeDeclaration).
+    return (Decl *)nullptr;
+  }
 }
 
 TypeResult Sema::ActOnDependentTag(Scope *S, unsigned TagSpec, TagUseKind TUK,
diff --git a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
index 09c2482168ab7..f307e813ff27a 100644
--- a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
+++ b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
@@ -2101,6 +2101,14 @@ Decl *TemplateDeclInstantiator::VisitStaticAssertDecl(StaticAssertDecl *D) {
       InstantiatedMessageExpr.get(), D->getRParenLoc(), D->isFailed());
 }
 
+Decl *TemplateDeclInstantiator::VisitExplicitInstantiationDecl(
+    ExplicitInstantiationDecl *D) {
+  // ExplicitInstantiationDecl is a source-info-only node and should not
+  // appear inside a template pattern. Nothing to instantiate.
+  llvm_unreachable("ExplicitInstantiationDecl should not be instantiated");
+  return nullptr;
+}
+
 Decl *TemplateDeclInstantiator::VisitEnumDecl(EnumDecl *D) {
   EnumDecl *PrevDecl = nullptr;
   if (EnumDecl *PatternPrev = getPreviousDeclForInstantiation(D)) {
diff --git a/clang/lib/Serialization/ASTCommon.cpp b/clang/lib/Serialization/ASTCommon.cpp
index 69db02f2efc40..089da7e082317 100644
--- a/clang/lib/Serialization/ASTCommon.cpp
+++ b/clang/lib/Serialization/ASTCommon.cpp
@@ -436,6 +436,7 @@ bool serialization::isRedeclarableDeclKind(unsigned Kind) {
   case Decl::Friend:
   case Decl::FriendTemplate:
   case Decl::StaticAssert:
+  case Decl::ExplicitInstantiation:
   case Decl::Block:
   case Decl::OutlinedFunction:
   case Decl::Captured:
diff --git a/clang/lib/Serialization/ASTReaderDecl.cpp b/clang/lib/Serialization/ASTReaderDecl.cpp
index 9033ea55bc5e2..eb13e15b92f14 100644
--- a/clang/lib/Serialization/ASTReaderDecl.cpp
+++ b/clang/lib/Serialization/ASTReaderDecl.cpp
@@ -405,6 +405,7 @@ class ASTDeclReader : public DeclVisitor<ASTDeclReader, void> {
   void VisitFriendDecl(FriendDecl *D);
   void VisitFriendTemplateDecl(FriendTemplateDecl *D);
   void VisitStaticAssertDecl(StaticAssertDecl *D);
+  void VisitExplicitInstantiationDecl(ExplicitInstantiationDecl *D);
   void VisitBlockDecl(BlockDecl *BD);
   void VisitOutlinedFunctionDecl(OutlinedFunctionDecl *D);
   void VisitCapturedDecl(CapturedDecl *CD);
@@ -2785,6 +2786,22 @@ void ASTDeclReader::VisitStaticAssertDecl(StaticAssertDecl *D) {
   D->RParenLoc = readSourceLocation();
 }
 
+void ASTDeclReader::VisitExplicitInstantiationDecl(
+    ExplicitInstantiationDecl *D) {
+  VisitDecl(D);
+  D->Specialization = readDeclAs<NamedDecl>();
+  D->Range = readSourceRange();
+  D->ExternLoc = readSourceLocation();
+  D->TagKWLoc = readSourceLocation();
+  D->QualifierLoc = Record.readNestedNameSpecifierLoc();
+  bool HasArgsAsWritten = Record.readInt();
+  if (HasArgsAsWritten)
+    D->TemplateArgsAsWritten = Record.readASTTemplateArgumentListInfo();
+  D->NameLoc = readSourceLocation();
+  D->TypeAsWritten = readTypeSourceInfo();
+  D->TSK = Record.readInt();
+}
+
 void ASTDeclReader::VisitEmptyDecl(EmptyDecl *D) {
   VisitDecl(D);
 }
@@ -4107,6 +4124,9 @@ Decl *ASTReader::ReadDeclRecord(GlobalDeclID ID) {
   case DECL_STATIC_ASSERT:
     D = StaticAssertDecl::CreateDeserialized(Context, ID);
     break;
+  case DECL_EXPLICIT_INSTANTIATION:
+    D = ExplicitInstantiationDecl::CreateDeserialized(Context, ID);
+    break;
   case DECL_OBJC_METHOD:
     D = ObjCMethodDecl::CreateDeserialized(Context, ID);
     break;
diff --git a/clang/lib/Serialization/ASTWriterDecl.cpp b/clang/lib/Serialization/ASTWriterDecl.cpp
index e415ac1e47862..42610a1985f60 100644
--- a/clang/lib/Serialization/ASTWriterDecl.cpp
+++ b/clang/lib/Serialization/ASTWriterDecl.cpp
@@ -144,6 +144,7 @@ namespace clang {
     void VisitFriendDecl(FriendDecl *D);
     void VisitFriendTemplateDecl(FriendTemplateDecl *D);
     void VisitStaticAssertDecl(StaticAssertDecl *D);
+    void VisitExplicitInstantiationDecl(ExplicitInstantiationDecl *D);
     void VisitBlockDecl(BlockDecl *D);
     void VisitOutlinedFunctionDecl(OutlinedFunctionDecl *D);
     void VisitCapturedDecl(CapturedDecl *D);
@@ -2176,6 +2177,23 @@ void ASTDeclWriter::VisitStaticAssertDecl(StaticAssertDecl *D) {
   Code = serialization::DECL_STATIC_ASSERT;
 }
 
+void ASTDeclWriter::VisitExplicitInstantiationDecl(
+    ExplicitInstantiationDecl *D) {
+  VisitDecl(D);
+  Record.AddDeclRef(D->getSpecialization());
+  Record.AddSourceRange(D->getSourceRange());
+  Record.AddSourceLocation(D->getExternLoc());
+  Record.AddSourceLocation(D->getTagKWLoc());
+  Record.AddNestedNameSpecifierLoc(D->getQualifierLoc());
+  Record.push_back(D->getTemplateArgsAsWritten() != nullptr);
+  if (D->getTemplateArgsAsWritten())
+    Record.AddASTTemplateArgumentListInfo(D->getTemplateArgsAsWritten());
+  Record.AddSourceLocation(D->getNameLoc());
+  Record.AddTypeSourceInfo(D->getTypeAsWritten());
+  Record.push_back(D->getTemplateSpecializationKind());
+  Code = serialization::DECL_EXPLICIT_INSTANTIATION;
+}
+
 /// Emit the DeclContext part of a declaration context decl.
 void ASTDeclWriter::VisitDeclContext(DeclContext *DC) {
   static_assert(DeclContext::NumDeclContextBits == 13,
diff --git a/clang/lib/Tooling/Syntax/BuildTree.cpp b/clang/lib/Tooling/Syntax/BuildTree.cpp
index 9d49d72dea69b..cff89b7dfd758 100644
--- a/clang/lib/Tooling/Syntax/BuildTree.cpp
+++ b/clang/lib/Tooling/Syntax/BuildTree.cpp
@@ -737,6 +737,13 @@ class BuildTreeVisitor : public RecursiveASTVisitor<BuildTreeVisitor> {
     return true;
   }
 
+  // ExplicitInstantiationDecl is an auxiliary AST node that records source
+  // info. The syntax tree is already built by TraverseClassTemplateSpecializationDecl
+  // or by the parser for function/variable templates, so skip this node.
+  bool TraverseExplicitInstantiationDecl(ExplicitInstantiationDecl *) {
+    return true;
+  }
+
   bool WalkUpFromTemplateDecl(TemplateDecl *S) {
     foldTemplateDeclaration(
         Builder.getDeclarationRange(S),
diff --git a/clang/test/AST/ast-dump-templates-pattern.cpp b/clang/test/AST/ast-dump-templates-pattern.cpp
index 624a9b1c7a94c..2b8efaeed43ed 100644
--- a/clang/test/AST/ast-dump-templates-pattern.cpp
+++ b/clang/test/AST/ast-dump-templates-pattern.cpp
@@ -18,7 +18,11 @@ namespace TestClassRedecl {
 // CHECK: |-ClassTemplateDecl {{.+}} <line:[[@LINE-6]]:{{.+}} A
 // CHECK: | |-CXXRecordDecl 0x[[TestClassRedecl_D2:[^ ]+]] prev 0x[[TestClassRedecl_D1]] {{.+}} struct A
 // CHECK: | `-ClassTemplateSpecialization 0x[[TestClassRedecl_S]] 'A'
-// CHECK: `-ClassTemplateSpecializationDecl 0x[[TestClassRedecl_S]] <line:[[@LINE-8]]:{{.+}} struct A definition instantiated_from 0x[[TestClassRedecl_D1]] explicit_instantiation_definition
+// CHECK: |-ClassTemplateSpecializationDecl 0x[[TestClassRedecl_S]] <line:[[@LINE-8]]:{{.+}} struct A definition instantiated_from 0x[[TestClassRedecl_D1]] explicit_instantiation_definition
+// CHECK: `-ExplicitInstantiationDecl {{.+}} <line:[[@LINE-9]]:{{.+}} template 'A'
+// CHECK:   |-ClassTemplateSpecialization 0x[[TestClassRedecl_S]] 'A'
+// CHECK:   `-TemplateArgument {{.+}} type 'int'
+// CHECK:     `-BuiltinType {{.+}} 'int'
 }
 
 namespace TestFunctionRedecl {
@@ -29,9 +33,15 @@ namespace TestFunctionRedecl {
 // CHECK: |-FunctionTemplateDecl 0x[[TestFunctionRedecl_T1:[^ ]+]] <line:[[@LINE-4]]:{{.+}} f
 // CHECK: | |-FunctionDecl 0x[[TestFunctionRedecl_D1:[^ ]+]] {{.+}} f 'void ()'
 // CHECK: | `-FunctionDecl 0x[[TestFunctionRedecl_S1:[^ ]+]] {{.+}} f 'void ()' explicit_instantiation_definition instantiated_from 0x[[TestFunctionRedecl_D1]]
-// CHECK: `-FunctionTemplateDecl 0x[[TestFunctionRedecl_T2:[^ ]+]] prev 0x[[TestFunctionRedecl_T1]] <line:[[@LINE-6]]:{{.+}} f
-// CHECK:   |-FunctionDecl 0x[[TestFunctionRedecl_D2:[^ ]+]] prev 0x[[TestFunctionRedecl_D1]] {{.+}} f 'void ()'
-// CHECK:   `-Function 0x[[TestFunctionRedecl_S1]] 'f' 'void ()'
+// CHECK: |-FunctionTemplateDecl 0x[[TestFunctionRedecl_T2:[^ ]+]] prev 0x[[TestFunctionRedecl_T1]] <line:[[@LINE-6]]:{{.+}} f
+// CHECK: | |-FunctionDecl 0x[[TestFunctionRedecl_D2:[^ ]+]] prev 0x[[TestFunctionRedecl_D1]] {{.+}} f 'void ()'
+// CHECK: | `-Function 0x[[TestFunctionRedecl_S1]] 'f' 'void ()'
+// CHECK: `-ExplicitInstantiationDecl {{.+}} <line:[[@LINE-8]]:{{.+}} template 'f'
+// CHECK:   |-Function 0x[[TestFunctionRedecl_S1]] 'f' 'void ()'
+// CHECK:   |-TemplateArgument {{.+}} type 'int'
+// CHECK:   | `-BuiltinType {{.+}} 'int'
+// CHECK:   `-FunctionProtoTypeLoc {{.+}} 'void ()' cdecl
+// CHECK:     `-BuiltinTypeLoc {{.+}} 'void'
 }
 
 // FIXME: Bogus instantiated_from self-reference.
@@ -46,7 +56,12 @@ namespace TestVariableRedecl {
 // CHECK: |-VarTemplateDecl 0x[[TestVariableRedecl_T2:[^ ]+]] prev 0x[[TestVariableRedecl_T1]] <line:[[@LINE-6]]:{{.+}} a
 // CHECK: | |-VarDecl 0x[[TestVariableRedecl_D2:[^ ]+]] prev 0x[[TestVariableRedecl_D1]] {{.+}} a 'T' extern instantiated_from 0x[[TestVariableRedecl_D1]]
 // CHECK: | `-VarTemplateSpecialization 0x[[TestVariableRedecl_S1]] 'a' 'int'
-// CHECK: `-VarTemplateSpecializationDecl 0x[[TestVariableRedecl_S1]] {{.+}} a 'int' explicit_instantiation_definition cinit instantiated_from 0x[[TestVariableRedecl_D1]]
+// CHECK: |-VarTemplateSpecializationDecl 0x[[TestVariableRedecl_S1]] {{.+}} a 'int' explicit_instantiation_definition cinit instantiated_from 0x[[TestVariableRedecl_D1]]
+// CHECK: `-ExplicitInstantiationDecl {{.+}} <line:[[@LINE-9]]:{{.+}} template 'a'
+// CHECK:   |-VarTemplateSpecialization 0x[[TestVariableRedecl_S1]] 'a' 'int'
+// CHECK:   |-TemplateArgument {{.+}} type 'int'
+// CHECK:   | `-BuiltinType {{.+}} 'int'
+// CHECK:   `-BuiltinTypeLoc {{.+}} 'int'
 }
 
 namespace TestNestedClassRedecl {
@@ -70,5 +85,10 @@ namespace TestNestedClassRedecl {
 // CHECK: | `-ClassTemplateSpecialization 0x[[TestNestedClassRedecl_A_S1]] 'A'
 // CHECK: |-ClassTemplateDecl 0x{{.+}} parent 0x[[TestNestedClassRedecl_A_D1]] prev 0x[[TestNestedClassRedecl_B_T1]] <line:[[@LINE-14]]:{{.+}} B
 // CHECK: | `-CXXRecordDecl 0x[[TestNestedClassRedecl_B_D2:[^ ]+]] parent 0x[[TestNestedClassRedecl_A_D1]] prev 0x[[TestNestedClassRedecl_B_D1]] {{.+}} struct B definition
-// CHECK: `-ClassTemplateSpecializationDecl 0x[[TestNestedClassRedecl_B_S1]] parent 0x[[TestNestedClassRedecl_A_S1]] <line:[[@LINE-15]]:{{.+}} struct B definition instantiated_from 0x[[TestNestedClassRedecl_B_D2]] explicit_instantiation_definition
+// CHECK: |-ClassTemplateSpecializationDecl 0x[[TestNestedClassRedecl_B_S1]] parent 0x[[TestNestedClassRedecl_A_S1]] <line:[[@LINE-15]]:{{.+}} struct B definition instantiated_from 0x[[TestNestedClassRedecl_B_D2]] explicit_instantiation_definition
+// CHECK: `-ExplicitInstantiationDecl {{.+}} <line:[[@LINE-16]]:{{.+}} template 'B'
+// CHECK:   |-NestedNameSpecifier TypeSpec {{.+}}
+// CHECK:   |-ClassTemplateSpecialization 0x[[TestNestedClassRedecl_B_S1]] 'B'
+// CHECK:   `-TemplateArgument {{.+}} type 'char'
+// CHECK:     `-BuiltinType {{.+}} 'char'
 }
diff --git a/clang/test/AST/ast-dump-templates.cpp b/clang/test/AST/ast-dump-templates.cpp
index 8cf9b6a29e332..fac806ffa9f37 100644
--- a/clang/test/AST/ast-dump-templates.cpp
+++ b/clang/test/AST/ast-dump-templates.cpp
@@ -5032,12 +5032,31 @@ namespace TestAbbreviatedTemplateDecls {
 // JSON-NEXT:        "TemplateInstantiationPattern": "0x{{.*}}"
 // JSON-NEXT:       }
 // JSON-NEXT:      ]
-// JSON-NEXT:     }
-// JSON-NEXT:    ]
-// JSON-NEXT:   },
-// JSON-NEXT:   {
-// JSON-NEXT:    "id": "0x{{.*}}",
-// JSON-NEXT:    "kind": "NamespaceDecl",
+// JSON-NEXT:     },
+// JSON-NEXT:     {
+// JSON-NEXT:      "id": "0x{{.*}}",
+// JSON-NEXT:      "kind": "ExplicitInstantiationDecl",
+// JSON-NEXT:      "loc": {
+// JSON-NEXT:       "offset": {{[0-9]+}},
+// JSON-NEXT:       "line": 102,
+// JSON-NEXT:       "col": 1,
+// JSON-NEXT:       "tokLen": 8
+// JSON-NEXT:      },
+// JSON-NEXT:      "range": {
+// JSON-NEXT:       "begin": {
+// JSON-NEXT:        "offset": {{[0-9]+}},
+// JSON-NEXT:        "col": 1,
+// JSON-NEXT:        "tokLen": 8
+// JSON-NEXT:       },
+// JSON-NEXT:       "end": {
+// JSON-NEXT:        "offset": {{[0-9]+}},
+// JSON-NEXT:        "col": 30,
+// JSON-NEXT:        "tokLen": 1
+// JSON-NEXT:       }
+// JSON-NEXT:      },
+// JSON-NEXT:      "specializationDeclId": "0x{{.*}}",
+// JSON-NEXT:      "templateSpecializationKind": "explicit_instantiation_definition",
+// JSON:    "kind": "NamespaceDecl",
 // JSON-NEXT:    "loc": {
 // JSON-NEXT:     "offset": 3310,
 // JSON-NEXT:     "line": 105,
@@ -6317,12 +6336,31 @@ namespace TestAbbreviatedTemplateDecls {
 // JSON-NEXT:        "tagUsed": "struct"
 // JSON-NEXT:       }
 // JSON-NEXT:      ]
-// JSON-NEXT:     }
-// JSON-NEXT:    ]
-// JSON-NEXT:   },
-// JSON-NEXT:   {
-// JSON-NEXT:    "id": "0x{{.*}}",
-// JSON-NEXT:    "kind": "NamespaceDecl",
+// JSON-NEXT:     },
+// JSON-NEXT:     {
+// JSON-NEXT:      "id": "0x{{.*}}",
+// JSON-NEXT:      "kind": "ExplicitInstantiationDecl",
+// JSON-NEXT:      "loc": {
+// JSON-NEXT:       "offset": {{[0-9]+}},
+// JSON-NEXT:       "line": 133,
+// JSON-NEXT:       "col": 3,
+// JSON-NEXT:       "tokLen": 8
+// JSON-NEXT:      },
+// JSON-NEXT:      "range": {
+// JSON-NEXT:       "begin": {
+// JSON-NEXT:        "offset": {{[0-9]+}},
+// JSON-NEXT:        "col": 3,
+// JSON-NEXT:        "tokLen": 8
+// JSON-NEXT:       },
+// JSON-NEXT:       "end": {
+// JSON-NEXT:        "offset": {{[0-9]+}},
+// JSON-NEXT:        "col": 22,
+// JSON-NEXT:        "tokLen": 1
+// JSON-NEXT:       }
+// JSON-NEXT:      },
+// JSON-NEXT:      "specializationDeclId": "0x{{.*}}",
+// JSON-NEXT:      "templateSpecializationKind": "explicit_instantiation_definition",
+// JSON:    "kind": "NamespaceDecl",
 // JSON-NEXT:    "loc": {
 // JSON-NEXT:     "offset": 4584,
 // JSON-NEXT:     "line": 142,
diff --git a/clang/test/AST/explicit-instantiation-source-info.cpp b/clang/test/AST/explicit-instantiation-source-info.cpp
new file mode 100644
index 0000000000000..0903ba140cb9e
--- /dev/null
+++ b/clang/test/AST/explicit-instantiation-source-info.cpp
@@ -0,0 +1,166 @@
+// RUN: %clang_cc1 -fsyntax-only -ast-dump %s | FileCheck %s
+
+namespace ns {
+  template <typename T> void foo(T x) {}
+  template <typename T> T bar = T{};
+  template <typename T> struct S {
+    void method(T x) {}
+    static T sval;
+    template <typename U> void tmpl(U u) {}
+    struct Inner { T val; };
+  };
+  template <typename T> T S<T>::sval = T{};
+}
+
+// (a) function template
+template void ns::foo<int>(int);
+// CHECK: ExplicitInstantiationDecl {{.*}} <line:[[@LINE-1]]:1, col:31> col:1 explicit_instantiation_definition template 'foo'
+// CHECK-NEXT: NestedNameSpecifier Namespace {{.*}} 'ns'
+// CHECK-NEXT: Function {{.*}} 'foo' 'void (int)'
+// CHECK-NEXT: TemplateArgument <col:23> type 'int'
+// CHECK-NEXT:   BuiltinType {{.*}} 'int'
+// CHECK-NEXT: FunctionProtoTypeLoc <col:10, col:31> 'void (int)' cdecl
+// CHECK-NEXT:   ParmVarDecl {{.*}} <col:28> col:31 'int'
+// CHECK-NEXT:     BuiltinTypeLoc <col:28> 'int'
+// CHECK-NEXT:   BuiltinTypeLoc <col:10> 'void'
+
+// (b) variable template
+template int ns::bar<int>;
+// CHECK: ExplicitInstantiationDecl {{.*}} <line:[[@LINE-1]]:1, col:25> col:1 explicit_instantiation_definition template 'bar'
+// CHECK-NEXT: NestedNameSpecifier Namespace {{.*}} 'ns'
+// CHECK-NEXT: VarTemplateSpecialization {{.*}} 'bar' 'int'
+// CHECK-NEXT: TemplateArgument <col:22> type 'int'
+// CHECK-NEXT:   BuiltinType {{.*}} 'int'
+// CHECK-NEXT: BuiltinTypeLoc <col:10> 'int'
+
+// (c) class template
+template struct ns::S<int>;
+// CHECK: ExplicitInstantiationDecl {{.*}} <line:[[@LINE-1]]:1, col:26> col:1 explicit_instantiation_definition template 'S'
+// CHECK-NEXT: NestedNameSpecifier Namespace {{.*}} 'ns'
+// CHECK-NEXT: ClassTemplateSpecialization {{.*}} 'S'
+// CHECK-NEXT: TemplateArgument <col:23> type 'int'
+// CHECK-NEXT:   BuiltinType {{.*}} 'int'
+
+// (d) member function
+template void ns::S<long>::method(long);
+// CHECK: ExplicitInstantiationDecl {{.*}} <line:[[@LINE-1]]:1, col:39> col:1 explicit_instantiation_definition template 'method'
+// CHECK-NEXT: NestedNameSpecifier TypeSpec 'ns::S<long>'
+// CHECK-NEXT: CXXMethod {{.*}} 'method' 'void (long)'
+// CHECK-NEXT: FunctionProtoTypeLoc <col:10, col:39> 'void (long)' cdecl
+// CHECK-NEXT:   ParmVarDecl {{.*}} <col:35> col:39 'long'
+// CHECK-NEXT:     BuiltinTypeLoc <col:35> 'long'
+// CHECK-NEXT:   BuiltinTypeLoc <col:10> 'void'
+
+// (e) static data member
+template long ns::S<long>::sval;
+// CHECK: ExplicitInstantiationDecl {{.*}} <line:[[@LINE-1]]:1, col:28> col:1 explicit_instantiation_definition template 'sval'
+// CHECK-NEXT: NestedNameSpecifier TypeSpec 'ns::S<long>'
+// CHECK-NEXT: Var {{.*}} 'sval' 'long'
+// CHECK-NEXT: BuiltinTypeLoc <col:10> 'long'
+
+// (f) member function template
+template void ns::S<long>::tmpl<double>(double);
+// CHECK: ExplicitInstantiationDecl {{.*}} <line:[[@LINE-1]]:1, col:47> col:1 explicit_instantiation_definition template 'tmpl'
+// CHECK-NEXT: NestedNameSpecifier TypeSpec 'ns::S<long>'
+// CHECK-NEXT: CXXMethod {{.*}} 'tmpl' 'void (double)'
+// CHECK-NEXT: TemplateArgument <col:33> type 'double'
+// CHECK-NEXT:   BuiltinType {{.*}} 'double'
+// CHECK-NEXT: FunctionProtoTypeLoc <col:10, col:47> 'void (double)' cdecl
+// CHECK-NEXT:   ParmVarDecl {{.*}} <col:41> col:47 'double'
+// CHECK-NEXT:     BuiltinTypeLoc <col:41> 'double'
+// CHECK-NEXT:   BuiltinTypeLoc <col:10> 'void'
+
+// (g) nested class (no template args or type)
+template struct ns::S<long>::Inner;
+// CHECK: ExplicitInstantiationDecl {{.*}} <line:[[@LINE-1]]:1, col:30> col:1 explicit_instantiation_definition template 'Inner'
+// CHECK-NEXT: NestedNameSpecifier TypeSpec 'ns::S<long>'
+// CHECK-NEXT: CXXRecord {{.*}} 'Inner'
+
+// extern template variants
+extern template void ns::foo<float>(float);
+// CHECK: ExplicitInstantiationDecl {{.*}} <line:[[@LINE-1]]:1, col:42> col:8 explicit_instantiation_declaration extern template 'foo'
+// CHECK-NEXT: NestedNameSpecifier Namespace {{.*}} 'ns'
+// CHECK-NEXT: Function {{.*}} 'foo' 'void (float)'
+// CHECK-NEXT: TemplateArgument <col:30> type 'float'
+// CHECK-NEXT:   BuiltinType {{.*}} 'float'
+// CHECK-NEXT: FunctionProtoTypeLoc <col:17, col:42> 'void (float)' cdecl
+// CHECK-NEXT:   ParmVarDecl {{.*}} <col:37> col:42 'float'
+// CHECK-NEXT:     BuiltinTypeLoc <col:37> 'float'
+// CHECK-NEXT:   BuiltinTypeLoc <col:17> 'void'
+
+extern template struct ns::S<float>;
+// CHECK: ExplicitInstantiationDecl {{.*}} <line:[[@LINE-1]]:1, col:35> col:8 explicit_instantiation_declaration extern template 'S'
+// CHECK-NEXT: NestedNameSpecifier Namespace {{.*}} 'ns'
+// CHECK-NEXT: ClassTemplateSpecialization {{.*}} 'S'
+// CHECK-NEXT: TemplateArgument <col:30> type 'float'
+// CHECK-NEXT:   BuiltinType {{.*}} 'float'
+
+// extern template: variable template
+extern template double ns::bar<double>;
+// CHECK: ExplicitInstantiationDecl {{.*}} <line:[[@LINE-1]]:1, col:38> col:8 explicit_instantiation_declaration extern template 'bar'
+// CHECK-NEXT: NestedNameSpecifier Namespace {{.*}} 'ns'
+// CHECK-NEXT: VarTemplateSpecialization {{.*}} 'bar' 'double'
+// CHECK-NEXT: TemplateArgument <col:32> type 'double'
+// CHECK-NEXT:   BuiltinType {{.*}} 'double'
+// CHECK-NEXT: BuiltinTypeLoc <col:17> 'double'
+
+// extern template: member function
+extern template void ns::S<double>::method(double);
+// CHECK: ExplicitInstantiationDecl {{.*}} <line:[[@LINE-1]]:1, col:50> col:8 explicit_instantiation_declaration extern template 'method'
+// CHECK-NEXT: NestedNameSpecifier TypeSpec 'ns::S<double>'
+// CHECK-NEXT: CXXMethod {{.*}} 'method' 'void (double)'
+// CHECK-NEXT: FunctionProtoTypeLoc <col:17, col:50> 'void (double)' cdecl
+// CHECK-NEXT:   ParmVarDecl {{.*}} <col:44> col:50 'double'
+// CHECK-NEXT:     BuiltinTypeLoc <col:44> 'double'
+// CHECK-NEXT:   BuiltinTypeLoc <col:17> 'void'
+
+// extern template: static data member
+extern template double ns::S<double>::sval;
+// CHECK: ExplicitInstantiationDecl {{.*}} <line:[[@LINE-1]]:1, col:39> col:8 explicit_instantiation_declaration extern template 'sval'
+// CHECK-NEXT: NestedNameSpecifier TypeSpec 'ns::S<double>'
+// CHECK-NEXT: Var {{.*}} 'sval' 'double'
+// CHECK-NEXT: BuiltinTypeLoc <col:17> 'double'
+
+// extern template: member function template
+extern template void ns::S<double>::tmpl<float>(float);
+// CHECK: ExplicitInstantiationDecl {{.*}} <line:[[@LINE-1]]:1, col:54> col:8 explicit_instantiation_declaration extern template 'tmpl'
+// CHECK-NEXT: NestedNameSpecifier TypeSpec 'ns::S<double>'
+// CHECK-NEXT: CXXMethod {{.*}} 'tmpl' 'void (float)'
+// CHECK-NEXT: TemplateArgument <col:42> type 'float'
+// CHECK-NEXT:   BuiltinType {{.*}} 'float'
+// CHECK-NEXT: FunctionProtoTypeLoc <col:17, col:54> 'void (float)' cdecl
+// CHECK-NEXT:   ParmVarDecl {{.*}} <col:49> col:54 'float'
+// CHECK-NEXT:     BuiltinTypeLoc <col:49> 'float'
+// CHECK-NEXT:   BuiltinTypeLoc <col:17> 'void'
+
+// extern template: nested class (no template args or type)
+extern template struct ns::S<double>::Inner;
+// CHECK: ExplicitInstantiationDecl {{.*}} <line:[[@LINE-1]]:1, col:39> col:8 explicit_instantiation_declaration extern template 'Inner'
+// CHECK-NEXT: NestedNameSpecifier TypeSpec 'ns::S<double>'
+// CHECK-NEXT: CXXRecord {{.*}} 'Inner'
+
+// Same-namespace explicit instantiation (no cross-namespace qualifier)
+namespace ns {
+  template void foo<short>(short);
+  // CHECK: ExplicitInstantiationDecl {{.*}} <line:[[@LINE-1]]:3, col:33> col:3 explicit_instantiation_definition template 'foo'
+  // CHECK-NEXT: Function {{.*}} 'foo' 'void (short)'
+  // CHECK-NEXT: TemplateArgument <col:21> type 'short'
+  // CHECK-NEXT:   BuiltinType {{.*}} 'short'
+  // CHECK-NEXT: FunctionProtoTypeLoc <col:12, col:33> 'void (short)' cdecl
+  // CHECK-NEXT:   ParmVarDecl {{.*}} <col:28> col:33 'short'
+  // CHECK-NEXT:     BuiltinTypeLoc <col:28> 'short'
+  // CHECK-NEXT:   BuiltinTypeLoc <col:12> 'void'
+
+  template short bar<short>;
+  // CHECK: ExplicitInstantiationDecl {{.*}} <line:[[@LINE-1]]:3, col:27> col:3 explicit_instantiation_definition template 'bar'
+  // CHECK-NEXT: VarTemplateSpecialization {{.*}} 'bar' 'short'
+  // CHECK-NEXT: TemplateArgument <col:22> type 'short'
+  // CHECK-NEXT:   BuiltinType {{.*}} 'short'
+  // CHECK-NEXT: BuiltinTypeLoc <col:12> 'short'
+
+  template struct S<short>;
+  // CHECK: ExplicitInstantiationDecl {{.*}} <line:[[@LINE-1]]:3, col:26> col:3 explicit_instantiation_definition template 'S'
+  // CHECK-NEXT: ClassTemplateSpecialization {{.*}} 'S'
+  // CHECK-NEXT: TemplateArgument <col:21> type 'short'
+  // CHECK-NEXT:   BuiltinType {{.*}} 'short'
+}
diff --git a/clang/test/SemaTemplate/explicit-instantiation-diag-location.cpp b/clang/test/SemaTemplate/explicit-instantiation-diag-location.cpp
new file mode 100644
index 0000000000000..b8d22efaac7df
--- /dev/null
+++ b/clang/test/SemaTemplate/explicit-instantiation-diag-location.cpp
@@ -0,0 +1,31 @@
+// RUN: %clang_cc1 -fsyntax-only -verify %s
+
+// Verify that the "previous explicit instantiation" note points to the first
+// explicit instantiation statement, not to the implicit instantiation site.
+// This is a regression test for https://github.com/llvm/llvm-project/issues/21133
+
+// Function template with implicit instantiation before explicit.
+template <typename T> void f() {}
+void use_f() { f<int>(); }
+template void f<int>();          // expected-note{{previous explicit instantiation is here}}
+template void f<int>();          // expected-error{{duplicate explicit instantiation of 'f<int>'}}
+
+// Class template with implicit instantiation before explicit.
+template <typename T> struct S {};
+void use_S(S<int>) {}
+template struct S<int>;          // expected-note{{previous explicit instantiation is here}}
+template struct S<int>;          // expected-error{{duplicate explicit instantiation of 'S<int>'}}
+
+// Cross-namespace: template in ns, explicit instantiation at global scope.
+namespace ns {
+  template <typename T> void g() {}
+}
+void use_g() { ns::g<double>(); }
+template void ns::g<double>();   // expected-note{{previous explicit instantiation is here}}
+template void ns::g<double>();   // expected-error{{duplicate explicit instantiation of 'g<double>'}}
+
+// Variable template with implicit instantiation before explicit.
+template <typename T> T var = T{};
+int use_var = var<int>;
+template int var<int>;           // expected-note{{previous explicit instantiation is here}}
+template int var<int>;           // expected-error{{duplicate explicit instantiation of 'var<int>'}}
diff --git a/clang/tools/libclang/CIndex.cpp b/clang/tools/libclang/CIndex.cpp
index 3ee37ed2dfc27..b3516456822a6 100644
--- a/clang/tools/libclang/CIndex.cpp
+++ b/clang/tools/libclang/CIndex.cpp
@@ -7257,6 +7257,7 @@ CXCursor clang_getCursorDefinition(CXCursor C) {
   case Decl::FileScopeAsm:
   case Decl::TopLevelStmt:
   case Decl::StaticAssert:
+  case Decl::ExplicitInstantiation:
   case Decl::Block:
   case Decl::OutlinedFunction:
   case Decl::Captured:
@@ -8837,6 +8838,7 @@ static CXLanguageKind getDeclLanguage(const Decl *D) {
   case Decl::NamespaceAlias:
   case Decl::NonTypeTemplateParm:
   case Decl::StaticAssert:
+  case Decl::ExplicitInstantiation:
   case Decl::TemplateTemplateParm:
   case Decl::TemplateTypeParm:
   case Decl::UnresolvedUsingTypename:

>From 61c859d6a02a4099aac88bca9bbe8d31a27b1579 Mon Sep 17 00:00:00 2001
From: ykiko <ykikoykikoykiko at gmail.com>
Date: Sun, 12 Apr 2026 03:29:48 +0800
Subject: [PATCH 02/15] clang-format

---
 clang/include/clang/AST/ASTNodeTraverser.h |  3 +-
 clang/include/clang/AST/DeclTemplate.h     | 14 +++---
 clang/lib/AST/DeclTemplate.cpp             |  9 ++--
 clang/lib/Sema/SemaTemplate.cpp            | 55 +++++++++++-----------
 clang/lib/Tooling/Syntax/BuildTree.cpp     |  5 +-
 5 files changed, 44 insertions(+), 42 deletions(-)

diff --git a/clang/include/clang/AST/ASTNodeTraverser.h b/clang/include/clang/AST/ASTNodeTraverser.h
index 39a6864bf169e..77a4d5655b196 100644
--- a/clang/include/clang/AST/ASTNodeTraverser.h
+++ b/clang/include/clang/AST/ASTNodeTraverser.h
@@ -685,7 +685,8 @@ class ASTNodeTraverser
 
   void VisitExplicitInstantiationDecl(const ExplicitInstantiationDecl *D) {
     // The specialization is already elsewhere in the AST; don't re-traverse it.
-    // Traverse source-location sub-nodes: template arguments and type-as-written.
+    // Traverse source-location sub-nodes: template arguments and
+    // type-as-written.
     if (const auto *ArgsAsWritten = D->getTemplateArgsAsWritten())
       for (unsigned I = 0, E = ArgsAsWritten->NumTemplateArgs; I != E; ++I)
         Visit((*ArgsAsWritten)[I].getArgument(),
diff --git a/clang/include/clang/AST/DeclTemplate.h b/clang/include/clang/AST/DeclTemplate.h
index b470b520531f1..6f148e1cf2e6d 100644
--- a/clang/include/clang/AST/DeclTemplate.h
+++ b/clang/include/clang/AST/DeclTemplate.h
@@ -3440,7 +3440,8 @@ class ExplicitInstantiationDecl : public Decl {
   /// arguments were deduced.
   const ASTTemplateArgumentListInfo *TemplateArgsAsWritten = nullptr;
 
-  /// Location of the entity name (e.g., 'foo' in 'template void ns::foo<int>(int)').
+  /// Location of the entity name (e.g., 'foo' in 'template void
+  /// ns::foo<int>(int)').
   SourceLocation NameLoc;
 
   /// Type source info for the declaration type:
@@ -3455,10 +3456,8 @@ class ExplicitInstantiationDecl : public Decl {
   unsigned TSK : 3;
 
   ExplicitInstantiationDecl(DeclContext *DC, SourceRange Range,
-                            NamedDecl *Specialization,
-                            SourceLocation ExternLoc,
-                            SourceLocation TemplateLoc,
-                            SourceLocation TagKWLoc,
+                            NamedDecl *Specialization, SourceLocation ExternLoc,
+                            SourceLocation TemplateLoc, SourceLocation TagKWLoc,
                             NestedNameSpecifierLoc QualifierLoc,
                             const ASTTemplateArgumentListInfo *ArgsAsWritten,
                             SourceLocation NameLoc,
@@ -3469,8 +3468,9 @@ class ExplicitInstantiationDecl : public Decl {
         TagKWLoc(TagKWLoc), QualifierLoc(QualifierLoc),
         TemplateArgsAsWritten(ArgsAsWritten), NameLoc(NameLoc),
         TypeAsWritten(TypeAsWritten), TSK(TSK) {
-    assert((TSK == TSK_ExplicitInstantiationDeclaration) == ExternLoc.isValid()
-           && "ExternLoc should be valid iff TSK is a declaration");
+    assert((TSK == TSK_ExplicitInstantiationDeclaration) ==
+               ExternLoc.isValid() &&
+           "ExternLoc should be valid iff TSK is a declaration");
   }
 
   ExplicitInstantiationDecl(EmptyShell Empty)
diff --git a/clang/lib/AST/DeclTemplate.cpp b/clang/lib/AST/DeclTemplate.cpp
index 76e3de5394d86..ecce1a43d298c 100644
--- a/clang/lib/AST/DeclTemplate.cpp
+++ b/clang/lib/AST/DeclTemplate.cpp
@@ -1792,12 +1792,11 @@ ExplicitInstantiationDecl *ExplicitInstantiationDecl::Create(
     NamedDecl *Specialization, SourceLocation ExternLoc,
     SourceLocation TemplateLoc, SourceLocation TagKWLoc,
     NestedNameSpecifierLoc QualifierLoc,
-    const ASTTemplateArgumentListInfo *ArgsAsWritten,
-    SourceLocation NameLoc, TypeSourceInfo *TypeAsWritten,
-    TemplateSpecializationKind TSK) {
+    const ASTTemplateArgumentListInfo *ArgsAsWritten, SourceLocation NameLoc,
+    TypeSourceInfo *TypeAsWritten, TemplateSpecializationKind TSK) {
   return new (C, DC) ExplicitInstantiationDecl(
-      DC, Range, Specialization, ExternLoc, TemplateLoc, TagKWLoc,
-      QualifierLoc, ArgsAsWritten, NameLoc, TypeAsWritten, TSK);
+      DC, Range, Specialization, ExternLoc, TemplateLoc, TagKWLoc, QualifierLoc,
+      ArgsAsWritten, NameLoc, TypeAsWritten, TSK);
 }
 
 ExplicitInstantiationDecl *
diff --git a/clang/lib/Sema/SemaTemplate.cpp b/clang/lib/Sema/SemaTemplate.cpp
index 66fc2b6fc81e3..a94f0e2fe3c1e 100644
--- a/clang/lib/Sema/SemaTemplate.cpp
+++ b/clang/lib/Sema/SemaTemplate.cpp
@@ -9320,9 +9320,8 @@ static void addExplicitInstantiationDecl(
     ASTContext &Context, DeclContext *CurContext, const CXXScopeSpec &SS,
     SourceLocation EndLoc, NamedDecl *Spec, SourceLocation ExternLoc,
     SourceLocation TemplateLoc, SourceLocation TagKWLoc,
-    const ASTTemplateArgumentListInfo *ArgsAsWritten,
-    SourceLocation NameLoc, TypeSourceInfo *TypeAsWritten,
-    TemplateSpecializationKind TSK) {
+    const ASTTemplateArgumentListInfo *ArgsAsWritten, SourceLocation NameLoc,
+    TypeSourceInfo *TypeAsWritten, TemplateSpecializationKind TSK) {
   NestedNameSpecifierLoc QualifierLoc;
   if (SS.isNotEmpty())
     QualifierLoc = SS.getWithLocInContext(Context);
@@ -10438,10 +10437,10 @@ DeclResult Sema::ActOnExplicitInstantiation(
     // Set the template specialization kind.
     Specialization->setTemplateSpecializationKind(TSK);
 
-    addExplicitInstantiationDecl(
-        Context, CurContext, SS, RAngleLoc, Specialization, ExternLoc,
-        TemplateLoc, KWLoc, Specialization->getTemplateArgsAsWritten(),
-        TemplateNameLoc, nullptr, TSK);
+    addExplicitInstantiationDecl(Context, CurContext, SS, RAngleLoc,
+                                 Specialization, ExternLoc, TemplateLoc, KWLoc,
+                                 Specialization->getTemplateArgsAsWritten(),
+                                 TemplateNameLoc, nullptr, TSK);
     return Specialization;
   }
 
@@ -10531,10 +10530,10 @@ DeclResult Sema::ActOnExplicitInstantiation(
     Specialization->setTemplateSpecializationKind(TSK);
   }
 
-  addExplicitInstantiationDecl(
-      Context, CurContext, SS, RAngleLoc, Specialization, ExternLoc,
-      TemplateLoc, KWLoc, Specialization->getTemplateArgsAsWritten(),
-      TemplateNameLoc, nullptr, TSK);
+  addExplicitInstantiationDecl(Context, CurContext, SS, RAngleLoc,
+                               Specialization, ExternLoc, TemplateLoc, KWLoc,
+                               Specialization->getTemplateArgsAsWritten(),
+                               TemplateNameLoc, nullptr, TSK);
   return Specialization;
 }
 
@@ -10651,8 +10650,8 @@ Sema::ActOnExplicitInstantiation(Scope *S, SourceLocation ExternLoc,
     MarkVTableUsed(NameLoc, RecordDef, true);
 
   addExplicitInstantiationDecl(Context, CurContext, SS, NameLoc, Record,
-                               ExternLoc, TemplateLoc, KWLoc, nullptr,
-                               NameLoc, nullptr, TSK);
+                               ExternLoc, TemplateLoc, KWLoc, nullptr, NameLoc,
+                               nullptr, TSK);
   return TagD;
 }
 
@@ -10874,12 +10873,13 @@ DeclResult Sema::ActOnExplicitInstantiation(Scope *S,
       const ASTTemplateArgumentListInfo *ArgsAsWritten = nullptr;
       if (auto *VTSD = dyn_cast<VarTemplateSpecializationDecl>(Prev))
         ArgsAsWritten = VTSD->getTemplateArgsAsWritten();
-      addExplicitInstantiationDecl(
-          Context, CurContext, D.getCXXScopeSpec(),
-          D.getSourceRange().getEnd(), Prev, ExternLoc, TemplateLoc,
-          SourceLocation(), ArgsAsWritten, D.getIdentifierLoc(), T, TSK);
+      addExplicitInstantiationDecl(Context, CurContext, D.getCXXScopeSpec(),
+                                   D.getSourceRange().getEnd(), Prev, ExternLoc,
+                                   TemplateLoc, SourceLocation(), ArgsAsWritten,
+                                   D.getIdentifierLoc(), T, TSK);
       // Don't return the EID to the Parser — doing so would trigger
-      // unrelated semantic actions (e.g. access checks via FinalizeDeclaration).
+      // unrelated semantic actions (e.g. access checks via
+      // FinalizeDeclaration).
       return (Decl *)nullptr;
     }
   }
@@ -11063,12 +11063,13 @@ DeclResult Sema::ActOnExplicitInstantiation(Scope *S,
       if (HasExplicitTemplateArgs)
         ArgsAsWritten =
             ASTTemplateArgumentListInfo::Create(Context, TemplateArgs);
-      addExplicitInstantiationDecl(
-          Context, CurContext, D.getCXXScopeSpec(),
-          D.getSourceRange().getEnd(), Specialization, ExternLoc, TemplateLoc,
-          SourceLocation(), ArgsAsWritten, D.getIdentifierLoc(), T, TSK);
+      addExplicitInstantiationDecl(Context, CurContext, D.getCXXScopeSpec(),
+                                   D.getSourceRange().getEnd(), Specialization,
+                                   ExternLoc, TemplateLoc, SourceLocation(),
+                                   ArgsAsWritten, D.getIdentifierLoc(), T, TSK);
       // Don't return the EID to the Parser — doing so would trigger
-      // unrelated semantic actions (e.g. access checks via FinalizeDeclaration).
+      // unrelated semantic actions (e.g. access checks via
+      // FinalizeDeclaration).
       return (Decl *)nullptr;
     }
   }
@@ -11131,10 +11132,10 @@ DeclResult Sema::ActOnExplicitInstantiation(Scope *S,
     if (HasExplicitTemplateArgs)
       ArgsAsWritten =
           ASTTemplateArgumentListInfo::Create(Context, TemplateArgs);
-    addExplicitInstantiationDecl(
-        Context, CurContext, D.getCXXScopeSpec(),
-        D.getSourceRange().getEnd(), Specialization, ExternLoc, TemplateLoc,
-        SourceLocation(), ArgsAsWritten, D.getIdentifierLoc(), T, TSK);
+    addExplicitInstantiationDecl(Context, CurContext, D.getCXXScopeSpec(),
+                                 D.getSourceRange().getEnd(), Specialization,
+                                 ExternLoc, TemplateLoc, SourceLocation(),
+                                 ArgsAsWritten, D.getIdentifierLoc(), T, TSK);
     // Don't return the EID to the Parser — doing so would trigger
     // unrelated semantic actions (e.g. access checks via FinalizeDeclaration).
     return (Decl *)nullptr;
diff --git a/clang/lib/Tooling/Syntax/BuildTree.cpp b/clang/lib/Tooling/Syntax/BuildTree.cpp
index cff89b7dfd758..4fd5c009353fa 100644
--- a/clang/lib/Tooling/Syntax/BuildTree.cpp
+++ b/clang/lib/Tooling/Syntax/BuildTree.cpp
@@ -738,8 +738,9 @@ class BuildTreeVisitor : public RecursiveASTVisitor<BuildTreeVisitor> {
   }
 
   // ExplicitInstantiationDecl is an auxiliary AST node that records source
-  // info. The syntax tree is already built by TraverseClassTemplateSpecializationDecl
-  // or by the parser for function/variable templates, so skip this node.
+  // info. The syntax tree is already built by
+  // TraverseClassTemplateSpecializationDecl or by the parser for
+  // function/variable templates, so skip this node.
   bool TraverseExplicitInstantiationDecl(ExplicitInstantiationDecl *) {
     return true;
   }

>From bdcc69ce6c8da49f379806e7c750eba9c74f0f2f Mon Sep 17 00:00:00 2001
From: ykiko <ykikoykikoykiko at gmail.com>
Date: Sun, 12 Apr 2026 03:31:51 +0800
Subject: [PATCH 03/15] add ExplicitInstantiation to
 getIdentifierNamespaceForKind

---
 clang/lib/AST/DeclBase.cpp | 1 +
 1 file changed, 1 insertion(+)

diff --git a/clang/lib/AST/DeclBase.cpp b/clang/lib/AST/DeclBase.cpp
index 18b6a5b06bab8..2cbdff9da2e68 100644
--- a/clang/lib/AST/DeclBase.cpp
+++ b/clang/lib/AST/DeclBase.cpp
@@ -1019,6 +1019,7 @@ unsigned Decl::getIdentifierNamespaceForKind(Kind DeclKind) {
     case ImplicitConceptSpecialization:
     case OpenACCDeclare:
     case OpenACCRoutine:
+    case ExplicitInstantiation:
       // Never looked up by name.
       return 0;
   }

>From 0d63c412337938ddcbea5e9d6c3e03730b9ae80f Mon Sep 17 00:00:00 2001
From: ykiko <ykikoykikoykiko at gmail.com>
Date: Sun, 12 Apr 2026 04:45:10 +0800
Subject: [PATCH 04/15] fix CI failures from ExplicitInstantiationDecl

---
 clang/include/clang/AST/DeclTemplate.h        |  6 +++---
 clang/include/clang/AST/RecursiveASTVisitor.h | 17 +++++++++--------
 clang/lib/Sema/SemaModule.cpp                 |  2 +-
 3 files changed, 13 insertions(+), 12 deletions(-)

diff --git a/clang/include/clang/AST/DeclTemplate.h b/clang/include/clang/AST/DeclTemplate.h
index 6f148e1cf2e6d..f75af0c759fdf 100644
--- a/clang/include/clang/AST/DeclTemplate.h
+++ b/clang/include/clang/AST/DeclTemplate.h
@@ -3468,9 +3468,9 @@ class ExplicitInstantiationDecl : public Decl {
         TagKWLoc(TagKWLoc), QualifierLoc(QualifierLoc),
         TemplateArgsAsWritten(ArgsAsWritten), NameLoc(NameLoc),
         TypeAsWritten(TypeAsWritten), TSK(TSK) {
-    assert((TSK == TSK_ExplicitInstantiationDeclaration) ==
-               ExternLoc.isValid() &&
-           "ExternLoc should be valid iff TSK is a declaration");
+    // Note: ExternLoc and TSK can diverge in MSVC mode, where dllimport on
+    // an explicit instantiation definition (no 'extern' keyword) causes
+    // TSK to be changed to TSK_ExplicitInstantiationDeclaration.
   }
 
   ExplicitInstantiationDecl(EmptyShell Empty)
diff --git a/clang/include/clang/AST/RecursiveASTVisitor.h b/clang/include/clang/AST/RecursiveASTVisitor.h
index 37bcfb5b2ed76..d7a99bc81577f 100644
--- a/clang/include/clang/AST/RecursiveASTVisitor.h
+++ b/clang/include/clang/AST/RecursiveASTVisitor.h
@@ -2208,14 +2208,17 @@ bool RecursiveASTVisitor<Derived>::TraverseTemplateArgumentLocsHelper(
        the source code anywhere.  (Note the instatiated *type* --              \
        set<int> -- is written, and will still get a callback of                \
        TemplateSpecializationType).  For explicit instantiations               \
-       ("template set<int>;"), we do need a callback, since this               \
-       is the only callback that's made for this instantiation.                \
-       We use getTemplateArgsAsWritten() to distinguish. */                    \
+       ("template set<int>;"), the ExplicitInstantiationDecl node              \
+       handles traversal of template args and qualifier.                       \
+       For explicit specializations ("template<> set<int> {...};"),            \
+       we traverse template args here since there is no EID. */                \
     if (const auto *ArgsWritten = D->getTemplateArgsAsWritten()) {             \
       assert(D->getTemplateSpecializationKind() != TSK_ImplicitInstantiation); \
-      /* The args that remains unspecialized. */                               \
-      TRY_TO(TraverseTemplateArgumentLocsHelper(                               \
-          ArgsWritten->getTemplateArgs(), ArgsWritten->NumTemplateArgs));      \
+      if (D->getTemplateSpecializationKind() ==                                \
+          TSK_ExplicitSpecialization) {                                        \
+        TRY_TO(TraverseTemplateArgumentLocsHelper(                             \
+            ArgsWritten->getTemplateArgs(), ArgsWritten->NumTemplateArgs));    \
+      }                                                                        \
     }                                                                          \
                                                                                \
     if (getDerived().shouldVisitTemplateInstantiations() ||                    \
@@ -2223,8 +2226,6 @@ bool RecursiveASTVisitor<Derived>::TraverseTemplateArgumentLocsHelper(
       /* Traverse base definition for explicit specializations */              \
       TRY_TO(Traverse##DECLKIND##Helper(D));                                   \
     } else {                                                                   \
-      TRY_TO(TraverseNestedNameSpecifierLoc(D->getQualifierLoc()));            \
-                                                                               \
       /* Returning from here skips traversing the                              \
          declaration context of the *TemplateSpecializationDecl                \
          (embedded in the DEF_TRAVERSE_DECL() macro)                           \
diff --git a/clang/lib/Sema/SemaModule.cpp b/clang/lib/Sema/SemaModule.cpp
index 7618c795d9d4c..67f46b64cf047 100644
--- a/clang/lib/Sema/SemaModule.cpp
+++ b/clang/lib/Sema/SemaModule.cpp
@@ -935,7 +935,7 @@ static bool checkExportedDecl(Sema &S, Decl *D, SourceLocation BlockStart) {
   // HLSL: export declaration is valid only on functions
   if (S.getLangOpts().HLSL) {
     // Export-within-export was already diagnosed in ActOnStartExportDecl
-    if (!isa<FunctionDecl, ExportDecl>(D)) {
+    if (!isa<FunctionDecl, ExportDecl, ExplicitInstantiationDecl>(D)) {
       S.Diag(D->getBeginLoc(), diag::err_hlsl_export_not_on_function);
       D->setInvalidDecl();
       return false;

>From e88b891aa5e9c7e45d79ca321e40169c64ad8516 Mon Sep 17 00:00:00 2001
From: ykiko <ykikoykikoykiko at gmail.com>
Date: Sun, 12 Apr 2026 04:51:59 +0800
Subject: [PATCH 05/15] clang-format

---
 clang/include/clang/AST/RecursiveASTVisitor.h | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/clang/include/clang/AST/RecursiveASTVisitor.h b/clang/include/clang/AST/RecursiveASTVisitor.h
index d7a99bc81577f..4412d722fa273 100644
--- a/clang/include/clang/AST/RecursiveASTVisitor.h
+++ b/clang/include/clang/AST/RecursiveASTVisitor.h
@@ -2214,8 +2214,7 @@ bool RecursiveASTVisitor<Derived>::TraverseTemplateArgumentLocsHelper(
        we traverse template args here since there is no EID. */                \
     if (const auto *ArgsWritten = D->getTemplateArgsAsWritten()) {             \
       assert(D->getTemplateSpecializationKind() != TSK_ImplicitInstantiation); \
-      if (D->getTemplateSpecializationKind() ==                                \
-          TSK_ExplicitSpecialization) {                                        \
+      if (D->getTemplateSpecializationKind() == TSK_ExplicitSpecialization) {  \
         TRY_TO(TraverseTemplateArgumentLocsHelper(                             \
             ArgsWritten->getTemplateArgs(), ArgsWritten->NumTemplateArgs));    \
       }                                                                        \

>From 61e48953630e6ed1dd825785bf8b50f2681ce878 Mon Sep 17 00:00:00 2001
From: ykiko <ykikoykikoykiko at gmail.com>
Date: Sun, 12 Apr 2026 08:44:53 +0800
Subject: [PATCH 06/15] update clang-tidy tests for ExplicitInstantiationDecl

---
 .../test/clang-tidy/checkers/misc/unused-using-decls.cpp     | 2 --
 .../test/clang-tidy/checkers/modernize/avoid-c-arrays.cpp    | 5 +++--
 2 files changed, 3 insertions(+), 4 deletions(-)

diff --git a/clang-tools-extra/test/clang-tidy/checkers/misc/unused-using-decls.cpp b/clang-tools-extra/test/clang-tidy/checkers/misc/unused-using-decls.cpp
index f6fd321a613dc..8bc0f3fbb3c8d 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/misc/unused-using-decls.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/misc/unused-using-decls.cpp
@@ -168,9 +168,7 @@ using n::K;
 
 using n::N;
 
-// FIXME: Currently non-type template arguments are not supported.
 using n::Constant;
-// CHECK-MESSAGES: :[[@LINE-1]]:10: warning: using decl 'Constant' is unused
 
 using n::Q;
 
diff --git a/clang-tools-extra/test/clang-tidy/checkers/modernize/avoid-c-arrays.cpp b/clang-tools-extra/test/clang-tidy/checkers/modernize/avoid-c-arrays.cpp
index 5f1bbee075b18..7d5f177c477bd 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/modernize/avoid-c-arrays.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/modernize/avoid-c-arrays.cpp
@@ -211,11 +211,12 @@ T some_constant{};
 // explicit instantiations
 template
 int some_constant<int[5]>[5];
-// CHECK-MESSAGES: :[[@LINE-1]]:19: warning: do not declare C-style arrays, use 'std::array' instead [modernize-avoid-c-arrays]
+// CHECK-MESSAGES: :[[@LINE-1]]:1: warning: do not declare C-style arrays, use 'std::array' instead [modernize-avoid-c-arrays]
+// CHECK-MESSAGES: :[[@LINE-2]]:19: warning: do not declare C-style arrays, use 'std::array' instead [modernize-avoid-c-arrays]
 
 template
 int some_constant<decltype(ak)>[4];
-// no diagnostic is expected here since explicit instantiations aren't represented as `TypeLoc` in the AST and we hence cannot match them as such
+// CHECK-MESSAGES: :[[@LINE-1]]:1: warning: do not declare C-style arrays, use 'std::array' instead [modernize-avoid-c-arrays]
 
 MyArrayType mk;
 // no diagnostic is expected here since no C-style array type got written here

>From ab583c266c9e37549d6588c5627a152deaf9a3a0 Mon Sep 17 00:00:00 2001
From: ykiko <ykikoykikoykiko at gmail.com>
Date: Sun, 12 Apr 2026 10:24:16 +0800
Subject: [PATCH 07/15] address review nits and add ast-print support for
 ExplicitInstantiationDecl

---
 clang/include/clang/AST/DeclTemplate.h        | 26 ++++----
 clang/lib/AST/DeclPrinter.cpp                 | 39 +++++++++++
 clang/lib/AST/DeclTemplate.cpp                |  8 +--
 clang/lib/Sema/SemaTemplate.cpp               | 66 ++++++++-----------
 .../lib/Sema/SemaTemplateInstantiateDecl.cpp  |  1 -
 clang/lib/Serialization/ASTReaderDecl.cpp     |  2 +-
 clang/lib/Serialization/ASTWriterDecl.cpp     |  2 +-
 .../AST/ast-print-explicit-instantiation.cpp  | 41 ++++++++++++
 8 files changed, 127 insertions(+), 58 deletions(-)
 create mode 100644 clang/test/AST/ast-print-explicit-instantiation.cpp

diff --git a/clang/include/clang/AST/DeclTemplate.h b/clang/include/clang/AST/DeclTemplate.h
index f75af0c759fdf..748d50e529de4 100644
--- a/clang/include/clang/AST/DeclTemplate.h
+++ b/clang/include/clang/AST/DeclTemplate.h
@@ -3407,12 +3407,6 @@ const Decl &adjustDeclToTemplate(const Decl &D);
 
 /// Represents an explicit instantiation of a template entity in source code.
 ///
-/// This node records source location information for an explicit instantiation
-/// statement. It does not participate in name lookup (inherits from Decl, not
-/// NamedDecl), and does not affect code generation. The underlying
-/// specialization decl (FunctionDecl, VarDecl, CXXRecordDecl, etc.) continues
-/// to handle all semantic and codegen responsibilities.
-///
 /// \code
 ///   template void ns::foo<int>(int);        // function template
 ///   extern template struct ns::S<int>;      // class template (extern)
@@ -3423,8 +3417,8 @@ class ExplicitInstantiationDecl : public Decl {
   /// The underlying specialization being explicitly instantiated.
   NamedDecl *Specialization = nullptr;
 
-  /// The source range of the entire explicit instantiation statement.
-  SourceRange Range;
+  /// End location of the explicit instantiation statement.
+  SourceLocation EndLoc;
 
   /// Location of the 'extern' keyword (invalid if not extern template).
   SourceLocation ExternLoc;
@@ -3455,8 +3449,8 @@ class ExplicitInstantiationDecl : public Decl {
   LLVM_PREFERRED_TYPE(TemplateSpecializationKind)
   unsigned TSK : 3;
 
-  ExplicitInstantiationDecl(DeclContext *DC, SourceRange Range,
-                            NamedDecl *Specialization, SourceLocation ExternLoc,
+  ExplicitInstantiationDecl(DeclContext *DC, NamedDecl *Specialization,
+                            SourceLocation EndLoc, SourceLocation ExternLoc,
                             SourceLocation TemplateLoc, SourceLocation TagKWLoc,
                             NestedNameSpecifierLoc QualifierLoc,
                             const ASTTemplateArgumentListInfo *ArgsAsWritten,
@@ -3464,7 +3458,7 @@ class ExplicitInstantiationDecl : public Decl {
                             TypeSourceInfo *TypeAsWritten,
                             TemplateSpecializationKind TSK)
       : Decl(ExplicitInstantiation, DC, TemplateLoc),
-        Specialization(Specialization), Range(Range), ExternLoc(ExternLoc),
+        Specialization(Specialization), EndLoc(EndLoc), ExternLoc(ExternLoc),
         TagKWLoc(TagKWLoc), QualifierLoc(QualifierLoc),
         TemplateArgsAsWritten(ArgsAsWritten), NameLoc(NameLoc),
         TypeAsWritten(TypeAsWritten), TSK(TSK) {
@@ -3483,8 +3477,8 @@ class ExplicitInstantiationDecl : public Decl {
   friend class ASTDeclWriter;
 
   static ExplicitInstantiationDecl *
-  Create(ASTContext &C, DeclContext *DC, SourceRange Range,
-         NamedDecl *Specialization, SourceLocation ExternLoc,
+  Create(ASTContext &C, DeclContext *DC, NamedDecl *Specialization,
+         SourceLocation EndLoc, SourceLocation ExternLoc,
          SourceLocation TemplateLoc, SourceLocation TagKWLoc,
          NestedNameSpecifierLoc QualifierLoc,
          const ASTTemplateArgumentListInfo *ArgsAsWritten,
@@ -3496,7 +3490,11 @@ class ExplicitInstantiationDecl : public Decl {
 
   NamedDecl *getSpecialization() const { return Specialization; }
 
-  SourceRange getSourceRange() const override LLVM_READONLY { return Range; }
+  SourceRange getSourceRange() const override LLVM_READONLY {
+    SourceLocation Begin =
+        ExternLoc.isValid() ? ExternLoc : getLocation();
+    return SourceRange(Begin, EndLoc);
+  }
 
   SourceLocation getExternLoc() const { return ExternLoc; }
   SourceLocation getTemplateLoc() const { return getLocation(); }
diff --git a/clang/lib/AST/DeclPrinter.cpp b/clang/lib/AST/DeclPrinter.cpp
index 5e377a6c0c247..4dc29d36e94e5 100644
--- a/clang/lib/AST/DeclPrinter.cpp
+++ b/clang/lib/AST/DeclPrinter.cpp
@@ -86,6 +86,7 @@ namespace {
     void VisitTemplateDecl(const TemplateDecl *D);
     void VisitFunctionTemplateDecl(FunctionTemplateDecl *D);
     void VisitClassTemplateDecl(ClassTemplateDecl *D);
+    void VisitExplicitInstantiationDecl(ExplicitInstantiationDecl *D);
     void VisitClassTemplateSpecializationDecl(
                                             ClassTemplateSpecializationDecl *D);
     void VisitClassTemplatePartialSpecializationDecl(
@@ -1335,6 +1336,44 @@ void DeclPrinter::VisitClassTemplateDecl(ClassTemplateDecl *D) {
   }
 }
 
+void DeclPrinter::VisitExplicitInstantiationDecl(
+    ExplicitInstantiationDecl *D) {
+  if (D->isExternTemplate())
+    Out << "extern ";
+  Out << "template ";
+
+  NamedDecl *Spec = D->getSpecialization();
+
+  // Build the qualified name with template arguments.
+  std::string Name;
+  llvm::raw_string_ostream NameOS(Name);
+  if (D->getQualifierLoc())
+    D->getQualifierLoc().getNestedNameSpecifier().print(NameOS, Policy);
+  Spec->printName(NameOS, Policy);
+  if (auto *ArgsAsWritten = D->getTemplateArgsAsWritten())
+    printTemplateArgumentList(NameOS, ArgsAsWritten->arguments(), Policy);
+
+  if (auto *RD = dyn_cast<RecordDecl>(Spec)) {
+    Out << RD->getKindName() << " " << Name;
+  } else if (auto *FD = dyn_cast<FunctionDecl>(Spec)) {
+    FD->getReturnType().print(Out, Policy);
+    Out << " " << Name << "(";
+    for (unsigned I = 0, N = FD->getNumParams(); I < N; ++I) {
+      if (I)
+        Out << ", ";
+      FD->getParamDecl(I)->print(Out, Policy);
+    }
+    if (FD->isVariadic()) {
+      if (FD->getNumParams())
+        Out << ", ";
+      Out << "...";
+    }
+    Out << ")";
+  } else if (auto *TSI = D->getTypeAsWritten()) {
+    TSI->getType().print(Out, Policy, Name);
+  }
+}
+
 void DeclPrinter::VisitClassTemplateSpecializationDecl(
                                            ClassTemplateSpecializationDecl *D) {
   Out << "template<> ";
diff --git a/clang/lib/AST/DeclTemplate.cpp b/clang/lib/AST/DeclTemplate.cpp
index ecce1a43d298c..b502f328a6f9b 100644
--- a/clang/lib/AST/DeclTemplate.cpp
+++ b/clang/lib/AST/DeclTemplate.cpp
@@ -1788,15 +1788,15 @@ const Decl &clang::adjustDeclToTemplate(const Decl &D) {
 void ExplicitInstantiationDecl::anchor() {}
 
 ExplicitInstantiationDecl *ExplicitInstantiationDecl::Create(
-    ASTContext &C, DeclContext *DC, SourceRange Range,
-    NamedDecl *Specialization, SourceLocation ExternLoc,
+    ASTContext &C, DeclContext *DC, NamedDecl *Specialization,
+    SourceLocation EndLoc, SourceLocation ExternLoc,
     SourceLocation TemplateLoc, SourceLocation TagKWLoc,
     NestedNameSpecifierLoc QualifierLoc,
     const ASTTemplateArgumentListInfo *ArgsAsWritten, SourceLocation NameLoc,
     TypeSourceInfo *TypeAsWritten, TemplateSpecializationKind TSK) {
   return new (C, DC) ExplicitInstantiationDecl(
-      DC, Range, Specialization, ExternLoc, TemplateLoc, TagKWLoc, QualifierLoc,
-      ArgsAsWritten, NameLoc, TypeAsWritten, TSK);
+      DC, Specialization, EndLoc, ExternLoc, TemplateLoc, TagKWLoc,
+      QualifierLoc, ArgsAsWritten, NameLoc, TypeAsWritten, TSK);
 }
 
 ExplicitInstantiationDecl *
diff --git a/clang/lib/Sema/SemaTemplate.cpp b/clang/lib/Sema/SemaTemplate.cpp
index a94f0e2fe3c1e..a523c7ef9a885 100644
--- a/clang/lib/Sema/SemaTemplate.cpp
+++ b/clang/lib/Sema/SemaTemplate.cpp
@@ -9322,14 +9322,10 @@ static void addExplicitInstantiationDecl(
     SourceLocation TemplateLoc, SourceLocation TagKWLoc,
     const ASTTemplateArgumentListInfo *ArgsAsWritten, SourceLocation NameLoc,
     TypeSourceInfo *TypeAsWritten, TemplateSpecializationKind TSK) {
-  NestedNameSpecifierLoc QualifierLoc;
-  if (SS.isNotEmpty())
-    QualifierLoc = SS.getWithLocInContext(Context);
-  SourceLocation BeginLoc = ExternLoc.isValid() ? ExternLoc : TemplateLoc;
+  NestedNameSpecifierLoc QualifierLoc = SS.getWithLocInContext(Context);
   auto *EID = ExplicitInstantiationDecl::Create(
-      Context, CurContext, SourceRange(BeginLoc, EndLoc), Spec, ExternLoc,
-      TemplateLoc, TagKWLoc, QualifierLoc, ArgsAsWritten, NameLoc,
-      TypeAsWritten, TSK);
+      Context, CurContext, Spec, EndLoc, ExternLoc, TemplateLoc, TagKWLoc,
+      QualifierLoc, ArgsAsWritten, NameLoc, TypeAsWritten, TSK);
   CurContext->addDecl(EID);
 }
 
@@ -10869,19 +10865,17 @@ DeclResult Sema::ActOnExplicitInstantiation(Scope *S,
       return true;
     }
 
-    {
-      const ASTTemplateArgumentListInfo *ArgsAsWritten = nullptr;
-      if (auto *VTSD = dyn_cast<VarTemplateSpecializationDecl>(Prev))
-        ArgsAsWritten = VTSD->getTemplateArgsAsWritten();
-      addExplicitInstantiationDecl(Context, CurContext, D.getCXXScopeSpec(),
-                                   D.getSourceRange().getEnd(), Prev, ExternLoc,
-                                   TemplateLoc, SourceLocation(), ArgsAsWritten,
-                                   D.getIdentifierLoc(), T, TSK);
-      // Don't return the EID to the Parser — doing so would trigger
-      // unrelated semantic actions (e.g. access checks via
-      // FinalizeDeclaration).
-      return (Decl *)nullptr;
-    }
+    const ASTTemplateArgumentListInfo *ArgsAsWritten = nullptr;
+    if (auto *VTSD = dyn_cast<VarTemplateSpecializationDecl>(Prev))
+      ArgsAsWritten = VTSD->getTemplateArgsAsWritten();
+    addExplicitInstantiationDecl(
+        Context, CurContext, D.getCXXScopeSpec(), D.getSourceRange().getEnd(),
+        Prev, ExternLoc, TemplateLoc, SourceLocation(), ArgsAsWritten,
+        D.getIdentifierLoc(), T, TSK);
+    // Don't return the EID to the Parser — doing so would trigger
+    // unrelated semantic actions (e.g. access checks via
+    // FinalizeDeclaration).
+    return (Decl *)nullptr;
   }
 
   // If the declarator is a template-id, translate the parser's template
@@ -11063,10 +11057,10 @@ DeclResult Sema::ActOnExplicitInstantiation(Scope *S,
       if (HasExplicitTemplateArgs)
         ArgsAsWritten =
             ASTTemplateArgumentListInfo::Create(Context, TemplateArgs);
-      addExplicitInstantiationDecl(Context, CurContext, D.getCXXScopeSpec(),
-                                   D.getSourceRange().getEnd(), Specialization,
-                                   ExternLoc, TemplateLoc, SourceLocation(),
-                                   ArgsAsWritten, D.getIdentifierLoc(), T, TSK);
+      addExplicitInstantiationDecl(
+          Context, CurContext, D.getCXXScopeSpec(),
+          D.getSourceRange().getEnd(), Specialization, ExternLoc, TemplateLoc,
+          SourceLocation(), ArgsAsWritten, D.getIdentifierLoc(), T, TSK);
       // Don't return the EID to the Parser — doing so would trigger
       // unrelated semantic actions (e.g. access checks via
       // FinalizeDeclaration).
@@ -11127,19 +11121,17 @@ DeclResult Sema::ActOnExplicitInstantiation(Scope *S,
               : Specialization->getInstantiatedFromMemberFunction(),
       D.getIdentifierLoc(), D.getCXXScopeSpec().isSet(), TSK);
 
-  {
-    const ASTTemplateArgumentListInfo *ArgsAsWritten = nullptr;
-    if (HasExplicitTemplateArgs)
-      ArgsAsWritten =
-          ASTTemplateArgumentListInfo::Create(Context, TemplateArgs);
-    addExplicitInstantiationDecl(Context, CurContext, D.getCXXScopeSpec(),
-                                 D.getSourceRange().getEnd(), Specialization,
-                                 ExternLoc, TemplateLoc, SourceLocation(),
-                                 ArgsAsWritten, D.getIdentifierLoc(), T, TSK);
-    // Don't return the EID to the Parser — doing so would trigger
-    // unrelated semantic actions (e.g. access checks via FinalizeDeclaration).
-    return (Decl *)nullptr;
-  }
+  const ASTTemplateArgumentListInfo *ArgsAsWritten = nullptr;
+  if (HasExplicitTemplateArgs)
+    ArgsAsWritten =
+        ASTTemplateArgumentListInfo::Create(Context, TemplateArgs);
+  addExplicitInstantiationDecl(
+      Context, CurContext, D.getCXXScopeSpec(), D.getSourceRange().getEnd(),
+      Specialization, ExternLoc, TemplateLoc, SourceLocation(), ArgsAsWritten,
+      D.getIdentifierLoc(), T, TSK);
+  // Don't return the EID to the Parser — doing so would trigger
+  // unrelated semantic actions (e.g. access checks via FinalizeDeclaration).
+  return (Decl *)nullptr;
 }
 
 TypeResult Sema::ActOnDependentTag(Scope *S, unsigned TagSpec, TagUseKind TUK,
diff --git a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
index f307e813ff27a..f44ce02196c5c 100644
--- a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
+++ b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
@@ -2106,7 +2106,6 @@ Decl *TemplateDeclInstantiator::VisitExplicitInstantiationDecl(
   // ExplicitInstantiationDecl is a source-info-only node and should not
   // appear inside a template pattern. Nothing to instantiate.
   llvm_unreachable("ExplicitInstantiationDecl should not be instantiated");
-  return nullptr;
 }
 
 Decl *TemplateDeclInstantiator::VisitEnumDecl(EnumDecl *D) {
diff --git a/clang/lib/Serialization/ASTReaderDecl.cpp b/clang/lib/Serialization/ASTReaderDecl.cpp
index eb13e15b92f14..a7a4e1d56731e 100644
--- a/clang/lib/Serialization/ASTReaderDecl.cpp
+++ b/clang/lib/Serialization/ASTReaderDecl.cpp
@@ -2790,7 +2790,7 @@ void ASTDeclReader::VisitExplicitInstantiationDecl(
     ExplicitInstantiationDecl *D) {
   VisitDecl(D);
   D->Specialization = readDeclAs<NamedDecl>();
-  D->Range = readSourceRange();
+  D->EndLoc = readSourceLocation();
   D->ExternLoc = readSourceLocation();
   D->TagKWLoc = readSourceLocation();
   D->QualifierLoc = Record.readNestedNameSpecifierLoc();
diff --git a/clang/lib/Serialization/ASTWriterDecl.cpp b/clang/lib/Serialization/ASTWriterDecl.cpp
index 42610a1985f60..24ec1ea46d821 100644
--- a/clang/lib/Serialization/ASTWriterDecl.cpp
+++ b/clang/lib/Serialization/ASTWriterDecl.cpp
@@ -2181,7 +2181,7 @@ void ASTDeclWriter::VisitExplicitInstantiationDecl(
     ExplicitInstantiationDecl *D) {
   VisitDecl(D);
   Record.AddDeclRef(D->getSpecialization());
-  Record.AddSourceRange(D->getSourceRange());
+  Record.AddSourceLocation(D->getEndLoc());
   Record.AddSourceLocation(D->getExternLoc());
   Record.AddSourceLocation(D->getTagKWLoc());
   Record.AddNestedNameSpecifierLoc(D->getQualifierLoc());
diff --git a/clang/test/AST/ast-print-explicit-instantiation.cpp b/clang/test/AST/ast-print-explicit-instantiation.cpp
new file mode 100644
index 0000000000000..0e63269c78485
--- /dev/null
+++ b/clang/test/AST/ast-print-explicit-instantiation.cpp
@@ -0,0 +1,41 @@
+// RUN: %clang_cc1 -ast-print %s -o - -std=c++17 | FileCheck %s
+
+namespace ns {
+template <typename T> struct S { T x; };
+template <typename T> void foo(T) {}
+template <typename T> T var = T();
+}
+
+// Class template explicit instantiation.
+// CHECK: template struct ns::S<int>;
+template struct ns::S<int>;
+// CHECK: extern template struct ns::S<float>;
+extern template struct ns::S<float>;
+
+// Function template explicit instantiation.
+// CHECK: template void ns::foo<int>(int);
+template void ns::foo<int>(int);
+// CHECK: extern template void ns::foo<float>(float);
+extern template void ns::foo<float>(float);
+
+// Function template without explicit template args.
+template <typename T> void bar(T) {}
+// CHECK: template void bar(int);
+template void bar(int);
+
+// Variable template explicit instantiation.
+// CHECK: template int ns::var<int>;
+template int ns::var<int>;
+// CHECK: extern template float ns::var<float>;
+extern template float ns::var<float>;
+
+// Nested class of class template.
+template <typename T> struct X { struct Inner {}; };
+// CHECK: template struct X<int>::Inner;
+template struct X<int>::Inner;
+
+// Member function of class template.
+template <typename T> struct Outer { void method(); };
+template <typename T> void Outer<T>::method() {}
+// CHECK: template void Outer<int>::method();
+template void Outer<int>::method();

>From 426dc42348260dd6e8dcc2c458f2971e776de404 Mon Sep 17 00:00:00 2001
From: ykiko <ykikoykikoykiko at gmail.com>
Date: Sun, 12 Apr 2026 10:41:01 +0800
Subject: [PATCH 08/15] compute EndLoc from existing fields instead of storing
 it

---
 clang/include/clang/AST/DeclTemplate.h    | 19 +++-----
 clang/lib/AST/DeclPrinter.cpp             |  3 +-
 clang/lib/AST/DeclTemplate.cpp            | 28 +++++++++--
 clang/lib/Sema/SemaTemplate.cpp           | 58 +++++++++++------------
 clang/lib/Serialization/ASTReaderDecl.cpp |  1 -
 clang/lib/Serialization/ASTWriterDecl.cpp |  1 -
 6 files changed, 58 insertions(+), 52 deletions(-)

diff --git a/clang/include/clang/AST/DeclTemplate.h b/clang/include/clang/AST/DeclTemplate.h
index 748d50e529de4..bfc580509c1d2 100644
--- a/clang/include/clang/AST/DeclTemplate.h
+++ b/clang/include/clang/AST/DeclTemplate.h
@@ -3417,9 +3417,6 @@ class ExplicitInstantiationDecl : public Decl {
   /// The underlying specialization being explicitly instantiated.
   NamedDecl *Specialization = nullptr;
 
-  /// End location of the explicit instantiation statement.
-  SourceLocation EndLoc;
-
   /// Location of the 'extern' keyword (invalid if not extern template).
   SourceLocation ExternLoc;
 
@@ -3450,7 +3447,7 @@ class ExplicitInstantiationDecl : public Decl {
   unsigned TSK : 3;
 
   ExplicitInstantiationDecl(DeclContext *DC, NamedDecl *Specialization,
-                            SourceLocation EndLoc, SourceLocation ExternLoc,
+                            SourceLocation ExternLoc,
                             SourceLocation TemplateLoc, SourceLocation TagKWLoc,
                             NestedNameSpecifierLoc QualifierLoc,
                             const ASTTemplateArgumentListInfo *ArgsAsWritten,
@@ -3458,7 +3455,7 @@ class ExplicitInstantiationDecl : public Decl {
                             TypeSourceInfo *TypeAsWritten,
                             TemplateSpecializationKind TSK)
       : Decl(ExplicitInstantiation, DC, TemplateLoc),
-        Specialization(Specialization), EndLoc(EndLoc), ExternLoc(ExternLoc),
+        Specialization(Specialization), ExternLoc(ExternLoc),
         TagKWLoc(TagKWLoc), QualifierLoc(QualifierLoc),
         TemplateArgsAsWritten(ArgsAsWritten), NameLoc(NameLoc),
         TypeAsWritten(TypeAsWritten), TSK(TSK) {
@@ -3478,9 +3475,8 @@ class ExplicitInstantiationDecl : public Decl {
 
   static ExplicitInstantiationDecl *
   Create(ASTContext &C, DeclContext *DC, NamedDecl *Specialization,
-         SourceLocation EndLoc, SourceLocation ExternLoc,
-         SourceLocation TemplateLoc, SourceLocation TagKWLoc,
-         NestedNameSpecifierLoc QualifierLoc,
+         SourceLocation ExternLoc, SourceLocation TemplateLoc,
+         SourceLocation TagKWLoc, NestedNameSpecifierLoc QualifierLoc,
          const ASTTemplateArgumentListInfo *ArgsAsWritten,
          SourceLocation NameLoc, TypeSourceInfo *TypeAsWritten,
          TemplateSpecializationKind TSK);
@@ -3490,11 +3486,8 @@ class ExplicitInstantiationDecl : public Decl {
 
   NamedDecl *getSpecialization() const { return Specialization; }
 
-  SourceRange getSourceRange() const override LLVM_READONLY {
-    SourceLocation Begin =
-        ExternLoc.isValid() ? ExternLoc : getLocation();
-    return SourceRange(Begin, EndLoc);
-  }
+  SourceRange getSourceRange() const override LLVM_READONLY;
+  SourceLocation getEndLoc() const LLVM_READONLY;
 
   SourceLocation getExternLoc() const { return ExternLoc; }
   SourceLocation getTemplateLoc() const { return getLocation(); }
diff --git a/clang/lib/AST/DeclPrinter.cpp b/clang/lib/AST/DeclPrinter.cpp
index 4dc29d36e94e5..0cba2f46d6b40 100644
--- a/clang/lib/AST/DeclPrinter.cpp
+++ b/clang/lib/AST/DeclPrinter.cpp
@@ -1336,8 +1336,7 @@ void DeclPrinter::VisitClassTemplateDecl(ClassTemplateDecl *D) {
   }
 }
 
-void DeclPrinter::VisitExplicitInstantiationDecl(
-    ExplicitInstantiationDecl *D) {
+void DeclPrinter::VisitExplicitInstantiationDecl(ExplicitInstantiationDecl *D) {
   if (D->isExternTemplate())
     Out << "extern ";
   Out << "template ";
diff --git a/clang/lib/AST/DeclTemplate.cpp b/clang/lib/AST/DeclTemplate.cpp
index b502f328a6f9b..6c290a9f2f9e1 100644
--- a/clang/lib/AST/DeclTemplate.cpp
+++ b/clang/lib/AST/DeclTemplate.cpp
@@ -1789,17 +1789,35 @@ void ExplicitInstantiationDecl::anchor() {}
 
 ExplicitInstantiationDecl *ExplicitInstantiationDecl::Create(
     ASTContext &C, DeclContext *DC, NamedDecl *Specialization,
-    SourceLocation EndLoc, SourceLocation ExternLoc,
-    SourceLocation TemplateLoc, SourceLocation TagKWLoc,
-    NestedNameSpecifierLoc QualifierLoc,
+    SourceLocation ExternLoc, SourceLocation TemplateLoc,
+    SourceLocation TagKWLoc, NestedNameSpecifierLoc QualifierLoc,
     const ASTTemplateArgumentListInfo *ArgsAsWritten, SourceLocation NameLoc,
     TypeSourceInfo *TypeAsWritten, TemplateSpecializationKind TSK) {
   return new (C, DC) ExplicitInstantiationDecl(
-      DC, Specialization, EndLoc, ExternLoc, TemplateLoc, TagKWLoc,
-      QualifierLoc, ArgsAsWritten, NameLoc, TypeAsWritten, TSK);
+      DC, Specialization, ExternLoc, TemplateLoc, TagKWLoc, QualifierLoc,
+      ArgsAsWritten, NameLoc, TypeAsWritten, TSK);
 }
 
 ExplicitInstantiationDecl *
 ExplicitInstantiationDecl::CreateDeserialized(ASTContext &C, GlobalDeclID ID) {
   return new (C, ID) ExplicitInstantiationDecl(EmptyShell());
 }
+
+SourceLocation ExplicitInstantiationDecl::getEndLoc() const {
+  if (TypeAsWritten) {
+    if (auto FTL = TypeAsWritten->getTypeLoc().getAs<FunctionTypeLoc>())
+      return FTL.getRParenLoc();
+    if (TemplateArgsAsWritten)
+      return TemplateArgsAsWritten->RAngleLoc;
+    // Static data member: end at the name.
+    return NameLoc;
+  }
+  if (TemplateArgsAsWritten)
+    return TemplateArgsAsWritten->RAngleLoc;
+  return NameLoc;
+}
+
+SourceRange ExplicitInstantiationDecl::getSourceRange() const {
+  SourceLocation Begin = ExternLoc.isValid() ? ExternLoc : getLocation();
+  return SourceRange(Begin, getEndLoc());
+}
diff --git a/clang/lib/Sema/SemaTemplate.cpp b/clang/lib/Sema/SemaTemplate.cpp
index a523c7ef9a885..b589b5870ba44 100644
--- a/clang/lib/Sema/SemaTemplate.cpp
+++ b/clang/lib/Sema/SemaTemplate.cpp
@@ -9318,14 +9318,14 @@ static void StripImplicitInstantiation(NamedDecl *D, bool MinGW) {
 /// explicit template instantiation statement, and add it to \p CurContext.
 static void addExplicitInstantiationDecl(
     ASTContext &Context, DeclContext *CurContext, const CXXScopeSpec &SS,
-    SourceLocation EndLoc, NamedDecl *Spec, SourceLocation ExternLoc,
-    SourceLocation TemplateLoc, SourceLocation TagKWLoc,
-    const ASTTemplateArgumentListInfo *ArgsAsWritten, SourceLocation NameLoc,
-    TypeSourceInfo *TypeAsWritten, TemplateSpecializationKind TSK) {
+    NamedDecl *Spec, SourceLocation ExternLoc, SourceLocation TemplateLoc,
+    SourceLocation TagKWLoc, const ASTTemplateArgumentListInfo *ArgsAsWritten,
+    SourceLocation NameLoc, TypeSourceInfo *TypeAsWritten,
+    TemplateSpecializationKind TSK) {
   NestedNameSpecifierLoc QualifierLoc = SS.getWithLocInContext(Context);
   auto *EID = ExplicitInstantiationDecl::Create(
-      Context, CurContext, Spec, EndLoc, ExternLoc, TemplateLoc, TagKWLoc,
-      QualifierLoc, ArgsAsWritten, NameLoc, TypeAsWritten, TSK);
+      Context, CurContext, Spec, ExternLoc, TemplateLoc, TagKWLoc, QualifierLoc,
+      ArgsAsWritten, NameLoc, TypeAsWritten, TSK);
   CurContext->addDecl(EID);
 }
 
@@ -10433,8 +10433,8 @@ DeclResult Sema::ActOnExplicitInstantiation(
     // Set the template specialization kind.
     Specialization->setTemplateSpecializationKind(TSK);
 
-    addExplicitInstantiationDecl(Context, CurContext, SS, RAngleLoc,
-                                 Specialization, ExternLoc, TemplateLoc, KWLoc,
+    addExplicitInstantiationDecl(Context, CurContext, SS, Specialization,
+                                 ExternLoc, TemplateLoc, KWLoc,
                                  Specialization->getTemplateArgsAsWritten(),
                                  TemplateNameLoc, nullptr, TSK);
     return Specialization;
@@ -10526,8 +10526,8 @@ DeclResult Sema::ActOnExplicitInstantiation(
     Specialization->setTemplateSpecializationKind(TSK);
   }
 
-  addExplicitInstantiationDecl(Context, CurContext, SS, RAngleLoc,
-                               Specialization, ExternLoc, TemplateLoc, KWLoc,
+  addExplicitInstantiationDecl(Context, CurContext, SS, Specialization,
+                               ExternLoc, TemplateLoc, KWLoc,
                                Specialization->getTemplateArgsAsWritten(),
                                TemplateNameLoc, nullptr, TSK);
   return Specialization;
@@ -10605,9 +10605,9 @@ Sema::ActOnExplicitInstantiation(Scope *S, SourceLocation ExternLoc,
                                                HasNoEffect))
       return true;
     if (HasNoEffect) {
-      addExplicitInstantiationDecl(Context, CurContext, SS, NameLoc, Record,
-                                   ExternLoc, TemplateLoc, KWLoc, nullptr,
-                                   NameLoc, nullptr, TSK);
+      addExplicitInstantiationDecl(Context, CurContext, SS, Record, ExternLoc,
+                                   TemplateLoc, KWLoc, nullptr, NameLoc,
+                                   nullptr, TSK);
       return TagD;
     }
   }
@@ -10645,9 +10645,9 @@ Sema::ActOnExplicitInstantiation(Scope *S, SourceLocation ExternLoc,
   if (TSK == TSK_ExplicitInstantiationDefinition)
     MarkVTableUsed(NameLoc, RecordDef, true);
 
-  addExplicitInstantiationDecl(Context, CurContext, SS, NameLoc, Record,
-                               ExternLoc, TemplateLoc, KWLoc, nullptr, NameLoc,
-                               nullptr, TSK);
+  addExplicitInstantiationDecl(Context, CurContext, SS, Record, ExternLoc,
+                               TemplateLoc, KWLoc, nullptr, NameLoc, nullptr,
+                               TSK);
   return TagD;
 }
 
@@ -10868,10 +10868,9 @@ DeclResult Sema::ActOnExplicitInstantiation(Scope *S,
     const ASTTemplateArgumentListInfo *ArgsAsWritten = nullptr;
     if (auto *VTSD = dyn_cast<VarTemplateSpecializationDecl>(Prev))
       ArgsAsWritten = VTSD->getTemplateArgsAsWritten();
-    addExplicitInstantiationDecl(
-        Context, CurContext, D.getCXXScopeSpec(), D.getSourceRange().getEnd(),
-        Prev, ExternLoc, TemplateLoc, SourceLocation(), ArgsAsWritten,
-        D.getIdentifierLoc(), T, TSK);
+    addExplicitInstantiationDecl(Context, CurContext, D.getCXXScopeSpec(), Prev,
+                                 ExternLoc, TemplateLoc, SourceLocation(),
+                                 ArgsAsWritten, D.getIdentifierLoc(), T, TSK);
     // Don't return the EID to the Parser — doing so would trigger
     // unrelated semantic actions (e.g. access checks via
     // FinalizeDeclaration).
@@ -11057,10 +11056,10 @@ DeclResult Sema::ActOnExplicitInstantiation(Scope *S,
       if (HasExplicitTemplateArgs)
         ArgsAsWritten =
             ASTTemplateArgumentListInfo::Create(Context, TemplateArgs);
-      addExplicitInstantiationDecl(
-          Context, CurContext, D.getCXXScopeSpec(),
-          D.getSourceRange().getEnd(), Specialization, ExternLoc, TemplateLoc,
-          SourceLocation(), ArgsAsWritten, D.getIdentifierLoc(), T, TSK);
+      addExplicitInstantiationDecl(Context, CurContext, D.getCXXScopeSpec(),
+                                   Specialization, ExternLoc, TemplateLoc,
+                                   SourceLocation(), ArgsAsWritten,
+                                   D.getIdentifierLoc(), T, TSK);
       // Don't return the EID to the Parser — doing so would trigger
       // unrelated semantic actions (e.g. access checks via
       // FinalizeDeclaration).
@@ -11123,12 +11122,11 @@ DeclResult Sema::ActOnExplicitInstantiation(Scope *S,
 
   const ASTTemplateArgumentListInfo *ArgsAsWritten = nullptr;
   if (HasExplicitTemplateArgs)
-    ArgsAsWritten =
-        ASTTemplateArgumentListInfo::Create(Context, TemplateArgs);
-  addExplicitInstantiationDecl(
-      Context, CurContext, D.getCXXScopeSpec(), D.getSourceRange().getEnd(),
-      Specialization, ExternLoc, TemplateLoc, SourceLocation(), ArgsAsWritten,
-      D.getIdentifierLoc(), T, TSK);
+    ArgsAsWritten = ASTTemplateArgumentListInfo::Create(Context, TemplateArgs);
+  addExplicitInstantiationDecl(Context, CurContext, D.getCXXScopeSpec(),
+                               Specialization, ExternLoc, TemplateLoc,
+                               SourceLocation(), ArgsAsWritten,
+                               D.getIdentifierLoc(), T, TSK);
   // Don't return the EID to the Parser — doing so would trigger
   // unrelated semantic actions (e.g. access checks via FinalizeDeclaration).
   return (Decl *)nullptr;
diff --git a/clang/lib/Serialization/ASTReaderDecl.cpp b/clang/lib/Serialization/ASTReaderDecl.cpp
index a7a4e1d56731e..52612e1145081 100644
--- a/clang/lib/Serialization/ASTReaderDecl.cpp
+++ b/clang/lib/Serialization/ASTReaderDecl.cpp
@@ -2790,7 +2790,6 @@ void ASTDeclReader::VisitExplicitInstantiationDecl(
     ExplicitInstantiationDecl *D) {
   VisitDecl(D);
   D->Specialization = readDeclAs<NamedDecl>();
-  D->EndLoc = readSourceLocation();
   D->ExternLoc = readSourceLocation();
   D->TagKWLoc = readSourceLocation();
   D->QualifierLoc = Record.readNestedNameSpecifierLoc();
diff --git a/clang/lib/Serialization/ASTWriterDecl.cpp b/clang/lib/Serialization/ASTWriterDecl.cpp
index 24ec1ea46d821..706cb161e3b0b 100644
--- a/clang/lib/Serialization/ASTWriterDecl.cpp
+++ b/clang/lib/Serialization/ASTWriterDecl.cpp
@@ -2181,7 +2181,6 @@ void ASTDeclWriter::VisitExplicitInstantiationDecl(
     ExplicitInstantiationDecl *D) {
   VisitDecl(D);
   Record.AddDeclRef(D->getSpecialization());
-  Record.AddSourceLocation(D->getEndLoc());
   Record.AddSourceLocation(D->getExternLoc());
   Record.AddSourceLocation(D->getTagKWLoc());
   Record.AddNestedNameSpecifierLoc(D->getQualifierLoc());

>From 7a2129fc1a8e722b2b50f68f3d78150d19e4becf Mon Sep 17 00:00:00 2001
From: ykiko <ykikoykikoykiko at gmail.com>
Date: Sun, 12 Apr 2026 11:59:46 +0800
Subject: [PATCH 09/15] address review feedback: return EID, wrap test in
 namespace

---
 clang/lib/Sema/SemaTemplate.cpp               | 37 +++++++------------
 .../AST/ast-print-explicit-instantiation.cpp  |  6 ---
 .../explicit-instantiation-diag-location.cpp  | 17 ++++-----
 3 files changed, 22 insertions(+), 38 deletions(-)

diff --git a/clang/lib/Sema/SemaTemplate.cpp b/clang/lib/Sema/SemaTemplate.cpp
index b589b5870ba44..5caf6108f8c93 100644
--- a/clang/lib/Sema/SemaTemplate.cpp
+++ b/clang/lib/Sema/SemaTemplate.cpp
@@ -9316,7 +9316,7 @@ static void StripImplicitInstantiation(NamedDecl *D, bool MinGW) {
 
 /// Create an ExplicitInstantiationDecl to record source-location info for an
 /// explicit template instantiation statement, and add it to \p CurContext.
-static void addExplicitInstantiationDecl(
+static ExplicitInstantiationDecl *addExplicitInstantiationDecl(
     ASTContext &Context, DeclContext *CurContext, const CXXScopeSpec &SS,
     NamedDecl *Spec, SourceLocation ExternLoc, SourceLocation TemplateLoc,
     SourceLocation TagKWLoc, const ASTTemplateArgumentListInfo *ArgsAsWritten,
@@ -9327,6 +9327,7 @@ static void addExplicitInstantiationDecl(
       Context, CurContext, Spec, ExternLoc, TemplateLoc, TagKWLoc, QualifierLoc,
       ArgsAsWritten, NameLoc, TypeAsWritten, TSK);
   CurContext->addDecl(EID);
+  return EID;
 }
 
 /// Compute the diagnostic location for an explicit instantiation
@@ -10868,13 +10869,10 @@ DeclResult Sema::ActOnExplicitInstantiation(Scope *S,
     const ASTTemplateArgumentListInfo *ArgsAsWritten = nullptr;
     if (auto *VTSD = dyn_cast<VarTemplateSpecializationDecl>(Prev))
       ArgsAsWritten = VTSD->getTemplateArgsAsWritten();
-    addExplicitInstantiationDecl(Context, CurContext, D.getCXXScopeSpec(), Prev,
-                                 ExternLoc, TemplateLoc, SourceLocation(),
-                                 ArgsAsWritten, D.getIdentifierLoc(), T, TSK);
-    // Don't return the EID to the Parser — doing so would trigger
-    // unrelated semantic actions (e.g. access checks via
-    // FinalizeDeclaration).
-    return (Decl *)nullptr;
+    return addExplicitInstantiationDecl(Context, CurContext, D.getCXXScopeSpec(),
+                                        Prev, ExternLoc, TemplateLoc,
+                                        SourceLocation(), ArgsAsWritten,
+                                        D.getIdentifierLoc(), T, TSK);
   }
 
   // If the declarator is a template-id, translate the parser's template
@@ -11056,14 +11054,10 @@ DeclResult Sema::ActOnExplicitInstantiation(Scope *S,
       if (HasExplicitTemplateArgs)
         ArgsAsWritten =
             ASTTemplateArgumentListInfo::Create(Context, TemplateArgs);
-      addExplicitInstantiationDecl(Context, CurContext, D.getCXXScopeSpec(),
-                                   Specialization, ExternLoc, TemplateLoc,
-                                   SourceLocation(), ArgsAsWritten,
-                                   D.getIdentifierLoc(), T, TSK);
-      // Don't return the EID to the Parser — doing so would trigger
-      // unrelated semantic actions (e.g. access checks via
-      // FinalizeDeclaration).
-      return (Decl *)nullptr;
+      return addExplicitInstantiationDecl(
+          Context, CurContext, D.getCXXScopeSpec(), Specialization, ExternLoc,
+          TemplateLoc, SourceLocation(), ArgsAsWritten, D.getIdentifierLoc(), T,
+          TSK);
     }
   }
 
@@ -11123,13 +11117,10 @@ DeclResult Sema::ActOnExplicitInstantiation(Scope *S,
   const ASTTemplateArgumentListInfo *ArgsAsWritten = nullptr;
   if (HasExplicitTemplateArgs)
     ArgsAsWritten = ASTTemplateArgumentListInfo::Create(Context, TemplateArgs);
-  addExplicitInstantiationDecl(Context, CurContext, D.getCXXScopeSpec(),
-                               Specialization, ExternLoc, TemplateLoc,
-                               SourceLocation(), ArgsAsWritten,
-                               D.getIdentifierLoc(), T, TSK);
-  // Don't return the EID to the Parser — doing so would trigger
-  // unrelated semantic actions (e.g. access checks via FinalizeDeclaration).
-  return (Decl *)nullptr;
+  return addExplicitInstantiationDecl(Context, CurContext, D.getCXXScopeSpec(),
+                                      Specialization, ExternLoc, TemplateLoc,
+                                      SourceLocation(), ArgsAsWritten,
+                                      D.getIdentifierLoc(), T, TSK);
 }
 
 TypeResult Sema::ActOnDependentTag(Scope *S, unsigned TagSpec, TagUseKind TUK,
diff --git a/clang/test/AST/ast-print-explicit-instantiation.cpp b/clang/test/AST/ast-print-explicit-instantiation.cpp
index 0e63269c78485..b05225c8a2dc8 100644
--- a/clang/test/AST/ast-print-explicit-instantiation.cpp
+++ b/clang/test/AST/ast-print-explicit-instantiation.cpp
@@ -6,35 +6,29 @@ template <typename T> void foo(T) {}
 template <typename T> T var = T();
 }
 
-// Class template explicit instantiation.
 // CHECK: template struct ns::S<int>;
 template struct ns::S<int>;
 // CHECK: extern template struct ns::S<float>;
 extern template struct ns::S<float>;
 
-// Function template explicit instantiation.
 // CHECK: template void ns::foo<int>(int);
 template void ns::foo<int>(int);
 // CHECK: extern template void ns::foo<float>(float);
 extern template void ns::foo<float>(float);
 
-// Function template without explicit template args.
 template <typename T> void bar(T) {}
 // CHECK: template void bar(int);
 template void bar(int);
 
-// Variable template explicit instantiation.
 // CHECK: template int ns::var<int>;
 template int ns::var<int>;
 // CHECK: extern template float ns::var<float>;
 extern template float ns::var<float>;
 
-// Nested class of class template.
 template <typename T> struct X { struct Inner {}; };
 // CHECK: template struct X<int>::Inner;
 template struct X<int>::Inner;
 
-// Member function of class template.
 template <typename T> struct Outer { void method(); };
 template <typename T> void Outer<T>::method() {}
 // CHECK: template void Outer<int>::method();
diff --git a/clang/test/SemaTemplate/explicit-instantiation-diag-location.cpp b/clang/test/SemaTemplate/explicit-instantiation-diag-location.cpp
index b8d22efaac7df..7d9c35a07570a 100644
--- a/clang/test/SemaTemplate/explicit-instantiation-diag-location.cpp
+++ b/clang/test/SemaTemplate/explicit-instantiation-diag-location.cpp
@@ -2,21 +2,24 @@
 
 // Verify that the "previous explicit instantiation" note points to the first
 // explicit instantiation statement, not to the implicit instantiation site.
-// This is a regression test for https://github.com/llvm/llvm-project/issues/21133
 
-// Function template with implicit instantiation before explicit.
+namespace GH21133 {
+
 template <typename T> void f() {}
 void use_f() { f<int>(); }
 template void f<int>();          // expected-note{{previous explicit instantiation is here}}
 template void f<int>();          // expected-error{{duplicate explicit instantiation of 'f<int>'}}
 
-// Class template with implicit instantiation before explicit.
 template <typename T> struct S {};
 void use_S(S<int>) {}
 template struct S<int>;          // expected-note{{previous explicit instantiation is here}}
 template struct S<int>;          // expected-error{{duplicate explicit instantiation of 'S<int>'}}
 
-// Cross-namespace: template in ns, explicit instantiation at global scope.
+template <typename T> T var = T{};
+int use_var = var<int>;
+template int var<int>;           // expected-note{{previous explicit instantiation is here}}
+template int var<int>;           // expected-error{{duplicate explicit instantiation of 'var<int>'}}
+
 namespace ns {
   template <typename T> void g() {}
 }
@@ -24,8 +27,4 @@ void use_g() { ns::g<double>(); }
 template void ns::g<double>();   // expected-note{{previous explicit instantiation is here}}
 template void ns::g<double>();   // expected-error{{duplicate explicit instantiation of 'g<double>'}}
 
-// Variable template with implicit instantiation before explicit.
-template <typename T> T var = T{};
-int use_var = var<int>;
-template int var<int>;           // expected-note{{previous explicit instantiation is here}}
-template int var<int>;           // expected-error{{duplicate explicit instantiation of 'var<int>'}}
+} // namespace GH21133

>From 104d21e220afba344dc8232ca3643eccbc04ccab Mon Sep 17 00:00:00 2001
From: ykiko <ykikoykikoykiko at gmail.com>
Date: Sun, 12 Apr 2026 12:17:00 +0800
Subject: [PATCH 10/15] revert returning EID from ActOnExplicitInstantiation

Returning EID to the Parser triggers access checks on private
members, causing test failures. Revert to returning nullptr.
---
 clang/lib/Sema/SemaTemplate.cpp | 29 +++++++++++++++--------------
 1 file changed, 15 insertions(+), 14 deletions(-)

diff --git a/clang/lib/Sema/SemaTemplate.cpp b/clang/lib/Sema/SemaTemplate.cpp
index 5caf6108f8c93..5cdbe6fe06812 100644
--- a/clang/lib/Sema/SemaTemplate.cpp
+++ b/clang/lib/Sema/SemaTemplate.cpp
@@ -9316,7 +9316,7 @@ static void StripImplicitInstantiation(NamedDecl *D, bool MinGW) {
 
 /// Create an ExplicitInstantiationDecl to record source-location info for an
 /// explicit template instantiation statement, and add it to \p CurContext.
-static ExplicitInstantiationDecl *addExplicitInstantiationDecl(
+static void addExplicitInstantiationDecl(
     ASTContext &Context, DeclContext *CurContext, const CXXScopeSpec &SS,
     NamedDecl *Spec, SourceLocation ExternLoc, SourceLocation TemplateLoc,
     SourceLocation TagKWLoc, const ASTTemplateArgumentListInfo *ArgsAsWritten,
@@ -9327,7 +9327,6 @@ static ExplicitInstantiationDecl *addExplicitInstantiationDecl(
       Context, CurContext, Spec, ExternLoc, TemplateLoc, TagKWLoc, QualifierLoc,
       ArgsAsWritten, NameLoc, TypeAsWritten, TSK);
   CurContext->addDecl(EID);
-  return EID;
 }
 
 /// Compute the diagnostic location for an explicit instantiation
@@ -10869,10 +10868,10 @@ DeclResult Sema::ActOnExplicitInstantiation(Scope *S,
     const ASTTemplateArgumentListInfo *ArgsAsWritten = nullptr;
     if (auto *VTSD = dyn_cast<VarTemplateSpecializationDecl>(Prev))
       ArgsAsWritten = VTSD->getTemplateArgsAsWritten();
-    return addExplicitInstantiationDecl(Context, CurContext, D.getCXXScopeSpec(),
-                                        Prev, ExternLoc, TemplateLoc,
-                                        SourceLocation(), ArgsAsWritten,
-                                        D.getIdentifierLoc(), T, TSK);
+    addExplicitInstantiationDecl(Context, CurContext, D.getCXXScopeSpec(), Prev,
+                                 ExternLoc, TemplateLoc, SourceLocation(),
+                                 ArgsAsWritten, D.getIdentifierLoc(), T, TSK);
+    return (Decl *)nullptr;
   }
 
   // If the declarator is a template-id, translate the parser's template
@@ -11054,10 +11053,11 @@ DeclResult Sema::ActOnExplicitInstantiation(Scope *S,
       if (HasExplicitTemplateArgs)
         ArgsAsWritten =
             ASTTemplateArgumentListInfo::Create(Context, TemplateArgs);
-      return addExplicitInstantiationDecl(
-          Context, CurContext, D.getCXXScopeSpec(), Specialization, ExternLoc,
-          TemplateLoc, SourceLocation(), ArgsAsWritten, D.getIdentifierLoc(), T,
-          TSK);
+      addExplicitInstantiationDecl(Context, CurContext, D.getCXXScopeSpec(),
+                                   Specialization, ExternLoc, TemplateLoc,
+                                   SourceLocation(), ArgsAsWritten,
+                                   D.getIdentifierLoc(), T, TSK);
+      return (Decl *)nullptr;
     }
   }
 
@@ -11117,10 +11117,11 @@ DeclResult Sema::ActOnExplicitInstantiation(Scope *S,
   const ASTTemplateArgumentListInfo *ArgsAsWritten = nullptr;
   if (HasExplicitTemplateArgs)
     ArgsAsWritten = ASTTemplateArgumentListInfo::Create(Context, TemplateArgs);
-  return addExplicitInstantiationDecl(Context, CurContext, D.getCXXScopeSpec(),
-                                      Specialization, ExternLoc, TemplateLoc,
-                                      SourceLocation(), ArgsAsWritten,
-                                      D.getIdentifierLoc(), T, TSK);
+  addExplicitInstantiationDecl(Context, CurContext, D.getCXXScopeSpec(),
+                               Specialization, ExternLoc, TemplateLoc,
+                               SourceLocation(), ArgsAsWritten,
+                               D.getIdentifierLoc(), T, TSK);
+  return (Decl *)nullptr;
 }
 
 TypeResult Sema::ActOnDependentTag(Scope *S, unsigned TagSpec, TagUseKind TUK,

>From f359da203a5bb0084067b0c8bae87aba3044ddfd Mon Sep 17 00:00:00 2001
From: ykiko <ykikoykikoykiko at gmail.com>
Date: Sun, 12 Apr 2026 12:54:38 +0800
Subject: [PATCH 11/15] fix DiagLocForExplicitInstantiation and add member
 template tests

---
 clang/lib/Sema/SemaTemplate.cpp               |  2 +-
 .../AST/ast-print-explicit-instantiation.cpp  | 27 ++++++++++++-
 .../explicit-instantiation-source-info.cpp    | 38 +++++++++++++++++++
 .../explicit-instantiation-diag-location.cpp  | 37 ++++++++++++++++++
 4 files changed, 102 insertions(+), 2 deletions(-)

diff --git a/clang/lib/Sema/SemaTemplate.cpp b/clang/lib/Sema/SemaTemplate.cpp
index 5cdbe6fe06812..9ded4de683ded 100644
--- a/clang/lib/Sema/SemaTemplate.cpp
+++ b/clang/lib/Sema/SemaTemplate.cpp
@@ -9339,7 +9339,7 @@ static SourceLocation DiagLocForExplicitInstantiation(
   for (DeclContext *DC = D->getDeclContext(); DC; DC = DC->getParent()) {
     for (auto *Decl : DC->decls()) {
       if (auto *EID = dyn_cast<ExplicitInstantiationDecl>(Decl))
-        if (EID->getSpecialization() == D)
+        if (EID->getSpecialization()->getCanonicalDecl() == D->getCanonicalDecl())
           return EID->getTemplateLoc();
     }
   }
diff --git a/clang/test/AST/ast-print-explicit-instantiation.cpp b/clang/test/AST/ast-print-explicit-instantiation.cpp
index b05225c8a2dc8..6c794e9575039 100644
--- a/clang/test/AST/ast-print-explicit-instantiation.cpp
+++ b/clang/test/AST/ast-print-explicit-instantiation.cpp
@@ -29,7 +29,32 @@ template <typename T> struct X { struct Inner {}; };
 // CHECK: template struct X<int>::Inner;
 template struct X<int>::Inner;
 
-template <typename T> struct Outer { void method(); };
+template <typename T> struct Outer {
+  void method();
+  template <typename U> void f(U);
+  template <typename U> static U var;
+  template <typename U> struct Inner {};
+};
 template <typename T> void Outer<T>::method() {}
+template <typename T> template <typename U> void Outer<T>::f(U) {}
+template <typename T> template <typename U> U Outer<T>::var = U{};
+
 // CHECK: template void Outer<int>::method();
 template void Outer<int>::method();
+// CHECK: template void Outer<int>::f<double>(double);
+template void Outer<int>::f<double>(double);
+// CHECK: template double Outer<int>::var<double>;
+template double Outer<int>::var<double>;
+// CHECK: template struct Outer<int>::Inner<double>;
+template struct Outer<int>::Inner<double>;
+
+template <typename T> struct A {
+  template <typename U> struct B {
+    template <typename V> void g(V);
+  };
+};
+template <typename T> template <typename U> template <typename V>
+void A<T>::B<U>::g(V) {}
+
+// CHECK: template void A<int>::B<double>::g<float>(float);
+template void A<int>::B<double>::g<float>(float);
diff --git a/clang/test/AST/explicit-instantiation-source-info.cpp b/clang/test/AST/explicit-instantiation-source-info.cpp
index 0903ba140cb9e..e24c3dae8a5b1 100644
--- a/clang/test/AST/explicit-instantiation-source-info.cpp
+++ b/clang/test/AST/explicit-instantiation-source-info.cpp
@@ -7,9 +7,18 @@ namespace ns {
     void method(T x) {}
     static T sval;
     template <typename U> void tmpl(U u) {}
+    template <typename U> static U mvar;
+    template <typename U> struct Nested {};
     struct Inner { T val; };
   };
   template <typename T> T S<T>::sval = T{};
+  template <typename T> template <typename U> U S<T>::mvar = U{};
+
+  template <typename T> struct A {
+    template <typename U> struct B {
+      template <typename V> void deep(V v) {}
+    };
+  };
 }
 
 // (a) function template
@@ -139,6 +148,35 @@ extern template struct ns::S<double>::Inner;
 // CHECK-NEXT: NestedNameSpecifier TypeSpec 'ns::S<double>'
 // CHECK-NEXT: CXXRecord {{.*}} 'Inner'
 
+// member variable template
+template double ns::S<long>::mvar<double>;
+// CHECK: ExplicitInstantiationDecl {{.*}} <line:[[@LINE-1]]:1, col:41> col:1 explicit_instantiation_definition template 'mvar'
+// CHECK-NEXT: NestedNameSpecifier TypeSpec 'ns::S<long>'
+// CHECK-NEXT: VarTemplateSpecialization {{.*}} 'mvar' 'double'
+// CHECK-NEXT: TemplateArgument <col:35> type 'double'
+// CHECK-NEXT:   BuiltinType {{.*}} 'double'
+// CHECK-NEXT: BuiltinTypeLoc <col:10> 'double'
+
+// member class template
+template struct ns::S<long>::Nested<double>;
+// CHECK: ExplicitInstantiationDecl {{.*}} <line:[[@LINE-1]]:1, col:43> col:1 explicit_instantiation_definition template 'Nested'
+// CHECK-NEXT: NestedNameSpecifier TypeSpec 'ns::S<long>'
+// CHECK-NEXT: ClassTemplateSpecialization {{.*}} 'Nested'
+// CHECK-NEXT: TemplateArgument <col:37> type 'double'
+// CHECK-NEXT:   BuiltinType {{.*}} 'double'
+
+// deeply nested: A<int>::B<double>::deep<float>
+template void ns::A<int>::B<double>::deep<float>(float);
+// CHECK: ExplicitInstantiationDecl {{.*}} <line:[[@LINE-1]]:1, col:55> col:1 explicit_instantiation_definition template 'deep'
+// CHECK-NEXT: NestedNameSpecifier TypeSpec 'ns::A<int>::B<double>'
+// CHECK-NEXT: CXXMethod {{.*}} 'deep' 'void (float)'
+// CHECK-NEXT: TemplateArgument <col:43> type 'float'
+// CHECK-NEXT:   BuiltinType {{.*}} 'float'
+// CHECK-NEXT: FunctionProtoTypeLoc <col:10, col:55> 'void (float)' cdecl
+// CHECK-NEXT:   ParmVarDecl {{.*}} <col:50> col:55 'float'
+// CHECK-NEXT:     BuiltinTypeLoc <col:50> 'float'
+// CHECK-NEXT:   BuiltinTypeLoc <col:10> 'void'
+
 // Same-namespace explicit instantiation (no cross-namespace qualifier)
 namespace ns {
   template void foo<short>(short);
diff --git a/clang/test/SemaTemplate/explicit-instantiation-diag-location.cpp b/clang/test/SemaTemplate/explicit-instantiation-diag-location.cpp
index 7d9c35a07570a..e4f08762175a9 100644
--- a/clang/test/SemaTemplate/explicit-instantiation-diag-location.cpp
+++ b/clang/test/SemaTemplate/explicit-instantiation-diag-location.cpp
@@ -27,4 +27,41 @@ void use_g() { ns::g<double>(); }
 template void ns::g<double>();   // expected-note{{previous explicit instantiation is here}}
 template void ns::g<double>();   // expected-error{{duplicate explicit instantiation of 'g<double>'}}
 
+template <typename T> struct Outer {
+  template <typename U> void f(U);
+  template <typename U> static U var;
+  template <typename U> struct Inner {};
+};
+template <typename T> template <typename U> void Outer<T>::f(U) {}
+template <typename T> template <typename U> U Outer<T>::var = U{};
+
+void use_members() {
+  Outer<int> o;
+  o.f<double>(1.0);
+  (void)Outer<int>::var<double>;
+  Outer<int>::Inner<double> inner;
+}
+
+template void Outer<int>::f<double>(double);  // expected-note{{previous explicit instantiation is here}}
+template void Outer<int>::f<double>(double);  // expected-error{{duplicate explicit instantiation of 'f<double>'}}
+
+template double Outer<int>::var<double>;      // expected-note{{previous explicit instantiation is here}}
+template double Outer<int>::var<double>;      // expected-error{{duplicate explicit instantiation of 'var<double>'}}
+
+template struct Outer<int>::Inner<double>;    // expected-note{{previous explicit instantiation is here}}
+template struct Outer<int>::Inner<double>;    // expected-error{{duplicate explicit instantiation of 'Inner<double>'}}
+
+template <typename T> struct A {
+  template <typename U> struct B {
+    template <typename V> void f(V);
+  };
+};
+template <typename T> template <typename U> template <typename V>
+void A<T>::B<U>::f(V) {}
+
+void use_nested() { A<int>::B<double> b; b.f<float>(1.0f); }
+
+template void A<int>::B<double>::f<float>(float);  // expected-note{{previous explicit instantiation is here}}
+template void A<int>::B<double>::f<float>(float);  // expected-error{{duplicate explicit instantiation of 'f<float>'}}
+
 } // namespace GH21133

>From 9eaab97ffd6cc2e517e4979968c0b5717677ef05 Mon Sep 17 00:00:00 2001
From: ykiko <ykikoykikoykiko at gmail.com>
Date: Sun, 12 Apr 2026 12:59:31 +0800
Subject: [PATCH 12/15] fix clang-format

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

diff --git a/clang/lib/Sema/SemaTemplate.cpp b/clang/lib/Sema/SemaTemplate.cpp
index 9ded4de683ded..1721bf8783805 100644
--- a/clang/lib/Sema/SemaTemplate.cpp
+++ b/clang/lib/Sema/SemaTemplate.cpp
@@ -9339,7 +9339,8 @@ static SourceLocation DiagLocForExplicitInstantiation(
   for (DeclContext *DC = D->getDeclContext(); DC; DC = DC->getParent()) {
     for (auto *Decl : DC->decls()) {
       if (auto *EID = dyn_cast<ExplicitInstantiationDecl>(Decl))
-        if (EID->getSpecialization()->getCanonicalDecl() == D->getCanonicalDecl())
+        if (EID->getSpecialization()->getCanonicalDecl() ==
+            D->getCanonicalDecl())
           return EID->getTemplateLoc();
     }
   }

>From 6fcf2394e82b9d3d23dec168c5c63cbd0edd649a Mon Sep 17 00:00:00 2001
From: ykiko <ykikoykikoykiko at gmail.com>
Date: Sun, 12 Apr 2026 18:08:14 +0800
Subject: [PATCH 13/15] address style review and fix getEndLoc for postfix
 types

---
 clang/include/clang/AST/ASTNodeTraverser.h    |  5 ++---
 clang/lib/AST/DeclPrinter.cpp                 | 13 +++++++------
 clang/lib/AST/DeclTemplate.cpp                | 19 ++++++++++++++++++-
 .../explicit-instantiation-source-info.cpp    | 10 ++++++++++
 4 files changed, 37 insertions(+), 10 deletions(-)

diff --git a/clang/include/clang/AST/ASTNodeTraverser.h b/clang/include/clang/AST/ASTNodeTraverser.h
index 77a4d5655b196..3a523ba070bb0 100644
--- a/clang/include/clang/AST/ASTNodeTraverser.h
+++ b/clang/include/clang/AST/ASTNodeTraverser.h
@@ -688,9 +688,8 @@ class ASTNodeTraverser
     // Traverse source-location sub-nodes: template arguments and
     // type-as-written.
     if (const auto *ArgsAsWritten = D->getTemplateArgsAsWritten())
-      for (unsigned I = 0, E = ArgsAsWritten->NumTemplateArgs; I != E; ++I)
-        Visit((*ArgsAsWritten)[I].getArgument(),
-              (*ArgsAsWritten)[I].getSourceRange());
+      for (const TemplateArgumentLoc &Loc : ArgsAsWritten->arguments())
+        Visit(Loc.getArgument(), Loc.getSourceRange());
     if (TypeSourceInfo *TSI = D->getTypeAsWritten())
       Visit(TSI->getTypeLoc());
   }
diff --git a/clang/lib/AST/DeclPrinter.cpp b/clang/lib/AST/DeclPrinter.cpp
index 0cba2f46d6b40..fac7ab023d968 100644
--- a/clang/lib/AST/DeclPrinter.cpp
+++ b/clang/lib/AST/DeclPrinter.cpp
@@ -1357,19 +1357,20 @@ void DeclPrinter::VisitExplicitInstantiationDecl(ExplicitInstantiationDecl *D) {
   } else if (auto *FD = dyn_cast<FunctionDecl>(Spec)) {
     FD->getReturnType().print(Out, Policy);
     Out << " " << Name << "(";
-    for (unsigned I = 0, N = FD->getNumParams(); I < N; ++I) {
-      if (I)
-        Out << ", ";
-      FD->getParamDecl(I)->print(Out, Policy);
+    llvm::ListSeparator LS;
+    for (const ParmVarDecl *P : FD->parameters()) {
+      Out << LS;
+      P->print(Out, Policy);
     }
     if (FD->isVariadic()) {
-      if (FD->getNumParams())
-        Out << ", ";
+      Out << LS;
       Out << "...";
     }
     Out << ")";
   } else if (auto *TSI = D->getTypeAsWritten()) {
     TSI->getType().print(Out, Policy, Name);
+  } else {
+    llvm_unreachable("unexpected specialization kind");
   }
 }
 
diff --git a/clang/lib/AST/DeclTemplate.cpp b/clang/lib/AST/DeclTemplate.cpp
index 6c290a9f2f9e1..c9d2272d91a22 100644
--- a/clang/lib/AST/DeclTemplate.cpp
+++ b/clang/lib/AST/DeclTemplate.cpp
@@ -1805,15 +1805,32 @@ ExplicitInstantiationDecl::CreateDeserialized(ASTContext &C, GlobalDeclID ID) {
 
 SourceLocation ExplicitInstantiationDecl::getEndLoc() const {
   if (TypeAsWritten) {
+    // template void S<int>::f<double>(double);
+    //                                       ^
     if (auto FTL = TypeAsWritten->getTypeLoc().getAs<FunctionTypeLoc>())
       return FTL.getRParenLoc();
+    // template int S<int>::var<double>;
+    //                                ^
     if (TemplateArgsAsWritten)
       return TemplateArgsAsWritten->RAngleLoc;
-    // Static data member: end at the name.
+    // For postfix declarators (arrays, function pointers, etc.), the TypeLoc
+    // extends past the name. Pick whichever location is later.
+    // Cf. DeclaratorDecl::getSourceRange().
+    // template int S<int>::arr[1];       -> ']'
+    // template void (*S<int>::fptr)(int) -> ')'
+    // template long S<int>::sval;        -> 'sval'
+    SourceLocation TypeEnd = TypeAsWritten->getTypeLoc().getEndLoc();
+    if (TypeEnd.isValid() &&
+        TypeEnd.getRawEncoding() > NameLoc.getRawEncoding())
+      return TypeEnd;
     return NameLoc;
   }
+  // template struct S<int>::Inner<double>;
+  //                               ^
   if (TemplateArgsAsWritten)
     return TemplateArgsAsWritten->RAngleLoc;
+  // template struct S<int>::Inner;
+  //                         ^
   return NameLoc;
 }
 
diff --git a/clang/test/AST/explicit-instantiation-source-info.cpp b/clang/test/AST/explicit-instantiation-source-info.cpp
index e24c3dae8a5b1..183a6f5b8ac57 100644
--- a/clang/test/AST/explicit-instantiation-source-info.cpp
+++ b/clang/test/AST/explicit-instantiation-source-info.cpp
@@ -6,12 +6,14 @@ namespace ns {
   template <typename T> struct S {
     void method(T x) {}
     static T sval;
+    static T arr[1];
     template <typename U> void tmpl(U u) {}
     template <typename U> static U mvar;
     template <typename U> struct Nested {};
     struct Inner { T val; };
   };
   template <typename T> T S<T>::sval = T{};
+  template <typename T> T S<T>::arr[1] = {};
   template <typename T> template <typename U> U S<T>::mvar = U{};
 
   template <typename T> struct A {
@@ -67,6 +69,14 @@ template long ns::S<long>::sval;
 // CHECK-NEXT: Var {{.*}} 'sval' 'long'
 // CHECK-NEXT: BuiltinTypeLoc <col:10> 'long'
 
+// (e2) static data member with postfix type (array)
+template long ns::S<long>::arr[1];
+// CHECK: ExplicitInstantiationDecl {{.*}} <line:[[@LINE-1]]:1, col:33> col:1 explicit_instantiation_definition template 'arr'
+// CHECK-NEXT: NestedNameSpecifier TypeSpec 'ns::S<long>'
+// CHECK-NEXT: Var {{.*}} 'arr' 'long[1]'
+// CHECK-NEXT: ConstantArrayTypeLoc <col:10, col:33> 'long[1]' 1
+// CHECK-NEXT:   BuiltinTypeLoc <col:10> 'long'
+
 // (f) member function template
 template void ns::S<long>::tmpl<double>(double);
 // CHECK: ExplicitInstantiationDecl {{.*}} <line:[[@LINE-1]]:1, col:47> col:1 explicit_instantiation_definition template 'tmpl'

>From 0961d5fe2f369329deefb091a6351f1bfd1dd3bd Mon Sep 17 00:00:00 2001
From: ykiko <ykikoykikoykiko at gmail.com>
Date: Sun, 12 Apr 2026 18:43:39 +0800
Subject: [PATCH 14/15] simplify getEndLoc to take-max pattern

---
 clang/lib/AST/DeclTemplate.cpp | 34 +++++++++-------------------------
 1 file changed, 9 insertions(+), 25 deletions(-)

diff --git a/clang/lib/AST/DeclTemplate.cpp b/clang/lib/AST/DeclTemplate.cpp
index c9d2272d91a22..ffafb6c398bd0 100644
--- a/clang/lib/AST/DeclTemplate.cpp
+++ b/clang/lib/AST/DeclTemplate.cpp
@@ -1804,34 +1804,18 @@ ExplicitInstantiationDecl::CreateDeserialized(ASTContext &C, GlobalDeclID ID) {
 }
 
 SourceLocation ExplicitInstantiationDecl::getEndLoc() const {
+  // Pick the rightmost location among the name, template args, and type.
+  // For postfix types (arrays, function pointers) and function templates,
+  // the TypeLoc extends past the name. Cf. DeclaratorDecl::getSourceRange().
+  SourceLocation End = NameLoc;
+  if (TemplateArgsAsWritten && TemplateArgsAsWritten->RAngleLoc > End)
+    End = TemplateArgsAsWritten->RAngleLoc;
   if (TypeAsWritten) {
-    // template void S<int>::f<double>(double);
-    //                                       ^
-    if (auto FTL = TypeAsWritten->getTypeLoc().getAs<FunctionTypeLoc>())
-      return FTL.getRParenLoc();
-    // template int S<int>::var<double>;
-    //                                ^
-    if (TemplateArgsAsWritten)
-      return TemplateArgsAsWritten->RAngleLoc;
-    // For postfix declarators (arrays, function pointers, etc.), the TypeLoc
-    // extends past the name. Pick whichever location is later.
-    // Cf. DeclaratorDecl::getSourceRange().
-    // template int S<int>::arr[1];       -> ']'
-    // template void (*S<int>::fptr)(int) -> ')'
-    // template long S<int>::sval;        -> 'sval'
     SourceLocation TypeEnd = TypeAsWritten->getTypeLoc().getEndLoc();
-    if (TypeEnd.isValid() &&
-        TypeEnd.getRawEncoding() > NameLoc.getRawEncoding())
-      return TypeEnd;
-    return NameLoc;
+    if (TypeEnd.isValid() && TypeEnd > End)
+      End = TypeEnd;
   }
-  // template struct S<int>::Inner<double>;
-  //                               ^
-  if (TemplateArgsAsWritten)
-    return TemplateArgsAsWritten->RAngleLoc;
-  // template struct S<int>::Inner;
-  //                         ^
-  return NameLoc;
+  return End;
 }
 
 SourceRange ExplicitInstantiationDecl::getSourceRange() const {

>From 1eb99001ff1edc58f81058bf675b0e25a66160d0 Mon Sep 17 00:00:00 2001
From: ykiko <ykikoykikoykiko at gmail.com>
Date: Mon, 13 Apr 2026 15:46:04 +0800
Subject: [PATCH 15/15] make ExplicitInstantiationDecl redeclarable and add
 cross-module merge support

---
 clang/include/clang/AST/ASTContext.h          | 15 +++++
 clang/include/clang/AST/DeclTemplate.h        | 36 +++++++++--
 clang/include/clang/Serialization/ASTReader.h |  6 ++
 clang/lib/AST/ASTContext.cpp                  | 11 ++++
 clang/lib/AST/DeclTemplate.cpp                | 18 +++++-
 clang/lib/Sema/SemaTemplate.cpp               | 18 ++----
 clang/lib/Serialization/ASTCommon.cpp         |  2 +-
 clang/lib/Serialization/ASTReaderDecl.cpp     | 36 +++++++++++
 clang/lib/Serialization/ASTWriterDecl.cpp     | 16 +++++
 .../explicit-instantiation-in-module.cppm     | 61 ++++++++++++++++++
 .../Modules/explicit-instantiation-merge.cppm | 62 +++++++++++++++++++
 11 files changed, 261 insertions(+), 20 deletions(-)
 create mode 100644 clang/test/Modules/explicit-instantiation-in-module.cppm
 create mode 100644 clang/test/Modules/explicit-instantiation-merge.cppm

diff --git a/clang/include/clang/AST/ASTContext.h b/clang/include/clang/AST/ASTContext.h
index ba1b58489c327..806d728abcde5 100644
--- a/clang/include/clang/AST/ASTContext.h
+++ b/clang/include/clang/AST/ASTContext.h
@@ -104,6 +104,7 @@ class CXXRecordDecl;
 class DiagnosticsEngine;
 class DynTypedNodeList;
 class Expr;
+class ExplicitInstantiationDecl;
 enum class FloatModeKind;
 class GlobalDecl;
 class IdentifierTable;
@@ -656,6 +657,12 @@ class ASTContext : public RefCountedBase<ASTContext> {
 
   llvm::DenseMap<FieldDecl *, FieldDecl *> InstantiatedFromUnnamedFieldDecl;
 
+  /// Maps a canonical specialization Decl to the first
+  /// ExplicitInstantiationDecl that references it. Further EIDs for the same
+  /// specialization are chained via the Redeclarable mechanism.
+  llvm::DenseMap<const Decl *, ExplicitInstantiationDecl *>
+      ExplicitInstantiations;
+
   /// Mapping that stores the methods overridden by a given C++
   /// member function.
   ///
@@ -1134,6 +1141,14 @@ class ASTContext : public RefCountedBase<ASTContext> {
   /// Erase the attributes corresponding to the given declaration.
   void eraseDeclAttrs(const Decl *D);
 
+  /// Get the first ExplicitInstantiationDecl for a given specialization.
+  ExplicitInstantiationDecl *
+  getExplicitInstantiationDecl(const Decl *Spec) const;
+
+  /// Set the ExplicitInstantiationDecl for a given specialization.
+  void setExplicitInstantiationDecl(const Decl *Spec,
+                                    ExplicitInstantiationDecl *EID);
+
   /// If this variable is an instantiated static data member of a
   /// class template specialization, returns the templated static data member
   /// from which it was instantiated.
diff --git a/clang/include/clang/AST/DeclTemplate.h b/clang/include/clang/AST/DeclTemplate.h
index bfc580509c1d2..2cd53558b600c 100644
--- a/clang/include/clang/AST/DeclTemplate.h
+++ b/clang/include/clang/AST/DeclTemplate.h
@@ -3413,7 +3413,8 @@ const Decl &adjustDeclToTemplate(const Decl &D);
 ///   template int ns::bar<int>;              // variable template
 ///   template void ns::S<int>::method(int);  // member function
 /// \endcode
-class ExplicitInstantiationDecl : public Decl {
+class ExplicitInstantiationDecl : public Decl,
+                                  public Redeclarable<ExplicitInstantiationDecl> {
   /// The underlying specialization being explicitly instantiated.
   NamedDecl *Specialization = nullptr;
 
@@ -3446,7 +3447,14 @@ class ExplicitInstantiationDecl : public Decl {
   LLVM_PREFERRED_TYPE(TemplateSpecializationKind)
   unsigned TSK : 3;
 
-  ExplicitInstantiationDecl(DeclContext *DC, NamedDecl *Specialization,
+  using redeclarable_base = Redeclarable<ExplicitInstantiationDecl>;
+
+  ExplicitInstantiationDecl *getNextRedeclarationImpl() override;
+  ExplicitInstantiationDecl *getPreviousDeclImpl() override;
+  ExplicitInstantiationDecl *getMostRecentDeclImpl() override;
+
+  ExplicitInstantiationDecl(ASTContext &C, DeclContext *DC,
+                            NamedDecl *Specialization,
                             SourceLocation ExternLoc,
                             SourceLocation TemplateLoc, SourceLocation TagKWLoc,
                             NestedNameSpecifierLoc QualifierLoc,
@@ -3454,7 +3462,7 @@ class ExplicitInstantiationDecl : public Decl {
                             SourceLocation NameLoc,
                             TypeSourceInfo *TypeAsWritten,
                             TemplateSpecializationKind TSK)
-      : Decl(ExplicitInstantiation, DC, TemplateLoc),
+      : Decl(ExplicitInstantiation, DC, TemplateLoc), redeclarable_base(C),
         Specialization(Specialization), ExternLoc(ExternLoc),
         TagKWLoc(TagKWLoc), QualifierLoc(QualifierLoc),
         TemplateArgsAsWritten(ArgsAsWritten), NameLoc(NameLoc),
@@ -3464,8 +3472,9 @@ class ExplicitInstantiationDecl : public Decl {
     // TSK to be changed to TSK_ExplicitInstantiationDeclaration.
   }
 
-  ExplicitInstantiationDecl(EmptyShell Empty)
-      : Decl(ExplicitInstantiation, Empty), TSK(TSK_Undeclared) {}
+  ExplicitInstantiationDecl(ASTContext &C, EmptyShell Empty)
+      : Decl(ExplicitInstantiation, Empty), redeclarable_base(C),
+        TSK(TSK_Undeclared) {}
 
   virtual void anchor();
 
@@ -3473,6 +3482,16 @@ class ExplicitInstantiationDecl : public Decl {
   friend class ASTDeclReader;
   friend class ASTDeclWriter;
 
+  using redecl_range = redeclarable_base::redecl_range;
+  using redecl_iterator = redeclarable_base::redecl_iterator;
+
+  using redeclarable_base::redecls_begin;
+  using redeclarable_base::redecls_end;
+  using redeclarable_base::redecls;
+  using redeclarable_base::getPreviousDecl;
+  using redeclarable_base::getMostRecentDecl;
+  using redeclarable_base::isFirstDecl;
+
   static ExplicitInstantiationDecl *
   Create(ASTContext &C, DeclContext *DC, NamedDecl *Specialization,
          SourceLocation ExternLoc, SourceLocation TemplateLoc,
@@ -3484,6 +3503,13 @@ class ExplicitInstantiationDecl : public Decl {
   static ExplicitInstantiationDecl *CreateDeserialized(ASTContext &C,
                                                        GlobalDeclID ID);
 
+  ExplicitInstantiationDecl *getCanonicalDecl() override {
+    return getFirstDecl();
+  }
+  const ExplicitInstantiationDecl *getCanonicalDecl() const {
+    return getFirstDecl();
+  }
+
   NamedDecl *getSpecialization() const { return Specialization; }
 
   SourceRange getSourceRange() const override LLVM_READONLY;
diff --git a/clang/include/clang/Serialization/ASTReader.h b/clang/include/clang/Serialization/ASTReader.h
index 8394647885bd3..550abb10e70fb 100644
--- a/clang/include/clang/Serialization/ASTReader.h
+++ b/clang/include/clang/Serialization/ASTReader.h
@@ -105,6 +105,7 @@ class SwitchCase;
 class TargetOptions;
 class Token;
 class TypedefNameDecl;
+class ExplicitInstantiationDecl;
 class ValueDecl;
 class VarDecl;
 
@@ -651,6 +652,11 @@ class ASTReader
   llvm::DenseMap<std::pair<const Decl *, unsigned>, NamedDecl *>
       LambdaDeclarationsForMerging;
 
+  /// Map from canonical specialization to the corresponding explicit
+  /// instantiation declaration, for merging across modules.
+  llvm::DenseMap<const Decl *, ExplicitInstantiationDecl *>
+      ExplicitInstantiationDeclsForMerging;
+
   /// Key used to identify LifetimeExtendedTemporaryDecl for merging,
   /// containing the lifetime-extending declaration and the mangling number.
   using LETemporaryKey = std::pair<Decl *, unsigned>;
diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp
index ee7f823b014b2..99b0dec1cc836 100644
--- a/clang/lib/AST/ASTContext.cpp
+++ b/clang/lib/AST/ASTContext.cpp
@@ -1496,6 +1496,17 @@ void ASTContext::eraseDeclAttrs(const Decl *D) {
   }
 }
 
+ExplicitInstantiationDecl *
+ASTContext::getExplicitInstantiationDecl(const Decl *Spec) const {
+  auto It = ExplicitInstantiations.find(Spec->getCanonicalDecl());
+  return It != ExplicitInstantiations.end() ? It->second : nullptr;
+}
+
+void ASTContext::setExplicitInstantiationDecl(const Decl *Spec,
+                                              ExplicitInstantiationDecl *EID) {
+  ExplicitInstantiations[Spec->getCanonicalDecl()] = EID;
+}
+
 // FIXME: Remove ?
 MemberSpecializationInfo *
 ASTContext::getInstantiatedFromStaticDataMember(const VarDecl *Var) {
diff --git a/clang/lib/AST/DeclTemplate.cpp b/clang/lib/AST/DeclTemplate.cpp
index ffafb6c398bd0..8ed8d9ac74fca 100644
--- a/clang/lib/AST/DeclTemplate.cpp
+++ b/clang/lib/AST/DeclTemplate.cpp
@@ -1794,13 +1794,27 @@ ExplicitInstantiationDecl *ExplicitInstantiationDecl::Create(
     const ASTTemplateArgumentListInfo *ArgsAsWritten, SourceLocation NameLoc,
     TypeSourceInfo *TypeAsWritten, TemplateSpecializationKind TSK) {
   return new (C, DC) ExplicitInstantiationDecl(
-      DC, Specialization, ExternLoc, TemplateLoc, TagKWLoc, QualifierLoc,
+      C, DC, Specialization, ExternLoc, TemplateLoc, TagKWLoc, QualifierLoc,
       ArgsAsWritten, NameLoc, TypeAsWritten, TSK);
 }
 
 ExplicitInstantiationDecl *
 ExplicitInstantiationDecl::CreateDeserialized(ASTContext &C, GlobalDeclID ID) {
-  return new (C, ID) ExplicitInstantiationDecl(EmptyShell());
+  return new (C, ID) ExplicitInstantiationDecl(C, EmptyShell());
+}
+
+ExplicitInstantiationDecl *
+ExplicitInstantiationDecl::getNextRedeclarationImpl() {
+  return getNextRedeclaration();
+}
+
+ExplicitInstantiationDecl *ExplicitInstantiationDecl::getPreviousDeclImpl() {
+  return getPreviousDecl();
+}
+
+ExplicitInstantiationDecl *
+ExplicitInstantiationDecl::getMostRecentDeclImpl() {
+  return getMostRecentDecl();
 }
 
 SourceLocation ExplicitInstantiationDecl::getEndLoc() const {
diff --git a/clang/lib/Sema/SemaTemplate.cpp b/clang/lib/Sema/SemaTemplate.cpp
index 1721bf8783805..3ff02e1ed84cd 100644
--- a/clang/lib/Sema/SemaTemplate.cpp
+++ b/clang/lib/Sema/SemaTemplate.cpp
@@ -9326,24 +9326,18 @@ static void addExplicitInstantiationDecl(
   auto *EID = ExplicitInstantiationDecl::Create(
       Context, CurContext, Spec, ExternLoc, TemplateLoc, TagKWLoc, QualifierLoc,
       ArgsAsWritten, NameLoc, TypeAsWritten, TSK);
+  if (auto *Prev = Context.getExplicitInstantiationDecl(Spec))
+    EID->setPreviousDecl(Prev);
+  Context.setExplicitInstantiationDecl(Spec, EID);
   CurContext->addDecl(EID);
 }
 
 /// Compute the diagnostic location for an explicit instantiation
 //  declaration or definition.
 static SourceLocation DiagLocForExplicitInstantiation(
-    NamedDecl* D, SourceLocation PointOfInstantiation) {
-  // Search for an ExplicitInstantiationDecl that references D. Per
-  // [temp.explicit], explicit instantiations must appear in an enclosing
-  // namespace of the template, so the EID is in D's DeclContext or an ancestor.
-  for (DeclContext *DC = D->getDeclContext(); DC; DC = DC->getParent()) {
-    for (auto *Decl : DC->decls()) {
-      if (auto *EID = dyn_cast<ExplicitInstantiationDecl>(Decl))
-        if (EID->getSpecialization()->getCanonicalDecl() ==
-            D->getCanonicalDecl())
-          return EID->getTemplateLoc();
-    }
-  }
+    NamedDecl *D, SourceLocation PointOfInstantiation) {
+  if (auto *EID = D->getASTContext().getExplicitInstantiationDecl(D))
+    return EID->getTemplateLoc();
 
   // Explicit instantiations following a specialization have no effect and
   // hence no PointOfInstantiation. In that case, walk decl backwards
diff --git a/clang/lib/Serialization/ASTCommon.cpp b/clang/lib/Serialization/ASTCommon.cpp
index 089da7e082317..9523c0493f3d8 100644
--- a/clang/lib/Serialization/ASTCommon.cpp
+++ b/clang/lib/Serialization/ASTCommon.cpp
@@ -397,6 +397,7 @@ bool serialization::isRedeclarableDeclKind(unsigned Kind) {
   case Decl::ObjCProtocol:
   case Decl::ObjCInterface:
   case Decl::Empty:
+  case Decl::ExplicitInstantiation:
     return true;
 
   // Never redeclarable.
@@ -436,7 +437,6 @@ bool serialization::isRedeclarableDeclKind(unsigned Kind) {
   case Decl::Friend:
   case Decl::FriendTemplate:
   case Decl::StaticAssert:
-  case Decl::ExplicitInstantiation:
   case Decl::Block:
   case Decl::OutlinedFunction:
   case Decl::Captured:
diff --git a/clang/lib/Serialization/ASTReaderDecl.cpp b/clang/lib/Serialization/ASTReaderDecl.cpp
index 52612e1145081..ded035d79047a 100644
--- a/clang/lib/Serialization/ASTReaderDecl.cpp
+++ b/clang/lib/Serialization/ASTReaderDecl.cpp
@@ -109,6 +109,9 @@ class ASTDeclMerger {
   void mergeLambda(CXXRecordDecl *D, RedeclarableResult &Redecl, Decl &Context,
                    unsigned Number);
 
+  void mergeExplicitInstantiationDecl(ExplicitInstantiationDecl *D,
+                                      RedeclarableResult &Redecl);
+
   /// \param KeyDeclID the decl ID of the key declaration \param D.
   /// GlobalDeclID() if \param is not a key declaration.
   /// See the comments of ASTReader::KeyDecls for the explanation
@@ -2788,6 +2791,7 @@ void ASTDeclReader::VisitStaticAssertDecl(StaticAssertDecl *D) {
 
 void ASTDeclReader::VisitExplicitInstantiationDecl(
     ExplicitInstantiationDecl *D) {
+  RedeclarableResult Redecl = VisitRedeclarable(D);
   VisitDecl(D);
   D->Specialization = readDeclAs<NamedDecl>();
   D->ExternLoc = readSourceLocation();
@@ -2799,6 +2803,13 @@ void ASTDeclReader::VisitExplicitInstantiationDecl(
   D->NameLoc = readSourceLocation();
   D->TypeAsWritten = readTypeSourceInfo();
   D->TSK = Record.readInt();
+
+  // Merge with an existing EID for the same specialization from another module.
+  MergeImpl.mergeExplicitInstantiationDecl(D, Redecl);
+
+  // Rebuild the ASTContext map from specialization to EID.
+  if (D->Specialization)
+    Reader.getContext().setExplicitInstantiationDecl(D->Specialization, D);
 }
 
 void ASTDeclReader::VisitEmptyDecl(EmptyDecl *D) {
@@ -2938,6 +2949,31 @@ void ASTDeclMerger::mergeLambda(CXXRecordDecl *D, RedeclarableResult &Redecl,
     Slot = D;
 }
 
+/// Attempt to merge D with a previous explicit instantiation declaration for
+/// the same specialization, which is found by the canonical specialization decl.
+void ASTDeclMerger::mergeExplicitInstantiationDecl(
+    ExplicitInstantiationDecl *D, RedeclarableResult &Redecl) {
+  if (!Reader.getContext().getLangOpts().Modules)
+    return;
+
+  if (!D->isFirstDecl())
+    return;
+
+  if (auto *Existing = Redecl.getKnownMergeTarget())
+    mergeRedeclarable(D, cast<ExplicitInstantiationDecl>(Existing), Redecl);
+
+  auto *Spec = D->getSpecialization();
+  if (!Spec)
+    return;
+
+  auto *CanonSpec = Spec->getCanonicalDecl();
+  auto &Slot = Reader.ExplicitInstantiationDeclsForMerging[CanonSpec];
+  if (Slot)
+    mergeRedeclarable(D, Slot, Redecl);
+  else
+    Slot = D;
+}
+
 void ASTDeclReader::mergeRedeclarableTemplate(RedeclarableTemplateDecl *D,
                                               RedeclarableResult &Redecl) {
   mergeRedeclarable(D, Redecl);
diff --git a/clang/lib/Serialization/ASTWriterDecl.cpp b/clang/lib/Serialization/ASTWriterDecl.cpp
index 706cb161e3b0b..9687781bfe980 100644
--- a/clang/lib/Serialization/ASTWriterDecl.cpp
+++ b/clang/lib/Serialization/ASTWriterDecl.cpp
@@ -919,6 +919,11 @@ void ASTDeclWriter::VisitFunctionDecl(FunctionDecl *D) {
     }
   }
 
+  // Ensure associated ExplicitInstantiationDecl survives reduced BMI.
+  if (auto *EID = Record.getASTContext().getExplicitInstantiationDecl(D))
+    Writer.RelatedDeclsMap[Writer.GetDeclRef(D)].push_back(
+        Writer.GetDeclRef(EID));
+
   Record.push_back(D->param_size());
   for (auto *P : D->parameters())
     Record.AddDeclRef(P);
@@ -1967,6 +1972,11 @@ void ASTDeclWriter::VisitClassTemplateSpecializationDecl(
       Writer.GetDeclRef(DG->getCanonicalDecl());
   }
 
+  // Ensure associated ExplicitInstantiationDecl survives reduced BMI.
+  if (auto *EID = Record.getASTContext().getExplicitInstantiationDecl(D))
+    Writer.RelatedDeclsMap[Writer.GetDeclRef(D)].push_back(
+        Writer.GetDeclRef(EID));
+
   Code = serialization::DECL_CLASS_TEMPLATE_SPECIALIZATION;
 }
 
@@ -2036,6 +2046,11 @@ void ASTDeclWriter::VisitVarTemplateSpecializationDecl(
     Record.AddDeclRef(D->getSpecializedTemplate()->getCanonicalDecl());
   }
 
+  // Ensure associated ExplicitInstantiationDecl survives reduced BMI.
+  if (auto *EID = Record.getASTContext().getExplicitInstantiationDecl(D))
+    Writer.RelatedDeclsMap[Writer.GetDeclRef(D)].push_back(
+        Writer.GetDeclRef(EID));
+
   Code = serialization::DECL_VAR_TEMPLATE_SPECIALIZATION;
 }
 
@@ -2179,6 +2194,7 @@ void ASTDeclWriter::VisitStaticAssertDecl(StaticAssertDecl *D) {
 
 void ASTDeclWriter::VisitExplicitInstantiationDecl(
     ExplicitInstantiationDecl *D) {
+  VisitRedeclarable(D);
   VisitDecl(D);
   Record.AddDeclRef(D->getSpecialization());
   Record.AddSourceLocation(D->getExternLoc());
diff --git a/clang/test/Modules/explicit-instantiation-in-module.cppm b/clang/test/Modules/explicit-instantiation-in-module.cppm
new file mode 100644
index 0000000000000..6e3bd156e98d6
--- /dev/null
+++ b/clang/test/Modules/explicit-instantiation-in-module.cppm
@@ -0,0 +1,61 @@
+// RUN: rm -rf %t
+// RUN: mkdir -p %t
+// RUN: split-file %s %t
+//
+// Full BMI: both EIDs survive.
+// RUN: %clang_cc1 -std=c++20 %t/M.cppm -I%t -emit-module-interface -o %t/M.pcm
+// RUN: %clang_cc1 -std=c++20 -fprebuilt-module-path=%t %t/use.cpp -verify -fsyntax-only
+// RUN: %clang_cc1 -std=c++20 -fprebuilt-module-path=%t %t/use.cpp -ast-dump-all 2>&1 | FileCheck %s --check-prefix=FULL
+//
+// Reduced BMI: both EIDs survive (linked from the specialization).
+// RUN: %clang_cc1 -std=c++20 %t/M.cppm -I%t -emit-reduced-module-interface -o %t/M.pcm
+// RUN: %clang_cc1 -std=c++20 -fprebuilt-module-path=%t %t/use.cpp -verify -fsyntax-only
+// RUN: %clang_cc1 -std=c++20 -fprebuilt-module-path=%t %t/use.cpp -ast-dump-all 2>&1 | FileCheck %s --check-prefix=REDUCED
+
+//--- header.h
+#ifndef HEADER_H
+#define HEADER_H
+template <typename T>
+struct GMFStruct {
+  T value;
+  T get() const { return value; }
+};
+#endif
+
+//--- M.cppm
+module;
+#include "header.h"
+
+// Explicit instantiation in GMF.
+template struct GMFStruct<int>;
+
+export module M;
+
+export template <typename T>
+struct PurvStruct {
+  T value;
+  T get() const { return value; }
+};
+
+// Explicit instantiation in module purview.
+template struct PurvStruct<int>;
+
+export using ::GMFStruct;
+
+//--- use.cpp
+// expected-no-diagnostics
+import M;
+
+// FULL: ExplicitInstantiationDecl {{.*}} imported in M.<global> {{.*}} explicit_instantiation_definition template 'GMFStruct'
+// FULL: ExplicitInstantiationDecl {{.*}} imported in M {{.*}} explicit_instantiation_definition template 'PurvStruct'
+
+// REDUCED: ExplicitInstantiationDecl {{.*}} imported in M.<global> {{.*}} explicit_instantiation_definition template 'GMFStruct'
+// REDUCED: ExplicitInstantiationDecl {{.*}} imported in M {{.*}} explicit_instantiation_definition template 'PurvStruct'
+
+void test() {
+  GMFStruct<int> g;
+  g.value = 1;
+
+  PurvStruct<int> p;
+  p.value = 2;
+}
diff --git a/clang/test/Modules/explicit-instantiation-merge.cppm b/clang/test/Modules/explicit-instantiation-merge.cppm
new file mode 100644
index 0000000000000..40610d0355372
--- /dev/null
+++ b/clang/test/Modules/explicit-instantiation-merge.cppm
@@ -0,0 +1,62 @@
+// RUN: rm -rf %t
+// RUN: mkdir -p %t
+// RUN: split-file %s %t
+//
+// Two modules both include the same header and explicitly instantiate the same
+// specialization. After importing both, the EIDs should be merged and at least
+// one should survive.
+//
+// RUN: %clang_cc1 -std=c++20 %t/A.cppm -I%t -emit-module-interface -o %t/A.pcm
+// RUN: %clang_cc1 -std=c++20 %t/B.cppm -I%t -emit-module-interface -o %t/B.pcm
+// RUN: %clang_cc1 -std=c++20 -fprebuilt-module-path=%t %t/use.cpp -verify -fsyntax-only
+// RUN: %clang_cc1 -std=c++20 -fprebuilt-module-path=%t %t/use.cpp -ast-dump-all 2>&1 | FileCheck %s --check-prefix=CHECK-AST
+//
+// Verify cross-module duplicate diagnostic points to the module's EID location.
+// RUN: %clang_cc1 -std=c++20 -fprebuilt-module-path=%t %t/dup.cpp -verify -fsyntax-only
+//
+// RUN: %clang_cc1 -std=c++20 %t/A.cppm -I%t -emit-reduced-module-interface -o %t/A.pcm
+// RUN: %clang_cc1 -std=c++20 %t/B.cppm -I%t -emit-reduced-module-interface -o %t/B.pcm
+// RUN: %clang_cc1 -std=c++20 -fprebuilt-module-path=%t %t/use.cpp -verify -fsyntax-only
+// RUN: %clang_cc1 -std=c++20 -fprebuilt-module-path=%t %t/use.cpp -ast-dump-all 2>&1 | FileCheck %s --check-prefix=CHECK-AST
+// RUN: %clang_cc1 -std=c++20 -fprebuilt-module-path=%t %t/dup.cpp -verify -fsyntax-only
+
+//--- header.h
+#ifndef HEADER_H
+#define HEADER_H
+template <typename T>
+struct S {
+  T value;
+};
+#endif
+
+//--- A.cppm
+module;
+#include "header.h"
+template struct S<int>; // #A-inst
+export module A;
+export using ::S;
+
+//--- B.cppm
+module;
+#include "header.h"
+template struct S<int>;
+export module B;
+export using ::S;
+
+//--- use.cpp
+// expected-no-diagnostics
+import A;
+import B;
+
+// CHECK-AST: ExplicitInstantiationDecl {{.*}} imported {{.*}} explicit_instantiation_definition template 'S'
+
+void test() {
+  S<int> s;
+  s.value = 42;
+}
+
+//--- dup.cpp
+import A;
+
+template struct S<int>; // expected-error {{duplicate explicit instantiation of 'S<int>'}}
+                        // expected-note at A.cppm:3 {{previous explicit instantiation is here}}



More information about the cfe-commits mailing list