xref: /llvm-project/clang-tools-extra/clang-tidy/abseil/UpgradeDurationConversionsCheck.cpp (revision 11a411a49b62c129bba551df4587dd446fcdc660)
1 //===--- UpgradeDurationConversionsCheck.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 "UpgradeDurationConversionsCheck.h"
10 #include "DurationRewriter.h"
11 #include "clang/AST/ASTContext.h"
12 #include "clang/ASTMatchers/ASTMatchFinder.h"
13 #include "clang/Lex/Lexer.h"
14 
15 using namespace clang::ast_matchers;
16 
17 namespace clang::tidy::abseil {
18 
registerMatchers(MatchFinder * Finder)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
23   // `int64_t`.
24   //
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.
27 
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.
30   Finder->addMatcher(
31       cxxOperatorCallExpr(
32           argumentCountIs(2),
33           hasArgument(
34               0, expr(hasType(cxxRecordDecl(hasName("::absl::Duration"))))),
35           hasArgument(1, expr().bind("arg")),
36           callee(functionDecl(
37               hasParent(functionTemplateDecl()),
38               unless(hasTemplateArgument(0, refersToType(builtinType()))),
39               hasAnyName("operator*=", "operator/="))))
40           .bind("OuterExpr"),
41       this);
42 
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.
45   Finder->addMatcher(
46       cxxMemberCallExpr(
47           callee(cxxMethodDecl(
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")))
53           .bind("OuterExpr"),
54       this);
55 
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
58   // built-in type.
59   Finder->addMatcher(
60       callExpr(callee(functionDecl(
61                    hasParent(functionTemplateDecl()),
62                    unless(hasTemplateArgument(0, refersToType(builtinType()))),
63                    hasAnyName("::absl::operator*", "::absl::operator/"))),
64                argumentCountIs(2),
65                hasArgument(0, expr(hasType(
66                                   cxxRecordDecl(hasName("::absl::Duration"))))),
67                hasArgument(1, expr().bind("arg")))
68           .bind("OuterExpr"),
69       this);
70 
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`.
73   Finder->addMatcher(
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"))))))
81           .bind("OuterExpr"),
82       this);
83 
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`.
87   //
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.
91 
92   // Match calls like:
93   //   `absl::Nanoseconds(x)`
94   //   `absl::Microseconds(x)`
95   //   `absl::Milliseconds(x)`
96   //   `absl::Seconds(x)`
97   //   `absl::Minutes(x)`
98   //   `absl::Hours(x)`
99   // where `x` is not of a built-in type.
100   Finder->addMatcher(
101       traverse(TK_AsIs, implicitCastExpr(
102                             anyOf(hasCastKind(CK_UserDefinedConversion),
103                                   has(implicitCastExpr(
104                                       hasCastKind(CK_UserDefinedConversion)))),
105                             hasParent(callExpr(
106                                 callee(functionDecl(
107                                     DurationFactoryFunction(),
108                                     unless(hasParent(functionTemplateDecl())))),
109                                 hasArgument(0, expr().bind("arg")))))
110                             .bind("OuterExpr")),
111       this);
112 }
113 
check(const MatchFinder::MatchResult & Result)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";
119 
120   TraversalKindScope RAII(*Result.Context, TK_AsIs);
121 
122   const auto *ArgExpr = Result.Nodes.getNodeAs<Expr>("arg");
123   SourceLocation Loc = ArgExpr->getBeginLoc();
124 
125   const auto *OuterExpr = Result.Nodes.getNodeAs<Expr>("OuterExpr");
126 
127   if (!match(isInTemplateInstantiation(), *OuterExpr, *Result.Context)
128            .empty()) {
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.
135       diag(Loc, Message);
136     }
137     return;
138   }
139 
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);
146 
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.
154     return;
155 
156   Diag << FixItHint::CreateInsertion(SourceRange.getBegin(),
157                                      "static_cast<int64_t>(")
158        << FixItHint::CreateInsertion(SourceRange.getEnd(), ")");
159 }
160 
161 } // namespace clang::tidy::abseil
162