Skip to content

Commit e17c6f3

Browse files
committed
[clang-tidy] Add readability-redundant-casting check
Detects explicit type casting operations that involve the same source and destination types, and subsequently recommend their removal. Covers a range of explicit casting operations. Its primary objective is to enhance code readability and maintainability by eliminating unnecessary type casting.
1 parent 497b2eb commit e17c6f3

File tree

10 files changed

+537
-0
lines changed

10 files changed

+537
-0
lines changed

clang-tools-extra/clang-tidy/readability/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ add_clang_library(clangTidyReadabilityModule
3333
QualifiedAutoCheck.cpp
3434
ReadabilityTidyModule.cpp
3535
RedundantAccessSpecifiersCheck.cpp
36+
RedundantCastingCheck.cpp
3637
RedundantControlFlowCheck.cpp
3738
RedundantDeclarationCheck.cpp
3839
RedundantFunctionPtrDereferenceCheck.cpp

clang-tools-extra/clang-tidy/readability/ReadabilityTidyModule.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
#include "OperatorsRepresentationCheck.h"
3737
#include "QualifiedAutoCheck.h"
3838
#include "RedundantAccessSpecifiersCheck.h"
39+
#include "RedundantCastingCheck.h"
3940
#include "RedundantControlFlowCheck.h"
4041
#include "RedundantDeclarationCheck.h"
4142
#include "RedundantFunctionPtrDereferenceCheck.h"
@@ -111,6 +112,8 @@ class ReadabilityModule : public ClangTidyModule {
111112
"readability-qualified-auto");
112113
CheckFactories.registerCheck<RedundantAccessSpecifiersCheck>(
113114
"readability-redundant-access-specifiers");
115+
CheckFactories.registerCheck<RedundantCastingCheck>(
116+
"readability-redundant-casting");
114117
CheckFactories.registerCheck<RedundantFunctionPtrDereferenceCheck>(
115118
"readability-redundant-function-ptr-dereference");
116119
CheckFactories.registerCheck<RedundantMemberInitCheck>(
Lines changed: 242 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,242 @@
1+
//===--- RedundantCastingCheck.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 "RedundantCastingCheck.h"
10+
#include "../utils/FixItHintUtils.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::readability {
18+
19+
static bool areTypesEquals(QualType S, QualType D) {
20+
if (S == D)
21+
return true;
22+
23+
const auto *TS = S->getAs<TypedefType>();
24+
const auto *TD = D->getAs<TypedefType>();
25+
if (TS != TD)
26+
return false;
27+
28+
QualType PtrS = S->getPointeeType();
29+
QualType PtrD = D->getPointeeType();
30+
31+
if (!PtrS.isNull() && !PtrD.isNull())
32+
return areTypesEquals(PtrS, PtrD);
33+
34+
const DeducedType *DT = S->getContainedDeducedType();
35+
if (DT && DT->isDeduced())
36+
return D == DT->getDeducedType();
37+
38+
return false;
39+
}
40+
41+
static bool areTypesEquals(QualType TypeS, QualType TypeD,
42+
bool IgnoreTypeAliases) {
43+
const QualType CTypeS = TypeS.getCanonicalType();
44+
const QualType CTypeD = TypeD.getCanonicalType();
45+
if (CTypeS != CTypeD)
46+
return false;
47+
48+
return IgnoreTypeAliases || areTypesEquals(TypeS.getLocalUnqualifiedType(),
49+
TypeD.getLocalUnqualifiedType());
50+
}
51+
52+
static bool areBinaryOperatorOperandsTypesEqual(const Expr *E,
53+
bool IgnoreTypeAliases) {
54+
if (!E)
55+
return false;
56+
const Expr *WithoutImplicitAndParen = E->IgnoreParenImpCasts();
57+
if (!WithoutImplicitAndParen)
58+
return false;
59+
if (const auto *B = dyn_cast<BinaryOperator>(WithoutImplicitAndParen)) {
60+
const QualType NonReferenceType =
61+
WithoutImplicitAndParen->getType().getNonReferenceType();
62+
if (!areTypesEquals(
63+
B->getLHS()->IgnoreImplicit()->getType().getNonReferenceType(),
64+
NonReferenceType, IgnoreTypeAliases))
65+
return true;
66+
if (!areTypesEquals(
67+
B->getRHS()->IgnoreImplicit()->getType().getNonReferenceType(),
68+
NonReferenceType, IgnoreTypeAliases))
69+
return true;
70+
}
71+
return false;
72+
}
73+
74+
static const Decl *getSourceExprDecl(const Expr *SourceExpr) {
75+
const Expr *CleanSourceExpr = SourceExpr->IgnoreParenImpCasts();
76+
if (const auto *E = dyn_cast<DeclRefExpr>(CleanSourceExpr)) {
77+
return E->getDecl();
78+
}
79+
80+
if (const auto *E = dyn_cast<CallExpr>(CleanSourceExpr)) {
81+
return E->getCalleeDecl();
82+
}
83+
84+
if (const auto *E = dyn_cast<MemberExpr>(CleanSourceExpr)) {
85+
return E->getMemberDecl();
86+
}
87+
return nullptr;
88+
}
89+
90+
RedundantCastingCheck::RedundantCastingCheck(StringRef Name,
91+
ClangTidyContext *Context)
92+
: ClangTidyCheck(Name, Context),
93+
IgnoreMacros(Options.getLocalOrGlobal("IgnoreMacros", true)),
94+
IgnoreTypeAliases(Options.getLocalOrGlobal("IgnoreTypeAliases", false)) {}
95+
96+
void RedundantCastingCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
97+
Options.store(Opts, "IgnoreMacros", IgnoreMacros);
98+
Options.store(Opts, "IgnoreTypeAliases", IgnoreTypeAliases);
99+
}
100+
101+
void RedundantCastingCheck::registerMatchers(MatchFinder *Finder) {
102+
103+
auto SimpleType = qualType(hasCanonicalType(
104+
qualType(anyOf(builtinType(), references(builtinType()),
105+
references(pointsTo(qualType())), pointsTo(qualType())))));
106+
107+
auto BitfieldMemberExpr = memberExpr(member(fieldDecl(isBitField())));
108+
109+
Finder->addMatcher(
110+
explicitCastExpr(
111+
unless(hasCastKind(CK_ConstructorConversion)),
112+
unless(hasCastKind(CK_UserDefinedConversion)),
113+
unless(cxxFunctionalCastExpr(hasDestinationType(unless(SimpleType)))),
114+
115+
hasDestinationType(qualType().bind("type2")),
116+
hasSourceExpression(anyOf(
117+
expr(unless(initListExpr()), unless(BitfieldMemberExpr),
118+
hasType(qualType().bind("type1")))
119+
.bind("source"),
120+
initListExpr(unless(hasInit(1, expr())),
121+
hasInit(0, expr(unless(BitfieldMemberExpr),
122+
hasType(qualType().bind("type1")))
123+
.bind("source"))))))
124+
.bind("cast"),
125+
this);
126+
}
127+
128+
void RedundantCastingCheck::check(const MatchFinder::MatchResult &Result) {
129+
const auto *SourceExpr = Result.Nodes.getNodeAs<Expr>("source");
130+
auto TypeD = *Result.Nodes.getNodeAs<QualType>("type2");
131+
132+
if (SourceExpr->getValueKind() == VK_LValue &&
133+
TypeD.getCanonicalType()->isRValueReferenceType())
134+
return;
135+
136+
const auto TypeS =
137+
Result.Nodes.getNodeAs<QualType>("type1")->getNonReferenceType();
138+
TypeD = TypeD.getNonReferenceType();
139+
140+
if (!areTypesEquals(TypeS, TypeD, IgnoreTypeAliases))
141+
return;
142+
143+
if (areBinaryOperatorOperandsTypesEqual(SourceExpr, IgnoreTypeAliases))
144+
return;
145+
146+
const auto *CastExpr = Result.Nodes.getNodeAs<ExplicitCastExpr>("cast");
147+
if (IgnoreMacros &&
148+
(CastExpr->getBeginLoc().isMacroID() ||
149+
CastExpr->getEndLoc().isMacroID() || CastExpr->getExprLoc().isMacroID()))
150+
return;
151+
152+
{
153+
auto Diag = diag(CastExpr->getExprLoc(),
154+
"redundant explicit casting to the same type %0 as the "
155+
"sub-expression, remove this casting");
156+
Diag << TypeD;
157+
158+
const SourceManager &SM = *Result.SourceManager;
159+
const SourceLocation SourceExprBegin =
160+
SM.getExpansionLoc(SourceExpr->getBeginLoc());
161+
const SourceLocation SourceExprEnd =
162+
SM.getExpansionLoc(SourceExpr->getEndLoc());
163+
164+
if (SourceExprBegin != CastExpr->getBeginLoc())
165+
Diag << FixItHint::CreateRemoval(SourceRange(
166+
CastExpr->getBeginLoc(), SourceExprBegin.getLocWithOffset(-1)));
167+
168+
const SourceLocation NextToken = Lexer::getLocForEndOfToken(
169+
SourceExprEnd, 0U, SM, Result.Context->getLangOpts());
170+
171+
if (SourceExprEnd != CastExpr->getEndLoc()) {
172+
Diag << FixItHint::CreateRemoval(
173+
SourceRange(NextToken, CastExpr->getEndLoc()));
174+
}
175+
176+
if (utils::fixit::needParens(*SourceExpr)) {
177+
178+
Diag << FixItHint::CreateInsertion(SourceExprBegin, "(")
179+
<< FixItHint::CreateInsertion(NextToken, ")");
180+
}
181+
}
182+
183+
const auto *SourceExprDecl = getSourceExprDecl(SourceExpr);
184+
if (!SourceExprDecl)
185+
return;
186+
187+
if (const auto *D = dyn_cast<CXXConstructorDecl>(SourceExprDecl)) {
188+
diag(D->getLocation(),
189+
"source type originates from the invocation of this constructor",
190+
DiagnosticIDs::Note);
191+
return;
192+
}
193+
194+
if (const auto *D = dyn_cast<FunctionDecl>(SourceExprDecl)) {
195+
diag(D->getLocation(),
196+
"source type originates from the invocation of this "
197+
"%select{function|method}0",
198+
DiagnosticIDs::Note)
199+
<< isa<CXXMethodDecl>(D) << D->getReturnTypeSourceRange();
200+
return;
201+
}
202+
203+
if (const auto *D = dyn_cast<FieldDecl>(SourceExprDecl)) {
204+
diag(D->getLocation(),
205+
"source type originates from referencing this member",
206+
DiagnosticIDs::Note)
207+
<< SourceRange(D->getTypeSpecStartLoc(), D->getTypeSpecEndLoc());
208+
return;
209+
}
210+
211+
if (const auto *D = dyn_cast<ParmVarDecl>(SourceExprDecl)) {
212+
diag(D->getLocation(),
213+
"source type originates from referencing this parameter",
214+
DiagnosticIDs::Note)
215+
<< SourceRange(D->getTypeSpecStartLoc(), D->getTypeSpecEndLoc());
216+
return;
217+
}
218+
219+
if (const auto *D = dyn_cast<VarDecl>(SourceExprDecl)) {
220+
diag(D->getLocation(),
221+
"source type originates from referencing this variable",
222+
DiagnosticIDs::Note)
223+
<< SourceRange(D->getTypeSpecStartLoc(), D->getTypeSpecEndLoc());
224+
return;
225+
}
226+
227+
if (const auto *D = dyn_cast<EnumConstantDecl>(SourceExprDecl)) {
228+
diag(D->getLocation(),
229+
"source type originates from referencing this enum constant",
230+
DiagnosticIDs::Note);
231+
return;
232+
}
233+
234+
if (const auto *D = dyn_cast<BindingDecl>(SourceExprDecl)) {
235+
diag(D->getLocation(),
236+
"source type originates from referencing this bound variable",
237+
DiagnosticIDs::Note);
238+
return;
239+
}
240+
}
241+
242+
} // namespace clang::tidy::readability
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
//===--- RedundantCastingCheck.h - clang-tidy -------------------*- C++ -*-===//
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+
#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_REDUNDANTCASTINGCHECK_H
10+
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_REDUNDANTCASTINGCHECK_H
11+
12+
#include "../ClangTidyCheck.h"
13+
14+
namespace clang::tidy::readability {
15+
16+
/// Detects explicit type casting operations that involve the same source and
17+
/// destination types, and subsequently recommend their removal.
18+
///
19+
/// For the user-facing documentation see:
20+
/// http://clang.llvm.org/extra/clang-tidy/checks/readability/redundant-casting.html
21+
class RedundantCastingCheck : public ClangTidyCheck {
22+
public:
23+
RedundantCastingCheck(StringRef Name, ClangTidyContext *Context);
24+
void registerMatchers(ast_matchers::MatchFinder *Finder) override;
25+
void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
26+
void storeOptions(ClangTidyOptions::OptionMap &Opts) override;
27+
std::optional<TraversalKind> getCheckTraversalKind() const override {
28+
return TK_IgnoreUnlessSpelledInSource;
29+
}
30+
31+
private:
32+
const bool IgnoreMacros;
33+
const bool IgnoreTypeAliases;
34+
};
35+
36+
} // namespace clang::tidy::readability
37+
38+
#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_REDUNDANTCASTINGCHECK_H

clang-tools-extra/clang-tidy/utils/FixItHintUtils.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,11 @@ static bool needParensAfterUnaryOperator(const Expr &ExprNode) {
240240
return false;
241241
}
242242

243+
bool needParens(const Expr &ExprNode) {
244+
return !isa<clang::ParenExpr>(&ExprNode) &&
245+
needParensAfterUnaryOperator(ExprNode);
246+
}
247+
243248
// Format a pointer to an expression: prefix with '*' but simplify
244249
// when it already begins with '&'. Return empty string on failure.
245250
std::string formatDereference(const Expr &ExprNode, const ASTContext &Context) {

clang-tools-extra/clang-tidy/utils/FixItHintUtils.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,10 @@ addQualifierToVarDecl(const VarDecl &Var, const ASTContext &Context,
4747

4848
// \brief Format a pointer to an expression
4949
std::string formatDereference(const Expr &ExprNode, const ASTContext &Context);
50+
51+
// \brief Returns ``true`` if ExprNode ought to be enclosed within parentheses.
52+
bool needParens(const Expr &ExprNode);
53+
5054
} // namespace clang::tidy::utils::fixit
5155

5256
#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_UTILS_FIXITHINTUTILS_H

clang-tools-extra/docs/ReleaseNotes.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,12 @@ New checks
191191
Recommends the smallest possible underlying type for an ``enum`` or ``enum``
192192
class based on the range of its enumerators.
193193

194+
- New :doc:`readability-redundant-casting
195+
<clang-tidy/checks/readability/redundant-casting>` check.
196+
197+
Detects explicit type casting operations that involve the same source and
198+
destination types, and subsequently recommend their removal.
199+
194200
- New :doc:`readability-reference-to-constructed-temporary
195201
<clang-tidy/checks/readability/reference-to-constructed-temporary>` check.
196202

clang-tools-extra/docs/clang-tidy/checks/list.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -359,6 +359,7 @@ Clang-Tidy Checks
359359
:doc:`readability-operators-representation <readability/operators-representation>`, "Yes"
360360
:doc:`readability-qualified-auto <readability/qualified-auto>`, "Yes"
361361
:doc:`readability-redundant-access-specifiers <readability/redundant-access-specifiers>`, "Yes"
362+
:doc:`readability-redundant-casting <readability/redundant-casting>`, "Yes"
362363
:doc:`readability-redundant-control-flow <readability/redundant-control-flow>`, "Yes"
363364
:doc:`readability-redundant-declaration <readability/redundant-declaration>`, "Yes"
364365
:doc:`readability-redundant-function-ptr-dereference <readability/redundant-function-ptr-dereference>`, "Yes"
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
.. title:: clang-tidy - readability-redundant-casting
2+
3+
readability-redundant-casting
4+
=============================
5+
6+
Detects explicit type casting operations that involve the same source and
7+
destination types, and subsequently recommend their removal. Covers a range of
8+
explicit casting operations, including ``static_cast``, ``const_cast``, C-style
9+
casts, and ``reinterpret_cast``. Its primary objective is to enhance code
10+
readability and maintainability by eliminating unnecessary type casting.
11+
12+
.. code-block:: c++
13+
14+
int value = 42;
15+
int result = static_cast<int>(value);
16+
17+
In this example, the ``static_cast<int>(value)`` is redundant, as it performs
18+
a cast from an ``int`` to another ``int``.
19+
20+
Casting operations involving constructor conversions, user-defined conversions,
21+
functional casts, type-dependent casts, casts between distinct type aliases that
22+
refer to the same underlying type, as well as bitfield-related casts and casts
23+
directly from lvalue to rvalue, are all disregarded by the check.
24+
25+
Options
26+
-------
27+
28+
.. option:: IgnoreMacros
29+
30+
If set to `true`, the check will not give warnings inside macros. Default
31+
is `true`.
32+
33+
.. option:: IgnoreTypeAliases
34+
35+
When set to `false`, the check will consider type aliases, and when set to
36+
`true`, it will resolve all type aliases and operate on the underlying
37+
types. Default is `false`.

0 commit comments

Comments
 (0)