clang-tools 18.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 std::optional<StringRef> HeaderFileExtensionsOption =
44 Options.get("HeaderFileExtensions");
45 RawStringHeaderFileExtensions =
46 HeaderFileExtensionsOption.value_or(utils::defaultHeaderFileExtensions());
47 if (HeaderFileExtensionsOption) {
48 if (!utils::parseFileExtensions(RawStringHeaderFileExtensions,
49 HeaderFileExtensions,
51 this->configurationDiag("Invalid header file extension: '%0'")
52 << RawStringHeaderFileExtensions;
53 }
54 } else
55 HeaderFileExtensions = Context->getHeaderFileExtensions();
56}
57
58void 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")))),
64 this);
65 Finder->addMatcher(callExpr(callee(unresolvedLookupExpr().bind("used"))),
66 this);
67 Finder->addMatcher(
68 callExpr(hasDeclaration(functionDecl(
69 forEachTemplateArgument(templateArgument().bind("used"))))),
70 this);
71 Finder->addMatcher(loc(templateSpecializationType(forEachTemplateArgument(
72 templateArgument().bind("used")))),
73 this);
74 Finder->addMatcher(userDefinedLiteral().bind("used"), this);
75 Finder->addMatcher(
76 loc(elaboratedType(unless(hasQualifier(nestedNameSpecifier())),
77 hasUnqualifiedDesugaredType(type().bind("usedType")))),
78 this);
79 // Cases where we can identify the UsingShadowDecl directly, rather than
80 // just its target.
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);
85}
86
87void UnusedUsingDeclsCheck::check(const MatchFinder::MatchResult &Result) {
88 if (Result.Context->getDiagnostics().hasUncompilableErrorOccurred())
89 return;
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))
95 return;
96
97 if (const auto *Using = Result.Nodes.getNodeAs<UsingDecl>("using")) {
98 // Ignores using-declarations defined in macros.
99 if (Using->getLocation().isMacroID())
100 return;
101
102 // Ignores using-declarations defined in class definition.
103 if (isa<CXXRecordDecl>(Using->getDeclContext()))
104 return;
105
106 // FIXME: We ignore using-decls defined in function definitions at the
107 // moment because of false positives caused by ADL and different function
108 // scopes.
109 if (isa<FunctionDecl>(Using->getDeclContext()))
110 return;
111
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);
122 }
123 if (!Context.UsingTargetDecls.empty())
124 Contexts.push_back(Context);
125 return;
126 }
127
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());
134 return;
135 }
136 if (const auto *Specialization =
137 dyn_cast<ClassTemplateSpecializationDecl>(Used)) {
138 removeFromFoundDecls(Specialization->getSpecializedTemplate());
139 return;
140 }
141 if (const auto *ECD = dyn_cast<EnumConstantDecl>(Used)) {
142 if (const auto *ET = ECD->getType()->getAs<EnumType>())
143 removeFromFoundDecls(ET->getDecl());
144 }
145 };
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);
150 return;
151 }
152
153 if (const auto *T = Result.Nodes.getNodeAs<Type>("usedType")) {
154 if (const auto *ND = T->getAsTagDecl())
155 RemoveNamedDecl(ND);
156 return;
157 }
158
159 if (const auto *UsedShadow =
160 Result.Nodes.getNodeAs<UsingShadowDecl>("usedShadow")) {
161 removeFromFoundDecls(UsedShadow->getTargetDecl());
162 return;
163 }
164
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);
169 return;
170 }
171
172 if (Used->getKind() == TemplateArgument::Type) {
173 if (auto *RD = Used->getAsType()->getAsCXXRecordDecl())
174 removeFromFoundDecls(RD);
175 return;
176 }
177
178 if (Used->getKind() == TemplateArgument::Declaration) {
179 RemoveNamedDecl(Used->getAsDecl());
180 }
181 return;
182 }
183
184 if (const auto *DRE = Result.Nodes.getNodeAs<DeclRefExpr>("used")) {
185 RemoveNamedDecl(DRE->getDecl());
186 return;
187 }
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());
193 }
194 return;
195 }
196 // Check user-defined literals
197 if (const auto *UDL = Result.Nodes.getNodeAs<UserDefinedLiteral>("used"))
198 removeFromFoundDecls(UDL->getCalleeDecl());
199}
200
201void UnusedUsingDeclsCheck::removeFromFoundDecls(const Decl *D) {
202 if (!D)
203 return;
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.
207 //
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;
212 }
213}
214
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);
224 }
225 }
226 Contexts.clear();
227}
228
229} // namespace clang::tidy::misc
const FunctionDecl * Decl
CodeCompletionBuilder Builder
NodeType Type
llvm::StringRef Name
std::string MainFile
::clang::DynTypedNode Node
std::optional< StringRef > get(StringRef LocalName) const
Read a named option from the Context.
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.
DiagnosticBuilder configurationDiag(StringRef Description, DiagnosticIDs::Level Level=DiagnosticIDs::Warning) const
Adds a diagnostic to report errors in the check's configuration.
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)
StringRef defaultHeaderFileExtensions()
Returns recommended default value for the list of header file extensions.
StringRef defaultFileExtensionDelimiters()
Returns recommended default value for the list of file extension delimiters.
bool isFileExtension(StringRef FileName, const FileExtensionsSet &FileExtensions)
Decides whether a file has one of the specified file extensions.
bool parseFileExtensions(StringRef AllFileExtensions, FileExtensionsSet &FileExtensions, StringRef Delimiters)
Parses header file extensions from a semicolon-separated list.