clang-tools 20.0.0git
UnusedUsingDeclsCheck.cpp
Go to the documentation of this file.
1//===--- UnusedUsingDeclsCheck.cpp - clang-tidy----------------------------===//
2//
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
6//
7//===----------------------------------------------------------------------===//
8
10#include "clang/AST/ASTContext.h"
11#include "clang/AST/Decl.h"
12#include "clang/ASTMatchers/ASTMatchFinder.h"
13#include "clang/Lex/Lexer.h"
14
15using namespace clang::ast_matchers;
16
17namespace clang::tidy::misc {
18
19namespace {
20
21AST_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);
25 return false;
26}
27
28} // namespace
29
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.
33static 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);
38}
39
41 ClangTidyContext *Context)
42 : ClangTidyCheck(Name, Context),
43 HeaderFileExtensions(Context->getHeaderFileExtensions()) {}
44
45void UnusedUsingDeclsCheck::registerMatchers(MatchFinder *Finder) {
46 Finder->addMatcher(usingDecl(isExpansionInMainFile()).bind("using"), this);
47 auto DeclMatcher = hasDeclaration(namedDecl().bind("used"));
48 Finder->addMatcher(loc(templateSpecializationType(DeclMatcher)), this);
49 Finder->addMatcher(loc(deducedTemplateSpecializationType(
50 refsToTemplatedDecl(namedDecl().bind("used")))),
51 this);
52 Finder->addMatcher(callExpr(callee(unresolvedLookupExpr().bind("used"))),
53 this);
54 Finder->addMatcher(
55 callExpr(hasDeclaration(functionDecl(
56 forEachTemplateArgument(templateArgument().bind("used"))))),
57 this);
58 Finder->addMatcher(loc(templateSpecializationType(forEachTemplateArgument(
59 templateArgument().bind("used")))),
60 this);
61 Finder->addMatcher(userDefinedLiteral().bind("used"), this);
62 Finder->addMatcher(
63 loc(elaboratedType(unless(hasQualifier(nestedNameSpecifier())),
64 hasUnqualifiedDesugaredType(type().bind("usedType")))),
65 this);
66 // Cases where we can identify the UsingShadowDecl directly, rather than
67 // just its target.
68 // FIXME: cover more cases in this way, as the AST supports it.
69 auto ThroughShadowMatcher = throughUsingDecl(namedDecl().bind("usedShadow"));
70 Finder->addMatcher(declRefExpr(ThroughShadowMatcher), this);
71 Finder->addMatcher(loc(usingType(ThroughShadowMatcher)), this);
72}
73
74void UnusedUsingDeclsCheck::check(const MatchFinder::MatchResult &Result) {
75 if (Result.Context->getDiagnostics().hasUncompilableErrorOccurred())
76 return;
77 // We don't emit warnings on unused-using-decls from headers, so bail out if
78 // the main file is a header.
79 if (auto MainFile = Result.SourceManager->getFileEntryRefForID(
80 Result.SourceManager->getMainFileID());
81 utils::isFileExtension(MainFile->getName(), HeaderFileExtensions))
82 return;
83
84 if (const auto *Using = Result.Nodes.getNodeAs<UsingDecl>("using")) {
85 // Ignores using-declarations defined in macros.
86 if (Using->getLocation().isMacroID())
87 return;
88
89 // Ignores using-declarations defined in class definition.
90 if (isa<CXXRecordDecl>(Using->getDeclContext()))
91 return;
92
93 // FIXME: We ignore using-decls defined in function definitions at the
94 // moment because of false positives caused by ADL and different function
95 // scopes.
96 if (isa<FunctionDecl>(Using->getDeclContext()))
97 return;
98
99 UsingDeclContext Context(Using);
100 Context.UsingDeclRange = CharSourceRange::getCharRange(
101 Using->getBeginLoc(),
102 Lexer::findLocationAfterToken(
103 Using->getEndLoc(), tok::semi, *Result.SourceManager, getLangOpts(),
104 /*SkipTrailingWhitespaceAndNewLine=*/true));
105 for (const auto *UsingShadow : Using->shadows()) {
106 const auto *TargetDecl = UsingShadow->getTargetDecl()->getCanonicalDecl();
107 if (shouldCheckDecl(TargetDecl)) {
108 Context.UsingTargetDecls.insert(TargetDecl);
109 UsingTargetDeclsCache.insert(TargetDecl);
110 }
111 }
112 if (!Context.UsingTargetDecls.empty())
113 Contexts.push_back(Context);
114 return;
115 }
116
117 // Mark a corresponding using declaration as used.
118 auto RemoveNamedDecl = [&](const NamedDecl *Used) {
119 removeFromFoundDecls(Used);
120 // Also remove variants of Used.
121 if (const auto *FD = dyn_cast<FunctionDecl>(Used)) {
122 removeFromFoundDecls(FD->getPrimaryTemplate());
123 return;
124 }
125 if (const auto *Specialization =
126 dyn_cast<ClassTemplateSpecializationDecl>(Used)) {
127 removeFromFoundDecls(Specialization->getSpecializedTemplate());
128 return;
129 }
130 if (const auto *ECD = dyn_cast<EnumConstantDecl>(Used)) {
131 if (const auto *ET = ECD->getType()->getAs<EnumType>())
132 removeFromFoundDecls(ET->getDecl());
133 }
134 };
135 // We rely on the fact that the clang AST is walked in order, usages are only
136 // marked after a corresponding using decl has been found.
137 if (const auto *Used = Result.Nodes.getNodeAs<NamedDecl>("used")) {
138 RemoveNamedDecl(Used);
139 return;
140 }
141
142 if (const auto *T = Result.Nodes.getNodeAs<Type>("usedType")) {
143 if (const auto *ND = T->getAsTagDecl())
144 RemoveNamedDecl(ND);
145 return;
146 }
147
148 if (const auto *UsedShadow =
149 Result.Nodes.getNodeAs<UsingShadowDecl>("usedShadow")) {
150 removeFromFoundDecls(UsedShadow->getTargetDecl());
151 return;
152 }
153
154 if (const auto *Used = Result.Nodes.getNodeAs<TemplateArgument>("used")) {
155 if (Used->getKind() == TemplateArgument::Template) {
156 if (const auto *TD = Used->getAsTemplate().getAsTemplateDecl())
157 removeFromFoundDecls(TD);
158 return;
159 }
160
161 if (Used->getKind() == TemplateArgument::Type) {
162 if (auto *RD = Used->getAsType()->getAsCXXRecordDecl())
163 removeFromFoundDecls(RD);
164 return;
165 }
166
167 if (Used->getKind() == TemplateArgument::Declaration) {
168 RemoveNamedDecl(Used->getAsDecl());
169 }
170 return;
171 }
172
173 if (const auto *DRE = Result.Nodes.getNodeAs<DeclRefExpr>("used")) {
174 RemoveNamedDecl(DRE->getDecl());
175 return;
176 }
177 // Check the uninstantiated template function usage.
178 if (const auto *ULE = Result.Nodes.getNodeAs<UnresolvedLookupExpr>("used")) {
179 for (const NamedDecl *ND : ULE->decls()) {
180 if (const auto *USD = dyn_cast<UsingShadowDecl>(ND))
181 removeFromFoundDecls(USD->getTargetDecl()->getCanonicalDecl());
182 }
183 return;
184 }
185 // Check user-defined literals
186 if (const auto *UDL = Result.Nodes.getNodeAs<UserDefinedLiteral>("used"))
187 removeFromFoundDecls(UDL->getCalleeDecl());
188}
189
190void UnusedUsingDeclsCheck::removeFromFoundDecls(const Decl *D) {
191 if (!D)
192 return;
193 const Decl *CanonicalDecl = D->getCanonicalDecl();
194 if (!UsingTargetDeclsCache.contains(CanonicalDecl))
195 return;
196 // FIXME: Currently, we don't handle the using-decls being used in different
197 // scopes (such as different namespaces, different functions). Instead of
198 // giving an incorrect message, we mark all of them as used.
199 for (auto &Context : Contexts) {
200 if (Context.IsUsed)
201 continue;
202 if (Context.UsingTargetDecls.contains(CanonicalDecl))
203 Context.IsUsed = true;
204 }
205}
206
208 for (const auto &Context : Contexts) {
209 if (!Context.IsUsed) {
210 diag(Context.FoundUsingDecl->getLocation(), "using decl %0 is unused")
211 << Context.FoundUsingDecl;
212 // Emit a fix and a fix description of the check;
213 diag(Context.FoundUsingDecl->getLocation(),
214 /*Description=*/"remove the using", DiagnosticIDs::Note)
215 << FixItHint::CreateRemoval(Context.UsingDeclRange);
216 }
217 }
218 Contexts.clear();
219 UsingTargetDeclsCache.clear();
220}
221
222} // namespace clang::tidy::misc
const FunctionDecl * Decl
llvm::SmallString< 256U > Name
CodeCompletionBuilder Builder
NodeType Type
std::string MainFile
::clang::DynTypedNode Node
Base class for all clang-tidy checks.
DiagnosticBuilder diag(SourceLocation Loc, StringRef Description, DiagnosticIDs::Level Level=DiagnosticIDs::Warning)
Add a diagnostic with the check's name.
const LangOptions & getLangOpts() const
Returns the language options from the context.
Every ClangTidyCheck reports errors through a DiagnosticsEngine provided by this context.
void registerMatchers(ast_matchers::MatchFinder *Finder) override
Override this to register AST matchers with Finder.
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
ClangTidyChecks that register ASTMatchers should do the actual work in here.
UnusedUsingDeclsCheck(StringRef Name, ClangTidyContext *Context)
AST_MATCHER_P(UserDefinedLiteral, hasLiteral, clang::ast_matchers::internal::Matcher< Expr >, InnerMatcher)
static bool shouldCheckDecl(const Decl *TargetDecl)
bool isFileExtension(StringRef FileName, const FileExtensionsSet &FileExtensions)
Decides whether a file has one of the specified file extensions.