-
Notifications
You must be signed in to change notification settings - Fork 13.6k
[Clang] add fix-it hints for unknown attributes #141305
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
base: main
Are you sure you want to change the base?
Conversation
@llvm/pr-subscribers-clang-modules @llvm/pr-subscribers-clang Author: Oleksandr T. (a-tarasyuk) ChangesThis patch adds fix-it hints for unknown attribute names when Clang suggests a correction Full diff: https://github.com/llvm/llvm-project/pull/141305.diff 3 Files Affected:
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index b93fa33acc2a0..02cca0bb357f9 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -592,6 +592,9 @@ Improvements to Clang's diagnostics
trigger a ``'Blue' is deprecated`` warning, which can be turned off with
``-Wno-deprecated-switch-case``.
+- Clang now emits fix-it hints for unknown attributes when a spelling
+ correction is suggested.
+
Improvements to Clang's time-trace
----------------------------------
diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp
index 8ce51cc2882bf..05b8e7d5a75f0 100644
--- a/clang/lib/Sema/SemaDeclAttr.cpp
+++ b/clang/lib/Sema/SemaDeclAttr.cpp
@@ -7867,15 +7867,16 @@ void Sema::checkUnusedDeclAttributes(Declarator &D) {
void Sema::DiagnoseUnknownAttribute(const ParsedAttr &AL) {
std::string NormalizedFullName = '\'' + AL.getNormalizedFullName() + '\'';
+ SourceRange NR = AL.getNormalizedRange();
+ SourceLocation Loc = NR.getBegin();
+
if (auto CorrectedFullName =
AL.getCorrectedFullName(Context.getTargetInfo(), getLangOpts())) {
- Diag(AL.getNormalizedRange().getBegin(),
- diag::warn_unknown_attribute_ignored_suggestion)
- << NormalizedFullName << *CorrectedFullName << AL.getNormalizedRange();
+ Diag(Loc, diag::warn_unknown_attribute_ignored_suggestion)
+ << NormalizedFullName << *CorrectedFullName
+ << FixItHint::CreateReplacement(NR, *CorrectedFullName) << NR;
} else {
- Diag(AL.getNormalizedRange().getBegin(),
- diag::warn_unknown_attribute_ignored)
- << NormalizedFullName << AL.getNormalizedRange();
+ Diag(Loc, diag::warn_unknown_attribute_ignored) << NormalizedFullName << NR;
}
}
diff --git a/clang/test/FixIt/fixit-unknown-attributes.cpp b/clang/test/FixIt/fixit-unknown-attributes.cpp
new file mode 100644
index 0000000000000..6b74650942d0a
--- /dev/null
+++ b/clang/test/FixIt/fixit-unknown-attributes.cpp
@@ -0,0 +1,26 @@
+// RUN: %clang_cc1 -Wunknown-attributes -fsyntax-only -verify %s
+// RUN: %clang_cc1 -Wunknown-attributes -fsyntax-only -fdiagnostics-parseable-fixits %s 2>&1 | FileCheck %s
+
+[[gmu::deprected]] // expected-warning {{unknown attribute 'gmu::deprected' ignored; did you mean 'gnu::deprecated'?}}
+int f1(void) {
+ return 0;
+}
+// CHECK: fix-it:"{{.*}}":{[[@LINE-4]]:3-[[@LINE-4]]:17}:"gnu::deprecated"
+
+[[gmu::deprecated]] // expected-warning {{unknown attribute 'gmu::deprecated' ignored; did you mean 'gnu::deprecated'?}}
+int f2(void) {
+ return 0;
+}
+// CHECK: fix-it:"{{.*}}":{[[@LINE-4]]:3-[[@LINE-4]]:18}:"gnu::deprecated"
+
+[[gnu::deprected]] // expected-warning {{unknown attribute 'gnu::deprected' ignored; did you mean 'gnu::deprecated'?}}
+int f3(void) {
+ return 0;
+}
+// CHECK: fix-it:"{{.*}}":{[[@LINE-4]]:3-[[@LINE-4]]:17}:"gnu::deprecated"
+
+[[deprected]] // expected-warning {{unknown attribute 'deprected' ignored; did you mean 'deprecated'?}}
+int f4(void) {
+ return 0;
+}
+// CHECK: fix-it:"{{.*}}":{[[@LINE-4]]:3-[[@LINE-4]]:12}:"deprecated"
|
Can you handle this case |
int f5(void) { | ||
return 0; | ||
} | ||
// CHECK: fix-it:"{{.*}}":{[[@LINE-4]]:9-[[@LINE-4]]:24}:"gnu::deprecated" |
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 think this shows a bug with the patch because this seems to be replacing [[using gnu: deprected]]
with [[gnu::deprecated]]
. While this is 1:1 in this example, consider something like: [[using gnu: deprected, noreturn]]
where there are multiple attributes all sharing the same prefix.
I think using
should be handled in a separate patch; for this PR, I'd recommend not attempting to fix this situation. But we may not have that information in Sema, so that may be more involved.
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.
But we may not have that information in Sema, so that may be more involved.
I've checked — you're right. We should at least include the info if the attribute uses a common namespace
llvm-project/clang/lib/Parse/ParseDeclCXX.cpp
Line 4664 in 062353d
CommonScopeName = TryParseCXX11AttributeIdentifier( |
llvm-project/clang/lib/Parse/ParseDeclCXX.cpp
Lines 4715 to 4723 in 062353d
if (CommonScopeName) { | |
if (ScopeName) { | |
Diag(ScopeLoc, diag::err_using_attribute_ns_conflict) | |
<< SourceRange(CommonScopeLoc); | |
} else { | |
ScopeName = CommonScopeName; | |
ScopeLoc = CommonScopeLoc; | |
} | |
} |
I think using should be handled in a separate patch;
Sure, I'll look into it and prepare a new PR for this case.
clang/lib/Sema/SemaDeclAttr.cpp
Outdated
Diag(AL.getNormalizedRange().getBegin(), | ||
diag::warn_unknown_attribute_ignored) | ||
<< NormalizedFullName << AL.getNormalizedRange(); | ||
Diag(Loc, diag::warn_unknown_attribute_ignored) << NormalizedFullName << NR; |
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.
There are more places where we emit a warn_unknown_attribute_ignored
diagnostic; should any of those be handled as well? For example, target-specific attributes which are typos, etc.
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.
@AaronBallman, thanks for the feedback. I replaced diag::warn_unknown_attribute_ignored with DiagnoseUnknownAttribute
for known cases. Since this general message is used in many places, it might be worth reviewing all cases to see where it would be appropriate to use DiagnoseUnknownAttribute
instead. If any additinal cases should be covered, I’d appreciate it if you could point them out — I’ll be happy to investigate., thanks.
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 ones I'd look at replacing are:
ProhibitCXX11Attributes
ParseCXX11AttributeArgs
checkUnusedDeclAttributes
- maybe
CheckAttrTarget
? ActOnBaseSpecifier
just looks odd to me; we diagnose an unknown attribute but if we knew about the attribute, we'd diagnose it anyway. Probably something we want to address separately?ProcessStmtAttribute
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'm really in favor of this direction, this is great, thank you!
I have a few suggestions here (and if there is good reason, feel free to ignore the ones re return-type & setting invalid in particular), but this is pretty good for me.
@@ -1842,8 +1842,7 @@ void Parser::ProhibitCXX11Attributes(ParsedAttributes &Attrs, | |||
continue; | |||
if (AL.getKind() == ParsedAttr::UnknownAttribute) { | |||
if (WarnOnUnknownAttrs) | |||
Diag(AL.getLoc(), diag::warn_unknown_attribute_ignored) | |||
<< AL << AL.getRange(); | |||
Actions.DiagnoseUnknownAttribute(AL); |
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.
Curious that this is not setting it to invalid, but the branch below is. Is that a mistake?
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'll check, though it seems that unknown attributes should also be marked as invalid
@@ -7867,15 +7865,16 @@ void Sema::checkUnusedDeclAttributes(Declarator &D) { | |||
|
|||
void Sema::DiagnoseUnknownAttribute(const ParsedAttr &AL) { | |||
std::string NormalizedFullName = '\'' + AL.getNormalizedFullName() + '\''; | |||
SourceRange NR = AL.getNormalizedRange(); |
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.
There are a few uses of this function, I wonder if we switched it to returning bool
and taking by reference (instead of const-reference) if making this always return 'true' (such that the cases followed immediately by 'return true' could just return), AND having it set the Attribute as invalid would be valuable.
ALSO-ALSO: Could we change this to take AttributeCommonInfo
instead? Or is there something that we need the full ParsedAttr
for?
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.
AND having it set the Attribute as invalid would be valuable.
Could we change this to take AttributeCommonInfo instead? Or is there something that we need the full ParsedAttr for?
@erichkeane Thanks for the feedback. In general, it’s possible to use AttributeCommonInfo
, but I used ParsedAttr
because all existing cases that handle unknown attributes rely on ParsedAttr
. If we switch to AttributeCommonInfo
, we lose the ability to mark the attribute as invalid, since setIsInvalid
is only available on ParsedAttr
.
@erichkeane we've a discussion about this case with @AaronBallman. Unless we plan a broader refactoring to properly support attribute groups, would it make sense to provide fixit
only when there is no scope?
[deprecatd] // fix-it
To handle cases involving using
correctly, it's necessary to at least preserve whether the scope was part of an attribute-using-prefix
. WDYT?
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.
Re: ParsedAttr
/vs CommonInfo
: Ack, that makes sense.
Re fixit only with no scope: I think that is somewhat sensible. I think it is at least an improvement.
You're right that we'd have to some pretty sizable changes to represent the 'scope' via using to the AST, and I don't have a great idea how that should look. We'd either have to do a 'normalization' for the group to make our 'fixit' work (that is, we end up converting usings into explicits or vice versa?), OR represent them.
That said, we COULD fixit just the identifier where it is, right? Since we already know the scope and are correcting within the scope, we can't just change the current 'identifier' to what we're suggesting as a fixit? IF we limited it that way, it would 'inherit' whatever sort of scoping was already there perhaps?
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 said, we COULD fixit just the identifier where it is, right?
[[deprecatd]]
- fixit[[gmp::deprecatd]]
- we could provide afix-it
, but since there's no difference between::
and a scope introduced viausing
, it might lead to incorrect suggestions inusing
cases.[[using gmp: deprecatd, noreturn]]
- not ok, because fixit will begnu::deprecated
Since we already know the scope and are correcting within the scope, we can't just change the current 'identifier' to what we're suggesting as a fixit? IF we limited it that way, it would 'inherit' whatever sort of scoping was already there perhaps?
What I was thinking is that if we have information about the attribute-using prefix
, we could avoid normalization when suggesting a typo correction (while still using the scope::attr format in the diagnostic message). Instead, we could provide two separate fix-its: one for the scope and one for the attribute name. Not sure if that approach makes sense.
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.
What I was thinking is that if we have information about the
attribute-using prefix
, we could avoid normalization when suggesting a typo correction (while still using the scope::attr format in the diagnostic message). Instead, we could provide two separate fix-its: one for the scope and one for the attribute name. Not sure if that approach makes sense.
It DOES make sense. I was sorta hoping we could come up with a method to do the two separate on an individual identifier basis (so doing 1 level of correction at a scope, then a separate on on the attribute itself).
BUT this requires us to act separately on the scope so we cna do some sort of validation/fixit.
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.
BUT this requires us to act separately on the scope so we cna do some sort of validation/fixit.
To proceed, I think the starting point should be to track whether the scope was explicitly written as part of a using-prefix
. From what I understand, that might require introducing something like attrs groups
— or is there a more suitable approach?
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 think that isn't really necessary and we can just correct the scope earlier. If you look at Parser::ParseCXX11AttributeSpecifierInternal
, we separately parse the 'using' from the inline-scope. I'm suggesting we do an ActOnCXX11AttributeScope
where we actually do the correction to be a scope that we know.
That way, in the normal unknown-attributes code, we can treat the scope as 100% valid (since it has already been fixit'ed, or, just don't suggest if it isn't valid), and only suggest the name.
So we would be parsing:
[[using gnX::
-> ActOnCXX11AttributesScope
: Diagnose: "What scope did you mean? could it have been gnu?"
deprecat
-> :Diagnose "what attribute in gnu did you mean, could it have been deprecated?"
[[gnX::
-> ActOnCXX11AttributesScope
: Diagnose: "What scope did you mean? could it have been gnu?"
deprecat
-> :Diagnose "what attribute in gnu did you mean, could it have been deprecated?"
clang/lib/Sema/SemaStmtAttr.cpp
Outdated
? (unsigned)diag::warn_unhandled_ms_attribute_ignored | ||
: (unsigned)diag::warn_unknown_attribute_ignored) | ||
<< A << A.getRange(); | ||
if (A.isRegularKeywordAttribute() || A.isDeclspecAttribute()) |
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 'if' is REALLY pushing our 'single-statement, no curleys' rule, and I think hits: However, braces should be used in cases where the omission of braces harm the readability and maintainability of the code.
So I'd ask for curleys on this + the else.
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.
@erichkeane Sure, I'll change
db4a85b
to
50e590f
Compare
50e590f
to
9d2139c
Compare
9d2139c
to
77641f8
Compare
This patch adds fix-it hints for unknown attribute names when Clang suggests a correction