[OptTable] Fix typo VALUE => VALUES (NFCI) (#121523)
[llvm-project.git] / clang-tools-extra / clang-tidy / modernize / MinMaxUseInitializerListCheck.cpp
blob9861f4681db1b41feef1d4cebc7c72833ca55ffd
1 //===--- MinMaxUseInitializerListCheck.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 "MinMaxUseInitializerListCheck.h"
10 #include "../utils/ASTUtils.h"
11 #include "../utils/LexerUtils.h"
12 #include "clang/ASTMatchers/ASTMatchFinder.h"
13 #include "clang/Frontend/CompilerInstance.h"
14 #include "clang/Lex/Lexer.h"
16 using namespace clang;
18 namespace {
20 struct FindArgsResult {
21 const Expr *First;
22 const Expr *Last;
23 const Expr *Compare;
24 SmallVector<const clang::Expr *, 2> Args;
27 } // anonymous namespace
29 using namespace clang::ast_matchers;
31 namespace clang::tidy::modernize {
33 static FindArgsResult findArgs(const CallExpr *Call) {
34 FindArgsResult Result;
35 Result.First = nullptr;
36 Result.Last = nullptr;
37 Result.Compare = nullptr;
39 // check if the function has initializer list argument
40 if (Call->getNumArgs() < 3) {
41 auto ArgIterator = Call->arguments().begin();
43 const auto *InitListExpr =
44 dyn_cast<CXXStdInitializerListExpr>(*ArgIterator);
45 const auto *InitList =
46 InitListExpr != nullptr
47 ? dyn_cast<clang::InitListExpr>(
48 InitListExpr->getSubExpr()->IgnoreImplicit())
49 : nullptr;
51 if (InitList) {
52 Result.Args.append(InitList->inits().begin(), InitList->inits().end());
53 Result.First = *ArgIterator;
54 Result.Last = *ArgIterator;
56 // check if there is a comparison argument
57 std::advance(ArgIterator, 1);
58 if (ArgIterator != Call->arguments().end())
59 Result.Compare = *ArgIterator;
61 return Result;
63 Result.Args = SmallVector<const Expr *>(Call->arguments());
64 } else {
65 // if it has 3 arguments then the last will be the comparison
66 Result.Compare = *(std::next(Call->arguments().begin(), 2));
67 Result.Args = SmallVector<const Expr *>(llvm::drop_end(Call->arguments()));
69 Result.First = Result.Args.front();
70 Result.Last = Result.Args.back();
72 return Result;
75 // Returns `true` as `first` only if a nested call to `std::min` or
76 // `std::max` was found. Checking if `FixItHint`s were generated is not enough,
77 // as the explicit casts that the check introduces may be generated without a
78 // nested `std::min` or `std::max` call.
79 static std::pair<bool, SmallVector<FixItHint>>
80 generateReplacements(const MatchFinder::MatchResult &Match,
81 const CallExpr *TopCall, const FindArgsResult &Result,
82 const bool IgnoreNonTrivialTypes,
83 const std::uint64_t IgnoreTrivialTypesOfSizeAbove) {
84 SmallVector<FixItHint> FixItHints;
85 const SourceManager &SourceMngr = *Match.SourceManager;
86 const LangOptions &LanguageOpts = Match.Context->getLangOpts();
88 const QualType ResultType = TopCall->getDirectCallee()
89 ->getReturnType()
90 .getCanonicalType()
91 .getNonReferenceType()
92 .getUnqualifiedType();
94 // check if the type is trivial
95 const bool IsResultTypeTrivial = ResultType.isTrivialType(*Match.Context);
97 if ((!IsResultTypeTrivial && IgnoreNonTrivialTypes))
98 return {false, FixItHints};
100 if (IsResultTypeTrivial &&
101 static_cast<std::uint64_t>(
102 Match.Context->getTypeSizeInChars(ResultType).getQuantity()) >
103 IgnoreTrivialTypesOfSizeAbove)
104 return {false, FixItHints};
106 bool FoundNestedCall = false;
108 for (const Expr *Arg : Result.Args) {
109 const auto *InnerCall = dyn_cast<CallExpr>(Arg->IgnoreParenImpCasts());
111 // If the argument is not a nested call
112 if (!InnerCall) {
113 // check if typecast is required
114 const QualType ArgType = Arg->IgnoreParenImpCasts()
115 ->getType()
116 .getCanonicalType()
117 .getUnqualifiedType();
119 if (ArgType == ResultType)
120 continue;
122 const StringRef ArgText = Lexer::getSourceText(
123 CharSourceRange::getTokenRange(Arg->getSourceRange()), SourceMngr,
124 LanguageOpts);
126 const auto Replacement = Twine("static_cast<")
127 .concat(ResultType.getAsString(LanguageOpts))
128 .concat(">(")
129 .concat(ArgText)
130 .concat(")")
131 .str();
133 FixItHints.push_back(
134 FixItHint::CreateReplacement(Arg->getSourceRange(), Replacement));
135 continue;
138 // if the nested call is not the same as the top call
139 if (InnerCall->getDirectCallee()->getQualifiedNameAsString() !=
140 TopCall->getDirectCallee()->getQualifiedNameAsString())
141 continue;
143 const FindArgsResult InnerResult = findArgs(InnerCall);
145 // if the nested call doesn't have arguments skip it
146 if (!InnerResult.First || !InnerResult.Last)
147 continue;
149 // if the nested call doesn't have the same compare function
150 if ((Result.Compare || InnerResult.Compare) &&
151 !utils::areStatementsIdentical(Result.Compare, InnerResult.Compare,
152 *Match.Context))
153 continue;
155 // We have found a nested call
156 FoundNestedCall = true;
158 // remove the function call
159 FixItHints.push_back(
160 FixItHint::CreateRemoval(InnerCall->getCallee()->getSourceRange()));
162 // remove the parentheses
163 const auto LParen = utils::lexer::findNextTokenSkippingComments(
164 InnerCall->getCallee()->getEndLoc(), SourceMngr, LanguageOpts);
165 if (LParen.has_value() && LParen->is(tok::l_paren))
166 FixItHints.push_back(
167 FixItHint::CreateRemoval(SourceRange(LParen->getLocation())));
168 FixItHints.push_back(
169 FixItHint::CreateRemoval(SourceRange(InnerCall->getRParenLoc())));
171 // if the inner call has an initializer list arg
172 if (InnerResult.First == InnerResult.Last) {
173 // remove the initializer list braces
174 FixItHints.push_back(FixItHint::CreateRemoval(
175 CharSourceRange::getTokenRange(InnerResult.First->getBeginLoc())));
176 FixItHints.push_back(FixItHint::CreateRemoval(
177 CharSourceRange::getTokenRange(InnerResult.First->getEndLoc())));
180 const auto [_, InnerReplacements] = generateReplacements(
181 Match, InnerCall, InnerResult, IgnoreNonTrivialTypes,
182 IgnoreTrivialTypesOfSizeAbove);
184 FixItHints.append(InnerReplacements);
186 if (InnerResult.Compare) {
187 // find the comma after the value arguments
188 const auto Comma = utils::lexer::findNextTokenSkippingComments(
189 InnerResult.Last->getEndLoc(), SourceMngr, LanguageOpts);
191 // remove the comma and the comparison
192 if (Comma.has_value() && Comma->is(tok::comma))
193 FixItHints.push_back(
194 FixItHint::CreateRemoval(SourceRange(Comma->getLocation())));
196 FixItHints.push_back(
197 FixItHint::CreateRemoval(InnerResult.Compare->getSourceRange()));
201 return {FoundNestedCall, FixItHints};
204 MinMaxUseInitializerListCheck::MinMaxUseInitializerListCheck(
205 StringRef Name, ClangTidyContext *Context)
206 : ClangTidyCheck(Name, Context),
207 IgnoreNonTrivialTypes(Options.get("IgnoreNonTrivialTypes", true)),
208 IgnoreTrivialTypesOfSizeAbove(
209 Options.get("IgnoreTrivialTypesOfSizeAbove", 32L)),
210 Inserter(Options.getLocalOrGlobal("IncludeStyle",
211 utils::IncludeSorter::IS_LLVM),
212 areDiagsSelfContained()) {}
214 void MinMaxUseInitializerListCheck::storeOptions(
215 ClangTidyOptions::OptionMap &Opts) {
216 Options.store(Opts, "IgnoreNonTrivialTypes", IgnoreNonTrivialTypes);
217 Options.store(Opts, "IgnoreTrivialTypesOfSizeAbove",
218 IgnoreTrivialTypesOfSizeAbove);
219 Options.store(Opts, "IncludeStyle", Inserter.getStyle());
222 void MinMaxUseInitializerListCheck::registerMatchers(MatchFinder *Finder) {
223 auto CreateMatcher = [](const StringRef FunctionName) {
224 auto FuncDecl = functionDecl(hasName(FunctionName));
225 auto Expression = callExpr(callee(FuncDecl));
227 return callExpr(callee(FuncDecl),
228 anyOf(hasArgument(0, Expression),
229 hasArgument(1, Expression),
230 hasArgument(0, cxxStdInitializerListExpr())),
231 unless(hasParent(Expression)))
232 .bind("topCall");
235 Finder->addMatcher(CreateMatcher("::std::max"), this);
236 Finder->addMatcher(CreateMatcher("::std::min"), this);
239 void MinMaxUseInitializerListCheck::registerPPCallbacks(
240 const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) {
241 Inserter.registerPreprocessor(PP);
244 void MinMaxUseInitializerListCheck::check(
245 const MatchFinder::MatchResult &Match) {
247 const auto *TopCall = Match.Nodes.getNodeAs<CallExpr>("topCall");
249 const FindArgsResult Result = findArgs(TopCall);
250 const auto [FoundNestedCall, Replacements] =
251 generateReplacements(Match, TopCall, Result, IgnoreNonTrivialTypes,
252 IgnoreTrivialTypesOfSizeAbove);
254 if (!FoundNestedCall)
255 return;
257 const DiagnosticBuilder Diagnostic =
258 diag(TopCall->getBeginLoc(),
259 "do not use nested 'std::%0' calls, use an initializer list instead")
260 << TopCall->getDirectCallee()->getName()
261 << Inserter.createIncludeInsertion(
262 Match.SourceManager->getFileID(TopCall->getBeginLoc()),
263 "<algorithm>");
265 // if the top call doesn't have an initializer list argument
266 if (Result.First != Result.Last) {
267 // add { and } insertions
268 Diagnostic << FixItHint::CreateInsertion(Result.First->getBeginLoc(), "{");
270 Diagnostic << FixItHint::CreateInsertion(
271 Lexer::getLocForEndOfToken(Result.Last->getEndLoc(), 0,
272 *Match.SourceManager,
273 Match.Context->getLangOpts()),
274 "}");
277 Diagnostic << Replacements;
280 } // namespace clang::tidy::modernize