[clang-tools-extra] [clangd] Don't ignore external HFI in `SymbolCollector` (PR #88446)
https://github.com/DavidGoldman approved this pull request. https://github.com/llvm/llvm-project/pull/88446 ___ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang-tools-extra] [clangd] Add support for renaming ObjC properties and implicit properties (PR #81775)
https://github.com/DavidGoldman updated https://github.com/llvm/llvm-project/pull/81775 >From b0a2fb25c25ecfb20bb3f0aab2d398ea80caeff2 Mon Sep 17 00:00:00 2001 From: David Goldman Date: Fri, 26 Jan 2024 15:50:11 -0500 Subject: [PATCH 1/6] Add support for ObjC property renaming + implicit property renaming Objective-C properties can generate multiple decls: - an ivar decl - a getter method decl - a setter method decl Triggering the rename from any of those decls should trigger a rename of the property and use the proper name for each one according to the ObjC property naming conventions. I've added similar support for implicit properties, which is when a getter or setter like method is referred to via the property syntax (self.method) without an explicit property decl. --- clang-tools-extra/clangd/FindTarget.cpp | 16 + clang-tools-extra/clangd/refactor/Rename.cpp | 338 ++ .../clangd/unittests/RenameTests.cpp | 90 + .../unittests/SemanticHighlightingTests.cpp | 2 +- 4 files changed, 380 insertions(+), 66 deletions(-) diff --git a/clang-tools-extra/clangd/FindTarget.cpp b/clang-tools-extra/clangd/FindTarget.cpp index e702c6b3537a09..9def867011f696 100644 --- a/clang-tools-extra/clangd/FindTarget.cpp +++ b/clang-tools-extra/clangd/FindTarget.cpp @@ -740,6 +740,22 @@ llvm::SmallVector refInDecl(const Decl *D, /*IsDecl=*/true, {OIMD}}); } + +void VisitObjCPropertyImplDecl(const ObjCPropertyImplDecl *OPID) { + // Skiped compiler synthesized property impl decls - they will always + // have an invalid loc. + if (OPID->getLocation().isInvalid()) +return; + if (OPID->isIvarNameSpecified()) +Refs.push_back(ReferenceLoc{NestedNameSpecifierLoc(), +OPID->getPropertyIvarDeclLoc(), +/*IsDecl=*/false, +{OPID->getPropertyIvarDecl()}}); + Refs.push_back(ReferenceLoc{NestedNameSpecifierLoc(), + OPID->getLocation(), + /*IsDecl=*/false, + {OPID->getPropertyDecl()}}); +} }; Visitor V{Resolver}; diff --git a/clang-tools-extra/clangd/refactor/Rename.cpp b/clang-tools-extra/clangd/refactor/Rename.cpp index 4e135801f6853d..b53c24b8331ddb 100644 --- a/clang-tools-extra/clangd/refactor/Rename.cpp +++ b/clang-tools-extra/clangd/refactor/Rename.cpp @@ -21,6 +21,7 @@ #include "clang/AST/DeclCXX.h" #include "clang/AST/DeclObjC.h" #include "clang/AST/DeclTemplate.h" +#include "clang/AST/ExprObjC.h" #include "clang/AST/ParentMapContext.h" #include "clang/AST/Stmt.h" #include "clang/Basic/CharInfo.h" @@ -153,8 +154,111 @@ const NamedDecl *pickInterestingTarget(const NamedDecl *D) { return D; } -llvm::DenseSet locateDeclAt(ParsedAST , - SourceLocation TokenStartLoc) { +// Some AST nodes are synthesized by the compiler based on other nodes. e.g. +// ObjC methods and ivars can be synthesized based on an Objective-C property. +// +// We perform this check outside of canonicalization since we need to know which +// decl the user has actually triggered the rename on in order to remap all +// derived decls properly, since the naming patterns can slightly differ for +// every decl that the compiler synthesizes. +const NamedDecl *findOriginDecl(const NamedDecl *D) { + if (const auto *MD = dyn_cast(D)) { +if (const auto *PD = MD->findPropertyDecl(/*CheckOverrides=*/false)) + // FIXME(davg): We should only map to the protocol if the user hasn't + // explicitly given a setter/getter for the method - if they have we + // should either fail the rename or support basic 1 arg selector renames. + return canonicalRenameDecl(PD); + } + if (const auto *ID = dyn_cast(D)) { +for (const auto *PD : ID->getContainingInterface()->properties()) { + if (PD->getPropertyIvarDecl() == ID) +return canonicalRenameDecl(PD); +} + } + return D; +} + +std::string propertySetterName(llvm::StringRef PropertyName) { + std::string Setter = PropertyName.str(); + if (!Setter.empty()) +Setter[0] = llvm::toUpper(Setter[0]); + return "set" + Setter + ":"; +} + +// Returns a non-empty string if valid. +std::string setterToPropertyName(llvm::StringRef Setter) { + std::string Result; + if (!Setter.consume_front("set")) { +return Result; + } + Setter.consume_back(":"); // Optional. + Result = Setter.str(); + if (!Result.empty()) +Result[0] = llvm::toLower(Result[0]); + return Result; +} + +llvm::DenseMap +computeAllDeclsToNewName(const NamedDecl *Selected, llvm::StringRef NewName, + const NamedDecl *Origin) { + llvm::DenseMap DeclToName; + DeclToName[Selected] = NewName.str(); + + if (const auto *PD = dyn_cast(Origin)) { +// Need to derive
[clang-tools-extra] [clangd] Add support for renaming ObjC properties and implicit properties (PR #81775)
https://github.com/DavidGoldman updated https://github.com/llvm/llvm-project/pull/81775 >From b0a2fb25c25ecfb20bb3f0aab2d398ea80caeff2 Mon Sep 17 00:00:00 2001 From: David Goldman Date: Fri, 26 Jan 2024 15:50:11 -0500 Subject: [PATCH 1/5] Add support for ObjC property renaming + implicit property renaming Objective-C properties can generate multiple decls: - an ivar decl - a getter method decl - a setter method decl Triggering the rename from any of those decls should trigger a rename of the property and use the proper name for each one according to the ObjC property naming conventions. I've added similar support for implicit properties, which is when a getter or setter like method is referred to via the property syntax (self.method) without an explicit property decl. --- clang-tools-extra/clangd/FindTarget.cpp | 16 + clang-tools-extra/clangd/refactor/Rename.cpp | 338 ++ .../clangd/unittests/RenameTests.cpp | 90 + .../unittests/SemanticHighlightingTests.cpp | 2 +- 4 files changed, 380 insertions(+), 66 deletions(-) diff --git a/clang-tools-extra/clangd/FindTarget.cpp b/clang-tools-extra/clangd/FindTarget.cpp index e702c6b3537a09..9def867011f696 100644 --- a/clang-tools-extra/clangd/FindTarget.cpp +++ b/clang-tools-extra/clangd/FindTarget.cpp @@ -740,6 +740,22 @@ llvm::SmallVector refInDecl(const Decl *D, /*IsDecl=*/true, {OIMD}}); } + +void VisitObjCPropertyImplDecl(const ObjCPropertyImplDecl *OPID) { + // Skiped compiler synthesized property impl decls - they will always + // have an invalid loc. + if (OPID->getLocation().isInvalid()) +return; + if (OPID->isIvarNameSpecified()) +Refs.push_back(ReferenceLoc{NestedNameSpecifierLoc(), +OPID->getPropertyIvarDeclLoc(), +/*IsDecl=*/false, +{OPID->getPropertyIvarDecl()}}); + Refs.push_back(ReferenceLoc{NestedNameSpecifierLoc(), + OPID->getLocation(), + /*IsDecl=*/false, + {OPID->getPropertyDecl()}}); +} }; Visitor V{Resolver}; diff --git a/clang-tools-extra/clangd/refactor/Rename.cpp b/clang-tools-extra/clangd/refactor/Rename.cpp index 4e135801f6853d..b53c24b8331ddb 100644 --- a/clang-tools-extra/clangd/refactor/Rename.cpp +++ b/clang-tools-extra/clangd/refactor/Rename.cpp @@ -21,6 +21,7 @@ #include "clang/AST/DeclCXX.h" #include "clang/AST/DeclObjC.h" #include "clang/AST/DeclTemplate.h" +#include "clang/AST/ExprObjC.h" #include "clang/AST/ParentMapContext.h" #include "clang/AST/Stmt.h" #include "clang/Basic/CharInfo.h" @@ -153,8 +154,111 @@ const NamedDecl *pickInterestingTarget(const NamedDecl *D) { return D; } -llvm::DenseSet locateDeclAt(ParsedAST , - SourceLocation TokenStartLoc) { +// Some AST nodes are synthesized by the compiler based on other nodes. e.g. +// ObjC methods and ivars can be synthesized based on an Objective-C property. +// +// We perform this check outside of canonicalization since we need to know which +// decl the user has actually triggered the rename on in order to remap all +// derived decls properly, since the naming patterns can slightly differ for +// every decl that the compiler synthesizes. +const NamedDecl *findOriginDecl(const NamedDecl *D) { + if (const auto *MD = dyn_cast(D)) { +if (const auto *PD = MD->findPropertyDecl(/*CheckOverrides=*/false)) + // FIXME(davg): We should only map to the protocol if the user hasn't + // explicitly given a setter/getter for the method - if they have we + // should either fail the rename or support basic 1 arg selector renames. + return canonicalRenameDecl(PD); + } + if (const auto *ID = dyn_cast(D)) { +for (const auto *PD : ID->getContainingInterface()->properties()) { + if (PD->getPropertyIvarDecl() == ID) +return canonicalRenameDecl(PD); +} + } + return D; +} + +std::string propertySetterName(llvm::StringRef PropertyName) { + std::string Setter = PropertyName.str(); + if (!Setter.empty()) +Setter[0] = llvm::toUpper(Setter[0]); + return "set" + Setter + ":"; +} + +// Returns a non-empty string if valid. +std::string setterToPropertyName(llvm::StringRef Setter) { + std::string Result; + if (!Setter.consume_front("set")) { +return Result; + } + Setter.consume_back(":"); // Optional. + Result = Setter.str(); + if (!Result.empty()) +Result[0] = llvm::toLower(Result[0]); + return Result; +} + +llvm::DenseMap +computeAllDeclsToNewName(const NamedDecl *Selected, llvm::StringRef NewName, + const NamedDecl *Origin) { + llvm::DenseMap DeclToName; + DeclToName[Selected] = NewName.str(); + + if (const auto *PD = dyn_cast(Origin)) { +// Need to derive
[clang-tools-extra] [clangd] Add metric for rename decl kind (PR #83867)
https://github.com/DavidGoldman closed https://github.com/llvm/llvm-project/pull/83867 ___ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang-tools-extra] [clangd] Add metric for rename decl kind (PR #83867)
https://github.com/DavidGoldman created https://github.com/llvm/llvm-project/pull/83867 This will give us insight into what users are renaming in practice - for instance, try to gauge the impact of the ObjC rename support. >From 42dddf4a5fbd862bf2f122a6c6216fe0dbd34e54 Mon Sep 17 00:00:00 2001 From: David Goldman Date: Mon, 4 Mar 2024 11:35:46 -0500 Subject: [PATCH] [clangd] Add metric for rename decl kind This will give us insight into what users are renaming in practice. --- clang-tools-extra/clangd/refactor/Rename.cpp | 4 1 file changed, 4 insertions(+) diff --git a/clang-tools-extra/clangd/refactor/Rename.cpp b/clang-tools-extra/clangd/refactor/Rename.cpp index 4e135801f6853d..0cc7eecd6212be 100644 --- a/clang-tools-extra/clangd/refactor/Rename.cpp +++ b/clang-tools-extra/clangd/refactor/Rename.cpp @@ -1072,6 +1072,10 @@ llvm::Expected rename(const RenameInputs ) { if (Reject) return makeError(*Reject); + static constexpr trace::Metric RenameTriggerCounter( + "rename_trigger_count", trace::Metric::Counter, "decl_kind"); + RenameTriggerCounter.record(1, RenameDecl.getDeclKindName()); + // We have two implementations of the rename: // - AST-based rename: used for renaming local symbols, e.g. variables // defined in a function body; ___ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang-tools-extra] [clangd] Add support for renaming ObjC properties and implicit properties (PR #81775)
https://github.com/DavidGoldman updated https://github.com/llvm/llvm-project/pull/81775 >From b0a2fb25c25ecfb20bb3f0aab2d398ea80caeff2 Mon Sep 17 00:00:00 2001 From: David Goldman Date: Fri, 26 Jan 2024 15:50:11 -0500 Subject: [PATCH 1/4] Add support for ObjC property renaming + implicit property renaming Objective-C properties can generate multiple decls: - an ivar decl - a getter method decl - a setter method decl Triggering the rename from any of those decls should trigger a rename of the property and use the proper name for each one according to the ObjC property naming conventions. I've added similar support for implicit properties, which is when a getter or setter like method is referred to via the property syntax (self.method) without an explicit property decl. --- clang-tools-extra/clangd/FindTarget.cpp | 16 + clang-tools-extra/clangd/refactor/Rename.cpp | 338 ++ .../clangd/unittests/RenameTests.cpp | 90 + .../unittests/SemanticHighlightingTests.cpp | 2 +- 4 files changed, 380 insertions(+), 66 deletions(-) diff --git a/clang-tools-extra/clangd/FindTarget.cpp b/clang-tools-extra/clangd/FindTarget.cpp index e702c6b3537a09..9def867011f696 100644 --- a/clang-tools-extra/clangd/FindTarget.cpp +++ b/clang-tools-extra/clangd/FindTarget.cpp @@ -740,6 +740,22 @@ llvm::SmallVector refInDecl(const Decl *D, /*IsDecl=*/true, {OIMD}}); } + +void VisitObjCPropertyImplDecl(const ObjCPropertyImplDecl *OPID) { + // Skiped compiler synthesized property impl decls - they will always + // have an invalid loc. + if (OPID->getLocation().isInvalid()) +return; + if (OPID->isIvarNameSpecified()) +Refs.push_back(ReferenceLoc{NestedNameSpecifierLoc(), +OPID->getPropertyIvarDeclLoc(), +/*IsDecl=*/false, +{OPID->getPropertyIvarDecl()}}); + Refs.push_back(ReferenceLoc{NestedNameSpecifierLoc(), + OPID->getLocation(), + /*IsDecl=*/false, + {OPID->getPropertyDecl()}}); +} }; Visitor V{Resolver}; diff --git a/clang-tools-extra/clangd/refactor/Rename.cpp b/clang-tools-extra/clangd/refactor/Rename.cpp index 4e135801f6853d..b53c24b8331ddb 100644 --- a/clang-tools-extra/clangd/refactor/Rename.cpp +++ b/clang-tools-extra/clangd/refactor/Rename.cpp @@ -21,6 +21,7 @@ #include "clang/AST/DeclCXX.h" #include "clang/AST/DeclObjC.h" #include "clang/AST/DeclTemplate.h" +#include "clang/AST/ExprObjC.h" #include "clang/AST/ParentMapContext.h" #include "clang/AST/Stmt.h" #include "clang/Basic/CharInfo.h" @@ -153,8 +154,111 @@ const NamedDecl *pickInterestingTarget(const NamedDecl *D) { return D; } -llvm::DenseSet locateDeclAt(ParsedAST , - SourceLocation TokenStartLoc) { +// Some AST nodes are synthesized by the compiler based on other nodes. e.g. +// ObjC methods and ivars can be synthesized based on an Objective-C property. +// +// We perform this check outside of canonicalization since we need to know which +// decl the user has actually triggered the rename on in order to remap all +// derived decls properly, since the naming patterns can slightly differ for +// every decl that the compiler synthesizes. +const NamedDecl *findOriginDecl(const NamedDecl *D) { + if (const auto *MD = dyn_cast(D)) { +if (const auto *PD = MD->findPropertyDecl(/*CheckOverrides=*/false)) + // FIXME(davg): We should only map to the protocol if the user hasn't + // explicitly given a setter/getter for the method - if they have we + // should either fail the rename or support basic 1 arg selector renames. + return canonicalRenameDecl(PD); + } + if (const auto *ID = dyn_cast(D)) { +for (const auto *PD : ID->getContainingInterface()->properties()) { + if (PD->getPropertyIvarDecl() == ID) +return canonicalRenameDecl(PD); +} + } + return D; +} + +std::string propertySetterName(llvm::StringRef PropertyName) { + std::string Setter = PropertyName.str(); + if (!Setter.empty()) +Setter[0] = llvm::toUpper(Setter[0]); + return "set" + Setter + ":"; +} + +// Returns a non-empty string if valid. +std::string setterToPropertyName(llvm::StringRef Setter) { + std::string Result; + if (!Setter.consume_front("set")) { +return Result; + } + Setter.consume_back(":"); // Optional. + Result = Setter.str(); + if (!Result.empty()) +Result[0] = llvm::toLower(Result[0]); + return Result; +} + +llvm::DenseMap +computeAllDeclsToNewName(const NamedDecl *Selected, llvm::StringRef NewName, + const NamedDecl *Origin) { + llvm::DenseMap DeclToName; + DeclToName[Selected] = NewName.str(); + + if (const auto *PD = dyn_cast(Origin)) { +// Need to derive
[clang-tools-extra] [clangd] Fix renaming single argument ObjC methods (PR #82396)
https://github.com/DavidGoldman closed https://github.com/llvm/llvm-project/pull/82396 ___ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang-tools-extra] [clangd] Fix renaming single argument ObjC methods (PR #82396)
https://github.com/DavidGoldman updated https://github.com/llvm/llvm-project/pull/82396 >From 8a9c09575ed143e762faa7abf48285ed6b4b0335 Mon Sep 17 00:00:00 2001 From: David Goldman Date: Tue, 20 Feb 2024 13:14:26 -0500 Subject: [PATCH 1/3] [clangd] Fix renaming single argument ObjC methods Add a few more tests to verify this works (thanks to @ahoppen for the tests and finding this bug). --- clang-tools-extra/clangd/refactor/Rename.cpp | 15 +- .../clangd/unittests/RenameTests.cpp | 138 ++ 2 files changed, 148 insertions(+), 5 deletions(-) diff --git a/clang-tools-extra/clangd/refactor/Rename.cpp b/clang-tools-extra/clangd/refactor/Rename.cpp index 650862c99bcd12..1f0e54c19e2be1 100644 --- a/clang-tools-extra/clangd/refactor/Rename.cpp +++ b/clang-tools-extra/clangd/refactor/Rename.cpp @@ -811,8 +811,14 @@ renameWithinFile(ParsedAST , const NamedDecl , continue; Locs.push_back(RenameLoc); } - if (const auto *MD = dyn_cast()) -return renameObjCMethodWithinFile(AST, MD, NewName, std::move(Locs)); + if (const auto *MD = dyn_cast()) { +if (MD->getSelector().getNumArgs() > 1) + return renameObjCMethodWithinFile(AST, MD, NewName, std::move(Locs)); + +// Eat trailing : for single argument methods since they're actually +// considered a separate token during rename. +NewName.consume_back(":"); + } for (const auto : Locs) { if (auto Err = FilteredChanges.add(tooling::Replacement( SM, CharSourceRange::getTokenRange(Loc), NewName))) @@ -930,10 +936,9 @@ renameOutsideFile(const NamedDecl , llvm::StringRef MainFilePath, std::optional Selector = std::nullopt; llvm::SmallVector NewNames; if (const auto *MD = dyn_cast()) { - if (MD->getSelector().getNumArgs() > 1) { -RenameIdentifier = MD->getSelector().getNameForSlot(0).str(); + RenameIdentifier = MD->getSelector().getNameForSlot(0).str(); + if (MD->getSelector().getNumArgs() > 1) Selector = MD->getSelector(); - } } NewName.split(NewNames, ":"); diff --git a/clang-tools-extra/clangd/unittests/RenameTests.cpp b/clang-tools-extra/clangd/unittests/RenameTests.cpp index d91ef85d672711..7d9252110b27df 100644 --- a/clang-tools-extra/clangd/unittests/RenameTests.cpp +++ b/clang-tools-extra/clangd/unittests/RenameTests.cpp @@ -1943,6 +1943,144 @@ TEST(CrossFileRenameTests, WithUpToDateIndex) { } } +TEST(CrossFileRenameTests, ObjC) { + MockCompilationDatabase CDB; + CDB.ExtraClangFlags = {"-xobjective-c"}; + // rename is runnning on all "^" points in FooH. + struct Case { +llvm::StringRef FooH; +llvm::StringRef FooM; +llvm::StringRef NewName; +llvm::StringRef ExpectedFooH; +llvm::StringRef ExpectedFooM; + }; + Case Cases[] = {// --- Zero arg selector + { + // Input + R"cpp( +@interface Foo +- (int)performA^ction; +@end + )cpp", + R"cpp( +@implementation Foo +- (int)performAction { + [self performAction]; +} +@end + )cpp", + // New name + "performNewAction", + // Expected + R"cpp( +@interface Foo +- (int)performNewAction; +@end + )cpp", + R"cpp( +@implementation Foo +- (int)performNewAction { + [self performNewAction]; +} +@end + )cpp", + }, + // --- Single arg selector + { + // Input + R"cpp( +@interface Foo +- (int)performA^ction:(int)action; +@end + )cpp", + R"cpp( +@implementation Foo +- (int)performAction:(int)action { + [self performAction:action]; +} +@end + )cpp", + // New name + "performNewAction:", + // Expected + R"cpp( +@interface Foo +- (int)performNewAction:(int)action; +@end + )cpp", + R"cpp( +@implementation Foo +- (int)performNewAction:(int)action { + [self performNewAction:action]; +} +@end + )cpp", + }, + // --- Multi arg selector + { + // Input + R"cpp( +@interface Foo +- (int)performA^ction:(int)action with:(int)value; +@end + )cpp", + R"cpp( +@implementation Foo +- (int)performAction:(int)action with:(int)value { + [self performAction:action with:value]; +} +@end + )cpp", + // New name + "performNewAction:by:", + // Expected +
[clang-tools-extra] [clangd] Fix renaming single argument ObjC methods (PR #82396)
@@ -811,8 +811,14 @@ renameWithinFile(ParsedAST , const NamedDecl , continue; Locs.push_back(RenameLoc); } - if (const auto *MD = dyn_cast()) -return renameObjCMethodWithinFile(AST, MD, NewName, std::move(Locs)); + if (const auto *MD = dyn_cast()) { +if (MD->getSelector().getNumArgs() > 1) DavidGoldman wrote: Done. It does work for the one arg selector case but I thought we might as well use the simpler logic. https://github.com/llvm/llvm-project/pull/82396 ___ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang-tools-extra] [clangd] Fix renaming single argument ObjC methods (PR #82396)
https://github.com/DavidGoldman updated https://github.com/llvm/llvm-project/pull/82396 >From 8a9c09575ed143e762faa7abf48285ed6b4b0335 Mon Sep 17 00:00:00 2001 From: David Goldman Date: Tue, 20 Feb 2024 13:14:26 -0500 Subject: [PATCH 1/2] [clangd] Fix renaming single argument ObjC methods Add a few more tests to verify this works (thanks to @ahoppen for the tests and finding this bug). --- clang-tools-extra/clangd/refactor/Rename.cpp | 15 +- .../clangd/unittests/RenameTests.cpp | 138 ++ 2 files changed, 148 insertions(+), 5 deletions(-) diff --git a/clang-tools-extra/clangd/refactor/Rename.cpp b/clang-tools-extra/clangd/refactor/Rename.cpp index 650862c99bcd12..1f0e54c19e2be1 100644 --- a/clang-tools-extra/clangd/refactor/Rename.cpp +++ b/clang-tools-extra/clangd/refactor/Rename.cpp @@ -811,8 +811,14 @@ renameWithinFile(ParsedAST , const NamedDecl , continue; Locs.push_back(RenameLoc); } - if (const auto *MD = dyn_cast()) -return renameObjCMethodWithinFile(AST, MD, NewName, std::move(Locs)); + if (const auto *MD = dyn_cast()) { +if (MD->getSelector().getNumArgs() > 1) + return renameObjCMethodWithinFile(AST, MD, NewName, std::move(Locs)); + +// Eat trailing : for single argument methods since they're actually +// considered a separate token during rename. +NewName.consume_back(":"); + } for (const auto : Locs) { if (auto Err = FilteredChanges.add(tooling::Replacement( SM, CharSourceRange::getTokenRange(Loc), NewName))) @@ -930,10 +936,9 @@ renameOutsideFile(const NamedDecl , llvm::StringRef MainFilePath, std::optional Selector = std::nullopt; llvm::SmallVector NewNames; if (const auto *MD = dyn_cast()) { - if (MD->getSelector().getNumArgs() > 1) { -RenameIdentifier = MD->getSelector().getNameForSlot(0).str(); + RenameIdentifier = MD->getSelector().getNameForSlot(0).str(); + if (MD->getSelector().getNumArgs() > 1) Selector = MD->getSelector(); - } } NewName.split(NewNames, ":"); diff --git a/clang-tools-extra/clangd/unittests/RenameTests.cpp b/clang-tools-extra/clangd/unittests/RenameTests.cpp index d91ef85d672711..7d9252110b27df 100644 --- a/clang-tools-extra/clangd/unittests/RenameTests.cpp +++ b/clang-tools-extra/clangd/unittests/RenameTests.cpp @@ -1943,6 +1943,144 @@ TEST(CrossFileRenameTests, WithUpToDateIndex) { } } +TEST(CrossFileRenameTests, ObjC) { + MockCompilationDatabase CDB; + CDB.ExtraClangFlags = {"-xobjective-c"}; + // rename is runnning on all "^" points in FooH. + struct Case { +llvm::StringRef FooH; +llvm::StringRef FooM; +llvm::StringRef NewName; +llvm::StringRef ExpectedFooH; +llvm::StringRef ExpectedFooM; + }; + Case Cases[] = {// --- Zero arg selector + { + // Input + R"cpp( +@interface Foo +- (int)performA^ction; +@end + )cpp", + R"cpp( +@implementation Foo +- (int)performAction { + [self performAction]; +} +@end + )cpp", + // New name + "performNewAction", + // Expected + R"cpp( +@interface Foo +- (int)performNewAction; +@end + )cpp", + R"cpp( +@implementation Foo +- (int)performNewAction { + [self performNewAction]; +} +@end + )cpp", + }, + // --- Single arg selector + { + // Input + R"cpp( +@interface Foo +- (int)performA^ction:(int)action; +@end + )cpp", + R"cpp( +@implementation Foo +- (int)performAction:(int)action { + [self performAction:action]; +} +@end + )cpp", + // New name + "performNewAction:", + // Expected + R"cpp( +@interface Foo +- (int)performNewAction:(int)action; +@end + )cpp", + R"cpp( +@implementation Foo +- (int)performNewAction:(int)action { + [self performNewAction:action]; +} +@end + )cpp", + }, + // --- Multi arg selector + { + // Input + R"cpp( +@interface Foo +- (int)performA^ction:(int)action with:(int)value; +@end + )cpp", + R"cpp( +@implementation Foo +- (int)performAction:(int)action with:(int)value { + [self performAction:action with:value]; +} +@end + )cpp", + // New name + "performNewAction:by:", + // Expected +
[clang-tools-extra] [clangd] Fix renaming single argument ObjC methods (PR #82396)
https://github.com/DavidGoldman created https://github.com/llvm/llvm-project/pull/82396 Use the legacy non-ObjC rename logic when dealing with selectors that have zero or one arguments. In addition, make sure we don't add an extra `:` during the rename. Add a few more tests to verify this works (thanks to @ahoppen for the tests and finding this bug). >From 8a9c09575ed143e762faa7abf48285ed6b4b0335 Mon Sep 17 00:00:00 2001 From: David Goldman Date: Tue, 20 Feb 2024 13:14:26 -0500 Subject: [PATCH] [clangd] Fix renaming single argument ObjC methods Add a few more tests to verify this works (thanks to @ahoppen for the tests and finding this bug). --- clang-tools-extra/clangd/refactor/Rename.cpp | 15 +- .../clangd/unittests/RenameTests.cpp | 138 ++ 2 files changed, 148 insertions(+), 5 deletions(-) diff --git a/clang-tools-extra/clangd/refactor/Rename.cpp b/clang-tools-extra/clangd/refactor/Rename.cpp index 650862c99bcd12..1f0e54c19e2be1 100644 --- a/clang-tools-extra/clangd/refactor/Rename.cpp +++ b/clang-tools-extra/clangd/refactor/Rename.cpp @@ -811,8 +811,14 @@ renameWithinFile(ParsedAST , const NamedDecl , continue; Locs.push_back(RenameLoc); } - if (const auto *MD = dyn_cast()) -return renameObjCMethodWithinFile(AST, MD, NewName, std::move(Locs)); + if (const auto *MD = dyn_cast()) { +if (MD->getSelector().getNumArgs() > 1) + return renameObjCMethodWithinFile(AST, MD, NewName, std::move(Locs)); + +// Eat trailing : for single argument methods since they're actually +// considered a separate token during rename. +NewName.consume_back(":"); + } for (const auto : Locs) { if (auto Err = FilteredChanges.add(tooling::Replacement( SM, CharSourceRange::getTokenRange(Loc), NewName))) @@ -930,10 +936,9 @@ renameOutsideFile(const NamedDecl , llvm::StringRef MainFilePath, std::optional Selector = std::nullopt; llvm::SmallVector NewNames; if (const auto *MD = dyn_cast()) { - if (MD->getSelector().getNumArgs() > 1) { -RenameIdentifier = MD->getSelector().getNameForSlot(0).str(); + RenameIdentifier = MD->getSelector().getNameForSlot(0).str(); + if (MD->getSelector().getNumArgs() > 1) Selector = MD->getSelector(); - } } NewName.split(NewNames, ":"); diff --git a/clang-tools-extra/clangd/unittests/RenameTests.cpp b/clang-tools-extra/clangd/unittests/RenameTests.cpp index d91ef85d672711..7d9252110b27df 100644 --- a/clang-tools-extra/clangd/unittests/RenameTests.cpp +++ b/clang-tools-extra/clangd/unittests/RenameTests.cpp @@ -1943,6 +1943,144 @@ TEST(CrossFileRenameTests, WithUpToDateIndex) { } } +TEST(CrossFileRenameTests, ObjC) { + MockCompilationDatabase CDB; + CDB.ExtraClangFlags = {"-xobjective-c"}; + // rename is runnning on all "^" points in FooH. + struct Case { +llvm::StringRef FooH; +llvm::StringRef FooM; +llvm::StringRef NewName; +llvm::StringRef ExpectedFooH; +llvm::StringRef ExpectedFooM; + }; + Case Cases[] = {// --- Zero arg selector + { + // Input + R"cpp( +@interface Foo +- (int)performA^ction; +@end + )cpp", + R"cpp( +@implementation Foo +- (int)performAction { + [self performAction]; +} +@end + )cpp", + // New name + "performNewAction", + // Expected + R"cpp( +@interface Foo +- (int)performNewAction; +@end + )cpp", + R"cpp( +@implementation Foo +- (int)performNewAction { + [self performNewAction]; +} +@end + )cpp", + }, + // --- Single arg selector + { + // Input + R"cpp( +@interface Foo +- (int)performA^ction:(int)action; +@end + )cpp", + R"cpp( +@implementation Foo +- (int)performAction:(int)action { + [self performAction:action]; +} +@end + )cpp", + // New name + "performNewAction:", + // Expected + R"cpp( +@interface Foo +- (int)performNewAction:(int)action; +@end + )cpp", + R"cpp( +@implementation Foo +- (int)performNewAction:(int)action { + [self performNewAction:action]; +} +@end + )cpp", + }, + // --- Multi arg selector + { + // Input + R"cpp( +@interface Foo +- (int)performA^ction:(int)action with:(int)value; +@end + )cpp", + R"cpp( +@implementation Foo +
[clang-tools-extra] [clangd] Add support for renaming ObjC properties and implicit properties (PR #81775)
https://github.com/DavidGoldman updated https://github.com/llvm/llvm-project/pull/81775 >From 7a3859fc5f5f52fb283fd0b2feda57521ca88240 Mon Sep 17 00:00:00 2001 From: David Goldman Date: Fri, 26 Jan 2024 15:50:11 -0500 Subject: [PATCH 1/4] Add support for ObjC property renaming + implicit property renaming Objective-C properties can generate multiple decls: - an ivar decl - a getter method decl - a setter method decl Triggering the rename from any of those decls should trigger a rename of the property and use the proper name for each one according to the ObjC property naming conventions. I've added similar support for implicit properties, which is when a getter or setter like method is referred to via the property syntax (self.method) without an explicit property decl. --- clang-tools-extra/clangd/FindTarget.cpp | 16 + clang-tools-extra/clangd/refactor/Rename.cpp | 329 +++--- .../clangd/unittests/RenameTests.cpp | 90 + .../unittests/SemanticHighlightingTests.cpp | 2 +- 4 files changed, 381 insertions(+), 56 deletions(-) diff --git a/clang-tools-extra/clangd/FindTarget.cpp b/clang-tools-extra/clangd/FindTarget.cpp index e702c6b3537a09..9def867011f696 100644 --- a/clang-tools-extra/clangd/FindTarget.cpp +++ b/clang-tools-extra/clangd/FindTarget.cpp @@ -740,6 +740,22 @@ llvm::SmallVector refInDecl(const Decl *D, /*IsDecl=*/true, {OIMD}}); } + +void VisitObjCPropertyImplDecl(const ObjCPropertyImplDecl *OPID) { + // Skiped compiler synthesized property impl decls - they will always + // have an invalid loc. + if (OPID->getLocation().isInvalid()) +return; + if (OPID->isIvarNameSpecified()) +Refs.push_back(ReferenceLoc{NestedNameSpecifierLoc(), +OPID->getPropertyIvarDeclLoc(), +/*IsDecl=*/false, +{OPID->getPropertyIvarDecl()}}); + Refs.push_back(ReferenceLoc{NestedNameSpecifierLoc(), + OPID->getLocation(), + /*IsDecl=*/false, + {OPID->getPropertyDecl()}}); +} }; Visitor V{Resolver}; diff --git a/clang-tools-extra/clangd/refactor/Rename.cpp b/clang-tools-extra/clangd/refactor/Rename.cpp index 650862c99bcd12..8a3d664f8cb9b1 100644 --- a/clang-tools-extra/clangd/refactor/Rename.cpp +++ b/clang-tools-extra/clangd/refactor/Rename.cpp @@ -21,6 +21,7 @@ #include "clang/AST/DeclCXX.h" #include "clang/AST/DeclObjC.h" #include "clang/AST/DeclTemplate.h" +#include "clang/AST/ExprObjC.h" #include "clang/AST/ParentMapContext.h" #include "clang/AST/Stmt.h" #include "clang/Basic/CharInfo.h" @@ -153,8 +154,111 @@ const NamedDecl *pickInterestingTarget(const NamedDecl *D) { return D; } -llvm::DenseSet locateDeclAt(ParsedAST , - SourceLocation TokenStartLoc) { +// Some AST nodes are synthesized by the compiler based on other nodes. e.g. +// ObjC methods and ivars can be synthesized based on an Objective-C property. +// +// We perform this check outside of canonicalization since we need to know which +// decl the user has actually triggered the rename on in order to remap all +// derived decls properly, since the naming patterns can slightly differ for +// every decl that the compiler synthesizes. +const NamedDecl *findOriginDecl(const NamedDecl *D) { + if (const auto *MD = dyn_cast(D)) { +if (const auto *PD = MD->findPropertyDecl(/*CheckOverrides=*/false)) + // FIXME(davg): We should only map to the protocol if the user hasn't + // explicitly given a setter/getter for the method - if they have we + // should either fail the rename or support basic 1 arg selector renames. + return canonicalRenameDecl(PD); + } + if (const auto *ID = dyn_cast(D)) { +for (const auto *PD : ID->getContainingInterface()->properties()) { + if (PD->getPropertyIvarDecl() == ID) +return canonicalRenameDecl(PD); +} + } + return D; +} + +std::string propertySetterName(llvm::StringRef PropertyName) { + std::string Setter = PropertyName.str(); + if (!Setter.empty()) +Setter[0] = llvm::toUpper(Setter[0]); + return "set" + Setter + ":"; +} + +// Returns a non-empty string if valid. +std::string setterToPropertyName(llvm::StringRef Setter) { + std::string Result; + if (!Setter.consume_front("set")) { +return Result; + } + Setter.consume_back(":"); // Optional. + Result = Setter.str(); + if (!Result.empty()) +Result[0] = llvm::toLower(Result[0]); + return Result; +} + +llvm::DenseMap +computeAllDeclsToNewName(const NamedDecl *Selected, llvm::StringRef NewName, + const NamedDecl *Origin) { + llvm::DenseMap DeclToName; + DeclToName[Selected] = NewName.str(); + + if (const auto *PD = dyn_cast(Origin)) { +// Need to derive
[clang-tools-extra] [clangd] Add support for renaming ObjC properties and implicit properties (PR #81775)
https://github.com/DavidGoldman updated https://github.com/llvm/llvm-project/pull/81775 >From 7a3859fc5f5f52fb283fd0b2feda57521ca88240 Mon Sep 17 00:00:00 2001 From: David Goldman Date: Fri, 26 Jan 2024 15:50:11 -0500 Subject: [PATCH 1/3] Add support for ObjC property renaming + implicit property renaming Objective-C properties can generate multiple decls: - an ivar decl - a getter method decl - a setter method decl Triggering the rename from any of those decls should trigger a rename of the property and use the proper name for each one according to the ObjC property naming conventions. I've added similar support for implicit properties, which is when a getter or setter like method is referred to via the property syntax (self.method) without an explicit property decl. --- clang-tools-extra/clangd/FindTarget.cpp | 16 + clang-tools-extra/clangd/refactor/Rename.cpp | 329 +++--- .../clangd/unittests/RenameTests.cpp | 90 + .../unittests/SemanticHighlightingTests.cpp | 2 +- 4 files changed, 381 insertions(+), 56 deletions(-) diff --git a/clang-tools-extra/clangd/FindTarget.cpp b/clang-tools-extra/clangd/FindTarget.cpp index e702c6b3537a09..9def867011f696 100644 --- a/clang-tools-extra/clangd/FindTarget.cpp +++ b/clang-tools-extra/clangd/FindTarget.cpp @@ -740,6 +740,22 @@ llvm::SmallVector refInDecl(const Decl *D, /*IsDecl=*/true, {OIMD}}); } + +void VisitObjCPropertyImplDecl(const ObjCPropertyImplDecl *OPID) { + // Skiped compiler synthesized property impl decls - they will always + // have an invalid loc. + if (OPID->getLocation().isInvalid()) +return; + if (OPID->isIvarNameSpecified()) +Refs.push_back(ReferenceLoc{NestedNameSpecifierLoc(), +OPID->getPropertyIvarDeclLoc(), +/*IsDecl=*/false, +{OPID->getPropertyIvarDecl()}}); + Refs.push_back(ReferenceLoc{NestedNameSpecifierLoc(), + OPID->getLocation(), + /*IsDecl=*/false, + {OPID->getPropertyDecl()}}); +} }; Visitor V{Resolver}; diff --git a/clang-tools-extra/clangd/refactor/Rename.cpp b/clang-tools-extra/clangd/refactor/Rename.cpp index 650862c99bcd12..8a3d664f8cb9b1 100644 --- a/clang-tools-extra/clangd/refactor/Rename.cpp +++ b/clang-tools-extra/clangd/refactor/Rename.cpp @@ -21,6 +21,7 @@ #include "clang/AST/DeclCXX.h" #include "clang/AST/DeclObjC.h" #include "clang/AST/DeclTemplate.h" +#include "clang/AST/ExprObjC.h" #include "clang/AST/ParentMapContext.h" #include "clang/AST/Stmt.h" #include "clang/Basic/CharInfo.h" @@ -153,8 +154,111 @@ const NamedDecl *pickInterestingTarget(const NamedDecl *D) { return D; } -llvm::DenseSet locateDeclAt(ParsedAST , - SourceLocation TokenStartLoc) { +// Some AST nodes are synthesized by the compiler based on other nodes. e.g. +// ObjC methods and ivars can be synthesized based on an Objective-C property. +// +// We perform this check outside of canonicalization since we need to know which +// decl the user has actually triggered the rename on in order to remap all +// derived decls properly, since the naming patterns can slightly differ for +// every decl that the compiler synthesizes. +const NamedDecl *findOriginDecl(const NamedDecl *D) { + if (const auto *MD = dyn_cast(D)) { +if (const auto *PD = MD->findPropertyDecl(/*CheckOverrides=*/false)) + // FIXME(davg): We should only map to the protocol if the user hasn't + // explicitly given a setter/getter for the method - if they have we + // should either fail the rename or support basic 1 arg selector renames. + return canonicalRenameDecl(PD); + } + if (const auto *ID = dyn_cast(D)) { +for (const auto *PD : ID->getContainingInterface()->properties()) { + if (PD->getPropertyIvarDecl() == ID) +return canonicalRenameDecl(PD); +} + } + return D; +} + +std::string propertySetterName(llvm::StringRef PropertyName) { + std::string Setter = PropertyName.str(); + if (!Setter.empty()) +Setter[0] = llvm::toUpper(Setter[0]); + return "set" + Setter + ":"; +} + +// Returns a non-empty string if valid. +std::string setterToPropertyName(llvm::StringRef Setter) { + std::string Result; + if (!Setter.consume_front("set")) { +return Result; + } + Setter.consume_back(":"); // Optional. + Result = Setter.str(); + if (!Result.empty()) +Result[0] = llvm::toLower(Result[0]); + return Result; +} + +llvm::DenseMap +computeAllDeclsToNewName(const NamedDecl *Selected, llvm::StringRef NewName, + const NamedDecl *Origin) { + llvm::DenseMap DeclToName; + DeclToName[Selected] = NewName.str(); + + if (const auto *PD = dyn_cast(Origin)) { +// Need to derive
[clang-tools-extra] [clangd] Add support for renaming ObjC properties and implicit properties (PR #81775)
https://github.com/DavidGoldman updated https://github.com/llvm/llvm-project/pull/81775 >From 7a3859fc5f5f52fb283fd0b2feda57521ca88240 Mon Sep 17 00:00:00 2001 From: David Goldman Date: Fri, 26 Jan 2024 15:50:11 -0500 Subject: [PATCH 1/2] Add support for ObjC property renaming + implicit property renaming Objective-C properties can generate multiple decls: - an ivar decl - a getter method decl - a setter method decl Triggering the rename from any of those decls should trigger a rename of the property and use the proper name for each one according to the ObjC property naming conventions. I've added similar support for implicit properties, which is when a getter or setter like method is referred to via the property syntax (self.method) without an explicit property decl. --- clang-tools-extra/clangd/FindTarget.cpp | 16 + clang-tools-extra/clangd/refactor/Rename.cpp | 329 +++--- .../clangd/unittests/RenameTests.cpp | 90 + .../unittests/SemanticHighlightingTests.cpp | 2 +- 4 files changed, 381 insertions(+), 56 deletions(-) diff --git a/clang-tools-extra/clangd/FindTarget.cpp b/clang-tools-extra/clangd/FindTarget.cpp index e702c6b3537a09..9def867011f696 100644 --- a/clang-tools-extra/clangd/FindTarget.cpp +++ b/clang-tools-extra/clangd/FindTarget.cpp @@ -740,6 +740,22 @@ llvm::SmallVector refInDecl(const Decl *D, /*IsDecl=*/true, {OIMD}}); } + +void VisitObjCPropertyImplDecl(const ObjCPropertyImplDecl *OPID) { + // Skiped compiler synthesized property impl decls - they will always + // have an invalid loc. + if (OPID->getLocation().isInvalid()) +return; + if (OPID->isIvarNameSpecified()) +Refs.push_back(ReferenceLoc{NestedNameSpecifierLoc(), +OPID->getPropertyIvarDeclLoc(), +/*IsDecl=*/false, +{OPID->getPropertyIvarDecl()}}); + Refs.push_back(ReferenceLoc{NestedNameSpecifierLoc(), + OPID->getLocation(), + /*IsDecl=*/false, + {OPID->getPropertyDecl()}}); +} }; Visitor V{Resolver}; diff --git a/clang-tools-extra/clangd/refactor/Rename.cpp b/clang-tools-extra/clangd/refactor/Rename.cpp index 650862c99bcd12..8a3d664f8cb9b1 100644 --- a/clang-tools-extra/clangd/refactor/Rename.cpp +++ b/clang-tools-extra/clangd/refactor/Rename.cpp @@ -21,6 +21,7 @@ #include "clang/AST/DeclCXX.h" #include "clang/AST/DeclObjC.h" #include "clang/AST/DeclTemplate.h" +#include "clang/AST/ExprObjC.h" #include "clang/AST/ParentMapContext.h" #include "clang/AST/Stmt.h" #include "clang/Basic/CharInfo.h" @@ -153,8 +154,111 @@ const NamedDecl *pickInterestingTarget(const NamedDecl *D) { return D; } -llvm::DenseSet locateDeclAt(ParsedAST , - SourceLocation TokenStartLoc) { +// Some AST nodes are synthesized by the compiler based on other nodes. e.g. +// ObjC methods and ivars can be synthesized based on an Objective-C property. +// +// We perform this check outside of canonicalization since we need to know which +// decl the user has actually triggered the rename on in order to remap all +// derived decls properly, since the naming patterns can slightly differ for +// every decl that the compiler synthesizes. +const NamedDecl *findOriginDecl(const NamedDecl *D) { + if (const auto *MD = dyn_cast(D)) { +if (const auto *PD = MD->findPropertyDecl(/*CheckOverrides=*/false)) + // FIXME(davg): We should only map to the protocol if the user hasn't + // explicitly given a setter/getter for the method - if they have we + // should either fail the rename or support basic 1 arg selector renames. + return canonicalRenameDecl(PD); + } + if (const auto *ID = dyn_cast(D)) { +for (const auto *PD : ID->getContainingInterface()->properties()) { + if (PD->getPropertyIvarDecl() == ID) +return canonicalRenameDecl(PD); +} + } + return D; +} + +std::string propertySetterName(llvm::StringRef PropertyName) { + std::string Setter = PropertyName.str(); + if (!Setter.empty()) +Setter[0] = llvm::toUpper(Setter[0]); + return "set" + Setter + ":"; +} + +// Returns a non-empty string if valid. +std::string setterToPropertyName(llvm::StringRef Setter) { + std::string Result; + if (!Setter.consume_front("set")) { +return Result; + } + Setter.consume_back(":"); // Optional. + Result = Setter.str(); + if (!Result.empty()) +Result[0] = llvm::toLower(Result[0]); + return Result; +} + +llvm::DenseMap +computeAllDeclsToNewName(const NamedDecl *Selected, llvm::StringRef NewName, + const NamedDecl *Origin) { + llvm::DenseMap DeclToName; + DeclToName[Selected] = NewName.str(); + + if (const auto *PD = dyn_cast(Origin)) { +// Need to derive
[clang-tools-extra] [clangd] Add support for renaming ObjC properties and implicit properties (PR #81775)
https://github.com/DavidGoldman updated https://github.com/llvm/llvm-project/pull/81775 >From 7a3859fc5f5f52fb283fd0b2feda57521ca88240 Mon Sep 17 00:00:00 2001 From: David Goldman Date: Fri, 26 Jan 2024 15:50:11 -0500 Subject: [PATCH] Add support for ObjC property renaming + implicit property renaming Objective-C properties can generate multiple decls: - an ivar decl - a getter method decl - a setter method decl Triggering the rename from any of those decls should trigger a rename of the property and use the proper name for each one according to the ObjC property naming conventions. I've added similar support for implicit properties, which is when a getter or setter like method is referred to via the property syntax (self.method) without an explicit property decl. --- clang-tools-extra/clangd/FindTarget.cpp | 16 + clang-tools-extra/clangd/refactor/Rename.cpp | 329 +++--- .../clangd/unittests/RenameTests.cpp | 90 + .../unittests/SemanticHighlightingTests.cpp | 2 +- 4 files changed, 381 insertions(+), 56 deletions(-) diff --git a/clang-tools-extra/clangd/FindTarget.cpp b/clang-tools-extra/clangd/FindTarget.cpp index e702c6b3537a09..9def867011f696 100644 --- a/clang-tools-extra/clangd/FindTarget.cpp +++ b/clang-tools-extra/clangd/FindTarget.cpp @@ -740,6 +740,22 @@ llvm::SmallVector refInDecl(const Decl *D, /*IsDecl=*/true, {OIMD}}); } + +void VisitObjCPropertyImplDecl(const ObjCPropertyImplDecl *OPID) { + // Skiped compiler synthesized property impl decls - they will always + // have an invalid loc. + if (OPID->getLocation().isInvalid()) +return; + if (OPID->isIvarNameSpecified()) +Refs.push_back(ReferenceLoc{NestedNameSpecifierLoc(), +OPID->getPropertyIvarDeclLoc(), +/*IsDecl=*/false, +{OPID->getPropertyIvarDecl()}}); + Refs.push_back(ReferenceLoc{NestedNameSpecifierLoc(), + OPID->getLocation(), + /*IsDecl=*/false, + {OPID->getPropertyDecl()}}); +} }; Visitor V{Resolver}; diff --git a/clang-tools-extra/clangd/refactor/Rename.cpp b/clang-tools-extra/clangd/refactor/Rename.cpp index 650862c99bcd12..8a3d664f8cb9b1 100644 --- a/clang-tools-extra/clangd/refactor/Rename.cpp +++ b/clang-tools-extra/clangd/refactor/Rename.cpp @@ -21,6 +21,7 @@ #include "clang/AST/DeclCXX.h" #include "clang/AST/DeclObjC.h" #include "clang/AST/DeclTemplate.h" +#include "clang/AST/ExprObjC.h" #include "clang/AST/ParentMapContext.h" #include "clang/AST/Stmt.h" #include "clang/Basic/CharInfo.h" @@ -153,8 +154,111 @@ const NamedDecl *pickInterestingTarget(const NamedDecl *D) { return D; } -llvm::DenseSet locateDeclAt(ParsedAST , - SourceLocation TokenStartLoc) { +// Some AST nodes are synthesized by the compiler based on other nodes. e.g. +// ObjC methods and ivars can be synthesized based on an Objective-C property. +// +// We perform this check outside of canonicalization since we need to know which +// decl the user has actually triggered the rename on in order to remap all +// derived decls properly, since the naming patterns can slightly differ for +// every decl that the compiler synthesizes. +const NamedDecl *findOriginDecl(const NamedDecl *D) { + if (const auto *MD = dyn_cast(D)) { +if (const auto *PD = MD->findPropertyDecl(/*CheckOverrides=*/false)) + // FIXME(davg): We should only map to the protocol if the user hasn't + // explicitly given a setter/getter for the method - if they have we + // should either fail the rename or support basic 1 arg selector renames. + return canonicalRenameDecl(PD); + } + if (const auto *ID = dyn_cast(D)) { +for (const auto *PD : ID->getContainingInterface()->properties()) { + if (PD->getPropertyIvarDecl() == ID) +return canonicalRenameDecl(PD); +} + } + return D; +} + +std::string propertySetterName(llvm::StringRef PropertyName) { + std::string Setter = PropertyName.str(); + if (!Setter.empty()) +Setter[0] = llvm::toUpper(Setter[0]); + return "set" + Setter + ":"; +} + +// Returns a non-empty string if valid. +std::string setterToPropertyName(llvm::StringRef Setter) { + std::string Result; + if (!Setter.consume_front("set")) { +return Result; + } + Setter.consume_back(":"); // Optional. + Result = Setter.str(); + if (!Result.empty()) +Result[0] = llvm::toLower(Result[0]); + return Result; +} + +llvm::DenseMap +computeAllDeclsToNewName(const NamedDecl *Selected, llvm::StringRef NewName, + const NamedDecl *Origin) { + llvm::DenseMap DeclToName; + DeclToName[Selected] = NewName.str(); + + if (const auto *PD = dyn_cast(Origin)) { +// Need to derive the
[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)
https://github.com/DavidGoldman closed https://github.com/llvm/llvm-project/pull/76466 ___ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang-tools-extra] [clangd] Add support for renaming ObjC properties and implicit properties (PR #81775)
https://github.com/DavidGoldman created https://github.com/llvm/llvm-project/pull/81775 Add support for renaming Objective-C properties. These are special since a single property decl could generate 3 different decls - a getter method, a setter method, and an instance variable. In addition, support renaming implicit properties, e.g. referring to a getter method `- (id)foo;` via `self.foo` or a setter method `- (void)setFoo:(id)foo;` via `self.foo =` without an explicit property decl. >From 4caf5b3c779bf18236b4b0be5bc7147d10339f2b Mon Sep 17 00:00:00 2001 From: David Goldman Date: Tue, 26 Dec 2023 15:59:01 -0500 Subject: [PATCH 1/9] [clangd][SymbolCollector] Treat ObjC methods as spelled We'll treat multi-arg methods as spelled once we have full rename support for them. --- .../clangd/index/SymbolCollector.cpp | 6 ++- .../clangd/unittests/SymbolCollectorTests.cpp | 42 +++ 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/clang-tools-extra/clangd/index/SymbolCollector.cpp b/clang-tools-extra/clangd/index/SymbolCollector.cpp index 7ef4b15febad22..336bc3506bb360 100644 --- a/clang-tools-extra/clangd/index/SymbolCollector.cpp +++ b/clang-tools-extra/clangd/index/SymbolCollector.cpp @@ -174,7 +174,9 @@ bool isSpelled(SourceLocation Loc, const NamedDecl ) { auto Name = ND.getDeclName(); const auto NameKind = Name.getNameKind(); if (NameKind != DeclarationName::Identifier && - NameKind != DeclarationName::CXXConstructorName) + NameKind != DeclarationName::CXXConstructorName && + NameKind != DeclarationName::ObjCZeroArgSelector && + NameKind != DeclarationName::ObjCOneArgSelector) return false; const auto = ND.getASTContext(); const auto = AST.getSourceManager(); @@ -183,6 +185,8 @@ bool isSpelled(SourceLocation Loc, const NamedDecl ) { if (clang::Lexer::getRawToken(Loc, Tok, SM, LO)) return false; auto StrName = Name.getAsString(); + if (const auto *MD = dyn_cast()) +StrName = MD->getSelector().getNameForSlot(0).str(); return clang::Lexer::getSpelling(Tok, SM, LO) == StrName; } } // namespace diff --git a/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp b/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp index 9cdc57ec01f327..1d4e1c1d75ea23 100644 --- a/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp +++ b/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp @@ -105,6 +105,9 @@ MATCHER(refRange, "") { MATCHER_P2(OverriddenBy, Subject, Object, "") { return arg == Relation{Subject.ID, RelationKind::OverriddenBy, Object.ID}; } +MATCHER(isSpelled, "") { + return static_cast(arg.Kind & RefKind::Spelled); +} ::testing::Matcher &> haveRanges(const std::vector Ranges) { return ::testing::UnorderedPointwise(refRange(), Ranges); @@ -524,6 +527,45 @@ TEST_F(SymbolCollectorTest, templateArgs) { forCodeCompletion(false); } +TEST_F(SymbolCollectorTest, ObjCRefs) { + Annotations Header(R"( + @interface Person + - (void)$talk[[talk]]; + - (void)$say[[say]]:(id)something; + @end + @interface Person (Category) + - (void)categoryMethod; + - (void)multiArg:(id)a method:(id)b; + @end + )"); + Annotations Main(R"( + @implementation Person + - (void)$talk[[talk]] {} + - (void)$say[[say]]:(id)something {} + @end + + void fff(Person *p) { +[p $talk[[talk]]]; +[p $say[[say]]:0]; +[p categoryMethod]; +[p multiArg:0 method:0]; + } + )"); + CollectorOpts.RefFilter = RefKind::All; + CollectorOpts.CollectMainFileRefs = true; + TestFileName = testPath("test.m"); + runSymbolCollector(Header.code(), Main.code(), + {"-fblocks", "-xobjective-c++", "-Wno-objc-root-class"}); + EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "Person::talk").ID, + haveRanges(Main.ranges("talk"); + EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "Person::say:").ID, + haveRanges(Main.ranges("say"); + EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "Person::categoryMethod").ID, + ElementsAre(isSpelled(); + EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "Person::multiArg:method:").ID, + ElementsAre(Not(isSpelled()); +} + TEST_F(SymbolCollectorTest, ObjCSymbols) { const std::string Header = R"( @interface Person >From 1b6a09464ff5c7b1988fcb479d0a4ff876f696e6 Mon Sep 17 00:00:00 2001 From: David Goldman Date: Tue, 26 Dec 2023 16:12:03 -0500 Subject: [PATCH 2/9] Run clang-format --- .../clangd/unittests/SymbolCollectorTests.cpp | 10 ++ 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp b/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp index 1d4e1c1d75ea23..5c20b950e4eac0 100644 ---
[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)
DavidGoldman wrote: Thanks, PTAL, I'll save the remaining comments for follow ups. https://github.com/llvm/llvm-project/pull/76466 ___ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)
@@ -569,8 +840,13 @@ renameWithinFile(ParsedAST , const NamedDecl , // } if (!isInsideMainFile(RenameLoc, SM)) continue; +Locs.push_back(RenameLoc); + } + if (const auto *MD = dyn_cast()) +return renameObjCMethodWithinFile(AST, MD, NewName, std::move(Locs)); DavidGoldman wrote: SGTM, I'll look into this in a follow up (+ add some tests for that). https://github.com/llvm/llvm-project/pull/76466 ___ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)
@@ -53,13 +55,34 @@ struct RenameInputs { struct RenameResult { // The range of the symbol that the user can attempt to rename. Range Target; + // Placeholder text for the rename operation if non-empty. + std::string Placeholder; // Rename occurrences for the current main file. std::vector LocalChanges; // Complete edits for the rename, including LocalChanges. // If the full set of changes is unknown, this field is empty. FileEdits GlobalChanges; }; +/// Represents a symbol range where the symbol can potentially have multiple +/// tokens. +struct SymbolRange { + /// Ranges for the tokens that make up the symbol's name. + /// Usually a single range, but there can be multiple ranges if the tokens for + /// the symbol are split, e.g. ObjC selectors. + std::vector Ranges; DavidGoldman wrote: That makes sense, I think we might need to improve the empty selector case - I'll do that in a follow up. https://github.com/llvm/llvm-project/pull/76466 ___ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)
@@ -538,11 +564,205 @@ std::optional checkName(const NamedDecl , Conflict->getLocation().printToString(ASTCtx.getSourceManager())}; } } - if (Result) + if (Result) { InvalidNameMetric.record(1, toString(Result->K)); +return makeError(*Result); + } + return llvm::Error::success(); +} + +bool isSelectorLike(const syntax::Token , const syntax::Token ) { + return Cur.kind() == tok::identifier && Next.kind() == tok::colon && + // We require the selector name and : to be contiguous. + // e.g. support `foo:` but not `foo :`. + Cur.endLocation() == Next.location(); +} + +bool isMatchingSelectorName(const syntax::Token , const syntax::Token , +const SourceManager , +llvm::StringRef SelectorName) { + if (SelectorName.empty()) +return Cur.kind() == tok::colon; + return isSelectorLike(Cur, Next) && Cur.text(SM) == SelectorName; +} + +std::vector findAllSelectorPieces(llvm::ArrayRef Tokens, DavidGoldman wrote: Done https://github.com/llvm/llvm-project/pull/76466 ___ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)
@@ -538,11 +565,254 @@ std::optional checkName(const NamedDecl , Conflict->getLocation().printToString(ASTCtx.getSourceManager())}; } } - if (Result) + if (Result) { InvalidNameMetric.record(1, toString(Result->K)); +return makeError(*Result); + } + return std::nullopt; +} + +bool isMatchingSelectorName(const syntax::Token , const syntax::Token , +const SourceManager , +llvm::StringRef SelectorName) { + if (SelectorName.empty()) +return Cur.kind() == tok::colon; + return Cur.kind() == tok::identifier && Next.kind() == tok::colon && + Cur.text(SM) == SelectorName && + // We require the selector name and : to be contiguous to avoid + // potential conflicts with ternary expression. + // + // e.g. support `foo:` but not `foo :`. + Cur.endLocation() == Next.location(); +} + +bool isSelectorLike(const syntax::Token , const syntax::Token ) { + return Cur.kind() == tok::identifier && Next.kind() == tok::colon && + // We require the selector name and : to be contiguous. + // e.g. support `foo:` but not `foo :`. + Cur.endLocation() == Next.location(); +} + +bool parseMessageExpression(llvm::ArrayRef Tokens, +const SourceManager , unsigned Index, +unsigned Last, Selector Sel, +std::vector ) { + + unsigned NumArgs = Sel.getNumArgs(); + llvm::SmallVector Closes; + SelectorPieces.clear(); + while (Index < Last) { +const auto = Tokens[Index]; + +if (Closes.empty()) { + auto PieceCount = SelectorPieces.size(); + if (PieceCount < NumArgs && + isMatchingSelectorName(Tok, Tokens[Index + 1], SM, + Sel.getNameForSlot(PieceCount))) { +// If 'foo:' instead of ':' (empty selector), we need to skip the ':' +// token after the name. +if (!Sel.getNameForSlot(PieceCount).empty()) { + ++Index; +} +SelectorPieces.push_back( +halfOpenToRange(SM, Tok.range(SM).toCharRange(SM))); +continue; + } + // If we've found all pieces but the current token looks like another + // selector piece, it means the method being renamed is a strict prefix of + // the selector we've found - should be skipped. + if (SelectorPieces.size() >= NumArgs && + isSelectorLike(Tok, Tokens[Index + 1])) +return false; +} + +switch (Tok.kind()) { +case tok::l_square: + Closes.push_back(']'); + break; +case tok::l_paren: + Closes.push_back(')'); + break; +case tok::l_brace: + Closes.push_back('}'); + break; +case tok::r_square: + if (Closes.empty()) +return SelectorPieces.size() == NumArgs; + + if (Closes.back() != ']') +return false; + Closes.pop_back(); + break; +case tok::r_paren: + if (Closes.empty() || Closes.back() != ')') +return false; + Closes.pop_back(); + break; +case tok::r_brace: + if (Closes.empty() || Closes.back() != '}') +return false; + Closes.pop_back(); + break; +case tok::semi: + // top level ; ends all statements. + if (Closes.empty()) +return false; + break; +default: + break; +} + +++Index; + } + return false; +} + +/// Collects all ranges of the given identifier/selector in the source code. +/// +/// If a selector is given, this does a full lex of the given source code in +/// order to identify all selector fragments (e.g. in method exprs/decls) since +/// they are non-contiguous. +std::vector collectRenameIdentifierRanges( +llvm::StringRef Identifier, llvm::StringRef Content, +const LangOptions , std::optional Selector) { + std::vector Ranges; + if (!Selector) { +auto IdentifierRanges = +collectIdentifierRanges(Identifier, Content, LangOpts); +for (const auto : IdentifierRanges) + Ranges.emplace_back(R); +return Ranges; + } + // FIXME: InMemoryFileAdapter crashes unless the buffer is null terminated! + std::string NullTerminatedCode = Content.str(); + SourceManagerForFile FileSM("mock_file_name.cpp", NullTerminatedCode); + auto = FileSM.get(); + + // We track parens and braces to ensure that we don't accidentally try parsing + // a method declaration or definition which isn't at the top level or similar + // looking expressions (e.g. an @selector() expression). + unsigned ParenCount = 0; + unsigned BraceCount = 0; + unsigned NumArgs = Selector->getNumArgs(); + + std::vector SelectorPieces; + auto Tokens = syntax::tokenize(SM.getMainFileID(), SM, LangOpts); + unsigned Last = Tokens.size() - 1; + for (unsigned Index = 0; Index < Last; ++Index) { +const auto = Tokens[Index]; + DavidGoldman wrote: Done. This lexer logic runs independent of those
[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)
@@ -538,11 +565,254 @@ std::optional checkName(const NamedDecl , Conflict->getLocation().printToString(ASTCtx.getSourceManager())}; } } - if (Result) + if (Result) { InvalidNameMetric.record(1, toString(Result->K)); +return makeError(*Result); + } + return std::nullopt; +} + +bool isMatchingSelectorName(const syntax::Token , const syntax::Token , +const SourceManager , +llvm::StringRef SelectorName) { + if (SelectorName.empty()) +return Cur.kind() == tok::colon; + return Cur.kind() == tok::identifier && Next.kind() == tok::colon && + Cur.text(SM) == SelectorName && + // We require the selector name and : to be contiguous to avoid + // potential conflicts with ternary expression. + // + // e.g. support `foo:` but not `foo :`. + Cur.endLocation() == Next.location(); +} + +bool isSelectorLike(const syntax::Token , const syntax::Token ) { + return Cur.kind() == tok::identifier && Next.kind() == tok::colon && + // We require the selector name and : to be contiguous. + // e.g. support `foo:` but not `foo :`. + Cur.endLocation() == Next.location(); +} + +bool parseMessageExpression(llvm::ArrayRef Tokens, +const SourceManager , unsigned Index, +unsigned Last, Selector Sel, +std::vector ) { + + unsigned NumArgs = Sel.getNumArgs(); + llvm::SmallVector Closes; DavidGoldman wrote: Done https://github.com/llvm/llvm-project/pull/76466 ___ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)
DavidGoldman wrote: Done https://github.com/llvm/llvm-project/pull/76466 ___ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)
https://github.com/DavidGoldman updated https://github.com/llvm/llvm-project/pull/76466 >From 4caf5b3c779bf18236b4b0be5bc7147d10339f2b Mon Sep 17 00:00:00 2001 From: David Goldman Date: Tue, 26 Dec 2023 15:59:01 -0500 Subject: [PATCH 1/9] [clangd][SymbolCollector] Treat ObjC methods as spelled We'll treat multi-arg methods as spelled once we have full rename support for them. --- .../clangd/index/SymbolCollector.cpp | 6 ++- .../clangd/unittests/SymbolCollectorTests.cpp | 42 +++ 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/clang-tools-extra/clangd/index/SymbolCollector.cpp b/clang-tools-extra/clangd/index/SymbolCollector.cpp index 7ef4b15febad2..336bc3506bb36 100644 --- a/clang-tools-extra/clangd/index/SymbolCollector.cpp +++ b/clang-tools-extra/clangd/index/SymbolCollector.cpp @@ -174,7 +174,9 @@ bool isSpelled(SourceLocation Loc, const NamedDecl ) { auto Name = ND.getDeclName(); const auto NameKind = Name.getNameKind(); if (NameKind != DeclarationName::Identifier && - NameKind != DeclarationName::CXXConstructorName) + NameKind != DeclarationName::CXXConstructorName && + NameKind != DeclarationName::ObjCZeroArgSelector && + NameKind != DeclarationName::ObjCOneArgSelector) return false; const auto = ND.getASTContext(); const auto = AST.getSourceManager(); @@ -183,6 +185,8 @@ bool isSpelled(SourceLocation Loc, const NamedDecl ) { if (clang::Lexer::getRawToken(Loc, Tok, SM, LO)) return false; auto StrName = Name.getAsString(); + if (const auto *MD = dyn_cast()) +StrName = MD->getSelector().getNameForSlot(0).str(); return clang::Lexer::getSpelling(Tok, SM, LO) == StrName; } } // namespace diff --git a/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp b/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp index 9cdc57ec01f32..1d4e1c1d75ea2 100644 --- a/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp +++ b/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp @@ -105,6 +105,9 @@ MATCHER(refRange, "") { MATCHER_P2(OverriddenBy, Subject, Object, "") { return arg == Relation{Subject.ID, RelationKind::OverriddenBy, Object.ID}; } +MATCHER(isSpelled, "") { + return static_cast(arg.Kind & RefKind::Spelled); +} ::testing::Matcher &> haveRanges(const std::vector Ranges) { return ::testing::UnorderedPointwise(refRange(), Ranges); @@ -524,6 +527,45 @@ TEST_F(SymbolCollectorTest, templateArgs) { forCodeCompletion(false); } +TEST_F(SymbolCollectorTest, ObjCRefs) { + Annotations Header(R"( + @interface Person + - (void)$talk[[talk]]; + - (void)$say[[say]]:(id)something; + @end + @interface Person (Category) + - (void)categoryMethod; + - (void)multiArg:(id)a method:(id)b; + @end + )"); + Annotations Main(R"( + @implementation Person + - (void)$talk[[talk]] {} + - (void)$say[[say]]:(id)something {} + @end + + void fff(Person *p) { +[p $talk[[talk]]]; +[p $say[[say]]:0]; +[p categoryMethod]; +[p multiArg:0 method:0]; + } + )"); + CollectorOpts.RefFilter = RefKind::All; + CollectorOpts.CollectMainFileRefs = true; + TestFileName = testPath("test.m"); + runSymbolCollector(Header.code(), Main.code(), + {"-fblocks", "-xobjective-c++", "-Wno-objc-root-class"}); + EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "Person::talk").ID, + haveRanges(Main.ranges("talk"); + EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "Person::say:").ID, + haveRanges(Main.ranges("say"); + EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "Person::categoryMethod").ID, + ElementsAre(isSpelled(); + EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "Person::multiArg:method:").ID, + ElementsAre(Not(isSpelled()); +} + TEST_F(SymbolCollectorTest, ObjCSymbols) { const std::string Header = R"( @interface Person >From 1b6a09464ff5c7b1988fcb479d0a4ff876f696e6 Mon Sep 17 00:00:00 2001 From: David Goldman Date: Tue, 26 Dec 2023 16:12:03 -0500 Subject: [PATCH 2/9] Run clang-format --- .../clangd/unittests/SymbolCollectorTests.cpp | 10 ++ 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp b/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp index 1d4e1c1d75ea2..5c20b950e4eac 100644 --- a/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp +++ b/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp @@ -560,10 +560,12 @@ TEST_F(SymbolCollectorTest, ObjCRefs) { haveRanges(Main.ranges("talk"); EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "Person::say:").ID, haveRanges(Main.ranges("say"); - EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols,
[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)
https://github.com/DavidGoldman updated https://github.com/llvm/llvm-project/pull/76466 >From 4caf5b3c779bf18236b4b0be5bc7147d10339f2b Mon Sep 17 00:00:00 2001 From: David Goldman Date: Tue, 26 Dec 2023 15:59:01 -0500 Subject: [PATCH 1/8] [clangd][SymbolCollector] Treat ObjC methods as spelled We'll treat multi-arg methods as spelled once we have full rename support for them. --- .../clangd/index/SymbolCollector.cpp | 6 ++- .../clangd/unittests/SymbolCollectorTests.cpp | 42 +++ 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/clang-tools-extra/clangd/index/SymbolCollector.cpp b/clang-tools-extra/clangd/index/SymbolCollector.cpp index 7ef4b15febad22..336bc3506bb360 100644 --- a/clang-tools-extra/clangd/index/SymbolCollector.cpp +++ b/clang-tools-extra/clangd/index/SymbolCollector.cpp @@ -174,7 +174,9 @@ bool isSpelled(SourceLocation Loc, const NamedDecl ) { auto Name = ND.getDeclName(); const auto NameKind = Name.getNameKind(); if (NameKind != DeclarationName::Identifier && - NameKind != DeclarationName::CXXConstructorName) + NameKind != DeclarationName::CXXConstructorName && + NameKind != DeclarationName::ObjCZeroArgSelector && + NameKind != DeclarationName::ObjCOneArgSelector) return false; const auto = ND.getASTContext(); const auto = AST.getSourceManager(); @@ -183,6 +185,8 @@ bool isSpelled(SourceLocation Loc, const NamedDecl ) { if (clang::Lexer::getRawToken(Loc, Tok, SM, LO)) return false; auto StrName = Name.getAsString(); + if (const auto *MD = dyn_cast()) +StrName = MD->getSelector().getNameForSlot(0).str(); return clang::Lexer::getSpelling(Tok, SM, LO) == StrName; } } // namespace diff --git a/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp b/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp index 9cdc57ec01f327..1d4e1c1d75ea23 100644 --- a/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp +++ b/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp @@ -105,6 +105,9 @@ MATCHER(refRange, "") { MATCHER_P2(OverriddenBy, Subject, Object, "") { return arg == Relation{Subject.ID, RelationKind::OverriddenBy, Object.ID}; } +MATCHER(isSpelled, "") { + return static_cast(arg.Kind & RefKind::Spelled); +} ::testing::Matcher &> haveRanges(const std::vector Ranges) { return ::testing::UnorderedPointwise(refRange(), Ranges); @@ -524,6 +527,45 @@ TEST_F(SymbolCollectorTest, templateArgs) { forCodeCompletion(false); } +TEST_F(SymbolCollectorTest, ObjCRefs) { + Annotations Header(R"( + @interface Person + - (void)$talk[[talk]]; + - (void)$say[[say]]:(id)something; + @end + @interface Person (Category) + - (void)categoryMethod; + - (void)multiArg:(id)a method:(id)b; + @end + )"); + Annotations Main(R"( + @implementation Person + - (void)$talk[[talk]] {} + - (void)$say[[say]]:(id)something {} + @end + + void fff(Person *p) { +[p $talk[[talk]]]; +[p $say[[say]]:0]; +[p categoryMethod]; +[p multiArg:0 method:0]; + } + )"); + CollectorOpts.RefFilter = RefKind::All; + CollectorOpts.CollectMainFileRefs = true; + TestFileName = testPath("test.m"); + runSymbolCollector(Header.code(), Main.code(), + {"-fblocks", "-xobjective-c++", "-Wno-objc-root-class"}); + EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "Person::talk").ID, + haveRanges(Main.ranges("talk"); + EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "Person::say:").ID, + haveRanges(Main.ranges("say"); + EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "Person::categoryMethod").ID, + ElementsAre(isSpelled(); + EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "Person::multiArg:method:").ID, + ElementsAre(Not(isSpelled()); +} + TEST_F(SymbolCollectorTest, ObjCSymbols) { const std::string Header = R"( @interface Person >From 1b6a09464ff5c7b1988fcb479d0a4ff876f696e6 Mon Sep 17 00:00:00 2001 From: David Goldman Date: Tue, 26 Dec 2023 16:12:03 -0500 Subject: [PATCH 2/8] Run clang-format --- .../clangd/unittests/SymbolCollectorTests.cpp | 10 ++ 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp b/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp index 1d4e1c1d75ea23..5c20b950e4eac0 100644 --- a/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp +++ b/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp @@ -560,10 +560,12 @@ TEST_F(SymbolCollectorTest, ObjCRefs) { haveRanges(Main.ranges("talk"); EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "Person::say:").ID, haveRanges(Main.ranges("say"); - EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols,
[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)
@@ -538,11 +565,254 @@ std::optional checkName(const NamedDecl , Conflict->getLocation().printToString(ASTCtx.getSourceManager())}; } } - if (Result) + if (Result) { InvalidNameMetric.record(1, toString(Result->K)); +return makeError(*Result); + } + return std::nullopt; +} + +bool isMatchingSelectorName(const syntax::Token , const syntax::Token , +const SourceManager , +llvm::StringRef SelectorName) { + if (SelectorName.empty()) +return Cur.kind() == tok::colon; + return Cur.kind() == tok::identifier && Next.kind() == tok::colon && + Cur.text(SM) == SelectorName && + // We require the selector name and : to be contiguous to avoid + // potential conflicts with ternary expression. + // + // e.g. support `foo:` but not `foo :`. + Cur.endLocation() == Next.location(); DavidGoldman wrote: Done https://github.com/llvm/llvm-project/pull/76466 ___ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)
@@ -538,11 +565,254 @@ std::optional checkName(const NamedDecl , Conflict->getLocation().printToString(ASTCtx.getSourceManager())}; } } - if (Result) + if (Result) { InvalidNameMetric.record(1, toString(Result->K)); +return makeError(*Result); + } + return std::nullopt; +} + +bool isMatchingSelectorName(const syntax::Token , const syntax::Token , +const SourceManager , +llvm::StringRef SelectorName) { + if (SelectorName.empty()) +return Cur.kind() == tok::colon; + return Cur.kind() == tok::identifier && Next.kind() == tok::colon && + Cur.text(SM) == SelectorName && + // We require the selector name and : to be contiguous to avoid + // potential conflicts with ternary expression. + // + // e.g. support `foo:` but not `foo :`. + Cur.endLocation() == Next.location(); +} + +bool isSelectorLike(const syntax::Token , const syntax::Token ) { + return Cur.kind() == tok::identifier && Next.kind() == tok::colon && + // We require the selector name and : to be contiguous. + // e.g. support `foo:` but not `foo :`. + Cur.endLocation() == Next.location(); +} + +bool parseMessageExpression(llvm::ArrayRef Tokens, +const SourceManager , unsigned Index, +unsigned Last, Selector Sel, +std::vector ) { + + unsigned NumArgs = Sel.getNumArgs(); + llvm::SmallVector Closes; + SelectorPieces.clear(); + while (Index < Last) { +const auto = Tokens[Index]; + +if (Closes.empty()) { + auto PieceCount = SelectorPieces.size(); + if (PieceCount < NumArgs && + isMatchingSelectorName(Tok, Tokens[Index + 1], SM, + Sel.getNameForSlot(PieceCount))) { +// If 'foo:' instead of ':' (empty selector), we need to skip the ':' +// token after the name. +if (!Sel.getNameForSlot(PieceCount).empty()) { + ++Index; +} +SelectorPieces.push_back( +halfOpenToRange(SM, Tok.range(SM).toCharRange(SM))); +continue; + } + // If we've found all pieces but the current token looks like another + // selector piece, it means the method being renamed is a strict prefix of + // the selector we've found - should be skipped. + if (SelectorPieces.size() >= NumArgs && + isSelectorLike(Tok, Tokens[Index + 1])) +return false; +} + +switch (Tok.kind()) { +case tok::l_square: + Closes.push_back(']'); + break; +case tok::l_paren: + Closes.push_back(')'); + break; +case tok::l_brace: + Closes.push_back('}'); + break; +case tok::r_square: + if (Closes.empty()) +return SelectorPieces.size() == NumArgs; + + if (Closes.back() != ']') +return false; + Closes.pop_back(); + break; +case tok::r_paren: + if (Closes.empty() || Closes.back() != ')') +return false; + Closes.pop_back(); + break; +case tok::r_brace: + if (Closes.empty() || Closes.back() != '}') +return false; + Closes.pop_back(); + break; +case tok::semi: + // top level ; ends all statements. + if (Closes.empty()) +return false; + break; +default: + break; +} + +++Index; + } + return false; +} + +/// Collects all ranges of the given identifier/selector in the source code. +/// +/// If a selector is given, this does a full lex of the given source code in +/// order to identify all selector fragments (e.g. in method exprs/decls) since +/// they are non-contiguous. +std::vector collectRenameIdentifierRanges( +llvm::StringRef Identifier, llvm::StringRef Content, +const LangOptions , std::optional Selector) { + std::vector Ranges; + if (!Selector) { +auto IdentifierRanges = +collectIdentifierRanges(Identifier, Content, LangOpts); +for (const auto : IdentifierRanges) + Ranges.emplace_back(R); +return Ranges; + } + // FIXME: InMemoryFileAdapter crashes unless the buffer is null terminated! + std::string NullTerminatedCode = Content.str(); + SourceManagerForFile FileSM("mock_file_name.cpp", NullTerminatedCode); + auto = FileSM.get(); + + // We track parens and braces to ensure that we don't accidentally try parsing + // a method declaration or definition which isn't at the top level or similar + // looking expressions (e.g. an @selector() expression). + unsigned ParenCount = 0; + unsigned BraceCount = 0; + unsigned NumArgs = Selector->getNumArgs(); + + std::vector SelectorPieces; + auto Tokens = syntax::tokenize(SM.getMainFileID(), SM, LangOpts); DavidGoldman wrote: That's true, although a bit annoying since we need a SM to fetch this and I wouldn't want to force that into the caller. I could pass an optional Array ref of tokens to use to
[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)
@@ -508,24 +513,46 @@ static bool mayBeValidIdentifier(llvm::StringRef Ident) { !isAsciiIdentifierStart(Ident.front(), AllowDollar)) return false; for (char C : Ident) { +if (AllowColon && C == ':') DavidGoldman wrote: This doesn't check the entire selector - only a fragment, so I don't think there's a need to check spaces here. AFAIK selectors can have an empty first fragment too - any idea where that restriction is from? https://github.com/llvm/llvm-project/pull/76466 ___ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)
@@ -538,11 +565,254 @@ std::optional checkName(const NamedDecl , Conflict->getLocation().printToString(ASTCtx.getSourceManager())}; } } - if (Result) + if (Result) { InvalidNameMetric.record(1, toString(Result->K)); +return makeError(*Result); + } + return std::nullopt; +} + +bool isMatchingSelectorName(const syntax::Token , const syntax::Token , +const SourceManager , +llvm::StringRef SelectorName) { + if (SelectorName.empty()) +return Cur.kind() == tok::colon; + return Cur.kind() == tok::identifier && Next.kind() == tok::colon && + Cur.text(SM) == SelectorName && + // We require the selector name and : to be contiguous to avoid + // potential conflicts with ternary expression. + // + // e.g. support `foo:` but not `foo :`. + Cur.endLocation() == Next.location(); +} + +bool isSelectorLike(const syntax::Token , const syntax::Token ) { + return Cur.kind() == tok::identifier && Next.kind() == tok::colon && + // We require the selector name and : to be contiguous. + // e.g. support `foo:` but not `foo :`. + Cur.endLocation() == Next.location(); +} + +bool parseMessageExpression(llvm::ArrayRef Tokens, +const SourceManager , unsigned Index, +unsigned Last, Selector Sel, +std::vector ) { DavidGoldman wrote: Done https://github.com/llvm/llvm-project/pull/76466 ___ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)
@@ -538,11 +565,254 @@ std::optional checkName(const NamedDecl , Conflict->getLocation().printToString(ASTCtx.getSourceManager())}; } } - if (Result) + if (Result) { InvalidNameMetric.record(1, toString(Result->K)); +return makeError(*Result); + } + return std::nullopt; +} + +bool isMatchingSelectorName(const syntax::Token , const syntax::Token , +const SourceManager , +llvm::StringRef SelectorName) { + if (SelectorName.empty()) +return Cur.kind() == tok::colon; + return Cur.kind() == tok::identifier && Next.kind() == tok::colon && + Cur.text(SM) == SelectorName && + // We require the selector name and : to be contiguous to avoid + // potential conflicts with ternary expression. + // + // e.g. support `foo:` but not `foo :`. + Cur.endLocation() == Next.location(); +} + +bool isSelectorLike(const syntax::Token , const syntax::Token ) { + return Cur.kind() == tok::identifier && Next.kind() == tok::colon && + // We require the selector name and : to be contiguous. + // e.g. support `foo:` but not `foo :`. + Cur.endLocation() == Next.location(); +} + +bool parseMessageExpression(llvm::ArrayRef Tokens, +const SourceManager , unsigned Index, +unsigned Last, Selector Sel, +std::vector ) { + + unsigned NumArgs = Sel.getNumArgs(); + llvm::SmallVector Closes; + SelectorPieces.clear(); + while (Index < Last) { +const auto = Tokens[Index]; + +if (Closes.empty()) { + auto PieceCount = SelectorPieces.size(); + if (PieceCount < NumArgs && + isMatchingSelectorName(Tok, Tokens[Index + 1], SM, + Sel.getNameForSlot(PieceCount))) { +// If 'foo:' instead of ':' (empty selector), we need to skip the ':' +// token after the name. +if (!Sel.getNameForSlot(PieceCount).empty()) { + ++Index; +} +SelectorPieces.push_back( +halfOpenToRange(SM, Tok.range(SM).toCharRange(SM))); +continue; + } + // If we've found all pieces but the current token looks like another + // selector piece, it means the method being renamed is a strict prefix of + // the selector we've found - should be skipped. + if (SelectorPieces.size() >= NumArgs && + isSelectorLike(Tok, Tokens[Index + 1])) +return false; +} + +switch (Tok.kind()) { +case tok::l_square: + Closes.push_back(']'); + break; +case tok::l_paren: + Closes.push_back(')'); + break; +case tok::l_brace: + Closes.push_back('}'); + break; +case tok::r_square: + if (Closes.empty()) +return SelectorPieces.size() == NumArgs; + + if (Closes.back() != ']') +return false; + Closes.pop_back(); + break; +case tok::r_paren: + if (Closes.empty() || Closes.back() != ')') +return false; + Closes.pop_back(); + break; +case tok::r_brace: + if (Closes.empty() || Closes.back() != '}') +return false; + Closes.pop_back(); + break; +case tok::semi: + // top level ; ends all statements. + if (Closes.empty()) +return false; + break; +default: + break; +} + +++Index; + } + return false; +} + +/// Collects all ranges of the given identifier/selector in the source code. +/// +/// If a selector is given, this does a full lex of the given source code in +/// order to identify all selector fragments (e.g. in method exprs/decls) since +/// they are non-contiguous. +std::vector collectRenameIdentifierRanges( +llvm::StringRef Identifier, llvm::StringRef Content, +const LangOptions , std::optional Selector) { + std::vector Ranges; + if (!Selector) { +auto IdentifierRanges = +collectIdentifierRanges(Identifier, Content, LangOpts); +for (const auto : IdentifierRanges) + Ranges.emplace_back(R); +return Ranges; + } + // FIXME: InMemoryFileAdapter crashes unless the buffer is null terminated! + std::string NullTerminatedCode = Content.str(); + SourceManagerForFile FileSM("mock_file_name.cpp", NullTerminatedCode); + auto = FileSM.get(); + + // We track parens and braces to ensure that we don't accidentally try parsing + // a method declaration or definition which isn't at the top level or similar + // looking expressions (e.g. an @selector() expression). + unsigned ParenCount = 0; + unsigned BraceCount = 0; + unsigned NumArgs = Selector->getNumArgs(); + + std::vector SelectorPieces; + auto Tokens = syntax::tokenize(SM.getMainFileID(), SM, LangOpts); + unsigned Last = Tokens.size() - 1; + for (unsigned Index = 0; Index < Last; ++Index) { +const auto = Tokens[Index]; + +if (BraceCount == 0 && ParenCount == 0) { + auto PieceCount =
[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)
@@ -538,11 +565,254 @@ std::optional checkName(const NamedDecl , Conflict->getLocation().printToString(ASTCtx.getSourceManager())}; } } - if (Result) + if (Result) { InvalidNameMetric.record(1, toString(Result->K)); +return makeError(*Result); + } + return std::nullopt; +} + +bool isMatchingSelectorName(const syntax::Token , const syntax::Token , +const SourceManager , +llvm::StringRef SelectorName) { + if (SelectorName.empty()) +return Cur.kind() == tok::colon; + return Cur.kind() == tok::identifier && Next.kind() == tok::colon && + Cur.text(SM) == SelectorName && + // We require the selector name and : to be contiguous to avoid + // potential conflicts with ternary expression. + // + // e.g. support `foo:` but not `foo :`. + Cur.endLocation() == Next.location(); +} + +bool isSelectorLike(const syntax::Token , const syntax::Token ) { + return Cur.kind() == tok::identifier && Next.kind() == tok::colon && + // We require the selector name and : to be contiguous. + // e.g. support `foo:` but not `foo :`. + Cur.endLocation() == Next.location(); +} + +bool parseMessageExpression(llvm::ArrayRef Tokens, +const SourceManager , unsigned Index, +unsigned Last, Selector Sel, +std::vector ) { + + unsigned NumArgs = Sel.getNumArgs(); + llvm::SmallVector Closes; + SelectorPieces.clear(); + while (Index < Last) { +const auto = Tokens[Index]; + +if (Closes.empty()) { + auto PieceCount = SelectorPieces.size(); + if (PieceCount < NumArgs && + isMatchingSelectorName(Tok, Tokens[Index + 1], SM, + Sel.getNameForSlot(PieceCount))) { +// If 'foo:' instead of ':' (empty selector), we need to skip the ':' +// token after the name. +if (!Sel.getNameForSlot(PieceCount).empty()) { + ++Index; +} +SelectorPieces.push_back( +halfOpenToRange(SM, Tok.range(SM).toCharRange(SM))); +continue; + } + // If we've found all pieces but the current token looks like another + // selector piece, it means the method being renamed is a strict prefix of + // the selector we've found - should be skipped. + if (SelectorPieces.size() >= NumArgs && + isSelectorLike(Tok, Tokens[Index + 1])) +return false; +} + +switch (Tok.kind()) { +case tok::l_square: + Closes.push_back(']'); + break; +case tok::l_paren: + Closes.push_back(')'); + break; +case tok::l_brace: + Closes.push_back('}'); + break; +case tok::r_square: + if (Closes.empty()) +return SelectorPieces.size() == NumArgs; + + if (Closes.back() != ']') +return false; + Closes.pop_back(); + break; +case tok::r_paren: + if (Closes.empty() || Closes.back() != ')') +return false; + Closes.pop_back(); + break; +case tok::r_brace: + if (Closes.empty() || Closes.back() != '}') +return false; + Closes.pop_back(); + break; +case tok::semi: + // top level ; ends all statements. + if (Closes.empty()) +return false; + break; +default: + break; +} + +++Index; + } + return false; +} + +/// Collects all ranges of the given identifier/selector in the source code. +/// +/// If a selector is given, this does a full lex of the given source code in +/// order to identify all selector fragments (e.g. in method exprs/decls) since +/// they are non-contiguous. +std::vector collectRenameIdentifierRanges( +llvm::StringRef Identifier, llvm::StringRef Content, +const LangOptions , std::optional Selector) { + std::vector Ranges; + if (!Selector) { +auto IdentifierRanges = +collectIdentifierRanges(Identifier, Content, LangOpts); +for (const auto : IdentifierRanges) + Ranges.emplace_back(R); +return Ranges; + } + // FIXME: InMemoryFileAdapter crashes unless the buffer is null terminated! + std::string NullTerminatedCode = Content.str(); + SourceManagerForFile FileSM("mock_file_name.cpp", NullTerminatedCode); + auto = FileSM.get(); + + // We track parens and braces to ensure that we don't accidentally try parsing + // a method declaration or definition which isn't at the top level or similar + // looking expressions (e.g. an @selector() expression). + unsigned ParenCount = 0; + unsigned BraceCount = 0; + unsigned NumArgs = Selector->getNumArgs(); + + std::vector SelectorPieces; + auto Tokens = syntax::tokenize(SM.getMainFileID(), SM, LangOpts); + unsigned Last = Tokens.size() - 1; + for (unsigned Index = 0; Index < Last; ++Index) { +const auto = Tokens[Index]; + DavidGoldman wrote: The main edge case I mentioned is having one
[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)
@@ -508,24 +513,46 @@ static bool mayBeValidIdentifier(llvm::StringRef Ident) { !isAsciiIdentifierStart(Ident.front(), AllowDollar)) return false; for (char C : Ident) { +if (AllowColon && C == ':') + continue; if (llvm::isASCII(C) && !isAsciiIdentifierContinue(C, AllowDollar)) return false; } return true; } +std::string getName(const NamedDecl ) { + if (const auto *MD = dyn_cast()) +return MD->getSelector().getAsString(); + if (const auto *ID = RenameDecl.getIdentifier()) +return ID->getName().str(); + return ""; +} + // Check if we can rename the given RenameDecl into NewName. // Return details if the rename would produce a conflict. -std::optional checkName(const NamedDecl , - llvm::StringRef NewName) { +std::optional checkName(const NamedDecl , DavidGoldman wrote: Done https://github.com/llvm/llvm-project/pull/76466 ___ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang-tools-extra] [clangd][SymbolCollector] Treat ObjC methods as spelled (PR #76410)
https://github.com/DavidGoldman closed https://github.com/llvm/llvm-project/pull/76410 ___ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)
https://github.com/DavidGoldman updated https://github.com/llvm/llvm-project/pull/76466 >From 4caf5b3c779bf18236b4b0be5bc7147d10339f2b Mon Sep 17 00:00:00 2001 From: David Goldman Date: Tue, 26 Dec 2023 15:59:01 -0500 Subject: [PATCH 1/7] [clangd][SymbolCollector] Treat ObjC methods as spelled We'll treat multi-arg methods as spelled once we have full rename support for them. --- .../clangd/index/SymbolCollector.cpp | 6 ++- .../clangd/unittests/SymbolCollectorTests.cpp | 42 +++ 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/clang-tools-extra/clangd/index/SymbolCollector.cpp b/clang-tools-extra/clangd/index/SymbolCollector.cpp index 7ef4b15febad22f..336bc3506bb3608 100644 --- a/clang-tools-extra/clangd/index/SymbolCollector.cpp +++ b/clang-tools-extra/clangd/index/SymbolCollector.cpp @@ -174,7 +174,9 @@ bool isSpelled(SourceLocation Loc, const NamedDecl ) { auto Name = ND.getDeclName(); const auto NameKind = Name.getNameKind(); if (NameKind != DeclarationName::Identifier && - NameKind != DeclarationName::CXXConstructorName) + NameKind != DeclarationName::CXXConstructorName && + NameKind != DeclarationName::ObjCZeroArgSelector && + NameKind != DeclarationName::ObjCOneArgSelector) return false; const auto = ND.getASTContext(); const auto = AST.getSourceManager(); @@ -183,6 +185,8 @@ bool isSpelled(SourceLocation Loc, const NamedDecl ) { if (clang::Lexer::getRawToken(Loc, Tok, SM, LO)) return false; auto StrName = Name.getAsString(); + if (const auto *MD = dyn_cast()) +StrName = MD->getSelector().getNameForSlot(0).str(); return clang::Lexer::getSpelling(Tok, SM, LO) == StrName; } } // namespace diff --git a/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp b/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp index 9cdc57ec01f3276..1d4e1c1d75ea230 100644 --- a/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp +++ b/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp @@ -105,6 +105,9 @@ MATCHER(refRange, "") { MATCHER_P2(OverriddenBy, Subject, Object, "") { return arg == Relation{Subject.ID, RelationKind::OverriddenBy, Object.ID}; } +MATCHER(isSpelled, "") { + return static_cast(arg.Kind & RefKind::Spelled); +} ::testing::Matcher &> haveRanges(const std::vector Ranges) { return ::testing::UnorderedPointwise(refRange(), Ranges); @@ -524,6 +527,45 @@ TEST_F(SymbolCollectorTest, templateArgs) { forCodeCompletion(false); } +TEST_F(SymbolCollectorTest, ObjCRefs) { + Annotations Header(R"( + @interface Person + - (void)$talk[[talk]]; + - (void)$say[[say]]:(id)something; + @end + @interface Person (Category) + - (void)categoryMethod; + - (void)multiArg:(id)a method:(id)b; + @end + )"); + Annotations Main(R"( + @implementation Person + - (void)$talk[[talk]] {} + - (void)$say[[say]]:(id)something {} + @end + + void fff(Person *p) { +[p $talk[[talk]]]; +[p $say[[say]]:0]; +[p categoryMethod]; +[p multiArg:0 method:0]; + } + )"); + CollectorOpts.RefFilter = RefKind::All; + CollectorOpts.CollectMainFileRefs = true; + TestFileName = testPath("test.m"); + runSymbolCollector(Header.code(), Main.code(), + {"-fblocks", "-xobjective-c++", "-Wno-objc-root-class"}); + EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "Person::talk").ID, + haveRanges(Main.ranges("talk"); + EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "Person::say:").ID, + haveRanges(Main.ranges("say"); + EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "Person::categoryMethod").ID, + ElementsAre(isSpelled(); + EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "Person::multiArg:method:").ID, + ElementsAre(Not(isSpelled()); +} + TEST_F(SymbolCollectorTest, ObjCSymbols) { const std::string Header = R"( @interface Person >From 1b6a09464ff5c7b1988fcb479d0a4ff876f696e6 Mon Sep 17 00:00:00 2001 From: David Goldman Date: Tue, 26 Dec 2023 16:12:03 -0500 Subject: [PATCH 2/7] Run clang-format --- .../clangd/unittests/SymbolCollectorTests.cpp | 10 ++ 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp b/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp index 1d4e1c1d75ea230..5c20b950e4eac0d 100644 --- a/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp +++ b/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp @@ -560,10 +560,12 @@ TEST_F(SymbolCollectorTest, ObjCRefs) { haveRanges(Main.ranges("talk"); EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "Person::say:").ID, haveRanges(Main.ranges("say"); - EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols,
[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)
https://github.com/DavidGoldman updated https://github.com/llvm/llvm-project/pull/76466 >From 4caf5b3c779bf18236b4b0be5bc7147d10339f2b Mon Sep 17 00:00:00 2001 From: David Goldman Date: Tue, 26 Dec 2023 15:59:01 -0500 Subject: [PATCH 1/6] [clangd][SymbolCollector] Treat ObjC methods as spelled We'll treat multi-arg methods as spelled once we have full rename support for them. --- .../clangd/index/SymbolCollector.cpp | 6 ++- .../clangd/unittests/SymbolCollectorTests.cpp | 42 +++ 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/clang-tools-extra/clangd/index/SymbolCollector.cpp b/clang-tools-extra/clangd/index/SymbolCollector.cpp index 7ef4b15febad22f..336bc3506bb3608 100644 --- a/clang-tools-extra/clangd/index/SymbolCollector.cpp +++ b/clang-tools-extra/clangd/index/SymbolCollector.cpp @@ -174,7 +174,9 @@ bool isSpelled(SourceLocation Loc, const NamedDecl ) { auto Name = ND.getDeclName(); const auto NameKind = Name.getNameKind(); if (NameKind != DeclarationName::Identifier && - NameKind != DeclarationName::CXXConstructorName) + NameKind != DeclarationName::CXXConstructorName && + NameKind != DeclarationName::ObjCZeroArgSelector && + NameKind != DeclarationName::ObjCOneArgSelector) return false; const auto = ND.getASTContext(); const auto = AST.getSourceManager(); @@ -183,6 +185,8 @@ bool isSpelled(SourceLocation Loc, const NamedDecl ) { if (clang::Lexer::getRawToken(Loc, Tok, SM, LO)) return false; auto StrName = Name.getAsString(); + if (const auto *MD = dyn_cast()) +StrName = MD->getSelector().getNameForSlot(0).str(); return clang::Lexer::getSpelling(Tok, SM, LO) == StrName; } } // namespace diff --git a/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp b/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp index 9cdc57ec01f3276..1d4e1c1d75ea230 100644 --- a/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp +++ b/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp @@ -105,6 +105,9 @@ MATCHER(refRange, "") { MATCHER_P2(OverriddenBy, Subject, Object, "") { return arg == Relation{Subject.ID, RelationKind::OverriddenBy, Object.ID}; } +MATCHER(isSpelled, "") { + return static_cast(arg.Kind & RefKind::Spelled); +} ::testing::Matcher &> haveRanges(const std::vector Ranges) { return ::testing::UnorderedPointwise(refRange(), Ranges); @@ -524,6 +527,45 @@ TEST_F(SymbolCollectorTest, templateArgs) { forCodeCompletion(false); } +TEST_F(SymbolCollectorTest, ObjCRefs) { + Annotations Header(R"( + @interface Person + - (void)$talk[[talk]]; + - (void)$say[[say]]:(id)something; + @end + @interface Person (Category) + - (void)categoryMethod; + - (void)multiArg:(id)a method:(id)b; + @end + )"); + Annotations Main(R"( + @implementation Person + - (void)$talk[[talk]] {} + - (void)$say[[say]]:(id)something {} + @end + + void fff(Person *p) { +[p $talk[[talk]]]; +[p $say[[say]]:0]; +[p categoryMethod]; +[p multiArg:0 method:0]; + } + )"); + CollectorOpts.RefFilter = RefKind::All; + CollectorOpts.CollectMainFileRefs = true; + TestFileName = testPath("test.m"); + runSymbolCollector(Header.code(), Main.code(), + {"-fblocks", "-xobjective-c++", "-Wno-objc-root-class"}); + EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "Person::talk").ID, + haveRanges(Main.ranges("talk"); + EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "Person::say:").ID, + haveRanges(Main.ranges("say"); + EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "Person::categoryMethod").ID, + ElementsAre(isSpelled(); + EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "Person::multiArg:method:").ID, + ElementsAre(Not(isSpelled()); +} + TEST_F(SymbolCollectorTest, ObjCSymbols) { const std::string Header = R"( @interface Person >From 1b6a09464ff5c7b1988fcb479d0a4ff876f696e6 Mon Sep 17 00:00:00 2001 From: David Goldman Date: Tue, 26 Dec 2023 16:12:03 -0500 Subject: [PATCH 2/6] Run clang-format --- .../clangd/unittests/SymbolCollectorTests.cpp | 10 ++ 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp b/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp index 1d4e1c1d75ea230..5c20b950e4eac0d 100644 --- a/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp +++ b/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp @@ -560,10 +560,12 @@ TEST_F(SymbolCollectorTest, ObjCRefs) { haveRanges(Main.ranges("talk"); EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "Person::say:").ID, haveRanges(Main.ranges("say"); - EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols,
[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)
https://github.com/DavidGoldman updated https://github.com/llvm/llvm-project/pull/76466 >From 4caf5b3c779bf18236b4b0be5bc7147d10339f2b Mon Sep 17 00:00:00 2001 From: David Goldman Date: Tue, 26 Dec 2023 15:59:01 -0500 Subject: [PATCH 1/5] [clangd][SymbolCollector] Treat ObjC methods as spelled We'll treat multi-arg methods as spelled once we have full rename support for them. --- .../clangd/index/SymbolCollector.cpp | 6 ++- .../clangd/unittests/SymbolCollectorTests.cpp | 42 +++ 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/clang-tools-extra/clangd/index/SymbolCollector.cpp b/clang-tools-extra/clangd/index/SymbolCollector.cpp index 7ef4b15febad22f..336bc3506bb3608 100644 --- a/clang-tools-extra/clangd/index/SymbolCollector.cpp +++ b/clang-tools-extra/clangd/index/SymbolCollector.cpp @@ -174,7 +174,9 @@ bool isSpelled(SourceLocation Loc, const NamedDecl ) { auto Name = ND.getDeclName(); const auto NameKind = Name.getNameKind(); if (NameKind != DeclarationName::Identifier && - NameKind != DeclarationName::CXXConstructorName) + NameKind != DeclarationName::CXXConstructorName && + NameKind != DeclarationName::ObjCZeroArgSelector && + NameKind != DeclarationName::ObjCOneArgSelector) return false; const auto = ND.getASTContext(); const auto = AST.getSourceManager(); @@ -183,6 +185,8 @@ bool isSpelled(SourceLocation Loc, const NamedDecl ) { if (clang::Lexer::getRawToken(Loc, Tok, SM, LO)) return false; auto StrName = Name.getAsString(); + if (const auto *MD = dyn_cast()) +StrName = MD->getSelector().getNameForSlot(0).str(); return clang::Lexer::getSpelling(Tok, SM, LO) == StrName; } } // namespace diff --git a/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp b/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp index 9cdc57ec01f3276..1d4e1c1d75ea230 100644 --- a/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp +++ b/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp @@ -105,6 +105,9 @@ MATCHER(refRange, "") { MATCHER_P2(OverriddenBy, Subject, Object, "") { return arg == Relation{Subject.ID, RelationKind::OverriddenBy, Object.ID}; } +MATCHER(isSpelled, "") { + return static_cast(arg.Kind & RefKind::Spelled); +} ::testing::Matcher &> haveRanges(const std::vector Ranges) { return ::testing::UnorderedPointwise(refRange(), Ranges); @@ -524,6 +527,45 @@ TEST_F(SymbolCollectorTest, templateArgs) { forCodeCompletion(false); } +TEST_F(SymbolCollectorTest, ObjCRefs) { + Annotations Header(R"( + @interface Person + - (void)$talk[[talk]]; + - (void)$say[[say]]:(id)something; + @end + @interface Person (Category) + - (void)categoryMethod; + - (void)multiArg:(id)a method:(id)b; + @end + )"); + Annotations Main(R"( + @implementation Person + - (void)$talk[[talk]] {} + - (void)$say[[say]]:(id)something {} + @end + + void fff(Person *p) { +[p $talk[[talk]]]; +[p $say[[say]]:0]; +[p categoryMethod]; +[p multiArg:0 method:0]; + } + )"); + CollectorOpts.RefFilter = RefKind::All; + CollectorOpts.CollectMainFileRefs = true; + TestFileName = testPath("test.m"); + runSymbolCollector(Header.code(), Main.code(), + {"-fblocks", "-xobjective-c++", "-Wno-objc-root-class"}); + EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "Person::talk").ID, + haveRanges(Main.ranges("talk"); + EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "Person::say:").ID, + haveRanges(Main.ranges("say"); + EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "Person::categoryMethod").ID, + ElementsAre(isSpelled(); + EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "Person::multiArg:method:").ID, + ElementsAre(Not(isSpelled()); +} + TEST_F(SymbolCollectorTest, ObjCSymbols) { const std::string Header = R"( @interface Person >From 1b6a09464ff5c7b1988fcb479d0a4ff876f696e6 Mon Sep 17 00:00:00 2001 From: David Goldman Date: Tue, 26 Dec 2023 16:12:03 -0500 Subject: [PATCH 2/5] Run clang-format --- .../clangd/unittests/SymbolCollectorTests.cpp | 10 ++ 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp b/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp index 1d4e1c1d75ea230..5c20b950e4eac0d 100644 --- a/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp +++ b/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp @@ -560,10 +560,12 @@ TEST_F(SymbolCollectorTest, ObjCRefs) { haveRanges(Main.ranges("talk"); EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "Person::say:").ID, haveRanges(Main.ranges("say"); - EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols,
[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)
@@ -38,6 +38,11 @@ namespace clang { namespace clangd { + +std::vector collectRenameIdentifierRanges( DavidGoldman wrote: Done https://github.com/llvm/llvm-project/pull/76466 ___ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)
@@ -1029,5 +1172,151 @@ size_t renameRangeAdjustmentCost(ArrayRef Indexed, ArrayRef Lexed, return Cost; } +static bool isMatchingSelectorName(const syntax::Token , + const syntax::Token , + const SourceManager , + llvm::StringRef SelectorName) { + if (SelectorName.empty()) +return Cur.kind() == tok::colon; + return Cur.kind() == tok::identifier && Next.kind() == tok::colon && + Cur.text(SM) == SelectorName && + // We require the selector name and : to be contiguous. + // e.g. support `foo:` but not `foo :`. + Cur.endLocation() == Next.location(); +} + +static bool isSelectorLike(const syntax::Token , + const syntax::Token ) { + return Cur.kind() == tok::identifier && Next.kind() == tok::colon && + // We require the selector name and : to be contiguous. + // e.g. support `foo:` but not `foo :`. + Cur.endLocation() == Next.location(); +} + +static void +lex(llvm::StringRef Code, const LangOptions , +llvm::function_ref +Action) { + // FIXME: InMemoryFileAdapter crashes unless the buffer is null terminated! + std::string NullTerminatedCode = Code.str(); + SourceManagerForFile FileSM("mock_file_name.cpp", NullTerminatedCode); + auto = FileSM.get(); + for (const auto : syntax::tokenize(SM.getMainFileID(), SM, LangOpts)) +Action(Tok, SM); +} + +std::vector collectRenameIdentifierRanges( +llvm::StringRef Identifier, llvm::StringRef Content, +const LangOptions , std::optional Selector) { + std::vector Ranges; + if (!Selector) { +lex(Content, LangOpts, +[&](const syntax::Token , const SourceManager ) { + if (Tok.kind() != tok::identifier || Tok.text(SM) != Identifier) +return; + Ranges.emplace_back( + halfOpenToRange(SM, Tok.range(SM).toCharRange(SM))); +}); +return Ranges; + } + // FIXME: InMemoryFileAdapter crashes unless the buffer is null terminated! + std::string NullTerminatedCode = Content.str(); + SourceManagerForFile FileSM("mock_file_name.cpp", NullTerminatedCode); + auto = FileSM.get(); + + auto Tokens = syntax::tokenize(SM.getMainFileID(), SM, LangOpts); + unsigned Last = Tokens.size() - 1; + + // One parser state for top level and each `[]` pair, can be nested. + // Technically we should have a state or recursion for each ()/{} as well, + // but since we're expecting well formed code it shouldn't matter in practice. + struct ParserState { +unsigned ParenCount = 0; +unsigned BraceCount = 0; +std::vector Pieces; + }; + + // We have to track square brackets, parens and braces as we want to skip the + // tokens inside them. This ensures that we don't use identical selector + // pieces in inner message sends, blocks, lambdas and @selector expressions. + std::vector States = {ParserState()}; + unsigned NumPieces = Selector->getNumArgs(); + + for (unsigned Index = 0; Index < Last; ++Index) { DavidGoldman wrote: Done https://github.com/llvm/llvm-project/pull/76466 ___ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)
@@ -1029,5 +1172,151 @@ size_t renameRangeAdjustmentCost(ArrayRef Indexed, ArrayRef Lexed, return Cost; } +static bool isMatchingSelectorName(const syntax::Token , + const syntax::Token , + const SourceManager , + llvm::StringRef SelectorName) { + if (SelectorName.empty()) +return Cur.kind() == tok::colon; + return Cur.kind() == tok::identifier && Next.kind() == tok::colon && + Cur.text(SM) == SelectorName && + // We require the selector name and : to be contiguous. + // e.g. support `foo:` but not `foo :`. + Cur.endLocation() == Next.location(); +} + +static bool isSelectorLike(const syntax::Token , + const syntax::Token ) { + return Cur.kind() == tok::identifier && Next.kind() == tok::colon && + // We require the selector name and : to be contiguous. + // e.g. support `foo:` but not `foo :`. + Cur.endLocation() == Next.location(); +} + +static void +lex(llvm::StringRef Code, const LangOptions , +llvm::function_ref +Action) { + // FIXME: InMemoryFileAdapter crashes unless the buffer is null terminated! + std::string NullTerminatedCode = Code.str(); + SourceManagerForFile FileSM("mock_file_name.cpp", NullTerminatedCode); + auto = FileSM.get(); + for (const auto : syntax::tokenize(SM.getMainFileID(), SM, LangOpts)) +Action(Tok, SM); +} + +std::vector collectRenameIdentifierRanges( +llvm::StringRef Identifier, llvm::StringRef Content, +const LangOptions , std::optional Selector) { + std::vector Ranges; + if (!Selector) { +lex(Content, LangOpts, +[&](const syntax::Token , const SourceManager ) { + if (Tok.kind() != tok::identifier || Tok.text(SM) != Identifier) +return; + Ranges.emplace_back( + halfOpenToRange(SM, Tok.range(SM).toCharRange(SM))); +}); +return Ranges; + } + // FIXME: InMemoryFileAdapter crashes unless the buffer is null terminated! + std::string NullTerminatedCode = Content.str(); + SourceManagerForFile FileSM("mock_file_name.cpp", NullTerminatedCode); + auto = FileSM.get(); + + auto Tokens = syntax::tokenize(SM.getMainFileID(), SM, LangOpts); + unsigned Last = Tokens.size() - 1; + + // One parser state for top level and each `[]` pair, can be nested. + // Technically we should have a state or recursion for each ()/{} as well, + // but since we're expecting well formed code it shouldn't matter in practice. DavidGoldman wrote: Removed https://github.com/llvm/llvm-project/pull/76466 ___ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)
@@ -1029,5 +1172,151 @@ size_t renameRangeAdjustmentCost(ArrayRef Indexed, ArrayRef Lexed, return Cost; } +static bool isMatchingSelectorName(const syntax::Token , + const syntax::Token , + const SourceManager , + llvm::StringRef SelectorName) { + if (SelectorName.empty()) +return Cur.kind() == tok::colon; + return Cur.kind() == tok::identifier && Next.kind() == tok::colon && + Cur.text(SM) == SelectorName && + // We require the selector name and : to be contiguous. + // e.g. support `foo:` but not `foo :`. + Cur.endLocation() == Next.location(); +} + +static bool isSelectorLike(const syntax::Token , + const syntax::Token ) { + return Cur.kind() == tok::identifier && Next.kind() == tok::colon && + // We require the selector name and : to be contiguous. + // e.g. support `foo:` but not `foo :`. + Cur.endLocation() == Next.location(); +} + +static void +lex(llvm::StringRef Code, const LangOptions , +llvm::function_ref +Action) { + // FIXME: InMemoryFileAdapter crashes unless the buffer is null terminated! + std::string NullTerminatedCode = Code.str(); + SourceManagerForFile FileSM("mock_file_name.cpp", NullTerminatedCode); + auto = FileSM.get(); + for (const auto : syntax::tokenize(SM.getMainFileID(), SM, LangOpts)) +Action(Tok, SM); +} + +std::vector collectRenameIdentifierRanges( +llvm::StringRef Identifier, llvm::StringRef Content, +const LangOptions , std::optional Selector) { + std::vector Ranges; + if (!Selector) { +lex(Content, LangOpts, +[&](const syntax::Token , const SourceManager ) { + if (Tok.kind() != tok::identifier || Tok.text(SM) != Identifier) +return; + Ranges.emplace_back( + halfOpenToRange(SM, Tok.range(SM).toCharRange(SM))); +}); +return Ranges; + } + // FIXME: InMemoryFileAdapter crashes unless the buffer is null terminated! + std::string NullTerminatedCode = Content.str(); + SourceManagerForFile FileSM("mock_file_name.cpp", NullTerminatedCode); + auto = FileSM.get(); + + auto Tokens = syntax::tokenize(SM.getMainFileID(), SM, LangOpts); + unsigned Last = Tokens.size() - 1; + + // One parser state for top level and each `[]` pair, can be nested. + // Technically we should have a state or recursion for each ()/{} as well, + // but since we're expecting well formed code it shouldn't matter in practice. + struct ParserState { DavidGoldman wrote: Done https://github.com/llvm/llvm-project/pull/76466 ___ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)
@@ -1029,5 +1172,151 @@ size_t renameRangeAdjustmentCost(ArrayRef Indexed, ArrayRef Lexed, return Cost; } +static bool isMatchingSelectorName(const syntax::Token , + const syntax::Token , + const SourceManager , + llvm::StringRef SelectorName) { + if (SelectorName.empty()) +return Cur.kind() == tok::colon; + return Cur.kind() == tok::identifier && Next.kind() == tok::colon && + Cur.text(SM) == SelectorName && + // We require the selector name and : to be contiguous. + // e.g. support `foo:` but not `foo :`. + Cur.endLocation() == Next.location(); +} + +static bool isSelectorLike(const syntax::Token , + const syntax::Token ) { + return Cur.kind() == tok::identifier && Next.kind() == tok::colon && + // We require the selector name and : to be contiguous. + // e.g. support `foo:` but not `foo :`. + Cur.endLocation() == Next.location(); +} + +static void +lex(llvm::StringRef Code, const LangOptions , +llvm::function_ref +Action) { + // FIXME: InMemoryFileAdapter crashes unless the buffer is null terminated! + std::string NullTerminatedCode = Code.str(); + SourceManagerForFile FileSM("mock_file_name.cpp", NullTerminatedCode); + auto = FileSM.get(); + for (const auto : syntax::tokenize(SM.getMainFileID(), SM, LangOpts)) +Action(Tok, SM); +} + +std::vector collectRenameIdentifierRanges( +llvm::StringRef Identifier, llvm::StringRef Content, +const LangOptions , std::optional Selector) { + std::vector Ranges; + if (!Selector) { +lex(Content, LangOpts, +[&](const syntax::Token , const SourceManager ) { + if (Tok.kind() != tok::identifier || Tok.text(SM) != Identifier) +return; + Ranges.emplace_back( + halfOpenToRange(SM, Tok.range(SM).toCharRange(SM))); +}); +return Ranges; + } + // FIXME: InMemoryFileAdapter crashes unless the buffer is null terminated! + std::string NullTerminatedCode = Content.str(); + SourceManagerForFile FileSM("mock_file_name.cpp", NullTerminatedCode); + auto = FileSM.get(); + + auto Tokens = syntax::tokenize(SM.getMainFileID(), SM, LangOpts); + unsigned Last = Tokens.size() - 1; + + // One parser state for top level and each `[]` pair, can be nested. + // Technically we should have a state or recursion for each ()/{} as well, + // but since we're expecting well formed code it shouldn't matter in practice. + struct ParserState { +unsigned ParenCount = 0; +unsigned BraceCount = 0; +std::vector Pieces; + }; + + // We have to track square brackets, parens and braces as we want to skip the + // tokens inside them. This ensures that we don't use identical selector + // pieces in inner message sends, blocks, lambdas and @selector expressions. + std::vector States = {ParserState()}; + unsigned NumPieces = Selector->getNumArgs(); + + for (unsigned Index = 0; Index < Last; ++Index) { +auto = States.back(); +auto = State.Pieces; +const auto = Tokens[Index]; +const auto Kind = Tok.kind(); +auto PieceCount = Pieces.size(); + +if (State.ParenCount == 0) { + // Check for matches until we find all selector pieces. + if (PieceCount < NumPieces && + isMatchingSelectorName(Tok, Tokens[Index + 1], SM, + Selector->getNameForSlot(PieceCount))) { +if (!Selector->getNameForSlot(PieceCount).empty()) { + // Skip the ':' after the name. This ensures that it won't match a + // follow-up selector piece with an empty name. + ++Index; +} +Pieces.push_back(halfOpenToRange(SM, Tok.range(SM).toCharRange(SM))); +continue; + } + // If we've found all pieces, we still need to try to consume more pieces + // as it's possible the selector being renamed is a prefix of this method + // name. DavidGoldman wrote: Updated, parseMessageExpression can return early but the top level parse can't. https://github.com/llvm/llvm-project/pull/76466 ___ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)
@@ -1029,5 +1172,151 @@ size_t renameRangeAdjustmentCost(ArrayRef Indexed, ArrayRef Lexed, return Cost; } +static bool isMatchingSelectorName(const syntax::Token , + const syntax::Token , + const SourceManager , + llvm::StringRef SelectorName) { + if (SelectorName.empty()) +return Cur.kind() == tok::colon; + return Cur.kind() == tok::identifier && Next.kind() == tok::colon && + Cur.text(SM) == SelectorName && + // We require the selector name and : to be contiguous. + // e.g. support `foo:` but not `foo :`. + Cur.endLocation() == Next.location(); +} + +static bool isSelectorLike(const syntax::Token , + const syntax::Token ) { + return Cur.kind() == tok::identifier && Next.kind() == tok::colon && + // We require the selector name and : to be contiguous. + // e.g. support `foo:` but not `foo :`. + Cur.endLocation() == Next.location(); +} + +static void +lex(llvm::StringRef Code, const LangOptions , +llvm::function_ref +Action) { + // FIXME: InMemoryFileAdapter crashes unless the buffer is null terminated! + std::string NullTerminatedCode = Code.str(); + SourceManagerForFile FileSM("mock_file_name.cpp", NullTerminatedCode); + auto = FileSM.get(); + for (const auto : syntax::tokenize(SM.getMainFileID(), SM, LangOpts)) +Action(Tok, SM); +} + +std::vector collectRenameIdentifierRanges( +llvm::StringRef Identifier, llvm::StringRef Content, +const LangOptions , std::optional Selector) { + std::vector Ranges; + if (!Selector) { +lex(Content, LangOpts, +[&](const syntax::Token , const SourceManager ) { + if (Tok.kind() != tok::identifier || Tok.text(SM) != Identifier) +return; + Ranges.emplace_back( + halfOpenToRange(SM, Tok.range(SM).toCharRange(SM))); +}); +return Ranges; + } + // FIXME: InMemoryFileAdapter crashes unless the buffer is null terminated! + std::string NullTerminatedCode = Content.str(); + SourceManagerForFile FileSM("mock_file_name.cpp", NullTerminatedCode); + auto = FileSM.get(); + + auto Tokens = syntax::tokenize(SM.getMainFileID(), SM, LangOpts); + unsigned Last = Tokens.size() - 1; + + // One parser state for top level and each `[]` pair, can be nested. + // Technically we should have a state or recursion for each ()/{} as well, + // but since we're expecting well formed code it shouldn't matter in practice. + struct ParserState { +unsigned ParenCount = 0; +unsigned BraceCount = 0; +std::vector Pieces; + }; + + // We have to track square brackets, parens and braces as we want to skip the + // tokens inside them. This ensures that we don't use identical selector + // pieces in inner message sends, blocks, lambdas and @selector expressions. + std::vector States = {ParserState()}; + unsigned NumPieces = Selector->getNumArgs(); + + for (unsigned Index = 0; Index < Last; ++Index) { +auto = States.back(); +auto = State.Pieces; +const auto = Tokens[Index]; +const auto Kind = Tok.kind(); +auto PieceCount = Pieces.size(); + +if (State.ParenCount == 0) { + // Check for matches until we find all selector pieces. + if (PieceCount < NumPieces && + isMatchingSelectorName(Tok, Tokens[Index + 1], SM, + Selector->getNameForSlot(PieceCount))) { +if (!Selector->getNameForSlot(PieceCount).empty()) { DavidGoldman wrote: Invalid or empty selector piece (foo::) https://github.com/llvm/llvm-project/pull/76466 ___ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)
@@ -1029,5 +1172,151 @@ size_t renameRangeAdjustmentCost(ArrayRef Indexed, ArrayRef Lexed, return Cost; } +static bool isMatchingSelectorName(const syntax::Token , + const syntax::Token , + const SourceManager , + llvm::StringRef SelectorName) { + if (SelectorName.empty()) +return Cur.kind() == tok::colon; + return Cur.kind() == tok::identifier && Next.kind() == tok::colon && + Cur.text(SM) == SelectorName && + // We require the selector name and : to be contiguous. + // e.g. support `foo:` but not `foo :`. + Cur.endLocation() == Next.location(); +} + +static bool isSelectorLike(const syntax::Token , + const syntax::Token ) { + return Cur.kind() == tok::identifier && Next.kind() == tok::colon && + // We require the selector name and : to be contiguous. + // e.g. support `foo:` but not `foo :`. + Cur.endLocation() == Next.location(); +} + +static void +lex(llvm::StringRef Code, const LangOptions , +llvm::function_ref +Action) { + // FIXME: InMemoryFileAdapter crashes unless the buffer is null terminated! + std::string NullTerminatedCode = Code.str(); + SourceManagerForFile FileSM("mock_file_name.cpp", NullTerminatedCode); + auto = FileSM.get(); + for (const auto : syntax::tokenize(SM.getMainFileID(), SM, LangOpts)) +Action(Tok, SM); +} + +std::vector collectRenameIdentifierRanges( +llvm::StringRef Identifier, llvm::StringRef Content, +const LangOptions , std::optional Selector) { + std::vector Ranges; + if (!Selector) { +lex(Content, LangOpts, +[&](const syntax::Token , const SourceManager ) { + if (Tok.kind() != tok::identifier || Tok.text(SM) != Identifier) +return; + Ranges.emplace_back( + halfOpenToRange(SM, Tok.range(SM).toCharRange(SM))); +}); +return Ranges; + } + // FIXME: InMemoryFileAdapter crashes unless the buffer is null terminated! + std::string NullTerminatedCode = Content.str(); + SourceManagerForFile FileSM("mock_file_name.cpp", NullTerminatedCode); + auto = FileSM.get(); + + auto Tokens = syntax::tokenize(SM.getMainFileID(), SM, LangOpts); + unsigned Last = Tokens.size() - 1; + + // One parser state for top level and each `[]` pair, can be nested. + // Technically we should have a state or recursion for each ()/{} as well, + // but since we're expecting well formed code it shouldn't matter in practice. + struct ParserState { +unsigned ParenCount = 0; +unsigned BraceCount = 0; +std::vector Pieces; + }; + + // We have to track square brackets, parens and braces as we want to skip the + // tokens inside them. This ensures that we don't use identical selector + // pieces in inner message sends, blocks, lambdas and @selector expressions. + std::vector States = {ParserState()}; + unsigned NumPieces = Selector->getNumArgs(); + + for (unsigned Index = 0; Index < Last; ++Index) { +auto = States.back(); +auto = State.Pieces; +const auto = Tokens[Index]; +const auto Kind = Tok.kind(); +auto PieceCount = Pieces.size(); + +if (State.ParenCount == 0) { + // Check for matches until we find all selector pieces. + if (PieceCount < NumPieces && + isMatchingSelectorName(Tok, Tokens[Index + 1], SM, DavidGoldman wrote: It's not, Last = Len -1 and we go until < Last, so Len - 2. https://github.com/llvm/llvm-project/pull/76466 ___ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)
@@ -1029,5 +1172,151 @@ size_t renameRangeAdjustmentCost(ArrayRef Indexed, ArrayRef Lexed, return Cost; } +static bool isMatchingSelectorName(const syntax::Token , + const syntax::Token , + const SourceManager , + llvm::StringRef SelectorName) { + if (SelectorName.empty()) +return Cur.kind() == tok::colon; + return Cur.kind() == tok::identifier && Next.kind() == tok::colon && + Cur.text(SM) == SelectorName && + // We require the selector name and : to be contiguous. + // e.g. support `foo:` but not `foo :`. + Cur.endLocation() == Next.location(); +} + +static bool isSelectorLike(const syntax::Token , + const syntax::Token ) { + return Cur.kind() == tok::identifier && Next.kind() == tok::colon && + // We require the selector name and : to be contiguous. + // e.g. support `foo:` but not `foo :`. + Cur.endLocation() == Next.location(); +} + +static void +lex(llvm::StringRef Code, const LangOptions , +llvm::function_ref +Action) { + // FIXME: InMemoryFileAdapter crashes unless the buffer is null terminated! + std::string NullTerminatedCode = Code.str(); + SourceManagerForFile FileSM("mock_file_name.cpp", NullTerminatedCode); + auto = FileSM.get(); + for (const auto : syntax::tokenize(SM.getMainFileID(), SM, LangOpts)) +Action(Tok, SM); +} + +std::vector collectRenameIdentifierRanges( +llvm::StringRef Identifier, llvm::StringRef Content, +const LangOptions , std::optional Selector) { + std::vector Ranges; + if (!Selector) { +lex(Content, LangOpts, +[&](const syntax::Token , const SourceManager ) { + if (Tok.kind() != tok::identifier || Tok.text(SM) != Identifier) +return; + Ranges.emplace_back( + halfOpenToRange(SM, Tok.range(SM).toCharRange(SM))); +}); +return Ranges; + } + // FIXME: InMemoryFileAdapter crashes unless the buffer is null terminated! + std::string NullTerminatedCode = Content.str(); + SourceManagerForFile FileSM("mock_file_name.cpp", NullTerminatedCode); + auto = FileSM.get(); + + auto Tokens = syntax::tokenize(SM.getMainFileID(), SM, LangOpts); + unsigned Last = Tokens.size() - 1; + + // One parser state for top level and each `[]` pair, can be nested. + // Technically we should have a state or recursion for each ()/{} as well, + // but since we're expecting well formed code it shouldn't matter in practice. + struct ParserState { +unsigned ParenCount = 0; +unsigned BraceCount = 0; +std::vector Pieces; DavidGoldman wrote: Updated with a comment, selector pieces/ranges as well as ( and { count https://github.com/llvm/llvm-project/pull/76466 ___ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)
@@ -1029,5 +1172,151 @@ size_t renameRangeAdjustmentCost(ArrayRef Indexed, ArrayRef Lexed, return Cost; } +static bool isMatchingSelectorName(const syntax::Token , DavidGoldman wrote: Done https://github.com/llvm/llvm-project/pull/76466 ___ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)
@@ -1017,9 +1159,10 @@ size_t renameRangeAdjustmentCost(ArrayRef Indexed, ArrayRef Lexed, int LastDLine = 0, LastDColumn = 0; int Cost = 0; for (size_t I = 0; I < Indexed.size(); ++I) { -int DLine = Indexed[I].start.line - Lexed[MappedIndex[I]].start.line; -int DColumn = -Indexed[I].start.character - Lexed[MappedIndex[I]].start.character; +int DLine = +Indexed[I].start.line - Lexed[MappedIndex[I]].range().start.line; +int DColumn = Indexed[I].start.character - + Lexed[MappedIndex[I]].range().start.character; DavidGoldman wrote: This was formatted because it was changed, .start -> .range().start https://github.com/llvm/llvm-project/pull/76466 ___ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)
@@ -1007,7 +1148,8 @@ std::optional> getMappedRanges(ArrayRef Indexed, // diff[0]: line + 1 <- insert a line before edit 0. // diff[1]: column + 1 <- remove a line between edits 0 and 1, and insert a // character on edit 1. -size_t renameRangeAdjustmentCost(ArrayRef Indexed, ArrayRef Lexed, +size_t renameRangeAdjustmentCost(ArrayRef Indexed, + ArrayRef Lexed, DavidGoldman wrote: These are formatted now because Range -> SymbolRange https://github.com/llvm/llvm-project/pull/76466 ___ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)
@@ -1029,5 +1172,151 @@ size_t renameRangeAdjustmentCost(ArrayRef Indexed, ArrayRef Lexed, return Cost; } +static bool isMatchingSelectorName(const syntax::Token , + const syntax::Token , + const SourceManager , + llvm::StringRef SelectorName) { + if (SelectorName.empty()) +return Cur.kind() == tok::colon; + return Cur.kind() == tok::identifier && Next.kind() == tok::colon && + Cur.text(SM) == SelectorName && + // We require the selector name and : to be contiguous. + // e.g. support `foo:` but not `foo :`. + Cur.endLocation() == Next.location(); +} + +static bool isSelectorLike(const syntax::Token , + const syntax::Token ) { + return Cur.kind() == tok::identifier && Next.kind() == tok::colon && + // We require the selector name and : to be contiguous. + // e.g. support `foo:` but not `foo :`. + Cur.endLocation() == Next.location(); +} + +static void +lex(llvm::StringRef Code, const LangOptions , +llvm::function_ref +Action) { + // FIXME: InMemoryFileAdapter crashes unless the buffer is null terminated! + std::string NullTerminatedCode = Code.str(); + SourceManagerForFile FileSM("mock_file_name.cpp", NullTerminatedCode); + auto = FileSM.get(); + for (const auto : syntax::tokenize(SM.getMainFileID(), SM, LangOpts)) +Action(Tok, SM); +} + +std::vector collectRenameIdentifierRanges( +llvm::StringRef Identifier, llvm::StringRef Content, +const LangOptions , std::optional Selector) { + std::vector Ranges; + if (!Selector) { +lex(Content, LangOpts, +[&](const syntax::Token , const SourceManager ) { + if (Tok.kind() != tok::identifier || Tok.text(SM) != Identifier) +return; + Ranges.emplace_back( + halfOpenToRange(SM, Tok.range(SM).toCharRange(SM))); +}); +return Ranges; DavidGoldman wrote: Done. https://github.com/llvm/llvm-project/pull/76466 ___ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)
@@ -1029,5 +1172,151 @@ size_t renameRangeAdjustmentCost(ArrayRef Indexed, ArrayRef Lexed, return Cost; } +static bool isMatchingSelectorName(const syntax::Token , + const syntax::Token , + const SourceManager , + llvm::StringRef SelectorName) { + if (SelectorName.empty()) +return Cur.kind() == tok::colon; + return Cur.kind() == tok::identifier && Next.kind() == tok::colon && + Cur.text(SM) == SelectorName && + // We require the selector name and : to be contiguous. + // e.g. support `foo:` but not `foo :`. DavidGoldman wrote: Done. https://github.com/llvm/llvm-project/pull/76466 ___ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)
@@ -778,12 +868,44 @@ llvm::Expected rename(const RenameInputs ) { return makeError(ReasonToReject::NoSymbolFound); if (DeclsUnderCursor.size() > 1) return makeError(ReasonToReject::AmbiguousSymbol); + std::string Placeholder; + // We expect the token under the cursor to be changed unless the user is + // renaming an Objective-C selector with multiple pieces and only renames + // some of the selector piece(s). + bool RenamingCurToken = true; const auto = **DeclsUnderCursor.begin(); - const auto *ID = RenameDecl.getIdentifier(); - if (!ID) -return makeError(ReasonToReject::UnsupportedSymbol); - if (ID->getName() == RInputs.NewName) -return makeError(ReasonToReject::SameName); + if (const auto *MD = dyn_cast()) { +const auto Sel = MD->getSelector(); +if (Sel.getAsString() == RInputs.NewName) + return makeError(ReasonToReject::SameName); +if (Sel.getNumArgs() != RInputs.NewName.count(':') && +RInputs.NewName != "__clangd_rename_placeholder") + return makeError( + InvalidName{InvalidName::BadIdentifier, RInputs.NewName.str()}); +if (Sel.getNumArgs() > 1) + Placeholder = Sel.getAsString(); DavidGoldman wrote: Done https://github.com/llvm/llvm-project/pull/76466 ___ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)
@@ -778,12 +868,44 @@ llvm::Expected rename(const RenameInputs ) { return makeError(ReasonToReject::NoSymbolFound); if (DeclsUnderCursor.size() > 1) return makeError(ReasonToReject::AmbiguousSymbol); + std::string Placeholder; + // We expect the token under the cursor to be changed unless the user is + // renaming an Objective-C selector with multiple pieces and only renames + // some of the selector piece(s). + bool RenamingCurToken = true; const auto = **DeclsUnderCursor.begin(); - const auto *ID = RenameDecl.getIdentifier(); - if (!ID) -return makeError(ReasonToReject::UnsupportedSymbol); - if (ID->getName() == RInputs.NewName) -return makeError(ReasonToReject::SameName); + if (const auto *MD = dyn_cast()) { +const auto Sel = MD->getSelector(); +if (Sel.getAsString() == RInputs.NewName) + return makeError(ReasonToReject::SameName); +if (Sel.getNumArgs() != RInputs.NewName.count(':') && +RInputs.NewName != "__clangd_rename_placeholder") + return makeError( + InvalidName{InvalidName::BadIdentifier, RInputs.NewName.str()}); +if (Sel.getNumArgs() > 1) + Placeholder = Sel.getAsString(); + +// See if the token under the cursor should actually be renamed. +if (RInputs.NewName != "__clangd_rename_placeholder") { + llvm::StringRef NewName = RInputs.NewName; + llvm::SmallVector NewNames; + NewName.split(NewNames, ":"); + + unsigned NumSelectorLocs = MD->getNumSelectorLocs(); + for (unsigned I = 0; I < NumSelectorLocs; ++I) { +if (MD->getSelectorLoc(I) == IdentifierToken->location()) { + RenamingCurToken = Sel.getNameForSlot(I) != NewNames[I]; + break; +} + } +} + } else { +const auto *ID = RenameDecl.getIdentifier(); +if (!ID) + return makeError(ReasonToReject::UnsupportedSymbol); +if (ID->getName() == RInputs.NewName) + return makeError(ReasonToReject::SameName); DavidGoldman wrote: Done https://github.com/llvm/llvm-project/pull/76466 ___ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)
@@ -778,12 +868,44 @@ llvm::Expected rename(const RenameInputs ) { return makeError(ReasonToReject::NoSymbolFound); if (DeclsUnderCursor.size() > 1) return makeError(ReasonToReject::AmbiguousSymbol); + std::string Placeholder; + // We expect the token under the cursor to be changed unless the user is + // renaming an Objective-C selector with multiple pieces and only renames + // some of the selector piece(s). + bool RenamingCurToken = true; const auto = **DeclsUnderCursor.begin(); - const auto *ID = RenameDecl.getIdentifier(); - if (!ID) -return makeError(ReasonToReject::UnsupportedSymbol); - if (ID->getName() == RInputs.NewName) -return makeError(ReasonToReject::SameName); + if (const auto *MD = dyn_cast()) { +const auto Sel = MD->getSelector(); +if (Sel.getAsString() == RInputs.NewName) + return makeError(ReasonToReject::SameName); +if (Sel.getNumArgs() != RInputs.NewName.count(':') && +RInputs.NewName != "__clangd_rename_placeholder") + return makeError( + InvalidName{InvalidName::BadIdentifier, RInputs.NewName.str()}); +if (Sel.getNumArgs() > 1) + Placeholder = Sel.getAsString(); + +// See if the token under the cursor should actually be renamed. +if (RInputs.NewName != "__clangd_rename_placeholder") { + llvm::StringRef NewName = RInputs.NewName; + llvm::SmallVector NewNames; + NewName.split(NewNames, ":"); + + unsigned NumSelectorLocs = MD->getNumSelectorLocs(); + for (unsigned I = 0; I < NumSelectorLocs; ++I) { +if (MD->getSelectorLoc(I) == IdentifierToken->location()) { + RenamingCurToken = Sel.getNameForSlot(I) != NewNames[I]; + break; +} + } +} + } else { +const auto *ID = RenameDecl.getIdentifier(); +if (!ID) + return makeError(ReasonToReject::UnsupportedSymbol); DavidGoldman wrote: Done https://github.com/llvm/llvm-project/pull/76466 ___ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)
@@ -778,12 +868,44 @@ llvm::Expected rename(const RenameInputs ) { return makeError(ReasonToReject::NoSymbolFound); if (DeclsUnderCursor.size() > 1) return makeError(ReasonToReject::AmbiguousSymbol); + std::string Placeholder; + // We expect the token under the cursor to be changed unless the user is + // renaming an Objective-C selector with multiple pieces and only renames + // some of the selector piece(s). + bool RenamingCurToken = true; const auto = **DeclsUnderCursor.begin(); - const auto *ID = RenameDecl.getIdentifier(); - if (!ID) -return makeError(ReasonToReject::UnsupportedSymbol); - if (ID->getName() == RInputs.NewName) -return makeError(ReasonToReject::SameName); + if (const auto *MD = dyn_cast()) { +const auto Sel = MD->getSelector(); +if (Sel.getAsString() == RInputs.NewName) + return makeError(ReasonToReject::SameName); +if (Sel.getNumArgs() != RInputs.NewName.count(':') && +RInputs.NewName != "__clangd_rename_placeholder") + return makeError( + InvalidName{InvalidName::BadIdentifier, RInputs.NewName.str()}); DavidGoldman wrote: Done https://github.com/llvm/llvm-project/pull/76466 ___ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)
@@ -778,12 +868,44 @@ llvm::Expected rename(const RenameInputs ) { return makeError(ReasonToReject::NoSymbolFound); if (DeclsUnderCursor.size() > 1) return makeError(ReasonToReject::AmbiguousSymbol); + std::string Placeholder; + // We expect the token under the cursor to be changed unless the user is + // renaming an Objective-C selector with multiple pieces and only renames + // some of the selector piece(s). + bool RenamingCurToken = true; const auto = **DeclsUnderCursor.begin(); - const auto *ID = RenameDecl.getIdentifier(); - if (!ID) -return makeError(ReasonToReject::UnsupportedSymbol); - if (ID->getName() == RInputs.NewName) -return makeError(ReasonToReject::SameName); + if (const auto *MD = dyn_cast()) { +const auto Sel = MD->getSelector(); +if (Sel.getAsString() == RInputs.NewName) + return makeError(ReasonToReject::SameName); +if (Sel.getNumArgs() != RInputs.NewName.count(':') && +RInputs.NewName != "__clangd_rename_placeholder") + return makeError( + InvalidName{InvalidName::BadIdentifier, RInputs.NewName.str()}); DavidGoldman wrote: Never mind, didn't see your comments below https://github.com/llvm/llvm-project/pull/76466 ___ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)
@@ -778,12 +868,44 @@ llvm::Expected rename(const RenameInputs ) { return makeError(ReasonToReject::NoSymbolFound); if (DeclsUnderCursor.size() > 1) return makeError(ReasonToReject::AmbiguousSymbol); + std::string Placeholder; + // We expect the token under the cursor to be changed unless the user is + // renaming an Objective-C selector with multiple pieces and only renames + // some of the selector piece(s). + bool RenamingCurToken = true; const auto = **DeclsUnderCursor.begin(); - const auto *ID = RenameDecl.getIdentifier(); - if (!ID) -return makeError(ReasonToReject::UnsupportedSymbol); - if (ID->getName() == RInputs.NewName) -return makeError(ReasonToReject::SameName); + if (const auto *MD = dyn_cast()) { +const auto Sel = MD->getSelector(); +if (Sel.getAsString() == RInputs.NewName) + return makeError(ReasonToReject::SameName); +if (Sel.getNumArgs() != RInputs.NewName.count(':') && +RInputs.NewName != "__clangd_rename_placeholder") + return makeError( + InvalidName{InvalidName::BadIdentifier, RInputs.NewName.str()}); DavidGoldman wrote: Are you sure? I kept this separate since the else statement below performs similar logic outside of checkName. Should I move both into checkName? https://github.com/llvm/llvm-project/pull/76466 ___ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)
@@ -746,6 +812,30 @@ void findNearMiss( } // namespace +SymbolRange::SymbolRange(Range R) : Ranges({R}) {} + +SymbolRange::SymbolRange(std::vector Ranges) +: Ranges(std::move(Ranges)) {} + +Range SymbolRange::range() const { return Ranges.front(); } + +bool operator==(const SymbolRange , const SymbolRange ) { + return LHS.Ranges == RHS.Ranges; +} +bool operator!=(const SymbolRange , const SymbolRange ) { + return !(LHS == RHS); +} +bool operator<(const SymbolRange , const SymbolRange ) { + return LHS.range() < RHS.range(); +} + +std::vector symbolRanges(const std::vector Ranges) { DavidGoldman wrote: Done https://github.com/llvm/llvm-project/pull/76466 ___ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)
@@ -543,6 +550,45 @@ std::optional checkName(const NamedDecl , return Result; } +clangd::Range tokenRangeForLoc(ParsedAST , SourceLocation TokLoc, + const SourceManager , + const LangOptions ) { + const auto *Token = AST.getTokens().spelledTokenAt(TokLoc); + assert(Token && "got inclusion at wrong offset"); + clangd::Range Result; + Result.start = sourceLocToPosition(SM, Token->location()); + Result.end = sourceLocToPosition(SM, Token->endLocation()); + return Result; +} + +// AST-based ObjC method rename, it renames all occurrences in the main file +// even for selectors which may have multiple tokens. +llvm::Expected +renameObjCMethodWithinFile(ParsedAST , const ObjCMethodDecl *MD, + llvm::StringRef NewName, + std::vector Locs) { + const SourceManager = AST.getSourceManager(); + auto Code = SM.getBufferData(SM.getMainFileID()); + auto RenameIdentifier = MD->getSelector().getNameForSlot(0).str(); + llvm::SmallVector NewNames; + NewName.split(NewNames, ":"); + if (NewNames.empty()) +NewNames.push_back(NewName); + + std::vector Ranges; + const auto = MD->getASTContext().getLangOpts(); + for (const auto : Locs) +Ranges.push_back(tokenRangeForLoc(AST, Loc, SM, LangOpts)); + auto FilePath = AST.tuPath(); + auto RenameRanges = collectRenameIdentifierRanges( DavidGoldman wrote: Hmm, what do you think? we could call adjustRenameRanges to make sure what we've lexed matches. It's a bit more complex to guide lexing since we need to differentiate between method decls/exprs but maybe that could work. Just not sure it's worth adding much on top of this logic - we originally had a purely AST-visitor based approach but I'd prefer not to add more complexity here. https://github.com/llvm/llvm-project/pull/76466 ___ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)
@@ -543,6 +550,45 @@ std::optional checkName(const NamedDecl , return Result; } +clangd::Range tokenRangeForLoc(ParsedAST , SourceLocation TokLoc, + const SourceManager , + const LangOptions ) { + const auto *Token = AST.getTokens().spelledTokenAt(TokLoc); + assert(Token && "got inclusion at wrong offset"); + clangd::Range Result; + Result.start = sourceLocToPosition(SM, Token->location()); + Result.end = sourceLocToPosition(SM, Token->endLocation()); + return Result; +} + +// AST-based ObjC method rename, it renames all occurrences in the main file +// even for selectors which may have multiple tokens. DavidGoldman wrote: Done https://github.com/llvm/llvm-project/pull/76466 ___ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)
@@ -543,6 +550,45 @@ std::optional checkName(const NamedDecl , return Result; } +clangd::Range tokenRangeForLoc(ParsedAST , SourceLocation TokLoc, + const SourceManager , + const LangOptions ) { + const auto *Token = AST.getTokens().spelledTokenAt(TokLoc); + assert(Token && "got inclusion at wrong offset"); + clangd::Range Result; + Result.start = sourceLocToPosition(SM, Token->location()); + Result.end = sourceLocToPosition(SM, Token->endLocation()); + return Result; +} + +// AST-based ObjC method rename, it renames all occurrences in the main file +// even for selectors which may have multiple tokens. +llvm::Expected +renameObjCMethodWithinFile(ParsedAST , const ObjCMethodDecl *MD, + llvm::StringRef NewName, + std::vector Locs) { + const SourceManager = AST.getSourceManager(); + auto Code = SM.getBufferData(SM.getMainFileID()); + auto RenameIdentifier = MD->getSelector().getNameForSlot(0).str(); + llvm::SmallVector NewNames; + NewName.split(NewNames, ":"); + if (NewNames.empty()) DavidGoldman wrote: Done https://github.com/llvm/llvm-project/pull/76466 ___ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)
@@ -543,6 +550,45 @@ std::optional checkName(const NamedDecl , return Result; } +clangd::Range tokenRangeForLoc(ParsedAST , SourceLocation TokLoc, + const SourceManager , + const LangOptions ) { + const auto *Token = AST.getTokens().spelledTokenAt(TokLoc); + assert(Token && "got inclusion at wrong offset"); DavidGoldman wrote: Done https://github.com/llvm/llvm-project/pull/76466 ___ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)
@@ -681,12 +718,26 @@ renameOutsideFile(const NamedDecl , llvm::StringRef MainFilePath, ExpBuffer.getError().message()); continue; } +std::string RenameIdentifier = RenameDecl.getNameAsString(); +std::optional Selector = std::nullopt; +llvm::SmallVector NewNames; +if (const auto *MD = dyn_cast()) { + if (MD->getSelector().getNumArgs() > 1) { +RenameIdentifier = MD->getSelector().getNameForSlot(0).str(); +Selector = MD->getSelector(); +NewName.split(NewNames, ":"); DavidGoldman wrote: Yep, you can have empty selector fragments. https://github.com/llvm/llvm-project/pull/76466 ___ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)
@@ -893,22 +964,36 @@ llvm::Expected buildRenameEdit(llvm::StringRef AbsFilePath, return LastOffset; }; - std::vector> OccurrencesOffsets; - for (const auto : Occurrences) { -auto StartOffset = Offset(R.start); -if (!StartOffset) - return StartOffset.takeError(); -auto EndOffset = Offset(R.end); -if (!EndOffset) - return EndOffset.takeError(); -OccurrencesOffsets.push_back({*StartOffset, *EndOffset}); + struct OccurrenceOffset { +size_t Start; +size_t End; +llvm::StringRef NewName; + +OccurrenceOffset(size_t Start, size_t End, llvm::StringRef NewName) : + Start(Start), End(End), NewName(NewName) {} + }; + + std::vector OccurrencesOffsets; + for (const auto : Occurrences) { +for (auto It = SR.Ranges.begin(); It != SR.Ranges.end(); ++It) { DavidGoldman wrote: Done https://github.com/llvm/llvm-project/pull/76466 ___ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)
https://github.com/DavidGoldman updated https://github.com/llvm/llvm-project/pull/76466 >From 4caf5b3c779bf18236b4b0be5bc7147d10339f2b Mon Sep 17 00:00:00 2001 From: David Goldman Date: Tue, 26 Dec 2023 15:59:01 -0500 Subject: [PATCH 1/4] [clangd][SymbolCollector] Treat ObjC methods as spelled We'll treat multi-arg methods as spelled once we have full rename support for them. --- .../clangd/index/SymbolCollector.cpp | 6 ++- .../clangd/unittests/SymbolCollectorTests.cpp | 42 +++ 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/clang-tools-extra/clangd/index/SymbolCollector.cpp b/clang-tools-extra/clangd/index/SymbolCollector.cpp index 7ef4b15febad22f..336bc3506bb3608 100644 --- a/clang-tools-extra/clangd/index/SymbolCollector.cpp +++ b/clang-tools-extra/clangd/index/SymbolCollector.cpp @@ -174,7 +174,9 @@ bool isSpelled(SourceLocation Loc, const NamedDecl ) { auto Name = ND.getDeclName(); const auto NameKind = Name.getNameKind(); if (NameKind != DeclarationName::Identifier && - NameKind != DeclarationName::CXXConstructorName) + NameKind != DeclarationName::CXXConstructorName && + NameKind != DeclarationName::ObjCZeroArgSelector && + NameKind != DeclarationName::ObjCOneArgSelector) return false; const auto = ND.getASTContext(); const auto = AST.getSourceManager(); @@ -183,6 +185,8 @@ bool isSpelled(SourceLocation Loc, const NamedDecl ) { if (clang::Lexer::getRawToken(Loc, Tok, SM, LO)) return false; auto StrName = Name.getAsString(); + if (const auto *MD = dyn_cast()) +StrName = MD->getSelector().getNameForSlot(0).str(); return clang::Lexer::getSpelling(Tok, SM, LO) == StrName; } } // namespace diff --git a/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp b/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp index 9cdc57ec01f3276..1d4e1c1d75ea230 100644 --- a/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp +++ b/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp @@ -105,6 +105,9 @@ MATCHER(refRange, "") { MATCHER_P2(OverriddenBy, Subject, Object, "") { return arg == Relation{Subject.ID, RelationKind::OverriddenBy, Object.ID}; } +MATCHER(isSpelled, "") { + return static_cast(arg.Kind & RefKind::Spelled); +} ::testing::Matcher &> haveRanges(const std::vector Ranges) { return ::testing::UnorderedPointwise(refRange(), Ranges); @@ -524,6 +527,45 @@ TEST_F(SymbolCollectorTest, templateArgs) { forCodeCompletion(false); } +TEST_F(SymbolCollectorTest, ObjCRefs) { + Annotations Header(R"( + @interface Person + - (void)$talk[[talk]]; + - (void)$say[[say]]:(id)something; + @end + @interface Person (Category) + - (void)categoryMethod; + - (void)multiArg:(id)a method:(id)b; + @end + )"); + Annotations Main(R"( + @implementation Person + - (void)$talk[[talk]] {} + - (void)$say[[say]]:(id)something {} + @end + + void fff(Person *p) { +[p $talk[[talk]]]; +[p $say[[say]]:0]; +[p categoryMethod]; +[p multiArg:0 method:0]; + } + )"); + CollectorOpts.RefFilter = RefKind::All; + CollectorOpts.CollectMainFileRefs = true; + TestFileName = testPath("test.m"); + runSymbolCollector(Header.code(), Main.code(), + {"-fblocks", "-xobjective-c++", "-Wno-objc-root-class"}); + EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "Person::talk").ID, + haveRanges(Main.ranges("talk"); + EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "Person::say:").ID, + haveRanges(Main.ranges("say"); + EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "Person::categoryMethod").ID, + ElementsAre(isSpelled(); + EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "Person::multiArg:method:").ID, + ElementsAre(Not(isSpelled()); +} + TEST_F(SymbolCollectorTest, ObjCSymbols) { const std::string Header = R"( @interface Person >From 1b6a09464ff5c7b1988fcb479d0a4ff876f696e6 Mon Sep 17 00:00:00 2001 From: David Goldman Date: Tue, 26 Dec 2023 16:12:03 -0500 Subject: [PATCH 2/4] Run clang-format --- .../clangd/unittests/SymbolCollectorTests.cpp | 10 ++ 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp b/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp index 1d4e1c1d75ea230..5c20b950e4eac0d 100644 --- a/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp +++ b/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp @@ -560,10 +560,12 @@ TEST_F(SymbolCollectorTest, ObjCRefs) { haveRanges(Main.ranges("talk"); EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "Person::say:").ID, haveRanges(Main.ranges("say"); - EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols,
[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)
https://github.com/DavidGoldman updated https://github.com/llvm/llvm-project/pull/76466 >From 4caf5b3c779bf18236b4b0be5bc7147d10339f2b Mon Sep 17 00:00:00 2001 From: David Goldman Date: Tue, 26 Dec 2023 15:59:01 -0500 Subject: [PATCH 1/3] [clangd][SymbolCollector] Treat ObjC methods as spelled We'll treat multi-arg methods as spelled once we have full rename support for them. --- .../clangd/index/SymbolCollector.cpp | 6 ++- .../clangd/unittests/SymbolCollectorTests.cpp | 42 +++ 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/clang-tools-extra/clangd/index/SymbolCollector.cpp b/clang-tools-extra/clangd/index/SymbolCollector.cpp index 7ef4b15febad22..336bc3506bb360 100644 --- a/clang-tools-extra/clangd/index/SymbolCollector.cpp +++ b/clang-tools-extra/clangd/index/SymbolCollector.cpp @@ -174,7 +174,9 @@ bool isSpelled(SourceLocation Loc, const NamedDecl ) { auto Name = ND.getDeclName(); const auto NameKind = Name.getNameKind(); if (NameKind != DeclarationName::Identifier && - NameKind != DeclarationName::CXXConstructorName) + NameKind != DeclarationName::CXXConstructorName && + NameKind != DeclarationName::ObjCZeroArgSelector && + NameKind != DeclarationName::ObjCOneArgSelector) return false; const auto = ND.getASTContext(); const auto = AST.getSourceManager(); @@ -183,6 +185,8 @@ bool isSpelled(SourceLocation Loc, const NamedDecl ) { if (clang::Lexer::getRawToken(Loc, Tok, SM, LO)) return false; auto StrName = Name.getAsString(); + if (const auto *MD = dyn_cast()) +StrName = MD->getSelector().getNameForSlot(0).str(); return clang::Lexer::getSpelling(Tok, SM, LO) == StrName; } } // namespace diff --git a/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp b/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp index 9cdc57ec01f327..1d4e1c1d75ea23 100644 --- a/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp +++ b/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp @@ -105,6 +105,9 @@ MATCHER(refRange, "") { MATCHER_P2(OverriddenBy, Subject, Object, "") { return arg == Relation{Subject.ID, RelationKind::OverriddenBy, Object.ID}; } +MATCHER(isSpelled, "") { + return static_cast(arg.Kind & RefKind::Spelled); +} ::testing::Matcher &> haveRanges(const std::vector Ranges) { return ::testing::UnorderedPointwise(refRange(), Ranges); @@ -524,6 +527,45 @@ TEST_F(SymbolCollectorTest, templateArgs) { forCodeCompletion(false); } +TEST_F(SymbolCollectorTest, ObjCRefs) { + Annotations Header(R"( + @interface Person + - (void)$talk[[talk]]; + - (void)$say[[say]]:(id)something; + @end + @interface Person (Category) + - (void)categoryMethod; + - (void)multiArg:(id)a method:(id)b; + @end + )"); + Annotations Main(R"( + @implementation Person + - (void)$talk[[talk]] {} + - (void)$say[[say]]:(id)something {} + @end + + void fff(Person *p) { +[p $talk[[talk]]]; +[p $say[[say]]:0]; +[p categoryMethod]; +[p multiArg:0 method:0]; + } + )"); + CollectorOpts.RefFilter = RefKind::All; + CollectorOpts.CollectMainFileRefs = true; + TestFileName = testPath("test.m"); + runSymbolCollector(Header.code(), Main.code(), + {"-fblocks", "-xobjective-c++", "-Wno-objc-root-class"}); + EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "Person::talk").ID, + haveRanges(Main.ranges("talk"); + EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "Person::say:").ID, + haveRanges(Main.ranges("say"); + EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "Person::categoryMethod").ID, + ElementsAre(isSpelled(); + EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "Person::multiArg:method:").ID, + ElementsAre(Not(isSpelled()); +} + TEST_F(SymbolCollectorTest, ObjCSymbols) { const std::string Header = R"( @interface Person >From 1b6a09464ff5c7b1988fcb479d0a4ff876f696e6 Mon Sep 17 00:00:00 2001 From: David Goldman Date: Tue, 26 Dec 2023 16:12:03 -0500 Subject: [PATCH 2/3] Run clang-format --- .../clangd/unittests/SymbolCollectorTests.cpp | 10 ++ 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp b/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp index 1d4e1c1d75ea23..5c20b950e4eac0 100644 --- a/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp +++ b/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp @@ -560,10 +560,12 @@ TEST_F(SymbolCollectorTest, ObjCRefs) { haveRanges(Main.ranges("talk"); EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "Person::say:").ID, haveRanges(Main.ranges("say"); - EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols,
[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)
@@ -893,22 +964,36 @@ llvm::Expected buildRenameEdit(llvm::StringRef AbsFilePath, return LastOffset; }; - std::vector> OccurrencesOffsets; - for (const auto : Occurrences) { -auto StartOffset = Offset(R.start); -if (!StartOffset) - return StartOffset.takeError(); -auto EndOffset = Offset(R.end); -if (!EndOffset) - return EndOffset.takeError(); -OccurrencesOffsets.push_back({*StartOffset, *EndOffset}); + struct OccurrenceOffset { +size_t Start; +size_t End; +llvm::StringRef NewName; + +OccurrenceOffset(size_t Start, size_t End, llvm::StringRef NewName) : + Start(Start), End(End), NewName(NewName) {} + }; + + std::vector OccurrencesOffsets; + for (const auto : Occurrences) { +for (auto It = SR.Ranges.begin(); It != SR.Ranges.end(); ++It) { DavidGoldman wrote: Not sure I follow, the loop construction doesn't use NewNames at all, you might have misread it? https://github.com/llvm/llvm-project/pull/76466 ___ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)
@@ -1436,6 +1436,14 @@ struct RenameParams { }; bool fromJSON(const llvm::json::Value &, RenameParams &, llvm::json::Path); +struct PrepareRenameResult { + /// Range of the string to rename. + Range range; + /// Placeholder text to use in the editor, if set. + std::optional placeholder; DavidGoldman wrote: I think it'll be encoded slightly different but VS Code did appear to handle an empty placeholder properly, but I'll have clangd omit encoding it if empty to be safe. https://github.com/llvm/llvm-project/pull/76466 ___ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)
https://github.com/DavidGoldman updated https://github.com/llvm/llvm-project/pull/76466 >From 4caf5b3c779bf18236b4b0be5bc7147d10339f2b Mon Sep 17 00:00:00 2001 From: David Goldman Date: Tue, 26 Dec 2023 15:59:01 -0500 Subject: [PATCH 1/7] [clangd][SymbolCollector] Treat ObjC methods as spelled We'll treat multi-arg methods as spelled once we have full rename support for them. --- .../clangd/index/SymbolCollector.cpp | 6 ++- .../clangd/unittests/SymbolCollectorTests.cpp | 42 +++ 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/clang-tools-extra/clangd/index/SymbolCollector.cpp b/clang-tools-extra/clangd/index/SymbolCollector.cpp index 7ef4b15febad22..336bc3506bb360 100644 --- a/clang-tools-extra/clangd/index/SymbolCollector.cpp +++ b/clang-tools-extra/clangd/index/SymbolCollector.cpp @@ -174,7 +174,9 @@ bool isSpelled(SourceLocation Loc, const NamedDecl ) { auto Name = ND.getDeclName(); const auto NameKind = Name.getNameKind(); if (NameKind != DeclarationName::Identifier && - NameKind != DeclarationName::CXXConstructorName) + NameKind != DeclarationName::CXXConstructorName && + NameKind != DeclarationName::ObjCZeroArgSelector && + NameKind != DeclarationName::ObjCOneArgSelector) return false; const auto = ND.getASTContext(); const auto = AST.getSourceManager(); @@ -183,6 +185,8 @@ bool isSpelled(SourceLocation Loc, const NamedDecl ) { if (clang::Lexer::getRawToken(Loc, Tok, SM, LO)) return false; auto StrName = Name.getAsString(); + if (const auto *MD = dyn_cast()) +StrName = MD->getSelector().getNameForSlot(0).str(); return clang::Lexer::getSpelling(Tok, SM, LO) == StrName; } } // namespace diff --git a/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp b/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp index 9cdc57ec01f327..1d4e1c1d75ea23 100644 --- a/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp +++ b/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp @@ -105,6 +105,9 @@ MATCHER(refRange, "") { MATCHER_P2(OverriddenBy, Subject, Object, "") { return arg == Relation{Subject.ID, RelationKind::OverriddenBy, Object.ID}; } +MATCHER(isSpelled, "") { + return static_cast(arg.Kind & RefKind::Spelled); +} ::testing::Matcher &> haveRanges(const std::vector Ranges) { return ::testing::UnorderedPointwise(refRange(), Ranges); @@ -524,6 +527,45 @@ TEST_F(SymbolCollectorTest, templateArgs) { forCodeCompletion(false); } +TEST_F(SymbolCollectorTest, ObjCRefs) { + Annotations Header(R"( + @interface Person + - (void)$talk[[talk]]; + - (void)$say[[say]]:(id)something; + @end + @interface Person (Category) + - (void)categoryMethod; + - (void)multiArg:(id)a method:(id)b; + @end + )"); + Annotations Main(R"( + @implementation Person + - (void)$talk[[talk]] {} + - (void)$say[[say]]:(id)something {} + @end + + void fff(Person *p) { +[p $talk[[talk]]]; +[p $say[[say]]:0]; +[p categoryMethod]; +[p multiArg:0 method:0]; + } + )"); + CollectorOpts.RefFilter = RefKind::All; + CollectorOpts.CollectMainFileRefs = true; + TestFileName = testPath("test.m"); + runSymbolCollector(Header.code(), Main.code(), + {"-fblocks", "-xobjective-c++", "-Wno-objc-root-class"}); + EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "Person::talk").ID, + haveRanges(Main.ranges("talk"); + EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "Person::say:").ID, + haveRanges(Main.ranges("say"); + EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "Person::categoryMethod").ID, + ElementsAre(isSpelled(); + EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "Person::multiArg:method:").ID, + ElementsAre(Not(isSpelled()); +} + TEST_F(SymbolCollectorTest, ObjCSymbols) { const std::string Header = R"( @interface Person >From 1b6a09464ff5c7b1988fcb479d0a4ff876f696e6 Mon Sep 17 00:00:00 2001 From: David Goldman Date: Tue, 26 Dec 2023 16:12:03 -0500 Subject: [PATCH 2/7] Run clang-format --- .../clangd/unittests/SymbolCollectorTests.cpp | 10 ++ 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp b/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp index 1d4e1c1d75ea23..5c20b950e4eac0 100644 --- a/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp +++ b/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp @@ -560,10 +560,12 @@ TEST_F(SymbolCollectorTest, ObjCRefs) { haveRanges(Main.ranges("talk"); EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "Person::say:").ID, haveRanges(Main.ranges("say"); - EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols,
[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)
https://github.com/DavidGoldman updated https://github.com/llvm/llvm-project/pull/76466 >From 4caf5b3c779bf18236b4b0be5bc7147d10339f2b Mon Sep 17 00:00:00 2001 From: David Goldman Date: Tue, 26 Dec 2023 15:59:01 -0500 Subject: [PATCH 1/6] [clangd][SymbolCollector] Treat ObjC methods as spelled We'll treat multi-arg methods as spelled once we have full rename support for them. --- .../clangd/index/SymbolCollector.cpp | 6 ++- .../clangd/unittests/SymbolCollectorTests.cpp | 42 +++ 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/clang-tools-extra/clangd/index/SymbolCollector.cpp b/clang-tools-extra/clangd/index/SymbolCollector.cpp index 7ef4b15febad22..336bc3506bb360 100644 --- a/clang-tools-extra/clangd/index/SymbolCollector.cpp +++ b/clang-tools-extra/clangd/index/SymbolCollector.cpp @@ -174,7 +174,9 @@ bool isSpelled(SourceLocation Loc, const NamedDecl ) { auto Name = ND.getDeclName(); const auto NameKind = Name.getNameKind(); if (NameKind != DeclarationName::Identifier && - NameKind != DeclarationName::CXXConstructorName) + NameKind != DeclarationName::CXXConstructorName && + NameKind != DeclarationName::ObjCZeroArgSelector && + NameKind != DeclarationName::ObjCOneArgSelector) return false; const auto = ND.getASTContext(); const auto = AST.getSourceManager(); @@ -183,6 +185,8 @@ bool isSpelled(SourceLocation Loc, const NamedDecl ) { if (clang::Lexer::getRawToken(Loc, Tok, SM, LO)) return false; auto StrName = Name.getAsString(); + if (const auto *MD = dyn_cast()) +StrName = MD->getSelector().getNameForSlot(0).str(); return clang::Lexer::getSpelling(Tok, SM, LO) == StrName; } } // namespace diff --git a/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp b/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp index 9cdc57ec01f327..1d4e1c1d75ea23 100644 --- a/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp +++ b/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp @@ -105,6 +105,9 @@ MATCHER(refRange, "") { MATCHER_P2(OverriddenBy, Subject, Object, "") { return arg == Relation{Subject.ID, RelationKind::OverriddenBy, Object.ID}; } +MATCHER(isSpelled, "") { + return static_cast(arg.Kind & RefKind::Spelled); +} ::testing::Matcher &> haveRanges(const std::vector Ranges) { return ::testing::UnorderedPointwise(refRange(), Ranges); @@ -524,6 +527,45 @@ TEST_F(SymbolCollectorTest, templateArgs) { forCodeCompletion(false); } +TEST_F(SymbolCollectorTest, ObjCRefs) { + Annotations Header(R"( + @interface Person + - (void)$talk[[talk]]; + - (void)$say[[say]]:(id)something; + @end + @interface Person (Category) + - (void)categoryMethod; + - (void)multiArg:(id)a method:(id)b; + @end + )"); + Annotations Main(R"( + @implementation Person + - (void)$talk[[talk]] {} + - (void)$say[[say]]:(id)something {} + @end + + void fff(Person *p) { +[p $talk[[talk]]]; +[p $say[[say]]:0]; +[p categoryMethod]; +[p multiArg:0 method:0]; + } + )"); + CollectorOpts.RefFilter = RefKind::All; + CollectorOpts.CollectMainFileRefs = true; + TestFileName = testPath("test.m"); + runSymbolCollector(Header.code(), Main.code(), + {"-fblocks", "-xobjective-c++", "-Wno-objc-root-class"}); + EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "Person::talk").ID, + haveRanges(Main.ranges("talk"); + EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "Person::say:").ID, + haveRanges(Main.ranges("say"); + EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "Person::categoryMethod").ID, + ElementsAre(isSpelled(); + EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "Person::multiArg:method:").ID, + ElementsAre(Not(isSpelled()); +} + TEST_F(SymbolCollectorTest, ObjCSymbols) { const std::string Header = R"( @interface Person >From 1b6a09464ff5c7b1988fcb479d0a4ff876f696e6 Mon Sep 17 00:00:00 2001 From: David Goldman Date: Tue, 26 Dec 2023 16:12:03 -0500 Subject: [PATCH 2/6] Run clang-format --- .../clangd/unittests/SymbolCollectorTests.cpp | 10 ++ 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp b/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp index 1d4e1c1d75ea23..5c20b950e4eac0 100644 --- a/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp +++ b/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp @@ -560,10 +560,12 @@ TEST_F(SymbolCollectorTest, ObjCRefs) { haveRanges(Main.ranges("talk"); EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "Person::say:").ID, haveRanges(Main.ranges("say"); - EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols,
[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)
https://github.com/DavidGoldman updated https://github.com/llvm/llvm-project/pull/76466 >From 4caf5b3c779bf18236b4b0be5bc7147d10339f2b Mon Sep 17 00:00:00 2001 From: David Goldman Date: Tue, 26 Dec 2023 15:59:01 -0500 Subject: [PATCH 1/5] [clangd][SymbolCollector] Treat ObjC methods as spelled We'll treat multi-arg methods as spelled once we have full rename support for them. --- .../clangd/index/SymbolCollector.cpp | 6 ++- .../clangd/unittests/SymbolCollectorTests.cpp | 42 +++ 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/clang-tools-extra/clangd/index/SymbolCollector.cpp b/clang-tools-extra/clangd/index/SymbolCollector.cpp index 7ef4b15febad22..336bc3506bb360 100644 --- a/clang-tools-extra/clangd/index/SymbolCollector.cpp +++ b/clang-tools-extra/clangd/index/SymbolCollector.cpp @@ -174,7 +174,9 @@ bool isSpelled(SourceLocation Loc, const NamedDecl ) { auto Name = ND.getDeclName(); const auto NameKind = Name.getNameKind(); if (NameKind != DeclarationName::Identifier && - NameKind != DeclarationName::CXXConstructorName) + NameKind != DeclarationName::CXXConstructorName && + NameKind != DeclarationName::ObjCZeroArgSelector && + NameKind != DeclarationName::ObjCOneArgSelector) return false; const auto = ND.getASTContext(); const auto = AST.getSourceManager(); @@ -183,6 +185,8 @@ bool isSpelled(SourceLocation Loc, const NamedDecl ) { if (clang::Lexer::getRawToken(Loc, Tok, SM, LO)) return false; auto StrName = Name.getAsString(); + if (const auto *MD = dyn_cast()) +StrName = MD->getSelector().getNameForSlot(0).str(); return clang::Lexer::getSpelling(Tok, SM, LO) == StrName; } } // namespace diff --git a/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp b/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp index 9cdc57ec01f327..1d4e1c1d75ea23 100644 --- a/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp +++ b/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp @@ -105,6 +105,9 @@ MATCHER(refRange, "") { MATCHER_P2(OverriddenBy, Subject, Object, "") { return arg == Relation{Subject.ID, RelationKind::OverriddenBy, Object.ID}; } +MATCHER(isSpelled, "") { + return static_cast(arg.Kind & RefKind::Spelled); +} ::testing::Matcher &> haveRanges(const std::vector Ranges) { return ::testing::UnorderedPointwise(refRange(), Ranges); @@ -524,6 +527,45 @@ TEST_F(SymbolCollectorTest, templateArgs) { forCodeCompletion(false); } +TEST_F(SymbolCollectorTest, ObjCRefs) { + Annotations Header(R"( + @interface Person + - (void)$talk[[talk]]; + - (void)$say[[say]]:(id)something; + @end + @interface Person (Category) + - (void)categoryMethod; + - (void)multiArg:(id)a method:(id)b; + @end + )"); + Annotations Main(R"( + @implementation Person + - (void)$talk[[talk]] {} + - (void)$say[[say]]:(id)something {} + @end + + void fff(Person *p) { +[p $talk[[talk]]]; +[p $say[[say]]:0]; +[p categoryMethod]; +[p multiArg:0 method:0]; + } + )"); + CollectorOpts.RefFilter = RefKind::All; + CollectorOpts.CollectMainFileRefs = true; + TestFileName = testPath("test.m"); + runSymbolCollector(Header.code(), Main.code(), + {"-fblocks", "-xobjective-c++", "-Wno-objc-root-class"}); + EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "Person::talk").ID, + haveRanges(Main.ranges("talk"); + EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "Person::say:").ID, + haveRanges(Main.ranges("say"); + EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "Person::categoryMethod").ID, + ElementsAre(isSpelled(); + EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "Person::multiArg:method:").ID, + ElementsAre(Not(isSpelled()); +} + TEST_F(SymbolCollectorTest, ObjCSymbols) { const std::string Header = R"( @interface Person >From 1b6a09464ff5c7b1988fcb479d0a4ff876f696e6 Mon Sep 17 00:00:00 2001 From: David Goldman Date: Tue, 26 Dec 2023 16:12:03 -0500 Subject: [PATCH 2/5] Run clang-format --- .../clangd/unittests/SymbolCollectorTests.cpp | 10 ++ 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp b/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp index 1d4e1c1d75ea23..5c20b950e4eac0 100644 --- a/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp +++ b/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp @@ -560,10 +560,12 @@ TEST_F(SymbolCollectorTest, ObjCRefs) { haveRanges(Main.ranges("talk"); EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "Person::say:").ID, haveRanges(Main.ranges("say"); - EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols,
[clang-tools-extra] Add support for renaming objc methods, even those with multiple selector pieces (PR #76466)
https://github.com/DavidGoldman created https://github.com/llvm/llvm-project/pull/76466 This is based on top of #76410 >From 4caf5b3c779bf18236b4b0be5bc7147d10339f2b Mon Sep 17 00:00:00 2001 From: David Goldman Date: Tue, 26 Dec 2023 15:59:01 -0500 Subject: [PATCH 1/4] [clangd][SymbolCollector] Treat ObjC methods as spelled We'll treat multi-arg methods as spelled once we have full rename support for them. --- .../clangd/index/SymbolCollector.cpp | 6 ++- .../clangd/unittests/SymbolCollectorTests.cpp | 42 +++ 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/clang-tools-extra/clangd/index/SymbolCollector.cpp b/clang-tools-extra/clangd/index/SymbolCollector.cpp index 7ef4b15febad22..336bc3506bb360 100644 --- a/clang-tools-extra/clangd/index/SymbolCollector.cpp +++ b/clang-tools-extra/clangd/index/SymbolCollector.cpp @@ -174,7 +174,9 @@ bool isSpelled(SourceLocation Loc, const NamedDecl ) { auto Name = ND.getDeclName(); const auto NameKind = Name.getNameKind(); if (NameKind != DeclarationName::Identifier && - NameKind != DeclarationName::CXXConstructorName) + NameKind != DeclarationName::CXXConstructorName && + NameKind != DeclarationName::ObjCZeroArgSelector && + NameKind != DeclarationName::ObjCOneArgSelector) return false; const auto = ND.getASTContext(); const auto = AST.getSourceManager(); @@ -183,6 +185,8 @@ bool isSpelled(SourceLocation Loc, const NamedDecl ) { if (clang::Lexer::getRawToken(Loc, Tok, SM, LO)) return false; auto StrName = Name.getAsString(); + if (const auto *MD = dyn_cast()) +StrName = MD->getSelector().getNameForSlot(0).str(); return clang::Lexer::getSpelling(Tok, SM, LO) == StrName; } } // namespace diff --git a/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp b/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp index 9cdc57ec01f327..1d4e1c1d75ea23 100644 --- a/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp +++ b/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp @@ -105,6 +105,9 @@ MATCHER(refRange, "") { MATCHER_P2(OverriddenBy, Subject, Object, "") { return arg == Relation{Subject.ID, RelationKind::OverriddenBy, Object.ID}; } +MATCHER(isSpelled, "") { + return static_cast(arg.Kind & RefKind::Spelled); +} ::testing::Matcher &> haveRanges(const std::vector Ranges) { return ::testing::UnorderedPointwise(refRange(), Ranges); @@ -524,6 +527,45 @@ TEST_F(SymbolCollectorTest, templateArgs) { forCodeCompletion(false); } +TEST_F(SymbolCollectorTest, ObjCRefs) { + Annotations Header(R"( + @interface Person + - (void)$talk[[talk]]; + - (void)$say[[say]]:(id)something; + @end + @interface Person (Category) + - (void)categoryMethod; + - (void)multiArg:(id)a method:(id)b; + @end + )"); + Annotations Main(R"( + @implementation Person + - (void)$talk[[talk]] {} + - (void)$say[[say]]:(id)something {} + @end + + void fff(Person *p) { +[p $talk[[talk]]]; +[p $say[[say]]:0]; +[p categoryMethod]; +[p multiArg:0 method:0]; + } + )"); + CollectorOpts.RefFilter = RefKind::All; + CollectorOpts.CollectMainFileRefs = true; + TestFileName = testPath("test.m"); + runSymbolCollector(Header.code(), Main.code(), + {"-fblocks", "-xobjective-c++", "-Wno-objc-root-class"}); + EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "Person::talk").ID, + haveRanges(Main.ranges("talk"); + EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "Person::say:").ID, + haveRanges(Main.ranges("say"); + EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "Person::categoryMethod").ID, + ElementsAre(isSpelled(); + EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "Person::multiArg:method:").ID, + ElementsAre(Not(isSpelled()); +} + TEST_F(SymbolCollectorTest, ObjCSymbols) { const std::string Header = R"( @interface Person >From 1b6a09464ff5c7b1988fcb479d0a4ff876f696e6 Mon Sep 17 00:00:00 2001 From: David Goldman Date: Tue, 26 Dec 2023 16:12:03 -0500 Subject: [PATCH 2/4] Run clang-format --- .../clangd/unittests/SymbolCollectorTests.cpp | 10 ++ 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp b/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp index 1d4e1c1d75ea23..5c20b950e4eac0 100644 --- a/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp +++ b/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp @@ -560,10 +560,12 @@ TEST_F(SymbolCollectorTest, ObjCRefs) { haveRanges(Main.ranges("talk"); EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "Person::say:").ID, haveRanges(Main.ranges("say"); - EXPECT_THAT(Refs,
[clang-tools-extra] [clangd][SymbolCollector] Treat ObjC methods as spelled (PR #76410)
https://github.com/DavidGoldman updated https://github.com/llvm/llvm-project/pull/76410 >From 4caf5b3c779bf18236b4b0be5bc7147d10339f2b Mon Sep 17 00:00:00 2001 From: David Goldman Date: Tue, 26 Dec 2023 15:59:01 -0500 Subject: [PATCH 1/2] [clangd][SymbolCollector] Treat ObjC methods as spelled We'll treat multi-arg methods as spelled once we have full rename support for them. --- .../clangd/index/SymbolCollector.cpp | 6 ++- .../clangd/unittests/SymbolCollectorTests.cpp | 42 +++ 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/clang-tools-extra/clangd/index/SymbolCollector.cpp b/clang-tools-extra/clangd/index/SymbolCollector.cpp index 7ef4b15febad22..336bc3506bb360 100644 --- a/clang-tools-extra/clangd/index/SymbolCollector.cpp +++ b/clang-tools-extra/clangd/index/SymbolCollector.cpp @@ -174,7 +174,9 @@ bool isSpelled(SourceLocation Loc, const NamedDecl ) { auto Name = ND.getDeclName(); const auto NameKind = Name.getNameKind(); if (NameKind != DeclarationName::Identifier && - NameKind != DeclarationName::CXXConstructorName) + NameKind != DeclarationName::CXXConstructorName && + NameKind != DeclarationName::ObjCZeroArgSelector && + NameKind != DeclarationName::ObjCOneArgSelector) return false; const auto = ND.getASTContext(); const auto = AST.getSourceManager(); @@ -183,6 +185,8 @@ bool isSpelled(SourceLocation Loc, const NamedDecl ) { if (clang::Lexer::getRawToken(Loc, Tok, SM, LO)) return false; auto StrName = Name.getAsString(); + if (const auto *MD = dyn_cast()) +StrName = MD->getSelector().getNameForSlot(0).str(); return clang::Lexer::getSpelling(Tok, SM, LO) == StrName; } } // namespace diff --git a/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp b/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp index 9cdc57ec01f327..1d4e1c1d75ea23 100644 --- a/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp +++ b/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp @@ -105,6 +105,9 @@ MATCHER(refRange, "") { MATCHER_P2(OverriddenBy, Subject, Object, "") { return arg == Relation{Subject.ID, RelationKind::OverriddenBy, Object.ID}; } +MATCHER(isSpelled, "") { + return static_cast(arg.Kind & RefKind::Spelled); +} ::testing::Matcher &> haveRanges(const std::vector Ranges) { return ::testing::UnorderedPointwise(refRange(), Ranges); @@ -524,6 +527,45 @@ TEST_F(SymbolCollectorTest, templateArgs) { forCodeCompletion(false); } +TEST_F(SymbolCollectorTest, ObjCRefs) { + Annotations Header(R"( + @interface Person + - (void)$talk[[talk]]; + - (void)$say[[say]]:(id)something; + @end + @interface Person (Category) + - (void)categoryMethod; + - (void)multiArg:(id)a method:(id)b; + @end + )"); + Annotations Main(R"( + @implementation Person + - (void)$talk[[talk]] {} + - (void)$say[[say]]:(id)something {} + @end + + void fff(Person *p) { +[p $talk[[talk]]]; +[p $say[[say]]:0]; +[p categoryMethod]; +[p multiArg:0 method:0]; + } + )"); + CollectorOpts.RefFilter = RefKind::All; + CollectorOpts.CollectMainFileRefs = true; + TestFileName = testPath("test.m"); + runSymbolCollector(Header.code(), Main.code(), + {"-fblocks", "-xobjective-c++", "-Wno-objc-root-class"}); + EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "Person::talk").ID, + haveRanges(Main.ranges("talk"); + EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "Person::say:").ID, + haveRanges(Main.ranges("say"); + EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "Person::categoryMethod").ID, + ElementsAre(isSpelled(); + EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "Person::multiArg:method:").ID, + ElementsAre(Not(isSpelled()); +} + TEST_F(SymbolCollectorTest, ObjCSymbols) { const std::string Header = R"( @interface Person >From 1b6a09464ff5c7b1988fcb479d0a4ff876f696e6 Mon Sep 17 00:00:00 2001 From: David Goldman Date: Tue, 26 Dec 2023 16:12:03 -0500 Subject: [PATCH 2/2] Run clang-format --- .../clangd/unittests/SymbolCollectorTests.cpp | 10 ++ 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp b/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp index 1d4e1c1d75ea23..5c20b950e4eac0 100644 --- a/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp +++ b/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp @@ -560,10 +560,12 @@ TEST_F(SymbolCollectorTest, ObjCRefs) { haveRanges(Main.ranges("talk"); EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "Person::say:").ID, haveRanges(Main.ranges("say"); - EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols,
[clang-tools-extra] [clangd][SymbolCollector] Treat ObjC methods as spelled (PR #76410)
https://github.com/DavidGoldman created https://github.com/llvm/llvm-project/pull/76410 We'll treat multi-arg methods as spelled once we have full rename support for them. >From 4caf5b3c779bf18236b4b0be5bc7147d10339f2b Mon Sep 17 00:00:00 2001 From: David Goldman Date: Tue, 26 Dec 2023 15:59:01 -0500 Subject: [PATCH] [clangd][SymbolCollector] Treat ObjC methods as spelled We'll treat multi-arg methods as spelled once we have full rename support for them. --- .../clangd/index/SymbolCollector.cpp | 6 ++- .../clangd/unittests/SymbolCollectorTests.cpp | 42 +++ 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/clang-tools-extra/clangd/index/SymbolCollector.cpp b/clang-tools-extra/clangd/index/SymbolCollector.cpp index 7ef4b15febad22..336bc3506bb360 100644 --- a/clang-tools-extra/clangd/index/SymbolCollector.cpp +++ b/clang-tools-extra/clangd/index/SymbolCollector.cpp @@ -174,7 +174,9 @@ bool isSpelled(SourceLocation Loc, const NamedDecl ) { auto Name = ND.getDeclName(); const auto NameKind = Name.getNameKind(); if (NameKind != DeclarationName::Identifier && - NameKind != DeclarationName::CXXConstructorName) + NameKind != DeclarationName::CXXConstructorName && + NameKind != DeclarationName::ObjCZeroArgSelector && + NameKind != DeclarationName::ObjCOneArgSelector) return false; const auto = ND.getASTContext(); const auto = AST.getSourceManager(); @@ -183,6 +185,8 @@ bool isSpelled(SourceLocation Loc, const NamedDecl ) { if (clang::Lexer::getRawToken(Loc, Tok, SM, LO)) return false; auto StrName = Name.getAsString(); + if (const auto *MD = dyn_cast()) +StrName = MD->getSelector().getNameForSlot(0).str(); return clang::Lexer::getSpelling(Tok, SM, LO) == StrName; } } // namespace diff --git a/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp b/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp index 9cdc57ec01f327..1d4e1c1d75ea23 100644 --- a/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp +++ b/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp @@ -105,6 +105,9 @@ MATCHER(refRange, "") { MATCHER_P2(OverriddenBy, Subject, Object, "") { return arg == Relation{Subject.ID, RelationKind::OverriddenBy, Object.ID}; } +MATCHER(isSpelled, "") { + return static_cast(arg.Kind & RefKind::Spelled); +} ::testing::Matcher &> haveRanges(const std::vector Ranges) { return ::testing::UnorderedPointwise(refRange(), Ranges); @@ -524,6 +527,45 @@ TEST_F(SymbolCollectorTest, templateArgs) { forCodeCompletion(false); } +TEST_F(SymbolCollectorTest, ObjCRefs) { + Annotations Header(R"( + @interface Person + - (void)$talk[[talk]]; + - (void)$say[[say]]:(id)something; + @end + @interface Person (Category) + - (void)categoryMethod; + - (void)multiArg:(id)a method:(id)b; + @end + )"); + Annotations Main(R"( + @implementation Person + - (void)$talk[[talk]] {} + - (void)$say[[say]]:(id)something {} + @end + + void fff(Person *p) { +[p $talk[[talk]]]; +[p $say[[say]]:0]; +[p categoryMethod]; +[p multiArg:0 method:0]; + } + )"); + CollectorOpts.RefFilter = RefKind::All; + CollectorOpts.CollectMainFileRefs = true; + TestFileName = testPath("test.m"); + runSymbolCollector(Header.code(), Main.code(), + {"-fblocks", "-xobjective-c++", "-Wno-objc-root-class"}); + EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "Person::talk").ID, + haveRanges(Main.ranges("talk"); + EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "Person::say:").ID, + haveRanges(Main.ranges("say"); + EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "Person::categoryMethod").ID, + ElementsAre(isSpelled(); + EXPECT_THAT(Refs, Contains(Pair(findSymbol(Symbols, "Person::multiArg:method:").ID, + ElementsAre(Not(isSpelled()); +} + TEST_F(SymbolCollectorTest, ObjCSymbols) { const std::string Header = R"( @interface Person ___ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang] 9fe632b - [clang][HeaderSearch] Treat framework headers as Angled for suggestPath
Author: David Goldman Date: 2023-08-09T15:51:02-04:00 New Revision: 9fe632ba18f1398aa9e6fd531a2ed98fd79eba40 URL: https://github.com/llvm/llvm-project/commit/9fe632ba18f1398aa9e6fd531a2ed98fd79eba40 DIFF: https://github.com/llvm/llvm-project/commit/9fe632ba18f1398aa9e6fd531a2ed98fd79eba40.diff LOG: [clang][HeaderSearch] Treat framework headers as Angled for suggestPath - Rename the IsSystem flag to be IsAngled since that's how callers actually use the flag. - Since frameworks by convention use <> style includes, make sure we treat them as Angled Also update clangd's custom logic for frameworks accordingly. Differential Revision: https://reviews.llvm.org/D156704 Added: Modified: clang-tools-extra/clang-include-fixer/IncludeFixer.cpp clang-tools-extra/clangd/Headers.cpp clang-tools-extra/clangd/index/SymbolCollector.cpp clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp clang-tools-extra/include-cleaner/lib/HTMLReport.cpp clang-tools-extra/include-cleaner/lib/IncludeSpeller.cpp clang/include/clang/Lex/HeaderSearch.h clang/lib/Lex/HeaderSearch.cpp clang/lib/Sema/SemaLookup.cpp clang/unittests/Lex/HeaderSearchTest.cpp Removed: diff --git a/clang-tools-extra/clang-include-fixer/IncludeFixer.cpp b/clang-tools-extra/clang-include-fixer/IncludeFixer.cpp index 45fc15619ecab0..1bb5cf756b95b4 100644 --- a/clang-tools-extra/clang-include-fixer/IncludeFixer.cpp +++ b/clang-tools-extra/clang-include-fixer/IncludeFixer.cpp @@ -314,11 +314,11 @@ std::string IncludeFixerSemaSource::minimizeInclude( if (!Entry) return std::string(Include); - bool IsSystem = false; + bool IsAngled = false; std::string Suggestion = - HeaderSearch.suggestPathToFileForDiagnostics(*Entry, "", ); + HeaderSearch.suggestPathToFileForDiagnostics(*Entry, "", ); - return IsSystem ? '<' + Suggestion + '>' : '"' + Suggestion + '"'; + return IsAngled ? '<' + Suggestion + '>' : '"' + Suggestion + '"'; } /// Get the include fixer context for the queried symbol. diff --git a/clang-tools-extra/clangd/Headers.cpp b/clang-tools-extra/clangd/Headers.cpp index 83fba21b1d931a..6005069be01160 100644 --- a/clang-tools-extra/clangd/Headers.cpp +++ b/clang-tools-extra/clangd/Headers.cpp @@ -286,11 +286,11 @@ IncludeInserter::calculateIncludePath(const HeaderFile , assert(InsertedHeader.valid()); if (InsertedHeader.Verbatim) return InsertedHeader.File; - bool IsSystem = false; + bool IsAngled = false; std::string Suggested; if (HeaderSearchInfo) { Suggested = HeaderSearchInfo->suggestPathToFileForDiagnostics( -InsertedHeader.File, BuildDir, IncludingFile, ); +InsertedHeader.File, BuildDir, IncludingFile, ); } else { // Calculate include relative to including file only. StringRef IncludingDir = llvm::sys::path::parent_path(IncludingFile); @@ -303,7 +303,7 @@ IncludeInserter::calculateIncludePath(const HeaderFile , // FIXME: should we allow (some limited number of) "../header.h"? if (llvm::sys::path::is_absolute(Suggested)) return std::nullopt; - if (IsSystem) + if (IsAngled) Suggested = "<" + Suggested + ">"; else Suggested = "\"" + Suggested + "\""; diff --git a/clang-tools-extra/clangd/index/SymbolCollector.cpp b/clang-tools-extra/clangd/index/SymbolCollector.cpp index c9a211b9c4fc2c..e843413601f5a2 100644 --- a/clang-tools-extra/clangd/index/SymbolCollector.cpp +++ b/clang-tools-extra/clangd/index/SymbolCollector.cpp @@ -328,42 +328,33 @@ class SymbolCollector::HeaderFileURICache { // instead of // which should be used instead of directly // importing the header. - std::optional getFrameworkUmbrellaSpelling( - llvm::StringRef Framework, SrcMgr::CharacteristicKind HeadersDirKind, - const HeaderSearch , FrameworkHeaderPath ) { + std::optional + getFrameworkUmbrellaSpelling(llvm::StringRef Framework, + const HeaderSearch , + FrameworkHeaderPath ) { auto Res = CacheFrameworkToUmbrellaHeaderSpelling.try_emplace(Framework); auto *CachedSpelling = >second; if (!Res.second) { return HeaderPath.IsPrivateHeader ? CachedSpelling->PrivateHeader : CachedSpelling->PublicHeader; } -bool IsSystem = isSystem(HeadersDirKind); SmallString<256> UmbrellaPath(HeaderPath.HeadersParentDir); llvm::sys::path::append(UmbrellaPath, "Headers", Framework + ".h"); llvm::vfs::Status Status; auto StatErr = HS.getFileMgr().getNoncachedStatValue(UmbrellaPath, Status); -if (!StatErr) { - if (IsSystem) -CachedSpelling->PublicHeader = llvm::formatv("<{0}/{0}.h>", Framework); - else -CachedSpelling->PublicHeader = -llvm::formatv("\"{0}/{0}.h\"", Framework); -} +if (!StatErr) +
[clang-tools-extra] a42ce09 - [clang][Sema] Add CodeCompletionContext::CCC_ObjCClassForwardDecl
Author: David Goldman Date: 2023-06-27T16:25:40-04:00 New Revision: a42ce094d90341f88a845740b2e5783060f23e3e URL: https://github.com/llvm/llvm-project/commit/a42ce094d90341f88a845740b2e5783060f23e3e DIFF: https://github.com/llvm/llvm-project/commit/a42ce094d90341f88a845740b2e5783060f23e3e.diff LOG: [clang][Sema] Add CodeCompletionContext::CCC_ObjCClassForwardDecl - Use this new context in Sema to limit completions to seen ObjC class names - Use this new context in clangd to disable include insertions when completing ObjC forward decls Reviewed By: kadircet Differential Revision: https://reviews.llvm.org/D150978 Added: Modified: clang-tools-extra/clangd/CodeComplete.cpp clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp clang/include/clang/Sema/CodeCompleteConsumer.h clang/include/clang/Sema/Sema.h clang/lib/Frontend/ASTUnit.cpp clang/lib/Parse/ParseObjc.cpp clang/lib/Sema/CodeCompleteConsumer.cpp clang/lib/Sema/SemaCodeComplete.cpp clang/tools/libclang/CIndexCodeCompletion.cpp Removed: diff --git a/clang-tools-extra/clangd/CodeComplete.cpp b/clang-tools-extra/clangd/CodeComplete.cpp index f4f93a01ec90e..70f2634aa7763 100644 --- a/clang-tools-extra/clangd/CodeComplete.cpp +++ b/clang-tools-extra/clangd/CodeComplete.cpp @@ -214,7 +214,8 @@ struct CompletionCandidate { // Returns a token identifying the overload set this is part of. // 0 indicates it's not part of any overload set. size_t overloadSet(const CodeCompleteOptions , llvm::StringRef FileName, - IncludeInserter *Inserter) const { + IncludeInserter *Inserter, + CodeCompletionContext::Kind CCContextKind) const { if (!Opts.BundleOverloads.value_or(false)) return 0; @@ -223,7 +224,7 @@ struct CompletionCandidate { // bundle those, so we must resolve the header to be included here. std::string HeaderForHash; if (Inserter) { - if (auto Header = headerToInsertIfAllowed(Opts)) { + if (auto Header = headerToInsertIfAllowed(Opts, CCContextKind)) { if (auto HeaderFile = toHeaderFile(*Header, FileName)) { if (auto Spelled = Inserter->calculateIncludePath(*HeaderFile, FileName)) @@ -271,11 +272,21 @@ struct CompletionCandidate { return 0; } + bool contextAllowsHeaderInsertion(CodeCompletionContext::Kind Kind) const { +// Explicitly disable insertions for forward declarations since they don't +// reference the declaration. +if (Kind == CodeCompletionContext::CCC_ObjCClassForwardDecl) + return false; +return true; + } + // The best header to include if include insertion is allowed. std::optional - headerToInsertIfAllowed(const CodeCompleteOptions ) const { + headerToInsertIfAllowed(const CodeCompleteOptions , + CodeCompletionContext::Kind ContextKind) const { if (Opts.InsertIncludes == CodeCompleteOptions::NeverInsert || -RankedIncludeHeaders.empty()) +RankedIncludeHeaders.empty() || +!contextAllowsHeaderInsertion(ContextKind)) return std::nullopt; if (SemaResult && SemaResult->Declaration) { // Avoid inserting new #include if the declaration is found in the current @@ -401,7 +412,8 @@ struct CodeCompletionBuilder { std::move(*Spelled), Includes.shouldInsertInclude(*ResolvedDeclaring, *ResolvedInserted)); }; -bool ShouldInsert = C.headerToInsertIfAllowed(Opts).has_value(); +bool ShouldInsert = +C.headerToInsertIfAllowed(Opts, ContextKind).has_value(); Symbol::IncludeDirective Directive = insertionDirective(Opts); // Calculate include paths and edits for all possible headers. for (const auto : C.RankedIncludeHeaders) { @@ -780,6 +792,7 @@ bool contextAllowsIndex(enum CodeCompletionContext::Kind K) { case CodeCompletionContext::CCC_ObjCInterfaceName: case CodeCompletionContext::CCC_Symbol: case CodeCompletionContext::CCC_SymbolOrNewName: + case CodeCompletionContext::CCC_ObjCClassForwardDecl: return true; case CodeCompletionContext::CCC_OtherWithMacros: case CodeCompletionContext::CCC_DotMemberAccess: @@ -1422,6 +1435,10 @@ bool includeSymbolFromIndex(CodeCompletionContext::Kind Kind, else if (Kind == CodeCompletionContext::CCC_ObjCProtocolName) // Don't show anything else in ObjC protocol completions. return false; + + if (Kind == CodeCompletionContext::CCC_ObjCClassForwardDecl) +return Sym.SymInfo.Kind == index::SymbolKind::Class && + Sym.SymInfo.Lang == index::SymbolLanguage::ObjC; return true; } @@ -1832,8 +1849,8 @@ class CodeCompleteFlow { assert(IdentifierResult); C.Name = IdentifierResult->Name; } - if (auto OverloadSet = - C.overloadSet(Opts, FileName, Inserter ? &*Inserter : nullptr)) { +
[clang-tools-extra] 1b66840 - [clangd][ObjC] Support ObjC class rename from implementation decls
Author: David Goldman Date: 2023-06-26T14:43:37-04:00 New Revision: 1b66840f71030f5d5934e99398a59c3485ba111e URL: https://github.com/llvm/llvm-project/commit/1b66840f71030f5d5934e99398a59c3485ba111e DIFF: https://github.com/llvm/llvm-project/commit/1b66840f71030f5d5934e99398a59c3485ba111e.diff LOG: [clangd][ObjC] Support ObjC class rename from implementation decls Reviewed By: kadircet Differential Revision: https://reviews.llvm.org/D152720 Added: Modified: clang-tools-extra/clangd/FindTarget.cpp clang-tools-extra/clangd/SemanticHighlighting.cpp clang-tools-extra/clangd/refactor/Rename.cpp clang-tools-extra/clangd/unittests/FindTargetTests.cpp clang-tools-extra/clangd/unittests/RenameTests.cpp Removed: diff --git a/clang-tools-extra/clangd/FindTarget.cpp b/clang-tools-extra/clangd/FindTarget.cpp index eead9e6a3a7a4..630b75059b6ba 100644 --- a/clang-tools-extra/clangd/FindTarget.cpp +++ b/clang-tools-extra/clangd/FindTarget.cpp @@ -708,8 +708,23 @@ llvm::SmallVector refInDecl(const Decl *D, {OCID->getClassInterface()}}); Refs.push_back(ReferenceLoc{NestedNameSpecifierLoc(), OCID->getCategoryNameLoc(), - /*IsDecl=*/true, + /*IsDecl=*/false, {OCID->getCategoryDecl()}}); + Refs.push_back(ReferenceLoc{NestedNameSpecifierLoc(), + OCID->getCategoryNameLoc(), + /*IsDecl=*/true, + {OCID}}); +} + +void VisitObjCImplementationDecl(const ObjCImplementationDecl *OIMD) { + Refs.push_back(ReferenceLoc{NestedNameSpecifierLoc(), + OIMD->getLocation(), + /*IsDecl=*/false, + {OIMD->getClassInterface()}}); + Refs.push_back(ReferenceLoc{NestedNameSpecifierLoc(), + OIMD->getLocation(), + /*IsDecl=*/true, + {OIMD}}); } }; diff --git a/clang-tools-extra/clangd/SemanticHighlighting.cpp b/clang-tools-extra/clangd/SemanticHighlighting.cpp index 3826170892e8c..f6a3f7ac66aa0 100644 --- a/clang-tools-extra/clangd/SemanticHighlighting.cpp +++ b/clang-tools-extra/clangd/SemanticHighlighting.cpp @@ -128,7 +128,7 @@ std::optional kindForDecl(const NamedDecl *D, return HighlightingKind::Class; if (isa(D)) return HighlightingKind::Interface; - if (isa(D)) + if (isa(D)) return HighlightingKind::Namespace; if (auto *MD = dyn_cast(D)) return MD->isStatic() ? HighlightingKind::StaticMethod diff --git a/clang-tools-extra/clangd/refactor/Rename.cpp b/clang-tools-extra/clangd/refactor/Rename.cpp index b3270534b13b1..97ea5e1836579 100644 --- a/clang-tools-extra/clangd/refactor/Rename.cpp +++ b/clang-tools-extra/clangd/refactor/Rename.cpp @@ -19,6 +19,7 @@ #include "clang/AST/ASTTypeTraits.h" #include "clang/AST/Decl.h" #include "clang/AST/DeclCXX.h" +#include "clang/AST/DeclObjC.h" #include "clang/AST/DeclTemplate.h" #include "clang/AST/ParentMapContext.h" #include "clang/AST/Stmt.h" @@ -140,6 +141,18 @@ const NamedDecl *canonicalRenameDecl(const NamedDecl *D) { return dyn_cast(D->getCanonicalDecl()); } +// Some AST nodes can reference multiple declarations. We try to pick the +// relevant one to rename here. +const NamedDecl *pickInterestingTarget(const NamedDecl *D) { + // We only support renaming the class name, not the category name. This has + // to be done outside of canonicalization since we don't want a category name + // reference to be canonicalized to the class. + if (const auto *CD = dyn_cast(D)) +if (const auto CI = CD->getClassInterface()) + return CI; + return D; +} + llvm::DenseSet locateDeclAt(ParsedAST , SourceLocation TokenStartLoc) { unsigned Offset = @@ -156,6 +169,7 @@ llvm::DenseSet locateDeclAt(ParsedAST , targetDecl(SelectedNode->ASTNode, DeclRelation::Alias | DeclRelation::TemplatePattern, AST.getHeuristicResolver())) { +D = pickInterestingTarget(D); Result.insert(canonicalRenameDecl(D)); } return Result; diff --git a/clang-tools-extra/clangd/unittests/FindTargetTests.cpp b/clang-tools-extra/clangd/unittests/FindTargetTests.cpp index 9979628941bfe..64ac524fc5187 100644 --- a/clang-tools-extra/clangd/unittests/FindTargetTests.cpp +++ b/clang-tools-extra/clangd/unittests/FindTargetTests.cpp @@ -1127,6 +1127,16 @@ TEST_F(TargetDeclTest, ObjC) { )cpp"; EXPECT_DECLS("ObjCCategoryImplDecl", "@interface Foo(Ext)"); + Code = R"cpp( +@interface Foo +@end +@interface Foo (Ext) +@end +@implementation Foo
[clang-tools-extra] 042dd99 - [clangd] Full support for #import insertions
Author: David Goldman Date: 2023-01-09T09:48:30-05:00 New Revision: 042dd99484d6f393cc8a365def250e9d74c24d37 URL: https://github.com/llvm/llvm-project/commit/042dd99484d6f393cc8a365def250e9d74c24d37 DIFF: https://github.com/llvm/llvm-project/commit/042dd99484d6f393cc8a365def250e9d74c24d37.diff LOG: [clangd] Full support for #import insertions These are still disabled by default, but will work in ObjC code if you enable the `-import-insertions` flag. Completion requires ASTSignals to be available; before ASTSignals are available, we will always use #include. Once they are available, the behavior varies as follows: - For source files, use #import if the ObjC language flag is enabled - For header files: - If the ObjC language flag is disabled, use #include - If the header file contains any #imports, use #import - If the header file references any ObjC decls, use #import - Otherwise, use #include IncludeFixer support is similar, but it does not rely upon ASTSignals, instead it does the above checks excluding the scan for ObjC symbols. Differential Revision: https://reviews.llvm.org/D139458 Added: Modified: clang-tools-extra/clangd/AST.cpp clang-tools-extra/clangd/AST.h clang-tools-extra/clangd/ASTSignals.cpp clang-tools-extra/clangd/ASTSignals.h clang-tools-extra/clangd/ClangdServer.cpp clang-tools-extra/clangd/ClangdServer.h clang-tools-extra/clangd/CodeComplete.cpp clang-tools-extra/clangd/Compiler.h clang-tools-extra/clangd/IncludeFixer.cpp clang-tools-extra/clangd/IncludeFixer.h clang-tools-extra/clangd/ParsedAST.cpp clang-tools-extra/clangd/ParsedAST.h clang-tools-extra/clangd/tool/ClangdMain.cpp clang-tools-extra/clangd/unittests/ASTSignalsTests.cpp clang-tools-extra/clangd/unittests/ASTTests.cpp clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp Removed: diff --git a/clang-tools-extra/clangd/AST.cpp b/clang-tools-extra/clangd/AST.cpp index ab4cf4e7a82f4..17c4dbf6dbe70 100644 --- a/clang-tools-extra/clangd/AST.cpp +++ b/clang-tools-extra/clangd/AST.cpp @@ -376,6 +376,38 @@ const ObjCImplDecl *getCorrespondingObjCImpl(const ObjCContainerDecl *D) { return nullptr; } +Symbol::IncludeDirective +preferredIncludeDirective(llvm::StringRef FileName, const LangOptions , + ArrayRef MainFileIncludes, + ArrayRef TopLevelDecls) { + // Always prefer #include for non-ObjC code. + if (!LangOpts.ObjC) +return Symbol::IncludeDirective::Include; + // If this is not a header file and has ObjC set as the language, prefer + // #import. + if (!isHeaderFile(FileName, LangOpts)) +return Symbol::IncludeDirective::Import; + + // Headers lack proper compile flags most of the time, so we might treat a + // header as ObjC accidentally. Perform some extra checks to make sure this + // works. + + // Any file with a #import, should keep #import-ing. + for (auto : MainFileIncludes) +if (Inc.Directive == tok::pp_import) + return Symbol::IncludeDirective::Import; + + // Any file declaring an ObjC decl should also be #import-ing. + // No need to look over the references, as the file doesn't have any #imports, + // it must be declaring interesting ObjC-like decls. + for (const Decl *D : TopLevelDecls) +if (isa( +D)) + return Symbol::IncludeDirective::Import; + + return Symbol::IncludeDirective::Include; +} + std::string printType(const QualType QT, const DeclContext , const llvm::StringRef Placeholder) { std::string Result; diff --git a/clang-tools-extra/clangd/AST.h b/clang-tools-extra/clangd/AST.h index bdc0862e96347..fb0722d697cd0 100644 --- a/clang-tools-extra/clangd/AST.h +++ b/clang-tools-extra/clangd/AST.h @@ -13,6 +13,8 @@ #ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_AST_H #define LLVM_CLANG_TOOLS_EXTRA_CLANGD_AST_H +#include "Headers.h" +#include "index/Symbol.h" #include "index/SymbolID.h" #include "clang/AST/Decl.h" #include "clang/AST/DeclObjC.h" @@ -118,6 +120,18 @@ SymbolID getSymbolID(const llvm::StringRef MacroName, const MacroInfo *MI, /// return nullptr as protocols don't have an implementation. const ObjCImplDecl *getCorrespondingObjCImpl(const ObjCContainerDecl *D); +/// Infer the include directive to use for the given \p FileName. It aims for +/// #import for ObjC files and #include for the rest. +/// +/// - For source files we use LangOpts directly to infer ObjC-ness. +/// - For header files we also check for symbols declared by the file and +/// existing include directives, as the language can be set to ObjC++ as a +/// fallback in the absence of compile flags. +Symbol::IncludeDirective +preferredIncludeDirective(llvm::StringRef FileName, const LangOptions , + ArrayRef MainFileIncludes, + ArrayRef TopLevelDecls); + /// Returns a
[clang-tools-extra] 814c0bb - [clangd] Add flag to control #import include insertions
Author: David Goldman Date: 2023-01-09T09:48:29-05:00 New Revision: 814c0bb31660b2441c9a9a6eeaafc2e33c416842 URL: https://github.com/llvm/llvm-project/commit/814c0bb31660b2441c9a9a6eeaafc2e33c416842 DIFF: https://github.com/llvm/llvm-project/commit/814c0bb31660b2441c9a9a6eeaafc2e33c416842.diff LOG: [clangd] Add flag to control #import include insertions This will be disabled by default, hopefully we can enable for the next major release. Differential Revision: https://reviews.llvm.org/D139446 Added: Modified: clang-tools-extra/clangd/CodeComplete.h clang-tools-extra/clangd/tool/ClangdMain.cpp Removed: diff --git a/clang-tools-extra/clangd/CodeComplete.h b/clang-tools-extra/clangd/CodeComplete.h index 44482d1047294..a7c1ae95dcbf4 100644 --- a/clang-tools-extra/clangd/CodeComplete.h +++ b/clang-tools-extra/clangd/CodeComplete.h @@ -70,6 +70,10 @@ struct CodeCompleteOptions { NeverInsert, } InsertIncludes = IncludeInsertion::IWYU; + /// Whether include insertions for Objective-C code should use #import instead + /// of #include. + bool ImportInsertions = false; + /// A visual indicator to prepend to the completion label to indicate whether /// completion result would trigger an #include insertion or not. struct IncludeInsertionIndicator { diff --git a/clang-tools-extra/clangd/tool/ClangdMain.cpp b/clang-tools-extra/clangd/tool/ClangdMain.cpp index 4cdfb1a8b4449..06cfdcc1ca233 100644 --- a/clang-tools-extra/clangd/tool/ClangdMain.cpp +++ b/clang-tools-extra/clangd/tool/ClangdMain.cpp @@ -264,6 +264,14 @@ opt HeaderInsertion{ "Never insert #include directives as part of code completion")), }; +opt ImportInsertions{ +"import-insertions", +cat(Features), +desc("If header insertion is enabled, add #import directives when " + "accepting code completions or fixing includes in Objective-C code"), +init(CodeCompleteOptions().ImportInsertions), +}; + opt IncludeCleanerStdlib{ "include-cleaner-stdlib", cat(Features), @@ -913,6 +921,7 @@ clangd accepts flags on the commandline, and in the CLANGD_FLAGS environment var Opts.CodeComplete.BundleOverloads = CompletionStyle != Detailed; Opts.CodeComplete.ShowOrigins = ShowOrigins; Opts.CodeComplete.InsertIncludes = HeaderInsertion; + Opts.CodeComplete.ImportInsertions = ImportInsertions; if (!HeaderInsertionDecorators) { Opts.CodeComplete.IncludeIndicator.Insert.clear(); Opts.CodeComplete.IncludeIndicator.NoInsert.clear(); ___ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang] fc46d6e - [clang][Tooling] Add support for generating #import edits
Author: David Goldman Date: 2022-12-06T13:47:07-05:00 New Revision: fc46d6e67fab06d54c8948ebf959d62984116bc3 URL: https://github.com/llvm/llvm-project/commit/fc46d6e67fab06d54c8948ebf959d62984116bc3 DIFF: https://github.com/llvm/llvm-project/commit/fc46d6e67fab06d54c8948ebf959d62984116bc3.diff LOG: [clang][Tooling] Add support for generating #import edits And make use of this from clangd's CodeComplete and IncludeFixer, although currently they are both restricted only to #include symbols. Differential Revision: https://reviews.llvm.org/D128677 Added: Modified: clang-tools-extra/clangd/CodeComplete.cpp clang-tools-extra/clangd/Headers.cpp clang-tools-extra/clangd/Headers.h clang-tools-extra/clangd/IncludeFixer.cpp clang-tools-extra/clangd/IncludeFixer.h clang-tools-extra/clangd/unittests/HeadersTests.cpp clang/include/clang/Tooling/Inclusions/HeaderIncludes.h clang/lib/Format/Format.cpp clang/lib/Tooling/Inclusions/HeaderIncludes.cpp clang/unittests/Tooling/HeaderIncludesTest.cpp Removed: diff --git a/clang-tools-extra/clangd/CodeComplete.cpp b/clang-tools-extra/clangd/CodeComplete.cpp index 32a80ac054cbe..b445854e57255 100644 --- a/clang-tools-extra/clangd/CodeComplete.cpp +++ b/clang-tools-extra/clangd/CodeComplete.cpp @@ -395,7 +395,8 @@ struct CodeCompletionBuilder { CodeCompletion::IncludeCandidate Include; Include.Header = ToInclude->first; if (ToInclude->second && ShouldInsert) - Include.Insertion = Includes.insert(ToInclude->first); + Include.Insertion = Includes.insert( + ToInclude->first, tooling::IncludeDirective::Include); Completion.Includes.push_back(std::move(Include)); } else log("Failed to generate include insertion edits for adding header " diff --git a/clang-tools-extra/clangd/Headers.cpp b/clang-tools-extra/clangd/Headers.cpp index 95813894d5aa3..4314ef15d3eb3 100644 --- a/clang-tools-extra/clangd/Headers.cpp +++ b/clang-tools-extra/clangd/Headers.cpp @@ -345,10 +345,12 @@ IncludeInserter::calculateIncludePath(const HeaderFile , } llvm::Optional -IncludeInserter::insert(llvm::StringRef VerbatimHeader) const { +IncludeInserter::insert(llvm::StringRef VerbatimHeader, +tooling::IncludeDirective Directive) const { llvm::Optional Edit; - if (auto Insertion = Inserter.insert(VerbatimHeader.trim("\"<>"), - VerbatimHeader.startswith("<"))) + if (auto Insertion = + Inserter.insert(VerbatimHeader.trim("\"<>"), + VerbatimHeader.startswith("<"), Directive)) Edit = replacementToEdit(Code, *Insertion); return Edit; } diff --git a/clang-tools-extra/clangd/Headers.h b/clang-tools-extra/clangd/Headers.h index 0901a23f4dd83..5611b32b11aa1 100644 --- a/clang-tools-extra/clangd/Headers.h +++ b/clang-tools-extra/clangd/Headers.h @@ -50,7 +50,7 @@ struct SymbolInclude { /// The header to include. This is either a URI or a verbatim include which is /// quoted with <> or "". llvm::StringRef Header; - /// The include directive to use, e.g. #import or #include. + /// The include directive(s) that can be used, e.g. #import and/or #include. Symbol::IncludeDirective Directive; }; @@ -248,7 +248,8 @@ class IncludeInserter { /// Calculates an edit that inserts \p VerbatimHeader into code. If the header /// is already included, this returns std::nullopt. - llvm::Optional insert(llvm::StringRef VerbatimHeader) const; + llvm::Optional insert(llvm::StringRef VerbatimHeader, + tooling::IncludeDirective Directive) const; private: StringRef FileName; diff --git a/clang-tools-extra/clangd/IncludeFixer.cpp b/clang-tools-extra/clangd/IncludeFixer.cpp index 5ba5a4e45ba04..45811e10e192a 100644 --- a/clang-tools-extra/clangd/IncludeFixer.cpp +++ b/clang-tools-extra/clangd/IncludeFixer.cpp @@ -249,18 +249,22 @@ std::vector IncludeFixer::fix(DiagnosticsEngine::Level DiagLevel, } llvm::Optional IncludeFixer::insertHeader(llvm::StringRef Spelled, - llvm::StringRef Symbol) const { + llvm::StringRef Symbol, + tooling::IncludeDirective Directive) const { Fix F; - if (auto Edit = Inserter->insert(Spelled)) + if (auto Edit = Inserter->insert(Spelled, Directive)) F.Edits.push_back(std::move(*Edit)); else return std::nullopt; + llvm::StringRef DirectiveSpelling = + Directive == tooling::IncludeDirective::Include ? "Include" : "Import"; if (Symbol.empty()) -F.Message = llvm::formatv("Include {0}", Spelled); +F.Message = llvm::formatv("{0} {1}", DirectiveSpelling, Spelled); else -F.Message = llvm::formatv("Include {0} for symbol {1}",
[clang] 51f1ae5 - [clangd] Add new IncludeDirective to IncludeHeaderWithReferences
Author: David Goldman Date: 2022-12-06T13:47:07-05:00 New Revision: 51f1ae52b0c92a9783e7df328d05b1f95dca74d1 URL: https://github.com/llvm/llvm-project/commit/51f1ae52b0c92a9783e7df328d05b1f95dca74d1 DIFF: https://github.com/llvm/llvm-project/commit/51f1ae52b0c92a9783e7df328d05b1f95dca74d1.diff LOG: [clangd] Add new IncludeDirective to IncludeHeaderWithReferences The IncludeDirective contains both Include (the current behavior) and Import, which we can use in the future to provide #import suggestions for Objective-C files/symbols. Differential Revision: https://reviews.llvm.org/D128457 Added: Modified: clang-tools-extra/clangd/CodeComplete.cpp clang-tools-extra/clangd/Headers.cpp clang-tools-extra/clangd/Headers.h clang-tools-extra/clangd/IncludeFixer.cpp clang-tools-extra/clangd/index/Merge.cpp clang-tools-extra/clangd/index/Serialization.cpp clang-tools-extra/clangd/index/Symbol.h clang-tools-extra/clangd/index/SymbolCollector.cpp clang-tools-extra/clangd/index/SymbolCollector.h clang-tools-extra/clangd/index/YAMLSerialization.cpp clang-tools-extra/clangd/index/remote/Index.proto clang-tools-extra/clangd/index/remote/marshalling/Marshalling.cpp clang-tools-extra/clangd/test/index-serialization/Inputs/sample.idx clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp clang-tools-extra/clangd/unittests/DiagnosticsTests.cpp clang-tools-extra/clangd/unittests/IndexTests.cpp clang-tools-extra/clangd/unittests/SerializationTests.cpp clang/include/clang/Tooling/Inclusions/HeaderAnalysis.h clang/lib/Tooling/Inclusions/HeaderAnalysis.cpp clang/unittests/Tooling/HeaderAnalysisTest.cpp Removed: diff --git a/clang-tools-extra/clangd/CodeComplete.cpp b/clang-tools-extra/clangd/CodeComplete.cpp index 33088e08387fb..32a80ac054cbe 100644 --- a/clang-tools-extra/clangd/CodeComplete.cpp +++ b/clang-tools-extra/clangd/CodeComplete.cpp @@ -191,7 +191,7 @@ struct CompletionCandidate { const CodeCompletionResult *SemaResult = nullptr; const Symbol *IndexResult = nullptr; const RawIdentifier *IdentifierResult = nullptr; - llvm::SmallVector RankedIncludeHeaders; + llvm::SmallVector RankedIncludeHeaders; // Returns a token identifying the overload set this is part of. // 0 indicates it's not part of any overload set. @@ -267,7 +267,11 @@ struct CompletionCandidate { if (SM.isInMainFile(SM.getExpansionLoc(RD->getBeginLoc( return std::nullopt; } -return RankedIncludeHeaders[0]; +for (const auto : RankedIncludeHeaders) + // FIXME: We should support #import directives here. + if ((Inc.Directive & clang::clangd::Symbol::Include) != 0) +return Inc.Header; +return None; } using Bundle = llvm::SmallVector; @@ -383,7 +387,11 @@ struct CodeCompletionBuilder { bool ShouldInsert = C.headerToInsertIfAllowed(Opts).has_value(); // Calculate include paths and edits for all possible headers. for (const auto : C.RankedIncludeHeaders) { - if (auto ToInclude = Inserted(Inc)) { + // FIXME: We should support #import directives here. + if ((Inc.Directive & clang::clangd::Symbol::Include) == 0) +continue; + + if (auto ToInclude = Inserted(Inc.Header)) { CodeCompletion::IncludeCandidate Include; Include.Header = ToInclude->first; if (ToInclude->second && ShouldInsert) @@ -392,7 +400,7 @@ struct CodeCompletionBuilder { } else log("Failed to generate include insertion edits for adding header " "(FileURI='{0}', IncludeHeader='{1}') into {2}: {3}", -C.IndexResult->CanonicalDeclaration.FileURI, Inc, FileName, +C.IndexResult->CanonicalDeclaration.FileURI, Inc.Header, FileName, ToInclude.takeError()); } // Prefer includes that do not need edits (i.e. already exist). diff --git a/clang-tools-extra/clangd/Headers.cpp b/clang-tools-extra/clangd/Headers.cpp index a531197290702..95813894d5aa3 100644 --- a/clang-tools-extra/clangd/Headers.cpp +++ b/clang-tools-extra/clangd/Headers.cpp @@ -211,7 +211,7 @@ llvm::Expected toHeaderFile(llvm::StringRef Header, return HeaderFile{std::move(*Resolved), /*Verbatim=*/false}; } -llvm::SmallVector getRankedIncludes(const Symbol ) { +llvm::SmallVector getRankedIncludes(const Symbol ) { auto Includes = Sym.IncludeHeaders; // Sort in descending order by reference count and header length. llvm::sort(Includes, [](const Symbol::IncludeHeaderWithReferences , @@ -220,9 +220,9 @@ llvm::SmallVector getRankedIncludes(const Symbol ) { return LHS.IncludeHeader.size() < RHS.IncludeHeader.size(); return LHS.References > RHS.References; }); - llvm::SmallVector Headers; + llvm::SmallVector Headers; for (const auto : Includes) -Headers.push_back(Include.IncludeHeader); +
[clang-tools-extra] 2d5c4b8 - Fix use of dangling stack allocated string in IncludeFixer
Author: David Goldman Date: 2022-11-16T11:09:06-08:00 New Revision: 2d5c4b8f6e040eef33d4f49820868a81c4c9f1e9 URL: https://github.com/llvm/llvm-project/commit/2d5c4b8f6e040eef33d4f49820868a81c4c9f1e9 DIFF: https://github.com/llvm/llvm-project/commit/2d5c4b8f6e040eef33d4f49820868a81c4c9f1e9.diff LOG: Fix use of dangling stack allocated string in IncludeFixer IncludeFixer uses this BuildDir string later on if given relative paths. Differential Revision: https://reviews.llvm.org/D138047 Added: Modified: clang-tools-extra/clangd/ParsedAST.cpp Removed: diff --git a/clang-tools-extra/clangd/ParsedAST.cpp b/clang-tools-extra/clangd/ParsedAST.cpp index 50923c862c583..870a22cd7b1ef 100644 --- a/clang-tools-extra/clangd/ParsedAST.cpp +++ b/clang-tools-extra/clangd/ParsedAST.cpp @@ -465,6 +465,8 @@ ParsedAST::build(llvm::StringRef Filename, const ParseInputs , std::vector> CTChecks; ast_matchers::MatchFinder CTFinder; llvm::Optional CTContext; + // Must outlive FixIncludes. + auto BuildDir = VFS->getCurrentWorkingDirectory(); llvm::Optional FixIncludes; llvm::DenseMap OverriddenSeverity; // No need to run clang-tidy or IncludeFixerif we are not going to surface @@ -551,7 +553,6 @@ ParsedAST::build(llvm::StringRef Filename, const ParseInputs , // Add IncludeFixer which can recover diagnostics caused by missing includes // (e.g. incomplete type) and attach include insertion fixes to diagnostics. -auto BuildDir = VFS->getCurrentWorkingDirectory(); if (Inputs.Index && !BuildDir.getError()) { auto Style = getFormatStyleForFile(Filename, Inputs.Contents, *Inputs.TFS); ___ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang-tools-extra] e09c750 - [clangd][ObjC] Improve completions for protocols + category names
Author: David Goldman Date: 2022-09-08T11:30:26-04:00 New Revision: e09c75049854fee251e99c4c3c55f3f391f52a10 URL: https://github.com/llvm/llvm-project/commit/e09c75049854fee251e99c4c3c55f3f391f52a10 DIFF: https://github.com/llvm/llvm-project/commit/e09c75049854fee251e99c4c3c55f3f391f52a10.diff LOG: [clangd][ObjC] Improve completions for protocols + category names - Render protocols as interfaces to differentiate them from classes since a protocol and class can have the same name. Take this one step further though, and only recommend protocols in ObjC protocol completions. - Properly call `includeSymbolFromIndex` even with a cached speculative fuzzy find request - Don't use the index to provide completions for category names, symbols there don't make sense Differential Revision: https://reviews.llvm.org/D132962 Added: Modified: clang-tools-extra/clangd/CodeComplete.cpp clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp clang-tools-extra/clangd/unittests/TestIndex.cpp clang-tools-extra/clangd/unittests/TestIndex.h Removed: diff --git a/clang-tools-extra/clangd/CodeComplete.cpp b/clang-tools-extra/clangd/CodeComplete.cpp index f10235894190..9d9b0e748153 100644 --- a/clang-tools-extra/clangd/CodeComplete.cpp +++ b/clang-tools-extra/clangd/CodeComplete.cpp @@ -94,10 +94,14 @@ CompletionItemKind toCompletionItemKind(index::SymbolKind Kind) { case SK::Struct: return CompletionItemKind::Struct; case SK::Class: - case SK::Protocol: case SK::Extension: case SK::Union: return CompletionItemKind::Class; + case SK::Protocol: +// Use interface instead of class for diff erentiation of classes and +// protocols with the same name (e.g. @interface NSObject vs. @protocol +// NSObject). +return CompletionItemKind::Interface; case SK::TypeAlias: // We use the same kind as the VSCode C++ extension. // FIXME: pick a better option when we have one. @@ -712,13 +716,13 @@ bool contextAllowsIndex(enum CodeCompletionContext::Kind K) { case CodeCompletionContext::CCC_Type: case CodeCompletionContext::CCC_ParenthesizedExpression: case CodeCompletionContext::CCC_ObjCInterfaceName: - case CodeCompletionContext::CCC_ObjCCategoryName: case CodeCompletionContext::CCC_Symbol: case CodeCompletionContext::CCC_SymbolOrNewName: return true; case CodeCompletionContext::CCC_OtherWithMacros: case CodeCompletionContext::CCC_DotMemberAccess: case CodeCompletionContext::CCC_ArrowMemberAccess: + case CodeCompletionContext::CCC_ObjCCategoryName: case CodeCompletionContext::CCC_ObjCPropertyAccess: case CodeCompletionContext::CCC_MacroName: case CodeCompletionContext::CCC_MacroNameUse: @@ -1343,6 +1347,22 @@ bool allowIndex(CodeCompletionContext ) { llvm_unreachable("invalid NestedNameSpecifier kind"); } +// Should we include a symbol from the index given the completion kind? +// FIXME: Ideally we can filter in the fuzzy find request itself. +bool includeSymbolFromIndex(CodeCompletionContext::Kind Kind, +const Symbol ) { + // Objective-C protocols are only useful in ObjC protocol completions, + // in other places they're confusing, especially when they share the same + // identifier with a class. + if (Sym.SymInfo.Kind == index::SymbolKind::Protocol && + Sym.SymInfo.Lang == index::SymbolLanguage::ObjC) +return Kind == CodeCompletionContext::CCC_ObjCProtocolName; + else if (Kind == CodeCompletionContext::CCC_ObjCProtocolName) +// Don't show anything else in ObjC protocol completions. +return false; + return true; +} + std::future startAsyncFuzzyFind(const SymbolIndex , const FuzzyFindRequest ) { return runAsync([, Req]() { @@ -1675,14 +1695,6 @@ class CodeCompleteFlow { return Output; } - bool includeSymbolFromIndex(const Symbol ) { -if (CCContextKind == CodeCompletionContext::CCC_ObjCProtocolName) { - return Sym.SymInfo.Lang == index::SymbolLanguage::ObjC && - Sym.SymInfo.Kind == index::SymbolKind::Protocol; -} -return true; - } - SymbolSlab queryIndex() { trace::Span Tracer("Query index"); SPAN_ATTACH(Tracer, "limit", int64_t(Opts.Limit)); @@ -1716,11 +1728,8 @@ class CodeCompleteFlow { // Run the query against the index. SymbolSlab::Builder ResultsBuilder; -if (Opts.Index->fuzzyFind(Req, [&](const Symbol ) { - if (includeSymbolFromIndex(Sym)) -ResultsBuilder.insert(Sym); -})) - Incomplete = true; +Incomplete |= Opts.Index->fuzzyFind( +Req, [&](const Symbol ) { ResultsBuilder.insert(Sym); }); return std::move(ResultsBuilder).build(); } @@ -1783,6 +1792,8 @@ class CodeCompleteFlow { for (const auto : IndexResults) { if (UsedIndexResults.count()) continue; + if
[clang-tools-extra] 61ef0ab - [clangd] Add decl/def support to SymbolDetails
Author: David Goldman Date: 2022-08-01T14:42:19-04:00 New Revision: 61ef0ab70196bfdc4c301e12384aa91938b15cc4 URL: https://github.com/llvm/llvm-project/commit/61ef0ab70196bfdc4c301e12384aa91938b15cc4 DIFF: https://github.com/llvm/llvm-project/commit/61ef0ab70196bfdc4c301e12384aa91938b15cc4.diff LOG: [clangd] Add decl/def support to SymbolDetails Add an optional declarationRange and definitionRange to SymbolDetails. This will allow SourceKit-LSP to implement toggling between goto definition/declaration based on whether the symbol at the cursor is a definition or declaration. Differential Revision: https://reviews.llvm.org/D130041 Added: Modified: clang-tools-extra/clangd/AST.cpp clang-tools-extra/clangd/Protocol.cpp clang-tools-extra/clangd/Protocol.h clang-tools-extra/clangd/XRefs.cpp clang-tools-extra/clangd/test/symbol-info.test clang-tools-extra/clangd/unittests/SymbolInfoTests.cpp Removed: diff --git a/clang-tools-extra/clangd/AST.cpp b/clang-tools-extra/clangd/AST.cpp index 85c32574f9e67..31db4cf0f3cf5 100644 --- a/clang-tools-extra/clangd/AST.cpp +++ b/clang-tools-extra/clangd/AST.cpp @@ -14,6 +14,7 @@ #include "clang/AST/Decl.h" #include "clang/AST/DeclBase.h" #include "clang/AST/DeclCXX.h" +#include "clang/AST/DeclObjC.h" #include "clang/AST/DeclTemplate.h" #include "clang/AST/DeclarationName.h" #include "clang/AST/ExprCXX.h" diff --git a/clang-tools-extra/clangd/Protocol.cpp b/clang-tools-extra/clangd/Protocol.cpp index ab75faaf98586..622f527f75f4f 100644 --- a/clang-tools-extra/clangd/Protocol.cpp +++ b/clang-tools-extra/clangd/Protocol.cpp @@ -734,7 +734,9 @@ llvm::raw_ostream <<(llvm::raw_ostream , bool operator==(const SymbolDetails , const SymbolDetails ) { return LHS.name == RHS.name && LHS.containerName == RHS.containerName && - LHS.USR == RHS.USR && LHS.ID == RHS.ID; + LHS.USR == RHS.USR && LHS.ID == RHS.ID && + LHS.declarationRange == RHS.declarationRange && + LHS.definitionRange == RHS.definitionRange; } llvm::json::Value toJSON(const SymbolDetails ) { @@ -755,6 +757,12 @@ llvm::json::Value toJSON(const SymbolDetails ) { if (P.ID) Result["id"] = P.ID.str(); + if (P.declarationRange) +Result["declarationRange"] = *P.declarationRange; + + if (P.definitionRange) +Result["definitionRange"] = *P.definitionRange; + // FIXME: workaround for older gcc/clang return std::move(Result); } diff --git a/clang-tools-extra/clangd/Protocol.h b/clang-tools-extra/clangd/Protocol.h index 0087017efad4f..add24daa1bc6d 100644 --- a/clang-tools-extra/clangd/Protocol.h +++ b/clang-tools-extra/clangd/Protocol.h @@ -1093,6 +1093,10 @@ struct SymbolDetails { std::string USR; SymbolID ID; + + llvm::Optional declarationRange; + + llvm::Optional definitionRange; }; llvm::json::Value toJSON(const SymbolDetails &); llvm::raw_ostream <<(llvm::raw_ostream &, const SymbolDetails &); diff --git a/clang-tools-extra/clangd/XRefs.cpp b/clang-tools-extra/clangd/XRefs.cpp index c871ea0c437f4..c6a843ec1db43 100644 --- a/clang-tools-extra/clangd/XRefs.cpp +++ b/clang-tools-extra/clangd/XRefs.cpp @@ -1481,7 +1481,7 @@ std::vector getSymbolInfo(ParsedAST , Position Pos) { llvm::consumeError(CurLoc.takeError()); return {}; } - + auto MainFilePath = AST.tuPath(); std::vector Results; // We also want the targets of using-decls, so we include @@ -1489,6 +1489,8 @@ std::vector getSymbolInfo(ParsedAST , Position Pos) { DeclRelationSet Relations = DeclRelation::TemplatePattern | DeclRelation::Alias | DeclRelation::Underlying; for (const NamedDecl *D : getDeclAtPosition(AST, *CurLoc, Relations)) { +D = getPreferredDecl(D); + SymbolDetails NewSymbol; std::string QName = printQualifiedName(*D); auto SplitQName = splitQualifiedName(QName); @@ -1505,6 +1507,12 @@ std::vector getSymbolInfo(ParsedAST , Position Pos) { NewSymbol.USR = std::string(USR.str()); NewSymbol.ID = SymbolID(NewSymbol.USR); } +if (const NamedDecl *Def = getDefinition(D)) + NewSymbol.definitionRange = makeLocation( + AST.getASTContext(), nameLocation(*Def, SM), MainFilePath); +NewSymbol.declarationRange = +makeLocation(AST.getASTContext(), nameLocation(*D, SM), MainFilePath); + Results.push_back(std::move(NewSymbol)); } diff --git a/clang-tools-extra/clangd/test/symbol-info.test b/clang-tools-extra/clangd/test/symbol-info.test index 1142f3b80edb7..ba788e1c398eb 100644 --- a/clang-tools-extra/clangd/test/symbol-info.test +++ b/clang-tools-extra/clangd/test/symbol-info.test @@ -1,10 +1,36 @@ # RUN: clangd -lit-test < %s | FileCheck %s {"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"clangd","capabilities":{},"trace":"off"}} ---
[clang-tools-extra] 88a2ac6 - [clangd] Improve XRefs support for ObjCMethodDecl
Author: David Goldman Date: 2022-07-26T12:11:26-04:00 New Revision: 88a2ac6ad623cd7519070f6b0bf2de2793bb90dd URL: https://github.com/llvm/llvm-project/commit/88a2ac6ad623cd7519070f6b0bf2de2793bb90dd DIFF: https://github.com/llvm/llvm-project/commit/88a2ac6ad623cd7519070f6b0bf2de2793bb90dd.diff LOG: [clangd] Improve XRefs support for ObjCMethodDecl - Correct nameLocation to point to the first selector fragment instead of the - or + - getDefinition now searches through the proper impl decls to find the definition of the ObjCMethodDecl if one exists Differential Revision: https://reviews.llvm.org/D130095 Added: Modified: clang-tools-extra/clangd/AST.cpp clang-tools-extra/clangd/AST.h clang-tools-extra/clangd/XRefs.cpp clang-tools-extra/clangd/unittests/XRefsTests.cpp Removed: diff --git a/clang-tools-extra/clangd/AST.cpp b/clang-tools-extra/clangd/AST.cpp index f7d526fab0963..4ddfca328eaed 100644 --- a/clang-tools-extra/clangd/AST.cpp +++ b/clang-tools-extra/clangd/AST.cpp @@ -171,6 +171,9 @@ bool isImplementationDetail(const Decl *D) { SourceLocation nameLocation(const clang::Decl , const SourceManager ) { auto L = D.getLocation(); + // For `- (void)foo` we want `foo` not the `-`. + if (const auto *MD = dyn_cast()) +L = MD->getSelectorStartLoc(); if (isSpelledInSource(L, SM)) return SM.getSpellingLoc(L); return SM.getExpansionLoc(L); @@ -356,6 +359,20 @@ SymbolID getSymbolID(const llvm::StringRef MacroName, const MacroInfo *MI, return SymbolID(USR); } +const ObjCImplDecl *getCorrespondingObjCImpl(const ObjCContainerDecl *D) { + if (const auto *ID = dyn_cast(D)) +return ID->getImplementation(); + if (const auto *CD = dyn_cast(D)) { +if (CD->IsClassExtension()) { + if (const auto *ID = CD->getClassInterface()) +return ID->getImplementation(); + return nullptr; +} +return CD->getImplementation(); + } + return nullptr; +} + std::string printType(const QualType QT, const DeclContext , const llvm::StringRef Placeholder) { std::string Result; diff --git a/clang-tools-extra/clangd/AST.h b/clang-tools-extra/clangd/AST.h index e0024c27aca3a..f313161b6c608 100644 --- a/clang-tools-extra/clangd/AST.h +++ b/clang-tools-extra/clangd/AST.h @@ -93,6 +93,30 @@ SymbolID getSymbolID(const Decl *D); SymbolID getSymbolID(const llvm::StringRef MacroName, const MacroInfo *MI, const SourceManager ); +/// Return the corresponding implementation/definition for the given ObjC +/// container if it has one, otherwise, return nullptr. +/// +/// Objective-C classes can have three types of declarations: +/// +/// - forward declaration: @class MyClass; +/// - true declaration (interface definition): @interface MyClass ... @end +/// - true definition (implementation): @implementation MyClass ... @end +/// +/// Objective-C categories are extensions on classes: +/// +/// - declaration: @interface MyClass (Ext) ... @end +/// - definition: @implementation MyClass (Ext) ... @end +/// +/// With one special case, a class extension, which is normally used to keep +/// some declarations internal to a file without exposing them in a header. +/// +/// - class extension declaration: @interface MyClass () ... @end +/// - which really links to class definition: @implementation MyClass ... @end +/// +/// For Objective-C protocols, e.g. @protocol MyProtocol ... @end this will +/// return nullptr as protocols don't have an implementation. +const ObjCImplDecl *getCorrespondingObjCImpl(const ObjCContainerDecl *D); + /// Returns a QualType as string. The result doesn't contain unwritten scopes /// like anonymous/inline namespace. std::string printType(const QualType QT, const DeclContext , diff --git a/clang-tools-extra/clangd/XRefs.cpp b/clang-tools-extra/clangd/XRefs.cpp index c620b3897f6de..46827433e136d 100644 --- a/clang-tools-extra/clangd/XRefs.cpp +++ b/clang-tools-extra/clangd/XRefs.cpp @@ -83,32 +83,20 @@ const NamedDecl *getDefinition(const NamedDecl *D) { if (const auto *CTD = dyn_cast(D)) if (const auto *RD = CTD->getTemplatedDecl()) return RD->getDefinition(); - // Objective-C classes can have three types of declarations: - // - // - forward declaration: @class MyClass; - // - true declaration (interface definition): @interface MyClass ... @end - // - true definition (implementation): @implementation MyClass ... @end - // - // Objective-C categories are extensions are on classes: - // - // - declaration: @interface MyClass (Ext) ... @end - // - definition: @implementation MyClass (Ext) ... @end - // - // With one special case, a class extension, which is normally used to keep - // some declarations internal to a file without exposing them in a header. - // - // - class extension declaration: @interface MyClass () ... @end - // - which really links to class
[clang-tools-extra] dc6c1f1 - [clangd][ObjC] Fix ObjC method definition completion
Author: David Goldman Date: 2022-07-01T10:02:47-04:00 New Revision: dc6c1f181b8a95b959f590423ce007b819532290 URL: https://github.com/llvm/llvm-project/commit/dc6c1f181b8a95b959f590423ce007b819532290 DIFF: https://github.com/llvm/llvm-project/commit/dc6c1f181b8a95b959f590423ce007b819532290.diff LOG: [clangd][ObjC] Fix ObjC method definition completion D124637 improved filtering of method expressions, but not method definitions. With this change, clangd will now filter ObjC method definition completions based on their entire selector instead of only the first selector fragment. Differential Revision: https://reviews.llvm.org/D128821 Added: Modified: clang-tools-extra/clangd/CodeComplete.cpp clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp Removed: diff --git a/clang-tools-extra/clangd/CodeComplete.cpp b/clang-tools-extra/clangd/CodeComplete.cpp index fbd7488c07d21..2da83c05e9702 100644 --- a/clang-tools-extra/clangd/CodeComplete.cpp +++ b/clang-tools-extra/clangd/CodeComplete.cpp @@ -861,7 +861,7 @@ struct CompletionRecorder : public CodeCompleteConsumer { case CodeCompletionResult::RK_Macro: return Result.Macro->getName(); case CodeCompletionResult::RK_Pattern: - return Result.Pattern->getTypedText(); + break; } auto *CCS = codeCompletionString(Result); const CodeCompletionString::Chunk *OnlyText = nullptr; diff --git a/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp b/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp index f962c3f4ff336..6084a024bea38 100644 --- a/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp +++ b/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp @@ -3131,6 +3131,26 @@ TEST(CompletionTest, ObjectiveCMethodDeclaration) { EXPECT_THAT(C, ElementsAre(signature("(char)c secondArgument:(id)object"))); } +TEST(CompletionTest, ObjectiveCMethodDeclarationFilterOnEntireSelector) { + auto Results = completions(R"objc( + @interface Foo + - (int)valueForCharacter:(char)c secondArgument:(id)object; + @end + @implementation Foo + secondArg^ + @end +)objc", + /*IndexSymbols=*/{}, + /*Opts=*/{}, "Foo.m"); + + auto C = Results.Completions; + EXPECT_THAT(C, ElementsAre(named("valueForCharacter:"))); + EXPECT_THAT(C, ElementsAre(filterText("valueForCharacter:secondArgument:"))); + EXPECT_THAT(C, ElementsAre(kind(CompletionItemKind::Method))); + EXPECT_THAT(C, ElementsAre(qualifier("- (int)"))); + EXPECT_THAT(C, ElementsAre(signature("(char)c secondArgument:(id)object"))); +} + TEST(CompletionTest, ObjectiveCMethodDeclarationPrefixTyped) { auto Results = completions(R"objc( @interface Foo ___ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang-tools-extra] bc1f243 - [clangd] Improve ObjC protocol suggestions from the index
Author: David Goldman Date: 2022-06-15T15:02:37-04:00 New Revision: bc1f24332af3dedccb84a975e57adca495ea016d URL: https://github.com/llvm/llvm-project/commit/bc1f24332af3dedccb84a975e57adca495ea016d DIFF: https://github.com/llvm/llvm-project/commit/bc1f24332af3dedccb84a975e57adca495ea016d.diff LOG: [clangd] Improve ObjC protocol suggestions from the index When querying the index during an ObjC protocol name lookup for code completion, we should only suggest ObjC protocols. Differential Revision: https://reviews.llvm.org/D127125 Added: Modified: clang-tools-extra/clangd/CodeComplete.cpp clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp clang-tools-extra/clangd/unittests/TestIndex.cpp clang-tools-extra/clangd/unittests/TestIndex.h Removed: diff --git a/clang-tools-extra/clangd/CodeComplete.cpp b/clang-tools-extra/clangd/CodeComplete.cpp index 527b6cdc304db..09e516919c807 100644 --- a/clang-tools-extra/clangd/CodeComplete.cpp +++ b/clang-tools-extra/clangd/CodeComplete.cpp @@ -1673,6 +1673,14 @@ class CodeCompleteFlow { return Output; } + bool includeSymbolFromIndex(const Symbol ) { +if (CCContextKind == CodeCompletionContext::CCC_ObjCProtocolName) { + return Sym.SymInfo.Lang == index::SymbolLanguage::ObjC && + Sym.SymInfo.Kind == index::SymbolKind::Protocol; +} +return true; + } + SymbolSlab queryIndex() { trace::Span Tracer("Query index"); SPAN_ATTACH(Tracer, "limit", int64_t(Opts.Limit)); @@ -1706,8 +1714,10 @@ class CodeCompleteFlow { // Run the query against the index. SymbolSlab::Builder ResultsBuilder; -if (Opts.Index->fuzzyFind( -Req, [&](const Symbol ) { ResultsBuilder.insert(Sym); })) +if (Opts.Index->fuzzyFind(Req, [&](const Symbol ) { + if (includeSymbolFromIndex(Sym)) +ResultsBuilder.insert(Sym); +})) Incomplete = true; return std::move(ResultsBuilder).build(); } diff --git a/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp b/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp index 18b02af48095f..88698d3177168 100644 --- a/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp +++ b/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp @@ -3167,6 +3167,20 @@ TEST(CompletionTest, ObjectiveCMethodDeclarationFromMiddle) { EXPECT_THAT(C, ElementsAre(signature("(id)object"))); } +TEST(CompletionTest, ObjectiveCProtocolFromIndex) { + Symbol FoodClass = objcClass("FoodClass"); + Symbol SymFood = objcProtocol("Food"); + Symbol SymFooey = objcProtocol("Fooey"); + auto Results = completions(R"objc( + id +)objc", + {SymFood, FoodClass, SymFooey}, + /*Opts=*/{}, "Foo.m"); + + auto C = Results.Completions; + EXPECT_THAT(C, UnorderedElementsAre(named("Food"), named("Fooey"))); +} + TEST(CompletionTest, CursorInSnippets) { clangd::CodeCompleteOptions Options; Options.EnableSnippets = true; diff --git a/clang-tools-extra/clangd/unittests/TestIndex.cpp b/clang-tools-extra/clangd/unittests/TestIndex.cpp index 3378ac856fc6d..6f7bd3aac207b 100644 --- a/clang-tools-extra/clangd/unittests/TestIndex.cpp +++ b/clang-tools-extra/clangd/unittests/TestIndex.cpp @@ -81,6 +81,28 @@ Symbol conceptSym(llvm::StringRef Name) { return sym(Name, index::SymbolKind::Concept, "@CT@\\0"); } +Symbol objcSym(llvm::StringRef Name, index::SymbolKind Kind, + llvm::StringRef USRPrefix) { + Symbol Sym; + std::string USR = USRPrefix.str() + Name.str(); + Sym.Name = Name; + Sym.Scope = ""; + Sym.ID = SymbolID(USR); + Sym.SymInfo.Kind = Kind; + Sym.SymInfo.Lang = index::SymbolLanguage::ObjC; + Sym.Flags |= Symbol::IndexedForCodeCompletion; + Sym.Origin = SymbolOrigin::Static; + return Sym; +} + +Symbol objcClass(llvm::StringRef Name) { + return objcSym(Name, index::SymbolKind::Class, "objc(cs)"); +} + +Symbol objcProtocol(llvm::StringRef Name) { + return objcSym(Name, index::SymbolKind::Protocol, "objc(pl)"); +} + SymbolSlab generateSymbols(std::vector QualifiedNames) { SymbolSlab::Builder Slab; for (llvm::StringRef QName : QualifiedNames) diff --git a/clang-tools-extra/clangd/unittests/TestIndex.h b/clang-tools-extra/clangd/unittests/TestIndex.h index b55f82eac3292..6a4d2cb5cdd01 100644 --- a/clang-tools-extra/clangd/unittests/TestIndex.h +++ b/clang-tools-extra/clangd/unittests/TestIndex.h @@ -34,6 +34,14 @@ Symbol ns(llvm::StringRef Name); // Create a C++20 concept symbol. Symbol conceptSym(llvm::StringRef Name); +// Create an Objective-C symbol. +Symbol objcSym(llvm::StringRef Name, index::SymbolKind Kind, + llvm::StringRef USRPrefix); +// Create an @interface or @implementation. +Symbol objcClass(llvm::StringRef Name); +// Create an @protocol. +Symbol objcProtocol(llvm::StringRef Name); + // Create a slab of symbols
[clang-tools-extra] c797952 - [clangd] Minor fixes to ExtractVariableTests missed in D124486
Author: David Goldman Date: 2022-05-31T11:34:35-04:00 New Revision: c797952d4f012275b2e23f5ffcab1f39eacd184d URL: https://github.com/llvm/llvm-project/commit/c797952d4f012275b2e23f5ffcab1f39eacd184d DIFF: https://github.com/llvm/llvm-project/commit/c797952d4f012275b2e23f5ffcab1f39eacd184d.diff LOG: [clangd] Minor fixes to ExtractVariableTests missed in D124486 Added: Modified: clang-tools-extra/clangd/unittests/tweaks/ExtractVariableTests.cpp Removed: diff --git a/clang-tools-extra/clangd/unittests/tweaks/ExtractVariableTests.cpp b/clang-tools-extra/clangd/unittests/tweaks/ExtractVariableTests.cpp index bd06f318b2a9..70ecc202d0b2 100644 --- a/clang-tools-extra/clangd/unittests/tweaks/ExtractVariableTests.cpp +++ b/clang-tools-extra/clangd/unittests/tweaks/ExtractVariableTests.cpp @@ -305,22 +305,7 @@ TEST_F(ExtractVariableTest, Test) { EXPECT_EQ(IO.second, apply(IO.first)) << IO.first; } - ExtraArgs = {"-xobjective-c"}; - EXPECT_UNAVAILABLE(R"cpp( - __attribute__((objc_root_class)) - @interface Foo - - (void)setMethod1:(int)a; - - (int)method1; - @property int prop1; - @end - @implementation Foo - - (void)method { -[[self.method1]] = 1; -[[self.method1]] += 1; -[[self.prop1]] = 1; -[[self.prop1]] += 1; - } - @end)cpp"); + ExtraArgs = {"-xc"}; InputOutputs = { // Function Pointers {R"cpp(struct Handlers { @@ -345,6 +330,7 @@ TEST_F(ExtractVariableTest, Test) { void bar() { int (*placeholder)(int) = foo('c'); (void)placeholder; })cpp"}, + // Arithmetic on typedef types yields plain integer types {R"cpp(typedef long NSInteger; void varDecl() { NSInteger a = 2 * 5; @@ -355,6 +341,28 @@ TEST_F(ExtractVariableTest, Test) { NSInteger a = 2 * 5; long placeholder = a * 7; NSInteger b = placeholder + 3; })cpp"}, + }; + for (const auto : InputOutputs) { +EXPECT_EQ(IO.second, apply(IO.first)) << IO.first; + } + + ExtraArgs = {"-xobjective-c"}; + EXPECT_UNAVAILABLE(R"cpp( + __attribute__((objc_root_class)) + @interface Foo + - (void)setMethod1:(int)a; + - (int)method1; + @property int prop1; + @end + @implementation Foo + - (void)method { +[[self.method1]] = 1; +[[self.method1]] += 1; +[[self.prop1]] = 1; +[[self.prop1]] += 1; + } + @end)cpp"); + InputOutputs = { // Support ObjC property references (explicit property getter). {R"cpp(__attribute__((objc_root_class)) @interface Foo ___ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang-tools-extra] ae67984 - [clangd] ExtractVariable support for C and Objective-C
Author: David Goldman Date: 2022-05-31T11:14:51-04:00 New Revision: ae67984ca6d89c7ccdbdca258cd05c151d8b6431 URL: https://github.com/llvm/llvm-project/commit/ae67984ca6d89c7ccdbdca258cd05c151d8b6431 DIFF: https://github.com/llvm/llvm-project/commit/ae67984ca6d89c7ccdbdca258cd05c151d8b6431.diff LOG: [clangd] ExtractVariable support for C and Objective-C - Use the expression's type for non-C++ as the variable type. This works well, but might not preserve the typedefs due to type canonicalization. - Improve support for Objective-C property references which are represented using `ObjCPropertyRefExpr` and `BuiltinType::PseudoObject`. Differential Revision: https://reviews.llvm.org/D124486 Added: Modified: clang-tools-extra/clangd/refactor/tweaks/ExtractVariable.cpp clang-tools-extra/clangd/unittests/tweaks/ExtractVariableTests.cpp Removed: diff --git a/clang-tools-extra/clangd/refactor/tweaks/ExtractVariable.cpp b/clang-tools-extra/clangd/refactor/tweaks/ExtractVariable.cpp index 942d7eec6dbd8..5efe09fb3cb27 100644 --- a/clang-tools-extra/clangd/refactor/tweaks/ExtractVariable.cpp +++ b/clang-tools-extra/clangd/refactor/tweaks/ExtractVariable.cpp @@ -5,6 +5,7 @@ // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===--===// +#include "AST.h" #include "ParsedAST.h" #include "Protocol.h" #include "Selection.h" @@ -50,6 +51,7 @@ class ExtractionContext { private: bool Extractable = false; const clang::Expr *Expr; + QualType VarType; const SelectionTree::Node *ExprNode; // Stmt before which we will extract const clang::Stmt *InsertionPoint = nullptr; @@ -81,6 +83,31 @@ computeReferencedDecls(const clang::Expr *Expr) { return Visitor.ReferencedDecls; } +static QualType computeVariableType(const Expr *Expr, const ASTContext ) { + if (Ctx.getLangOpts().CPlusPlus11) +return Ctx.getAutoDeductType(); + + if (Expr->hasPlaceholderType(BuiltinType::PseudoObject)) { +if (const auto *PR = dyn_cast(Expr)) { + if (PR->isMessagingSetter()) { +// Don't support extracting a compound reference like `self.prop += 1` +// since the meaning changes after extraction since we'll no longer call +// the setter. Non compound access like `self.prop = 1` is invalid since +// it returns nil (setter method must have a void return type). +return QualType(); + } else if (PR->isMessagingGetter()) { +if (PR->isExplicitProperty()) + return PR->getExplicitProperty()->getType(); +else + return PR->getImplicitPropertyGetter()->getReturnType(); + } +} else { + return QualType(); +} + } + return Expr->getType(); +} + ExtractionContext::ExtractionContext(const SelectionTree::Node *Node, const SourceManager , const ASTContext ) @@ -90,6 +117,12 @@ ExtractionContext::ExtractionContext(const SelectionTree::Node *Node, InsertionPoint = computeInsertionPoint(); if (InsertionPoint) Extractable = true; + VarType = computeVariableType(Expr, Ctx); + if (VarType.isNull()) +Extractable = false; + else +// Strip the outer nullability since it's not common for local variables. +AttributedType::stripOuterNullability(VarType); } // checks whether extracting before InsertionPoint will take a @@ -173,9 +206,9 @@ ExtractionContext::insertDeclaration(llvm::StringRef VarName, toHalfOpenFileRange(SM, Ctx.getLangOpts(), InsertionPoint->getSourceRange()) ->getBegin(); - // FIXME: Replace auto with explicit type and add &/&& as necessary - std::string ExtractedVarDecl = std::string("auto ") + VarName.str() + " = " + - ExtractionCode.str() + "; "; + std::string ExtractedVarDecl = + printType(VarType, ExprNode->getDeclContext(), VarName) + " = " + + ExtractionCode.str() + "; "; return tooling::Replacement(SM, InsertionLoc, 0, ExtractedVarDecl); } @@ -365,15 +398,27 @@ bool eligibleForExtraction(const SelectionTree::Node *N) { return false; // Void expressions can't be assigned to variables. - if (const Type *ExprType = E->getType().getTypePtrOrNull()) -if (ExprType->isVoidType()) - return false; + const Type *ExprType = E->getType().getTypePtrOrNull(); + if (!ExprType || ExprType->isVoidType()) +return false; + + // Must know the type of the result in order to spell it, or instead use + // `auto` in C++. + if (!N->getDeclContext().getParentASTContext().getLangOpts().CPlusPlus11 && + !ExprType) +return false; // A plain reference to a name (e.g. variable) isn't worth extracting. // FIXME: really? What if it's e.g. `std::is_same::value`? - if (llvm::isa(E) || llvm::isa(E)) + if
[clang-tools-extra] 322e2a3 - [clangd][ObjC] Filter ObjC method completions on the remaining selector
Author: David Goldman Date: 2022-05-20T11:49:16-04:00 New Revision: 322e2a3b40fa5f6bfcb868e827960c812b185710 URL: https://github.com/llvm/llvm-project/commit/322e2a3b40fa5f6bfcb868e827960c812b185710 DIFF: https://github.com/llvm/llvm-project/commit/322e2a3b40fa5f6bfcb868e827960c812b185710.diff LOG: [clangd][ObjC] Filter ObjC method completions on the remaining selector Previously, clangd would filter completions only on the first part of the selector (first typed chunk) instead of all remaining selector fragments (all typed chunks). Differential Revision: https://reviews.llvm.org/D124637 Added: Modified: clang-tools-extra/clangd/CodeComplete.cpp clang-tools-extra/clangd/CodeComplete.h clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp clang/include/clang/Sema/CodeCompleteConsumer.h clang/lib/Sema/CodeCompleteConsumer.cpp Removed: diff --git a/clang-tools-extra/clangd/CodeComplete.cpp b/clang-tools-extra/clangd/CodeComplete.cpp index 920fb77f07dee..527b6cdc304db 100644 --- a/clang-tools-extra/clangd/CodeComplete.cpp +++ b/clang-tools-extra/clangd/CodeComplete.cpp @@ -303,6 +303,7 @@ struct CodeCompletionBuilder { assert(ASTCtx); Completion.Origin |= SymbolOrigin::AST; Completion.Name = std::string(llvm::StringRef(SemaCCS->getTypedText())); + Completion.FilterText = SemaCCS->getAllTypedText(); if (Completion.Scope.empty()) { if ((C.SemaResult->Kind == CodeCompletionResult::RK_Declaration) || (C.SemaResult->Kind == CodeCompletionResult::RK_Pattern)) @@ -335,6 +336,8 @@ struct CodeCompletionBuilder { Completion.Kind = toCompletionItemKind(C.IndexResult->SymInfo.Kind); if (Completion.Name.empty()) Completion.Name = std::string(C.IndexResult->Name); + if (Completion.FilterText.empty()) +Completion.FilterText = Completion.Name; // If the completion was visible to Sema, no qualifier is needed. This // avoids unneeded qualifiers in cases like with `using ns::X`. if (Completion.RequiredQualifier.empty() && !C.SemaResult) { @@ -352,6 +355,7 @@ struct CodeCompletionBuilder { Completion.Origin |= SymbolOrigin::Identifier; Completion.Kind = CompletionItemKind::Text; Completion.Name = std::string(C.IdentifierResult->Name); + Completion.FilterText = Completion.Name; } // Turn absolute path into a literal string that can be #included. @@ -860,7 +864,15 @@ struct CompletionRecorder : public CodeCompleteConsumer { return Result.Pattern->getTypedText(); } auto *CCS = codeCompletionString(Result); -return CCS->getTypedText(); +const CodeCompletionString::Chunk *OnlyText = nullptr; +for (auto : *CCS) { + if (C.Kind != CodeCompletionString::CK_TypedText) +continue; + if (OnlyText) +return CCAllocator->CopyString(CCS->getAllTypedText()); + OnlyText = +} +return OnlyText ? OnlyText->Text : llvm::StringRef(); } // Build a CodeCompletion string for R, which must be from Results. @@ -1980,6 +1992,7 @@ CodeCompleteResult codeCompleteComment(PathRef FileName, unsigned Offset, continue; CodeCompletion Item; Item.Name = Name.str() + "="; +Item.FilterText = Item.Name; Item.Kind = CompletionItemKind::Text; Result.Completions.push_back(Item); } @@ -2118,8 +2131,8 @@ CompletionItem CodeCompletion::render(const CodeCompleteOptions ) const { Doc.append(*Documentation); LSP.documentation = renderDoc(Doc, Opts.DocumentationFormat); } - LSP.sortText = sortText(Score.Total, Name); - LSP.filterText = Name; + LSP.sortText = sortText(Score.Total, FilterText); + LSP.filterText = FilterText; LSP.textEdit = {CompletionTokenRange, RequiredQualifier + Name}; // Merge continuous additionalTextEdits into main edit. The main motivation // behind this is to help LSP clients, it seems most of them are confused when diff --git a/clang-tools-extra/clangd/CodeComplete.h b/clang-tools-extra/clangd/CodeComplete.h index b76dd642943a9..73139ba40e765 100644 --- a/clang-tools-extra/clangd/CodeComplete.h +++ b/clang-tools-extra/clangd/CodeComplete.h @@ -155,6 +155,10 @@ struct CodeCompleteOptions { struct CodeCompletion { // The unqualified name of the symbol or other completion item. std::string Name; + // The name of the symbol for filtering and sorting purposes. Typically the + // same as `Name`, but may be diff erent e.g. for ObjC methods, `Name` is the + // first selector fragment but the `FilterText` is the entire selector. + std::string FilterText; // The scope qualifier for the symbol name. e.g. "ns1::ns2::" // Empty for non-symbol completions. Not inserted, but may be displayed. std::string Scope; diff --git a/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp
[clang] d9739f2 - Serialize PragmaAssumeNonNullLoc to support preambles
Author: David Goldman Date: 2022-03-31T11:08:01-04:00 New Revision: d9739f29cdd4c066763275d09e1d26ee315cfdf5 URL: https://github.com/llvm/llvm-project/commit/d9739f29cdd4c066763275d09e1d26ee315cfdf5 DIFF: https://github.com/llvm/llvm-project/commit/d9739f29cdd4c066763275d09e1d26ee315cfdf5.diff LOG: Serialize PragmaAssumeNonNullLoc to support preambles Previously, if a `#pragma clang assume_nonnull begin` was at the end of a premable with a `#pragma clang assume_nonnull end` at the end of the main file, clang would diagnose an unterminated begin in the preamble and an unbalanced end in the main file. With this change, those errors no longer occur and the case above is now properly handled. I've added a corresponding test to clangd, which makes use of preambles, in order to verify this works as expected. Differential Revision: https://reviews.llvm.org/D122179 Added: clang/test/Index/preamble-assume-nonnull.c Modified: clang-tools-extra/clangd/unittests/DiagnosticsTests.cpp clang/include/clang/Lex/Preprocessor.h clang/include/clang/Lex/PreprocessorOptions.h clang/include/clang/Serialization/ASTBitCodes.h clang/lib/Lex/PPLexerChange.cpp clang/lib/Serialization/ASTReader.cpp clang/lib/Serialization/ASTWriter.cpp Removed: diff --git a/clang-tools-extra/clangd/unittests/DiagnosticsTests.cpp b/clang-tools-extra/clangd/unittests/DiagnosticsTests.cpp index 097ada51b2e9a..5a5a53679facc 100644 --- a/clang-tools-extra/clangd/unittests/DiagnosticsTests.cpp +++ b/clang-tools-extra/clangd/unittests/DiagnosticsTests.cpp @@ -747,6 +747,40 @@ TEST(DiagnosticsTest, RecursivePreambleIfndefGuard) { EXPECT_THAT(TU.build().getLocalTopLevelDecls(), SizeIs(1)); } +TEST(DiagnosticsTest, PreambleWithPragmaAssumeNonnull) { + auto TU = TestTU::withCode(R"cpp( +#pragma clang assume_nonnull begin +void foo(int *x); +#pragma clang assume_nonnull end +)cpp"); + auto AST = TU.build(); + EXPECT_THAT(*AST.getDiagnostics(), IsEmpty()); + const auto *X = cast(findDecl(AST, "foo")).getParamDecl(0); + ASSERT_TRUE(X->getOriginalType()->getNullability(X->getASTContext()) == + NullabilityKind::NonNull); +} + +TEST(DiagnosticsTest, PreambleHeaderWithBadPragmaAssumeNonnull) { + Annotations Header(R"cpp( +#pragma clang assume_nonnull begin // error-ok +void foo(int *X); +)cpp"); + auto TU = TestTU::withCode(R"cpp( +#include "foo.h" // unterminated assume_nonnull should not affect bar. +void bar(int *Y); +)cpp"); + TU.AdditionalFiles = {{"foo.h", std::string(Header.code())}}; + auto AST = TU.build(); + EXPECT_THAT(*AST.getDiagnostics(), + ElementsAre(diagName("pp_eof_in_assume_nonnull"))); + const auto *X = cast(findDecl(AST, "foo")).getParamDecl(0); + ASSERT_TRUE(X->getOriginalType()->getNullability(X->getASTContext()) == + NullabilityKind::NonNull); + const auto *Y = cast(findDecl(AST, "bar")).getParamDecl(0); + ASSERT_FALSE( + Y->getOriginalType()->getNullability(X->getASTContext()).hasValue()); +} + TEST(DiagnosticsTest, InsideMacros) { Annotations Test(R"cpp( #define TEN 10 diff --git a/clang/include/clang/Lex/Preprocessor.h b/clang/include/clang/Lex/Preprocessor.h index 36bf8ed64c2bc..02b94872cd8ae 100644 --- a/clang/include/clang/Lex/Preprocessor.h +++ b/clang/include/clang/Lex/Preprocessor.h @@ -409,6 +409,14 @@ class Preprocessor { /// \#pragma clang assume_nonnull begin. SourceLocation PragmaAssumeNonNullLoc; + /// Set only for preambles which end with an active + /// \#pragma clang assume_nonnull begin. + /// + /// When the preamble is loaded into the main file, + /// `PragmaAssumeNonNullLoc` will be set to this to + /// replay the unterminated assume_nonnull. + SourceLocation PreambleRecordedPragmaAssumeNonNullLoc; + /// True if we hit the code-completion point. bool CodeCompletionReached = false; @@ -1762,6 +1770,21 @@ class Preprocessor { PragmaAssumeNonNullLoc = Loc; } + /// Get the location of the recorded unterminated \#pragma clang + /// assume_nonnull begin in the preamble, if one exists. + /// + /// Returns an invalid location if the premable did not end with + /// such a pragma active or if there is no recorded preamble. + SourceLocation getPreambleRecordedPragmaAssumeNonNullLoc() const { +return PreambleRecordedPragmaAssumeNonNullLoc; + } + + /// Record the location of the unterminated \#pragma clang + /// assume_nonnull begin in the preamble. + void setPreambleRecordedPragmaAssumeNonNullLoc(SourceLocation Loc) { +PreambleRecordedPragmaAssumeNonNullLoc = Loc; + } + /// Set the directory in which the main file should be considered /// to have been found, if it is not a real file. void setMainFileDir(const DirectoryEntry *Dir) { diff --git a/clang/include/clang/Lex/PreprocessorOptions.h b/clang/include/clang/Lex/PreprocessorOptions.h index
[clang-tools-extra] 8522a01 - Attempt forward fix for Linux buildbots for D116385
Author: David Goldman Date: 2022-03-17T12:49:16-04:00 New Revision: 8522a01e842e6791545864f2d255dc48190a7e34 URL: https://github.com/llvm/llvm-project/commit/8522a01e842e6791545864f2d255dc48190a7e34 DIFF: https://github.com/llvm/llvm-project/commit/8522a01e842e6791545864f2d255dc48190a7e34.diff LOG: Attempt forward fix for Linux buildbots for D116385 Added: Modified: clang-tools-extra/clangd/unittests/tweaks/ObjCMemberwiseInitializerTests.cpp Removed: diff --git a/clang-tools-extra/clangd/unittests/tweaks/ObjCMemberwiseInitializerTests.cpp b/clang-tools-extra/clangd/unittests/tweaks/ObjCMemberwiseInitializerTests.cpp index dc78d696bebd8..05235e01efad8 100644 --- a/clang-tools-extra/clangd/unittests/tweaks/ObjCMemberwiseInitializerTests.cpp +++ b/clang-tools-extra/clangd/unittests/tweaks/ObjCMemberwiseInitializerTests.cpp @@ -27,6 +27,7 @@ TEST_F(ObjCMemberwiseInitializerTest, TestAvailability) { @end )cpp"); + ExtraArgs.push_back("-fobjc-runtime=macosx"); ExtraArgs.push_back("-fobjc-arc"); // Ensure the action can be initiated on the interface and implementation, @@ -88,6 +89,7 @@ TEST_F(ObjCMemberwiseInitializerTest, TestAvailability) { TEST_F(ObjCMemberwiseInitializerTest, Test) { FileName = "TestTU.m"; + ExtraArgs.push_back("-fobjc-runtime=macosx"); ExtraArgs.push_back("-fobjc-arc"); const char *Input = R"cpp( ___ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang-tools-extra] 1af5fbd - [clangd] Code action for creating an ObjC initializer
Author: David Goldman Date: 2022-03-17T11:31:14-04:00 New Revision: 1af5fbd5c605372963f78351f721fa28ee5ba60e URL: https://github.com/llvm/llvm-project/commit/1af5fbd5c605372963f78351f721fa28ee5ba60e DIFF: https://github.com/llvm/llvm-project/commit/1af5fbd5c605372963f78351f721fa28ee5ba60e.diff LOG: [clangd] Code action for creating an ObjC initializer The code action creates an initializer for the selected ivars/properties, defaulting to all if only the interface/implementation container is selected. We add it based on the position of the first non initializer that we see, and default to adding it where the @end token is. We also use the ObjC parameter form of (nullable id) instead of (id _Nullable) if the property has the nullable attribute. Differential Revision: https://reviews.llvm.org/D116385 Added: clang-tools-extra/clangd/refactor/tweaks/ObjCMemberwiseInitializer.cpp clang-tools-extra/clangd/unittests/tweaks/ObjCMemberwiseInitializerTests.cpp Modified: clang-tools-extra/clangd/refactor/tweaks/CMakeLists.txt clang-tools-extra/clangd/unittests/CMakeLists.txt Removed: diff --git a/clang-tools-extra/clangd/refactor/tweaks/CMakeLists.txt b/clang-tools-extra/clangd/refactor/tweaks/CMakeLists.txt index caa69947c47fb..ae279781a6f52 100644 --- a/clang-tools-extra/clangd/refactor/tweaks/CMakeLists.txt +++ b/clang-tools-extra/clangd/refactor/tweaks/CMakeLists.txt @@ -22,6 +22,7 @@ add_clang_library(clangDaemonTweaks OBJECT ExtractFunction.cpp ExtractVariable.cpp ObjCLocalizeStringLiteral.cpp + ObjCMemberwiseInitializer.cpp PopulateSwitch.cpp RawStringLiteral.cpp RemoveUsingNamespace.cpp diff --git a/clang-tools-extra/clangd/refactor/tweaks/ObjCMemberwiseInitializer.cpp b/clang-tools-extra/clangd/refactor/tweaks/ObjCMemberwiseInitializer.cpp new file mode 100644 index 0..2f8f8f7863409 --- /dev/null +++ b/clang-tools-extra/clangd/refactor/tweaks/ObjCMemberwiseInitializer.cpp @@ -0,0 +1,329 @@ +//===--- ObjCMemberwiseInitializer.cpp ---*- C++-*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===--===// + +#include "ParsedAST.h" +#include "SourceCode.h" +#include "refactor/InsertionPoint.h" +#include "refactor/Tweak.h" +#include "support/Logger.h" +#include "clang/AST/DeclObjC.h" +#include "clang/AST/PrettyPrinter.h" +#include "clang/Basic/LLVM.h" +#include "clang/Basic/LangOptions.h" +#include "clang/Basic/SourceLocation.h" +#include "clang/Basic/SourceManager.h" +#include "clang/Tooling/Core/Replacement.h" +#include "llvm/ADT/None.h" +#include "llvm/ADT/Optional.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/ADT/iterator_range.h" +#include "llvm/Support/Casting.h" +#include "llvm/Support/Error.h" + +namespace clang { +namespace clangd { +namespace { + +static std::string capitalize(std::string Message) { + if (!Message.empty()) +Message[0] = llvm::toUpper(Message[0]); + return Message; +} + +static std::string getTypeStr(const QualType , const Decl , + unsigned PropertyAttributes) { + QualType T = OrigT; + PrintingPolicy Policy(D.getASTContext().getLangOpts()); + Policy.SuppressStrongLifetime = true; + std::string Prefix = ""; + // If the nullability is specified via a property attribute, use the shorter + // `nullable` form for the method parameter. + if (PropertyAttributes & ObjCPropertyAttribute::kind_nullability) { +if (auto Kind = AttributedType::stripOuterNullability(T)) { + switch (Kind.getValue()) { + case NullabilityKind::Nullable: +Prefix = "nullable "; +break; + case NullabilityKind::NonNull: +Prefix = "nonnull "; +break; + case NullabilityKind::Unspecified: +Prefix = "null_unspecified "; +break; + case NullabilityKind::NullableResult: +T = OrigT; +break; + } +} + } + return Prefix + T.getAsString(Policy); +} + +struct MethodParameter { + // Parameter name. + llvm::StringRef Name; + + // Type of the parameter. + std::string Type; + + // Assignment target (LHS). + std::string Assignee; + + MethodParameter(const ObjCIvarDecl ) { +// Convention maps `@property int foo` to ivar `int _foo`, so drop the +// leading `_` if there is one. +Name = ID.getName(); +Name.consume_front("_"); +Type = getTypeStr(ID.getType(), ID, ObjCPropertyAttribute::kind_noattr); +Assignee = ID.getName().str(); + } + MethodParameter(const ObjCPropertyDecl ) { +Name = PD.getName(); +Type = getTypeStr(PD.getType(), PD, PD.getPropertyAttributes()); +if (const auto *ID = PD.getPropertyIvarDecl()) + Assignee =
[clang-tools-extra] 54a962b - [clangd] Use `ObjCProtocolLoc` for generalized ObjC protocol support
Author: David Goldman Date: 2022-02-18T15:24:00-05:00 New Revision: 54a962bbfee86d5af90d5fdd39b4ff4ec8030f12 URL: https://github.com/llvm/llvm-project/commit/54a962bbfee86d5af90d5fdd39b4ff4ec8030f12 DIFF: https://github.com/llvm/llvm-project/commit/54a962bbfee86d5af90d5fdd39b4ff4ec8030f12.diff LOG: [clangd] Use `ObjCProtocolLoc` for generalized ObjC protocol support This removes clangd's existing workaround in favor of proper support via the newly added `ObjCProtocolLoc`. This improves support by allowing clangd to properly identify which protocol is selected now that `ObjCProtocolLoc` gets its own ASTNode. Differential Revision: https://reviews.llvm.org/D119366 Added: Modified: clang-tools-extra/clangd/FindTarget.cpp clang-tools-extra/clangd/Selection.cpp clang-tools-extra/clangd/unittests/FindTargetTests.cpp clang-tools-extra/clangd/unittests/HoverTests.cpp Removed: diff --git a/clang-tools-extra/clangd/FindTarget.cpp b/clang-tools-extra/clangd/FindTarget.cpp index e96aa25fd780c..1b7b7de4f9047 100644 --- a/clang-tools-extra/clangd/FindTarget.cpp +++ b/clang-tools-extra/clangd/FindTarget.cpp @@ -453,15 +453,6 @@ struct TargetFinder { void VisitObjCInterfaceType(const ObjCInterfaceType *OIT) { Outer.add(OIT->getDecl(), Flags); } - void VisitObjCObjectType(const ObjCObjectType *OOT) { -// Make all of the protocols targets since there's no child nodes for -// protocols. This isn't needed for the base type, which *does* have a -// child `ObjCInterfaceTypeLoc`. This structure is a hack, but it works -// well for go-to-definition. -unsigned NumProtocols = OOT->getNumProtocols(); -for (unsigned I = 0; I < NumProtocols; I++) - Outer.add(OOT->getProtocol(I), Flags); - } }; Visitor(*this, Flags).Visit(T.getTypePtr()); } @@ -547,6 +538,8 @@ allTargetDecls(const DynTypedNode , const HeuristicResolver *Resolver) { Finder.add(TAL->getArgument(), Flags); else if (const CXXBaseSpecifier *CBS = N.get()) Finder.add(CBS->getTypeSourceInfo()->getType(), Flags); + else if (const ObjCProtocolLoc *PL = N.get()) +Finder.add(PL->getProtocol(), Flags); return Finder.takeDecls(); } @@ -669,25 +662,7 @@ llvm::SmallVector refInDecl(const Decl *D, {OMD}}); } -void visitProtocolList( -llvm::iterator_range Protocols, -llvm::iterator_range Locations) { - for (const auto : llvm::zip(Protocols, Locations)) { -Refs.push_back(ReferenceLoc{NestedNameSpecifierLoc(), -std::get<1>(P), -/*IsDecl=*/false, -{std::get<0>(P)}}); - } -} - -void VisitObjCInterfaceDecl(const ObjCInterfaceDecl *OID) { - if (OID->isThisDeclarationADefinition()) -visitProtocolList(OID->protocols(), OID->protocol_locs()); - Base::VisitObjCInterfaceDecl(OID); // Visit the interface's name. -} - void VisitObjCCategoryDecl(const ObjCCategoryDecl *OCD) { - visitProtocolList(OCD->protocols(), OCD->protocol_locs()); // getLocation is the extended class's location, not the category's. Refs.push_back(ReferenceLoc{NestedNameSpecifierLoc(), OCD->getLocation(), @@ -709,12 +684,6 @@ llvm::SmallVector refInDecl(const Decl *D, /*IsDecl=*/true, {OCID->getCategoryDecl()}}); } - -void VisitObjCProtocolDecl(const ObjCProtocolDecl *OPD) { - if (OPD->isThisDeclarationADefinition()) -visitProtocolList(OPD->protocols(), OPD->protocol_locs()); - Base::VisitObjCProtocolDecl(OPD); // Visit the protocol's name. -} }; Visitor V{Resolver}; @@ -944,16 +913,6 @@ refInTypeLoc(TypeLoc L, const HeuristicResolver *Resolver) { /*IsDecl=*/false, {L.getIFaceDecl()}}); } - -void VisitObjCObjectTypeLoc(ObjCObjectTypeLoc L) { - unsigned NumProtocols = L.getNumProtocols(); - for (unsigned I = 0; I < NumProtocols; I++) { -Refs.push_back(ReferenceLoc{NestedNameSpecifierLoc(), -L.getProtocolLoc(I), -/*IsDecl=*/false, -{L.getProtocol(I)}}); - } -} }; Visitor V{Resolver}; @@ -1049,6 +1008,11 @@ class ExplicitReferenceCollector return RecursiveASTVisitor::TraverseNestedNameSpecifierLoc(L); } + bool TraverseObjCProtocolLoc(ObjCProtocolLoc ProtocolLoc) { +visitNode(DynTypedNode::create(ProtocolLoc)); +return true; + } + bool TraverseConstructorInitializer(CXXCtorInitializer *Init) { visitNode(DynTypedNode::create(*Init)); return
[clang] 805f7a4 - [clang] Add `ObjCProtocolLoc` to represent protocol references
Author: David Goldman Date: 2022-02-18T15:24:00-05:00 New Revision: 805f7a4fa4ce97277c3b73d0c204fc3aa4b072e1 URL: https://github.com/llvm/llvm-project/commit/805f7a4fa4ce97277c3b73d0c204fc3aa4b072e1 DIFF: https://github.com/llvm/llvm-project/commit/805f7a4fa4ce97277c3b73d0c204fc3aa4b072e1.diff LOG: [clang] Add `ObjCProtocolLoc` to represent protocol references Add `ObjCProtocolLoc` which behaves like `TypeLoc` but for `ObjCProtocolDecl` references. RecursiveASTVisitor now synthesizes `ObjCProtocolLoc` during traversal and the `ObjCProtocolLoc` can be stored in a `DynTypedNode`. In a follow up patch, I'll update clangd to make use of this to properly support protocol references for hover + goto definition. Differential Revision: https://reviews.llvm.org/D119363 Added: Modified: clang/include/clang/AST/ASTFwd.h clang/include/clang/AST/ASTTypeTraits.h clang/include/clang/AST/RecursiveASTVisitor.h clang/include/clang/AST/TypeLoc.h clang/lib/AST/ASTTypeTraits.cpp clang/lib/AST/ParentMapContext.cpp clang/unittests/AST/RecursiveASTVisitorTest.cpp Removed: diff --git a/clang/include/clang/AST/ASTFwd.h b/clang/include/clang/AST/ASTFwd.h index fdbd603ce5d04..f84b3238e32b5 100644 --- a/clang/include/clang/AST/ASTFwd.h +++ b/clang/include/clang/AST/ASTFwd.h @@ -33,6 +33,7 @@ class OMPClause; class Attr; #define ATTR(A) class A##Attr; #include "clang/Basic/AttrList.inc" +class ObjCProtocolLoc; } // end namespace clang diff --git a/clang/include/clang/AST/ASTTypeTraits.h b/clang/include/clang/AST/ASTTypeTraits.h index 6d96146a4d455..cd6b5143bf790 100644 --- a/clang/include/clang/AST/ASTTypeTraits.h +++ b/clang/include/clang/AST/ASTTypeTraits.h @@ -160,6 +160,7 @@ class ASTNodeKind { NKI_Attr, #define ATTR(A) NKI_##A##Attr, #include "clang/Basic/AttrList.inc" +NKI_ObjCProtocolLoc, NKI_NumberOfKinds }; @@ -213,6 +214,7 @@ KIND_TO_KIND_ID(Stmt) KIND_TO_KIND_ID(Type) KIND_TO_KIND_ID(OMPClause) KIND_TO_KIND_ID(Attr) +KIND_TO_KIND_ID(ObjCProtocolLoc) KIND_TO_KIND_ID(CXXBaseSpecifier) #define DECL(DERIVED, BASE) KIND_TO_KIND_ID(DERIVED##Decl) #include "clang/AST/DeclNodes.inc" @@ -499,7 +501,7 @@ class DynTypedNode { /// have storage or unique pointers and thus need to be stored by value. llvm::AlignedCharArrayUnion + QualType, TypeLoc, ObjCProtocolLoc> Storage; }; @@ -570,6 +572,10 @@ template <> struct DynTypedNode::BaseConverter : public PtrConverter {}; +template <> +struct DynTypedNode::BaseConverter +: public ValueConverter {}; + // The only operation we allow on unsupported types is \c get. // This allows to conveniently use \c DynTypedNode when having an arbitrary // AST node that is not supported, but prevents misuse - a user cannot create diff --git a/clang/include/clang/AST/RecursiveASTVisitor.h b/clang/include/clang/AST/RecursiveASTVisitor.h index f62dc36de556e..16da64100d424 100644 --- a/clang/include/clang/AST/RecursiveASTVisitor.h +++ b/clang/include/clang/AST/RecursiveASTVisitor.h @@ -324,6 +324,12 @@ template class RecursiveASTVisitor { /// \returns false if the visitation was terminated early, true otherwise. bool TraverseConceptReference(const ConceptReference ); + /// Recursively visit an Objective-C protocol reference with location + /// information. + /// + /// \returns false if the visitation was terminated early, true otherwise. + bool TraverseObjCProtocolLoc(ObjCProtocolLoc ProtocolLoc); + // Methods on Attrs // Visit an attribute. @@ -1340,7 +1346,12 @@ DEF_TRAVERSE_TYPELOC(DependentTemplateSpecializationType, { DEF_TRAVERSE_TYPELOC(PackExpansionType, { TRY_TO(TraverseTypeLoc(TL.getPatternLoc())); }) -DEF_TRAVERSE_TYPELOC(ObjCTypeParamType, {}) +DEF_TRAVERSE_TYPELOC(ObjCTypeParamType, { + for (unsigned I = 0, N = TL.getNumProtocols(); I != N; ++I) { +ObjCProtocolLoc ProtocolLoc(TL.getProtocol(I), TL.getProtocolLoc(I)); +TRY_TO(TraverseObjCProtocolLoc(ProtocolLoc)); + } +}) DEF_TRAVERSE_TYPELOC(ObjCInterfaceType, {}) @@ -1351,6 +1362,10 @@ DEF_TRAVERSE_TYPELOC(ObjCObjectType, { TRY_TO(TraverseTypeLoc(TL.getBaseLoc())); for (unsigned i = 0, n = TL.getNumTypeArgs(); i != n; ++i) TRY_TO(TraverseTypeLoc(TL.getTypeArgTInfo(i)->getTypeLoc())); + for (unsigned I = 0, N = TL.getNumProtocols(); I != N; ++I) { +ObjCProtocolLoc ProtocolLoc(TL.getProtocol(I), TL.getProtocolLoc(I)); +TRY_TO(TraverseObjCProtocolLoc(ProtocolLoc)); + } }) DEF_TRAVERSE_TYPELOC(ObjCObjectPointerType, @@ -1541,12 +1556,16 @@ DEF_TRAVERSE_DECL( DEF_TRAVERSE_DECL(ObjCCompatibleAliasDecl, {// FIXME: implement }) -DEF_TRAVERSE_DECL(ObjCCategoryDecl, {// FIXME: implement +DEF_TRAVERSE_DECL(ObjCCategoryDecl, { if (ObjCTypeParamList *typeParamList =
[clang-tools-extra] f98bf92 - Reland "[clangd] Properly compute framework-style include spelling"
Author: David Goldman Date: 2022-02-07T11:21:23-05:00 New Revision: f98bf92b624152c9f09e77137896f7e18dca365c URL: https://github.com/llvm/llvm-project/commit/f98bf92b624152c9f09e77137896f7e18dca365c DIFF: https://github.com/llvm/llvm-project/commit/f98bf92b624152c9f09e77137896f7e18dca365c.diff LOG: Reland "[clangd] Properly compute framework-style include spelling" Roll forward of https://reviews.llvm.org/D117056 with a fix. Proper initialization of `IsPrivateHeader` was missing, causing failures on Linux. Added: Modified: clang-tools-extra/clangd/index/SymbolCollector.cpp clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp Removed: diff --git a/clang-tools-extra/clangd/index/SymbolCollector.cpp b/clang-tools-extra/clangd/index/SymbolCollector.cpp index 3257041ffa0e3..ec0aa3c6f015a 100644 --- a/clang-tools-extra/clangd/index/SymbolCollector.cpp +++ b/clang-tools-extra/clangd/index/SymbolCollector.cpp @@ -183,6 +183,13 @@ const Decl *getRefContainer(const Decl *Enclosing, // including filename normalization, URI conversion etc. // Expensive checks are cached internally. class SymbolCollector::HeaderFileURICache { + struct FrameworkUmbrellaSpelling { +// Spelling for the public umbrella header, e.g. +llvm::Optional PublicHeader; +// Spelling for the private umbrella header, e.g. +// +llvm::Optional PrivateHeader; + }; // Weird double-indirect access to PP, which might not be ready yet when // HeaderFiles is created but will be by the time it's used. // (IndexDataConsumer::setPreprocessor can happen before or after initialize) @@ -193,6 +200,9 @@ class SymbolCollector::HeaderFileURICache { llvm::DenseMap CacheFEToURI; llvm::StringMap CachePathToURI; llvm::DenseMap CacheFIDToInclude; + llvm::StringMap CachePathToFrameworkSpelling; + llvm::StringMap + CacheFrameworkToUmbrellaHeaderSpelling; public: HeaderFileURICache(Preprocessor *, const SourceManager , @@ -249,6 +259,126 @@ class SymbolCollector::HeaderFileURICache { return R.first->second; } + struct FrameworkHeaderPath { +// Path to the framework directory containing the Headers/PrivateHeaders +// directories e.g. /Frameworks/Foundation.framework/ +llvm::StringRef HeadersParentDir; +// Subpath relative to the Headers or PrivateHeaders dir, e.g. NSObject.h +// Note: This is NOT relative to the `HeadersParentDir`. +llvm::StringRef HeaderSubpath; +// Whether this header is under the PrivateHeaders dir +bool IsPrivateHeader; + }; + + llvm::Optional + splitFrameworkHeaderPath(llvm::StringRef Path) { +using namespace llvm::sys; +path::reverse_iterator I = path::rbegin(Path); +path::reverse_iterator Prev = I; +path::reverse_iterator E = path::rend(Path); +while (I != E) { + if (*I == "Headers") { +FrameworkHeaderPath HeaderPath; +HeaderPath.HeadersParentDir = Path.substr(0, I - E); +HeaderPath.HeaderSubpath = Path.substr(Prev - E); +HeaderPath.IsPrivateHeader = false; +return HeaderPath; + } + if (*I == "PrivateHeaders") { +FrameworkHeaderPath HeaderPath; +HeaderPath.HeadersParentDir = Path.substr(0, I - E); +HeaderPath.HeaderSubpath = Path.substr(Prev - E); +HeaderPath.IsPrivateHeader = true; +return HeaderPath; + } + Prev = I; + ++I; +} +// Unexpected, must not be a framework header. +return llvm::None; + } + + // Frameworks typically have an umbrella header of the same name, e.g. + // instead of or + // instead of + // which should be used instead of directly + // importing the header. + llvm::Optional getFrameworkUmbrellaSpelling( + llvm::StringRef Framework, SrcMgr::CharacteristicKind HeadersDirKind, + HeaderSearch , FrameworkHeaderPath ) { +auto Res = CacheFrameworkToUmbrellaHeaderSpelling.try_emplace(Framework); +auto *CachedSpelling = >second; +if (!Res.second) { + return HeaderPath.IsPrivateHeader ? CachedSpelling->PrivateHeader +: CachedSpelling->PublicHeader; +} +bool IsSystem = isSystem(HeadersDirKind); +SmallString<256> UmbrellaPath(HeaderPath.HeadersParentDir); +llvm::sys::path::append(UmbrellaPath, "Headers", Framework + ".h"); + +llvm::vfs::Status Status; +auto StatErr = HS.getFileMgr().getNoncachedStatValue(UmbrellaPath, Status); +if (!StatErr) { + if (IsSystem) +CachedSpelling->PublicHeader = llvm::formatv("<{0}/{0}.h>", Framework); + else +CachedSpelling->PublicHeader = +llvm::formatv("\"{0}/{0}.h\"", Framework); +} + +UmbrellaPath = HeaderPath.HeadersParentDir; +llvm::sys::path::append(UmbrellaPath, "PrivateHeaders", +Framework + "_Private.h"); + +StatErr =
[clang-tools-extra] fb7ddd0 - Revert "[clangd] Properly compute framework-style include spelling"
Author: David Goldman Date: 2022-02-04T18:02:32-05:00 New Revision: fb7ddd0628f4894f9d14a2d1f84830607c5f946e URL: https://github.com/llvm/llvm-project/commit/fb7ddd0628f4894f9d14a2d1f84830607c5f946e DIFF: https://github.com/llvm/llvm-project/commit/fb7ddd0628f4894f9d14a2d1f84830607c5f946e.diff LOG: Revert "[clangd] Properly compute framework-style include spelling" This reverts commit 4dfd11324eb05d167392958c0f0f273cae6386c6 due to the failures on Linux CI: https://lab.llvm.org/buildbot/#/builders/188/builds/9296 Added: Modified: clang-tools-extra/clangd/index/SymbolCollector.cpp clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp Removed: diff --git a/clang-tools-extra/clangd/index/SymbolCollector.cpp b/clang-tools-extra/clangd/index/SymbolCollector.cpp index 7ae77d35ad7a1..3257041ffa0e3 100644 --- a/clang-tools-extra/clangd/index/SymbolCollector.cpp +++ b/clang-tools-extra/clangd/index/SymbolCollector.cpp @@ -183,13 +183,6 @@ const Decl *getRefContainer(const Decl *Enclosing, // including filename normalization, URI conversion etc. // Expensive checks are cached internally. class SymbolCollector::HeaderFileURICache { - struct FrameworkUmbrellaSpelling { -// Spelling for the public umbrella header, e.g. -llvm::Optional PublicHeader; -// Spelling for the private umbrella header, e.g. -// -llvm::Optional PrivateHeader; - }; // Weird double-indirect access to PP, which might not be ready yet when // HeaderFiles is created but will be by the time it's used. // (IndexDataConsumer::setPreprocessor can happen before or after initialize) @@ -200,9 +193,6 @@ class SymbolCollector::HeaderFileURICache { llvm::DenseMap CacheFEToURI; llvm::StringMap CachePathToURI; llvm::DenseMap CacheFIDToInclude; - llvm::StringMap CachePathToFrameworkSpelling; - llvm::StringMap - CacheFrameworkToUmbrellaHeaderSpelling; public: HeaderFileURICache(Preprocessor *, const SourceManager , @@ -259,125 +249,6 @@ class SymbolCollector::HeaderFileURICache { return R.first->second; } - struct FrameworkHeaderPath { -// Path to the framework directory containing the Headers/PrivateHeaders -// directories e.g. /Frameworks/Foundation.framework/ -llvm::StringRef HeadersParentDir; -// Subpath relative to the Headers or PrivateHeaders dir, e.g. NSObject.h -// Note: This is NOT relative to the `HeadersParentDir`. -llvm::StringRef HeaderSubpath; -// Whether this header is under the PrivateHeaders dir -bool IsPrivateHeader; - }; - - llvm::Optional - splitFrameworkHeaderPath(llvm::StringRef Path) { -using namespace llvm::sys; -path::reverse_iterator I = path::rbegin(Path); -path::reverse_iterator Prev = I; -path::reverse_iterator E = path::rend(Path); -while (I != E) { - if (*I == "Headers") { -FrameworkHeaderPath HeaderPath; -HeaderPath.HeadersParentDir = Path.substr(0, I - E); -HeaderPath.HeaderSubpath = Path.substr(Prev - E); -return HeaderPath; - } - if (*I == "PrivateHeaders") { -FrameworkHeaderPath HeaderPath; -HeaderPath.HeadersParentDir = Path.substr(0, I - E); -HeaderPath.HeaderSubpath = Path.substr(Prev - E); -HeaderPath.IsPrivateHeader = true; -return HeaderPath; - } - Prev = I; - ++I; -} -// Unexpected, must not be a framework header. -return llvm::None; - } - - // Frameworks typically have an umbrella header of the same name, e.g. - // instead of or - // instead of - // which should be used instead of directly - // importing the header. - llvm::Optional getFrameworkUmbrellaSpelling( - llvm::StringRef Framework, SrcMgr::CharacteristicKind HeadersDirKind, - HeaderSearch , FrameworkHeaderPath ) { -auto Res = CacheFrameworkToUmbrellaHeaderSpelling.try_emplace(Framework); -auto *CachedSpelling = >second; -if (!Res.second) { - return HeaderPath.IsPrivateHeader ? CachedSpelling->PrivateHeader -: CachedSpelling->PublicHeader; -} -bool IsSystem = isSystem(HeadersDirKind); -SmallString<256> UmbrellaPath(HeaderPath.HeadersParentDir); -llvm::sys::path::append(UmbrellaPath, "Headers", Framework + ".h"); - -llvm::vfs::Status Status; -auto StatErr = HS.getFileMgr().getNoncachedStatValue(UmbrellaPath, Status); -if (!StatErr) { - if (IsSystem) -CachedSpelling->PublicHeader = llvm::formatv("<{0}/{0}.h>", Framework); - else -CachedSpelling->PublicHeader = -llvm::formatv("\"{0}/{0}.h\"", Framework); -} - -UmbrellaPath = HeaderPath.HeadersParentDir; -llvm::sys::path::append(UmbrellaPath, "PrivateHeaders", -Framework + "_Private.h"); - -StatErr = HS.getFileMgr().getNoncachedStatValue(UmbrellaPath, Status); -if
[clang-tools-extra] 6abb70c - Attempt forward fix after 4dfd113
Author: David Goldman Date: 2022-02-04T17:47:38-05:00 New Revision: 6abb70c2d00812350153f4299a2491fdc3810ac3 URL: https://github.com/llvm/llvm-project/commit/6abb70c2d00812350153f4299a2491fdc3810ac3 DIFF: https://github.com/llvm/llvm-project/commit/6abb70c2d00812350153f4299a2491fdc3810ac3.diff LOG: Attempt forward fix after 4dfd113 If this doesn't work will just revert the change, can't seem to repro on macOS. Added: Modified: clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp Removed: diff --git a/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp b/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp index 1b934bd2342eb..7c1e1a96db003 100644 --- a/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp +++ b/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp @@ -675,14 +675,11 @@ TEST_F(SymbolCollectorTest, ObjCFrameworkIncludeHeader) { testPath("Frameworks/Foundation.framework/Headers/NSObject.h"), 0, llvm::MemoryBuffer::getMemBuffer(FrameworkHeader)); std::string PrivateFrameworkHeader = R"( -#import +#import @interface PrivateClass : NSObject @end )"; - InMemoryFileSystem->addFile( - testPath("Frameworks/Foundation.framework/Headers/NSObject.h"), 0, - llvm::MemoryBuffer::getMemBuffer(FrameworkHeader)); InMemoryFileSystem->addFile( testPath( "Frameworks/Foundation.framework/PrivateHeaders/NSObject+Private.h"), ___ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang-tools-extra] 4dfd113 - [clangd] Properly compute framework-style include spelling
Author: David Goldman Date: 2022-02-04T16:40:56-05:00 New Revision: 4dfd11324eb05d167392958c0f0f273cae6386c6 URL: https://github.com/llvm/llvm-project/commit/4dfd11324eb05d167392958c0f0f273cae6386c6 DIFF: https://github.com/llvm/llvm-project/commit/4dfd11324eb05d167392958c0f0f273cae6386c6.diff LOG: [clangd] Properly compute framework-style include spelling With this change, clangd now computes framework-style includes for framework headers at indexing time. Differential Revision: https://reviews.llvm.org/D117056 Added: Modified: clang-tools-extra/clangd/index/SymbolCollector.cpp clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp Removed: diff --git a/clang-tools-extra/clangd/index/SymbolCollector.cpp b/clang-tools-extra/clangd/index/SymbolCollector.cpp index 3257041ffa0e3..7ae77d35ad7a1 100644 --- a/clang-tools-extra/clangd/index/SymbolCollector.cpp +++ b/clang-tools-extra/clangd/index/SymbolCollector.cpp @@ -183,6 +183,13 @@ const Decl *getRefContainer(const Decl *Enclosing, // including filename normalization, URI conversion etc. // Expensive checks are cached internally. class SymbolCollector::HeaderFileURICache { + struct FrameworkUmbrellaSpelling { +// Spelling for the public umbrella header, e.g. +llvm::Optional PublicHeader; +// Spelling for the private umbrella header, e.g. +// +llvm::Optional PrivateHeader; + }; // Weird double-indirect access to PP, which might not be ready yet when // HeaderFiles is created but will be by the time it's used. // (IndexDataConsumer::setPreprocessor can happen before or after initialize) @@ -193,6 +200,9 @@ class SymbolCollector::HeaderFileURICache { llvm::DenseMap CacheFEToURI; llvm::StringMap CachePathToURI; llvm::DenseMap CacheFIDToInclude; + llvm::StringMap CachePathToFrameworkSpelling; + llvm::StringMap + CacheFrameworkToUmbrellaHeaderSpelling; public: HeaderFileURICache(Preprocessor *, const SourceManager , @@ -249,6 +259,125 @@ class SymbolCollector::HeaderFileURICache { return R.first->second; } + struct FrameworkHeaderPath { +// Path to the framework directory containing the Headers/PrivateHeaders +// directories e.g. /Frameworks/Foundation.framework/ +llvm::StringRef HeadersParentDir; +// Subpath relative to the Headers or PrivateHeaders dir, e.g. NSObject.h +// Note: This is NOT relative to the `HeadersParentDir`. +llvm::StringRef HeaderSubpath; +// Whether this header is under the PrivateHeaders dir +bool IsPrivateHeader; + }; + + llvm::Optional + splitFrameworkHeaderPath(llvm::StringRef Path) { +using namespace llvm::sys; +path::reverse_iterator I = path::rbegin(Path); +path::reverse_iterator Prev = I; +path::reverse_iterator E = path::rend(Path); +while (I != E) { + if (*I == "Headers") { +FrameworkHeaderPath HeaderPath; +HeaderPath.HeadersParentDir = Path.substr(0, I - E); +HeaderPath.HeaderSubpath = Path.substr(Prev - E); +return HeaderPath; + } + if (*I == "PrivateHeaders") { +FrameworkHeaderPath HeaderPath; +HeaderPath.HeadersParentDir = Path.substr(0, I - E); +HeaderPath.HeaderSubpath = Path.substr(Prev - E); +HeaderPath.IsPrivateHeader = true; +return HeaderPath; + } + Prev = I; + ++I; +} +// Unexpected, must not be a framework header. +return llvm::None; + } + + // Frameworks typically have an umbrella header of the same name, e.g. + // instead of or + // instead of + // which should be used instead of directly + // importing the header. + llvm::Optional getFrameworkUmbrellaSpelling( + llvm::StringRef Framework, SrcMgr::CharacteristicKind HeadersDirKind, + HeaderSearch , FrameworkHeaderPath ) { +auto Res = CacheFrameworkToUmbrellaHeaderSpelling.try_emplace(Framework); +auto *CachedSpelling = >second; +if (!Res.second) { + return HeaderPath.IsPrivateHeader ? CachedSpelling->PrivateHeader +: CachedSpelling->PublicHeader; +} +bool IsSystem = isSystem(HeadersDirKind); +SmallString<256> UmbrellaPath(HeaderPath.HeadersParentDir); +llvm::sys::path::append(UmbrellaPath, "Headers", Framework + ".h"); + +llvm::vfs::Status Status; +auto StatErr = HS.getFileMgr().getNoncachedStatValue(UmbrellaPath, Status); +if (!StatErr) { + if (IsSystem) +CachedSpelling->PublicHeader = llvm::formatv("<{0}/{0}.h>", Framework); + else +CachedSpelling->PublicHeader = +llvm::formatv("\"{0}/{0}.h\"", Framework); +} + +UmbrellaPath = HeaderPath.HeadersParentDir; +llvm::sys::path::append(UmbrellaPath, "PrivateHeaders", +Framework + "_Private.h"); + +StatErr = HS.getFileMgr().getNoncachedStatValue(UmbrellaPath, Status); +if
[clang] 9385ece - [HeaderSearch] Track framework name in LookupFile
Author: David Goldman Date: 2022-02-04T13:32:39-05:00 New Revision: 9385ece95a4a342ca4f4c46ea747f79d4ede9783 URL: https://github.com/llvm/llvm-project/commit/9385ece95a4a342ca4f4c46ea747f79d4ede9783 DIFF: https://github.com/llvm/llvm-project/commit/9385ece95a4a342ca4f4c46ea747f79d4ede9783.diff LOG: [HeaderSearch] Track framework name in LookupFile Previously, the Framework name was only set if the file came from a header mapped framework; now we'll always set the framework name if the file is in a framework. Differential Revision: https://reviews.llvm.org/D117830 Added: Modified: clang/lib/Lex/HeaderSearch.cpp clang/unittests/Lex/HeaderSearchTest.cpp Removed: diff --git a/clang/lib/Lex/HeaderSearch.cpp b/clang/lib/Lex/HeaderSearch.cpp index 39c125c395ef8..19e284f04b38c 100644 --- a/clang/lib/Lex/HeaderSearch.cpp +++ b/clang/lib/Lex/HeaderSearch.cpp @@ -1037,8 +1037,9 @@ Optional HeaderSearch::LookupFile( } } -// If this file is found in a header map and uses the framework style of -// includes, then this header is part of a framework we're building. +// Set the `Framework` info if this file is in a header map with framework +// style include spelling or found in a framework dir. The header map case +// is possible when building frameworks which use header maps. if (CurDir->isHeaderMap() && isAngled) { size_t SlashPos = Filename.find('/'); if (SlashPos != StringRef::npos) @@ -1046,6 +1047,11 @@ Optional HeaderSearch::LookupFile( getUniqueFrameworkName(StringRef(Filename.begin(), SlashPos)); if (CurDir->isIndexHeaderMap()) HFI.IndexHeaderMapHeader = 1; +} else if (CurDir->isFramework()) { + size_t SlashPos = Filename.find('/'); + if (SlashPos != StringRef::npos) +HFI.Framework = +getUniqueFrameworkName(StringRef(Filename.begin(), SlashPos)); } if (checkMSVCHeaderSearch(Diags, MSFE ? >getFileEntry() : nullptr, diff --git a/clang/unittests/Lex/HeaderSearchTest.cpp b/clang/unittests/Lex/HeaderSearchTest.cpp index 10ecbbd26659e..fbb5cb30754a7 100644 --- a/clang/unittests/Lex/HeaderSearchTest.cpp +++ b/clang/unittests/Lex/HeaderSearchTest.cpp @@ -186,6 +186,29 @@ TEST_F(HeaderSearchTest, NestedFramework) { "Sub/Sub.h"); } +TEST_F(HeaderSearchTest, HeaderFrameworkLookup) { + std::string HeaderPath = "/tmp/Frameworks/Foo.framework/Headers/Foo.h"; + addSystemFrameworkSearchDir("/tmp/Frameworks"); + VFS->addFile( + HeaderPath, 0, llvm::MemoryBuffer::getMemBufferCopy("", HeaderPath), + /*User=*/None, /*Group=*/None, llvm::sys::fs::file_type::regular_file); + + bool IsFrameworkFound = false; + auto FoundFile = Search.LookupFile( + "Foo/Foo.h", SourceLocation(), /*isAngled=*/true, /*FromDir=*/nullptr, + /*CurDir=*/nullptr, /*Includers=*/{}, /*SearchPath=*/nullptr, + /*RelativePath=*/nullptr, /*RequestingModule=*/nullptr, + /*SuggestedModule=*/nullptr, /*IsMapped=*/nullptr, ); + + EXPECT_TRUE(FoundFile.hasValue()); + EXPECT_TRUE(IsFrameworkFound); + auto = FoundFile.getValue(); + auto FI = Search.getExistingFileInfo(FE); + EXPECT_TRUE(FI); + EXPECT_TRUE(FI->IsValid); + EXPECT_EQ(FI->Framework.str(), "Foo"); +} + // Helper struct with null terminator character to make MemoryBuffer happy. template struct NullTerminatedFile : public FileTy { ___ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang] 0cf7e61 - [clang][HeaderSearch] Support framework includes in suggestPath...
Author: David Goldman Date: 2022-01-10T12:25:53-05:00 New Revision: 0cf7e61a42c7a08b0332204f1b60aa9fd1562089 URL: https://github.com/llvm/llvm-project/commit/0cf7e61a42c7a08b0332204f1b60aa9fd1562089 DIFF: https://github.com/llvm/llvm-project/commit/0cf7e61a42c7a08b0332204f1b60aa9fd1562089.diff LOG: [clang][HeaderSearch] Support framework includes in suggestPath... Clang will now search through the framework includes to identify the framework include path to a file, and then suggest a framework style include spelling for the file. Differential Revision: https://reviews.llvm.org/D115183 Added: Modified: clang/include/clang/Lex/HeaderSearch.h clang/lib/Lex/HeaderSearch.cpp clang/test/Modules/double-quotes.m clang/unittests/Lex/HeaderSearchTest.cpp Removed: diff --git a/clang/include/clang/Lex/HeaderSearch.h b/clang/include/clang/Lex/HeaderSearch.h index 0482bf6c053f..e056f009eae9 100644 --- a/clang/include/clang/Lex/HeaderSearch.h +++ b/clang/include/clang/Lex/HeaderSearch.h @@ -286,6 +286,12 @@ class HeaderSearch { /// Add an additional search path. void AddSearchPath(const DirectoryLookup , bool isAngled); + /// Add an additional system search path. + void AddSystemSearchPath(const DirectoryLookup ) { +SearchDirs.push_back(dir); +SearchDirsUsage.push_back(false); + } + /// Set the list of system header prefixes. void SetSystemHeaderPrefixes(ArrayRef> P) { SystemHeaderPrefixes.assign(P.begin(), P.end()); diff --git a/clang/lib/Lex/HeaderSearch.cpp b/clang/lib/Lex/HeaderSearch.cpp index f9d61ecef796..d4de4ed156b6 100644 --- a/clang/lib/Lex/HeaderSearch.cpp +++ b/clang/lib/Lex/HeaderSearch.cpp @@ -745,7 +745,8 @@ static const char *copyString(StringRef Str, llvm::BumpPtrAllocator ) { } static bool isFrameworkStylePath(StringRef Path, bool , - SmallVectorImpl ) { + SmallVectorImpl , + SmallVectorImpl ) { using namespace llvm::sys; path::const_iterator I = path::begin(Path); path::const_iterator E = path::end(Path); @@ -761,15 +762,22 @@ static bool isFrameworkStylePath(StringRef Path, bool , // and some other variations among these lines. int FoundComp = 0; while (I != E) { -if (*I == "Headers") +if (*I == "Headers") { ++FoundComp; -if (I->endswith(".framework")) { - FrameworkName.append(I->begin(), I->end()); - ++FoundComp; -} -if (*I == "PrivateHeaders") { +} else if (*I == "PrivateHeaders") { ++FoundComp; IsPrivateHeader = true; +} else if (I->endswith(".framework")) { + StringRef Name = I->drop_back(10); // Drop .framework + // Need to reset the strings and counter to support nested frameworks. + FrameworkName.clear(); + FrameworkName.append(Name.begin(), Name.end()); + IncludeSpelling.clear(); + IncludeSpelling.append(Name.begin(), Name.end()); + FoundComp = 1; +} else if (FoundComp >= 2) { + IncludeSpelling.push_back('/'); + IncludeSpelling.append(I->begin(), I->end()); } ++I; } @@ -784,20 +792,24 @@ diagnoseFrameworkInclude(DiagnosticsEngine , SourceLocation IncludeLoc, bool FoundByHeaderMap = false) { bool IsIncluderPrivateHeader = false; SmallString<128> FromFramework, ToFramework; - if (!isFrameworkStylePath(Includer, IsIncluderPrivateHeader, FromFramework)) + SmallString<128> FromIncludeSpelling, ToIncludeSpelling; + if (!isFrameworkStylePath(Includer, IsIncluderPrivateHeader, FromFramework, +FromIncludeSpelling)) return; bool IsIncludeePrivateHeader = false; - bool IsIncludeeInFramework = isFrameworkStylePath( - IncludeFE->getName(), IsIncludeePrivateHeader, ToFramework); + bool IsIncludeeInFramework = + isFrameworkStylePath(IncludeFE->getName(), IsIncludeePrivateHeader, + ToFramework, ToIncludeSpelling); if (!isAngled && !FoundByHeaderMap) { SmallString<128> NewInclude("<"); if (IsIncludeeInFramework) { - NewInclude += ToFramework.str().drop_back(10); // drop .framework - NewInclude += "/"; + NewInclude += ToIncludeSpelling; + NewInclude += ">"; +} else { + NewInclude += IncludeFilename; + NewInclude += ">"; } -NewInclude += IncludeFilename; -NewInclude += ">"; Diags.Report(IncludeLoc, diag::warn_quoted_include_in_framework_header) << IncludeFilename << FixItHint::CreateReplacement(IncludeLoc, NewInclude); @@ -1865,9 +1877,9 @@ std::string HeaderSearch::suggestPathToFileForDiagnostics( using namespace llvm::sys; unsigned BestPrefixLength = 0; - // Checks whether Dir and File shares a common prefix, if they do and that's - // the longest prefix we've seen so for it returns true and updates the - //