Skip to content

Commit

Permalink
[clang compat] Fix explicit instantiation handling
Browse files Browse the repository at this point in the history
llvm/llvm-project@1202837 removed
TemplateSpecializationType child node from
ClassTemplateSpecializationDecl, replacing it just with template
arguments.

To maintain instantiation scanning, the new InstantiatedTemplateVisitor
interface and other functions have been introduced which don't require
TemplateSpecializationType. They may be further reused for handling
the cases then a specific TemplateSpecializationType is lost.

The comment in VisitClassTemplateSpecializationDecl was wrong: it is not
clang but IWYU GetLocation function which attributes
ClassTemplateSpecializationDecl to the original template location.
An exception for explicit instantiations has been added to it. However,
method declarations should still be attributed to the class template
definition, hence the change for them to maintain the old behavior.

Because GetLocation doesn't "canonicalize" explicit instantiation
locations now, the change in VisitTemplateSpecializationType is needed
to handle the cases when the instantiation is explicit (as opposed
to implicit ones). Besides, it looks more straightforward to report
template declarations directly, not through
ClassTemplateSpecializationDecl.

A couple of new test cases added to make sure that the work has been
done properly.

One functional change: explicit instantiation definitions don't require
explicit instantiation declarations. I think it was just useless.
  • Loading branch information
bolshakov-a committed Jun 15, 2024
1 parent 53ce70e commit 4f620e8
Show file tree
Hide file tree
Showing 8 changed files with 151 additions and 43 deletions.
95 changes: 71 additions & 24 deletions iwyu.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2457,7 +2457,10 @@ class IwyuBaseAstVisitor : public BaseAstVisitor<Derived> {
if (CanIgnoreCurrentASTNode() || CanIgnoreType(type))
return true;

const NamedDecl* decl = TypeToDeclAsWritten(type);
// If not a full explicit specialization, report the original template
// or a partial specialization from which the specialization is instantiated
// (and don't report explicit instantiations here).
const NamedDecl* decl = GetInstantiatedFromDecl(TypeToDeclAsWritten(type));

// If we are forward-declarable, so are our template arguments.
if (CanForwardDeclareType(current_ast_node())) {
Expand Down Expand Up @@ -2900,6 +2903,10 @@ class InstantiatedTemplateVisitor
//
// ScanInstantiatedType() is similar, except that it looks through
// the definition of a class template instead of a statement.
//
// ScanInstantiatedClass() may be used in cases when a specific
// TemplateSpecializationType referring to the class template specialization
// is not known.

// resugar_map is a map from an unsugared (canonicalized) template
// type to the template type as written (or as close as we can find
Expand Down Expand Up @@ -2961,6 +2968,24 @@ class InstantiatedTemplateVisitor
const_cast<TemplateSpecializationType*>(type));
}

void ScanInstantiatedClass(ClassTemplateSpecializationDecl* decl,
ASTNode* caller_ast_node,
const map<const Type*, const Type*>& resugar_map,
const set<const Type*>& blocked_types) {
Clear();
caller_ast_node_ = caller_ast_node;
resugar_map_ = resugar_map;
blocked_types_ = blocked_types;

set_current_ast_node(caller_ast_node);

AstFlattenerVisitor nodeset_getter(compiler());
nodes_to_ignore_ = nodeset_getter.GetNodesBelow(
const_cast<NamedDecl*>(GetDefinitionAsWritten(decl)));

TraverseDataAndTypeMembersOfClassHelper(decl);
}

//------------------------------------------------------------
// Implements virtual methods from Base.

Expand Down Expand Up @@ -3557,15 +3582,20 @@ class InstantiatedTemplateVisitor
return true; // avoid recursion & repetition
traversed_decls_.insert(class_decl);

if (ReplayClassMemberUsesFromPrecomputedList(type))
return true;
return TraverseDataAndTypeMembersOfClassHelper(class_decl);
}

bool TraverseDataAndTypeMembersOfClassHelper(
const ClassTemplateSpecializationDecl* class_decl) {
// If we have cached the reporting done for this decl before,
// report again (but with the new caller_loc this time).
// Otherwise, for all reporting done in the rest of this scope,
// store in the cache for this function.
if (ReplayUsesFromCache(*ClassMembersFullUseCache(),
class_decl, caller_loc()))
return true;
if (ReplayClassMemberUsesFromPrecomputedList(type))
return true;

// Sometimes, an implicit specialization occurs to be not instantiated.
// TODO(bolshakov): don't report them at all as full uses or figure out
Expand Down Expand Up @@ -4063,35 +4093,44 @@ class IwyuAstConsumer
// we don't want iwyu to recommend removing the 'forward declare' of Foo.
//
// Additionally, this type of decl is also used to represent explicit template
// instantiations. Only instantiation definitions causing instantiation of
// member functions are handled here. Other instantiated parts of the template
// are handled in 'VisitTemplateSpecializationType', as usual.
// instantiations.
bool VisitClassTemplateSpecializationDecl(
ClassTemplateSpecializationDecl* decl) {
if (!IsExplicitInstantiation(decl)) {
if (!CanIgnoreCurrentASTNode()) {
ReportDeclForwardDeclareUse(CurrentLoc(),
decl->getSpecializedTemplate());
}
} else if (IsExplicitInstantiationDefinitionAsWritten(decl)) {
// Explicit instantiation definition causes instantiation of all
// the template methods. Scan them here to assure that all the needed
// template argument types are '#include'd.
const TypeLoc type_loc = decl->getTypeAsWritten()->getTypeLoc();
if (CanIgnoreLocation(GetLocation(&type_loc)))
} else {
if (CanIgnoreLocation(decl->getLocation()))
return true;
// Clang attributes 'ClassTemplateSpecializationDecl' to the original
// template location. Construct a new node corresponding to the template
// spec type location (as written) so that reportings from
// 'InstantiatedTemplateVisitor' are attributed to the correct location.
const ASTNode type_loc_node(&type_loc);
const TemplateInstantiationData data =
GetTplInstData(type_loc.getTypePtr());
// Clang instantiates methods in the first ("canonical") spec decl context
// (which may correspond to instantiation declaration, not to definition).
for (const CXXMethodDecl* member : decl->getCanonicalDecl()->methods()) {
instantiated_template_visitor_.ScanInstantiatedFunction(
member, &type_loc_node, data.resugar_map, data.provided_types);
// Report the original template definition, or a partial or full
// specialization, which appropriate. GetTagDefinition returns the right
// one.
ReportDeclUse(CurrentLoc(), GetTagDefinition(decl));

std::vector<TemplateArgument> args;
for (const auto arg : decl->getTemplateArgsAsWritten()->arguments())
args.push_back(arg.getArgument());

const TemplateInstantiationData data = GetTplInstData(args, decl);
instantiated_template_visitor_.ScanInstantiatedClass(
decl, current_ast_node(), data.resugar_map, data.provided_types);

if (IsExplicitInstantiationDefinitionAsWritten(decl)) {
// Explicit instantiation definition causes instantiation of all
// the template methods. Scan them here to assure that all the needed
// template argument types are '#include'd.

// Clang instantiates methods in the first ("canonical") spec decl
// context (which may correspond to instantiation declaration,
// not to definition).
for (const CXXMethodDecl* member :
decl->getCanonicalDecl()->methods()) {
instantiated_template_visitor_.ScanInstantiatedFunction(
member, current_ast_node(), data.resugar_map,
data.provided_types);
}
}
}

Expand Down Expand Up @@ -4465,6 +4504,14 @@ class IwyuAstConsumer
});
}

TemplateInstantiationData GetTplInstData(
llvm::ArrayRef<TemplateArgument> args,
const ClassTemplateSpecializationDecl* decl) const {
return GetTplInstDataForClass(args, decl, [this](const Type* type) {
return GetProvidedTypeComponents(type);
});
}

TemplateInstantiationData GetTplInstData(const FunctionDecl* decl,
const Expr* calling_expr) const {
return GetTplInstDataForFunction(
Expand Down
45 changes: 33 additions & 12 deletions iwyu_ast_util.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1064,7 +1064,7 @@ TemplateInstantiationData GetTplInstDataForFunction(
return TemplateInstantiationData{resugar_map, provided_types};
}

const NamedDecl* GetInstantiatedFromDecl(const CXXRecordDecl* class_decl) {
const NamedDecl* GetInstantiatedFromDecl(const NamedDecl* class_decl) {
if (const ClassTemplateSpecializationDecl* tpl_sp_decl =
DynCastFrom(class_decl)) { // an instantiated class template
PointerUnion<ClassTemplateDecl*, ClassTemplatePartialSpecializationDecl*>
Expand Down Expand Up @@ -1518,22 +1518,18 @@ bool HasImplicitConversionConstructor(const Type* type) {
return HasImplicitConversionCtor(cxx_class);
}

TemplateInstantiationData GetTplInstDataForClassNoComponentTypes(
const Type* type, function<set<const Type*>(const Type*)> provided_getter) {
static TemplateInstantiationData GetTplInstDataForClassNoComponentTypes(
ArrayRef<TemplateArgument> written_tpl_args,
const ClassTemplateSpecializationDecl* cls_tpl_decl,
function<set<const Type*>(const Type*)> provided_getter) {
map<const Type*, const Type*> retval;
const auto* tpl_spec_type = type->getAs<TemplateSpecializationType>();
if (!tpl_spec_type) {
return TemplateInstantiationData{retval, {}};
}

// Pull the template arguments out of the specialization type. If this is
// a ClassTemplateSpecializationDecl specifically, we want to
// get the arguments therefrom to correctly handle default arguments.
llvm::ArrayRef<TemplateArgument> tpl_args = tpl_spec_type->template_arguments();
llvm::ArrayRef<TemplateArgument> tpl_args = written_tpl_args;
unsigned num_args = tpl_args.size();

const NamedDecl* decl = TypeToDeclAsWritten(tpl_spec_type);
const auto* cls_tpl_decl = dyn_cast<ClassTemplateSpecializationDecl>(decl);
if (cls_tpl_decl) {
const TemplateArgumentList& tpl_arg_list =
cls_tpl_decl->getTemplateInstantiationArgs();
Expand All @@ -1548,9 +1544,9 @@ TemplateInstantiationData GetTplInstDataForClassNoComponentTypes(
set<unsigned> explicit_args; // indices into tpl_args we've filled
SugaredTypeEnumerator type_enumerator;
set<const Type*> provided_types;
for (unsigned i = 0; i < tpl_spec_type->template_arguments().size(); ++i) {
for (unsigned i = 0; i < written_tpl_args.size(); ++i) {
set<const Type*> arg_components =
type_enumerator.Enumerate(tpl_spec_type->template_arguments()[i]);
type_enumerator.Enumerate(written_tpl_args[i]);
// Go through all template types mentioned in the arg-as-written,
// and compare it against each of the types in the template decl
// (the latter are all desugared). If there's a match, update
Expand Down Expand Up @@ -1587,6 +1583,19 @@ TemplateInstantiationData GetTplInstDataForClassNoComponentTypes(
return TemplateInstantiationData{retval, provided_types};
}

TemplateInstantiationData GetTplInstDataForClassNoComponentTypes(
const clang::Type* type,
std::function<set<const clang::Type*>(const clang::Type*)>
provided_getter) {
const auto* tpl_spec_type = type->getAs<TemplateSpecializationType>();
if (!tpl_spec_type)
return TemplateInstantiationData{};
const NamedDecl* decl = TypeToDeclAsWritten(tpl_spec_type);
const auto* cls_tpl_decl = dyn_cast<ClassTemplateSpecializationDecl>(decl);
return GetTplInstDataForClassNoComponentTypes(
tpl_spec_type->template_arguments(), cls_tpl_decl, provided_getter);
}

TemplateInstantiationData GetTplInstDataForClass(
const Type* type, function<set<const Type*>(const Type*)> provided_getter) {
TemplateInstantiationData result =
Expand All @@ -1597,6 +1606,18 @@ TemplateInstantiationData GetTplInstDataForClass(
result.provided_types};
}

TemplateInstantiationData GetTplInstDataForClass(
ArrayRef<TemplateArgument> written_tpl_args,
const ClassTemplateSpecializationDecl* cls_tpl_decl,
function<set<const Type*>(const Type*)> provided_getter) {
TemplateInstantiationData result = GetTplInstDataForClassNoComponentTypes(
written_tpl_args, cls_tpl_decl, provided_getter);
return TemplateInstantiationData{
ResugarTypeComponents(
result.resugar_map), // add in the decomposition of retval
result.provided_types};
}

bool CanBeOpaqueDeclared(const EnumType* type) {
return type->getDecl()->isFixed();
}
Expand Down
12 changes: 11 additions & 1 deletion iwyu_ast_util.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,11 @@ class TypeDecl;
class ValueDecl;
} // namespace clang

namespace llvm {
template <typename>
class ArrayRef;
} // namespace llvm

namespace include_what_you_use {

using std::map;
Expand Down Expand Up @@ -613,7 +618,7 @@ TemplateInstantiationData GetTplInstDataForFunction(
// return the template decl, which provides the actual class body.
// We try to return a decl that's also a definition, when possible.
const clang::NamedDecl* GetInstantiatedFromDecl(
const clang::CXXRecordDecl* class_decl);
const clang::NamedDecl* class_decl);

// For an implicitly instantiated templated c++ class -- that is, a
// class like vector<int> that isn't explicitly written in the source
Expand Down Expand Up @@ -837,6 +842,11 @@ TemplateInstantiationData GetTplInstDataForClass(
const clang::Type* type,
std::function<set<const clang::Type*>(const clang::Type*)> provided_getter);

TemplateInstantiationData GetTplInstDataForClass(
llvm::ArrayRef<clang::TemplateArgument> written_tpl_args,
const clang::ClassTemplateSpecializationDecl* cls_tpl_decl,
std::function<set<const clang::Type*>(const clang::Type*)> provided_getter);

// Like GetTplInstDataForClass, but if a type has
// components (for instance, 'Foo*' and 'vector<Foo>' both
// have a component Foo), we don't include the components
Expand Down
9 changes: 7 additions & 2 deletions iwyu_location_util.cc
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#include "clang/AST/TemplateBase.h"
#include "clang/AST/TypeLoc.h"
#include "clang/Basic/SourceLocation.h"
#include "clang/Basic/Specifiers.h"
#include "iwyu_ast_util.h"
#include "iwyu_string_util.h"

Expand All @@ -42,6 +43,7 @@ using clang::SourceLocation;
using clang::SourceManager;
using clang::Stmt;
using clang::TemplateArgumentLoc;
using clang::TemplateSpecializationKind;
using clang::TypeLoc;
using clang::UnaryOperator;
using clang::UnresolvedMemberExpr;
Expand Down Expand Up @@ -79,10 +81,13 @@ SourceLocation GetLocation(const Decl* decl) {

if (const CXXMethodDecl* method_decl = DynCastFrom(decl)) {
if (method_decl->isImplicit())
decl = method_decl->getParent();
decl = GetDefinitionAsWritten(method_decl->getParent());
}
if (const ClassTemplateSpecializationDecl* spec = DynCastFrom(decl)) {
decl = GetDefinitionAsWritten(spec); // templated class
const TemplateSpecializationKind kind = spec->getSpecializationKind();
if (kind != clang::TSK_ExplicitInstantiationDeclaration &&
kind != clang::TSK_ExplicitInstantiationDefinition)
decl = GetDefinitionAsWritten(spec); // templated class
} else if (const FunctionDecl* fn_decl = DynCastFrom(decl)) {
if (fn_decl->getTemplateInstantiationPattern()) // templated function
decl = GetDefinitionAsWritten(fn_decl);
Expand Down
1 change: 0 additions & 1 deletion tests/cxx/expl_inst_select.cc
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@

// An explicit instantiation definition anchors a prior declaration.
// IWYU: Template is...*expl_inst_select-i1.h
// IWYU: Template is...*expl_inst_select-i2.h.*for explicit instantiation
template class Template<char>;

// An explicit instantiation declaration for later use.
Expand Down
10 changes: 10 additions & 0 deletions tests/cxx/explicit_instantiation-template.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,16 @@ class ClassWithMethodUsingPtr {
}
};

template <typename, typename = void>
class TplWithDefArg {};

template <typename T>
class TplWithDefArg<int, T> {};

constexpr int getInt() {
return 1;
}

#endif // INCLUDE_WHAT_YOU_USE_TESTS_CXX_EXPLICIT_INSTANTIATION_TEMPLATE_H_

/**** IWYU_SUMMARY
Expand Down
19 changes: 18 additions & 1 deletion tests/cxx/explicit_instantiation.cc
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,23 @@ template class ClassWithMethodUsingPtr<IndirectClass>;
// IWYU: IndirectClass is...*indirect.h
template class ClassWithUsingMethod2<IndirectClass>;

// IWYU should not forward-declare templates with default args. Test that
// an explicit instantiation after the template spec reference doesn't muss
// the tool.
// IWYU: TplWithDefArg is...*explicit_instantiation-template.h
TplWithDefArg<int>* p = nullptr;
// IWYU: TplWithDefArg is...*explicit_instantiation-template.h
extern template class TplWithDefArg<int>;

template <typename>
class TplUsingNondependentDecl {
// IWYU: getInt is...*explicit_instantiation-template.h
static constexpr int i = getInt();
};

// No reporting getInt here.
template class TplUsingNondependentDecl<void>;

/**** IWYU_SUMMARY
tests/cxx/explicit_instantiation.cc should add these lines:
Expand All @@ -105,7 +122,7 @@ tests/cxx/explicit_instantiation.cc should remove these lines:
The full include-list for tests/cxx/explicit_instantiation.cc:
#include "tests/cxx/explicit_instantiation-spec.h" // for Template
#include "tests/cxx/explicit_instantiation-template.h" // for ClassWithMethodUsingPtr, ClassWithUsingMethod, Template
#include "tests/cxx/explicit_instantiation-template.h" // for ClassWithMethodUsingPtr, ClassWithUsingMethod, Template, TplWithDefArg, getInt
#include "tests/cxx/explicit_instantiation-template2.h" // for ClassWithUsingMethod2
#include "tests/cxx/indirect.h" // for IndirectClass, IndirectTemplate
Expand Down
3 changes: 1 addition & 2 deletions tests/cxx/explicit_instantiation2.cc
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,14 @@
// Positive scenarios:
// 1a/1b: Implicit instantiation before and after an explicit instantiation
// definition (2), as it affects the semantics.
// 2: Explicit instantiation definition.
// 3: Full use in a template, provided as an explicit parameter.
// 5: Full use in a template, provided as an default parameter.
// 7: Full use in a template, provided as a template template parameter.
// 8: Fwd-decl use in a template, provided as a template template parameter.
//
// Negative scenarios, where the dependent template specialization is not
// required, or it does not provide an explicit instantiation:
// 2: Explicit instantiation definition.
// 4: Fwd-decl use in a template, provided as an explicit parameter.
// 6: Fwd-decl use in a template, provided as a default parameter.
// 9: Implicit instantiation of Template<int>
Expand All @@ -40,7 +40,6 @@
Template<bool> t1a; // 1a

// IWYU: Template is...*explicit_instantiation-template.h
// IWYU: Template is...*template_bool.h.*for explicit instantiation
template class Template<bool>; // 2

// Included explicit instantiation no longer reported here as a local definition
Expand Down

0 comments on commit 4f620e8

Please sign in to comment.