Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
9 changes: 9 additions & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,15 @@ Non-comprehensive list of changes in this release

- Added #pragma loop licm(disable) for llvm.loop.licm.disable metadata

- Added a new ``ExplicitInstantiationDecl`` AST node to represent explicit
template instantiations (e.g., ``template void foo<int>();`` or
``extern template class S<int>;``). Previously, source location information
for explicit instantiation statements was discarded after parsing. The new
node preserves the full source range including the ``extern`` and ``template``
keywords, qualifiers, template arguments as written, and the declared type,
enabling tools such as language servers and refactoring engines to accurately
map source locations back to explicit instantiation sites.

New Compiler Flags
------------------
- New option ``-fms-anonymous-structs`` / ``-fno-ms-anonymous-structs`` added
Expand Down
15 changes: 15 additions & 0 deletions clang/include/clang/AST/ASTContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ class CXXRecordDecl;
class DiagnosticsEngine;
class DynTypedNodeList;
class Expr;
class ExplicitInstantiationDecl;
enum class FloatModeKind;
class GlobalDecl;
class IdentifierTable;
Expand Down Expand Up @@ -656,6 +657,12 @@ class ASTContext : public RefCountedBase<ASTContext> {

llvm::DenseMap<FieldDecl *, FieldDecl *> InstantiatedFromUnnamedFieldDecl;

/// Maps a canonical specialization Decl to all ExplicitInstantiationDecls
/// that reference it (declarations and definitions).
llvm::DenseMap<const NamedDecl *,
llvm::TinyPtrVector<ExplicitInstantiationDecl *>>
ExplicitInstantiations;

/// Mapping that stores the methods overridden by a given C++
/// member function.
///
Expand Down Expand Up @@ -1134,6 +1141,14 @@ class ASTContext : public RefCountedBase<ASTContext> {
/// Erase the attributes corresponding to the given declaration.
void eraseDeclAttrs(const Decl *D);

/// Get all ExplicitInstantiationDecls for a given specialization.
ArrayRef<ExplicitInstantiationDecl *>
getExplicitInstantiationDecls(const NamedDecl *Spec) const;

/// Add an ExplicitInstantiationDecl for a given specialization.
void addExplicitInstantiationDecl(const NamedDecl *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.
Expand Down
9 changes: 9 additions & 0 deletions clang/include/clang/AST/ASTNodeTraverser.h
Original file line number Diff line number Diff line change
Expand Up @@ -683,6 +683,15 @@ class ASTNodeTraverser
Visit(D->getMessage());
}

void VisitExplicitInstantiationDecl(const ExplicitInstantiationDecl *D) {
if (TypeSourceInfo *TSI = D->getTypeAsWritten())
Visit(TSI->getTypeLoc());
for (unsigned I = 0, E = D->getNumTemplateArgs(); I != E; ++I) {
TemplateArgumentLoc Loc = D->getTemplateArg(I);
Visit(Loc.getArgument(), Loc.getSourceRange());
}
}
Comment thread
16bit-ykiko marked this conversation as resolved.

void VisitFunctionTemplateDecl(const FunctionTemplateDecl *D) {
dumpTemplateDecl(D);
}
Expand Down
133 changes: 133 additions & 0 deletions clang/include/clang/AST/DeclTemplate.h
Original file line number Diff line number Diff line change
Expand Up @@ -3405,6 +3405,139 @@ 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 final
: public Decl,
private llvm::TrailingObjects<ExplicitInstantiationDecl,
NestedNameSpecifierLoc,
const ASTTemplateArgumentListInfo *> {
friend class ASTDeclReader;
friend class ASTDeclWriter;
friend TrailingObjects;

/// The underlying specialization (low 3 bits: TSK).
llvm::PointerIntPair<NamedDecl *, 3, unsigned> SpecAndTSK;

/// TypeSourceInfo (low 2 bits: trailing-object flags).
/// Always non-null after construction.
/// - Class templates: TemplateSpecializationTypeLoc encoding keyword,
/// qualifier, template-name, and argument locations.
/// - Nested classes: TagTypeLoc encoding keyword, qualifier, and name.
/// - Function / variable templates: the declared type.
llvm::PointerIntPair<TypeSourceInfo *, 2, unsigned> TypeAndFlags;

/// Location of the 'extern' keyword (invalid if not extern template).
SourceLocation ExternLoc;

/// Location of the entity name (e.g., 'foo' in 'template void
/// ns::foo<int>(int)').
SourceLocation NameLoc;

enum TrailingFlags : unsigned {
HasQualifierFlag = 1,
HasArgsAsWrittenFlag = 2,
};

size_t numTrailingObjects(OverloadToken<NestedNameSpecifierLoc>) const {
return hasTrailingQualifier() ? 1 : 0;
}

/// Raw access to the internal TypeSourceInfo. For class templates this is
/// a TemplateSpecializationTypeLoc; for nested classes a TagTypeLoc.
/// Public getTypeAsWritten() returns null for those cases.
TypeSourceInfo *getRawTypeSourceInfo() const {
return TypeAndFlags.getPointer();
}

/// Returns the trailing ASTTemplateArgumentListInfo pointer, or null.
const ASTTemplateArgumentListInfo *getTrailingArgsInfo() const {
if (!hasTrailingArgsAsWritten())
return nullptr;
return *getTrailingObjects<const ASTTemplateArgumentListInfo *>();
}

ExplicitInstantiationDecl(
DeclContext *DC, NamedDecl *Specialization, SourceLocation ExternLoc,
SourceLocation TemplateLoc, NestedNameSpecifierLoc QualifierLoc,
const ASTTemplateArgumentListInfo *ArgsAsWritten, SourceLocation NameLoc,
TypeSourceInfo *TypeAsWritten, TemplateSpecializationKind TSK);

ExplicitInstantiationDecl(EmptyShell Empty)
: Decl(ExplicitInstantiation, Empty) {}

public:
static ExplicitInstantiationDecl *
Create(ASTContext &C, DeclContext *DC, NamedDecl *Specialization,
SourceLocation ExternLoc, SourceLocation TemplateLoc,
NestedNameSpecifierLoc QualifierLoc,
const ASTTemplateArgumentListInfo *ArgsAsWritten,
SourceLocation NameLoc, TypeSourceInfo *TypeAsWritten,
TemplateSpecializationKind TSK);

static ExplicitInstantiationDecl *
CreateDeserialized(ASTContext &C, GlobalDeclID ID, unsigned TrailingFlags);

NamedDecl *getSpecialization() const { return SpecAndTSK.getPointer(); }

SourceRange getSourceRange() const override LLVM_READONLY;
SourceLocation getEndLoc() const LLVM_READONLY;

SourceLocation getExternLoc() const { return ExternLoc; }
SourceLocation getTemplateLoc() const { return getLocation(); }
SourceLocation getNameLoc() const { return NameLoc; }

/// For class templates / nested classes, the tag keyword location is
/// stored inside TypeSourceInfo; otherwise returns an invalid location.
SourceLocation getTagKWLoc() const;

/// Whether the qualifier is stored as a trailing object (function / variable
/// templates) rather than inside TypeSourceInfo (class templates / nested
/// classes).
bool hasTrailingQualifier() const {
return TypeAndFlags.getInt() & HasQualifierFlag;
}
bool hasTrailingArgsAsWritten() const {
return TypeAndFlags.getInt() & HasArgsAsWrittenFlag;
}

/// Returns the qualifier regardless of where it is stored.
/// For class templates / nested classes, it is extracted from TypeSourceInfo
/// (TemplateSpecializationTypeLoc or TagTypeLoc).
/// For function / variable templates, it comes from a trailing object.
NestedNameSpecifierLoc getQualifierLoc() const;

/// Number of explicit template arguments, regardless of storage.
/// For class templates they come from TemplateSpecializationTypeLoc;
/// for function / variable templates from trailing
/// ASTTemplateArgumentListInfo.
unsigned getNumTemplateArgs() const;
TemplateArgumentLoc getTemplateArg(unsigned I) const;
SourceLocation getTemplateArgsLAngleLoc() const;
SourceLocation getTemplateArgsRAngleLoc() const;

/// For function / variable templates, returns the declared type (return type
/// or variable type). For class templates and nested classes returns null —
/// the qualifier, tag keyword, and template arguments are accessible via
/// getQualifierLoc(), getTagKWLoc(), and getTemplateArg().
TypeSourceInfo *getTypeAsWritten() const;

TemplateSpecializationKind getTemplateSpecializationKind() const {
return static_cast<TemplateSpecializationKind>(SpecAndTSK.getInt());
}

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
33 changes: 23 additions & 10 deletions clang/include/clang/AST/RecursiveASTVisitor.h
Original file line number Diff line number Diff line change
Expand Up @@ -1748,6 +1748,17 @@ DEF_TRAVERSE_DECL(StaticAssertDecl, {
TRY_TO(TraverseStmt(D->getMessage()));
})

DEF_TRAVERSE_DECL(ExplicitInstantiationDecl, {
// No double visiting: getTypeAsWritten() returns null for class
// templates/nested classes where the qualifier lives inside the TSI.
if (D->getQualifierLoc())
TRY_TO(TraverseNestedNameSpecifierLoc(D->getQualifierLoc()));
if (TypeSourceInfo *TSI = D->getTypeAsWritten())
TRY_TO(TraverseTypeLoc(TSI->getTypeLoc()));
for (unsigned I = 0, E = D->getNumTemplateArgs(); I != E; ++I)
TRY_TO(TraverseTemplateArgumentLoc(D->getTemplateArg(I)));
})
Comment thread
16bit-ykiko marked this conversation as 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 +2038,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 +2209,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.
/* 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
5 changes: 5 additions & 0 deletions clang/include/clang/AST/TypeBase.h
Original file line number Diff line number Diff line change
Expand Up @@ -1134,6 +1134,11 @@ class QualType {
/// Return true if this is a trivially copyable type
bool isTriviallyCopyConstructibleType(const ASTContext &Context) const;

/// Returns true if the type uses postfix declarator syntax, i.e. the
/// declarator component appears after the name (arrays, functions).
/// Looks through pointer-like types to the pointee.
bool hasPostfixDeclaratorSyntax() const;

/// Returns true if it is a class and it might be dynamic.
bool mayBeDynamicClass() const;

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
15 changes: 15 additions & 0 deletions clang/lib/AST/ASTContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1496,6 +1496,21 @@ void ASTContext::eraseDeclAttrs(const Decl *D) {
}
}

ArrayRef<ExplicitInstantiationDecl *>
ASTContext::getExplicitInstantiationDecls(const NamedDecl *Spec) const {
auto It =
ExplicitInstantiations.find(cast<NamedDecl>(Spec->getCanonicalDecl()));
if (It != ExplicitInstantiations.end())
return It->second;
return {};
}

void ASTContext::addExplicitInstantiationDecl(const NamedDecl *Spec,
ExplicitInstantiationDecl *EID) {
ExplicitInstantiations[cast<NamedDecl>(Spec->getCanonicalDecl())].push_back(
EID);
}

// FIXME: Remove ?
MemberSpecializationInfo *
ASTContext::getInstantiatedFromStaticDataMember(const VarDecl *Var) {
Expand Down
Loading
Loading