clang-tools 24.0.0git
UseIfConstevalCheck.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
12#include "clang/ASTMatchers/ASTMatchFinder.h"
13#include "clang/Basic/CharInfo.h"
14#include "clang/Lex/Lexer.h"
15
16using namespace clang::ast_matchers;
17
18namespace clang::tidy::modernize {
19
20namespace {
21
22struct BraceFix {
23 bool NeedsBraces = false;
24 utils::BraceInsertionHints Hints;
25};
26
27} // namespace
28
29static std::optional<SourceRange> getHeaderRange(const IfStmt *If,
30 const SourceManager &SM,
31 const LangOptions &LangOpts) {
32 if (If->getLParenLoc().isMacroID() || If->getRParenLoc().isMacroID())
33 return std::nullopt;
34
35 const SourceRange HeaderRange(If->getLParenLoc(), If->getRParenLoc());
36 // Validate that the token range is safely rewriteable in file source before
37 // offering a fix-it.
38 if (Lexer::makeFileCharRange(CharSourceRange::getTokenRange(HeaderRange), SM,
39 LangOpts)
40 .isInvalid())
41 return std::nullopt;
42 return HeaderRange;
43}
44
45static std::optional<BraceFix>
46getBraceFix(const Stmt *S, const LangOptions &LangOpts, const SourceManager &SM,
47 SourceLocation StartLoc,
48 SourceLocation EndLocHint = SourceLocation()) {
49 if (!S || isa<CompoundStmt>(S))
50 return BraceFix();
51 if (isa<AttributedStmt, LabelStmt, SwitchCase>(S))
52 return std::nullopt;
53
54 const auto Hints =
55 utils::getBraceInsertionsHints(S, LangOpts, SM, StartLoc, EndLocHint);
56 if (!Hints || !Hints.offersFixIts())
57 return std::nullopt;
58
59 return BraceFix{true, Hints};
60}
61
62static bool needsLeadingSpaceBeforeConsteval(SourceLocation LParenLoc,
63 const SourceManager &SM) {
64 bool Invalid = false;
65 const char *LParen = SM.getCharacterData(LParenLoc, &Invalid);
66 return Invalid || !isWhitespace(LParen[-1]);
67}
68
69void UseIfConstevalCheck::registerMatchers(MatchFinder *Finder) {
70 const auto IsConstantEvaluatedCall =
71 callExpr(callee(functionDecl(hasName("is_constant_evaluated"),
72 isInStdNamespace())))
73 .bind("call");
74 const auto IsNegatedConstantEvaluatedExpr =
75 unaryOperator(hasOperatorName("!"),
76 hasUnaryOperand(ignoringParens(IsConstantEvaluatedCall)))
77 .bind("negation");
78 const auto IsConstantEvaluatedExpr = ignoringParenImpCasts(
79 anyOf(IsConstantEvaluatedCall, IsNegatedConstantEvaluatedExpr));
80
81 Finder->addMatcher(
82 ifStmt(unless(isConstexpr()),
83 anyOf(hasCondition(IsConstantEvaluatedExpr),
84 hasConditionVariableStatement(declStmt(hasSingleDecl(
85 varDecl(hasInitializer(IsConstantEvaluatedExpr)))))))
86 .bind("if"),
87 this);
88}
89
90void UseIfConstevalCheck::check(const MatchFinder::MatchResult &Result) {
91 const auto *If = Result.Nodes.getNodeAs<IfStmt>("if");
92 const auto *Call = Result.Nodes.getNodeAs<CallExpr>("call");
93 assert(If && Call && "expected to match an if statement and its call");
94
95 const bool IsNegated = Result.Nodes.getNodeAs<UnaryOperator>("negation");
96 const llvm::StringRef ConstevalClause =
97 IsNegated ? "!consteval" : "consteval";
98 const SourceLocation DiagLoc =
99 Result.SourceManager->getExpansionLoc(Call->getExprLoc());
100
101 const auto Diag = diag(DiagLoc, "use 'if %0' instead of checking "
102 "'std::is_constant_evaluated()'")
103 << ConstevalClause;
104
105 if (If->hasInitStorage() || If->hasVarStorage())
106 return;
107
108 std::optional<SourceRange> HeaderRange =
109 getHeaderRange(If, *Result.SourceManager, getLangOpts());
110 if (!HeaderRange)
111 return;
112
113 std::optional<BraceFix> ThenBraceFix =
114 getBraceFix(If->getThen(), getLangOpts(), *Result.SourceManager,
115 If->getRParenLoc(), If->getElseLoc());
116 if (!ThenBraceFix)
117 return;
118
119 std::optional<BraceFix> ElseBraceFix = BraceFix();
120 if (If->getElse()) {
121 ElseBraceFix = getBraceFix(If->getElse(), getLangOpts(),
122 *Result.SourceManager, If->getElseLoc());
123 }
124 if (!ElseBraceFix)
125 return;
126
127 const bool NeedsLeadingSpace = needsLeadingSpaceBeforeConsteval(
128 If->getLParenLoc(), *Result.SourceManager);
129 const std::string HeaderReplacement = [&] {
130 std::string Replacement = ConstevalClause.str();
131 if (NeedsLeadingSpace)
132 Replacement.insert(0, 1, ' ');
133 if (ThenBraceFix->NeedsBraces)
134 Replacement += " {";
135 return Replacement;
136 }();
137 Diag << FixItHint::CreateReplacement(*HeaderRange, HeaderReplacement);
138
139 if (ThenBraceFix->NeedsBraces)
140 Diag << ThenBraceFix->Hints.closingBraceFixIt();
141
142 if (ElseBraceFix && ElseBraceFix->NeedsBraces)
143 Diag << ElseBraceFix->Hints.openingBraceFixIt()
144 << ElseBraceFix->Hints.closingBraceFixIt();
145}
146
147} // namespace clang::tidy::modernize
This file provides utilities to put braces around a statement.
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
void registerMatchers(ast_matchers::MatchFinder *Finder) override
static std::optional< BraceFix > getBraceFix(const Stmt *S, const LangOptions &LangOpts, const SourceManager &SM, SourceLocation StartLoc, SourceLocation EndLocHint=SourceLocation())
static std::optional< SourceRange > getHeaderRange(const IfStmt *If, const SourceManager &SM, const LangOptions &LangOpts)
static bool needsLeadingSpaceBeforeConsteval(SourceLocation LParenLoc, const SourceManager &SM)
BraceInsertionHints getBraceInsertionsHints(const Stmt *const S, const LangOptions &LangOpts, const SourceManager &SM, SourceLocation StartLoc, SourceLocation EndLocHint)
Create fix-it hints for braces that wrap the given statement when applied.