Skip to content

Conversation

quanzhuo
Copy link

…ection

This fix clangd/clangd#2488

@Copilot Copilot AI review requested due to automatic review settings October 17, 2025 08:34
Copy link

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 @ followed by their GitHub username.

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.

@llvmbot
Copy link
Member

llvmbot commented Oct 17, 2025

@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:

  • (modified) clang-tools-extra/clangd/Selection.cpp (+12)
  • (modified) clang-tools-extra/clangd/unittests/SelectionTests.cpp (+9)
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"},

@llvmbot
Copy link
Member

llvmbot commented Oct 17, 2025

@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:

  • (modified) clang-tools-extra/clangd/Selection.cpp (+12)
  • (modified) clang-tools-extra/clangd/unittests/SelectionTests.cpp (+9)
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"},

Copy link
Contributor

@Copilot Copilot AI left a 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.

Comment on lines +967 to +969
if (ATL.getModifiedLoc().getAs<FunctionTypeLoc>()) {
// Only claim the attribute's source range, not the whole type.
claimRange(ATL.getLocalSourceRange(), Result);
Copy link

Copilot AI Oct 17, 2025

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()).

Suggested change
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);

Copilot uses AI. Check for mistakes.

Copy link
Author

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:

  1. We encounter AttributedTypeLoc and only claim the attribute's source range
  2. RAV automatically traverses the inner FunctionTypeLoc
  3. 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.

@quanzhuo
Copy link
Author

Hi Commander, Could you please help to review this pr ?
Give me some hint if the change is not suitable, Thank you
@HighCommander4

@HighCommander4 HighCommander4 self-requested a review October 19, 2025 02:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

SelectionTree fails to select method tagged with a recognized attribute such as [[clang::lifetimebound]]

2 participants