-
Notifications
You must be signed in to change notification settings - Fork 14.3k
[clang] Fix-it hint for ++this
-> ++*this
when deref is modifiable
#94159
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
@llvm/pr-subscribers-clang Author: Rajveer Singh Bharadwaj (Rajveer100) ChangesResolves #93066 Full diff: https://github.com/llvm/llvm-project/pull/94159.diff 3 Files Affected:
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 270b0a1e01307..0f5445296e45f 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -8777,6 +8777,9 @@ def err_typecheck_incomplete_type_not_modifiable_lvalue : Error<
def err_typecheck_lvalue_casts_not_supported : Error<
"assignment to cast is illegal, lvalue casts are not supported">;
+def note_typecheck_expression_not_modifiable_lvalue : Note<
+ "dereference the pointer to modify">;
+
def err_typecheck_duplicate_vector_components_not_mlvalue : Error<
"vector is not assignable (contains duplicate components)">;
def err_block_decl_ref_not_modifiable_lvalue : Error<
diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index ff9c5ead36dcf..39e7962fcb2a6 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -13587,10 +13587,21 @@ static bool CheckForModifiableLvalue(Expr *E, SourceLocation Loc, Sema &S) {
SourceRange Assign;
if (Loc != OrigLoc)
Assign = SourceRange(OrigLoc, OrigLoc);
- if (NeedType)
+ if (NeedType) {
S.Diag(Loc, DiagID) << E->getType() << E->getSourceRange() << Assign;
- else
+ } else {
+ ExprResult Deref;
+ unsigned FixitDiagID = 0;
+ {
+ Sema::TentativeAnalysisScope Trap(S);
+ Deref = S.ActOnUnaryOp(S.getCurScope(), Loc, tok::star, E);
+ }
S.Diag(Loc, DiagID) << E->getSourceRange() << Assign;
+ if (Deref.isUsable() && Deref.get()->isModifiableLvalue(S.Context, &Loc) == Expr::MLV_Valid) {
+ FixitDiagID = diag::note_typecheck_expression_not_modifiable_lvalue;
+ S.Diag(Loc, FixitDiagID) << E->getSourceRange() << Assign;
+ }
+ }
return true;
}
diff --git a/clang/test/Sema/debug-93066.cpp b/clang/test/Sema/debug-93066.cpp
new file mode 100644
index 0000000000000..7abedcd0d4d80
--- /dev/null
+++ b/clang/test/Sema/debug-93066.cpp
@@ -0,0 +1,14 @@
+// RUN: %clang_cc1 -std=c++23 -fsyntax-only -verify %s
+
+struct S {
+ void f() {
+ ++this;
+ // expected-error@-1 {{expression is not assignable}}
+ // expected-note@-2 {{dereference the pointer to modify}}
+ }
+
+ void g() const {
+ ++this;
+ // expected-error@-1 {{expression is not assignable}}
+ }
+};
|
cc @Sirraide |
e637dc8
to
95ce40b
Compare
✅ With the latest revision this PR passed the C/C++ code formatter. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In addition to what I’ve pointed out below, there are a few more things to be done:
- This needs a release note (in
clang/docs/ReleaseNotes.rst
, probably under the ‘Improvements to Clang’s Diagnostics’ section). - I find it hard to believe that the test you added is the only thing affected by this; it’s very likely that there are other tests that are failing now because you also need to add the diagnostic there; please run the Clang test suite locally (e.g.
ninja check-clang
if you’re using Ninja as your generator) and fix any tests that this is probably breaking. - Some more tests, including tests involving templates, would also be a good idea.
Tests which fail currently: Clang :: Sema/exprs.c Also, what do you think about the fix-it hint wording for errors such as |
Don’t remember what that was for off the top of my head, but just the same fix-it should be fine imo |
95ce40b
to
6dec67c
Compare
6dec67c
to
5092ffd
Compare
@Sirraide |
5092ffd
to
e38b387
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is already looking better.
(Also, for the record, this is just a me thing and not official policy or anything, but I’d find it easier if you didn’t force-push because then I’d have an easier time telling what’s changed in recent commits; everything is going to get squashed into a single commit on merge anyway, so individual commits to the pr don’t have to be perfect or anything.)
e38b387
to
1a9ef88
Compare
I have added a The function change is also done. |
1a9ef88
to
3b25887
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The implementation looks fine, but a few more tests involving templates would be nice to see what this does with dependent types, e.g.
template <typename T>
void f(T& t) {
++t;
}
void g() {
int* a;
int* const b = a;
const int* const c = a;
f(a); // Ok.
f(b); // Error and fixit.
f(c); // Error, but no fixit.
}
I’d expect an error to be reported at instantiation time only; I doubt that there will be any complications here, but more tests are never a bad idea.
3b25887
to
765176a
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Two last comments, but other than that this LGTM.
765176a
to
bf11465
Compare
I have done the last few changes as well. Regarding the force-push, I will try to do single commits and later squash them! |
You don’t even need to squash them yourself; Github does that automatically because we use ‘squash and merge’. |
@Sirraide |
Sure; looks like everything is fine. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have some significant concerns about the approach of this PR. It seems to be producing notes that in a lot of cases suggest doing something that doesn't make sense or won't compile, and tentatively building a *p
expression is unsafe.
Can we revert and consider other approaches?
{ | ||
Sema::TentativeAnalysisScope Trap(S); | ||
Deref = S.ActOnUnaryOp(S.getCurScope(), Loc, tok::star, TE); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not really feasible to use a TentativeAnalysisScope
for this. Here are three reasons:
- If
ActOnUnaryOp
produces an "immediate context" diagnostic (the common case), such as a warning, then the note you produce below will be attached to that diagnostic instead of to the original diagnostic, so your note will be suppressed. - If
ActOnUnaryOp
triggers template instantiation, thenTentativeAnalysisScope
will not suppress diagnostics outside of the immediate context, potentially leading to additional spurious diagnostics being produced. - If this function is used to attach a note to a warning / extension diagnostic rather than an error (which doesn't appear to be the case right now, but it seems plausible that someone would make such a change to
CheckForModifiableLValue
in the future), then the diagnostics produced by (2) would lead to rejecting a valid program.
Checking for an overloaded operator*
seems overly complicated here, so I think the right thing to test here is just whether the expression has pointer type, and the pointee type is an object type (not void or a function type).
struct S { | ||
void f() { | ||
++this; // expected-error {{expression is not assignable}} | ||
// expected-note@-1 {{add '*' to dereference it}} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a bad diagnostic change: adding the *
doesn't help.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That’s true. Looking at this again, I’m candidly a bit confused that this seemed fine to me at the time...
} | ||
|
||
void g() const { | ||
++this; // expected-error {{expression is not assignable}} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Likewise.
@@ -65,8 +65,10 @@ void test4(void) { | |||
|
|||
void test5(int *X, float *P) { | |||
(float*)X = P; // expected-error {{assignment to cast is illegal, lvalue casts are not supported}} | |||
// expected-note@-1 {{add '*' to dereference it}} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a diagnostic regression.
#define FOO ((float*) X) | ||
FOO = P; // expected-error {{assignment to cast is illegal, lvalue casts are not supported}} | ||
// expected-note@-1 {{add '*' to dereference it}} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a diagnostic regression.
S.Diag(E->getBeginLoc(), | ||
diag::note_typecheck_add_deref_star_not_modifiable_lvalue) | ||
<< E->getSourceRange() | ||
<< FixItHint::CreateInsertion(E->getBeginLoc(), "*"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We shouldn't be suggesting adding a *
unless that would actually make sense in context. Eg, given:
const int *const p;
const int *const q;
p = q;
... we should not suggest a dereference -- the problem is clearly that p
is const
, and adding a *
doesn't help.
I suspect we simply don't have enough information to do this from within CheckForModifiableLvalue
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suspect we simply don't have enough information to do this from within CheckForModifiableLvalue.
Yeah, my idea was that by trying to build a *
expression we might be able to figure out whether that makes sense in the context, whereas just looking for e.g. pointer types would cause too many false positives (at least that’s what I thought), but it might just be that you end up with too many false positives either way...
Yeah, a lot of the points you’ve brought up make sense, and if you’re saying it’s a bad idea then I’ll trust you and revert this for now so we can think about this some more... Also, I suppose part of the issue for me at least was that I wasn’t 100% sure about this approach either, but I also didn’t want to create even more work for other people than they already have by asking someone else to review this too... I’ll be a bit more careful with this in the future. I also marked this as a good first issue because I thought it shouldn’t be that complicated to do this, but it seems I was wrong (this is also why I usually don’t like using that label...) |
That’s also what I was going for initially, but I thought that doing just that might lead to too many false positives... That said, considering all the issues that using |
Perhaps we can start by supplying <source>:2:3: error: cannot assign to variable 'n' with const-qualified type 'const int'
2 | ++n;
| ^ ~ ... is really bad -- this isn't an assignment, it's an increment. An enum of possible reasons why we're doing the check would let us significantly improve the diagnostic here. |
Having more context would probably help yeah. We should be able to improve that a bit even if the fix-it isn’t viable... |
Resolves #93066