clang-tools 20.0.0git
UpgradeDurationConversionsCheck.cpp
Go to the documentation of this file.
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
10#include "DurationRewriter.h"
11#include "clang/AST/ASTContext.h"
12#include "clang/ASTMatchers/ASTMatchFinder.h"
13#include "clang/Lex/Lexer.h"
14
15using namespace clang::ast_matchers;
16
17namespace clang::tidy::abseil {
18
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
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
SourceLocation Loc
DiagnosticBuilder diag(SourceLocation Loc, StringRef Description, DiagnosticIDs::Level Level=DiagnosticIDs::Warning)
Add a diagnostic with the check's name.
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
ClangTidyChecks that register ASTMatchers should do the actual work in here.
void registerMatchers(ast_matchers::MatchFinder *Finder) override
Override this to register AST matchers with Finder.