1//===--- UseConstraintsCheck.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
9#include "UseConstraintsCheck.h"
10#include "clang/AST/ASTContext.h"
11#include "clang/ASTMatchers/ASTMatchFinder.h"
12#include "clang/Lex/Lexer.h"
13
14#include "../utils/LexerUtils.h"
15
16#include <optional>
17#include <utility>
18
19using namespace clang::ast_matchers;
20
21namespace clang::tidy::modernize {
22
23struct EnableIfData {
24 TemplateSpecializationTypeLoc Loc;
25 TypeLoc Outer;
26};
27
28namespace {
29AST_MATCHER(FunctionDecl, hasOtherDeclarations) {
30 auto It = Node.redecls_begin();
31 auto EndIt = Node.redecls_end();
32
33 if (It == EndIt)
34 return false;
35
36 ++It;
37 return It != EndIt;
38}
39} // namespace
40
41void UseConstraintsCheck::registerMatchers(MatchFinder *Finder) {
42 Finder->addMatcher(
43 NodeMatch: functionTemplateDecl(
44 // Skip external libraries included as system headers
45 unless(isExpansionInSystemHeader()),
46 has(functionDecl(unless(hasOtherDeclarations()), isDefinition(),
47 hasReturnTypeLoc(ReturnMatcher: typeLoc().bind(ID: "return")))
48 .bind(ID: "function")))
49 .bind(ID: "functionTemplate"),
50 Action: this);
51}
52
53static std::optional<TemplateSpecializationTypeLoc>
54matchEnableIfSpecializationImplTypename(TypeLoc TheType) {
55 if (const auto Dep = TheType.getAs<DependentNameTypeLoc>()) {
56 const IdentifierInfo *Identifier = Dep.getTypePtr()->getIdentifier();
57 ElaboratedTypeKeyword Keyword = Dep.getTypePtr()->getKeyword();
58 if (!Identifier || Identifier->getName() != "type" ||
59 (Keyword != ElaboratedTypeKeyword::Typename &&
60 Keyword != ElaboratedTypeKeyword::None)) {
61 return std::nullopt;
62 }
63 TheType = Dep.getQualifierLoc().getTypeLoc();
64 if (TheType.isNull())
65 return std::nullopt;
66 }
67
68 if (const auto SpecializationLoc =
69 TheType.getAs<TemplateSpecializationTypeLoc>()) {
70
71 const auto *Specialization =
72 dyn_cast<TemplateSpecializationType>(SpecializationLoc.getTypePtr());
73 if (!Specialization)
74 return std::nullopt;
75
76 const TemplateDecl *TD =
77 Specialization->getTemplateName().getAsTemplateDecl();
78 if (!TD || TD->getName() != "enable_if")
79 return std::nullopt;
80
81 int NumArgs = SpecializationLoc.getNumArgs();
82 if (NumArgs != 1 && NumArgs != 2)
83 return std::nullopt;
84
85 return SpecializationLoc;
86 }
87 return std::nullopt;
88}
89
90static std::optional<TemplateSpecializationTypeLoc>
91matchEnableIfSpecializationImplTrait(TypeLoc TheType) {
92 if (const auto Elaborated = TheType.getAs<ElaboratedTypeLoc>())
93 TheType = Elaborated.getNamedTypeLoc();
94
95 if (const auto SpecializationLoc =
96 TheType.getAs<TemplateSpecializationTypeLoc>()) {
97
98 const auto *Specialization =
99 dyn_cast<TemplateSpecializationType>(SpecializationLoc.getTypePtr());
100 if (!Specialization)
101 return std::nullopt;
102
103 const TemplateDecl *TD =
104 Specialization->getTemplateName().getAsTemplateDecl();
105 if (!TD || TD->getName() != "enable_if_t")
106 return std::nullopt;
107
108 if (!Specialization->isTypeAlias())
109 return std::nullopt;
110
111 if (const auto *AliasedType =
112 dyn_cast<DependentNameType>(Specialization->getAliasedType())) {
113 ElaboratedTypeKeyword Keyword = AliasedType->getKeyword();
114 if (AliasedType->getIdentifier()->getName() != "type" ||
115 (Keyword != ElaboratedTypeKeyword::Typename &&
116 Keyword != ElaboratedTypeKeyword::None)) {
117 return std::nullopt;
118 }
119 } else {
120 return std::nullopt;
121 }
122 int NumArgs = SpecializationLoc.getNumArgs();
123 if (NumArgs != 1 && NumArgs != 2)
124 return std::nullopt;
125
126 return SpecializationLoc;
127 }
128 return std::nullopt;
129}
130
131static std::optional<TemplateSpecializationTypeLoc>
132matchEnableIfSpecializationImpl(TypeLoc TheType) {
133 if (auto EnableIf = matchEnableIfSpecializationImplTypename(TheType))
134 return EnableIf;
135 return matchEnableIfSpecializationImplTrait(TheType);
136}
137
138static std::optional<EnableIfData>
139matchEnableIfSpecialization(TypeLoc TheType) {
140 if (const auto Pointer = TheType.getAs<PointerTypeLoc>())
141 TheType = Pointer.getPointeeLoc();
142 else if (const auto Reference = TheType.getAs<ReferenceTypeLoc>())
143 TheType = Reference.getPointeeLoc();
144 if (const auto Qualified = TheType.getAs<QualifiedTypeLoc>())
145 TheType = Qualified.getUnqualifiedLoc();
146
147 if (auto EnableIf = matchEnableIfSpecializationImpl(TheType))
148 return EnableIfData{std::move(*EnableIf), TheType};
149 return std::nullopt;
150}
151
152static std::pair<std::optional<EnableIfData>, const Decl *>
153matchTrailingTemplateParam(const FunctionTemplateDecl *FunctionTemplate) {
154 // For non-type trailing param, match very specifically
155 // 'template <..., enable_if_type<Condition, Type> = Default>' where
156 // enable_if_type is 'enable_if' or 'enable_if_t'. E.g., 'template <typename
157 // T, enable_if_t<is_same_v<T, bool>, int*> = nullptr>
158 //
159 // Otherwise, match a trailing default type arg.
160 // E.g., 'template <typename T, typename = enable_if_t<is_same_v<T, bool>>>'
161
162 const TemplateParameterList *TemplateParams =
163 FunctionTemplate->getTemplateParameters();
164 if (TemplateParams->size() == 0)
165 return {};
166
167 const NamedDecl *LastParam =
168 TemplateParams->getParam(Idx: TemplateParams->size() - 1);
169 if (const auto *LastTemplateParam =
170 dyn_cast<NonTypeTemplateParmDecl>(LastParam)) {
171
172 if (!LastTemplateParam->hasDefaultArgument() ||
173 !LastTemplateParam->getName().empty())
174 return {};
175
176 return {matchEnableIfSpecialization(
177 LastTemplateParam->getTypeSourceInfo()->getTypeLoc()),
178 LastTemplateParam};
179 }
180 if (const auto *LastTemplateParam =
181 dyn_cast<TemplateTypeParmDecl>(LastParam)) {
182 if (LastTemplateParam->hasDefaultArgument() &&
183 LastTemplateParam->getIdentifier() == nullptr) {
184 return {
185 matchEnableIfSpecialization(LastTemplateParam->getDefaultArgument()
186 .getTypeSourceInfo()
187 ->getTypeLoc()),
188 LastTemplateParam};
189 }
190 }
191 return {};
192}
193
194template <typename T>
195static SourceLocation getRAngleFileLoc(const SourceManager &SM,
196 const T &Element) {
197 // getFileLoc handles the case where the RAngle loc is part of a synthesized
198 // '>>', which ends up allocating a 'scratch space' buffer in the source
199 // manager.
200 return SM.getFileLoc(Loc: Element.getRAngleLoc());
201}
202
203static SourceRange
204getConditionRange(ASTContext &Context,
205 const TemplateSpecializationTypeLoc &EnableIf) {
206 // TemplateArgumentLoc's SourceRange End is the location of the last token
207 // (per UnqualifiedId docs). E.g., in `enable_if<AAA && BBB>`, the End
208 // location will be the first 'B' in 'BBB'.
209 const LangOptions &LangOpts = Context.getLangOpts();
210 const SourceManager &SM = Context.getSourceManager();
211 if (EnableIf.getNumArgs() > 1) {
212 TemplateArgumentLoc NextArg = EnableIf.getArgLoc(i: 1);
213 return {EnableIf.getLAngleLoc().getLocWithOffset(Offset: 1),
214 utils::lexer::findPreviousTokenKind(
215 Start: NextArg.getSourceRange().getBegin(), SM, LangOpts, TK: tok::comma)};
216 }
217
218 return {EnableIf.getLAngleLoc().getLocWithOffset(Offset: 1),
219 getRAngleFileLoc(SM, Element: EnableIf)};
220}
221
222static SourceRange getTypeRange(ASTContext &Context,
223 const TemplateSpecializationTypeLoc &EnableIf) {
224 TemplateArgumentLoc Arg = EnableIf.getArgLoc(i: 1);
225 const LangOptions &LangOpts = Context.getLangOpts();
226 const SourceManager &SM = Context.getSourceManager();
227 return {utils::lexer::findPreviousTokenKind(Start: Arg.getSourceRange().getBegin(),
228 SM, LangOpts, TK: tok::comma)
229 .getLocWithOffset(Offset: 1),
230 getRAngleFileLoc(SM, Element: EnableIf)};
231}
232
233// Returns the original source text of the second argument of a call to
234// enable_if_t. E.g., in enable_if_t<Condition, TheType>, this function
235// returns 'TheType'.
236static std::optional<StringRef>
237getTypeText(ASTContext &Context,
238 const TemplateSpecializationTypeLoc &EnableIf) {
239 if (EnableIf.getNumArgs() > 1) {
240 const LangOptions &LangOpts = Context.getLangOpts();
241 const SourceManager &SM = Context.getSourceManager();
242 bool Invalid = false;
243 StringRef Text = Lexer::getSourceText(Range: CharSourceRange::getCharRange(
244 R: getTypeRange(Context, EnableIf)),
245 SM, LangOpts, Invalid: &Invalid)
246 .trim();
247 if (Invalid)
248 return std::nullopt;
249
250 return Text;
251 }
252
253 return "void";
254}
255
256static std::optional<SourceLocation>
257findInsertionForConstraint(const FunctionDecl *Function, ASTContext &Context) {
258 SourceManager &SM = Context.getSourceManager();
259 const LangOptions &LangOpts = Context.getLangOpts();
260
261 if (const auto *Constructor = dyn_cast<CXXConstructorDecl>(Val: Function)) {
262 for (const CXXCtorInitializer *Init : Constructor->inits()) {
263 if (Init->getSourceOrder() == 0)
264 return utils::lexer::findPreviousTokenKind(Start: Init->getSourceLocation(),
265 SM, LangOpts, TK: tok::colon);
266 }
267 if (!Constructor->inits().empty())
268 return std::nullopt;
269 }
270 if (Function->isDeleted()) {
271 SourceLocation FunctionEnd = Function->getSourceRange().getEnd();
272 return utils::lexer::findNextAnyTokenKind(Start: FunctionEnd, SM, LangOpts,
273 TK: tok::equal, TKs: tok::equal);
274 }
275 const Stmt *Body = Function->getBody();
276 if (!Body)
277 return std::nullopt;
278
279 return Body->getBeginLoc();
280}
281
282static bool isPrimaryExpression(const Expr *Expression) {
283 // This function is an incomplete approximation of checking whether
284 // an Expr is a primary expression. In particular, if this function
285 // returns true, the expression is a primary expression. The converse
286 // is not necessarily true.
287
288 if (const auto *Cast = dyn_cast<ImplicitCastExpr>(Val: Expression))
289 Expression = Cast->getSubExprAsWritten();
290 if (isa<ParenExpr, DependentScopeDeclRefExpr>(Val: Expression))
291 return true;
292
293 return false;
294}
295
296// Return the original source text of an enable_if_t condition, i.e., the
297// first template argument). For example, in
298// 'enable_if_t<FirstCondition || SecondCondition, AType>', the text
299// the text 'FirstCondition || SecondCondition' is returned.
300static std::optional<std::string> getConditionText(const Expr *ConditionExpr,
301 SourceRange ConditionRange,
302 ASTContext &Context) {
303 SourceManager &SM = Context.getSourceManager();
304 const LangOptions &LangOpts = Context.getLangOpts();
305
306 SourceLocation PrevTokenLoc = ConditionRange.getEnd();
307 if (PrevTokenLoc.isInvalid())
308 return std::nullopt;
309
310 const bool SkipComments = false;
311 Token PrevToken;
312 std::tie(args&: PrevToken, args&: PrevTokenLoc) = utils::lexer::getPreviousTokenAndStart(
313 Location: PrevTokenLoc, SM, LangOpts, SkipComments);
314 bool EndsWithDoubleSlash =
315 PrevToken.is(K: tok::comment) &&
316 Lexer::getSourceText(Range: CharSourceRange::getCharRange(
317 B: PrevTokenLoc, E: PrevTokenLoc.getLocWithOffset(Offset: 2)),
318 SM, LangOpts) == "//";
319
320 bool Invalid = false;
321 llvm::StringRef ConditionText = Lexer::getSourceText(
322 Range: CharSourceRange::getCharRange(R: ConditionRange), SM, LangOpts, Invalid: &Invalid);
323 if (Invalid)
324 return std::nullopt;
325
326 auto AddParens = [&](llvm::StringRef Text) -> std::string {
327 if (isPrimaryExpression(Expression: ConditionExpr))
328 return Text.str();
329 return "(" + Text.str() + ")";
330 };
331
332 if (EndsWithDoubleSlash)
333 return AddParens(ConditionText);
334 return AddParens(ConditionText.trim());
335}
336
337// Handle functions that return enable_if_t, e.g.,
338// template <...>
339// enable_if_t<Condition, ReturnType> function();
340//
341// Return a vector of FixItHints if the code can be replaced with
342// a C++20 requires clause. In the example above, returns FixItHints
343// to result in
344// template <...>
345// ReturnType function() requires Condition {}
346static std::vector<FixItHint> handleReturnType(const FunctionDecl *Function,
347 const TypeLoc &ReturnType,
348 const EnableIfData &EnableIf,
349 ASTContext &Context) {
350 TemplateArgumentLoc EnableCondition = EnableIf.Loc.getArgLoc(0);
351
352 SourceRange ConditionRange = getConditionRange(Context, EnableIf.Loc);
353
354 std::optional<std::string> ConditionText = getConditionText(
355 ConditionExpr: EnableCondition.getSourceExpression(), ConditionRange, Context);
356 if (!ConditionText)
357 return {};
358
359 std::optional<StringRef> TypeText = getTypeText(Context, EnableIf.Loc);
360 if (!TypeText)
361 return {};
362
363 SmallVector<AssociatedConstraint, 3> ExistingConstraints;
364 Function->getAssociatedConstraints(ACs&: ExistingConstraints);
365 if (!ExistingConstraints.empty()) {
366 // FIXME - Support adding new constraints to existing ones. Do we need to
367 // consider subsumption?
368 return {};
369 }
370
371 std::optional<SourceLocation> ConstraintInsertionLoc =
372 findInsertionForConstraint(Function, Context);
373 if (!ConstraintInsertionLoc)
374 return {};
375
376 std::vector<FixItHint> FixIts;
377 FixIts.push_back(x: FixItHint::CreateReplacement(
378 RemoveRange: CharSourceRange::getTokenRange(R: EnableIf.Outer.getSourceRange()),
379 Code: *TypeText));
380 FixIts.push_back(x: FixItHint::CreateInsertion(
381 InsertionLoc: *ConstraintInsertionLoc, Code: "requires " + *ConditionText + " "));
382 return FixIts;
383}
384
385// Handle enable_if_t in a trailing template parameter, e.g.,
386// template <..., enable_if_t<Condition, Type> = Type{}>
387// ReturnType function();
388//
389// Return a vector of FixItHints if the code can be replaced with
390// a C++20 requires clause. In the example above, returns FixItHints
391// to result in
392// template <...>
393// ReturnType function() requires Condition {}
394static std::vector<FixItHint>
395handleTrailingTemplateType(const FunctionTemplateDecl *FunctionTemplate,
396 const FunctionDecl *Function,
397 const Decl *LastTemplateParam,
398 const EnableIfData &EnableIf, ASTContext &Context) {
399 SourceManager &SM = Context.getSourceManager();
400 const LangOptions &LangOpts = Context.getLangOpts();
401
402 TemplateArgumentLoc EnableCondition = EnableIf.Loc.getArgLoc(0);
403
404 SourceRange ConditionRange = getConditionRange(Context, EnableIf.Loc);
405
406 std::optional<std::string> ConditionText = getConditionText(
407 ConditionExpr: EnableCondition.getSourceExpression(), ConditionRange, Context);
408 if (!ConditionText)
409 return {};
410
411 SmallVector<AssociatedConstraint, 3> ExistingConstraints;
412 Function->getAssociatedConstraints(ACs&: ExistingConstraints);
413 if (!ExistingConstraints.empty()) {
414 // FIXME - Support adding new constraints to existing ones. Do we need to
415 // consider subsumption?
416 return {};
417 }
418
419 SourceRange RemovalRange;
420 const TemplateParameterList *TemplateParams =
421 FunctionTemplate->getTemplateParameters();
422 if (!TemplateParams || TemplateParams->size() == 0)
423 return {};
424
425 if (TemplateParams->size() == 1) {
426 RemovalRange =
427 SourceRange(TemplateParams->getTemplateLoc(),
428 getRAngleFileLoc(SM, Element: *TemplateParams).getLocWithOffset(Offset: 1));
429 } else {
430 RemovalRange =
431 SourceRange(utils::lexer::findPreviousTokenKind(
432 Start: LastTemplateParam->getSourceRange().getBegin(), SM,
433 LangOpts, TK: tok::comma),
434 getRAngleFileLoc(SM, Element: *TemplateParams));
435 }
436
437 std::optional<SourceLocation> ConstraintInsertionLoc =
438 findInsertionForConstraint(Function, Context);
439 if (!ConstraintInsertionLoc)
440 return {};
441
442 std::vector<FixItHint> FixIts;
443 FixIts.push_back(
444 x: FixItHint::CreateRemoval(RemoveRange: CharSourceRange::getCharRange(R: RemovalRange)));
445 FixIts.push_back(x: FixItHint::CreateInsertion(
446 InsertionLoc: *ConstraintInsertionLoc, Code: "requires " + *ConditionText + " "));
447 return FixIts;
448}
449
450void UseConstraintsCheck::check(const MatchFinder::MatchResult &Result) {
451 const auto *FunctionTemplate =
452 Result.Nodes.getNodeAs<FunctionTemplateDecl>(ID: "functionTemplate");
453 const auto *Function = Result.Nodes.getNodeAs<FunctionDecl>(ID: "function");
454 const auto *ReturnType = Result.Nodes.getNodeAs<TypeLoc>(ID: "return");
455 if (!FunctionTemplate || !Function || !ReturnType)
456 return;
457
458 // Check for
459 //
460 // Case 1. Return type of function
461 //
462 // template <...>
463 // enable_if_t<Condition, ReturnType>::type function() {}
464 //
465 // Case 2. Trailing template parameter
466 //
467 // template <..., enable_if_t<Condition, Type> = Type{}>
468 // ReturnType function() {}
469 //
470 // or
471 //
472 // template <..., typename = enable_if_t<Condition, void>>
473 // ReturnType function() {}
474 //
475
476 // Case 1. Return type of function
477 if (auto EnableIf = matchEnableIfSpecialization(*ReturnType)) {
478 diag(Loc: ReturnType->getBeginLoc(),
479 Description: "use C++20 requires constraints instead of enable_if")
480 << handleReturnType(Function, *ReturnType, *EnableIf, *Result.Context);
481 return;
482 }
483
484 // Case 2. Trailing template parameter
485 if (auto [EnableIf, LastTemplateParam] =
486 matchTrailingTemplateParam(FunctionTemplate);
487 EnableIf && LastTemplateParam) {
488 diag(LastTemplateParam->getSourceRange().getBegin(),
489 "use C++20 requires constraints instead of enable_if")
490 << handleTrailingTemplateType(FunctionTemplate, Function,
491 LastTemplateParam, *EnableIf,
492 *Result.Context);
493 return;
494 }
495}
496
497} // namespace clang::tidy::modernize
498

Provided by KDAB

Privacy Policy
Update your C++ knowledge – Modern C++11/14/17 Training
Find out more

source code of clang-tools-extra/clang-tidy/modernize/UseConstraintsCheck.cpp