[clang][modules] Don't prevent translation of FW_Private includes when explicitly...
[llvm-project.git] / clang-tools-extra / clang-tidy / misc / UnusedUsingDeclsCheck.cpp
blob051375263e53c3fb64782529c211147672bccdde
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 //===----------------------------------------------------------------------===//
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 {
19 namespace {
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);
25 return false;
28 } // namespace
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,
49 HeaderFileExtensions,
50 utils::defaultFileExtensionDelimiters())) {
51 this->configurationDiag("Invalid header file extension: '%0'")
52 << RawStringHeaderFileExtensions;
54 } else
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")))),
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);
87 void 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;
97 if (const auto *Using = Result.Nodes.getNodeAs<UsingDecl>("using")) {
98 // Ignores using-declarations defined in macros.
99 if (Using->getLocation().isMacroID())
100 return;
102 // Ignores using-declarations defined in class definition.
103 if (isa<CXXRecordDecl>(Using->getDeclContext()))
104 return;
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;
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);
125 return;
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;
136 if (const auto *Specialization =
137 dyn_cast<ClassTemplateSpecializationDecl>(Used)) {
138 removeFromFoundDecls(Specialization->getSpecializedTemplate());
139 return;
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);
150 return;
153 if (const auto *T = Result.Nodes.getNodeAs<Type>("usedType")) {
154 if (const auto *ND = T->getAsTagDecl())
155 RemoveNamedDecl(ND);
156 return;
159 if (const auto *UsedShadow =
160 Result.Nodes.getNodeAs<UsingShadowDecl>("usedShadow")) {
161 removeFromFoundDecls(UsedShadow->getTargetDecl());
162 return;
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;
172 if (Used->getKind() == TemplateArgument::Type) {
173 if (auto *RD = Used->getAsType()->getAsCXXRecordDecl())
174 removeFromFoundDecls(RD);
175 return;
178 if (Used->getKind() == TemplateArgument::Declaration) {
179 RemoveNamedDecl(Used->getAsDecl());
181 return;
184 if (const auto *DRE = Result.Nodes.getNodeAs<DeclRefExpr>("used")) {
185 RemoveNamedDecl(DRE->getDecl());
186 return;
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());
194 return;
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) {
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.
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);
226 Contexts.clear();
229 } // namespace clang::tidy::misc