Skip to content
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
15a609e
[clang] Add ExplicitInstantiationDecl to preserve source info for exp…
16bit-ykiko Apr 11, 2026
61c859d
clang-format
16bit-ykiko Apr 11, 2026
bdcc69c
add ExplicitInstantiation to getIdentifierNamespaceForKind
16bit-ykiko Apr 11, 2026
fc93b95
Merge branch 'main' into fix-explicit-instantiation
16bit-ykiko Apr 11, 2026
0d63c41
fix CI failures from ExplicitInstantiationDecl
16bit-ykiko Apr 11, 2026
e88b891
clang-format
16bit-ykiko Apr 11, 2026
61e4895
update clang-tidy tests for ExplicitInstantiationDecl
16bit-ykiko Apr 12, 2026
ab583c2
address review nits and add ast-print support for ExplicitInstantiati…
16bit-ykiko Apr 12, 2026
426dc42
compute EndLoc from existing fields instead of storing it
16bit-ykiko Apr 12, 2026
7a2129f
address review feedback: return EID, wrap test in namespace
16bit-ykiko Apr 12, 2026
104d21e
revert returning EID from ActOnExplicitInstantiation
16bit-ykiko Apr 12, 2026
f359da2
fix DiagLocForExplicitInstantiation and add member template tests
16bit-ykiko Apr 12, 2026
9eaab97
fix clang-format
16bit-ykiko Apr 12, 2026
6fcf239
address style review and fix getEndLoc for postfix types
16bit-ykiko Apr 12, 2026
0961d5f
simplify getEndLoc to take-max pattern
16bit-ykiko Apr 12, 2026
1eb9900
make ExplicitInstantiationDecl redeclarable and add cross-module merg…
16bit-ykiko Apr 13, 2026
6f67e52
fix clang-format
16bit-ykiko Apr 13, 2026
687c114
Merge branch 'main' into fix-explicit-instantiation
16bit-ykiko Apr 13, 2026
c002e77
address review: reuse ASTContext map for merge and simplify GetDeclRef
16bit-ykiko Apr 13, 2026
34f2e7f
Merge branch 'fix-explicit-instantiation' of https://github.com/16bit…
16bit-ykiko Apr 13, 2026
c4e70d5
revert redeclarable, use TinyPtrVector and union optimization
16bit-ykiko Apr 13, 2026
9bdb212
optimize ExplicitInstantiationDecl layout with PointerIntPair and Tra…
16bit-ykiko Apr 13, 2026
bbafbc1
fix the compilation error
16bit-ykiko Apr 13, 2026
11cbbf3
fix TrailingObjects offset bug and avoid double NNS traversal
16bit-ykiko Apr 13, 2026
138dd77
add unified template argument accessors and unwrap getTypeAsWritten
16bit-ykiko Apr 13, 2026
d5cc496
extract typeIsPostfix to QualType::hasPostfixDeclaratorSyntax and fix…
16bit-ykiko Apr 13, 2026
244aa44
simplify trailing object access with helper methods
16bit-ykiko Apr 13, 2026
3d7977c
remove unconditional 'template' from EID dump output
16bit-ykiko Apr 13, 2026
6f5d4f6
fix test CHECK lines for removed 'template' in EID dump
16bit-ykiko Apr 13, 2026
42e74ca
Merge branch 'main' into fix-explicit-instantiation
16bit-ykiko Apr 14, 2026
28ad5ef
add release note for ExplicitInstantiationDecl
16bit-ykiko Apr 22, 2026
14ac130
address review: use NamedDecl, TypeLoc::getPrefix, and fix traversal …
16bit-ykiko Apr 22, 2026
6b68d62
fix CHECK lines for new traversal order in ExplicitInstantiationDecl
16bit-ykiko Apr 22, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
12 changes: 12 additions & 0 deletions clang/include/clang/AST/ASTNodeTraverser.h
Original file line number Diff line number Diff line change
Expand Up @@ -683,6 +683,18 @@ 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)
Comment thread
16bit-ykiko marked this conversation as resolved.
Resolved
Visit((*ArgsAsWritten)[I].getArgument(),
(*ArgsAsWritten)[I].getSourceRange());
if (TypeSourceInfo *TSI = D->getTypeAsWritten())
Visit(TSI->getTypeLoc());
}
Comment thread
16bit-ykiko marked this conversation as resolved.
Resolved

void VisitFunctionTemplateDecl(const FunctionTemplateDecl *D) {
dumpTemplateDecl(D);
}
Expand Down
107 changes: 107 additions & 0 deletions clang/include/clang/AST/DeclTemplate.h
Original file line number Diff line number Diff line change
Expand Up @@ -3405,6 +3405,113 @@ 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.
///
/// \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;

/// 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;
Comment thread
16bit-ykiko marked this conversation as resolved.
Resolved

/// Whether this is a declaration (extern template) or definition (template).
LLVM_PREFERRED_TYPE(TemplateSpecializationKind)
unsigned TSK : 3;

ExplicitInstantiationDecl(DeclContext *DC, 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), ExternLoc(ExternLoc),
TagKWLoc(TagKWLoc), QualifierLoc(QualifierLoc),
TemplateArgsAsWritten(ArgsAsWritten), NameLoc(NameLoc),
TypeAsWritten(TypeAsWritten), TSK(TSK) {
// 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)
: Decl(ExplicitInstantiation, Empty), TSK(TSK_Undeclared) {}

virtual void anchor();

public:
friend class ASTDeclReader;
friend class ASTDeclWriter;

static ExplicitInstantiationDecl *
Create(ASTContext &C, DeclContext *DC, 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;
SourceLocation getEndLoc() const LLVM_READONLY;

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
1 change: 1 addition & 0 deletions clang/include/clang/AST/JSONNodeDumper.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
32 changes: 22 additions & 10 deletions clang/include/clang/AST/RecursiveASTVisitor.h
Original file line number Diff line number Diff line change
Expand Up @@ -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()));
})
Comment thread
16bit-ykiko marked this conversation as resolved.
Resolved

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
Expand Down Expand Up @@ -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));
Expand Down Expand Up @@ -2196,23 +2208,23 @@ 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() || \
D->getTemplateSpecializationKind() == TSK_ExplicitSpecialization) { \
/* Traverse base definition for explicit specializations */ \
TRY_TO(Traverse##DECLKIND##Helper(D)); \
} else { \
TRY_TO(TraverseNestedNameSpecifierLoc(D->getQualifierLoc())); \
\
Comment thread
16bit-ykiko marked this conversation as resolved.
Resolved
/* Returning from here skips traversing the \
declaration context of the *TemplateSpecializationDecl \
(embedded in the DEF_TRAVERSE_DECL() macro) \
Expand Down
1 change: 1 addition & 0 deletions clang/include/clang/AST/TextNodeDumper.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
1 change: 1 addition & 0 deletions clang/include/clang/Basic/DeclNodes.td
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
5 changes: 4 additions & 1 deletion clang/include/clang/Serialization/ASTBitCodes.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
1 change: 1 addition & 0 deletions clang/lib/AST/DeclBase.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1019,6 +1019,7 @@ unsigned Decl::getIdentifierNamespaceForKind(Kind DeclKind) {
case ImplicitConceptSpecialization:
case OpenACCDeclare:
case OpenACCRoutine:
case ExplicitInstantiation:
// Never looked up by name.
return 0;
}
Expand Down
38 changes: 38 additions & 0 deletions clang/lib/AST/DeclPrinter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -1335,6 +1336,43 @@ 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 << ", ";
Comment thread
16bit-ykiko marked this conversation as resolved.
Resolved
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);
}
Comment thread
16bit-ykiko marked this conversation as resolved.
Resolved
}

void DeclPrinter::VisitClassTemplateSpecializationDecl(
ClassTemplateSpecializationDecl *D) {
Out << "template<> ";
Expand Down
37 changes: 37 additions & 0 deletions clang/lib/AST/DeclTemplate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1784,3 +1784,40 @@ const Decl &clang::adjustDeclToTemplate(const Decl &D) {
// FIXME: Adjust alias templates?
return D;
}

void ExplicitInstantiationDecl::anchor() {}

ExplicitInstantiationDecl *ExplicitInstantiationDecl::Create(
ASTContext &C, DeclContext *DC, 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, 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;
Comment thread
16bit-ykiko marked this conversation as resolved.
Resolved
}
if (TemplateArgsAsWritten)
return TemplateArgsAsWritten->RAngleLoc;
return NameLoc;
}

SourceRange ExplicitInstantiationDecl::getSourceRange() const {
SourceLocation Begin = ExternLoc.isValid() ? ExternLoc : getLocation();
return SourceRange(Begin, getEndLoc());
}
26 changes: 26 additions & 0 deletions clang/lib/AST/JSONNodeDumper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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()));
Expand Down
Loading
Loading