clang-tools 23.0.0git
TypeTraitsCheck.cpp
Go to the documentation of this file.
1//===----------------------------------------------------------------------===//
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
9#include "TypeTraitsCheck.h"
10#include "clang/ASTMatchers/ASTMatchFinder.h"
11#include "clang/ASTMatchers/ASTMatchers.h"
12#include "clang/Lex/Lexer.h"
13
14using namespace clang::ast_matchers;
15
16namespace clang::tidy::modernize {
17
18// FIXME: Add chrono::treat_as_floating_point_v and chrono::is_clock_v.
19// This will require restructuring the code to handle type traits not
20// defined directly in std.
21static const llvm::StringSet<> ValueTraits = {
22 "alignment_of",
23 "conjunction",
24 "disjunction",
25 "extent",
26 "has_unique_object_representations",
27 "has_virtual_destructor",
28 "is_abstract",
29 "is_aggregate",
30 "is_arithmetic",
31 "is_array",
32 "is_assignable",
33 "is_base_of",
34 "is_bind_expression",
35 "is_bounded_array",
36 "is_class",
37 "is_compound",
38 "is_const",
39 "is_constructible",
40 "is_convertible",
41 "is_copy_assignable",
42 "is_copy_constructible",
43 "is_default_constructible",
44 "is_destructible",
45 "is_empty",
46 "is_enum",
47 "is_error_code_enum",
48 "is_error_condition_enum",
49 "is_execution_policy",
50 "is_final",
51 "is_floating_point",
52 "is_function",
53 "is_fundamental",
54 "is_implicit_lifetime",
55 "is_integral",
56 "is_invocable",
57 "is_invocable_r",
58 "is_layout_compatible",
59 "is_lvalue_reference",
60 "is_member_function_pointer",
61 "is_member_object_pointer",
62 "is_member_pointer",
63 "is_move_assignable",
64 "is_move_constructible",
65 "is_nothrow_assignable",
66 "is_nothrow_constructible",
67 "is_nothrow_convertible",
68 "is_nothrow_copy_assignable",
69 "is_nothrow_copy_constructible",
70 "is_nothrow_default_constructible",
71 "is_nothrow_destructible",
72 "is_nothrow_invocable",
73 "is_nothrow_invocable_r",
74 "is_nothrow_move_assignable",
75 "is_nothrow_move_constructible",
76 "is_nothrow_relocatable",
77 "is_nothrow_swappable",
78 "is_nothrow_swappable_with",
79 "is_null_pointer",
80 "is_object",
81 "is_placeholder",
82 "is_pointer",
83 "is_pointer_interconvertible_base_of",
84 "is_polymorphic",
85 "is_reference",
86 "is_rvalue_reference",
87 "is_same",
88 "is_scalar",
89 "is_scoped_enum",
90 "is_signed",
91 "is_standard_layout",
92 "is_swappable",
93 "is_swappable_with",
94 "is_trivial",
95 "is_trivially_assignable",
96 "is_trivially_constructible",
97 "is_trivially_copy_assignable",
98 "is_trivially_copy_constructible",
99 "is_trivially_copyable",
100 "is_trivially_default_constructible",
101 "is_trivially_destructible",
102 "is_trivially_move_assignable",
103 "is_trivially_move_constructible",
104 "is_trivially_relocatable",
105 "is_unbounded_array",
106 "is_union",
107 "is_unsigned",
108 "is_virtual_base_of",
109 "is_void",
110 "is_volatile",
111 "negation",
112 "rank",
113 "ratio_equal",
114 "ratio_greater_equal",
115 "ratio_greater",
116 "ratio_less_equal",
117 "ratio_less",
118 "ratio_not_equal",
119 "reference_constructs_from_temporary",
120 "reference_converts_from_temporary",
121 "tuple_size",
122 "uses_allocator",
123 "variant_size",
124};
125
126static const llvm::StringSet<> TypeTraits = {
127 "remove_cv",
128 "remove_const",
129 "remove_volatile",
130 "add_cv",
131 "add_const",
132 "add_volatile",
133 "remove_reference",
134 "add_lvalue_reference",
135 "add_rvalue_reference",
136 "remove_pointer",
137 "add_pointer",
138 "make_signed",
139 "make_unsigned",
140 "remove_extent",
141 "remove_all_extents",
142 "aligned_storage",
143 "aligned_union",
144 "decay",
145 "remove_cvref",
146 "enable_if",
147 "conditional",
148 "common_type",
149 "common_reference",
150 "underlying_type",
151 "result_of",
152 "invoke_result",
153 "type_identity",
154 "compare_three_way_result",
155 "common_comparison_category",
156 "unwrap_ref_decay",
157 "unwrap_reference",
158 "tuple_element",
159 "variant_alternative",
160};
161
162static DeclarationName getName(const DependentScopeDeclRefExpr &D) {
163 return D.getDeclName();
164}
165
166static DeclarationName getName(const DeclRefExpr &D) {
167 return D.getDecl()->getDeclName();
168}
169
170namespace {
171AST_POLYMORPHIC_MATCHER(isValue, AST_POLYMORPHIC_SUPPORTED_TYPES(
172 DeclRefExpr, DependentScopeDeclRefExpr)) {
173 const IdentifierInfo *Ident = getName(Node).getAsIdentifierInfo();
174 return Ident && Ident->isStr("value");
175}
176
177AST_MATCHER(TypeLoc, isType) {
178 if (auto TL = Node.getAs<TypedefTypeLoc>()) {
179 const auto *TD = TL.getDecl();
180 return TD->getDeclName().isIdentifier() && TD->getName() == "type";
181 }
182 if (auto TL = Node.getAs<DependentNameTypeLoc>())
183 return TL.getTypePtr()->getIdentifier()->getName() == "type";
184 return false;
185}
186} // namespace
187
188static constexpr char Bind[] = "";
189
190void TypeTraitsCheck::registerMatchers(MatchFinder *Finder) {
191 // Only register matchers for trait<...>::value in c++17 mode.
192 if (getLangOpts().CPlusPlus17) {
193 Finder->addMatcher(mapAnyOf(declRefExpr, dependentScopeDeclRefExpr)
194 .with(isValue())
195 .bind(Bind),
196 this);
197 }
198 Finder->addMatcher(typeLoc(isType()).bind(Bind), this);
199
200 // Only register matchers for std::remove_cvref_t simplification in c++20
201 // mode.
202 if (getLangOpts().CPlusPlus20) {
203 Finder->addMatcher(templateSpecializationTypeLoc(
204 loc(qualType(hasDeclaration(
205 namedDecl(hasName("::std::remove_cv_t"))))),
206 hasTemplateArgumentLoc(
207 0, hasTypeLoc(templateSpecializationTypeLoc(loc(
208 qualType(hasDeclaration(namedDecl(hasName(
209 "::std::remove_reference_t")))))))))
210 .bind("remove_cvref"),
211 this);
212 }
213}
214
215static bool isNamedDeclInStdTraitsSet(const NamedDecl *ND,
216 const llvm::StringSet<> &Set) {
217 return ND->isInStdNamespace() && ND->getDeclName().isIdentifier() &&
218 Set.contains(ND->getName());
219}
220
221static bool checkTemplatedDecl(NestedNameSpecifier NNS,
222 const llvm::StringSet<> &Set) {
223 if (NNS.getKind() != NestedNameSpecifier::Kind::Type)
224 return false;
225 const auto *TST = NNS.getAsType()->getAs<TemplateSpecializationType>();
226 if (!TST)
227 return false;
228 if (const TemplateDecl *TD = TST->getTemplateName().getAsTemplateDecl())
229 return isNamedDeclInStdTraitsSet(TD, Set);
230 return false;
231}
232
234 : ClangTidyCheck(Name, Context),
235 IgnoreMacros(Options.get("IgnoreMacros", false)) {}
236
237void TypeTraitsCheck::check(const MatchFinder::MatchResult &Result) {
238 auto EmitValueWarning = [this, &Result](const NestedNameSpecifierLoc &QualLoc,
239 SourceLocation EndLoc) {
240 SourceLocation TemplateNameEndLoc;
241 if (auto TSTL =
242 QualLoc.getAsTypeLoc().getAs<TemplateSpecializationTypeLoc>())
243 TemplateNameEndLoc = Lexer::getLocForEndOfToken(
244 TSTL.getTemplateNameLoc(), 0, *Result.SourceManager,
245 Result.Context->getLangOpts());
246 else
247 return;
248
249 if (EndLoc.isMacroID() || QualLoc.getEndLoc().isMacroID() ||
250 TemplateNameEndLoc.isMacroID()) {
251 if (IgnoreMacros)
252 return;
253 diag(QualLoc.getBeginLoc(), "use c++17 style variable templates");
254 return;
255 }
256 diag(QualLoc.getBeginLoc(), "use c++17 style variable templates")
257 << FixItHint::CreateInsertion(TemplateNameEndLoc, "_v")
258 << FixItHint::CreateRemoval({QualLoc.getEndLoc(), EndLoc});
259 };
260
261 auto EmitTypeWarning = [this, &Result](const NestedNameSpecifierLoc &QualLoc,
262 SourceLocation EndLoc,
263 SourceLocation TypenameLoc) {
264 SourceLocation TemplateNameEndLoc;
265 if (auto TSTL =
266 QualLoc.getAsTypeLoc().getAs<TemplateSpecializationTypeLoc>())
267 TemplateNameEndLoc = Lexer::getLocForEndOfToken(
268 TSTL.getTemplateNameLoc(), 0, *Result.SourceManager,
269 Result.Context->getLangOpts());
270 else
271 return;
272
273 if (EndLoc.isMacroID() || QualLoc.getEndLoc().isMacroID() ||
274 TemplateNameEndLoc.isMacroID() || TypenameLoc.isMacroID()) {
275 if (IgnoreMacros)
276 return;
277 diag(QualLoc.getBeginLoc(), "use c++14 style type templates");
278 return;
279 }
280 auto Diag = diag(QualLoc.getBeginLoc(), "use c++14 style type templates");
281
282 if (TypenameLoc.isValid())
283 Diag << FixItHint::CreateRemoval(TypenameLoc);
284 Diag << FixItHint::CreateInsertion(TemplateNameEndLoc, "_t")
285 << FixItHint::CreateRemoval({QualLoc.getEndLoc(), EndLoc});
286 };
287
288 if (const auto *DRE = Result.Nodes.getNodeAs<DeclRefExpr>(Bind)) {
289 if (!DRE->hasQualifier())
290 return;
291 if (const auto *CTSD = dyn_cast_if_present<ClassTemplateSpecializationDecl>(
292 DRE->getQualifier().getAsRecordDecl())) {
294 EmitValueWarning(DRE->getQualifierLoc(), DRE->getEndLoc());
295 }
296 return;
297 }
298
299 if (const auto *TL = Result.Nodes.getNodeAs<TypedefTypeLoc>(Bind)) {
300 const NestedNameSpecifierLoc QualLoc = TL->getQualifierLoc();
301 const NestedNameSpecifier NNS = QualLoc.getNestedNameSpecifier();
302 if (const auto *CTSD = dyn_cast_if_present<ClassTemplateSpecializationDecl>(
303 NNS.getAsRecordDecl())) {
305 EmitTypeWarning(TL->getQualifierLoc(), TL->getEndLoc(),
306 TL->getElaboratedKeywordLoc());
307 }
308 return;
309 }
310
311 if (const auto *DSDRE =
312 Result.Nodes.getNodeAs<DependentScopeDeclRefExpr>(Bind)) {
313 if (checkTemplatedDecl(DSDRE->getQualifier(), ValueTraits))
314 EmitValueWarning(DSDRE->getQualifierLoc(), DSDRE->getEndLoc());
315 return;
316 }
317
318 if (const auto *DNTL = Result.Nodes.getNodeAs<DependentNameTypeLoc>(Bind)) {
319 const NestedNameSpecifierLoc QualLoc = DNTL->getQualifierLoc();
320 if (checkTemplatedDecl(QualLoc.getNestedNameSpecifier(), TypeTraits))
321 EmitTypeWarning(QualLoc, DNTL->getEndLoc(),
322 DNTL->getElaboratedKeywordLoc());
323 return;
324 }
325
326 if (const auto *TSTL = Result.Nodes.getNodeAs<TemplateSpecializationTypeLoc>(
327 "remove_cvref")) {
328 auto InnerTL = TSTL->getArgLoc(0)
329 .getTypeSourceInfo()
330 ->getTypeLoc()
331 .castAs<TemplateSpecializationTypeLoc>();
332 if (IgnoreMacros &&
333 (TSTL->getBeginLoc().isMacroID() || InnerTL.getBeginLoc().isMacroID()))
334 return;
335
336 auto Diag = diag(TSTL->getBeginLoc(), "use c++20 type alias");
337 Diag << FixItHint::CreateReplacement(
338 SourceRange(TSTL->getBeginLoc(), InnerTL.getLAngleLoc()),
339 "std::remove_cvref_t<")
340 << FixItHint::CreateRemoval(InnerTL.getRAngleLoc());
341 return;
342 }
343}
344
346 Options.store(Opts, "IgnoreMacros", IgnoreMacros);
347}
348} // namespace clang::tidy::modernize
Every ClangTidyCheck reports errors through a DiagnosticsEngine provided by this context.
void registerMatchers(ast_matchers::MatchFinder *Finder) override
void storeOptions(ClangTidyOptions::OptionMap &Opts) override
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
TypeTraitsCheck(StringRef Name, ClangTidyContext *Context)
AST_POLYMORPHIC_MATCHER(isInAbseilFile, AST_POLYMORPHIC_SUPPORTED_TYPES(Decl, Stmt, TypeLoc, NestedNameSpecifierLoc))
Matches AST nodes that were found within Abseil files.
static constexpr char Bind[]
static bool checkTemplatedDecl(NestedNameSpecifier NNS, const llvm::StringSet<> &Set)
static DeclarationName getName(const DependentScopeDeclRefExpr &D)
static const llvm::StringSet ValueTraits
static const llvm::StringSet TypeTraits
static bool isNamedDeclInStdTraitsSet(const NamedDecl *ND, const llvm::StringSet<> &Set)
llvm::StringMap< ClangTidyValue > OptionMap