Skip to content

Commit 5bbe501

Browse files
committed
[clang-tidy] Warn on functional C-style casts
The google-readability-casting check is meant to be on par with cpplint's readability/casting check, according to the documentation. However it currently does not diagnose functional casts, like: float x = 1.5F; int y = int(x); This is detected by cpplint, however, and the guidelines are clear that such a cast is only allowed when the type is a class type (constructor call): > You may use cast formats like `T(x)` only when `T` is a class type. Therefore, update the clang-tidy check to check this case. Differential Revision: https://reviews.llvm.org/D114427
1 parent 5047e3a commit 5bbe501

File tree

3 files changed

+94
-14
lines changed

3 files changed

+94
-14
lines changed

clang-tools-extra/clang-tidy/google/AvoidCStyleCastsCheck.cpp

Lines changed: 40 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@ void AvoidCStyleCastsCheck::registerMatchers(
3131
unless(isInTemplateInstantiation()))
3232
.bind("cast"),
3333
this);
34+
Finder->addMatcher(
35+
cxxFunctionalCastExpr(unless(hasDescendant(cxxConstructExpr())),
36+
unless(hasDescendant(initListExpr())))
37+
.bind("cast"),
38+
this);
3439
}
3540

3641
static bool needsConstCast(QualType SourceType, QualType DestType) {
@@ -55,8 +60,39 @@ static bool pointedUnqualifiedTypesAreEqual(QualType T1, QualType T2) {
5560
return T1.getUnqualifiedType() == T2.getUnqualifiedType();
5661
}
5762

63+
static clang::CharSourceRange getReplaceRange(const ExplicitCastExpr *Expr) {
64+
if (const auto *CastExpr = dyn_cast<CStyleCastExpr>(Expr)) {
65+
return CharSourceRange::getCharRange(
66+
CastExpr->getLParenLoc(),
67+
CastExpr->getSubExprAsWritten()->getBeginLoc());
68+
} else if (const auto *CastExpr = dyn_cast<CXXFunctionalCastExpr>(Expr)) {
69+
return CharSourceRange::getCharRange(CastExpr->getBeginLoc(),
70+
CastExpr->getLParenLoc());
71+
} else
72+
llvm_unreachable("Unsupported CastExpr");
73+
}
74+
75+
static StringRef getDestTypeString(const SourceManager &SM,
76+
const LangOptions &LangOpts,
77+
const ExplicitCastExpr *Expr) {
78+
SourceLocation BeginLoc;
79+
SourceLocation EndLoc;
80+
81+
if (const auto *CastExpr = dyn_cast<CStyleCastExpr>(Expr)) {
82+
BeginLoc = CastExpr->getLParenLoc().getLocWithOffset(1);
83+
EndLoc = CastExpr->getRParenLoc().getLocWithOffset(-1);
84+
} else if (const auto *CastExpr = dyn_cast<CXXFunctionalCastExpr>(Expr)) {
85+
BeginLoc = CastExpr->getBeginLoc();
86+
EndLoc = CastExpr->getLParenLoc().getLocWithOffset(-1);
87+
} else
88+
llvm_unreachable("Unsupported CastExpr");
89+
90+
return Lexer::getSourceText(CharSourceRange::getTokenRange(BeginLoc, EndLoc),
91+
SM, LangOpts);
92+
}
93+
5894
void AvoidCStyleCastsCheck::check(const MatchFinder::MatchResult &Result) {
59-
const auto *CastExpr = Result.Nodes.getNodeAs<CStyleCastExpr>("cast");
95+
const auto *CastExpr = Result.Nodes.getNodeAs<ExplicitCastExpr>("cast");
6096

6197
// Ignore casts in macros.
6298
if (CastExpr->getExprLoc().isMacroID())
@@ -80,8 +116,7 @@ void AvoidCStyleCastsCheck::check(const MatchFinder::MatchResult &Result) {
80116
const QualType SourceType = SourceTypeAsWritten.getCanonicalType();
81117
const QualType DestType = DestTypeAsWritten.getCanonicalType();
82118

83-
auto ReplaceRange = CharSourceRange::getCharRange(
84-
CastExpr->getLParenLoc(), CastExpr->getSubExprAsWritten()->getBeginLoc());
119+
CharSourceRange ReplaceRange = getReplaceRange(CastExpr);
85120

86121
bool FnToFnCast =
87122
IsFunction(SourceTypeAsWritten) && IsFunction(DestTypeAsWritten);
@@ -124,18 +159,14 @@ void AvoidCStyleCastsCheck::check(const MatchFinder::MatchResult &Result) {
124159

125160
// Leave type spelling exactly as it was (unlike
126161
// getTypeAsWritten().getAsString() which would spell enum types 'enum X').
127-
StringRef DestTypeString =
128-
Lexer::getSourceText(CharSourceRange::getTokenRange(
129-
CastExpr->getLParenLoc().getLocWithOffset(1),
130-
CastExpr->getRParenLoc().getLocWithOffset(-1)),
131-
SM, getLangOpts());
162+
StringRef DestTypeString = getDestTypeString(SM, getLangOpts(), CastExpr);
132163

133164
auto Diag =
134165
diag(CastExpr->getBeginLoc(), "C-style casts are discouraged; use %0");
135166

136167
auto ReplaceWithCast = [&](std::string CastText) {
137168
const Expr *SubExpr = CastExpr->getSubExprAsWritten()->IgnoreImpCasts();
138-
if (!isa<ParenExpr>(SubExpr)) {
169+
if (!isa<ParenExpr>(SubExpr) && !isa<CXXFunctionalCastExpr>(CastExpr)) {
139170
CastText.push_back('(');
140171
Diag << FixItHint::CreateInsertion(
141172
Lexer::getLocForEndOfToken(SubExpr->getEndLoc(), 0, SM,

clang-tools-extra/docs/ReleaseNotes.rst

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,9 +133,12 @@ New check aliases
133133
Changes in existing checks
134134
^^^^^^^^^^^^^^^^^^^^^^^^^^
135135

136-
- Removed default setting `cppcoreguidelines-explicit-virtual-functions.IgnoreDestructors = "true"`,
136+
- Removed default setting ``cppcoreguidelines-explicit-virtual-functions.IgnoreDestructors = "true"``,
137137
to match the current state of the C++ Core Guidelines.
138138

139+
- Updated :doc:`google-readability-casting
140+
<clang-tidy/checks/google-readability-casting>` to diagnose and fix functional
141+
casts, to achieve feature parity with the corresponding ``cpplint.py`` check.
139142

140143
Removed checks
141144
^^^^^^^^^^^^^^

clang-tools-extra/test/clang-tidy/checkers/google-readability-casting.cpp

Lines changed: 50 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -143,11 +143,10 @@ void f(int a, double b, const char *cpc, const void *cpv, X *pX) {
143143
// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: redundant cast to the same type
144144
// CHECK-FIXES: {{^}} kZero;
145145

146-
int b2 = int(b);
147-
int b3 = static_cast<double>(b);
148-
int b4 = b;
146+
int b2 = static_cast<double>(b);
147+
int b3 = b;
149148
double aa = a;
150-
(void)b2;
149+
(void)aa;
151150
return (void)g();
152151
}
153152

@@ -321,3 +320,50 @@ void conversions() {
321320
// CHECK-MESSAGES: :[[@LINE-1]]:13: warning: C-style casts are discouraged; use constructor call syntax [
322321
// CHECK-FIXES: auto s6 = S(cr);
323322
}
323+
324+
template <class T>
325+
T functional_cast_template_used_by_class(float i) {
326+
return T(i);
327+
}
328+
329+
template <class T>
330+
T functional_cast_template_used_by_int(float i) {
331+
return T(i);
332+
// CHECK-MESSAGES: :[[@LINE-1]]:10: warning: C-style casts are discouraged; use static_cast
333+
// CHECK-FIXES: return static_cast<T>(i);
334+
}
335+
336+
struct S2 {
337+
S2(float);
338+
};
339+
using T = S2;
340+
using U = S2 &;
341+
342+
void functional_casts() {
343+
float x = 1.5F;
344+
auto y = int(x);
345+
// CHECK-MESSAGES: :[[@LINE-1]]:12: warning: C-style casts are discouraged; use static_cast
346+
// CHECK-FIXES: auto y = static_cast<int>(x);
347+
348+
#pragma clang diagnostic push
349+
#pragma clang diagnostic ignored "-Wc++11-narrowing"
350+
// This if fine, compiler will warn about implicit conversions with brace initialization
351+
auto z = int{x};
352+
#pragma clang diagnostic pop
353+
354+
// Functional casts are allowed if S is of class type
355+
const char *str = "foo";
356+
auto s = S(str);
357+
358+
// Functional casts in template functions
359+
functional_cast_template_used_by_class<S2>(x);
360+
functional_cast_template_used_by_int<int>(x);
361+
362+
// New expressions are not functional casts
363+
auto w = new int(x);
364+
365+
// Typedefs
366+
S2 t = T(x); // OK, constructor call
367+
S2 u = U(x); // NOK, it's a reinterpret_cast in disguise
368+
// CHECK-MESSAGES: :[[@LINE-1]]:10: warning: C-style casts are discouraged; use static_cast/const_cast/reinterpret_cast
369+
}

0 commit comments

Comments
 (0)