1 //===--- UnusedUsingDeclsCheck.cpp - clang-tidy----------------------------===//
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
7 //===----------------------------------------------------------------------===//
9 #include "UnusedUsingDeclsCheck.h"
10 #include "clang/AST/ASTContext.h"
11 #include "clang/AST/Decl.h"
12 #include "clang/ASTMatchers/ASTMatchFinder.h"
13 #include "clang/Lex/Lexer.h"
15 using namespace clang::ast_matchers
;
17 namespace clang::tidy::misc
{
21 AST_MATCHER_P(DeducedTemplateSpecializationType
, refsToTemplatedDecl
,
22 clang::ast_matchers::internal::Matcher
<NamedDecl
>, DeclMatcher
) {
23 if (const auto *TD
= Node
.getTemplateName().getAsTemplateDecl())
24 return DeclMatcher
.matches(*TD
, Finder
, Builder
);
30 // A function that helps to tell whether a TargetDecl in a UsingDecl will be
31 // checked. Only variable, function, function template, class template, class,
32 // enum declaration and enum constant declaration are considered.
33 static bool shouldCheckDecl(const Decl
*TargetDecl
) {
34 return isa
<RecordDecl
>(TargetDecl
) || isa
<ClassTemplateDecl
>(TargetDecl
) ||
35 isa
<FunctionDecl
>(TargetDecl
) || isa
<VarDecl
>(TargetDecl
) ||
36 isa
<FunctionTemplateDecl
>(TargetDecl
) || isa
<EnumDecl
>(TargetDecl
) ||
37 isa
<EnumConstantDecl
>(TargetDecl
);
40 UnusedUsingDeclsCheck::UnusedUsingDeclsCheck(StringRef Name
,
41 ClangTidyContext
*Context
)
42 : ClangTidyCheck(Name
, Context
) {
43 std::optional
<StringRef
> HeaderFileExtensionsOption
=
44 Options
.get("HeaderFileExtensions");
45 RawStringHeaderFileExtensions
=
46 HeaderFileExtensionsOption
.value_or(utils::defaultHeaderFileExtensions());
47 if (HeaderFileExtensionsOption
) {
48 if (!utils::parseFileExtensions(RawStringHeaderFileExtensions
,
50 utils::defaultFileExtensionDelimiters())) {
51 this->configurationDiag("Invalid header file extension: '%0'")
52 << RawStringHeaderFileExtensions
;
55 HeaderFileExtensions
= Context
->getHeaderFileExtensions();
58 void UnusedUsingDeclsCheck::registerMatchers(MatchFinder
*Finder
) {
59 Finder
->addMatcher(usingDecl(isExpansionInMainFile()).bind("using"), this);
60 auto DeclMatcher
= hasDeclaration(namedDecl().bind("used"));
61 Finder
->addMatcher(loc(templateSpecializationType(DeclMatcher
)), this);
62 Finder
->addMatcher(loc(deducedTemplateSpecializationType(
63 refsToTemplatedDecl(namedDecl().bind("used")))),
65 Finder
->addMatcher(callExpr(callee(unresolvedLookupExpr().bind("used"))),
68 callExpr(hasDeclaration(functionDecl(
69 forEachTemplateArgument(templateArgument().bind("used"))))),
71 Finder
->addMatcher(loc(templateSpecializationType(forEachTemplateArgument(
72 templateArgument().bind("used")))),
74 Finder
->addMatcher(userDefinedLiteral().bind("used"), this);
76 loc(elaboratedType(unless(hasQualifier(nestedNameSpecifier())),
77 hasUnqualifiedDesugaredType(type().bind("usedType")))),
79 // Cases where we can identify the UsingShadowDecl directly, rather than
81 // FIXME: cover more cases in this way, as the AST supports it.
82 auto ThroughShadowMatcher
= throughUsingDecl(namedDecl().bind("usedShadow"));
83 Finder
->addMatcher(declRefExpr(ThroughShadowMatcher
), this);
84 Finder
->addMatcher(loc(usingType(ThroughShadowMatcher
)), this);
87 void UnusedUsingDeclsCheck::check(const MatchFinder::MatchResult
&Result
) {
88 if (Result
.Context
->getDiagnostics().hasUncompilableErrorOccurred())
90 // We don't emit warnings on unused-using-decls from headers, so bail out if
91 // the main file is a header.
92 if (auto MainFile
= Result
.SourceManager
->getFileEntryRefForID(
93 Result
.SourceManager
->getMainFileID());
94 utils::isFileExtension(MainFile
->getName(), HeaderFileExtensions
))
97 if (const auto *Using
= Result
.Nodes
.getNodeAs
<UsingDecl
>("using")) {
98 // Ignores using-declarations defined in macros.
99 if (Using
->getLocation().isMacroID())
102 // Ignores using-declarations defined in class definition.
103 if (isa
<CXXRecordDecl
>(Using
->getDeclContext()))
106 // FIXME: We ignore using-decls defined in function definitions at the
107 // moment because of false positives caused by ADL and different function
109 if (isa
<FunctionDecl
>(Using
->getDeclContext()))
112 UsingDeclContext
Context(Using
);
113 Context
.UsingDeclRange
= CharSourceRange::getCharRange(
114 Using
->getBeginLoc(),
115 Lexer::findLocationAfterToken(
116 Using
->getEndLoc(), tok::semi
, *Result
.SourceManager
, getLangOpts(),
117 /*SkipTrailingWhitespaceAndNewLine=*/true));
118 for (const auto *UsingShadow
: Using
->shadows()) {
119 const auto *TargetDecl
= UsingShadow
->getTargetDecl()->getCanonicalDecl();
120 if (shouldCheckDecl(TargetDecl
))
121 Context
.UsingTargetDecls
.insert(TargetDecl
);
123 if (!Context
.UsingTargetDecls
.empty())
124 Contexts
.push_back(Context
);
128 // Mark a corresponding using declaration as used.
129 auto RemoveNamedDecl
= [&](const NamedDecl
*Used
) {
130 removeFromFoundDecls(Used
);
131 // Also remove variants of Used.
132 if (const auto *FD
= dyn_cast
<FunctionDecl
>(Used
)) {
133 removeFromFoundDecls(FD
->getPrimaryTemplate());
136 if (const auto *Specialization
=
137 dyn_cast
<ClassTemplateSpecializationDecl
>(Used
)) {
138 removeFromFoundDecls(Specialization
->getSpecializedTemplate());
141 if (const auto *ECD
= dyn_cast
<EnumConstantDecl
>(Used
)) {
142 if (const auto *ET
= ECD
->getType()->getAs
<EnumType
>())
143 removeFromFoundDecls(ET
->getDecl());
146 // We rely on the fact that the clang AST is walked in order, usages are only
147 // marked after a corresponding using decl has been found.
148 if (const auto *Used
= Result
.Nodes
.getNodeAs
<NamedDecl
>("used")) {
149 RemoveNamedDecl(Used
);
153 if (const auto *T
= Result
.Nodes
.getNodeAs
<Type
>("usedType")) {
154 if (const auto *ND
= T
->getAsTagDecl())
159 if (const auto *UsedShadow
=
160 Result
.Nodes
.getNodeAs
<UsingShadowDecl
>("usedShadow")) {
161 removeFromFoundDecls(UsedShadow
->getTargetDecl());
165 if (const auto *Used
= Result
.Nodes
.getNodeAs
<TemplateArgument
>("used")) {
166 if (Used
->getKind() == TemplateArgument::Template
) {
167 if (const auto *TD
= Used
->getAsTemplate().getAsTemplateDecl())
168 removeFromFoundDecls(TD
);
172 if (Used
->getKind() == TemplateArgument::Type
) {
173 if (auto *RD
= Used
->getAsType()->getAsCXXRecordDecl())
174 removeFromFoundDecls(RD
);
178 if (Used
->getKind() == TemplateArgument::Declaration
) {
179 RemoveNamedDecl(Used
->getAsDecl());
184 if (const auto *DRE
= Result
.Nodes
.getNodeAs
<DeclRefExpr
>("used")) {
185 RemoveNamedDecl(DRE
->getDecl());
188 // Check the uninstantiated template function usage.
189 if (const auto *ULE
= Result
.Nodes
.getNodeAs
<UnresolvedLookupExpr
>("used")) {
190 for (const NamedDecl
*ND
: ULE
->decls()) {
191 if (const auto *USD
= dyn_cast
<UsingShadowDecl
>(ND
))
192 removeFromFoundDecls(USD
->getTargetDecl()->getCanonicalDecl());
196 // Check user-defined literals
197 if (const auto *UDL
= Result
.Nodes
.getNodeAs
<UserDefinedLiteral
>("used"))
198 removeFromFoundDecls(UDL
->getCalleeDecl());
201 void UnusedUsingDeclsCheck::removeFromFoundDecls(const Decl
*D
) {
204 // FIXME: Currently, we don't handle the using-decls being used in different
205 // scopes (such as different namespaces, different functions). Instead of
206 // giving an incorrect message, we mark all of them as used.
208 // FIXME: Use a more efficient way to find a matching context.
209 for (auto &Context
: Contexts
) {
210 if (Context
.UsingTargetDecls
.contains(D
->getCanonicalDecl()))
211 Context
.IsUsed
= true;
215 void UnusedUsingDeclsCheck::onEndOfTranslationUnit() {
216 for (const auto &Context
: Contexts
) {
217 if (!Context
.IsUsed
) {
218 diag(Context
.FoundUsingDecl
->getLocation(), "using decl %0 is unused")
219 << Context
.FoundUsingDecl
;
220 // Emit a fix and a fix description of the check;
221 diag(Context
.FoundUsingDecl
->getLocation(),
222 /*Description=*/"remove the using", DiagnosticIDs::Note
)
223 << FixItHint::CreateRemoval(Context
.UsingDeclRange
);
229 } // namespace clang::tidy::misc