[AST] Add a new TemplateKind for template decls found via a using decl.

This is the template version of https://reviews.llvm.org/D114251.

This patch introduces a new template name kind (UsingTemplateName). The
UsingTemplateName stores the found using-shadow decl (and underlying
template can be retrieved from the using-shadow decl). With the new
template name, we can be able to find the using decl that a template
typeloc (e.g. TemplateSpecializationTypeLoc) found its underlying template,
which is useful for tooling use cases (include cleaner etc).

This patch merely focuses on adding the node to the AST.

Next steps:
- support using-decl in qualified template name;
- update the clangd and other tools to use this new node;
- add ast matchers for matching different kinds of template names;

Differential Revision: https://reviews.llvm.org/D123127
This commit is contained in:
Haojian Wu 2022-04-11 14:44:46 +02:00
parent 7d7771f34d
commit 5a5be4044f
22 changed files with 239 additions and 13 deletions

View File

@ -184,6 +184,7 @@ class DumpVisitor : public RecursiveASTVisitor<DumpVisitor> {
TEMPLATE_KIND(DependentTemplate);
TEMPLATE_KIND(SubstTemplateTemplateParm);
TEMPLATE_KIND(SubstTemplateTemplateParmPack);
TEMPLATE_KIND(UsingTemplate);
#undef TEMPLATE_KIND
}
llvm_unreachable("Unhandled NameKind enum");

View File

@ -762,6 +762,7 @@ public:
case TemplateName::QualifiedTemplate:
case TemplateName::SubstTemplateTemplateParm:
case TemplateName::SubstTemplateTemplateParmPack:
case TemplateName::UsingTemplate:
// Names that could be resolved to a TemplateDecl are handled elsewhere.
break;
}

View File

@ -620,6 +620,16 @@ let Class = PropertyTypeCase<TemplateName, "Template"> in {
return TemplateName(declaration);
}]>;
}
let Class = PropertyTypeCase<TemplateName, "UsingTemplate"> in {
def : Property<"foundDecl", UsingShadowDeclRef> {
let Read = [{ node.getAsUsingShadowDecl() }];
}
def : Creator<[{
return TemplateName(foundDecl);
}]>;
}
let Class = PropertyTypeCase<TemplateName, "OverloadedTemplate"> in {
def : Property<"overloads", Array<NamedDeclRef>> {
let Read = [{ node.getAsOverloadedTemplate()->decls() }];

View File

@ -39,6 +39,7 @@ class SubstTemplateTemplateParmStorage;
class TemplateArgument;
class TemplateDecl;
class TemplateTemplateParmDecl;
class UsingShadowDecl;
/// Implementation class used to describe either a set of overloaded
/// template names or an already-substituted template template parameter pack.
@ -190,7 +191,8 @@ public:
class TemplateName {
using StorageType =
llvm::PointerUnion<TemplateDecl *, UncommonTemplateNameStorage *,
QualifiedTemplateName *, DependentTemplateName *>;
QualifiedTemplateName *, DependentTemplateName *,
UsingShadowDecl *>;
StorageType Storage;
@ -224,7 +226,11 @@ public:
/// A template template parameter pack that has been substituted for
/// a template template argument pack, but has not yet been expanded into
/// individual arguments.
SubstTemplateTemplateParmPack
SubstTemplateTemplateParmPack,
/// A template name that refers to a template declaration found through a
/// specific using shadow declaration.
UsingTemplate,
};
TemplateName() = default;
@ -235,6 +241,7 @@ public:
explicit TemplateName(SubstTemplateTemplateParmPackStorage *Storage);
explicit TemplateName(QualifiedTemplateName *Qual);
explicit TemplateName(DependentTemplateName *Dep);
explicit TemplateName(UsingShadowDecl *Using);
/// Determine whether this template name is NULL.
bool isNull() const;
@ -287,6 +294,13 @@ public:
/// structure, if any.
DependentTemplateName *getAsDependentTemplateName() const;
/// Retrieve the using shadow declaration through which the underlying
/// template declaration is introduced.
///
/// The underlying template declaration is not stored in the template name, it
/// can be retrieved via the using shadow declaration.
UsingShadowDecl *getAsUsingShadowDecl() const;
TemplateName getUnderlying() const;
/// Get the template name to substitute when this template name is used as a

View File

@ -317,6 +317,8 @@ public:
void VisitTagType(const TagType *T);
void VisitTemplateTypeParmType(const TemplateTypeParmType *T);
void VisitAutoType(const AutoType *T);
void VisitDeducedTemplateSpecializationType(
const DeducedTemplateSpecializationType *T);
void VisitTemplateSpecializationType(const TemplateSpecializationType *T);
void VisitInjectedClassNameType(const InjectedClassNameType *T);
void VisitObjCInterfaceType(const ObjCInterfaceType *T);

View File

@ -6125,6 +6125,9 @@ ASTContext::getNameForTemplate(TemplateName Name,
return DeclarationNameInfo(subst->getParameterPack()->getDeclName(),
NameLoc);
}
case TemplateName::UsingTemplate:
return DeclarationNameInfo(Name.getAsUsingShadowDecl()->getDeclName(),
NameLoc);
}
llvm_unreachable("bad template name kind!");
@ -6133,6 +6136,7 @@ ASTContext::getNameForTemplate(TemplateName Name,
TemplateName
ASTContext::getCanonicalTemplateName(const TemplateName &Name) const {
switch (Name.getKind()) {
case TemplateName::UsingTemplate:
case TemplateName::QualifiedTemplate:
case TemplateName::Template: {
TemplateDecl *Template = Name.getAsTemplateDecl();

View File

@ -9193,6 +9193,12 @@ Expected<TemplateName> ASTImporter::Import(TemplateName From) {
return ToContext.getSubstTemplateTemplateParmPack(
cast<TemplateTemplateParmDecl>(*ParamOrErr), *ArgPackOrErr);
}
case TemplateName::UsingTemplate: {
auto UsingOrError = Import(From.getAsUsingShadowDecl());
if (!UsingOrError)
return UsingOrError.takeError();
return TemplateName(cast<UsingShadowDecl>(*UsingOrError));
}
}
llvm_unreachable("Invalid template name kind");

View File

@ -517,6 +517,7 @@ static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
case TemplateName::Template:
case TemplateName::QualifiedTemplate:
case TemplateName::SubstTemplateTemplateParm:
case TemplateName::UsingTemplate:
// It is sufficient to check value of getAsTemplateDecl.
break;

View File

@ -2207,6 +2207,7 @@ void CXXNameMangler::mangleType(TemplateName TN) {
TD = TN.getAsQualifiedTemplateName()->getTemplateDecl();
goto HaveDecl;
case TemplateName::UsingTemplate:
case TemplateName::Template:
TD = TN.getAsTemplateDecl();
goto HaveDecl;
@ -2383,6 +2384,12 @@ bool CXXNameMangler::mangleUnresolvedTypeOrSimpleId(QualType Ty,
Out << "_SUBSTPACK_";
break;
}
case TemplateName::UsingTemplate: {
TemplateDecl *TD = TN.getAsTemplateDecl();
assert(TD && !isa<TemplateTemplateParmDecl>(TD));
mangleSourceNameWithAbiTags(TD);
break;
}
}
// Note: we don't pass in the template name here. We are mangling the

View File

@ -150,6 +150,7 @@ void ODRHash::AddTemplateName(TemplateName Name) {
case TemplateName::DependentTemplate:
case TemplateName::SubstTemplateTemplateParm:
case TemplateName::SubstTemplateTemplateParmPack:
case TemplateName::UsingTemplate:
break;
}
}

View File

@ -13,6 +13,7 @@
#include "clang/AST/TemplateName.h"
#include "clang/AST/Decl.h"
#include "clang/AST/DeclBase.h"
#include "clang/AST/DeclCXX.h"
#include "clang/AST/DeclTemplate.h"
#include "clang/AST/DependenceFlags.h"
#include "clang/AST/NestedNameSpecifier.h"
@ -76,6 +77,7 @@ TemplateName::TemplateName(SubstTemplateTemplateParmPackStorage *Storage)
: Storage(Storage) {}
TemplateName::TemplateName(QualifiedTemplateName *Qual) : Storage(Qual) {}
TemplateName::TemplateName(DependentTemplateName *Dep) : Storage(Dep) {}
TemplateName::TemplateName(UsingShadowDecl *Using) : Storage(Using) {}
bool TemplateName::isNull() const { return Storage.isNull(); }
@ -86,6 +88,8 @@ TemplateName::NameKind TemplateName::getKind() const {
return DependentTemplate;
if (Storage.is<QualifiedTemplateName *>())
return QualifiedTemplate;
if (Storage.is<UsingShadowDecl *>())
return UsingTemplate;
UncommonTemplateNameStorage *uncommon
= Storage.get<UncommonTemplateNameStorage*>();
@ -108,6 +112,9 @@ TemplateDecl *TemplateName::getAsTemplateDecl() const {
if (SubstTemplateTemplateParmStorage *sub = getAsSubstTemplateTemplateParm())
return sub->getReplacement().getAsTemplateDecl();
if (UsingShadowDecl *USD = getAsUsingShadowDecl())
return cast<TemplateDecl>(USD->getTargetDecl());
return nullptr;
}
@ -153,6 +160,10 @@ DependentTemplateName *TemplateName::getAsDependentTemplateName() const {
return Storage.dyn_cast<DependentTemplateName *>();
}
UsingShadowDecl *TemplateName::getAsUsingShadowDecl() const {
return Storage.dyn_cast<UsingShadowDecl *>();
}
TemplateName TemplateName::getNameToSubstitute() const {
TemplateDecl *Decl = getAsTemplateDecl();
@ -222,7 +233,20 @@ bool TemplateName::containsUnexpandedParameterPack() const {
void TemplateName::print(raw_ostream &OS, const PrintingPolicy &Policy,
Qualified Qual) const {
if (TemplateDecl *Template = Storage.dyn_cast<TemplateDecl *>())
TemplateDecl *Template = Storage.dyn_cast<TemplateDecl *>();
if (getKind() == TemplateName::UsingTemplate) {
// After `namespace ns { using std::vector }`, what is the fully-qualified
// name of the UsingTemplateName `vector` within ns?
//
// - ns::vector (the qualified name of the using-shadow decl)
// - std::vector (the qualifier name of the underlying template decl)
//
// Similar to the UsingType behavior, std::vector is much more common, and
// provides more information in practice, we print the underlying template
// decl of the using-shadow decl.
Template = getAsTemplateDecl();
}
if (Template)
if (Policy.CleanUglifiedParameters &&
isa<TemplateTemplateParmDecl>(Template) && Template->getIdentifier())
OS << Template->getIdentifier()->deuglifiedName();
@ -262,6 +286,7 @@ void TemplateName::print(raw_ostream &OS, const PrintingPolicy &Policy,
else if (AssumedTemplateStorage *Assumed = getAsAssumedTemplateName()) {
Assumed->getDeclName().print(OS, Policy);
} else {
assert(getKind() == TemplateName::OverloadedTemplate);
OverloadedTemplateStorage *OTS = getAsOverloadedTemplate();
(*OTS->begin())->printName(OS);
}

View File

@ -900,12 +900,17 @@ void TextNodeDumper::VisitIntegralTemplateArgument(const TemplateArgument &TA) {
}
void TextNodeDumper::VisitTemplateTemplateArgument(const TemplateArgument &TA) {
if (TA.getAsTemplate().getKind() == TemplateName::UsingTemplate)
OS << " using";
OS << " template ";
TA.getAsTemplate().dump(OS);
}
void TextNodeDumper::VisitTemplateExpansionTemplateArgument(
const TemplateArgument &TA) {
if (TA.getAsTemplateOrTemplatePattern().getKind() ==
TemplateName::UsingTemplate)
OS << " using";
OS << " template expansion ";
TA.getAsTemplateOrTemplatePattern().dump(OS);
}
@ -1575,10 +1580,18 @@ void TextNodeDumper::VisitAutoType(const AutoType *T) {
}
}
void TextNodeDumper::VisitDeducedTemplateSpecializationType(
const DeducedTemplateSpecializationType *T) {
if (T->getTemplateName().getKind() == TemplateName::UsingTemplate)
OS << " using";
}
void TextNodeDumper::VisitTemplateSpecializationType(
const TemplateSpecializationType *T) {
if (T->isTypeAlias())
OS << " alias";
if (T->getTemplateName().getKind() == TemplateName::UsingTemplate)
OS << " using";
OS << " ";
T->getTemplateName().dump(OS);
}

View File

@ -3686,7 +3686,8 @@ TemplateSpecializationType::TemplateSpecializationType(
"Use DependentTemplateSpecializationType for dependent template-name");
assert((T.getKind() == TemplateName::Template ||
T.getKind() == TemplateName::SubstTemplateTemplateParm ||
T.getKind() == TemplateName::SubstTemplateTemplateParmPack) &&
T.getKind() == TemplateName::SubstTemplateTemplateParmPack ||
T.getKind() == TemplateName::UsingTemplate) &&
"Unexpected template name for TemplateSpecializationType");
auto *TemplateArgs = reinterpret_cast<TemplateArgument *>(this + 1);

View File

@ -504,9 +504,11 @@ ParsedType Sema::getTypeName(const IdentifierInfo &II, SourceLocation NameLoc,
FoundUsingShadow = nullptr;
} else if (AllowDeducedTemplate) {
if (auto *TD = getAsTypeTemplateDecl(IIDecl)) {
// FIXME: TemplateName should include FoundUsingShadow sugar.
T = Context.getDeducedTemplateSpecializationType(TemplateName(TD),
QualType(), false);
assert(!FoundUsingShadow || FoundUsingShadow->getTargetDecl() == TD);
TemplateName Template =
FoundUsingShadow ? TemplateName(FoundUsingShadow) : TemplateName(TD);
T = Context.getDeducedTemplateSpecializationType(Template, QualType(),
false);
// Don't wrap in a further UsingType.
FoundUsingShadow = nullptr;
}
@ -1107,12 +1109,20 @@ Corrected:
IsFunctionTemplate = isa<FunctionTemplateDecl>(TD);
IsVarTemplate = isa<VarTemplateDecl>(TD);
if (SS.isNotEmpty())
UsingShadowDecl *FoundUsingShadow =
dyn_cast<UsingShadowDecl>(*Result.begin());
if (SS.isNotEmpty()) {
// FIXME: support using shadow-declaration in qualified template name.
Template =
Context.getQualifiedTemplateName(SS.getScopeRep(),
/*TemplateKeyword=*/false, TD);
else
Template = TemplateName(TD);
} else {
assert(!FoundUsingShadow ||
TD == cast<TemplateDecl>(FoundUsingShadow->getTargetDecl()));
Template = FoundUsingShadow ? TemplateName(FoundUsingShadow)
: TemplateName(TD);
}
} else {
// All results were non-template functions. This is a function template
// name.

View File

@ -11023,6 +11023,8 @@ void Sema::CheckDeductionGuideDeclarator(Declarator &D, QualType &R,
TemplateName SpecifiedName = RetTST.getTypePtr()->getTemplateName();
bool TemplateMatches =
Context.hasSameTemplateName(SpecifiedName, GuidedTemplate);
// FIXME: We should consider other template kinds (using, qualified),
// otherwise we will emit bogus diagnostics.
if (SpecifiedName.getKind() == TemplateName::Template && TemplateMatches)
AcceptableReturnType = true;
else {

View File

@ -11,11 +11,13 @@
#include "TreeTransform.h"
#include "clang/AST/ASTConsumer.h"
#include "clang/AST/ASTContext.h"
#include "clang/AST/Decl.h"
#include "clang/AST/DeclFriend.h"
#include "clang/AST/DeclTemplate.h"
#include "clang/AST/Expr.h"
#include "clang/AST/ExprCXX.h"
#include "clang/AST/RecursiveASTVisitor.h"
#include "clang/AST/TemplateName.h"
#include "clang/AST/TypeVisitor.h"
#include "clang/Basic/Builtins.h"
#include "clang/Basic/LangOptions.h"
@ -223,6 +225,7 @@ TemplateNameKind Sema::isTemplateName(Scope *S,
return TNK_Non_template;
NamedDecl *D = nullptr;
UsingShadowDecl *FoundUsingShadow = dyn_cast<UsingShadowDecl>(*R.begin());
if (R.isAmbiguous()) {
// If we got an ambiguity involving a non-function template, treat this
// as a template name, and pick an arbitrary template for error recovery.
@ -233,6 +236,7 @@ TemplateNameKind Sema::isTemplateName(Scope *S,
AnyFunctionTemplates = true;
else {
D = FoundTemplate;
FoundUsingShadow = dyn_cast<UsingShadowDecl>(FoundD);
break;
}
}
@ -283,10 +287,14 @@ TemplateNameKind Sema::isTemplateName(Scope *S,
if (SS.isSet() && !SS.isInvalid()) {
NestedNameSpecifier *Qualifier = SS.getScopeRep();
Template = Context.getQualifiedTemplateName(Qualifier,
hasTemplateKeyword, TD);
// FIXME: store the using TemplateName in QualifiedTemplateName if
// the TD is referred via a using-declaration.
Template =
Context.getQualifiedTemplateName(Qualifier, hasTemplateKeyword, TD);
} else {
Template = TemplateName(TD);
Template =
FoundUsingShadow ? TemplateName(FoundUsingShadow) : TemplateName(TD);
assert(!FoundUsingShadow || FoundUsingShadow->getTargetDecl() == TD);
}
if (isa<FunctionTemplateDecl>(TD)) {

View File

@ -0,0 +1,35 @@
// RUN: %clang_cc1 -triple x86_64-unknown-unknown -std=c++17 -ast-dump %s | FileCheck -strict-whitespace %s
// Tests to verify we construct correct using template names.
// TemplateNames are not dumped, so the sugar here isn't obvious. However
// the "using" on the TemplateSpecializationTypes shows that the
// UsingTemplateName is present.
namespace ns {
template<typename T> class S {
public:
S(T);
};
}
using ns::S;
// TemplateName in TemplateSpecializationType.
template<typename T>
using A = S<T>;
// CHECK: TypeAliasDecl
// CHECK-NEXT: `-TemplateSpecializationType {{.*}} 'S<T>' dependent using S
// TemplateName in TemplateArgument.
template <template <typename> class T> class X {};
using B = X<S>;
// CHECK: TypeAliasDecl
// CHECK-NEXT: `-TemplateSpecializationType {{.*}} 'X<ns::S>' sugar X
// CHECK-NEXT: |-TemplateArgument using template S
// CHECK-NEXT: `-RecordType {{.*}} 'X<ns::S>'
// CHECK-NEXT: `-ClassTemplateSpecialization {{.*}} 'X'
// TemplateName in DeducedTemplateSpecializationType.
S DeducedTemplateSpecializationT(123);
using C = decltype(DeducedTemplateSpecializationT);
// CHECK: DecltypeType {{.*}}
// CHECK-NEXT: |-DeclRefExpr {{.*}}
// CHECK-NEXT: `-DeducedTemplateSpecializationType {{.*}} 'ns::S<int>' sugar using

View File

@ -55,6 +55,9 @@ namespace WrongScope {
}
using N::NamedNS1;
NamedNS1(int) -> NamedNS1<int>; // expected-error {{deduction guide must be declared in the same scope as template}}
// FIXME: remove the following bogus diagnostic
// expected-error@-2{{deduction guide is not written as a specialization of template 'NamedNS1'}}
using namespace N;
NamedNS2(int) -> NamedNS2<int>; // expected-error {{deduction guide must be declared in the same scope as template}}
struct ClassMemberA {

View File

@ -1442,6 +1442,7 @@ bool CursorVisitor::VisitTemplateParameters(
bool CursorVisitor::VisitTemplateName(TemplateName Name, SourceLocation Loc) {
switch (Name.getKind()) {
case TemplateName::Template:
case TemplateName::UsingTemplate:
return Visit(MakeCursorTemplateRef(Name.getAsTemplateDecl(), Loc, TU));
case TemplateName::OverloadedTemplate:

View File

@ -890,6 +890,18 @@ TEST_P(ImportDecl, ImportUsingDecl) {
functionDecl(hasDescendant(usingDecl(hasName("bar")))));
}
TEST_P(ImportDecl, ImportUsingTemplate) {
MatchVerifier<Decl> Verifier;
testImport("namespace ns { template <typename T> struct S {}; }"
"template <template <typename> class T> class X {};"
"void declToImport() {"
"using ns::S; X<S> xi; }",
Lang_CXX11, "", Lang_CXX11, Verifier,
functionDecl(
hasDescendant(varDecl(hasTypeLoc(templateSpecializationTypeLoc(
hasAnyTemplateArgumentLoc(templateArgumentLoc())))))));
}
TEST_P(ImportDecl, ImportUsingEnumDecl) {
MatchVerifier<Decl> Verifier;
testImport("namespace foo { enum bar { baz, toto, quux }; }"

View File

@ -31,6 +31,7 @@ add_clang_unittest(ASTTests
SourceLocationTest.cpp
StmtPrinterTest.cpp
StructuralEquivalenceTest.cpp
TemplateNameTest.cpp
TypePrinterTest.cpp
)

View File

@ -0,0 +1,68 @@
//===- unittests/AST/TemplateNameTest.cpp --- Tests for TemplateName ------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "ASTPrint.h"
#include "clang/AST/ASTContext.h"
#include "clang/ASTMatchers/ASTMatchers.h"
#include "llvm/Support/raw_ostream.h"
#include "gtest/gtest.h"
namespace clang {
namespace {
using namespace ast_matchers;
std::string printTemplateName(TemplateName TN, const PrintingPolicy &Policy,
TemplateName::Qualified Qual) {
std::string Result;
llvm::raw_string_ostream Out(Result);
TN.print(Out, Policy, Qual);
return Out.str();
}
TEST(TemplateName, PrintUsingTemplate) {
std::string Code = R"cpp(
namespace std {
template <typename> struct vector {};
}
namespace absl { using std::vector; }
template<template <typename> class T> class X;
using absl::vector;
using A = X<vector>;
)cpp";
auto AST = tooling::buildASTFromCode(Code);
ASTContext &Ctx = AST->getASTContext();
// Match X in X<vector>.
auto Matcher = templateArgumentLoc().bind("id");
// !TemplateArgumentLoc is a local storage of the MatchCallback!
internal::CollectMatchesCallback StorageCB;
MatchFinder Finder;
Finder.addMatcher(Matcher, &StorageCB);
Finder.matchAST(Ctx);
const auto *Template =
selectFirst<TemplateArgumentLoc>("id", StorageCB.Nodes);
assert(Template);
TemplateName TN = Template->getArgument().getAsTemplate();
EXPECT_EQ(TN.getKind(), TemplateName::UsingTemplate);
EXPECT_EQ(TN.getAsUsingShadowDecl()->getTargetDecl(), TN.getAsTemplateDecl());
EXPECT_EQ(printTemplateName(TN, Ctx.getPrintingPolicy(),
TemplateName::Qualified::Fully),
"std::vector");
EXPECT_EQ(printTemplateName(TN, Ctx.getPrintingPolicy(),
TemplateName::Qualified::AsWritten),
"vector");
EXPECT_EQ(printTemplateName(TN, Ctx.getPrintingPolicy(),
TemplateName::Qualified::None),
"vector");
}
} // namespace
} // namespace clang