| 1 | //===--- UseStdNumbersCheck.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 "UseStdNumbersCheck.h" |
| 10 | #include "../ClangTidyDiagnosticConsumer.h" |
| 11 | #include "clang/AST/ASTContext.h" |
| 12 | #include "clang/AST/Decl.h" |
| 13 | #include "clang/AST/Expr.h" |
| 14 | #include "clang/AST/Stmt.h" |
| 15 | #include "clang/AST/Type.h" |
| 16 | #include "clang/ASTMatchers/ASTMatchFinder.h" |
| 17 | #include "clang/ASTMatchers/ASTMatchers.h" |
| 18 | #include "clang/ASTMatchers/ASTMatchersInternal.h" |
| 19 | #include "clang/ASTMatchers/ASTMatchersMacros.h" |
| 20 | #include "clang/Basic/Diagnostic.h" |
| 21 | #include "clang/Basic/LLVM.h" |
| 22 | #include "clang/Basic/LangOptions.h" |
| 23 | #include "clang/Basic/SourceLocation.h" |
| 24 | #include "clang/Basic/SourceManager.h" |
| 25 | #include "clang/Lex/Lexer.h" |
| 26 | #include "llvm/ADT/STLExtras.h" |
| 27 | #include "llvm/ADT/SmallVector.h" |
| 28 | #include "llvm/ADT/StringRef.h" |
| 29 | #include "llvm/Support/FormatVariadic.h" |
| 30 | #include "llvm/Support/MathExtras.h" |
| 31 | #include <array> |
| 32 | #include <cmath> |
| 33 | #include <cstdint> |
| 34 | #include <cstdlib> |
| 35 | #include <initializer_list> |
| 36 | #include <string> |
| 37 | #include <tuple> |
| 38 | #include <utility> |
| 39 | |
| 40 | namespace { |
| 41 | using namespace clang::ast_matchers; |
| 42 | using clang::ast_matchers::internal::Matcher; |
| 43 | using llvm::StringRef; |
| 44 | |
| 45 | AST_MATCHER_P2(clang::FloatingLiteral, near, double, Value, double, |
| 46 | DiffThreshold) { |
| 47 | return std::abs(x: Node.getValueAsApproximateDouble() - Value) < DiffThreshold; |
| 48 | } |
| 49 | |
| 50 | AST_MATCHER_P(clang::QualType, hasCanonicalTypeUnqualified, |
| 51 | Matcher<clang::QualType>, InnerMatcher) { |
| 52 | return !Node.isNull() && |
| 53 | InnerMatcher.matches(Node: Node->getCanonicalTypeUnqualified(), Finder, |
| 54 | Builder); |
| 55 | } |
| 56 | |
| 57 | AST_MATCHER(clang::QualType, isArithmetic) { |
| 58 | return !Node.isNull() && Node->isArithmeticType(); |
| 59 | } |
| 60 | AST_MATCHER(clang::QualType, isFloating) { |
| 61 | return !Node.isNull() && Node->isFloatingType(); |
| 62 | } |
| 63 | |
| 64 | AST_MATCHER_P(clang::Expr, anyOfExhaustive, std::vector<Matcher<clang::Stmt>>, |
| 65 | Exprs) { |
| 66 | bool FoundMatch = false; |
| 67 | for (const auto &InnerMatcher : Exprs) { |
| 68 | clang::ast_matchers::internal::BoundNodesTreeBuilder Result = *Builder; |
| 69 | if (InnerMatcher.matches(Node, Finder, &Result)) { |
| 70 | *Builder = std::move(Result); |
| 71 | FoundMatch = true; |
| 72 | } |
| 73 | } |
| 74 | return FoundMatch; |
| 75 | } |
| 76 | |
| 77 | // Using this struct to store the 'DiffThreshold' config value to create the |
| 78 | // matchers without the need to pass 'DiffThreshold' into every matcher. |
| 79 | // 'DiffThreshold' is needed in the 'near' matcher, which is used for matching |
| 80 | // the literal of every constant and for formulas' subexpressions that look at |
| 81 | // literals. |
| 82 | struct MatchBuilder { |
| 83 | auto |
| 84 | ignoreParenAndArithmeticCasting(const Matcher<clang::Expr> Matcher) const { |
| 85 | return expr(hasType(InnerMatcher: qualType(isArithmetic())), ignoringParenCasts(InnerMatcher: Matcher)); |
| 86 | } |
| 87 | |
| 88 | auto ignoreParenAndFloatingCasting(const Matcher<clang::Expr> Matcher) const { |
| 89 | return expr(hasType(InnerMatcher: qualType(isFloating())), ignoringParenCasts(InnerMatcher: Matcher)); |
| 90 | } |
| 91 | |
| 92 | auto matchMathCall(const StringRef FunctionName, |
| 93 | const Matcher<clang::Expr> ArgumentMatcher) const { |
| 94 | auto HasAnyPrecisionName = hasAnyName( |
| 95 | FunctionName, (FunctionName + "l" ).str(), |
| 96 | (FunctionName + "f" ).str()); // Support long double(l) and float(f). |
| 97 | return expr(ignoreParenAndFloatingCasting( |
| 98 | Matcher: callExpr(callee(InnerMatcher: functionDecl(HasAnyPrecisionName, |
| 99 | hasParameter(N: 0, InnerMatcher: hasType(InnerMatcher: isArithmetic())))), |
| 100 | hasArgument(N: 0, InnerMatcher: ArgumentMatcher)))); |
| 101 | } |
| 102 | |
| 103 | auto matchSqrt(const Matcher<clang::Expr> ArgumentMatcher) const { |
| 104 | return matchMathCall(FunctionName: "sqrt" , ArgumentMatcher); |
| 105 | } |
| 106 | |
| 107 | // Used for top-level matchers (i.e. the match that replaces Val with its |
| 108 | // constant). |
| 109 | // |
| 110 | // E.g. The matcher of `std::numbers::pi` uses this matcher to look for |
| 111 | // floatLiterals that have the value of pi. |
| 112 | // |
| 113 | // If the match is for a top-level match, we only care about the literal. |
| 114 | auto matchFloatLiteralNear(const StringRef Constant, const double Val) const { |
| 115 | return expr(ignoreParenAndFloatingCasting( |
| 116 | Matcher: floatLiteral(near(Value: Val, DiffThreshold)).bind(ID: Constant))); |
| 117 | } |
| 118 | |
| 119 | // Used for non-top-level matchers (i.e. matchers that are used as inner |
| 120 | // matchers for top-level matchers). |
| 121 | // |
| 122 | // E.g.: The matcher of `std::numbers::log2e` uses this matcher to check if |
| 123 | // `e` of `log2(e)` is declared constant and initialized with the value for |
| 124 | // eulers number. |
| 125 | // |
| 126 | // Here, we do care about literals and about DeclRefExprs to variable |
| 127 | // declarations that are constant and initialized with `Val`. This allows |
| 128 | // top-level matchers to see through declared constants for their inner |
| 129 | // matches like the `std::numbers::log2e` matcher. |
| 130 | auto matchFloatValueNear(const double Val) const { |
| 131 | const auto Float = floatLiteral(near(Value: Val, DiffThreshold)); |
| 132 | |
| 133 | const auto Dref = declRefExpr( |
| 134 | to(InnerMatcher: varDecl(hasType(InnerMatcher: qualType(isConstQualified(), isFloating())), |
| 135 | hasInitializer(InnerMatcher: ignoreParenAndFloatingCasting(Matcher: Float))))); |
| 136 | return expr(ignoreParenAndFloatingCasting(Matcher: anyOf(Float, Dref))); |
| 137 | } |
| 138 | |
| 139 | auto matchValue(const int64_t ValInt) const { |
| 140 | const auto Int = |
| 141 | expr(ignoreParenAndArithmeticCasting(Matcher: integerLiteral(equals(Value: ValInt)))); |
| 142 | const auto Float = expr(ignoreParenAndFloatingCasting( |
| 143 | Matcher: matchFloatValueNear(Val: static_cast<double>(ValInt)))); |
| 144 | const auto Dref = declRefExpr(to(InnerMatcher: varDecl( |
| 145 | hasType(InnerMatcher: qualType(isConstQualified(), isArithmetic())), |
| 146 | hasInitializer(InnerMatcher: expr(anyOf(ignoringImplicit(InnerMatcher: Int), |
| 147 | ignoreParenAndFloatingCasting(Matcher: Float))))))); |
| 148 | return expr(anyOf(Int, Float, Dref)); |
| 149 | } |
| 150 | |
| 151 | auto match1Div(const Matcher<clang::Expr> Match) const { |
| 152 | return binaryOperator(hasOperatorName(Name: "/" ), hasLHS(InnerMatcher: matchValue(ValInt: 1)), |
| 153 | hasRHS(InnerMatcher: Match)); |
| 154 | } |
| 155 | |
| 156 | auto matchEuler() const { |
| 157 | return expr(anyOf(matchFloatValueNear(Val: llvm::numbers::e), |
| 158 | matchMathCall(FunctionName: "exp" , ArgumentMatcher: matchValue(ValInt: 1)))); |
| 159 | } |
| 160 | auto matchEulerTopLevel() const { |
| 161 | return expr(anyOf(matchFloatLiteralNear(Constant: "e_literal" , Val: llvm::numbers::e), |
| 162 | matchMathCall(FunctionName: "exp" , ArgumentMatcher: matchValue(ValInt: 1)).bind(ID: "e_pattern" ))) |
| 163 | .bind(ID: "e" ); |
| 164 | } |
| 165 | |
| 166 | auto matchLog2Euler() const { |
| 167 | return expr( |
| 168 | anyOf( |
| 169 | matchFloatLiteralNear(Constant: "log2e_literal" , Val: llvm::numbers::log2e), |
| 170 | matchMathCall(FunctionName: "log2" , ArgumentMatcher: matchEuler()).bind(ID: "log2e_pattern" ))) |
| 171 | .bind(ID: "log2e" ); |
| 172 | } |
| 173 | |
| 174 | auto matchLog10Euler() const { |
| 175 | return expr( |
| 176 | anyOf( |
| 177 | matchFloatLiteralNear(Constant: "log10e_literal" , |
| 178 | Val: llvm::numbers::log10e), |
| 179 | matchMathCall(FunctionName: "log10" , ArgumentMatcher: matchEuler()).bind(ID: "log10e_pattern" ))) |
| 180 | .bind(ID: "log10e" ); |
| 181 | } |
| 182 | |
| 183 | auto matchPi() const { return matchFloatValueNear(Val: llvm::numbers::pi); } |
| 184 | auto matchPiTopLevel() const { |
| 185 | return matchFloatLiteralNear(Constant: "pi_literal" , Val: llvm::numbers::pi).bind(ID: "pi" ); |
| 186 | } |
| 187 | |
| 188 | auto matchEgamma() const { |
| 189 | return matchFloatLiteralNear(Constant: "egamma_literal" , Val: llvm::numbers::egamma) |
| 190 | .bind(ID: "egamma" ); |
| 191 | } |
| 192 | |
| 193 | auto matchInvPi() const { |
| 194 | return expr(anyOf(matchFloatLiteralNear(Constant: "inv_pi_literal" , |
| 195 | Val: llvm::numbers::inv_pi), |
| 196 | match1Div(Match: matchPi()).bind(ID: "inv_pi_pattern" ))) |
| 197 | .bind(ID: "inv_pi" ); |
| 198 | } |
| 199 | |
| 200 | auto matchInvSqrtPi() const { |
| 201 | return expr(anyOf( |
| 202 | matchFloatLiteralNear(Constant: "inv_sqrtpi_literal" , |
| 203 | Val: llvm::numbers::inv_sqrtpi), |
| 204 | match1Div(Match: matchSqrt(ArgumentMatcher: matchPi())).bind(ID: "inv_sqrtpi_pattern" ))) |
| 205 | .bind(ID: "inv_sqrtpi" ); |
| 206 | } |
| 207 | |
| 208 | auto matchLn2() const { |
| 209 | return expr(anyOf(matchFloatLiteralNear(Constant: "ln2_literal" , Val: llvm::numbers::ln2), |
| 210 | matchMathCall(FunctionName: "log" , ArgumentMatcher: matchValue(ValInt: 2)).bind(ID: "ln2_pattern" ))) |
| 211 | .bind(ID: "ln2" ); |
| 212 | } |
| 213 | |
| 214 | auto machterLn10() const { |
| 215 | return expr( |
| 216 | anyOf(matchFloatLiteralNear(Constant: "ln10_literal" , Val: llvm::numbers::ln10), |
| 217 | matchMathCall(FunctionName: "log" , ArgumentMatcher: matchValue(ValInt: 10)).bind(ID: "ln10_pattern" ))) |
| 218 | .bind(ID: "ln10" ); |
| 219 | } |
| 220 | |
| 221 | auto matchSqrt2() const { |
| 222 | return expr(anyOf(matchFloatLiteralNear(Constant: "sqrt2_literal" , |
| 223 | Val: llvm::numbers::sqrt2), |
| 224 | matchSqrt(ArgumentMatcher: matchValue(ValInt: 2)).bind(ID: "sqrt2_pattern" ))) |
| 225 | .bind(ID: "sqrt2" ); |
| 226 | } |
| 227 | |
| 228 | auto matchSqrt3() const { |
| 229 | return expr(anyOf(matchFloatLiteralNear(Constant: "sqrt3_literal" , |
| 230 | Val: llvm::numbers::sqrt3), |
| 231 | matchSqrt(ArgumentMatcher: matchValue(ValInt: 3)).bind(ID: "sqrt3_pattern" ))) |
| 232 | .bind(ID: "sqrt3" ); |
| 233 | } |
| 234 | |
| 235 | auto matchInvSqrt3() const { |
| 236 | return expr(anyOf(matchFloatLiteralNear(Constant: "inv_sqrt3_literal" , |
| 237 | Val: llvm::numbers::inv_sqrt3), |
| 238 | match1Div(Match: matchSqrt(ArgumentMatcher: matchValue(ValInt: 3))) |
| 239 | .bind(ID: "inv_sqrt3_pattern" ))) |
| 240 | .bind(ID: "inv_sqrt3" ); |
| 241 | } |
| 242 | |
| 243 | auto matchPhi() const { |
| 244 | const auto PhiFormula = binaryOperator( |
| 245 | hasOperatorName(Name: "/" ), |
| 246 | hasLHS(InnerMatcher: binaryOperator( |
| 247 | hasOperatorName(Name: "+" ), hasEitherOperand(InnerMatcher: matchValue(ValInt: 1)), |
| 248 | hasEitherOperand(InnerMatcher: matchMathCall(FunctionName: "sqrt" , ArgumentMatcher: matchValue(ValInt: 5))))), |
| 249 | hasRHS(InnerMatcher: matchValue(ValInt: 2))); |
| 250 | return expr(anyOf(PhiFormula.bind(ID: "phi_pattern" ), |
| 251 | matchFloatLiteralNear(Constant: "phi_literal" , Val: llvm::numbers::phi))) |
| 252 | .bind(ID: "phi" ); |
| 253 | } |
| 254 | |
| 255 | double DiffThreshold; |
| 256 | }; |
| 257 | |
| 258 | std::string getCode(const StringRef Constant, const bool IsFloat, |
| 259 | const bool IsLongDouble) { |
| 260 | if (IsFloat) { |
| 261 | return ("std::numbers::" + Constant + "_v<float>" ).str(); |
| 262 | } |
| 263 | if (IsLongDouble) { |
| 264 | return ("std::numbers::" + Constant + "_v<long double>" ).str(); |
| 265 | } |
| 266 | return ("std::numbers::" + Constant).str(); |
| 267 | } |
| 268 | |
| 269 | bool isRangeOfCompleteMacro(const clang::SourceRange &Range, |
| 270 | const clang::SourceManager &SM, |
| 271 | const clang::LangOptions &LO) { |
| 272 | if (!Range.getBegin().isMacroID()) { |
| 273 | return false; |
| 274 | } |
| 275 | if (!clang::Lexer::isAtStartOfMacroExpansion(loc: Range.getBegin(), SM, LangOpts: LO)) { |
| 276 | return false; |
| 277 | } |
| 278 | |
| 279 | if (!Range.getEnd().isMacroID()) { |
| 280 | return false; |
| 281 | } |
| 282 | |
| 283 | if (!clang::Lexer::isAtEndOfMacroExpansion(loc: Range.getEnd(), SM, LangOpts: LO)) { |
| 284 | return false; |
| 285 | } |
| 286 | |
| 287 | return true; |
| 288 | } |
| 289 | |
| 290 | } // namespace |
| 291 | |
| 292 | namespace clang::tidy::modernize { |
| 293 | UseStdNumbersCheck::UseStdNumbersCheck(const StringRef Name, |
| 294 | ClangTidyContext *const Context) |
| 295 | : ClangTidyCheck(Name, Context), |
| 296 | IncludeInserter(Options.getLocalOrGlobal(LocalName: "IncludeStyle" , |
| 297 | Default: utils::IncludeSorter::IS_LLVM), |
| 298 | areDiagsSelfContained()), |
| 299 | DiffThresholdString{Options.get(LocalName: "DiffThreshold" , Default: "0.001" )} { |
| 300 | if (DiffThresholdString.getAsDouble(Result&: DiffThreshold)) { |
| 301 | configurationDiag( |
| 302 | Description: "Invalid DiffThreshold config value: '%0', expected a double" ) |
| 303 | << DiffThresholdString; |
| 304 | DiffThreshold = 0.001; |
| 305 | } |
| 306 | } |
| 307 | |
| 308 | void UseStdNumbersCheck::registerMatchers(MatchFinder *const Finder) { |
| 309 | const auto Matches = MatchBuilder{.DiffThreshold: DiffThreshold}; |
| 310 | std::vector<Matcher<clang::Stmt>> ConstantMatchers = { |
| 311 | Matches.matchLog2Euler(), Matches.matchLog10Euler(), |
| 312 | Matches.matchEulerTopLevel(), Matches.matchEgamma(), |
| 313 | Matches.matchInvSqrtPi(), Matches.matchInvPi(), |
| 314 | Matches.matchPiTopLevel(), Matches.matchLn2(), |
| 315 | Matches.machterLn10(), Matches.matchSqrt2(), |
| 316 | Matches.matchInvSqrt3(), Matches.matchSqrt3(), |
| 317 | Matches.matchPhi(), |
| 318 | }; |
| 319 | |
| 320 | Finder->addMatcher( |
| 321 | NodeMatch: expr( |
| 322 | anyOfExhaustive(Exprs: std::move(ConstantMatchers)), |
| 323 | unless(hasParent(explicitCastExpr(hasDestinationType(InnerMatcher: isFloating())))), |
| 324 | hasType(InnerMatcher: qualType(hasCanonicalTypeUnqualified( |
| 325 | InnerMatcher: anyOf(qualType(asString(Name: "float" )).bind(ID: "float" ), |
| 326 | qualType(asString(Name: "double" )), |
| 327 | qualType(asString(Name: "long double" )).bind(ID: "long double" )))))), |
| 328 | Action: this); |
| 329 | } |
| 330 | |
| 331 | void UseStdNumbersCheck::check(const MatchFinder::MatchResult &Result) { |
| 332 | /* |
| 333 | List of all math constants in the `<numbers>` header |
| 334 | + e |
| 335 | + log2e |
| 336 | + log10e |
| 337 | + pi |
| 338 | + inv_pi |
| 339 | + inv_sqrtpi |
| 340 | + ln2 |
| 341 | + ln10 |
| 342 | + sqrt2 |
| 343 | + sqrt3 |
| 344 | + inv_sqrt3 |
| 345 | + egamma |
| 346 | + phi |
| 347 | */ |
| 348 | |
| 349 | // The ordering determines what constants are looked at first. |
| 350 | // E.g. look at 'inv_sqrt3' before 'sqrt3' to be able to replace the larger |
| 351 | // expression |
| 352 | constexpr auto Constants = std::array<std::pair<StringRef, double>, 13>{ |
| 353 | std::pair{StringRef{"log2e" }, llvm::numbers::log2e}, |
| 354 | std::pair{StringRef{"log10e" }, llvm::numbers::log10e}, |
| 355 | std::pair{StringRef{"e" }, llvm::numbers::e}, |
| 356 | std::pair{StringRef{"egamma" }, llvm::numbers::egamma}, |
| 357 | std::pair{StringRef{"inv_sqrtpi" }, llvm::numbers::inv_sqrtpi}, |
| 358 | std::pair{StringRef{"inv_pi" }, llvm::numbers::inv_pi}, |
| 359 | std::pair{StringRef{"pi" }, llvm::numbers::pi}, |
| 360 | std::pair{StringRef{"ln2" }, llvm::numbers::ln2}, |
| 361 | std::pair{StringRef{"ln10" }, llvm::numbers::ln10}, |
| 362 | std::pair{StringRef{"sqrt2" }, llvm::numbers::sqrt2}, |
| 363 | std::pair{StringRef{"inv_sqrt3" }, llvm::numbers::inv_sqrt3}, |
| 364 | std::pair{StringRef{"sqrt3" }, llvm::numbers::sqrt3}, |
| 365 | std::pair{StringRef{"phi" }, llvm::numbers::phi}, |
| 366 | }; |
| 367 | |
| 368 | auto MatchedLiterals = |
| 369 | llvm::SmallVector<std::tuple<std::string, double, const Expr *>>{}; |
| 370 | |
| 371 | const auto &SM = *Result.SourceManager; |
| 372 | const auto &LO = Result.Context->getLangOpts(); |
| 373 | |
| 374 | const auto IsFloat = Result.Nodes.getNodeAs<QualType>(ID: "float" ) != nullptr; |
| 375 | const auto IsLongDouble = |
| 376 | Result.Nodes.getNodeAs<QualType>(ID: "long double" ) != nullptr; |
| 377 | |
| 378 | for (const auto &[ConstantName, ConstantValue] : Constants) { |
| 379 | const auto *const Match = Result.Nodes.getNodeAs<Expr>(ID: ConstantName); |
| 380 | if (Match == nullptr) { |
| 381 | continue; |
| 382 | } |
| 383 | |
| 384 | const auto Range = Match->getSourceRange(); |
| 385 | |
| 386 | const auto IsMacro = Range.getBegin().isMacroID(); |
| 387 | |
| 388 | // We do not want to emit a diagnostic when we are matching a macro, but the |
| 389 | // match inside of the macro does not cover the whole macro. |
| 390 | if (IsMacro && !isRangeOfCompleteMacro(Range, SM, LO)) { |
| 391 | continue; |
| 392 | } |
| 393 | |
| 394 | if (const auto PatternBindString = (ConstantName + "_pattern" ).str(); |
| 395 | Result.Nodes.getNodeAs<Expr>(ID: PatternBindString) != nullptr) { |
| 396 | const auto Code = getCode(Constant: ConstantName, IsFloat, IsLongDouble); |
| 397 | diag(Range.getBegin(), "prefer '%0' to this %select{formula|macro}1" ) |
| 398 | << Code << IsMacro << FixItHint::CreateReplacement(Range, Code); |
| 399 | return; |
| 400 | } |
| 401 | |
| 402 | const auto LiteralBindString = (ConstantName + "_literal" ).str(); |
| 403 | if (const auto *const Literal = |
| 404 | Result.Nodes.getNodeAs<FloatingLiteral>(ID: LiteralBindString)) { |
| 405 | MatchedLiterals.emplace_back( |
| 406 | Args: ConstantName, |
| 407 | Args: std::abs(x: Literal->getValueAsApproximateDouble() - ConstantValue), |
| 408 | Args: Match); |
| 409 | } |
| 410 | } |
| 411 | |
| 412 | // We may have had no matches with literals, but a match with a pattern that |
| 413 | // was a part of a macro which was therefore skipped. |
| 414 | if (MatchedLiterals.empty()) { |
| 415 | return; |
| 416 | } |
| 417 | |
| 418 | llvm::sort(C&: MatchedLiterals, Comp: llvm::less_second()); |
| 419 | |
| 420 | const auto &[Constant, Diff, Node] = MatchedLiterals.front(); |
| 421 | |
| 422 | const auto Range = Node->getSourceRange(); |
| 423 | const auto IsMacro = Range.getBegin().isMacroID(); |
| 424 | |
| 425 | // We do not want to emit a diagnostic when we are matching a macro, but the |
| 426 | // match inside of the macro does not cover the whole macro. |
| 427 | if (IsMacro && !isRangeOfCompleteMacro(Range, SM, LO)) { |
| 428 | return; |
| 429 | } |
| 430 | |
| 431 | const auto Code = getCode(Constant, IsFloat, IsLongDouble); |
| 432 | diag(Range.getBegin(), |
| 433 | "prefer '%0' to this %select{literal|macro}1, differs by '%2'" ) |
| 434 | << Code << IsMacro << llvm::formatv(Fmt: "{0:e2}" , Vals: Diff).str() |
| 435 | << FixItHint::CreateReplacement(Range, Code) |
| 436 | << IncludeInserter.createIncludeInsertion( |
| 437 | FileID: Result.SourceManager->getFileID(Range.getBegin()), Header: "<numbers>" ); |
| 438 | } |
| 439 | |
| 440 | void UseStdNumbersCheck::registerPPCallbacks( |
| 441 | const SourceManager &SM, Preprocessor *const PP, |
| 442 | Preprocessor *const ModuleExpanderPP) { |
| 443 | IncludeInserter.registerPreprocessor(PP); |
| 444 | } |
| 445 | |
| 446 | void UseStdNumbersCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { |
| 447 | Options.store(Options&: Opts, LocalName: "IncludeStyle" , Value: IncludeInserter.getStyle()); |
| 448 | Options.store(Options&: Opts, LocalName: "DiffThreshold" , Value: DiffThresholdString); |
| 449 | } |
| 450 | } // namespace clang::tidy::modernize |
| 451 | |