Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions clang-tools-extra/clangd/Selection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Comment on lines +967 to +969
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.

return;
}
}
}
claimRange(getSourceRange(N), Result);
}
Expand Down
9 changes: 9 additions & 0 deletions clang-tools-extra/clangd/unittests/SelectionTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"},
Expand Down
Loading