Skip to content

Commit 1ad1f98

Browse files
authored
[clang-tidy] Add readability-redundant-casting check (#70595)
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. Closes #67534
1 parent 6a433d7 commit 1ad1f98

File tree

9 files changed

+562
-0
lines changed

9 files changed

+562
-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
@@ -35,6 +35,7 @@ add_clang_library(clangTidyReadabilityModule
3535
QualifiedAutoCheck.cpp
3636
ReadabilityTidyModule.cpp
3737
RedundantAccessSpecifiersCheck.cpp
38+
RedundantCastingCheck.cpp
3839
RedundantControlFlowCheck.cpp
3940
RedundantDeclarationCheck.cpp
4041
RedundantFunctionPtrDereferenceCheck.cpp

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
#include "OperatorsRepresentationCheck.h"
3939
#include "QualifiedAutoCheck.h"
4040
#include "RedundantAccessSpecifiersCheck.h"
41+
#include "RedundantCastingCheck.h"
4142
#include "RedundantControlFlowCheck.h"
4243
#include "RedundantDeclarationCheck.h"
4344
#include "RedundantFunctionPtrDereferenceCheck.h"
@@ -117,6 +118,8 @@ class ReadabilityModule : public ClangTidyModule {
117118
"readability-qualified-auto");
118119
CheckFactories.registerCheck<RedundantAccessSpecifiersCheck>(
119120
"readability-redundant-access-specifiers");
121+
CheckFactories.registerCheck<RedundantCastingCheck>(
122+
"readability-redundant-casting");
120123
CheckFactories.registerCheck<RedundantFunctionPtrDereferenceCheck>(
121124
"readability-redundant-function-ptr-dereference");
122125
CheckFactories.registerCheck<RedundantMemberInitCheck>(
Lines changed: 252 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,252 @@
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 areTypesEqual(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 areTypesEqual(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 areTypesEqual(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 || areTypesEqual(TypeS.getLocalUnqualifiedType(),
49+
TypeD.getLocalUnqualifiedType());
50+
}
51+
52+
static bool areBinaryOperatorOperandsTypesEqualToOperatorResultType(
53+
const Expr *E, bool IgnoreTypeAliases) {
54+
if (!E)
55+
return true;
56+
const Expr *WithoutImplicitAndParen = E->IgnoreParenImpCasts();
57+
if (!WithoutImplicitAndParen)
58+
return true;
59+
if (const auto *B = dyn_cast<BinaryOperator>(WithoutImplicitAndParen)) {
60+
const QualType Type = WithoutImplicitAndParen->getType();
61+
if (Type.isNull())
62+
return true;
63+
64+
const QualType NonReferenceType = Type.getNonReferenceType();
65+
const QualType LHSType = B->getLHS()->IgnoreImplicit()->getType();
66+
if (LHSType.isNull() || !areTypesEqual(LHSType.getNonReferenceType(),
67+
NonReferenceType, IgnoreTypeAliases))
68+
return false;
69+
const QualType RHSType = B->getRHS()->IgnoreImplicit()->getType();
70+
if (RHSType.isNull() || !areTypesEqual(RHSType.getNonReferenceType(),
71+
NonReferenceType, IgnoreTypeAliases))
72+
return false;
73+
}
74+
return true;
75+
}
76+
77+
static const Decl *getSourceExprDecl(const Expr *SourceExpr) {
78+
const Expr *CleanSourceExpr = SourceExpr->IgnoreParenImpCasts();
79+
if (const auto *E = dyn_cast<DeclRefExpr>(CleanSourceExpr)) {
80+
return E->getDecl();
81+
}
82+
83+
if (const auto *E = dyn_cast<CallExpr>(CleanSourceExpr)) {
84+
return E->getCalleeDecl();
85+
}
86+
87+
if (const auto *E = dyn_cast<MemberExpr>(CleanSourceExpr)) {
88+
return E->getMemberDecl();
89+
}
90+
return nullptr;
91+
}
92+
93+
RedundantCastingCheck::RedundantCastingCheck(StringRef Name,
94+
ClangTidyContext *Context)
95+
: ClangTidyCheck(Name, Context),
96+
IgnoreMacros(Options.getLocalOrGlobal("IgnoreMacros", true)),
97+
IgnoreTypeAliases(Options.getLocalOrGlobal("IgnoreTypeAliases", false)) {}
98+
99+
void RedundantCastingCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
100+
Options.store(Opts, "IgnoreMacros", IgnoreMacros);
101+
Options.store(Opts, "IgnoreTypeAliases", IgnoreTypeAliases);
102+
}
103+
104+
void RedundantCastingCheck::registerMatchers(MatchFinder *Finder) {
105+
auto SimpleType = qualType(hasCanonicalType(
106+
qualType(anyOf(builtinType(), references(builtinType()),
107+
references(pointsTo(qualType())), pointsTo(qualType())))));
108+
109+
auto BitfieldMemberExpr = memberExpr(member(fieldDecl(isBitField())));
110+
111+
Finder->addMatcher(
112+
explicitCastExpr(
113+
unless(hasCastKind(CK_ConstructorConversion)),
114+
unless(hasCastKind(CK_UserDefinedConversion)),
115+
unless(cxxFunctionalCastExpr(hasDestinationType(unless(SimpleType)))),
116+
117+
hasDestinationType(qualType().bind("dstType")),
118+
hasSourceExpression(anyOf(
119+
expr(unless(initListExpr()), unless(BitfieldMemberExpr),
120+
hasType(qualType().bind("srcType")))
121+
.bind("source"),
122+
initListExpr(unless(hasInit(1, expr())),
123+
hasInit(0, expr(unless(BitfieldMemberExpr),
124+
hasType(qualType().bind("srcType")))
125+
.bind("source"))))))
126+
.bind("cast"),
127+
this);
128+
}
129+
130+
void RedundantCastingCheck::check(const MatchFinder::MatchResult &Result) {
131+
const auto *SourceExpr = Result.Nodes.getNodeAs<Expr>("source");
132+
auto TypeD = *Result.Nodes.getNodeAs<QualType>("dstType");
133+
134+
if (SourceExpr->getValueKind() == VK_LValue &&
135+
TypeD.getCanonicalType()->isRValueReferenceType())
136+
return;
137+
138+
const auto TypeS =
139+
Result.Nodes.getNodeAs<QualType>("srcType")->getNonReferenceType();
140+
TypeD = TypeD.getNonReferenceType();
141+
142+
if (!areTypesEqual(TypeS, TypeD, IgnoreTypeAliases))
143+
return;
144+
145+
if (!areBinaryOperatorOperandsTypesEqualToOperatorResultType(
146+
SourceExpr, IgnoreTypeAliases))
147+
return;
148+
149+
const auto *CastExpr = Result.Nodes.getNodeAs<ExplicitCastExpr>("cast");
150+
if (IgnoreMacros &&
151+
(CastExpr->getBeginLoc().isMacroID() ||
152+
CastExpr->getEndLoc().isMacroID() || CastExpr->getExprLoc().isMacroID()))
153+
return;
154+
155+
{
156+
auto Diag = diag(CastExpr->getExprLoc(),
157+
"redundant explicit casting to the same type %0 as the "
158+
"sub-expression, remove this casting");
159+
Diag << TypeD;
160+
161+
const SourceManager &SM = *Result.SourceManager;
162+
const SourceLocation SourceExprBegin =
163+
SM.getExpansionLoc(SourceExpr->getBeginLoc());
164+
const SourceLocation SourceExprEnd =
165+
SM.getExpansionLoc(SourceExpr->getEndLoc());
166+
167+
if (SourceExprBegin != CastExpr->getBeginLoc())
168+
Diag << FixItHint::CreateRemoval(SourceRange(
169+
CastExpr->getBeginLoc(), SourceExprBegin.getLocWithOffset(-1)));
170+
171+
const SourceLocation NextToken = Lexer::getLocForEndOfToken(
172+
SourceExprEnd, 0U, SM, Result.Context->getLangOpts());
173+
174+
if (SourceExprEnd != CastExpr->getEndLoc()) {
175+
Diag << FixItHint::CreateRemoval(
176+
SourceRange(NextToken, CastExpr->getEndLoc()));
177+
}
178+
179+
if (utils::fixit::areParensNeededForStatement(*SourceExpr)) {
180+
Diag << FixItHint::CreateInsertion(SourceExprBegin, "(")
181+
<< FixItHint::CreateInsertion(NextToken, ")");
182+
}
183+
}
184+
185+
const auto *SourceExprDecl = getSourceExprDecl(SourceExpr);
186+
if (!SourceExprDecl)
187+
return;
188+
189+
if (const auto *D = dyn_cast<CXXConstructorDecl>(SourceExprDecl)) {
190+
diag(D->getLocation(),
191+
"source type originates from the invocation of this constructor",
192+
DiagnosticIDs::Note);
193+
return;
194+
}
195+
196+
if (const auto *D = dyn_cast<FunctionDecl>(SourceExprDecl)) {
197+
diag(D->getLocation(),
198+
"source type originates from the invocation of this "
199+
"%select{function|method}0",
200+
DiagnosticIDs::Note)
201+
<< isa<CXXMethodDecl>(D) << D->getReturnTypeSourceRange();
202+
return;
203+
}
204+
205+
if (const auto *D = dyn_cast<FieldDecl>(SourceExprDecl)) {
206+
diag(D->getLocation(),
207+
"source type originates from referencing this member",
208+
DiagnosticIDs::Note)
209+
<< SourceRange(D->getTypeSpecStartLoc(), D->getTypeSpecEndLoc());
210+
return;
211+
}
212+
213+
if (const auto *D = dyn_cast<ParmVarDecl>(SourceExprDecl)) {
214+
diag(D->getLocation(),
215+
"source type originates from referencing this parameter",
216+
DiagnosticIDs::Note)
217+
<< SourceRange(D->getTypeSpecStartLoc(), D->getTypeSpecEndLoc());
218+
return;
219+
}
220+
221+
if (const auto *D = dyn_cast<VarDecl>(SourceExprDecl)) {
222+
diag(D->getLocation(),
223+
"source type originates from referencing this variable",
224+
DiagnosticIDs::Note)
225+
<< SourceRange(D->getTypeSpecStartLoc(), D->getTypeSpecEndLoc());
226+
return;
227+
}
228+
229+
if (const auto *D = dyn_cast<EnumConstantDecl>(SourceExprDecl)) {
230+
diag(D->getLocation(),
231+
"source type originates from referencing this enum constant",
232+
DiagnosticIDs::Note);
233+
return;
234+
}
235+
236+
if (const auto *D = dyn_cast<BindingDecl>(SourceExprDecl)) {
237+
diag(D->getLocation(),
238+
"source type originates from referencing this bound variable",
239+
DiagnosticIDs::Note);
240+
return;
241+
}
242+
243+
if (const auto *D = dyn_cast<NonTypeTemplateParmDecl>(SourceExprDecl)) {
244+
diag(D->getLocation(),
245+
"source type originates from referencing this non-type template "
246+
"parameter",
247+
DiagnosticIDs::Note);
248+
return;
249+
}
250+
}
251+
252+
} // 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.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ std::string formatDereference(const Expr &ExprNode, const ASTContext &Context);
5151
// \brief Checks whatever a expression require extra () to be always used in
5252
// safe way in any other expression.
5353
bool areParensNeededForStatement(const Stmt &Node);
54+
5455
} // namespace clang::tidy::utils::fixit
5556

5657
#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
@@ -235,6 +235,12 @@ New checks
235235
Finds return statements with ``void`` values used within functions with
236236
``void`` result types.
237237

238+
- New :doc:`readability-redundant-casting
239+
<clang-tidy/checks/readability/redundant-casting>` check.
240+
241+
Detects explicit type casting operations that involve the same source and
242+
destination types, and subsequently recommend their removal.
243+
238244
- New :doc:`readability-reference-to-constructed-temporary
239245
<clang-tidy/checks/readability/reference-to-constructed-temporary>` check.
240246

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,7 @@ Clang-Tidy Checks
365365
:doc:`readability-operators-representation <readability/operators-representation>`, "Yes"
366366
:doc:`readability-qualified-auto <readability/qualified-auto>`, "Yes"
367367
:doc:`readability-redundant-access-specifiers <readability/redundant-access-specifiers>`, "Yes"
368+
:doc:`readability-redundant-casting <readability/redundant-casting>`, "Yes"
368369
:doc:`readability-redundant-control-flow <readability/redundant-control-flow>`, "Yes"
369370
:doc:`readability-redundant-declaration <readability/redundant-declaration>`, "Yes"
370371
: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)