1 //===--- UpgradeDurationConversionsCheck.cpp - clang-tidy -----------------===//
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
7 //===----------------------------------------------------------------------===//
9 #include "UpgradeDurationConversionsCheck.h"
10 #include "DurationRewriter.h"
11 #include "clang/AST/ASTContext.h"
12 #include "clang/ASTMatchers/ASTMatchFinder.h"
13 #include "clang/Lex/Lexer.h"
15 using namespace clang::ast_matchers
;
17 namespace clang::tidy::abseil
{
19 void UpgradeDurationConversionsCheck::registerMatchers(MatchFinder
*Finder
) {
20 // For the arithmetic calls, we match only the uses of the templated operators
21 // where the template parameter is not a built-in type. This means the
22 // instantiation makes use of an available user defined conversion to
25 // The implementation of these templates will be updated to fail SFINAE for
26 // non-integral types. We match them to suggest an explicit cast.
28 // Match expressions like `a *= b` and `a /= b` where `a` has type
29 // `absl::Duration` and `b` is not of a built-in type.
34 0, expr(hasType(cxxRecordDecl(hasName("::absl::Duration"))))),
35 hasArgument(1, expr().bind("arg")),
37 hasParent(functionTemplateDecl()),
38 unless(hasTemplateArgument(0, refersToType(builtinType()))),
39 hasAnyName("operator*=", "operator/="))))
43 // Match expressions like `a.operator*=(b)` and `a.operator/=(b)` where `a`
44 // has type `absl::Duration` and `b` is not of a built-in type.
48 ofClass(cxxRecordDecl(hasName("::absl::Duration"))),
49 hasParent(functionTemplateDecl()),
50 unless(hasTemplateArgument(0, refersToType(builtinType()))),
51 hasAnyName("operator*=", "operator/="))),
52 argumentCountIs(1), hasArgument(0, expr().bind("arg")))
56 // Match expressions like `a * b`, `a / b`, `operator*(a, b)`, and
57 // `operator/(a, b)` where `a` has type `absl::Duration` and `b` is not of a
60 callExpr(callee(functionDecl(
61 hasParent(functionTemplateDecl()),
62 unless(hasTemplateArgument(0, refersToType(builtinType()))),
63 hasAnyName("::absl::operator*", "::absl::operator/"))),
65 hasArgument(0, expr(hasType(
66 cxxRecordDecl(hasName("::absl::Duration"))))),
67 hasArgument(1, expr().bind("arg")))
71 // Match expressions like `a * b` and `operator*(a, b)` where `a` is not of a
72 // built-in type and `b` has type `absl::Duration`.
74 callExpr(callee(functionDecl(
75 hasParent(functionTemplateDecl()),
76 unless(hasTemplateArgument(0, refersToType(builtinType()))),
77 hasName("::absl::operator*"))),
78 argumentCountIs(2), hasArgument(0, expr().bind("arg")),
79 hasArgument(1, expr(hasType(
80 cxxRecordDecl(hasName("::absl::Duration"))))))
84 // For the factory functions, we match only the non-templated overloads that
85 // take an `int64_t` parameter. Within these calls, we care about implicit
86 // casts through a user defined conversion to `int64_t`.
88 // The factory functions will be updated to be templated and SFINAE on whether
89 // the template parameter is an integral type. This complements the already
90 // existing templated overloads that only accept floating point types.
93 // `absl::Nanoseconds(x)`
94 // `absl::Microseconds(x)`
95 // `absl::Milliseconds(x)`
99 // where `x` is not of a built-in type.
101 traverse(TK_AsIs
, implicitCastExpr(
102 anyOf(hasCastKind(CK_UserDefinedConversion
),
103 has(implicitCastExpr(
104 hasCastKind(CK_UserDefinedConversion
)))),
107 DurationFactoryFunction(),
108 unless(hasParent(functionTemplateDecl())))),
109 hasArgument(0, expr().bind("arg")))))
114 void UpgradeDurationConversionsCheck::check(
115 const MatchFinder::MatchResult
&Result
) {
116 const llvm::StringRef Message
=
117 "implicit conversion to 'int64_t' is deprecated in this context; use an "
118 "explicit cast instead";
120 TraversalKindScope
RAII(*Result
.Context
, TK_AsIs
);
122 const auto *ArgExpr
= Result
.Nodes
.getNodeAs
<Expr
>("arg");
123 SourceLocation Loc
= ArgExpr
->getBeginLoc();
125 const auto *OuterExpr
= Result
.Nodes
.getNodeAs
<Expr
>("OuterExpr");
127 if (!match(isInTemplateInstantiation(), *OuterExpr
, *Result
.Context
)
129 if (MatchedTemplateLocations
.count(Loc
) == 0) {
130 // For each location matched in a template instantiation, we check if the
131 // location can also be found in `MatchedTemplateLocations`. If it is not
132 // found, that means the expression did not create a match without the
133 // instantiation and depends on template parameters. A manual fix is
134 // probably required so we provide only a warning.
140 // We gather source locations from template matches not in template
141 // instantiations for future matches.
142 internal::Matcher
<Stmt
> IsInsideTemplate
=
143 hasAncestor(decl(anyOf(classTemplateDecl(), functionTemplateDecl())));
144 if (!match(IsInsideTemplate
, *ArgExpr
, *Result
.Context
).empty())
145 MatchedTemplateLocations
.insert(Loc
);
147 DiagnosticBuilder Diag
= diag(Loc
, Message
);
148 CharSourceRange SourceRange
= Lexer::makeFileCharRange(
149 CharSourceRange::getTokenRange(ArgExpr
->getSourceRange()),
150 *Result
.SourceManager
, Result
.Context
->getLangOpts());
151 if (SourceRange
.isInvalid())
152 // An invalid source range likely means we are inside a macro body. A manual
153 // fix is likely needed so we do not create a fix-it hint.
156 Diag
<< FixItHint::CreateInsertion(SourceRange
.getBegin(),
157 "static_cast<int64_t>(")
158 << FixItHint::CreateInsertion(SourceRange
.getEnd(), ")");
161 } // namespace clang::tidy::abseil