-
Notifications
You must be signed in to change notification settings - Fork 15.1k
Improve attribute range handling for attributed function types in sel… #163926
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
|
Thank you for submitting a Pull Request (PR) to the LLVM Project! This PR will be automatically labeled and the relevant teams will be notified. If you wish to, you can add reviewers by using the "Reviewers" section on this page. If this is not working for you, it is probably because you do not have write permissions for the repository. In which case you can instead tag reviewers by name in a comment by using If you have received no comments on your PR for a week, you can request a review by "ping"ing the PR by adding a comment “Ping”. The common courtesy "ping" rate is once a week. Please remember that you are asking for valuable time from other developers. If you have further questions, they may be answered by the LLVM GitHub User Guide. You can also ask questions in a comment on this PR, on the LLVM Discord or on the forums. |
|
@llvm/pr-subscribers-clang-tools-extra Author: Quan Zhuo (quanzhuo) Changes…ection This fix clangd/clangd#2488 Full diff: https://github.com/llvm/llvm-project/pull/163926.diff 2 Files Affected:
diff --git a/clang-tools-extra/clangd/Selection.cpp b/clang-tools-extra/clangd/Selection.cpp
index 06165dfbbcdd2..faa00d20497fa 100644
--- a/clang-tools-extra/clangd/Selection.cpp
+++ b/clang-tools-extra/clangd/Selection.cpp
@@ -958,6 +958,18 @@ class SelectionVisitor : public RecursiveASTVisitor<SelectionVisitor> {
claimRange(SourceRange(FTL.getLParenLoc(), FTL.getEndLoc()), Result);
return;
}
+ if (auto ATL = TL->getAs<AttributedTypeLoc>()) {
+ // For attributed function types like `int foo() [[attr]]`, the
+ // AttributedTypeLoc's range includes the function name. We want to
+ // allow the function name to be associated with the FunctionDecl
+ // rather than the AttributedTypeLoc, so we only claim the attribute
+ // range itself.
+ if (ATL.getModifiedLoc().getAs<FunctionTypeLoc>()) {
+ // Only claim the attribute's source range, not the whole type.
+ claimRange(ATL.getLocalSourceRange(), Result);
+ return;
+ }
+ }
}
claimRange(getSourceRange(N), Result);
}
diff --git a/clang-tools-extra/clangd/unittests/SelectionTests.cpp b/clang-tools-extra/clangd/unittests/SelectionTests.cpp
index 3df19d8fc174d..103c00ebd5696 100644
--- a/clang-tools-extra/clangd/unittests/SelectionTests.cpp
+++ b/clang-tools-extra/clangd/unittests/SelectionTests.cpp
@@ -311,6 +311,15 @@ TEST(SelectionTest, CommonAncestor) {
{"[[void foo^()]];", "FunctionProtoTypeLoc"},
{"[[^void foo^()]];", "FunctionDecl"},
{"[[void ^foo()]];", "FunctionDecl"},
+ // Tricky case: with function attributes, the AttributedTypeLoc's range
+ // includes the function name, but we want the name to be associated with
+ // the FunctionDecl.
+ {"struct X { [[void ^foo() [[clang::lifetimebound]]]]; };",
+ "FunctionDecl"},
+ {"struct X { [[void ^foo() const [[clang::lifetimebound]]]]; };",
+ "FunctionDecl"},
+ {"struct X { [[const int* ^Get() const [[clang::lifetimebound]]]]; };",
+ "FunctionDecl"},
// Tricky case: two VarDecls share a specifier.
{"[[int ^a]], b;", "VarDecl"},
{"[[int a, ^b]];", "VarDecl"},
|
|
@llvm/pr-subscribers-clangd Author: Quan Zhuo (quanzhuo) Changes…ection This fix clangd/clangd#2488 Full diff: https://github.com/llvm/llvm-project/pull/163926.diff 2 Files Affected:
diff --git a/clang-tools-extra/clangd/Selection.cpp b/clang-tools-extra/clangd/Selection.cpp
index 06165dfbbcdd2..faa00d20497fa 100644
--- a/clang-tools-extra/clangd/Selection.cpp
+++ b/clang-tools-extra/clangd/Selection.cpp
@@ -958,6 +958,18 @@ class SelectionVisitor : public RecursiveASTVisitor<SelectionVisitor> {
claimRange(SourceRange(FTL.getLParenLoc(), FTL.getEndLoc()), Result);
return;
}
+ if (auto ATL = TL->getAs<AttributedTypeLoc>()) {
+ // For attributed function types like `int foo() [[attr]]`, the
+ // AttributedTypeLoc's range includes the function name. We want to
+ // allow the function name to be associated with the FunctionDecl
+ // rather than the AttributedTypeLoc, so we only claim the attribute
+ // range itself.
+ if (ATL.getModifiedLoc().getAs<FunctionTypeLoc>()) {
+ // Only claim the attribute's source range, not the whole type.
+ claimRange(ATL.getLocalSourceRange(), Result);
+ return;
+ }
+ }
}
claimRange(getSourceRange(N), Result);
}
diff --git a/clang-tools-extra/clangd/unittests/SelectionTests.cpp b/clang-tools-extra/clangd/unittests/SelectionTests.cpp
index 3df19d8fc174d..103c00ebd5696 100644
--- a/clang-tools-extra/clangd/unittests/SelectionTests.cpp
+++ b/clang-tools-extra/clangd/unittests/SelectionTests.cpp
@@ -311,6 +311,15 @@ TEST(SelectionTest, CommonAncestor) {
{"[[void foo^()]];", "FunctionProtoTypeLoc"},
{"[[^void foo^()]];", "FunctionDecl"},
{"[[void ^foo()]];", "FunctionDecl"},
+ // Tricky case: with function attributes, the AttributedTypeLoc's range
+ // includes the function name, but we want the name to be associated with
+ // the FunctionDecl.
+ {"struct X { [[void ^foo() [[clang::lifetimebound]]]]; };",
+ "FunctionDecl"},
+ {"struct X { [[void ^foo() const [[clang::lifetimebound]]]]; };",
+ "FunctionDecl"},
+ {"struct X { [[const int* ^Get() const [[clang::lifetimebound]]]]; };",
+ "FunctionDecl"},
// Tricky case: two VarDecls share a specifier.
{"[[int ^a]], b;", "VarDecl"},
{"[[int a, ^b]];", "VarDecl"},
|
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.
Pull Request Overview
Fix selection mapping for attributed function types so that the function name is associated with the FunctionDecl, not the AttributedTypeLoc.
- Add unit tests covering function declarations with trailing attributes and const-qualified methods.
- Adjust SelectionVisitor to claim only the attribute token range for AttributedTypeLoc wrapping function types.
Reviewed Changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| clang-tools-extra/clangd/Selection.cpp | Special-case AttributedTypeLoc wrapping function types to claim only attribute tokens during selection. |
| clang-tools-extra/clangd/unittests/SelectionTests.cpp | Add tests ensuring the function name maps to FunctionDecl when attributes are present on the function type. |
| if (ATL.getModifiedLoc().getAs<FunctionTypeLoc>()) { | ||
| // Only claim the attribute's source range, not the whole type. | ||
| claimRange(ATL.getLocalSourceRange(), Result); |
Copilot
AI
Oct 17, 2025
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.
Returning immediately after claiming the attribute range prevents the existing FunctionProtoTypeLoc handling (at lines 958–960) from running for attributed function types. As a result, the function's parameter list and trailing part (from lParen to EndLoc) will not be claimed by the type loc, potentially leaving those tokens unclaimed or attributed to the wrong node. Suggestion: claim the attribute range, then also claim the inner FunctionProtoTypeLoc's range before returning, e.g., check ATL.getModifiedLoc().getAs() and claim SourceRange(FTL.getLParenLoc(), FTL.getEndLoc()).
| if (ATL.getModifiedLoc().getAs<FunctionTypeLoc>()) { | |
| // Only claim the attribute's source range, not the whole type. | |
| claimRange(ATL.getLocalSourceRange(), Result); | |
| if (auto FTL = ATL.getModifiedLoc().getAs<FunctionTypeLoc>()) { | |
| // Claim the attribute's source range. | |
| claimRange(ATL.getLocalSourceRange(), Result); | |
| // Also claim the function's parameter list and trailing part. | |
| claimRange(SourceRange(FTL.getLParenLoc(), FTL.getEndLoc()), Result); |
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 RecursiveASTVisitor automatically traverses the inner type of AttributedTypeLoc. Looking at the RAV implementation in RecursiveASTVisitor.h:
DEF_TRAVERSE_TYPELOC(AttributedType,
{ TRY_TO(TraverseTypeLoc(TL.getModifiedLoc())); })This means when we visit an AttributedTypeLoc, the RAV will automatically traverse its ModifiedLoc (the inner FunctionTypeLoc).
So the flow is:
- We encounter
AttributedTypeLocand only claim the attribute's source range - RAV automatically traverses the inner
FunctionTypeLoc - The existing special handling for
FunctionTypeLoc(lines 957-959) claims the parameter list range
If we claim the function's parameter list in the AttributedTypeLoc handler as suggested, it would be claimed twice: once in the AttributedTypeLoc case and again when the inner FunctionTypeLoc is traversed.
|
Hi Commander, Could you please help to review this pr ? |
HighCommander4
left a comment
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.
Thanks for the patch! Looks great, I just have a suggestion for one additonal test case.
| "FunctionDecl"}, | ||
| {"struct X { [[void ^foo() const [[clang::lifetimebound]]]]; };", | ||
| "FunctionDecl"}, | ||
| {"struct X { [[const int* ^Get() const [[clang::lifetimebound]]]]; };", |
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.
Could you add a case where we successfully hit the AttributedTypeLoc (e.g. [[clang::^lifetimebound]])?
HighCommander4
left a comment
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.
CI is showing the test is failing, that will need to be fixed.
Ah, the reason for the test failure is slightly annoying: there's a conflict between the C++ attribute syntax, and the syntax of the annotations used to mark up our test cases (see the documentation of the annotation syntax and this FIXME.) This can be worked around by using an alternative syntax for C++ attributes, |
|
Thanks for the review! I've added the test for selecting The issue appears to be that the test annotation parser stops at the Thanks for your guidance! |
It looks like the problem is not with the expected range marker (this is It looks like the AST is reporting that the We can revise the test to expect the node to end there, i.e.: though I find it more readable to use the digraph for both brackets: and maybe add a comment saying that this is a quirk in the AST modeling of |
|
Hi @HighCommander4, Thank you for the detailed explanation! I've updated the test case and comment as you suggested. The test should pass now. Thanks again for your patient guidance! |
HighCommander4
left a comment
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.
Thanks, LGTM!
|
@quanzhuo Congratulations on having your first Pull Request (PR) merged into the LLVM Project! Your changes will be combined with recent changes from other authors, then tested by our build bots. If there is a problem with a build, you may receive a report in an email or a comment on this PR. Please check whether problems have been caused by your change specifically, as the builds can include changes from many authors. It is not uncommon for your change to be included in a build that fails due to someone else's changes, or infrastructure issues. How to do this, and the rest of the post-merge process, is covered in detail here. If your change does cause a problem, it may be reverted, or you can revert it yourself. This is a normal part of LLVM development. You can fix your changes and open a new PR to merge them again. If you don't get any reports, no action is required from you. Your changes are working as expected, well done! |
This ensures a method name continues to target the method's declaration even if the method's type uses an attribute. Before this change, the AttributedTypeLoc would claim the method name. Fixes clangd/clangd#2488
…ection
This fix clangd/clangd#2488