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