Skip to content

Commit 54f57d3

Browse files
committed
[clang] Add a fixit for warn-self-assign if LHS is a field with the same name as parameter on RHS
Add a fix-it for the common case of setters/constructors using parameters with the same name as fields ```lang=c++ struct A{ int X; A(int X) { /*this->*/X = X; } void setX(int X) { /*this->*/X = X; }; ``` Reviewed By: aaron.ballman Differential Revision: https://reviews.llvm.org/D129202
1 parent fc9b37d commit 54f57d3

File tree

8 files changed

+101
-16
lines changed

8 files changed

+101
-16
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,9 @@ Improvements to Clang's diagnostics
279279
unevaluated operands of a ``typeid`` expression, as they are now
280280
modeled correctly in the CFG. This fixes
281281
`Issue 21668 <https://github.com/llvm/llvm-project/issues/21668>`_.
282+
- ``-Wself-assign``, ``-Wself-assign-overloaded`` and ``-Wself-move`` will
283+
suggest a fix if the decl being assigned is a parameter that shadows a data
284+
member of the contained class.
282285

283286
Non-comprehensive list of changes in this release
284287
-------------------------------------------------

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6610,13 +6610,16 @@ def warn_addition_in_bitshift : Warning<
66106610
"'%1' will be evaluated first">, InGroup<ShiftOpParentheses>;
66116611

66126612
def warn_self_assignment_builtin : Warning<
6613-
"explicitly assigning value of variable of type %0 to itself">,
6613+
"explicitly assigning value of variable of type %0 to itself%select{|; did "
6614+
"you mean to assign to member %2?}1">,
66146615
InGroup<SelfAssignment>, DefaultIgnore;
66156616
def warn_self_assignment_overloaded : Warning<
6616-
"explicitly assigning value of variable of type %0 to itself">,
6617+
"explicitly assigning value of variable of type %0 to itself%select{|; did "
6618+
"you mean to assign to member %2?}1">,
66176619
InGroup<SelfAssignmentOverloaded>, DefaultIgnore;
66186620
def warn_self_move : Warning<
6619-
"explicitly moving variable of type %0 to itself">,
6621+
"explicitly moving variable of type %0 to itself%select{|; did you mean to "
6622+
"move to member %2?}1">,
66206623
InGroup<SelfMove>, DefaultIgnore;
66216624

66226625
def err_builtin_move_forward_unsupported : Error<

clang/include/clang/Sema/Sema.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5170,6 +5170,11 @@ class Sema final {
51705170
void DiagnoseSelfMove(const Expr *LHSExpr, const Expr *RHSExpr,
51715171
SourceLocation OpLoc);
51725172

5173+
/// Returns a field in a CXXRecordDecl that has the same name as the decl \p
5174+
/// SelfAssigned when inside a CXXMethodDecl.
5175+
const FieldDecl *
5176+
getSelfAssignmentClassMemberCandidate(const ValueDecl *SelfAssigned);
5177+
51735178
/// Warn if we're implicitly casting from a _Nullable pointer type to a
51745179
/// _Nonnull one.
51755180
void diagnoseNullableToNonnullConversion(QualType DstType, QualType SrcType,

clang/lib/Sema/SemaChecking.cpp

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16830,9 +16830,15 @@ void Sema::DiagnoseSelfMove(const Expr *LHSExpr, const Expr *RHSExpr,
1683016830
RHSDeclRef->getDecl()->getCanonicalDecl())
1683116831
return;
1683216832

16833-
Diag(OpLoc, diag::warn_self_move) << LHSExpr->getType()
16834-
<< LHSExpr->getSourceRange()
16835-
<< RHSExpr->getSourceRange();
16833+
auto D = Diag(OpLoc, diag::warn_self_move)
16834+
<< LHSExpr->getType() << LHSExpr->getSourceRange()
16835+
<< RHSExpr->getSourceRange();
16836+
if (const FieldDecl *F =
16837+
getSelfAssignmentClassMemberCandidate(RHSDeclRef->getDecl()))
16838+
D << 1 << F
16839+
<< FixItHint::CreateInsertion(LHSDeclRef->getBeginLoc(), "this->");
16840+
else
16841+
D << 0;
1683616842
return;
1683716843
}
1683816844

@@ -16867,16 +16873,16 @@ void Sema::DiagnoseSelfMove(const Expr *LHSExpr, const Expr *RHSExpr,
1686716873
RHSDeclRef->getDecl()->getCanonicalDecl())
1686816874
return;
1686916875

16870-
Diag(OpLoc, diag::warn_self_move) << LHSExpr->getType()
16871-
<< LHSExpr->getSourceRange()
16872-
<< RHSExpr->getSourceRange();
16876+
Diag(OpLoc, diag::warn_self_move)
16877+
<< LHSExpr->getType() << 0 << LHSExpr->getSourceRange()
16878+
<< RHSExpr->getSourceRange();
1687316879
return;
1687416880
}
1687516881

1687616882
if (isa<CXXThisExpr>(LHSBase) && isa<CXXThisExpr>(RHSBase))
16877-
Diag(OpLoc, diag::warn_self_move) << LHSExpr->getType()
16878-
<< LHSExpr->getSourceRange()
16879-
<< RHSExpr->getSourceRange();
16883+
Diag(OpLoc, diag::warn_self_move)
16884+
<< LHSExpr->getType() << 0 << LHSExpr->getSourceRange()
16885+
<< RHSExpr->getSourceRange();
1688016886
}
1688116887

1688216888
//===--- Layout compatibility ----------------------------------------------//

clang/lib/Sema/SemaExpr.cpp

Lines changed: 44 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14600,6 +14600,40 @@ static inline UnaryOperatorKind ConvertTokenKindToUnaryOpcode(
1460014600
return Opc;
1460114601
}
1460214602

14603+
const FieldDecl *
14604+
Sema::getSelfAssignmentClassMemberCandidate(const ValueDecl *SelfAssigned) {
14605+
// Explore the case for adding 'this->' to the LHS of a self assignment, very
14606+
// common for setters.
14607+
// struct A {
14608+
// int X;
14609+
// -void setX(int X) { X = X; }
14610+
// +void setX(int X) { this->X = X; }
14611+
// };
14612+
14613+
// Only consider parameters for self assignment fixes.
14614+
if (!isa<ParmVarDecl>(SelfAssigned))
14615+
return nullptr;
14616+
const auto *Method =
14617+
dyn_cast_or_null<CXXMethodDecl>(getCurFunctionDecl(true));
14618+
if (!Method)
14619+
return nullptr;
14620+
14621+
const CXXRecordDecl *Parent = Method->getParent();
14622+
// In theory this is fixable if the lambda explicitly captures this, but
14623+
// that's added complexity that's rarely going to be used.
14624+
if (Parent->isLambda())
14625+
return nullptr;
14626+
14627+
// FIXME: Use an actual Lookup operation instead of just traversing fields
14628+
// in order to get base class fields.
14629+
auto Field =
14630+
llvm::find_if(Parent->fields(),
14631+
[Name(SelfAssigned->getDeclName())](const FieldDecl *F) {
14632+
return F->getDeclName() == Name;
14633+
});
14634+
return (Field != Parent->field_end()) ? *Field : nullptr;
14635+
}
14636+
1460314637
/// DiagnoseSelfAssignment - Emits a warning if a value is assigned to itself.
1460414638
/// This warning suppressed in the event of macro expansions.
1460514639
static void DiagnoseSelfAssignment(Sema &S, Expr *LHSExpr, Expr *RHSExpr,
@@ -14630,10 +14664,16 @@ static void DiagnoseSelfAssignment(Sema &S, Expr *LHSExpr, Expr *RHSExpr,
1463014664
if (RefTy->getPointeeType().isVolatileQualified())
1463114665
return;
1463214666

14633-
S.Diag(OpLoc, IsBuiltin ? diag::warn_self_assignment_builtin
14634-
: diag::warn_self_assignment_overloaded)
14635-
<< LHSDeclRef->getType() << LHSExpr->getSourceRange()
14636-
<< RHSExpr->getSourceRange();
14667+
auto Diag = S.Diag(OpLoc, IsBuiltin ? diag::warn_self_assignment_builtin
14668+
: diag::warn_self_assignment_overloaded)
14669+
<< LHSDeclRef->getType() << LHSExpr->getSourceRange()
14670+
<< RHSExpr->getSourceRange();
14671+
if (const FieldDecl *SelfAssignField =
14672+
S.getSelfAssignmentClassMemberCandidate(RHSDecl))
14673+
Diag << 1 << SelfAssignField
14674+
<< FixItHint::CreateInsertion(LHSDeclRef->getBeginLoc(), "this->");
14675+
else
14676+
Diag << 0;
1463714677
}
1463814678

1463914679
/// Check if a bitwise-& is performed on an Objective-C pointer. This

clang/test/SemaCXX/warn-self-assign-builtin.cpp

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,3 +65,26 @@ void instantiate() {
6565
g<int>();
6666
g<S>();
6767
}
68+
69+
struct Foo {
70+
int A;
71+
72+
Foo(int A) : A(A) {}
73+
74+
void setA(int A) {
75+
A = A; // expected-warning{{explicitly assigning value of variable of type 'int' to itself; did you mean to assign to member 'A'?}}
76+
}
77+
78+
void setThroughLambda() {
79+
[](int A) {
80+
// To fix here we would need to insert an explicit capture 'this'
81+
A = A; // expected-warning{{explicitly assigning}}
82+
}(5);
83+
84+
[this](int A) {
85+
this->A = 0;
86+
// This fix would be possible by just adding this-> as above, but currently unsupported.
87+
A = A; // expected-warning{{explicitly assigning}}
88+
}(5);
89+
}
90+
};

clang/test/SemaCXX/warn-self-assign-field-builtin.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ struct C {
44
int a;
55
int b;
66

7+
C(int a, int b) : a(a), b(b) {}
8+
79
void f() {
810
a = a; // expected-warning {{assigning field to itself}}
911
b = b; // expected-warning {{assigning field to itself}}

clang/test/SemaCXX/warn-self-move.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ class field_test {
3939
other.x = std::move(x);
4040
other.x = std::move(other.x); // expected-warning{{explicitly moving}}
4141
}
42+
void withSuggest(int x) {
43+
x = std::move(x); // expected-warning{{explicitly moving variable of type 'int' to itself; did you mean to move to member 'x'?}}
44+
}
4245
};
4346

4447
struct A {};

0 commit comments

Comments
 (0)