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"
34#include <initializer_list>
41using clang::ast_matchers::internal::Matcher;
44AST_MATCHER_P2(clang::FloatingLiteral, near,
double, Value,
double,
46 return std::abs(Node.getValueAsApproximateDouble() - Value) < DiffThreshold;
50 Matcher<clang::QualType>, InnerMatcher) {
51 return !Node.isNull() &&
52 InnerMatcher.matches(Node->getCanonicalTypeUnqualified(), Finder,
57 return !Node.isNull() && Node->isArithmeticType();
60 return !Node.isNull() && Node->isFloatingType();
63AST_MATCHER_P(clang::Expr, anyOfExhaustive, std::vector<Matcher<clang::Stmt>>,
65 bool FoundMatch =
false;
66 for (
const auto &InnerMatcher : Exprs) {
67 clang::ast_matchers::internal::BoundNodesTreeBuilder Result = *Builder;
68 if (InnerMatcher.matches(Node, Finder, &Result)) {
69 *Builder = std::move(Result);
83 ignoreParenAndArithmeticCasting(
const Matcher<clang::Expr> &Matcher)
const {
84 return expr(hasType(qualType(isArithmetic())), ignoringParenCasts(Matcher));
88 ignoreParenAndFloatingCasting(
const Matcher<clang::Expr> &Matcher)
const {
89 return expr(hasType(qualType(isFloating())), ignoringParenCasts(Matcher));
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());
97 return expr(ignoreParenAndFloatingCasting(
98 callExpr(callee(functionDecl(HasAnyPrecisionName,
99 hasParameter(0, hasType(isArithmetic())))),
100 hasArgument(0, ArgumentMatcher))));
103 auto matchSqrt(
const Matcher<clang::Expr> &ArgumentMatcher)
const {
104 return matchMathCall(
"sqrt", ArgumentMatcher);
114 auto matchFloatLiteralNear(
const StringRef Constant,
const double Val)
const {
115 return expr(ignoreParenAndFloatingCasting(
116 floatLiteral(near(Val, DiffThreshold)).bind(Constant)));
130 auto matchFloatValueNear(
const double Val)
const {
131 const auto Float = floatLiteral(near(Val, DiffThreshold));
133 const auto Dref = declRefExpr(
134 to(varDecl(hasType(qualType(isConstQualified(), isFloating())),
135 hasInitializer(ignoreParenAndFloatingCasting(Float)))));
136 return expr(ignoreParenAndFloatingCasting(anyOf(Float, Dref)));
139 auto matchValue(
const int64_t ValInt)
const {
141 expr(ignoreParenAndArithmeticCasting(integerLiteral(equals(ValInt))));
142 const auto Float = expr(ignoreParenAndFloatingCasting(
143 matchFloatValueNear(
static_cast<double>(ValInt))));
144 const auto Dref = declRefExpr(to(varDecl(
145 hasType(qualType(isConstQualified(), isArithmetic())),
146 hasInitializer(expr(anyOf(ignoringImplicit(Int),
147 ignoreParenAndFloatingCasting(Float)))))));
148 return expr(anyOf(Int, Float, Dref));
151 auto match1Div(
const Matcher<clang::Expr> &Match)
const {
152 return binaryOperator(hasOperatorName(
"/"), hasLHS(matchValue(1)),
156 auto matchEuler()
const {
157 return expr(anyOf(matchFloatValueNear(llvm::numbers::e),
158 matchMathCall(
"exp", matchValue(1))));
160 auto matchEulerTopLevel()
const {
161 return expr(anyOf(matchFloatLiteralNear(
"e_literal", llvm::numbers::e),
162 matchMathCall(
"exp", matchValue(1)).bind(
"e_pattern")))
166 auto matchLog2Euler()
const {
169 matchFloatLiteralNear(
"log2e_literal", llvm::numbers::log2e),
170 matchMathCall(
"log2", matchEuler()).bind(
"log2e_pattern")))
174 auto matchLog10Euler()
const {
177 matchFloatLiteralNear(
"log10e_literal",
178 llvm::numbers::log10e),
179 matchMathCall(
"log10", matchEuler()).bind(
"log10e_pattern")))
183 auto matchPi()
const {
return matchFloatValueNear(llvm::numbers::pi); }
184 auto matchPiTopLevel()
const {
185 return matchFloatLiteralNear(
"pi_literal", llvm::numbers::pi).bind(
"pi");
188 auto matchEgamma()
const {
189 return matchFloatLiteralNear(
"egamma_literal", llvm::numbers::egamma)
193 auto matchInvPi()
const {
194 return expr(anyOf(matchFloatLiteralNear(
"inv_pi_literal",
195 llvm::numbers::inv_pi),
196 match1Div(matchPi()).bind(
"inv_pi_pattern")))
200 auto matchInvSqrtPi()
const {
202 matchFloatLiteralNear(
"inv_sqrtpi_literal",
203 llvm::numbers::inv_sqrtpi),
204 match1Div(matchSqrt(matchPi())).bind(
"inv_sqrtpi_pattern")))
208 auto matchLn2()
const {
209 return expr(anyOf(matchFloatLiteralNear(
"ln2_literal", llvm::numbers::ln2),
210 matchMathCall(
"log", matchValue(2)).bind(
"ln2_pattern")))
214 auto machterLn10()
const {
216 anyOf(matchFloatLiteralNear(
"ln10_literal", llvm::numbers::ln10),
217 matchMathCall(
"log", matchValue(10)).bind(
"ln10_pattern")))
221 auto matchSqrt2()
const {
222 return expr(anyOf(matchFloatLiteralNear(
"sqrt2_literal",
223 llvm::numbers::sqrt2),
224 matchSqrt(matchValue(2)).bind(
"sqrt2_pattern")))
228 auto matchSqrt3()
const {
229 return expr(anyOf(matchFloatLiteralNear(
"sqrt3_literal",
230 llvm::numbers::sqrt3),
231 matchSqrt(matchValue(3)).bind(
"sqrt3_pattern")))
235 auto matchInvSqrt3()
const {
236 return expr(anyOf(matchFloatLiteralNear(
"inv_sqrt3_literal",
237 llvm::numbers::inv_sqrt3),
238 match1Div(matchSqrt(matchValue(3)))
239 .bind(
"inv_sqrt3_pattern")))
243 auto matchPhi()
const {
244 const auto PhiFormula = binaryOperator(
245 hasOperatorName(
"/"),
246 hasLHS(binaryOperator(
247 hasOperatorName(
"+"), hasEitherOperand(matchValue(1)),
248 hasEitherOperand(matchMathCall(
"sqrt", matchValue(5))))),
249 hasRHS(matchValue(2)));
250 return expr(anyOf(PhiFormula.bind(
"phi_pattern"),
251 matchFloatLiteralNear(
"phi_literal", llvm::numbers::phi)))
255 double DiffThreshold;
260static std::string
getCode(
const StringRef Constant,
const bool IsFloat,
261 const bool IsLongDouble) {
263 return (
"std::numbers::" + Constant +
"_v<float>").str();
265 return (
"std::numbers::" + Constant +
"_v<long double>").str();
266 return (
"std::numbers::" + Constant).str();
270 const clang::SourceManager &SM,
271 const clang::LangOptions &LO) {
272 if (!Range.getBegin().isMacroID())
274 if (!clang::Lexer::isAtStartOfMacroExpansion(Range.getBegin(), SM, LO))
277 if (!Range.getEnd().isMacroID())
280 if (!clang::Lexer::isAtEndOfMacroExpansion(Range.getEnd(), SM, LO))
290 IncludeInserter(Options.getLocalOrGlobal(
"IncludeStyle",
291 utils::IncludeSorter::IS_LLVM),
292 areDiagsSelfContained()),
293 DiffThresholdString{Options.get(
"DiffThreshold",
"0.001")} {
294 if (DiffThresholdString.getAsDouble(DiffThreshold)) {
296 "Invalid DiffThreshold config value: '%0', expected a double")
297 << DiffThresholdString;
298 DiffThreshold = 0.001;
303 const auto Matches = MatchBuilder{DiffThreshold};
304 const std::vector<Matcher<clang::Stmt>> ConstantMatchers = {
305 Matches.matchLog2Euler(), Matches.matchLog10Euler(),
306 Matches.matchEulerTopLevel(), Matches.matchEgamma(),
307 Matches.matchInvSqrtPi(), Matches.matchInvPi(),
308 Matches.matchPiTopLevel(), Matches.matchLn2(),
309 Matches.machterLn10(), Matches.matchSqrt2(),
310 Matches.matchInvSqrt3(), Matches.matchSqrt3(),
316 anyOfExhaustive(ConstantMatchers),
317 unless(hasParent(explicitCastExpr(hasDestinationType(isFloating())))),
318 hasType(qualType(hasCanonicalTypeUnqualified(
319 anyOf(qualType(asString(
"float")).bind(
"float"),
320 qualType(asString(
"double")),
321 qualType(asString(
"long double")).bind(
"long double")))))),
346 constexpr auto Constants = std::array<std::pair<StringRef, double>, 13>{
347 std::pair{StringRef{
"log2e"}, llvm::numbers::log2e},
348 std::pair{StringRef{
"log10e"}, llvm::numbers::log10e},
349 std::pair{StringRef{
"e"}, llvm::numbers::e},
350 std::pair{StringRef{
"egamma"}, llvm::numbers::egamma},
351 std::pair{StringRef{
"inv_sqrtpi"}, llvm::numbers::inv_sqrtpi},
352 std::pair{StringRef{
"inv_pi"}, llvm::numbers::inv_pi},
353 std::pair{StringRef{
"pi"}, llvm::numbers::pi},
354 std::pair{StringRef{
"ln2"}, llvm::numbers::ln2},
355 std::pair{StringRef{
"ln10"}, llvm::numbers::ln10},
356 std::pair{StringRef{
"sqrt2"}, llvm::numbers::sqrt2},
357 std::pair{StringRef{
"inv_sqrt3"}, llvm::numbers::inv_sqrt3},
358 std::pair{StringRef{
"sqrt3"}, llvm::numbers::sqrt3},
359 std::pair{StringRef{
"phi"}, llvm::numbers::phi},
362 auto MatchedLiterals =
363 llvm::SmallVector<std::tuple<std::string, double, const Expr *>>{};
365 const auto &SM = *Result.SourceManager;
366 const auto &LO = Result.Context->getLangOpts();
368 const auto IsFloat = Result.Nodes.getNodeAs<QualType>(
"float") !=
nullptr;
369 const auto IsLongDouble =
370 Result.Nodes.getNodeAs<QualType>(
"long double") !=
nullptr;
372 for (
const auto &[ConstantName, ConstantValue] : Constants) {
373 const auto *
const Match = Result.Nodes.getNodeAs<Expr>(ConstantName);
374 if (Match ==
nullptr)
377 const auto Range = Match->getSourceRange();
379 const auto IsMacro = Range.getBegin().isMacroID();
386 if (
const auto PatternBindString = (ConstantName +
"_pattern").str();
387 Result.Nodes.getNodeAs<Expr>(PatternBindString) !=
nullptr) {
388 const auto Code =
getCode(ConstantName, IsFloat, IsLongDouble);
389 diag(Range.getBegin(),
"prefer '%0' to this %select{formula|macro}1")
390 << Code << IsMacro << FixItHint::CreateReplacement(Range, Code);
394 const auto LiteralBindString = (ConstantName +
"_literal").str();
395 if (
const auto *
const Literal =
396 Result.Nodes.getNodeAs<FloatingLiteral>(LiteralBindString)) {
397 MatchedLiterals.emplace_back(
399 std::abs(Literal->getValueAsApproximateDouble() - ConstantValue),
406 if (MatchedLiterals.empty())
409 llvm::sort(MatchedLiterals, llvm::less_second());
411 const auto &[Constant, Diff, Node] = MatchedLiterals.front();
413 const auto Range = Node->getSourceRange();
414 const auto IsMacro = Range.getBegin().isMacroID();
421 const auto Code =
getCode(Constant, IsFloat, IsLongDouble);
422 diag(Range.getBegin(),
423 "prefer '%0' to this %select{literal|macro}1, differs by '%2'")
424 << Code << IsMacro << llvm::formatv(
"{0:e2}", Diff).str()
425 << FixItHint::CreateReplacement(Range, Code)
426 << IncludeInserter.createIncludeInsertion(
427 Result.SourceManager->getFileID(Range.getBegin()),
"<numbers>");
431 const SourceManager &SM, Preprocessor *
const PP,
432 Preprocessor *
const ModuleExpanderPP) {
433 IncludeInserter.registerPreprocessor(PP);
437 Options.store(Opts,
"IncludeStyle", IncludeInserter.getStyle());
438 Options.store(Opts,
"DiffThreshold", DiffThresholdString);
static bool isRangeOfCompleteMacro(const clang::SourceRange &Range, const clang::SourceManager &SM, const clang::LangOptions &LO)
static std::string getCode(const StringRef Constant, const bool IsFloat, const bool IsLongDouble)
Every ClangTidyCheck reports errors through a DiagnosticsEngine provided by this context.
void registerPPCallbacks(const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) override
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
UseStdNumbersCheck(StringRef Name, ClangTidyContext *Context)
void storeOptions(ClangTidyOptions::OptionMap &Opts) override
void registerMatchers(ast_matchers::MatchFinder *Finder) override
AST_MATCHER_P(Stmt, isStatementIdenticalToBoundNode, std::string, ID)
AST_MATCHER(BinaryOperator, isRelationalOperator)
llvm::StringMap< ClangTidyValue > OptionMap