r355008 - Support framework import/include auto-completion
Author: dgoldman Date: Wed Feb 27 09:40:33 2019 New Revision: 355008 URL: http://llvm.org/viewvc/llvm-project?rev=355008&view=rev Log: Support framework import/include auto-completion Frameworks filesystem representations: UIKit.framework/Headers/%header% Framework import format: #import Thus the completion code must map the input format of to the path of UIKit.framework/Headers as well as strip the ".framework" suffix when auto-completing the framework name. Added: cfe/trunk/test/CodeCompletion/included-frameworks.m Modified: cfe/trunk/lib/Sema/SemaCodeComplete.cpp Modified: cfe/trunk/lib/Sema/SemaCodeComplete.cpp URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/Sema/SemaCodeComplete.cpp?rev=355008&r1=355007&r2=355008&view=diff == --- cfe/trunk/lib/Sema/SemaCodeComplete.cpp (original) +++ cfe/trunk/lib/Sema/SemaCodeComplete.cpp Wed Feb 27 09:40:33 2019 @@ -8404,10 +8404,23 @@ void Sema::CodeCompleteIncludedFile(llvm }; // Helper: scans IncludeDir for nice files, and adds results for each. - auto AddFilesFromIncludeDir = [&](StringRef IncludeDir, bool IsSystem) { + auto AddFilesFromIncludeDir = [&](StringRef IncludeDir, +bool IsSystem, +DirectoryLookup::LookupType_t LookupType) { llvm::SmallString<128> Dir = IncludeDir; -if (!NativeRelDir.empty()) - llvm::sys::path::append(Dir, NativeRelDir); +if (!NativeRelDir.empty()) { + if (LookupType == DirectoryLookup::LT_Framework) { +// For a framework dir, #include actually maps to +// a path of Foo.framework/Headers/Bar/. +auto Begin = llvm::sys::path::begin(NativeRelDir); +auto End = llvm::sys::path::end(NativeRelDir); + +llvm::sys::path::append(Dir, *Begin + ".framework", "Headers"); +llvm::sys::path::append(Dir, ++Begin, End); + } else { +llvm::sys::path::append(Dir, NativeRelDir); + } +} std::error_code EC; unsigned Count = 0; @@ -8418,6 +8431,12 @@ void Sema::CodeCompleteIncludedFile(llvm StringRef Filename = llvm::sys::path::filename(It->path()); switch (It->type()) { case llvm::sys::fs::file_type::directory_file: +// All entries in a framework directory must have a ".framework" suffix, +// but the suffix does not appear in the source code's include/import. +if (LookupType == DirectoryLookup::LT_Framework && +NativeRelDir.empty() && !Filename.consume_back(".framework")) + break; + AddCompletion(Filename, /*IsDirectory=*/true); break; case llvm::sys::fs::file_type::regular_file: @@ -8446,10 +8465,12 @@ void Sema::CodeCompleteIncludedFile(llvm // header maps are not (currently) enumerable. break; case DirectoryLookup::LT_NormalDir: - AddFilesFromIncludeDir(IncludeDir.getDir()->getName(), IsSystem); + AddFilesFromIncludeDir(IncludeDir.getDir()->getName(), IsSystem, + DirectoryLookup::LT_NormalDir); break; case DirectoryLookup::LT_Framework: - AddFilesFromIncludeDir(IncludeDir.getFrameworkDir()->getName(), IsSystem); + AddFilesFromIncludeDir(IncludeDir.getFrameworkDir()->getName(), IsSystem, + DirectoryLookup::LT_Framework); break; } }; @@ -8463,7 +8484,8 @@ void Sema::CodeCompleteIncludedFile(llvm // The current directory is on the include path for "quoted" includes. auto *CurFile = PP.getCurrentFileLexer()->getFileEntry(); if (CurFile && CurFile->getDir()) - AddFilesFromIncludeDir(CurFile->getDir()->getName(), false); + AddFilesFromIncludeDir(CurFile->getDir()->getName(), false, + DirectoryLookup::LT_NormalDir); for (const auto &D : make_range(S.quoted_dir_begin(), S.quoted_dir_end())) AddFilesFromDirLookup(D, false); } Added: cfe/trunk/test/CodeCompletion/included-frameworks.m URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/test/CodeCompletion/included-frameworks.m?rev=355008&view=auto == --- cfe/trunk/test/CodeCompletion/included-frameworks.m (added) +++ cfe/trunk/test/CodeCompletion/included-frameworks.m Wed Feb 27 09:40:33 2019 @@ -0,0 +1,29 @@ +// RUN: rm -rf %t && mkdir -p %t/Foo.framework/Headers/SubFolder && mkdir %t/NotAFramework/ +// RUN: touch %t/Foo.framework/Headers/Foo.h && touch %t/Foo.framework/Headers/FOOClass.h +// RUN: touch %t/Foo.framework/Headers/SubFolder/FOOInternal.h + +#import + +#import + +// Note: the run lines follow their respective tests, since line/column +// matter in this test. + +// Autocomplete frameworks without the ".framework" extension. +// +// RUN: %clang -fsyntax-only -F %t -Xclang -code-completion-at=%s:5:10 %s -o - | FileCheck -check-prefix=CHECK-1 %s +// CHECK-1-NOT:
r357720 - Special case ObjCPropertyDecl for printing
Author: dgoldman Date: Thu Apr 4 13:13:22 2019 New Revision: 357720 URL: http://llvm.org/viewvc/llvm-project?rev=357720&view=rev Log: Special case ObjCPropertyDecl for printing ObjCPropertyDecl should use the category interface as a context similar to what is done for methods. Previously category methods would be printed as `::property`; now they are printed as `Class::property`. Modified: cfe/trunk/lib/AST/Decl.cpp cfe/trunk/unittests/AST/NamedDeclPrinterTest.cpp Modified: cfe/trunk/lib/AST/Decl.cpp URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/AST/Decl.cpp?rev=357720&r1=357719&r2=357720&view=diff == --- cfe/trunk/lib/AST/Decl.cpp (original) +++ cfe/trunk/lib/AST/Decl.cpp Thu Apr 4 13:13:22 2019 @@ -1531,10 +1531,16 @@ void NamedDecl::printQualifiedName(raw_o const PrintingPolicy &P) const { const DeclContext *Ctx = getDeclContext(); - // For ObjC methods, look through categories and use the interface as context. + // For ObjC methods and properties, look through categories and use the + // interface as context. if (auto *MD = dyn_cast(this)) if (auto *ID = MD->getClassInterface()) Ctx = ID; + if (auto *PD = dyn_cast(this)) { +if (auto *MD = PD->getGetterMethodDecl()) + if (auto *ID = MD->getClassInterface()) +Ctx = ID; + } if (Ctx->isFunctionOrMethod()) { printName(OS); Modified: cfe/trunk/unittests/AST/NamedDeclPrinterTest.cpp URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/unittests/AST/NamedDeclPrinterTest.cpp?rev=357720&r1=357719&r2=357720&view=diff == --- cfe/trunk/unittests/AST/NamedDeclPrinterTest.cpp (original) +++ cfe/trunk/unittests/AST/NamedDeclPrinterTest.cpp Thu Apr 4 13:13:22 2019 @@ -115,6 +115,18 @@ PrintedWrittenNamedDeclCXX11Matches(Stri "input.cc"); } +::testing::AssertionResult +PrintedWrittenPropertyDeclObjCMatches(StringRef Code, StringRef DeclName, + StringRef ExpectedPrinted) { + std::vector Args{"-std=c++11", "-xobjective-c++"}; + return PrintedNamedDeclMatches(Code, + Args, + /*SuppressUnwrittenScope*/ true, + objcPropertyDecl(hasName(DeclName)).bind("id"), + ExpectedPrinted, + "input.m"); +} + } // unnamed namespace TEST(NamedDeclPrinter, TestNamespace1) { @@ -179,3 +191,31 @@ TEST(NamedDeclPrinter, TestLinkageInName "A", "X::A")); } + +TEST(NamedDeclPrinter, TestObjCClassExtension) { + ASSERT_TRUE(PrintedWrittenPropertyDeclObjCMatches( +R"( + @interface Obj + @end + + @interface Obj () + @property(nonatomic) int property; + @end +)", +"property", +"Obj::property")); +} + +TEST(NamedDeclPrinter, TestObjCClassExtensionWithGetter) { + ASSERT_TRUE(PrintedWrittenPropertyDeclObjCMatches( +R"( + @interface Obj + @end + + @interface Obj () + @property(nonatomic, getter=myPropertyGetter) int property; + @end +)", +"property", +"Obj::property")); +} ___ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
r357799 - Fix error in NamedDeclPrinterTest
Author: dgoldman Date: Fri Apr 5 12:17:24 2019 New Revision: 357799 URL: http://llvm.org/viewvc/llvm-project?rev=357799&view=rev Log: Fix error in NamedDeclPrinterTest Caused by D56924, shouldn't use raw string literals in macros. Differential Revision: https://reviews.llvm.org/D60326 Modified: cfe/trunk/unittests/AST/NamedDeclPrinterTest.cpp Modified: cfe/trunk/unittests/AST/NamedDeclPrinterTest.cpp URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/unittests/AST/NamedDeclPrinterTest.cpp?rev=357799&r1=357798&r2=357799&view=diff == --- cfe/trunk/unittests/AST/NamedDeclPrinterTest.cpp (original) +++ cfe/trunk/unittests/AST/NamedDeclPrinterTest.cpp Fri Apr 5 12:17:24 2019 @@ -193,29 +193,33 @@ TEST(NamedDeclPrinter, TestLinkageInName } TEST(NamedDeclPrinter, TestObjCClassExtension) { - ASSERT_TRUE(PrintedWrittenPropertyDeclObjCMatches( -R"( - @interface Obj - @end + const char *Code = +R"( + @interface Obj + @end - @interface Obj () - @property(nonatomic) int property; - @end -)", + @interface Obj () + @property(nonatomic) int property; + @end +)"; + ASSERT_TRUE(PrintedWrittenPropertyDeclObjCMatches( +Code, "property", "Obj::property")); } TEST(NamedDeclPrinter, TestObjCClassExtensionWithGetter) { - ASSERT_TRUE(PrintedWrittenPropertyDeclObjCMatches( -R"( - @interface Obj - @end + const char *Code = +R"( + @interface Obj + @end - @interface Obj () - @property(nonatomic, getter=myPropertyGetter) int property; - @end -)", + @interface Obj () + @property(nonatomic, getter=myPropertyGetter) int property; + @end +)"; + ASSERT_TRUE(PrintedWrittenPropertyDeclObjCMatches( +Code, "property", "Obj::property")); } ___ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
r357937 - Clean up ObjCPropertyDecl printing
Author: dgoldman Date: Mon Apr 8 12:52:45 2019 New Revision: 357937 URL: http://llvm.org/viewvc/llvm-project?rev=357937&view=rev Log: Clean up ObjCPropertyDecl printing Summary: - `@property(attr, attr2)` instead of `@property ( attr,attr2 )`. - Change priority of attributes (see code/comments inline). - Support for printing weak and unsafe_unretained attributes. Subscribers: arphaman, jfb, cfe-commits Tags: #clang Differential Revision: https://reviews.llvm.org/D57965 Added: cfe/trunk/test/AST/ast-print-objc-property.m Modified: cfe/trunk/lib/AST/DeclPrinter.cpp cfe/trunk/test/Index/comment-objc-decls.m cfe/trunk/test/Index/comment-unqualified-objc-pointer.m cfe/trunk/test/PCH/chain-remap-types.m Modified: cfe/trunk/lib/AST/DeclPrinter.cpp URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/AST/DeclPrinter.cpp?rev=357937&r1=357936&r2=357937&view=diff == --- cfe/trunk/lib/AST/DeclPrinter.cpp (original) +++ cfe/trunk/lib/AST/DeclPrinter.cpp Mon Apr 8 12:52:45 2019 @@ -1391,6 +1391,13 @@ void DeclPrinter::VisitObjCCompatibleAli /// PrintObjCPropertyDecl - print a property declaration. /// +/// Print attributes in the following order: +/// - class +/// - nonatomic | atomic +/// - assign | retain | strong | copy | weak | unsafe_unretained +/// - readwrite | readonly +/// - getter & setter +/// - nullability void DeclPrinter::VisitObjCPropertyDecl(ObjCPropertyDecl *PDecl) { if (PDecl->getPropertyImplementation() == ObjCPropertyDecl::Required) Out << "@required\n"; @@ -1402,58 +1409,69 @@ void DeclPrinter::VisitObjCPropertyDecl( Out << "@property"; if (PDecl->getPropertyAttributes() != ObjCPropertyDecl::OBJC_PR_noattr) { bool first = true; -Out << " ("; -if (PDecl->getPropertyAttributes() & -ObjCPropertyDecl::OBJC_PR_readonly) { - Out << (first ? ' ' : ',') << "readonly"; +Out << "("; +if (PDecl->getPropertyAttributes() & ObjCPropertyDecl::OBJC_PR_class) { + Out << (first ? "" : ", ") << "class"; first = false; } -if (PDecl->getPropertyAttributes() & ObjCPropertyDecl::OBJC_PR_getter) { - Out << (first ? ' ' : ',') << "getter = "; - PDecl->getGetterName().print(Out); +if (PDecl->getPropertyAttributes() & +ObjCPropertyDecl::OBJC_PR_nonatomic) { + Out << (first ? "" : ", ") << "nonatomic"; first = false; } -if (PDecl->getPropertyAttributes() & ObjCPropertyDecl::OBJC_PR_setter) { - Out << (first ? ' ' : ',') << "setter = "; - PDecl->getSetterName().print(Out); +if (PDecl->getPropertyAttributes() & +ObjCPropertyDecl::OBJC_PR_atomic) { + Out << (first ? "" : ", ") << "atomic"; first = false; } if (PDecl->getPropertyAttributes() & ObjCPropertyDecl::OBJC_PR_assign) { - Out << (first ? ' ' : ',') << "assign"; - first = false; -} - -if (PDecl->getPropertyAttributes() & -ObjCPropertyDecl::OBJC_PR_readwrite) { - Out << (first ? ' ' : ',') << "readwrite"; + Out << (first ? "" : ", ") << "assign"; first = false; } - if (PDecl->getPropertyAttributes() & ObjCPropertyDecl::OBJC_PR_retain) { - Out << (first ? ' ' : ',') << "retain"; + Out << (first ? "" : ", ") << "retain"; first = false; } if (PDecl->getPropertyAttributes() & ObjCPropertyDecl::OBJC_PR_strong) { - Out << (first ? ' ' : ',') << "strong"; + Out << (first ? "" : ", ") << "strong"; first = false; } - if (PDecl->getPropertyAttributes() & ObjCPropertyDecl::OBJC_PR_copy) { - Out << (first ? ' ' : ',') << "copy"; + Out << (first ? "" : ", ") << "copy"; + first = false; +} +if (PDecl->getPropertyAttributes() & ObjCPropertyDecl::OBJC_PR_weak) { + Out << (first ? "" : ", ") << "weak"; + first = false; +} +if (PDecl->getPropertyAttributes() +& ObjCPropertyDecl::OBJC_PR_unsafe_unretained) { + Out << (first ? "" : ", ") << "unsafe_unretained"; first = false; } if (PDecl->getPropertyAttributes() & -ObjCPropertyDecl::OBJC_PR_nonatomic) { - Out << (first ? ' ' : ',') << "nonatomic"; +ObjCPropertyDecl::OBJC_PR_readwrite) { + Out << (first ? "" : ", ") << "readwrite"; first = false; } if (PDecl->getPropertyAttributes() & -ObjCPropertyDecl::OBJC_PR_atomic) { - Out << (first ? ' ' : ',') << "atomic"; +ObjCPropertyDecl::OBJC_PR_readonly) { + Out << (first ? "" : ", ") << "readonly"; + first = false; +} + +if (PDecl->getPropertyAttributes() & ObjCPropertyDecl::OBJC_PR_getter) { + Out << (first ? "" : ", ") << "getter = "; + PDecl->getGetterName().print(Out); + first = false; +} +if (PDecl->getPropertyAttributes() & ObjCPropertyDecl::OBJC_PR_setter) { + Out << (first ? "" : ", ") << "setter = "; + PDecl->getSetterName().print(
r371859 - [Sema][Typo Correction] Fix potential infite loop on ambiguity checks
Author: dgoldman Date: Fri Sep 13 07:43:24 2019 New Revision: 371859 URL: http://llvm.org/viewvc/llvm-project?rev=371859&view=rev Log: [Sema][Typo Correction] Fix potential infite loop on ambiguity checks Summary: This fixes a bug introduced in D62648, where Clang could infinite loop if it became stuck on a single TypoCorrection when it was supposed to be testing ambiguous corrections. Although not a common case, it could happen if there are multiple possible corrections with the same edit distance. The fix is simply to wipe the TypoExpr from the `TransformCache` so that the call to `TransformTypoExpr` doesn't use the `CachedEntry`. Reviewers: rsmith Subscribers: cfe-commits Tags: #clang Differential Revision: https://reviews.llvm.org/D67515 Added: cfe/trunk/test/Sema/typo-correction-ambiguity.cpp Modified: cfe/trunk/lib/Sema/SemaExprCXX.cpp Modified: cfe/trunk/lib/Sema/SemaExprCXX.cpp URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/Sema/SemaExprCXX.cpp?rev=371859&r1=371858&r2=371859&view=diff == --- cfe/trunk/lib/Sema/SemaExprCXX.cpp (original) +++ cfe/trunk/lib/Sema/SemaExprCXX.cpp Fri Sep 13 07:43:24 2019 @@ -7755,6 +7755,10 @@ class TransformTypos : public TreeTransf TypoCorrection TC = SemaRef.getTypoExprState(TE).Consumer->peekNextCorrection(); TypoCorrection Next; do { + // Fetch the next correction by erasing the typo from the cache and calling + // `TryTransform` which will iterate through corrections in + // `TransformTypoExpr`. + TransformCache.erase(TE); ExprResult AmbigRes = CheckForRecursiveTypos(TryTransform(E), IsAmbiguous); if (!AmbigRes.isInvalid() || IsAmbiguous) { Added: cfe/trunk/test/Sema/typo-correction-ambiguity.cpp URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/test/Sema/typo-correction-ambiguity.cpp?rev=371859&view=auto == --- cfe/trunk/test/Sema/typo-correction-ambiguity.cpp (added) +++ cfe/trunk/test/Sema/typo-correction-ambiguity.cpp Fri Sep 13 07:43:24 2019 @@ -0,0 +1,29 @@ +// RUN: %clang_cc1 -fsyntax-only -verify %s + +// Check the following typo correction behavior in namespaces: +// - no typos are diagnosed when an expression has ambiguous (multiple) corrections +// - proper iteration through multiple potentially ambiguous corrections + +namespace AmbiguousCorrection +{ + void method_Bar(); + void method_Foo(); + void method_Zoo(); +}; + +void testAmbiguousNoSuggestions() +{ + AmbiguousCorrection::method_Ace(); // expected-error {{no member named 'method_Ace' in namespace 'AmbiguousCorrection'}} +} + +namespace MultipleCorrectionsButNotAmbiguous +{ + int PrefixType_Name(int value); // expected-note {{'PrefixType_Name' declared here}} + int PrefixType_MIN(); + int PrefixType_MAX(); +}; + +int testMultipleCorrectionsButNotAmbiguous() { + int val = MultipleCorrectionsButNotAmbiguous::PrefixType_Enum(0); // expected-error {{no member named 'PrefixType_Enum' in namespace 'MultipleCorrectionsButNotAmbiguous'; did you mean 'PrefixType_Name'?}} + return val; +} ___ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
r375453 - [clang-fuzzer] Add new fuzzer target for Objective-C
Author: dgoldman Date: Mon Oct 21 13:45:02 2019 New Revision: 375453 URL: http://llvm.org/viewvc/llvm-project?rev=375453&view=rev Log: [clang-fuzzer] Add new fuzzer target for Objective-C Summary: - Similar to that of `clang-fuzzer` itself but instead only targets Objective-C source files via cc1 - Also adds an example corpus directory containing some input for Objective-C Subscribers: mgorny, jfb, cfe-commits Tags: #clang Differential Revision: https://reviews.llvm.org/D69171 Added: cfe/trunk/tools/clang-fuzzer/ClangObjectiveCFuzzer.cpp - copied, changed from r375167, cfe/trunk/tools/clang-fuzzer/ClangFuzzer.cpp cfe/trunk/tools/clang-fuzzer/corpus_examples/ cfe/trunk/tools/clang-fuzzer/corpus_examples/objc/ cfe/trunk/tools/clang-fuzzer/corpus_examples/objc/BasicClass.m cfe/trunk/tools/clang-fuzzer/corpus_examples/objc/ClassCategory.m cfe/trunk/tools/clang-fuzzer/corpus_examples/objc/ClassExtension.m cfe/trunk/tools/clang-fuzzer/corpus_examples/objc/SharedInstance.m Modified: cfe/trunk/tools/clang-fuzzer/CMakeLists.txt cfe/trunk/tools/clang-fuzzer/ClangFuzzer.cpp cfe/trunk/tools/clang-fuzzer/Dockerfile cfe/trunk/tools/clang-fuzzer/README.txt cfe/trunk/tools/clang-fuzzer/handle-cxx/handle_cxx.cpp cfe/trunk/tools/clang-fuzzer/handle-cxx/handle_cxx.h Modified: cfe/trunk/tools/clang-fuzzer/CMakeLists.txt URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/tools/clang-fuzzer/CMakeLists.txt?rev=375453&r1=375452&r2=375453&view=diff == --- cfe/trunk/tools/clang-fuzzer/CMakeLists.txt (original) +++ cfe/trunk/tools/clang-fuzzer/CMakeLists.txt Mon Oct 21 13:45:02 2019 @@ -12,6 +12,7 @@ endif() # Needed by LLVM's CMake checks because this file defines multiple targets. set(LLVM_OPTIONAL_SOURCES ClangFuzzer.cpp + ClangObjectiveCFuzzer.cpp DummyClangFuzzer.cpp ExampleClangProtoFuzzer.cpp ExampleClangLoopProtoFuzzer.cpp @@ -119,4 +120,16 @@ target_link_libraries(clang-fuzzer PRIVATE ${LLVM_LIB_FUZZING_ENGINE} clangHandleCXX + ) + +add_clang_executable(clang-objc-fuzzer + EXCLUDE_FROM_ALL + ${DUMMY_MAIN} + ClangObjectiveCFuzzer.cpp + ) + +target_link_libraries(clang-objc-fuzzer + PRIVATE + ${LLVM_LIB_FUZZING_ENGINE} + clangHandleCXX ) Modified: cfe/trunk/tools/clang-fuzzer/ClangFuzzer.cpp URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/tools/clang-fuzzer/ClangFuzzer.cpp?rev=375453&r1=375452&r2=375453&view=diff == --- cfe/trunk/tools/clang-fuzzer/ClangFuzzer.cpp (original) +++ cfe/trunk/tools/clang-fuzzer/ClangFuzzer.cpp Mon Oct 21 13:45:02 2019 @@ -20,6 +20,6 @@ extern "C" int LLVMFuzzerInitialize(int extern "C" int LLVMFuzzerTestOneInput(uint8_t *data, size_t size) { std::string s((const char *)data, size); - HandleCXX(s, {"-O2"}); + HandleCXX(s, "./test.cc", {"-O2"}); return 0; } Copied: cfe/trunk/tools/clang-fuzzer/ClangObjectiveCFuzzer.cpp (from r375167, cfe/trunk/tools/clang-fuzzer/ClangFuzzer.cpp) URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/tools/clang-fuzzer/ClangObjectiveCFuzzer.cpp?p2=cfe/trunk/tools/clang-fuzzer/ClangObjectiveCFuzzer.cpp&p1=cfe/trunk/tools/clang-fuzzer/ClangFuzzer.cpp&r1=375167&r2=375453&rev=375453&view=diff == --- cfe/trunk/tools/clang-fuzzer/ClangFuzzer.cpp (original) +++ cfe/trunk/tools/clang-fuzzer/ClangObjectiveCFuzzer.cpp Mon Oct 21 13:45:02 2019 @@ -1,4 +1,4 @@ -//===-- ClangFuzzer.cpp - Fuzz Clang --===// +//===-- ClangObjectiveCFuzzer.cpp - Fuzz Clang ===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. @@ -7,8 +7,8 @@ //===--===// /// /// \file -/// This file implements a function that runs Clang on a single -/// input. This function is then linked into the Fuzzer library. +/// This file implements a function that runs Clang on a single Objective-C +/// input. This function is then linked into the Fuzzer library. /// //===--===// @@ -16,10 +16,9 @@ using namespace clang_fuzzer; -extern "C" int LLVMFuzzerInitialize(int *argc, char ***argv) { return 0; } - extern "C" int LLVMFuzzerTestOneInput(uint8_t *data, size_t size) { - std::string s((const char *)data, size); - HandleCXX(s, {"-O2"}); + std::string s(reinterpret_cast(data), size); + HandleCXX(s, "./test.m", {"-O2"}); return 0; } + Modified: cfe/trunk/tools/clang-fuzzer/Dockerfile URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/tools/clang-fuzzer/Dockerfile?rev=375453&r1=375452&r2=375453&view=diff =
[clang] 7a2b704 - [Sema][Typo Correction] Fix another infinite loop on ambiguity
Author: David Goldman Date: 2019-10-25T13:20:27-04:00 New Revision: 7a2b704bf0cf65f9eb46fe3668a83b75aa2d80a6 URL: https://github.com/llvm/llvm-project/commit/7a2b704bf0cf65f9eb46fe3668a83b75aa2d80a6 DIFF: https://github.com/llvm/llvm-project/commit/7a2b704bf0cf65f9eb46fe3668a83b75aa2d80a6.diff LOG: [Sema][Typo Correction] Fix another infinite loop on ambiguity See also: D67515 - For the given call expression we would end up repeatedly trying to transform the same expression over and over again - Fix is to keep the old TransformCache when checking for ambiguity Differential Revision: https://reviews.llvm.org/D69060 Added: clang/test/Sema/typo-correction-ambiguity.c Modified: clang/lib/Sema/SemaExprCXX.cpp Removed: diff --git a/clang/lib/Sema/SemaExprCXX.cpp b/clang/lib/Sema/SemaExprCXX.cpp index 18efd8335d9d..4fdd15bf466f 100644 --- a/clang/lib/Sema/SemaExprCXX.cpp +++ b/clang/lib/Sema/SemaExprCXX.cpp @@ -7780,8 +7780,9 @@ class TransformTypos : public TreeTransform { // If we found a valid result, double check to make sure it's not ambiguous. if (!IsAmbiguous && !Res.isInvalid() && !AmbiguousTypoExprs.empty()) { - auto SavedTransformCache = std::move(TransformCache); - TransformCache.clear(); + auto SavedTransformCache = + llvm::SmallDenseMap(TransformCache); + // Ensure none of the TypoExprs have multiple typo correction candidates // with the same edit length that pass all the checks and filters. while (!AmbiguousTypoExprs.empty()) { diff --git a/clang/test/Sema/typo-correction-ambiguity.c b/clang/test/Sema/typo-correction-ambiguity.c new file mode 100644 index ..bebbf25ce291 --- /dev/null +++ b/clang/test/Sema/typo-correction-ambiguity.c @@ -0,0 +1,14 @@ +// RUN: %clang_cc1 -fsyntax-only -verify %s + +// Check the following typo correction behavior in C: +// - no typos are diagnosed when a call expression has ambiguous (multiple) corrections + +int v_63; + +void v_2_0(int v_452, int v_454) {} + +int v_3_0() { + for (int v_345 = 0 ; v_63;) + v_2_0(v_195, // expected-error {{use of undeclared identifier 'v_195'}} + v_231); // expected-error {{use of undeclared identifier 'v_231'}} +} ___ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[Differential] D84067: Fix issue in typo handling which could lead clang to hang
This revision was landed with ongoing or failed builds. This revision was automatically updated to reflect the committed changes. Closed by commit rGdde98c82c0ad: Fix issue in typo handling which could lead clang to hang (authored by dgoldman). Repository: rG LLVM Github Monorepo CHANGES SINCE LAST ACTION https://reviews.llvm.org/D84067/new/ https://reviews.llvm.org/D84067 Files: clang/include/clang/Sema/SemaInternal.h clang/lib/Sema/SemaExprCXX.cpp clang/test/Sema/typo-correction-no-hang.cpp clang/test/Sema/typo-correction-recursive.cpp Index: clang/test/Sema/typo-correction-recursive.cpp === --- clang/test/Sema/typo-correction-recursive.cpp +++ clang/test/Sema/typo-correction-recursive.cpp @@ -118,3 +118,15 @@ asDeepASItGet(). functionE(); } + +struct Dog { + int age; //expected-note{{'age' declared here}} + int size; //expected-note{{'size' declared here}} +}; + +int from_dog_years(int DogYears, int DogSize); +int get_dog_years() { + struct Dog doggo; + return from_dog_years(doggo.agee, //expected-error{{no member named 'agee' in 'Dog'; did you mean 'age'}} +doggo.sizee); //expected-error{{no member named 'sizee' in 'Dog'; did you mean 'size'}} +} Index: clang/test/Sema/typo-correction-no-hang.cpp === --- /dev/null +++ clang/test/Sema/typo-correction-no-hang.cpp @@ -0,0 +1,40 @@ +// RUN: %clang_cc1 -fsyntax-only -verify %s + +// From `test/Sema/typo-correction.c` but for C++ since the behavior varies +// between the two languages. +struct rdar38642201 { + int fieldName; +}; + +void rdar38642201_callee(int x, int y); +void rdar38642201_caller() { + struct rdar38642201 structVar; + rdar38642201_callee( + structVar1.fieldName1.member1, //expected-error{{use of undeclared identifier 'structVar1'}} + structVar2.fieldName2.member2); //expected-error{{use of undeclared identifier 'structVar2'}} +} + +// Similar reproducer. +class A { +public: + int minut() const = delete; + int hour() const = delete; + + int longit() const; //expected-note{{'longit' declared here}} + int latit() const; +}; + +class B { +public: + A depar() const { return A(); } +}; + +int Foo(const B &b) { + return b.deparT().hours() * 60 + //expected-error{{no member named 'deparT' in 'B'}} + b.deparT().minutes(); //expected-error{{no member named 'deparT' in 'B'}} +} + +int Bar(const B &b) { + return b.depar().longitude() + //expected-error{{no member named 'longitude' in 'A'; did you mean 'longit'?}} + b.depar().latitude(); //expected-error{{no member named 'latitude' in 'A'}} +} Index: clang/lib/Sema/SemaExprCXX.cpp === --- clang/lib/Sema/SemaExprCXX.cpp +++ clang/lib/Sema/SemaExprCXX.cpp @@ -7980,19 +7980,26 @@ } } - /// If corrections for the first TypoExpr have been exhausted for a - /// given combination of the other TypoExprs, retry those corrections against - /// the next combination of substitutions for the other TypoExprs by advancing - /// to the next potential correction of the second TypoExpr. For the second - /// and subsequent TypoExprs, if its stream of corrections has been exhausted, - /// the stream is reset and the next TypoExpr's stream is advanced by one (a - /// TypoExpr's correction stream is advanced by removing the TypoExpr from the - /// TransformCache). Returns true if there is still any untried combinations - /// of corrections. + /// Try to advance the typo correction state of the first unfinished TypoExpr. + /// We allow advancement of the correction stream by removing it from the + /// TransformCache which allows `TransformTypoExpr` to advance during the + /// next transformation attempt. + /// + /// Any substitution attempts for the previous TypoExprs (which must have been + /// finished) will need to be retried since it's possible that they will now + /// be invalid given the latest advancement. + /// + /// We need to be sure that we're making progress - it's possible that the + /// tree is so malformed that the transform never makes it to the + /// `TransformTypoExpr`. + /// + /// Returns true if there are any untried correction combinations. bool CheckAndAdvanceTypoExprCorrectionStreams() { for (auto TE : TypoExprs) { auto &State = SemaRef.getTypoExprState(TE); TransformCache.erase(TE); + if (!State.Consumer->hasMadeAnyCorrectionProgress()) +return false; if (!State.Consumer->finished()) return true; State.Consumer->resetCorrectionStream(); Index: clang/include/clang/Sema/SemaInternal.h === --- clang/include/clang/Sema/SemaInternal.h +++ clang/include/clang/Sema/SemaInternal.h @@ -168,6 +168,11 @@ return TC; } + /// In the case of deeply invalid ex
[clang] dde98c8 - Fix issue in typo handling which could lead clang to hang
Author: David Goldman Date: 2020-07-20T11:42:11-04:00 New Revision: dde98c82c0ad02410229e7e5c9efcbb0ab42a995 URL: https://github.com/llvm/llvm-project/commit/dde98c82c0ad02410229e7e5c9efcbb0ab42a995 DIFF: https://github.com/llvm/llvm-project/commit/dde98c82c0ad02410229e7e5c9efcbb0ab42a995.diff LOG: Fix issue in typo handling which could lead clang to hang Summary: We need to detect when certain TypoExprs are not being transformed due to invalid trees, otherwise we risk endlessly trying to fix it. Reviewers: rsmith Subscribers: cfe-commits Tags: #clang Differential Revision: https://reviews.llvm.org/D84067 Added: clang/test/Sema/typo-correction-no-hang.cpp Modified: clang/include/clang/Sema/SemaInternal.h clang/lib/Sema/SemaExprCXX.cpp clang/test/Sema/typo-correction-recursive.cpp Removed: diff --git a/clang/include/clang/Sema/SemaInternal.h b/clang/include/clang/Sema/SemaInternal.h index cdaf7b70a92f..842eec099540 100644 --- a/clang/include/clang/Sema/SemaInternal.h +++ b/clang/include/clang/Sema/SemaInternal.h @@ -168,6 +168,11 @@ class TypoCorrectionConsumer : public VisibleDeclConsumer { return TC; } + /// In the case of deeply invalid expressions, `getNextCorrection()` will + /// never be called since the transform never makes progress. If we don't + /// detect this we risk trying to correct typos forever. + bool hasMadeAnyCorrectionProgress() const { return CurrentTCIndex != 0; } + /// Reset the consumer's position in the stream of viable corrections /// (i.e. getNextCorrection() will return each of the previously returned /// corrections in order before returning any new corrections). diff --git a/clang/lib/Sema/SemaExprCXX.cpp b/clang/lib/Sema/SemaExprCXX.cpp index 001fabff96ff..10da9595c658 100644 --- a/clang/lib/Sema/SemaExprCXX.cpp +++ b/clang/lib/Sema/SemaExprCXX.cpp @@ -7980,19 +7980,26 @@ class TransformTypos : public TreeTransform { } } - /// If corrections for the first TypoExpr have been exhausted for a - /// given combination of the other TypoExprs, retry those corrections against - /// the next combination of substitutions for the other TypoExprs by advancing - /// to the next potential correction of the second TypoExpr. For the second - /// and subsequent TypoExprs, if its stream of corrections has been exhausted, - /// the stream is reset and the next TypoExpr's stream is advanced by one (a - /// TypoExpr's correction stream is advanced by removing the TypoExpr from the - /// TransformCache). Returns true if there is still any untried combinations - /// of corrections. + /// Try to advance the typo correction state of the first unfinished TypoExpr. + /// We allow advancement of the correction stream by removing it from the + /// TransformCache which allows `TransformTypoExpr` to advance during the + /// next transformation attempt. + /// + /// Any substitution attempts for the previous TypoExprs (which must have been + /// finished) will need to be retried since it's possible that they will now + /// be invalid given the latest advancement. + /// + /// We need to be sure that we're making progress - it's possible that the + /// tree is so malformed that the transform never makes it to the + /// `TransformTypoExpr`. + /// + /// Returns true if there are any untried correction combinations. bool CheckAndAdvanceTypoExprCorrectionStreams() { for (auto TE : TypoExprs) { auto &State = SemaRef.getTypoExprState(TE); TransformCache.erase(TE); + if (!State.Consumer->hasMadeAnyCorrectionProgress()) +return false; if (!State.Consumer->finished()) return true; State.Consumer->resetCorrectionStream(); diff --git a/clang/test/Sema/typo-correction-no-hang.cpp b/clang/test/Sema/typo-correction-no-hang.cpp new file mode 100644 index ..3c591645be25 --- /dev/null +++ b/clang/test/Sema/typo-correction-no-hang.cpp @@ -0,0 +1,40 @@ +// RUN: %clang_cc1 -fsyntax-only -verify %s + +// From `test/Sema/typo-correction.c` but for C++ since the behavior varies +// between the two languages. +struct rdar38642201 { + int fieldName; +}; + +void rdar38642201_callee(int x, int y); +void rdar38642201_caller() { + struct rdar38642201 structVar; + rdar38642201_callee( + structVar1.fieldName1.member1, //expected-error{{use of undeclared identifier 'structVar1'}} + structVar2.fieldName2.member2); //expected-error{{use of undeclared identifier 'structVar2'}} +} + +// Similar reproducer. +class A { +public: + int minut() const = delete; + int hour() const = delete; + + int longit() const; //expected-note{{'longit' declared here}} + int latit() const; +}; + +class B { +public: + A depar() const { return A(); } +}; + +int Foo(const B &b) { + return b.deparT().hours() * 60 + //expected-error{{no member named 'deparT' in 'B'}} + b.deparT().minutes
[clang] 2ef65ad - [Sema][CodeComplete][ObjC] Don't include arrow/dot fixits
Author: David Goldman Date: 2020-06-08T12:46:00-04:00 New Revision: 2ef65adb6f9dbebdd250dc6210e813711fb478d9 URL: https://github.com/llvm/llvm-project/commit/2ef65adb6f9dbebdd250dc6210e813711fb478d9 DIFF: https://github.com/llvm/llvm-project/commit/2ef65adb6f9dbebdd250dc6210e813711fb478d9.diff LOG: [Sema][CodeComplete][ObjC] Don't include arrow/dot fixits Summary: Exempt ObjC from arrow/dot fixits since this has limited value for Objective-C, where properties (referenced by dot syntax) are normally backed by ivars (referenced by arrow syntax). In addition, the current implementation doesn't properly mark the fix it condition for Objective-C. This was initially added in https://reviews.llvm.org/D41537 for C++ and then later C, don't believe the Objective-C changes were intentional. Reviewers: sammccall, yvvan Subscribers: jfb, cfe-commits Tags: #clang Differential Revision: https://reviews.llvm.org/D81263 Added: clang/test/CodeCompletion/objc-member-access.m Modified: clang/lib/Sema/SemaCodeComplete.cpp Removed: diff --git a/clang/lib/Sema/SemaCodeComplete.cpp b/clang/lib/Sema/SemaCodeComplete.cpp index 0aad0568714f..5539aef917d0 100644 --- a/clang/lib/Sema/SemaCodeComplete.cpp +++ b/clang/lib/Sema/SemaCodeComplete.cpp @@ -5195,7 +5195,12 @@ void Sema::CodeCompleteMemberReferenceExpr(Scope *S, Expr *Base, Results.AddResult(std::move(Result)); } } else if (!IsArrow && BaseType->isObjCObjectPointerType()) { - // Objective-C property reference. + // Objective-C property reference. Bail if we're performing fix-it code + // completion since Objective-C properties are normally backed by ivars, + // most Objective-C fix-its here would have little value. + if (AccessOpFixIt.hasValue()) { +return false; + } AddedPropertiesSet AddedProperties; if (const ObjCObjectPointerType *ObjCPtr = @@ -5215,7 +5220,12 @@ void Sema::CodeCompleteMemberReferenceExpr(Scope *S, Expr *Base, /*InOriginalClass*/ false); } else if ((IsArrow && BaseType->isObjCObjectPointerType()) || (!IsArrow && BaseType->isObjCObjectType())) { - // Objective-C instance variable access. + // Objective-C instance variable access. Bail if we're performing fix-it + // code completion since Objective-C properties are normally backed by + // ivars, most Objective-C fix-its here would have little value. + if (AccessOpFixIt.hasValue()) { +return false; + } ObjCInterfaceDecl *Class = nullptr; if (const ObjCObjectPointerType *ObjCPtr = BaseType->getAs()) diff --git a/clang/test/CodeCompletion/objc-member-access.m b/clang/test/CodeCompletion/objc-member-access.m new file mode 100644 index ..b3dbd85af91b --- /dev/null +++ b/clang/test/CodeCompletion/objc-member-access.m @@ -0,0 +1,22 @@ +// Note: the run lines follow their respective tests, since line/column +// matter in this test. + +@interface TypeWithPropertiesBackedByIvars { + int _bar; + int _foo; +} +@property(nonatomic) int foo; +@property(nonatomic) int bar; +@end + +int getFoo(id object) { + TypeWithPropertiesBackedByIvars *model = (TypeWithPropertiesBackedByIvars *)object; + int foo = model.; + return foo; +} + +// RUN: %clang_cc1 -fsyntax-only -code-completion-with-fixits -code-completion-at=%s:14:19 %s -o - | FileCheck -check-prefix=CHECK-CC1 %s +// CHECK-CC1-NOT: [#int#]_bar +// CHECK-CC1-NOT: [#int#]_foo +// CHECK-CC1: [#int#]bar +// CHECK-CC1: [#int#]foo ___ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang] ea201e8 - [AST][ObjC] Fix crash when printing invalid objc categories
Author: David Goldman Date: 2020-07-10T15:35:14-04:00 New Revision: ea201e83e292f39c3ee7fe8810a348ee98000398 URL: https://github.com/llvm/llvm-project/commit/ea201e83e292f39c3ee7fe8810a348ee98000398 DIFF: https://github.com/llvm/llvm-project/commit/ea201e83e292f39c3ee7fe8810a348ee98000398.diff LOG: [AST][ObjC] Fix crash when printing invalid objc categories Summary: If no valid interface definition was found previously we would crash. With this change instead we just print `<>` in place of the NULL interface. In the future this could be improved by saving the invalid interface's name and using that. Reviewers: sammccall, gribozavr Subscribers: cfe-commits Tags: #clang Differential Revision: https://reviews.llvm.org/D83513 Added: Modified: clang/lib/AST/DeclPrinter.cpp clang/unittests/AST/DeclPrinterTest.cpp Removed: diff --git a/clang/lib/AST/DeclPrinter.cpp b/clang/lib/AST/DeclPrinter.cpp index 4df6512e6c76..2e48b2b46c4d 100644 --- a/clang/lib/AST/DeclPrinter.cpp +++ b/clang/lib/AST/DeclPrinter.cpp @@ -1374,7 +1374,12 @@ void DeclPrinter::VisitObjCProtocolDecl(ObjCProtocolDecl *PID) { } void DeclPrinter::VisitObjCCategoryImplDecl(ObjCCategoryImplDecl *PID) { - Out << "@implementation " << *PID->getClassInterface() << '(' << *PID <<")\n"; + Out << "@implementation "; + if (const auto *CID = PID->getClassInterface()) +Out << *CID; + else +Out << "<>"; + Out << '(' << *PID << ")\n"; VisitDeclContext(PID, false); Out << "@end"; @@ -1382,7 +1387,11 @@ void DeclPrinter::VisitObjCCategoryImplDecl(ObjCCategoryImplDecl *PID) { } void DeclPrinter::VisitObjCCategoryDecl(ObjCCategoryDecl *PID) { - Out << "@interface " << *PID->getClassInterface(); + Out << "@interface "; + if (const auto *CID = PID->getClassInterface()) +Out << *CID; + else +Out << "<>"; if (auto TypeParams = PID->getTypeParamList()) { PrintObjCTypeParams(TypeParams); } diff --git a/clang/unittests/AST/DeclPrinterTest.cpp b/clang/unittests/AST/DeclPrinterTest.cpp index 018e99237ae9..939c8b52c12c 100644 --- a/clang/unittests/AST/DeclPrinterTest.cpp +++ b/clang/unittests/AST/DeclPrinterTest.cpp @@ -76,14 +76,16 @@ ::testing::AssertionResult PrintedDeclMatches(StringRef Code, const std::vector &Args, const DeclarationMatcher &NodeMatch, StringRef ExpectedPrinted, StringRef FileName, - PrintingPolicyModifier PolicyModifier = nullptr) { + PrintingPolicyModifier PolicyModifier = nullptr, + bool AllowError = false) { PrintMatch Printer(PolicyModifier); MatchFinder Finder; Finder.addMatcher(NodeMatch, &Printer); std::unique_ptr Factory( newFrontendActionFactory(&Finder)); - if (!runToolOnCodeWithArgs(Factory->create(), Code, Args, FileName)) + if (!runToolOnCodeWithArgs(Factory->create(), Code, Args, FileName) && + !AllowError) return testing::AssertionFailure() << "Parsing error in \"" << Code.str() << "\""; @@ -170,16 +172,12 @@ PrintedDeclCXX1ZMatches(StringRef Code, const DeclarationMatcher &NodeMatch, "input.cc"); } -::testing::AssertionResult PrintedDeclObjCMatches( - StringRef Code, - const DeclarationMatcher &NodeMatch, - StringRef ExpectedPrinted) { +::testing::AssertionResult +PrintedDeclObjCMatches(StringRef Code, const DeclarationMatcher &NodeMatch, + StringRef ExpectedPrinted, bool AllowError = false) { std::vector Args(1, ""); - return PrintedDeclMatches(Code, -Args, -NodeMatch, -ExpectedPrinted, -"input.m"); + return PrintedDeclMatches(Code, Args, NodeMatch, ExpectedPrinted, "input.m", +/*PolicyModifier=*/nullptr, AllowError); } } // unnamed namespace @@ -1321,3 +1319,17 @@ TEST(DeclPrinter, TestObjCProtocol2) { namedDecl(hasName("P1")).bind("id"), "@protocol P1\n@end")); } + +TEST(DeclPrinter, TestObjCCategoryInvalidInterface) { + ASSERT_TRUE(PrintedDeclObjCMatches( + "@interface I (Extension) @end", + namedDecl(hasName("Extension")).bind("id"), + "@interface <>(Extension)\n@end", /*AllowError=*/true)); +} + +TEST(DeclPrinter, TestObjCCategoryImplInvalidInterface) { + ASSERT_TRUE(PrintedDeclObjCMatches( + "@implementation I (Extension) @end", + namedDecl(hasName("Extension")).bind("id"), + "@implementation <>(Extension)\n@end", /*AllowError=*/true)); +} ___ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang] c61ef1f - [Sema][CodeComplete][ObjC] Don't split the first selector fragment
Author: David Goldman Date: 2020-06-25T13:58:27-04:00 New Revision: c61ef1f25c7fe774e68d20beb956a3b12a353b95 URL: https://github.com/llvm/llvm-project/commit/c61ef1f25c7fe774e68d20beb956a3b12a353b95 DIFF: https://github.com/llvm/llvm-project/commit/c61ef1f25c7fe774e68d20beb956a3b12a353b95.diff LOG: [Sema][CodeComplete][ObjC] Don't split the first selector fragment Summary: Standardize the formatting of selector fragments to include the ':', e.g. for `- (void)foobar:(int)foobar;`, report `{foobar:}` instead of `{foobar}{:}`. This was normally the case except for a couple of places where it was split. This also improves integration with clangd since it relies upon the `:` to identify ObjC selectors. NOTE: It is possible to have selector fragments that are just `:` with no text, we now handle this properly for the first fragment. Reviewers: sammccall, doug.gregor Subscribers: ilya-biryukov, dexonsmith, arphaman, kadircet, usaxena95, cfe-commits Tags: #clang Differential Revision: https://reviews.llvm.org/D82306 Added: Modified: clang/lib/Sema/SemaCodeComplete.cpp clang/test/Index/complete-method-decls.m clang/test/Index/complete-parameterized-classes.m Removed: diff --git a/clang/lib/Sema/SemaCodeComplete.cpp b/clang/lib/Sema/SemaCodeComplete.cpp index 5539aef917d0..913c43886b4e 100644 --- a/clang/lib/Sema/SemaCodeComplete.cpp +++ b/clang/lib/Sema/SemaCodeComplete.cpp @@ -8133,8 +8133,8 @@ static void AddObjCKeyValueCompletions(ObjCPropertyDecl *Property, Builder.AddChunk(CodeCompletionString::CK_RightParen); } - Builder.AddTypedTextChunk(Allocator.CopyString(SelectorId->getName())); - Builder.AddTypedTextChunk(":"); + Builder.AddTypedTextChunk( + Allocator.CopyString(SelectorId->getName() + ":")); AddObjCPassingTypeChunk(Property->getType(), /*Quals=*/0, Context, Policy, Builder); Builder.AddTextChunk(Key); @@ -8722,39 +8722,43 @@ void Sema::CodeCompleteObjCMethodDecl(Scope *S, Optional IsInstanceMethod, Selector Sel = Method->getSelector(); -// Add the first part of the selector to the pattern. -Builder.AddTypedTextChunk( -Builder.getAllocator().CopyString(Sel.getNameForSlot(0))); - -// Add parameters to the pattern. -unsigned I = 0; -for (ObjCMethodDecl::param_iterator P = Method->param_begin(), -PEnd = Method->param_end(); - P != PEnd; (void)++P, ++I) { - // Add the part of the selector name. - if (I == 0) -Builder.AddTypedTextChunk(":"); - else if (I < Sel.getNumArgs()) { -Builder.AddChunk(CodeCompletionString::CK_HorizontalSpace); -Builder.AddTypedTextChunk( -Builder.getAllocator().CopyString(Sel.getNameForSlot(I) + ":")); - } else -break; - - // Add the parameter type. - QualType ParamType; - if ((*P)->getObjCDeclQualifier() & Decl::OBJC_TQ_CSNullability) -ParamType = (*P)->getType(); - else -ParamType = (*P)->getOriginalType(); - ParamType = ParamType.substObjCTypeArgs( - Context, {}, ObjCSubstitutionContext::Parameter); - AttributedType::stripOuterNullability(ParamType); - AddObjCPassingTypeChunk(ParamType, (*P)->getObjCDeclQualifier(), Context, - Policy, Builder); +if (Sel.isUnarySelector()) { + // Unary selectors have no arguments. + Builder.AddTypedTextChunk( + Builder.getAllocator().CopyString(Sel.getNameForSlot(0))); +} else { + // Add all parameters to the pattern. + unsigned I = 0; + for (ObjCMethodDecl::param_iterator P = Method->param_begin(), + PEnd = Method->param_end(); + P != PEnd; (void)++P, ++I) { +// Add the part of the selector name. +if (I == 0) + Builder.AddTypedTextChunk( + Builder.getAllocator().CopyString(Sel.getNameForSlot(I) + ":")); +else if (I < Sel.getNumArgs()) { + Builder.AddChunk(CodeCompletionString::CK_HorizontalSpace); + Builder.AddTypedTextChunk( + Builder.getAllocator().CopyString(Sel.getNameForSlot(I) + ":")); +} else + break; - if (IdentifierInfo *Id = (*P)->getIdentifier()) -Builder.AddTextChunk(Builder.getAllocator().CopyString(Id->getName())); +// Add the parameter type. +QualType ParamType; +if ((*P)->getObjCDeclQualifier() & Decl::OBJC_TQ_CSNullability) + ParamType = (*P)->getType(); +else + ParamType = (*P)->getOriginalType(); +ParamType = ParamType.substObjCTypeArgs( +Context, {}, ObjCSubstitutionContext::Parameter); +AttributedType::stripOuterNullability(ParamType); +AddObjCPassingTypeChunk(ParamType, (*P)->getObjCDeclQualifier(
[clang-tools-extra] cb29c33 - [clangd][ObjC] Improve xrefs for protocols and classes
Author: David Goldman Date: 2020-08-11T12:36:31-04:00 New Revision: cb29c33984bf40beebd22edf80a5034cf8849307 URL: https://github.com/llvm/llvm-project/commit/cb29c33984bf40beebd22edf80a5034cf8849307 DIFF: https://github.com/llvm/llvm-project/commit/cb29c33984bf40beebd22edf80a5034cf8849307.diff LOG: [clangd][ObjC] Improve xrefs for protocols and classes Summary: Previously clangd would jump to forward declarations for protocols and classes instead of their definition/implementation. Reviewers: sammccall Subscribers: ilya-biryukov, MaskRay, jkorous, arphaman, kadircet, usaxena95, cfe-commits Tags: #clang Differential Revision: https://reviews.llvm.org/D83501 Added: Modified: clang-tools-extra/clangd/FindTarget.cpp clang-tools-extra/clangd/XRefs.cpp clang-tools-extra/clangd/index/SymbolCollector.cpp clang-tools-extra/clangd/unittests/FindTargetTests.cpp clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp clang-tools-extra/clangd/unittests/XRefsTests.cpp Removed: diff --git a/clang-tools-extra/clangd/FindTarget.cpp b/clang-tools-extra/clangd/FindTarget.cpp index 734b8432c838..f73a6e584972 100644 --- a/clang-tools-extra/clangd/FindTarget.cpp +++ b/clang-tools-extra/clangd/FindTarget.cpp @@ -355,7 +355,21 @@ struct TargetFinder { Flags |= Rel::Underlying; // continue with the underlying decl. } else if (const auto *DG = dyn_cast(D)) { D = DG->getDeducedTemplate(); +} else if (const ObjCImplementationDecl *IID = + dyn_cast(D)) { + // Treat ObjC{Interface,Implementation}Decl as if they were a decl/def + // pair as long as the interface isn't implicit. + if (const auto *CID = IID->getClassInterface()) +if (const auto *DD = CID->getDefinition()) + if (!DD->isImplicitInterfaceDecl()) +D = DD; +} else if (const ObjCCategoryImplDecl *CID = + dyn_cast(D)) { + // Treat ObjC{Category,CategoryImpl}Decl as if they were a decl/def pair. + D = CID->getCategoryDecl(); } +if (!D) + return; if (const Decl *Pat = getTemplatePattern(D)) { assert(Pat != D); diff --git a/clang-tools-extra/clangd/XRefs.cpp b/clang-tools-extra/clangd/XRefs.cpp index b61ff4979320..9936c67cb6e5 100644 --- a/clang-tools-extra/clangd/XRefs.cpp +++ b/clang-tools-extra/clangd/XRefs.cpp @@ -78,6 +78,32 @@ const NamedDecl *getDefinition(const NamedDecl *D) { return VD->getDefinition(); if (const auto *FD = dyn_cast(D)) return FD->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 definition: @implementation MyClass ... @end + 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(); + } // Only a single declaration is allowed. if (isa(D) || isa(D) || isa(D)) // except cases above @@ -223,6 +249,37 @@ locateMacroReferent(const syntax::Token &TouchedIdentifier, ParsedAST &AST, return llvm::None; } +// A wrapper around `Decl::getCanonicalDecl` to support cases where Clang's +// definition of a canonical declaration doesn't match up to what a programmer +// would expect. For example, Objective-C classes can have three types of +// declarations: +// +// - forward declaration(s): @class MyClass; +// - true declaration (interface definition): @interface MyClass ... @end +// - true definition (implementation): @implementation MyClass ... @end +// +// Clang will consider the forward declaration to be the canonical declaration +// because it is first. We actually want the class definition if it is +// available since that is what a programmer would consider the primary +// declaration to be. +const NamedDecl *getPreferredDecl(const NamedDecl *D) { + // FIXME: Canonical declarations of some symbols might refer to built-in + // decls with possibly-invalid source locations (e.g. global new operator). + // In such cases we should pick up a redecl with valid source location + // instead of failing. + D = llvm::cast(D->getCanon
[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 &CC) { 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 &Sym) { + // 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 &Index, const FuzzyFindRequest &Req) { return runAsync([&Index, Req]() { @@ -1675,14 +1695,6 @@ class CodeCompleteFlow { return Output; } - bool includeSymbolFromIndex(const Symbol &Sym) { -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 &Sym) { - if (includeSymbolFromIndex(Sym)) -ResultsBuilder.insert(Sym); -})) - Incomplete = true; +Incomplete |= Opts.Index->fuzzyFind( +Req, [&](const Symbol &Sym) { ResultsBuilder.insert(Sym); }); return std::move(ResultsBuilder).build(); } @@ -1783,6 +1792,8 @@ class CodeCompleteFlow { for (const auto &IndexResult : IndexResults) { if (UsedIndexResults.co
[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 &ND) { 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 &AST = ND.getASTContext(); const auto &SM = AST.getSourceManager(); @@ -183,6 +185,8 @@ bool isSpelled(SourceLocation Loc, const NamedDecl &ND) { if (clang::Lexer::getRawToken(Loc, Tok, SM, LO)) return false; auto StrName = Name.getAsString(); + if (const auto *MD = dyn_cast(&ND)) +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-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 &ND) { 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 &AST = ND.getASTContext(); const auto &SM = AST.getSourceManager(); @@ -183,6 +185,8 @@ bool isSpelled(SourceLocation Loc, const NamedDecl &ND) { if (clang::Lexer::getRawToken(Loc, Tok, SM, LO)) return false; auto StrName = Name.getAsString(); + if (const auto *MD = dyn_cast(&ND)) +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
[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 &ND) { 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 &AST = ND.getASTContext(); const auto &SM = AST.getSourceManager(); @@ -183,6 +185,8 @@ bool isSpelled(SourceLocation Loc, const NamedDecl &ND) { if (clang::Lexer::getRawToken(Loc, Tok, SM, LO)) return false; auto StrName = Name.getAsString(); + if (const auto *MD = dyn_cast(&ND)) +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_T
[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 &ND) { 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 &AST = ND.getASTContext(); const auto &SM = AST.getSourceManager(); @@ -183,6 +185,8 @@ bool isSpelled(SourceLocation Loc, const NamedDecl &ND) { if (clang::Lexer::getRawToken(Loc, Tok, SM, LO)) return false; auto StrName = Name.getAsString(); + if (const auto *MD = dyn_cast(&ND)) +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
[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 &ND) { 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 &AST = ND.getASTContext(); const auto &SM = AST.getSourceManager(); @@ -183,6 +185,8 @@ bool isSpelled(SourceLocation Loc, const NamedDecl &ND) { if (clang::Lexer::getRawToken(Loc, Tok, SM, LO)) return false; auto StrName = Name.getAsString(); + if (const auto *MD = dyn_cast(&ND)) +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
[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 &ND) { 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 &AST = ND.getASTContext(); const auto &SM = AST.getSourceManager(); @@ -183,6 +185,8 @@ bool isSpelled(SourceLocation Loc, const NamedDecl &ND) { if (clang::Lexer::getRawToken(Loc, Tok, SM, LO)) return false; auto StrName = Name.getAsString(); + if (const auto *MD = dyn_cast(&ND)) +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
[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)
@@ -893,22 +964,36 @@ llvm::Expected buildRenameEdit(llvm::StringRef AbsFilePath, return LastOffset; }; - std::vector> OccurrencesOffsets; - for (const auto &R : 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 &SR : 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)
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 &ND) { 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 &AST = ND.getASTContext(); const auto &SM = AST.getSourceManager(); @@ -183,6 +185,8 @@ bool isSpelled(SourceLocation Loc, const NamedDecl &ND) { if (clang::Lexer::getRawToken(Loc, Tok, SM, LO)) return false; auto StrName = Name.getAsString(); + if (const auto *MD = dyn_cast(&ND)) +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
[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] 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 a7aabc3e1df2a..dc5382
[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 &D, const SourceManager &SM) { auto L = D.getLocation(); + // For `- (void)foo` we want `foo` not the `-`. + if (const auto *MD = dyn_cast(&D)) +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 &CurContext, 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 &SM); +/// 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 &CurContext, 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
[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 &operator<<(llvm::raw_ostream &O, bool operator==(const SymbolDetails &LHS, const SymbolDetails &RHS) { 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 &P) { @@ -755,6 +757,12 @@ llvm::json::Value toJSON(const SymbolDetails &P) { 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 &operator<<(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 &AST, 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 &AST, 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 &AST, 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] 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 &OrigT, const Decl &D, + 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 &ID) { +// 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 &PD) { +Name = PD.getName(); +Type = getTypeStr(PD.getType(), PD, PD.getPropertyAttributes()); +if (const auto *ID = PD.getPropertyIvarDecl()) + Assignee =
[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] 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 ? &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, &IsFrameworkFound); + + EXPECT_TRUE(FoundFile.hasValue()); + EXPECT_TRUE(IsFrameworkFound); + auto &FE = 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-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 *&PP, const SourceManager &SM, @@ -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 &HS, FrameworkHeaderPath &HeaderPath) { +auto Res = CacheFrameworkToUmbrellaHeaderSpelling.try_emplace(Framework); +auto *CachedSpelling = &Res.first->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(UmbrellaP
[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] 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 *&PP, const SourceManager &SM, @@ -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 &HS, FrameworkHeaderPath &HeaderPath) { -auto Res = CacheFrameworkToUmbrellaHeaderSpelling.try_emplace(Framework); -auto *CachedSpelling = &Res.first->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(UmbrellaP
[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 *&PP, const SourceManager &SM, @@ -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 &HS, FrameworkHeaderPath &HeaderPath) { +auto Res = CacheFrameworkToUmbrellaHeaderSpelling.try_emplace(Framework); +auto *CachedSpelling = &Res.first->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.getFil
[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 &C : *CCS) { + if (C.Kind != CodeCompletionString::CK_TypedText) +continue; + if (OnlyText) +return CCAllocator->CopyString(CCS->getAllTypedText()); + OnlyText = &C; +} +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 &Opts) 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 b/clang-tools-extra/clangd/unittests/C
[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 &RenameDecl, 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 &Cur, const syntax::Token &Next, +const SourceManager &SM, +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)
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 &ND) { 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 &AST = ND.getASTContext(); const auto &SM = AST.getSourceManager(); @@ -183,6 +185,8 @@ bool isSpelled(SourceLocation Loc, const NamedDecl &ND) { if (clang::Lexer::getRawToken(Loc, Tok, SM, LO)) return false; auto StrName = Name.getAsString(); + if (const auto *MD = dyn_cast(&ND)) +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
[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 &ND) { 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 &AST = ND.getASTContext(); const auto &SM = AST.getSourceManager(); @@ -183,6 +185,8 @@ bool isSpelled(SourceLocation Loc, const NamedDecl &ND) { if (clang::Lexer::getRawToken(Loc, Tok, SM, LO)) return false; auto StrName = Name.getAsString(); + if (const auto *MD = dyn_cast(&ND)) +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(Symbo
[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)
@@ -538,11 +565,254 @@ std::optional checkName(const NamedDecl &RenameDecl, 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 &Cur, const syntax::Token &Next, +const SourceManager &SM, +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 &Cur, const syntax::Token &Next) { + 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 &SM, unsigned Index, +unsigned Last, Selector Sel, +std::vector &SelectorPieces) { + + 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)
@@ -538,11 +565,254 @@ std::optional checkName(const NamedDecl &RenameDecl, 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 &Cur, const syntax::Token &Next, +const SourceManager &SM, +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 &Cur, const syntax::Token &Next) { + 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 &SM, unsigned Index, +unsigned Last, Selector Sel, +std::vector &SelectorPieces) { + + unsigned NumArgs = Sel.getNumArgs(); + llvm::SmallVector Closes; + SelectorPieces.clear(); + while (Index < Last) { +const auto &Tok = 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 &LangOpts, std::optional Selector) { + std::vector Ranges; + if (!Selector) { +auto IdentifierRanges = +collectIdentifierRanges(Identifier, Content, LangOpts); +for (const auto &R : 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 &SM = 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 &Tok = Tokens[Index]; +
[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 &RenameDecl, 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 &Cur, const syntax::Token &Next) { + 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 &Cur, const syntax::Token &Next, +const SourceManager &SM, +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)
@@ -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)
@@ -569,8 +840,13 @@ renameWithinFile(ParsedAST &AST, const NamedDecl &RenameDecl, // } if (!isInsideMainFile(RenameLoc, SM)) continue; +Locs.push_back(RenameLoc); + } + if (const auto *MD = dyn_cast(&RenameDecl)) +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)
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] [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 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 &AST, - 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 deriv
[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 &AST, - 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 deriv
[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 &AST, const NamedDecl &RenameDecl, continue; Locs.push_back(RenameLoc); } - if (const auto *MD = dyn_cast(&RenameDecl)) -return renameObjCMethodWithinFile(AST, MD, NewName, std::move(Locs)); + if (const auto *MD = dyn_cast(&RenameDecl)) { +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 &Loc : Locs) { if (auto Err = FilteredChanges.add(tooling::Replacement( SM, CharSourceRange::getTokenRange(Loc), NewName))) @@ -930,10 +936,9 @@ renameOutsideFile(const NamedDecl &RenameDecl, llvm::StringRef MainFilePath, std::optional Selector = std::nullopt; llvm::SmallVector NewNames; if (const auto *MD = dyn_cast(&RenameDecl)) { - 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 +
[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] 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 &AST, - 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 deriv
[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 &RInputs) { 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][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)
@@ -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 &RenameDecl) { + if (const auto *MD = dyn_cast(&RenameDecl)) +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 &RenameDecl, - llvm::StringRef NewName) { +std::optional checkName(const NamedDecl &RenameDecl, 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 &RenameDecl, 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 &Cur, const syntax::Token &Next, +const SourceManager &SM, +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 &Cur, const syntax::Token &Next) { + 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 &SM, unsigned Index, +unsigned Last, Selector Sel, +std::vector &SelectorPieces) { + + unsigned NumArgs = Sel.getNumArgs(); + llvm::SmallVector Closes; + SelectorPieces.clear(); + while (Index < Last) { +const auto &Tok = 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 &LangOpts, std::optional Selector) { + std::vector Ranges; + if (!Selector) { +auto IdentifierRanges = +collectIdentifierRanges(Identifier, Content, LangOpts); +for (const auto &R : 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 &SM = 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 &Tok = Tokens[Index]; +
[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 &RenameDecl, 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 &Cur, const syntax::Token &Next, +const SourceManager &SM, +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 &Cur, const syntax::Token &Next) { + 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 &SM, unsigned Index, +unsigned Last, Selector Sel, +std::vector &SelectorPieces) { + + unsigned NumArgs = Sel.getNumArgs(); + llvm::SmallVector Closes; + SelectorPieces.clear(); + while (Index < Last) { +const auto &Tok = 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 &LangOpts, std::optional Selector) { + std::vector Ranges; + if (!Selector) { +auto IdentifierRanges = +collectIdentifierRanges(Identifier, Content, LangOpts); +for (const auto &R : 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 &SM = 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 &Tok = Tokens[Index]; + +if (BraceCou
[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 &RenameDecl, 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 &Cur, const syntax::Token &Next, +const SourceManager &SM, +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 &Cur, const syntax::Token &Next) { + 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 &SM, unsigned Index, +unsigned Last, Selector Sel, +std::vector &SelectorPieces) { 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)
@@ -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 &RenameDecl, 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 &Cur, const syntax::Token &Next, +const SourceManager &SM, +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 &Cur, const syntax::Token &Next) { + 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 &SM, unsigned Index, +unsigned Last, Selector Sel, +std::vector &SelectorPieces) { + + unsigned NumArgs = Sel.getNumArgs(); + llvm::SmallVector Closes; + SelectorPieces.clear(); + while (Index < Last) { +const auto &Tok = 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 &LangOpts, std::optional Selector) { + std::vector Ranges; + if (!Selector) { +auto IdentifierRanges = +collectIdentifierRanges(Identifier, Content, LangOpts); +for (const auto &R : 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 &SM = 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
[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 &ND) { 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 &AST = ND.getASTContext(); const auto &SM = AST.getSourceManager(); @@ -183,6 +185,8 @@ bool isSpelled(SourceLocation Loc, const NamedDecl &ND) { if (clang::Lexer::getRawToken(Loc, Tok, SM, LO)) return false; auto StrName = Name.getAsString(); + if (const auto *MD = dyn_cast(&ND)) +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(find
[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 &R : 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 &SR : 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)
@@ -681,12 +718,26 @@ renameOutsideFile(const NamedDecl &RenameDecl, 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(&RenameDecl)) { + 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)
@@ -543,6 +550,45 @@ std::optional checkName(const NamedDecl &RenameDecl, return Result; } +clangd::Range tokenRangeForLoc(ParsedAST &AST, SourceLocation TokLoc, + const SourceManager &SM, + const LangOptions &LangOpts) { + 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)
@@ -543,6 +550,45 @@ std::optional checkName(const NamedDecl &RenameDecl, return Result; } +clangd::Range tokenRangeForLoc(ParsedAST &AST, SourceLocation TokLoc, + const SourceManager &SM, + const LangOptions &LangOpts) { + 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 &AST, const ObjCMethodDecl *MD, + llvm::StringRef NewName, + std::vector Locs) { + const SourceManager &SM = 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 &RenameDecl, return Result; } +clangd::Range tokenRangeForLoc(ParsedAST &AST, SourceLocation TokLoc, + const SourceManager &SM, + const LangOptions &LangOpts) { + 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 &RenameDecl, return Result; } +clangd::Range tokenRangeForLoc(ParsedAST &AST, SourceLocation TokLoc, + const SourceManager &SM, + const LangOptions &LangOpts) { + 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 &AST, const ObjCMethodDecl *MD, + llvm::StringRef NewName, + std::vector Locs) { + const SourceManager &SM = 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 &LangOpts = MD->getASTContext().getLangOpts(); + for (const auto &Loc : 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)
@@ -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 &LHS, const SymbolRange &RHS) { + return LHS.Ranges == RHS.Ranges; +} +bool operator!=(const SymbolRange &LHS, const SymbolRange &RHS) { + return !(LHS == RHS); +} +bool operator<(const SymbolRange &LHS, const SymbolRange &RHS) { + 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)
@@ -778,12 +868,44 @@ llvm::Expected rename(const RenameInputs &RInputs) { 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 &RenameDecl = **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(&RenameDecl)) { +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)
@@ -778,12 +868,44 @@ llvm::Expected rename(const RenameInputs &RInputs) { 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 &RenameDecl = **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(&RenameDecl)) { +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 &RInputs) { 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 &RenameDecl = **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(&RenameDecl)) { +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 &RInputs) { 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 &RenameDecl = **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(&RenameDecl)) { +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 &RInputs) { 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 &RenameDecl = **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(&RenameDecl)) { +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 &RInputs) { 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 &RenameDecl = **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(&RenameDecl)) { +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)
@@ -1029,5 +1172,151 @@ size_t renameRangeAdjustmentCost(ArrayRef Indexed, ArrayRef Lexed, return Cost; } +static bool isMatchingSelectorName(const syntax::Token &Cur, + const syntax::Token &Next, + const SourceManager &SM, + 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)
@@ -1029,5 +1172,151 @@ size_t renameRangeAdjustmentCost(ArrayRef Indexed, ArrayRef Lexed, return Cost; } +static bool isMatchingSelectorName(const syntax::Token &Cur, + const syntax::Token &Next, + const SourceManager &SM, + 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 &Cur, + const syntax::Token &Next) { + 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 &LangOpts, +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 &SM = FileSM.get(); + for (const auto &Tok : syntax::tokenize(SM.getMainFileID(), SM, LangOpts)) +Action(Tok, SM); +} + +std::vector collectRenameIdentifierRanges( +llvm::StringRef Identifier, llvm::StringRef Content, +const LangOptions &LangOpts, std::optional Selector) { + std::vector Ranges; + if (!Selector) { +lex(Content, LangOpts, +[&](const syntax::Token &Tok, const SourceManager &SM) { + 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)
@@ -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)
@@ -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)
@@ -1029,5 +1172,151 @@ size_t renameRangeAdjustmentCost(ArrayRef Indexed, ArrayRef Lexed, return Cost; } +static bool isMatchingSelectorName(const syntax::Token &Cur, 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 &Cur, + const syntax::Token &Next, + const SourceManager &SM, + 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 &Cur, + const syntax::Token &Next) { + 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 &LangOpts, +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 &SM = FileSM.get(); + for (const auto &Tok : syntax::tokenize(SM.getMainFileID(), SM, LangOpts)) +Action(Tok, SM); +} + +std::vector collectRenameIdentifierRanges( +llvm::StringRef Identifier, llvm::StringRef Content, +const LangOptions &LangOpts, std::optional Selector) { + std::vector Ranges; + if (!Selector) { +lex(Content, LangOpts, +[&](const syntax::Token &Tok, const SourceManager &SM) { + 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 &SM = 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 &Cur, + const syntax::Token &Next, + const SourceManager &SM, + 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 &Cur, + const syntax::Token &Next) { + 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 &LangOpts, +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 &SM = FileSM.get(); + for (const auto &Tok : syntax::tokenize(SM.getMainFileID(), SM, LangOpts)) +Action(Tok, SM); +} + +std::vector collectRenameIdentifierRanges( +llvm::StringRef Identifier, llvm::StringRef Content, +const LangOptions &LangOpts, std::optional Selector) { + std::vector Ranges; + if (!Selector) { +lex(Content, LangOpts, +[&](const syntax::Token &Tok, const SourceManager &SM) { + 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 &SM = 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 &State = States.back(); +auto &Pieces = State.Pieces; +const auto &Tok = 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 &Cur, + const syntax::Token &Next, + const SourceManager &SM, + 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 &Cur, + const syntax::Token &Next) { + 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 &LangOpts, +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 &SM = FileSM.get(); + for (const auto &Tok : syntax::tokenize(SM.getMainFileID(), SM, LangOpts)) +Action(Tok, SM); +} + +std::vector collectRenameIdentifierRanges( +llvm::StringRef Identifier, llvm::StringRef Content, +const LangOptions &LangOpts, std::optional Selector) { + std::vector Ranges; + if (!Selector) { +lex(Content, LangOpts, +[&](const syntax::Token &Tok, const SourceManager &SM) { + 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 &SM = 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 &State = States.back(); +auto &Pieces = State.Pieces; +const auto &Tok = 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 &Cur, + const syntax::Token &Next, + const SourceManager &SM, + 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 &Cur, + const syntax::Token &Next) { + 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 &LangOpts, +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 &SM = FileSM.get(); + for (const auto &Tok : syntax::tokenize(SM.getMainFileID(), SM, LangOpts)) +Action(Tok, SM); +} + +std::vector collectRenameIdentifierRanges( +llvm::StringRef Identifier, llvm::StringRef Content, +const LangOptions &LangOpts, std::optional Selector) { + std::vector Ranges; + if (!Selector) { +lex(Content, LangOpts, +[&](const syntax::Token &Tok, const SourceManager &SM) { + 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 &SM = 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 &State = States.back(); +auto &Pieces = State.Pieces; +const auto &Tok = 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 &Cur, + const syntax::Token &Next, + const SourceManager &SM, + 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 &Cur, + const syntax::Token &Next) { + 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 &LangOpts, +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 &SM = FileSM.get(); + for (const auto &Tok : syntax::tokenize(SM.getMainFileID(), SM, LangOpts)) +Action(Tok, SM); +} + +std::vector collectRenameIdentifierRanges( +llvm::StringRef Identifier, llvm::StringRef Content, +const LangOptions &LangOpts, std::optional Selector) { + std::vector Ranges; + if (!Selector) { +lex(Content, LangOpts, +[&](const syntax::Token &Tok, const SourceManager &SM) { + 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 &SM = 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 &Cur, + const syntax::Token &Next, + const SourceManager &SM, + 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 &Cur, + const syntax::Token &Next) { + 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 &LangOpts, +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 &SM = FileSM.get(); + for (const auto &Tok : syntax::tokenize(SM.getMainFileID(), SM, LangOpts)) +Action(Tok, SM); +} + +std::vector collectRenameIdentifierRanges( +llvm::StringRef Identifier, llvm::StringRef Content, +const LangOptions &LangOpts, std::optional Selector) { + std::vector Ranges; + if (!Selector) { +lex(Content, LangOpts, +[&](const syntax::Token &Tok, const SourceManager &SM) { + 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 &SM = 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 &Cur, + const syntax::Token &Next, + const SourceManager &SM, + 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 &Cur, + const syntax::Token &Next) { + 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 &LangOpts, +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 &SM = FileSM.get(); + for (const auto &Tok : syntax::tokenize(SM.getMainFileID(), SM, LangOpts)) +Action(Tok, SM); +} + +std::vector collectRenameIdentifierRanges( +llvm::StringRef Identifier, llvm::StringRef Content, +const LangOptions &LangOpts, std::optional Selector) { + std::vector Ranges; + if (!Selector) { +lex(Content, LangOpts, +[&](const syntax::Token &Tok, const SourceManager &SM) { + 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 &SM = 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)
@@ -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)
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 &ND) { 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 &AST = ND.getASTContext(); const auto &SM = AST.getSourceManager(); @@ -183,6 +185,8 @@ bool isSpelled(SourceLocation Loc, const NamedDecl &ND) { if (clang::Lexer::getRawToken(Loc, Tok, SM, LO)) return false; auto StrName = Name.getAsString(); + if (const auto *MD = dyn_cast(&ND)) +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(find
[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 &ND) { 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 &AST = ND.getASTContext(); const auto &SM = AST.getSourceManager(); @@ -183,6 +185,8 @@ bool isSpelled(SourceLocation Loc, const NamedDecl &ND) { if (clang::Lexer::getRawToken(Loc, Tok, SM, LO)) return false; auto StrName = Name.getAsString(); + if (const auto *MD = dyn_cast(&ND)) +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(find
[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 &ND) { 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 &AST = ND.getASTContext(); const auto &SM = AST.getSourceManager(); @@ -183,6 +185,8 @@ bool isSpelled(SourceLocation Loc, const NamedDecl &ND) { if (clang::Lexer::getRawToken(Loc, Tok, SM, LO)) return false; auto StrName = Name.getAsString(); + if (const auto *MD = dyn_cast(&ND)) +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(find
r369427 - [Sema][Typo] Fix assertion failure for expressions with multiple typos
Author: dgoldman Date: Tue Aug 20 12:03:15 2019 New Revision: 369427 URL: http://llvm.org/viewvc/llvm-project?rev=369427&view=rev Log: [Sema][Typo] Fix assertion failure for expressions with multiple typos Summary: As Typo Resolution can create new TypoExprs while resolving typos, it is necessary to recurse through the expression to search for more typos. This should fix the assertion failure in `clang::Sema::~Sema()`: `DelayedTypos.empty() && "Uncorrected typos!"` Notes: - In case some TypoExprs are created but thrown away, Sema now has a Vector that is used to keep track of newly created typos. - For expressions with multiple typos, we only give suggestions if we are able to resolve all typos in the expression - This patch is similar to D37521 except that it does not eagerly commit to a correction for the first typo in the expression. Instead, it will search for corrections which fix all of the typos in the expression. Subscribers: cfe-commits Tags: #clang Differential Revision: https://reviews.llvm.org/D62648 Added: cfe/trunk/test/Sema/typo-correction-recursive.cpp Modified: cfe/trunk/include/clang/Sema/Sema.h cfe/trunk/lib/Sema/SemaExprCXX.cpp cfe/trunk/lib/Sema/SemaLookup.cpp Modified: cfe/trunk/include/clang/Sema/Sema.h URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/include/clang/Sema/Sema.h?rev=369427&r1=369426&r2=369427&view=diff == --- cfe/trunk/include/clang/Sema/Sema.h (original) +++ cfe/trunk/include/clang/Sema/Sema.h Tue Aug 20 12:03:15 2019 @@ -405,6 +405,12 @@ public: /// Source location for newly created implicit MSInheritanceAttrs SourceLocation ImplicitMSInheritanceAttrLoc; + /// Holds TypoExprs that are created from `createDelayedTypo`. This is used by + /// `TransformTypos` in order to keep track of any TypoExprs that are created + /// recursively during typo correction and wipe them away if the correction + /// fails. + llvm::SmallVector TypoExprs; + /// pragma clang section kind enum PragmaClangSectionKind { PCSK_Invalid = 0, Modified: cfe/trunk/lib/Sema/SemaExprCXX.cpp URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/Sema/SemaExprCXX.cpp?rev=369427&r1=369426&r2=369427&view=diff == --- cfe/trunk/lib/Sema/SemaExprCXX.cpp (original) +++ cfe/trunk/lib/Sema/SemaExprCXX.cpp Tue Aug 20 12:03:15 2019 @@ -7580,15 +7580,22 @@ class TransformTypos : public TreeTransf llvm::SmallDenseMap OverloadResolution; /// Emit diagnostics for all of the TypoExprs encountered. + /// /// If the TypoExprs were successfully corrected, then the diagnostics should /// suggest the corrections. Otherwise the diagnostics will not suggest /// anything (having been passed an empty TypoCorrection). - void EmitAllDiagnostics() { + /// + /// If we've failed to correct due to ambiguous corrections, we need to + /// be sure to pass empty corrections and replacements. Otherwise it's + /// possible that the Consumer has a TypoCorrection that failed to ambiguity + /// and we don't want to report those diagnostics. + void EmitAllDiagnostics(bool IsAmbiguous) { for (TypoExpr *TE : TypoExprs) { auto &State = SemaRef.getTypoExprState(TE); if (State.DiagHandler) { -TypoCorrection TC = State.Consumer->getCurrentCorrection(); -ExprResult Replacement = TransformCache[TE]; +TypoCorrection TC = IsAmbiguous +? TypoCorrection() : State.Consumer->getCurrentCorrection(); +ExprResult Replacement = IsAmbiguous ? ExprError() : TransformCache[TE]; // Extract the NamedDecl from the transformed TypoExpr and add it to the // TypoCorrection, replacing the existing decls. This ensures the right @@ -7650,6 +7657,145 @@ class TransformTypos : public TreeTransf return ExprFilter(Res.get()); } + // Since correcting typos may intoduce new TypoExprs, this function + // checks for new TypoExprs and recurses if it finds any. Note that it will + // only succeed if it is able to correct all typos in the given expression. + ExprResult CheckForRecursiveTypos(ExprResult Res, bool &IsAmbiguous) { +if (Res.isInvalid()) { + return Res; +} +// Check to see if any new TypoExprs were created. If so, we need to recurse +// to check their validity. +Expr *FixedExpr = Res.get(); + +auto SavedTypoExprs = std::move(TypoExprs); +auto SavedAmbiguousTypoExprs = std::move(AmbiguousTypoExprs); +TypoExprs.clear(); +AmbiguousTypoExprs.clear(); + +FindTypoExprs(TypoExprs).TraverseStmt(FixedExpr); +if (!TypoExprs.empty()) { + // Recurse to handle newly created TypoExprs. If we're not able to + // handle them, discard these TypoExprs. + ExprResult RecurResult = + RecursiveTransformLoop(FixedExpr, IsAmbiguous); + if (RecurResult.isInvalid()) { +
[clang-tools-extra] ea9888b - [clangd] Respect `--sysroot` argument if it is set
Author: David Goldman Date: 2020-01-09T11:02:58-05:00 New Revision: ea9888b8f6f20887647b77ebf5864d647fd2ea44 URL: https://github.com/llvm/llvm-project/commit/ea9888b8f6f20887647b77ebf5864d647fd2ea44 DIFF: https://github.com/llvm/llvm-project/commit/ea9888b8f6f20887647b77ebf5864d647fd2ea44.diff LOG: [clangd] Respect `--sysroot` argument if it is set Summary: - Since `--sysroot` is a superset of the `-isysroot` argument, we shouldn't add the `-isysroot` if we detect a `--sysroot` flag. Reviewers: sammccall Subscribers: ilya-biryukov, MaskRay, jkorous, arphaman, kadircet, usaxena95, cfe-commits Tags: #clang Differential Revision: https://reviews.llvm.org/D72415 Added: Modified: clang-tools-extra/clangd/CompileCommands.cpp Removed: diff --git a/clang-tools-extra/clangd/CompileCommands.cpp b/clang-tools-extra/clangd/CompileCommands.cpp index b1eca02813b3..f11b1236803c 100644 --- a/clang-tools-extra/clangd/CompileCommands.cpp +++ b/clang-tools-extra/clangd/CompileCommands.cpp @@ -155,7 +155,9 @@ void CommandMangler::adjust(std::vector &Cmd) const { if (ResourceDir && !Has("-resource-dir")) Cmd.push_back(("-resource-dir=" + *ResourceDir)); - if (Sysroot && !Has("-isysroot")) { + // Don't set `-isysroot` if it is already set or if `--sysroot` is set. + // `--sysroot` is a superset of the `-isysroot` argument. + if (Sysroot && !Has("-isysroot") && !Has("--sysroot")) { Cmd.push_back("-isysroot"); Cmd.push_back(*Sysroot); } ___ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang-tools-extra] d5c022d - [clangd][ObjC] Support nullability annotations
Author: David Goldman Date: 2020-10-20T17:36:32-04:00 New Revision: d5c022d846999127352aa3c08c0f81418a942aab URL: https://github.com/llvm/llvm-project/commit/d5c022d846999127352aa3c08c0f81418a942aab DIFF: https://github.com/llvm/llvm-project/commit/d5c022d846999127352aa3c08c0f81418a942aab.diff LOG: [clangd][ObjC] Support nullability annotations Nullability annotations are implmented using attributes; previusly clangd would skip over AttributedTypeLoc since their location points to the attribute instead of the modified type. Also add some test cases for this. Differential Revision: https://reviews.llvm.org/D89579 Added: Modified: clang-tools-extra/clangd/Selection.cpp clang-tools-extra/clangd/unittests/FindTargetTests.cpp clang-tools-extra/clangd/unittests/HoverTests.cpp clang-tools-extra/clangd/unittests/SelectionTests.cpp Removed: diff --git a/clang-tools-extra/clangd/Selection.cpp b/clang-tools-extra/clangd/Selection.cpp index 5c08485d8b92..fbd72be320a7 100644 --- a/clang-tools-extra/clangd/Selection.cpp +++ b/clang-tools-extra/clangd/Selection.cpp @@ -605,6 +605,10 @@ class SelectionVisitor : public RecursiveASTVisitor { bool canSafelySkipNode(const DynTypedNode &N) { SourceRange S = N.getSourceRange(); if (auto *TL = N.get()) { + // FIXME: TypeLoc::getBeginLoc()/getEndLoc() are pretty fragile + // heuristics. We should consider only pruning critical TypeLoc nodes, to + // be more robust. + // DeclTypeTypeLoc::getSourceRange() is incomplete, which would lead to // failing // to descend into the child expression. @@ -616,6 +620,10 @@ class SelectionVisitor : public RecursiveASTVisitor { // rid of this patch. if (auto DT = TL->getAs()) S.setEnd(DT.getUnderlyingExpr()->getEndLoc()); + // AttributedTypeLoc may point to the attribute's range, NOT the modified + // type's range. + if (auto AT = TL->getAs()) +S = AT.getModifiedLoc().getSourceRange(); } if (!SelChecker.mayHit(S)) { dlog("{1}skip: {0}", printNodeToString(N, PrintPolicy), indent()); diff --git a/clang-tools-extra/clangd/unittests/FindTargetTests.cpp b/clang-tools-extra/clangd/unittests/FindTargetTests.cpp index a68ea1d6a3dc..dd7e9878a6d5 100644 --- a/clang-tools-extra/clangd/unittests/FindTargetTests.cpp +++ b/clang-tools-extra/clangd/unittests/FindTargetTests.cpp @@ -831,6 +831,24 @@ TEST_F(TargetDeclTest, ObjC) { EXPECT_DECLS("ObjCPropertyRefExpr", "@property(atomic, retain, readwrite) I *x"); + Code = R"cpp( +@interface MYObject +@end +@interface Interface +@property(retain) [[MYObject]] *x; +@end + )cpp"; + EXPECT_DECLS("ObjCInterfaceTypeLoc", "@interface MYObject"); + + Code = R"cpp( +@interface MYObject2 +@end +@interface Interface +@property(retain, nonnull) [[MYObject2]] *x; +@end + )cpp"; + EXPECT_DECLS("ObjCInterfaceTypeLoc", "@interface MYObject2"); + Code = R"cpp( @protocol Foo @end diff --git a/clang-tools-extra/clangd/unittests/HoverTests.cpp b/clang-tools-extra/clangd/unittests/HoverTests.cpp index 48c0fef45ab8..87a1b42315b3 100644 --- a/clang-tools-extra/clangd/unittests/HoverTests.cpp +++ b/clang-tools-extra/clangd/unittests/HoverTests.cpp @@ -1991,6 +1991,34 @@ TEST(Hover, All) { HI.NamespaceScope = "ObjC::"; // FIXME: fix it HI.Definition = "char data"; }}, + { + R"cpp( + @interface MYObject + @end + @interface Interface + @property(retain) [[MYOb^ject]] *x; + @end + )cpp", + [](HoverInfo &HI) { +HI.Name = "MYObject"; +HI.Kind = index::SymbolKind::Class; +HI.NamespaceScope = ""; +HI.Definition = "@interface MYObject\n@end"; + }}, + { + R"cpp( + @interface MYObject + @end + @interface Interface + - (void)doWith:([[MYOb^ject]] *)object; + @end + )cpp", + [](HoverInfo &HI) { +HI.Name = "MYObject"; +HI.Kind = index::SymbolKind::Class; +HI.NamespaceScope = ""; +HI.Definition = "@interface MYObject\n@end"; + }}, }; // Create a tiny index, so tests above can verify documentation is fetched. diff --git a/clang-tools-extra/clangd/unittests/SelectionTests.cpp b/clang-tools-extra/clangd/unittests/SelectionTests.cpp index 2026292bc6f3..55b05ebe11ab 100644 --- a/clang-tools-extra/clangd/unittests/SelectionTests.cpp +++ b/clang-tools-extra/clangd/unittests/SelectionTests.cpp @@ -356,6 +356,22 @@ TEST(SelectionTest, CommonAncestor) { )cpp", "DeclRefExpr"}, + // Objective-C nullability attributes. + { + R"cpp( +@interface I{} +@property(nullable) [[^I
[clang-tools-extra] 2a030e6 - [clangd][ObjC] Fix issue completing a method decl by name
Author: David Goldman Date: 2021-06-01T13:35:05-04:00 New Revision: 2a030e680e0812c652ed4ae2b012e285a5514ffa URL: https://github.com/llvm/llvm-project/commit/2a030e680e0812c652ed4ae2b012e285a5514ffa DIFF: https://github.com/llvm/llvm-project/commit/2a030e680e0812c652ed4ae2b012e285a5514ffa.diff LOG: [clangd][ObjC] Fix issue completing a method decl by name When completing an Objective-C method declaration by name, we need to preserve the leading text as a `qualifier` so we insert it properly before the first typed text chunk. Differential Revision: https://reviews.llvm.org/D100798 Added: Modified: clang-tools-extra/clangd/CodeCompletionStrings.cpp clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp Removed: diff --git a/clang-tools-extra/clangd/CodeCompletionStrings.cpp b/clang-tools-extra/clangd/CodeCompletionStrings.cpp index d4a3bdafcae0..8205c88a5b66 100644 --- a/clang-tools-extra/clangd/CodeCompletionStrings.cpp +++ b/clang-tools-extra/clangd/CodeCompletionStrings.cpp @@ -114,6 +114,7 @@ void getSignature(const CodeCompletionString &CCS, std::string *Signature, } unsigned SnippetArg = 0; bool HadObjCArguments = false; + bool HadInformativeChunks = false; for (const auto &Chunk : CCS) { // Informative qualifier chunks only clutter completion results, skip // them. @@ -129,10 +130,14 @@ void getSignature(const CodeCompletionString &CCS, std::string *Signature, // reclassified as qualifiers. // // Objective-C: - // Objective-C methods may have multiple typed-text chunks, so we must - // treat them carefully. For Objective-C methods, all typed-text chunks - // will end in ':' (unless there are no arguments, in which case we - // can safely treat them as C++). + // Objective-C methods expressions may have multiple typed-text chunks, + // so we must treat them carefully. For Objective-C methods, all + // typed-text and informative chunks will end in ':' (unless there are + // no arguments, in which case we can safely treat them as C++). + // + // Completing a method declaration itself (not a method expression) is + // similar except that we use the `RequiredQualifiers` to store the + // text before the selector, e.g. `- (void)`. if (!llvm::StringRef(Chunk.Text).endswith(":")) { // Treat as C++. if (RequiredQualifiers) *RequiredQualifiers = std::move(*Signature); @@ -147,6 +152,28 @@ void getSignature(const CodeCompletionString &CCS, std::string *Signature, // methods. if (!HadObjCArguments) { HadObjCArguments = true; + // If we have no previous informative chunks (informative selector + // fragments in practice), we treat any previous chunks as + // `RequiredQualifiers` so they will be added as a prefix during the + // completion. + // + // e.g. to complete `- (void)doSomething:(id)argument`: + // - Completion name: `doSomething:` + // - RequiredQualifiers: `- (void)` + // - Snippet/Signature suffix: `(id)argument` + // + // This diff ers from the case when we're completing a method + // expression with a previous informative selector fragment. + // + // e.g. to complete `[self doSomething:nil ^somethingElse:(id)]`: + // - Previous Informative Chunk: `doSomething:` + // - Completion name: `somethingElse:` + // - Snippet/Signature suffix: `(id)` + if (!HadInformativeChunks) { +if (RequiredQualifiers) + *RequiredQualifiers = std::move(*Signature); +Snippet->clear(); + } Signature->clear(); } else { // Subsequent argument, considered part of snippet/signature. *Signature += Chunk.Text; @@ -173,6 +200,7 @@ void getSignature(const CodeCompletionString &CCS, std::string *Signature, *Snippet += '}'; break; case CodeCompletionString::CK_Informative: + HadInformativeChunks = true; // For example, the word "const" for a const method, or the name of // the base class for methods that are part of the base class. *Signature += Chunk.Text; diff --git a/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp b/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp index e4e56f2b5e38..78f6e7c4aa9f 100644 --- a/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp +++ b/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp @@ -2796,6 +2796,79 @@ TEST(CompletionTest, ObjectiveCMethodTwoArgumentsFromMiddle) { EXPECT_THAT(C, ElementsAre(SnippetSuffix("${1:(unsigned int)}"))); } +TEST(CompletionTest, ObjectiveCSimpleMethodDeclaration) { + auto Results = completions(R"objc( + @interface Foo + - (void)foo; + @end
[clang] 13a8aa3 - [clang] RecursiveASTVisitor visits ObjCPropertyRefExpr's class receiver
Author: David Goldman Date: 2021-06-01T14:45:25-04:00 New Revision: 13a8aa3ee15a67048deeb193fe8b86005fdf9d10 URL: https://github.com/llvm/llvm-project/commit/13a8aa3ee15a67048deeb193fe8b86005fdf9d10 DIFF: https://github.com/llvm/llvm-project/commit/13a8aa3ee15a67048deeb193fe8b86005fdf9d10.diff LOG: [clang] RecursiveASTVisitor visits ObjCPropertyRefExpr's class receiver We now make up a TypeLoc for the class receiver to simplify visiting, notably for indexing, availability, and clangd. Differential Revision: https://reviews.llvm.org/D101645 Added: Modified: clang-tools-extra/clangd/FindTarget.cpp clang-tools-extra/clangd/unittests/FindTargetTests.cpp clang/include/clang/AST/RecursiveASTVisitor.h clang/lib/Index/IndexBody.cpp clang/lib/Sema/SemaAvailability.cpp clang/test/Index/Core/index-source.m Removed: diff --git a/clang-tools-extra/clangd/FindTarget.cpp b/clang-tools-extra/clangd/FindTarget.cpp index 4789d28ecf48c..c8eca4e7ca8c7 100644 --- a/clang-tools-extra/clangd/FindTarget.cpp +++ b/clang-tools-extra/clangd/FindTarget.cpp @@ -306,9 +306,6 @@ struct TargetFinder { Outer.add(OME->getMethodDecl(), Flags); } void VisitObjCPropertyRefExpr(const ObjCPropertyRefExpr *OPRE) { -// FIXME: We miss visiting the class receiver if one exists, which -// means we skip the corresponding ObjCInterfaceDecl ref since it -// doesn't have a corresponding node. if (OPRE->isExplicitProperty()) Outer.add(OPRE->getExplicitProperty(), Flags); else { @@ -766,13 +763,6 @@ llvm::SmallVector refInStmt(const Stmt *S, } void VisitObjCPropertyRefExpr(const ObjCPropertyRefExpr *E) { - // There's no contained TypeLoc node for a class receiver type. - if (E->isClassReceiver()) { -Refs.push_back(ReferenceLoc{NestedNameSpecifierLoc(), -E->getReceiverLocation(), -/*IsDecl=*/false, -{E->getClassReceiver()}}); - } Refs.push_back(ReferenceLoc{ NestedNameSpecifierLoc(), E->getLocation(), /*IsDecl=*/false, diff --git a/clang-tools-extra/clangd/unittests/FindTargetTests.cpp b/clang-tools-extra/clangd/unittests/FindTargetTests.cpp index beca7c292583d..67dcb574d500a 100644 --- a/clang-tools-extra/clangd/unittests/FindTargetTests.cpp +++ b/clang-tools-extra/clangd/unittests/FindTargetTests.cpp @@ -981,8 +981,7 @@ TEST_F(TargetDeclTest, ObjC) { id value = [[Foo]].sharedInstance; } )cpp"; - // FIXME: We currently can't identify the interface here. - EXPECT_DECLS("ObjCPropertyRefExpr", "+ (id)sharedInstance"); + EXPECT_DECLS("ObjCInterfaceTypeLoc", "@interface Foo"); Code = R"cpp( @interface Foo diff --git a/clang/include/clang/AST/RecursiveASTVisitor.h b/clang/include/clang/AST/RecursiveASTVisitor.h index b3ea6f7817ef9..0623a5c8d5ea8 100644 --- a/clang/include/clang/AST/RecursiveASTVisitor.h +++ b/clang/include/clang/AST/RecursiveASTVisitor.h @@ -2638,7 +2638,16 @@ DEF_TRAVERSE_STMT(ObjCMessageExpr, { TRY_TO(TraverseTypeLoc(TInfo->getTypeLoc())); }) -DEF_TRAVERSE_STMT(ObjCPropertyRefExpr, {}) +DEF_TRAVERSE_STMT(ObjCPropertyRefExpr, { + if (S->isClassReceiver()) { +ObjCInterfaceDecl *IDecl = S->getClassReceiver(); +QualType Type = IDecl->getASTContext().getObjCInterfaceType(IDecl); +ObjCInterfaceLocInfo Data; +Data.NameLoc = S->getReceiverLocation(); +Data.NameEndLoc = Data.NameLoc; +TRY_TO(TraverseTypeLoc(TypeLoc(Type, &Data))); + } +}) DEF_TRAVERSE_STMT(ObjCSubscriptRefExpr, {}) DEF_TRAVERSE_STMT(ObjCProtocolExpr, {}) DEF_TRAVERSE_STMT(ObjCSelectorExpr, {}) diff --git a/clang/lib/Index/IndexBody.cpp b/clang/lib/Index/IndexBody.cpp index 7279e3b53c815..fa35f749d0284 100644 --- a/clang/lib/Index/IndexBody.cpp +++ b/clang/lib/Index/IndexBody.cpp @@ -286,9 +286,6 @@ class BodyIndexer : public RecursiveASTVisitor { } bool VisitObjCPropertyRefExpr(ObjCPropertyRefExpr *E) { -if (E->isClassReceiver()) - IndexCtx.handleReference(E->getClassReceiver(), E->getReceiverLocation(), - Parent, ParentDC); if (E->isExplicitProperty()) { SmallVector Relations; SymbolRoleSet Roles = getRolesForRef(E, Relations); diff --git a/clang/lib/Sema/SemaAvailability.cpp b/clang/lib/Sema/SemaAvailability.cpp index d69600aaf94ea..bb704b1066cf8 100644 --- a/clang/lib/Sema/SemaAvailability.cpp +++ b/clang/lib/Sema/SemaAvailability.cpp @@ -683,11 +683,7 @@ class DiagnoseUnguardedAvailability // to any useful diagnostics. bool TraverseCaseStmt(CaseStmt *CS) { return TraverseStmt(CS->getSubStmt()); } - bool VisitObjCPropertyRefExpr(ObjCPropertyRefExpr *PRE) { -if (PRE->isClassReceiver()) - DiagnoseDeclAvailability(PRE->getClassReceiver(), PRE->getRecei
[clang-tools-extra] 2f951ca - [clangd] Add support for the `defaultLibrary` semantic token modifier
Author: David Goldman Date: 2021-06-02T10:24:29-04:00 New Revision: 2f951ca98b7a12fed7ac4ebf790a0fbabc02d80b URL: https://github.com/llvm/llvm-project/commit/2f951ca98b7a12fed7ac4ebf790a0fbabc02d80b DIFF: https://github.com/llvm/llvm-project/commit/2f951ca98b7a12fed7ac4ebf790a0fbabc02d80b.diff LOG: [clangd] Add support for the `defaultLibrary` semantic token modifier This allows us to differentiate symbols from the system (e.g. system includes or sysroot) differently than symbols defined in the user's project, which can be used by editors to display them differently. This is currently based on `FileCharacteristic`, but we can consider alternatives such as `Sysroot` and file paths in the future. Differential Revision: https://reviews.llvm.org/D101554 Added: Modified: clang-tools-extra/clangd/SemanticHighlighting.cpp clang-tools-extra/clangd/SemanticHighlighting.h clang-tools-extra/clangd/test/initialize-params.test clang-tools-extra/clangd/test/semantic-tokens.test clang-tools-extra/clangd/unittests/SemanticHighlightingTests.cpp Removed: diff --git a/clang-tools-extra/clangd/SemanticHighlighting.cpp b/clang-tools-extra/clangd/SemanticHighlighting.cpp index 12ccdff82d028..bb192596f8c52 100644 --- a/clang-tools-extra/clangd/SemanticHighlighting.cpp +++ b/clang-tools-extra/clangd/SemanticHighlighting.cpp @@ -246,6 +246,29 @@ bool isDependent(const Decl *D) { return false; } +/// Returns true if `Decl` is considered to be from a default/system library. +/// This currently checks the systemness of the file by include type, although +/// diff erent heuristics may be used in the future (e.g. sysroot paths). +bool isDefaultLibrary(const Decl *D) { + SourceLocation Loc = D->getLocation(); + if (!Loc.isValid()) +return false; + return D->getASTContext().getSourceManager().isInSystemHeader(Loc); +} + +bool isDefaultLibrary(const Type *T) { + if (!T) +return false; + const Type *Underlying = T->getPointeeOrArrayElementType(); + if (Underlying->isBuiltinType()) +return true; + if (auto *TD = dyn_cast(Underlying)) +return isDefaultLibrary(TD->getDecl()); + if (auto *TD = Underlying->getAsTagDecl()) +return isDefaultLibrary(TD); + return false; +} + // For a macro usage `DUMP(foo)`, we want: // - DUMP --> "macro" // - foo --> "variable". @@ -473,6 +496,8 @@ class CollectExtraHighlightings .addModifier(HighlightingModifier::Deduced); if (auto Mod = scopeModifier(L.getTypePtr())) Tok.addModifier(*Mod); + if (isDefaultLibrary(L.getTypePtr())) +Tok.addModifier(HighlightingModifier::DefaultLibrary); } return true; } @@ -485,8 +510,11 @@ class CollectExtraHighlightings H.getResolver())) { auto &Tok = H.addToken(D->getTypeSpecStartLoc(), *K) .addModifier(HighlightingModifier::Deduced); - if (auto Mod = scopeModifier(AT->getDeducedType().getTypePtrOrNull())) + const Type *Deduced = AT->getDeducedType().getTypePtrOrNull(); + if (auto Mod = scopeModifier(Deduced)) Tok.addModifier(*Mod); + if (isDefaultLibrary(Deduced)) +Tok.addModifier(HighlightingModifier::DefaultLibrary); } return true; } @@ -494,7 +522,7 @@ class CollectExtraHighlightings // We handle objective-C selectors specially, because one reference can // cover several non-contiguous tokens. void highlightObjCSelector(const ArrayRef &Locs, bool Decl, - bool Class) { + bool Class, bool DefaultLibrary) { HighlightingKind Kind = Class ? HighlightingKind::StaticMethod : HighlightingKind::Method; for (SourceLocation Part : Locs) { @@ -504,20 +532,27 @@ class CollectExtraHighlightings Tok.addModifier(HighlightingModifier::Declaration); if (Class) Tok.addModifier(HighlightingModifier::Static); + if (DefaultLibrary) +Tok.addModifier(HighlightingModifier::DefaultLibrary); } } bool VisitObjCMethodDecl(ObjCMethodDecl *OMD) { llvm::SmallVector Locs; OMD->getSelectorLocs(Locs); -highlightObjCSelector(Locs, /*Decl=*/true, OMD->isClassMethod()); +highlightObjCSelector(Locs, /*Decl=*/true, OMD->isClassMethod(), + isDefaultLibrary(OMD)); return true; } bool VisitObjCMessageExpr(ObjCMessageExpr *OME) { llvm::SmallVector Locs; OME->getSelectorLocs(Locs); -highlightObjCSelector(Locs, /*Decl=*/false, OME->isClassMessage()); +bool DefaultLibrary = false; +if (ObjCMethodDecl *OMD = OME->getMethodDecl()) + DefaultLibrary = isDefaultLibrary(OMD); +highlightObjCSelector(Locs, /*Decl=*/false, OME->isClassMessage(), + DefaultLibrary); return true; } @@ -643,6 +678,8 @@ std::vector getSemanticHighlightings(Pa
[clang] d75fb1e - [clangd] Support `#pragma mark` in the outline
Author: David Goldman Date: 2021-09-23T17:13:30-04:00 New Revision: d75fb1ee794e94a011e88739df84c359c987a65b URL: https://github.com/llvm/llvm-project/commit/d75fb1ee794e94a011e88739df84c359c987a65b DIFF: https://github.com/llvm/llvm-project/commit/d75fb1ee794e94a011e88739df84c359c987a65b.diff LOG: [clangd] Support `#pragma mark` in the outline Xcode uses `#pragma mark -` to draw a divider in the outline view and `#pragma mark Note` to add `Note` in the outline view. For more information, see https://nshipster.com/pragma/. Since the LSP spec doesn't contain dividers for the symbol outline, instead we treat `#pragma mark -` as a group with children - the decls that come after it, implicitly terminating when the symbol's parent ends. The following code: ``` @implementation MyClass - (id)init {} - (int)foo; @end ``` Would give an outline like ``` MyClass > Overrides > init > Public Accessors > foo ``` Differential Revision: https://reviews.llvm.org/D105904 Added: Modified: clang-tools-extra/clangd/CollectMacros.cpp clang-tools-extra/clangd/CollectMacros.h clang-tools-extra/clangd/FindSymbols.cpp clang-tools-extra/clangd/ParsedAST.cpp clang-tools-extra/clangd/ParsedAST.h clang-tools-extra/clangd/Preamble.cpp clang-tools-extra/clangd/Preamble.h clang-tools-extra/clangd/SourceCode.cpp clang-tools-extra/clangd/SourceCode.h clang-tools-extra/clangd/unittests/FindSymbolsTests.cpp clang-tools-extra/clangd/unittests/ParsedASTTests.cpp clang/include/clang/Lex/PPCallbacks.h Removed: diff --git a/clang-tools-extra/clangd/CollectMacros.cpp b/clang-tools-extra/clangd/CollectMacros.cpp index 0e89b35d9d56d..9bcc3c1995415 100644 --- a/clang-tools-extra/clangd/CollectMacros.cpp +++ b/clang-tools-extra/clangd/CollectMacros.cpp @@ -30,5 +30,33 @@ void CollectMainFileMacros::add(const Token &MacroNameTok, const MacroInfo *MI, else Out.UnknownMacros.push_back({Range, IsDefinition}); } + +class CollectPragmaMarks : public PPCallbacks { +public: + explicit CollectPragmaMarks(const SourceManager &SM, + std::vector &Out) + : SM(SM), Out(Out) {} + + void PragmaMark(SourceLocation Loc, StringRef Trivia) override { +if (isInsideMainFile(Loc, SM)) { + // FIXME: This range should just cover `XX` in `#pragma mark XX` and + // `- XX` in `#pragma mark - XX`. + Position Start = sourceLocToPosition(SM, Loc); + Position End = {Start.line + 1, 0}; + Out.emplace_back(clangd::PragmaMark{{Start, End}, Trivia.str()}); +} + } + +private: + const SourceManager &SM; + std::vector &Out; +}; + +std::unique_ptr +collectPragmaMarksCallback(const SourceManager &SM, + std::vector &Out) { + return std::make_unique(SM, Out); +} + } // namespace clangd } // namespace clang diff --git a/clang-tools-extra/clangd/CollectMacros.h b/clang-tools-extra/clangd/CollectMacros.h index 3240111e5a33d..2167ebe2e3560 100644 --- a/clang-tools-extra/clangd/CollectMacros.h +++ b/clang-tools-extra/clangd/CollectMacros.h @@ -99,6 +99,18 @@ class CollectMainFileMacros : public PPCallbacks { MainFileMacros &Out; }; +/// Represents a `#pragma mark` in the main file. +/// +/// There can be at most one pragma mark per line. +struct PragmaMark { + Range Rng; + std::string Trivia; +}; + +/// Collect all pragma marks from the main file. +std::unique_ptr +collectPragmaMarksCallback(const SourceManager &, std::vector &Out); + } // namespace clangd } // namespace clang diff --git a/clang-tools-extra/clangd/FindSymbols.cpp b/clang-tools-extra/clangd/FindSymbols.cpp index e4846ac7a59d3..edbeeed9e2ca6 100644 --- a/clang-tools-extra/clangd/FindSymbols.cpp +++ b/clang-tools-extra/clangd/FindSymbols.cpp @@ -523,9 +523,135 @@ class DocumentOutline { ParsedAST &AST; }; +struct PragmaMarkSymbol { + DocumentSymbol DocSym; + bool IsGroup; +}; + +/// Merge in `PragmaMarkSymbols`, sorted ascending by range, into the given +/// `DocumentSymbol` tree. +void mergePragmas(DocumentSymbol &Root, ArrayRef Pragmas) { + while (!Pragmas.empty()) { +// We'll figure out where the Pragmas.front() should go. +PragmaMarkSymbol P = std::move(Pragmas.front()); +Pragmas = Pragmas.drop_front(); +DocumentSymbol *Cur = &Root; +while (Cur->range.contains(P.DocSym.range)) { + bool Swapped = false; + for (auto &C : Cur->children) { +// We assume at most 1 child can contain the pragma (as pragmas are on +// a single line, and children have disjoint ranges). +if (C.range.contains(P.DocSym.range)) { + Cur = &C; + Swapped = true; + break; +} + } + // Cur is the parent of P since none of the children contain P. + if (!Swapped) +break; +} +// Pragma isn't a group s
[clang-tools-extra] a90d57b - [clangd] Improve PopulateSwitch tweak
Author: David Goldman Date: 2021-10-04T10:15:37-04:00 New Revision: a90d57b6cc5f1237199fa6974eeb1cb168a7aed3 URL: https://github.com/llvm/llvm-project/commit/a90d57b6cc5f1237199fa6974eeb1cb168a7aed3 DIFF: https://github.com/llvm/llvm-project/commit/a90d57b6cc5f1237199fa6974eeb1cb168a7aed3.diff LOG: [clangd] Improve PopulateSwitch tweak - Support enums in C and ObjC as their AST representations differ slightly. - Add support for typedef'ed enums. Differential Revision: https://reviews.llvm.org/D110954 Added: Modified: clang-tools-extra/clangd/refactor/tweaks/PopulateSwitch.cpp clang-tools-extra/clangd/unittests/tweaks/PopulateSwitchTests.cpp Removed: diff --git a/clang-tools-extra/clangd/refactor/tweaks/PopulateSwitch.cpp b/clang-tools-extra/clangd/refactor/tweaks/PopulateSwitch.cpp index 40af43fef4aaf..4e2b15b8f22af 100644 --- a/clang-tools-extra/clangd/refactor/tweaks/PopulateSwitch.cpp +++ b/clang-tools-extra/clangd/refactor/tweaks/PopulateSwitch.cpp @@ -113,7 +113,8 @@ bool PopulateSwitch::prepare(const Selection &Sel) { return false; // Ignore implicit casts, since enums implicitly cast to integer types. Cond = Cond->IgnoreParenImpCasts(); - EnumT = Cond->getType()->getAsAdjusted(); + // Get the canonical type to handle typedefs. + EnumT = Cond->getType().getCanonicalType()->getAsAdjusted(); if (!EnumT) return false; EnumD = EnumT->getDecl(); @@ -152,14 +153,30 @@ bool PopulateSwitch::prepare(const Selection &Sel) { if (CS->caseStmtIsGNURange()) return false; +// Support for direct references to enum constants. This is required to +// support C and ObjC which don't contain values in their ConstantExprs. +// The general way to get the value of a case is EvaluateAsRValue, but we'd +// rather not deal with that in case the AST is broken. +if (auto *DRE = dyn_cast(CS->getLHS()->IgnoreParenCasts())) { + if (auto *Enumerator = dyn_cast(DRE->getDecl())) { +auto Iter = ExpectedCases.find(Normalize(Enumerator->getInitVal())); +if (Iter != ExpectedCases.end()) + Iter->second.setCovered(); +continue; + } +} + +// ConstantExprs with values are expected for C++, otherwise the storage +// kind will be None. + // Case expression is not a constant expression or is value-dependent, // so we may not be able to work out which cases are covered. const ConstantExpr *CE = dyn_cast(CS->getLHS()); if (!CE || CE->isValueDependent()) return false; -// Unsure if this case could ever come up, but prevents an unreachable -// executing in getResultAsAPSInt. +// We need a stored value in order to continue; currently both C and ObjC +// enums won't have one. if (CE->getResultStorageKind() == ConstantExpr::RSK_None) return false; auto Iter = ExpectedCases.find(Normalize(CE->getResultAsAPSInt())); diff --git a/clang-tools-extra/clangd/unittests/tweaks/PopulateSwitchTests.cpp b/clang-tools-extra/clangd/unittests/tweaks/PopulateSwitchTests.cpp index 13277c99cc314..05833736f3113 100644 --- a/clang-tools-extra/clangd/unittests/tweaks/PopulateSwitchTests.cpp +++ b/clang-tools-extra/clangd/unittests/tweaks/PopulateSwitchTests.cpp @@ -23,6 +23,7 @@ TEST_F(PopulateSwitchTest, Test) { CodeContext Context; llvm::StringRef TestSource; llvm::StringRef ExpectedSource; +llvm::StringRef FileName = "TestTU.cpp"; }; Case Cases[]{ @@ -206,10 +207,43 @@ TEST_F(PopulateSwitchTest, Test) { R""(template void f() {enum Enum {A}; ^switch (A) {}})"", "unavailable", }, + {// C: Only filling in missing enumerators + Function, + R""( +enum CEnum {A,B,C}; +enum CEnum val = A; +^switch (val) {case B:break;} + )"", + R""( +enum CEnum {A,B,C}; +enum CEnum val = A; +switch (val) {case B:break;case A:case C:break;} + )"", + "TestTU.c"}, + {// C: Only filling in missing enumerators w/ typedefs + Function, + R""( +typedef unsigned long UInteger; +enum ControlState : UInteger; +typedef enum ControlState ControlState; +enum ControlState : UInteger {A,B,C}; +ControlState controlState = A; +switch (^controlState) {case A:break;} + )"", + R""( +typedef unsigned long UInteger; +enum ControlState : UInteger; +typedef enum ControlState ControlState; +enum ControlState : UInteger {A,B,C}; +ControlState controlState = A; +switch (controlState) {case A:break;case B:case C:break;} + )"", + "TestTU.c"}, }; for (const auto &Case : Cases) { Context = Case.Context; +FileName = Case.FileName; EXPECT_EQ(apply(Case.T
[clang-tools-extra] 07c5a80 - Improve hover scopes for Objective-C code
Author: David Goldman Date: 2021-02-12T10:27:32-05:00 New Revision: 07c5a800dc1769f3f684d4a864f3903ac9ffa9f3 URL: https://github.com/llvm/llvm-project/commit/07c5a800dc1769f3f684d4a864f3903ac9ffa9f3 DIFF: https://github.com/llvm/llvm-project/commit/07c5a800dc1769f3f684d4a864f3903ac9ffa9f3.diff LOG: Improve hover scopes for Objective-C code - Instead of `AppDelegate::application:didFinishLaunchingWithOptions:` you will now see `-[AppDelegate application:didFinishLaunchingWithOptions:]` - Also include categories in the name when printing the scopes, e.g. `Class(Category)` and `-[Class(Category) method]` Differential Revision: https://reviews.llvm.org/D68590 Added: Modified: clang-tools-extra/clangd/AST.cpp clang-tools-extra/clangd/AST.h clang-tools-extra/clangd/Hover.cpp clang-tools-extra/clangd/unittests/HoverTests.cpp Removed: diff --git a/clang-tools-extra/clangd/AST.cpp b/clang-tools-extra/clangd/AST.cpp index aecaf7e6b8f7..abcfc1af848b 100644 --- a/clang-tools-extra/clangd/AST.cpp +++ b/clang-tools-extra/clangd/AST.cpp @@ -283,6 +283,52 @@ std::string printNamespaceScope(const DeclContext &DC) { return ""; } +static llvm::StringRef +getNameOrErrForObjCInterface(const ObjCInterfaceDecl *ID) { + return ID ? ID->getName() : "<>"; +} + +std::string printObjCMethod(const ObjCMethodDecl &Method) { + std::string Name; + llvm::raw_string_ostream OS(Name); + + OS << (Method.isInstanceMethod() ? '-' : '+') << '['; + + // Should always be true. + if (const ObjCContainerDecl *C = + dyn_cast(Method.getDeclContext())) +OS << printObjCContainer(*C); + + Method.getSelector().print(OS << ' '); + if (Method.isVariadic()) +OS << ", ..."; + + OS << ']'; + OS.flush(); + return Name; +} + +std::string printObjCContainer(const ObjCContainerDecl &C) { + if (const ObjCCategoryDecl *Category = dyn_cast(&C)) { +std::string Name; +llvm::raw_string_ostream OS(Name); +const ObjCInterfaceDecl *Class = Category->getClassInterface(); +OS << getNameOrErrForObjCInterface(Class) << '(' << Category->getName() + << ')'; +OS.flush(); +return Name; + } + if (const ObjCCategoryImplDecl *CID = dyn_cast(&C)) { +std::string Name; +llvm::raw_string_ostream OS(Name); +const ObjCInterfaceDecl *Class = CID->getClassInterface(); +OS << getNameOrErrForObjCInterface(Class) << '(' << CID->getName() << ')'; +OS.flush(); +return Name; + } + return C.getNameAsString(); +} + SymbolID getSymbolID(const Decl *D) { llvm::SmallString<128> USR; if (index::generateUSRForDecl(D, USR)) diff --git a/clang-tools-extra/clangd/AST.h b/clang-tools-extra/clangd/AST.h index b603964189e8..94984a915422 100644 --- a/clang-tools-extra/clangd/AST.h +++ b/clang-tools-extra/clangd/AST.h @@ -15,6 +15,7 @@ #include "index/SymbolID.h" #include "clang/AST/Decl.h" +#include "clang/AST/DeclObjC.h" #include "clang/AST/NestedNameSpecifier.h" #include "clang/Basic/SourceLocation.h" #include "clang/Lex/MacroInfo.h" @@ -64,6 +65,14 @@ std::string printName(const ASTContext &Ctx, const NamedDecl &ND); /// string if decl is not a template specialization. std::string printTemplateSpecializationArgs(const NamedDecl &ND); +/// Print the Objective-C method name, including the full container name, e.g. +/// `-[MyClass(Category) method:]` +std::string printObjCMethod(const ObjCMethodDecl &Method); + +/// Print the Objective-C container name including categories, e.g. `MyClass`, +// `MyClass()`, `MyClass(Category)`, and `MyProtocol`. +std::string printObjCContainer(const ObjCContainerDecl &C); + /// Gets the symbol ID for a declaration. Returned SymbolID might be null. SymbolID getSymbolID(const Decl *D); diff --git a/clang-tools-extra/clangd/Hover.cpp b/clang-tools-extra/clangd/Hover.cpp index 6d707c8d1521..42acebd7f293 100644 --- a/clang-tools-extra/clangd/Hover.cpp +++ b/clang-tools-extra/clangd/Hover.cpp @@ -22,6 +22,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/Expr.h" #include "clang/AST/ExprCXX.h" @@ -64,6 +65,15 @@ PrintingPolicy getPrintingPolicy(PrintingPolicy Base) { std::string getLocalScope(const Decl *D) { std::vector Scopes; const DeclContext *DC = D->getDeclContext(); + + // ObjC scopes won't have multiple components for us to join, instead: + // - Methods: "-[Class methodParam1:methodParam2]" + // - Classes, categories, and protocols: "MyClass(Category)" + if (const ObjCMethodDecl *MD = dyn_cast(DC)) +return printObjCMethod(*MD); + else if (const ObjCContainerDecl *CD = dyn_cast(DC)) +return printObjCContainer(*CD); + auto GetName = [](const TypeDecl *D) { if (!D->getDeclName().isEmpty()) { PrintingPolicy Policy = D->getASTContext().getPrintingPolicy(); @@ -90,6 +
[clang] ba06ac8 - [clangd] Support `#pragma mark` in the outline
Author: David Goldman Date: 2021-08-09T16:23:53-04:00 New Revision: ba06ac8b45ca2ad047131fb9cc9af922cb913ea1 URL: https://github.com/llvm/llvm-project/commit/ba06ac8b45ca2ad047131fb9cc9af922cb913ea1 DIFF: https://github.com/llvm/llvm-project/commit/ba06ac8b45ca2ad047131fb9cc9af922cb913ea1.diff LOG: [clangd] Support `#pragma mark` in the outline Xcode uses `#pragma mark -` to draw a divider in the outline view and `#pragma mark Note` to add `Note` in the outline view. For more information, see https://nshipster.com/pragma/. Since the LSP spec doesn't contain dividers for the symbol outline, instead we treat `#pragma mark -` as a group with children - the decls that come after it, implicitly terminating when the symbol's parent ends. The following code: ``` @implementation MyClass - (id)init {} - (int)foo; @end ``` Would give an outline like ``` MyClass > Overrides > init > Public Accessors > foo ``` Differential Revision: https://reviews.llvm.org/D105904 Added: Modified: clang-tools-extra/clangd/CollectMacros.cpp clang-tools-extra/clangd/CollectMacros.h clang-tools-extra/clangd/FindSymbols.cpp clang-tools-extra/clangd/ParsedAST.cpp clang-tools-extra/clangd/ParsedAST.h clang-tools-extra/clangd/Preamble.cpp clang-tools-extra/clangd/Preamble.h clang-tools-extra/clangd/SourceCode.cpp clang-tools-extra/clangd/SourceCode.h clang-tools-extra/clangd/unittests/FindSymbolsTests.cpp clang-tools-extra/clangd/unittests/ParsedASTTests.cpp clang/include/clang/Lex/PPCallbacks.h Removed: diff --git a/clang-tools-extra/clangd/CollectMacros.cpp b/clang-tools-extra/clangd/CollectMacros.cpp index 0e89b35d9d56d..9bcc3c1995415 100644 --- a/clang-tools-extra/clangd/CollectMacros.cpp +++ b/clang-tools-extra/clangd/CollectMacros.cpp @@ -30,5 +30,33 @@ void CollectMainFileMacros::add(const Token &MacroNameTok, const MacroInfo *MI, else Out.UnknownMacros.push_back({Range, IsDefinition}); } + +class CollectPragmaMarks : public PPCallbacks { +public: + explicit CollectPragmaMarks(const SourceManager &SM, + std::vector &Out) + : SM(SM), Out(Out) {} + + void PragmaMark(SourceLocation Loc, StringRef Trivia) override { +if (isInsideMainFile(Loc, SM)) { + // FIXME: This range should just cover `XX` in `#pragma mark XX` and + // `- XX` in `#pragma mark - XX`. + Position Start = sourceLocToPosition(SM, Loc); + Position End = {Start.line + 1, 0}; + Out.emplace_back(clangd::PragmaMark{{Start, End}, Trivia.str()}); +} + } + +private: + const SourceManager &SM; + std::vector &Out; +}; + +std::unique_ptr +collectPragmaMarksCallback(const SourceManager &SM, + std::vector &Out) { + return std::make_unique(SM, Out); +} + } // namespace clangd } // namespace clang diff --git a/clang-tools-extra/clangd/CollectMacros.h b/clang-tools-extra/clangd/CollectMacros.h index 3240111e5a33d..2167ebe2e3560 100644 --- a/clang-tools-extra/clangd/CollectMacros.h +++ b/clang-tools-extra/clangd/CollectMacros.h @@ -99,6 +99,18 @@ class CollectMainFileMacros : public PPCallbacks { MainFileMacros &Out; }; +/// Represents a `#pragma mark` in the main file. +/// +/// There can be at most one pragma mark per line. +struct PragmaMark { + Range Rng; + std::string Trivia; +}; + +/// Collect all pragma marks from the main file. +std::unique_ptr +collectPragmaMarksCallback(const SourceManager &, std::vector &Out); + } // namespace clangd } // namespace clang diff --git a/clang-tools-extra/clangd/FindSymbols.cpp b/clang-tools-extra/clangd/FindSymbols.cpp index e4846ac7a59d3..edbeeed9e2ca6 100644 --- a/clang-tools-extra/clangd/FindSymbols.cpp +++ b/clang-tools-extra/clangd/FindSymbols.cpp @@ -523,9 +523,135 @@ class DocumentOutline { ParsedAST &AST; }; +struct PragmaMarkSymbol { + DocumentSymbol DocSym; + bool IsGroup; +}; + +/// Merge in `PragmaMarkSymbols`, sorted ascending by range, into the given +/// `DocumentSymbol` tree. +void mergePragmas(DocumentSymbol &Root, ArrayRef Pragmas) { + while (!Pragmas.empty()) { +// We'll figure out where the Pragmas.front() should go. +PragmaMarkSymbol P = std::move(Pragmas.front()); +Pragmas = Pragmas.drop_front(); +DocumentSymbol *Cur = &Root; +while (Cur->range.contains(P.DocSym.range)) { + bool Swapped = false; + for (auto &C : Cur->children) { +// We assume at most 1 child can contain the pragma (as pragmas are on +// a single line, and children have disjoint ranges). +if (C.range.contains(P.DocSym.range)) { + Cur = &C; + Swapped = true; + break; +} + } + // Cur is the parent of P since none of the children contain P. + if (!Swapped) +break; +} +// Pragma isn't a group s
[clang-tools-extra] 5709842 - [clangd] Fix highlighting for implicit ObjC property refs
Author: David Goldman Date: 2021-06-30T12:31:50-04:00 New Revision: 570984204f24c326699dedcc05793b77b013f068 URL: https://github.com/llvm/llvm-project/commit/570984204f24c326699dedcc05793b77b013f068 DIFF: https://github.com/llvm/llvm-project/commit/570984204f24c326699dedcc05793b77b013f068.diff LOG: [clangd] Fix highlighting for implicit ObjC property refs Objective-C lets you use the `self.prop` syntax as sugar for both `[self prop]` and `[self setProp:]`, but clangd previously did not provide a semantic token for `prop`. Now, we provide a semantic token, treating it like a normal property except it's backed by a `ObjCMethodDecl` instead of a `ObjCPropertyDecl`. Differential Revision: https://reviews.llvm.org/D104117 Added: Modified: clang-tools-extra/clangd/SemanticHighlighting.cpp clang-tools-extra/clangd/unittests/SemanticHighlightingTests.cpp Removed: diff --git a/clang-tools-extra/clangd/SemanticHighlighting.cpp b/clang-tools-extra/clangd/SemanticHighlighting.cpp index bb192596f8c52..b49eb785f2deb 100644 --- a/clang-tools-extra/clangd/SemanticHighlighting.cpp +++ b/clang-tools-extra/clangd/SemanticHighlighting.cpp @@ -556,6 +556,42 @@ class CollectExtraHighlightings return true; } + // Objective-C allows you to use property syntax `self.prop` as sugar for + // `[self prop]` and `[self setProp:]` when there's no explicit `@property` + // for `prop` as well as for class properties. We treat this like a property + // even though semantically it's equivalent to a method expression. + void highlightObjCImplicitPropertyRef(const ObjCMethodDecl *OMD, +SourceLocation Loc) { +auto &Tok = H.addToken(Loc, HighlightingKind::Field) +.addModifier(HighlightingModifier::ClassScope); +if (OMD->isClassMethod()) + Tok.addModifier(HighlightingModifier::Static); +if (isDefaultLibrary(OMD)) + Tok.addModifier(HighlightingModifier::DefaultLibrary); + } + + bool VisitObjCPropertyRefExpr(ObjCPropertyRefExpr *OPRE) { +// We need to handle implicit properties here since they will appear to +// reference `ObjCMethodDecl` via an implicit `ObjCMessageExpr`, so normal +// highlighting will not work. +if (!OPRE->isImplicitProperty()) + return true; +// A single property expr can reference both a getter and setter, but we can +// only provide a single semantic token, so prefer the getter. In most cases +// the end result should be the same, although it's technically possible +// that the user defines a setter for a system SDK. +if (OPRE->isMessagingGetter()) { + highlightObjCImplicitPropertyRef(OPRE->getImplicitPropertyGetter(), + OPRE->getLocation()); + return true; +} +if (OPRE->isMessagingSetter()) { + highlightObjCImplicitPropertyRef(OPRE->getImplicitPropertySetter(), + OPRE->getLocation()); +} +return true; + } + bool VisitOverloadExpr(OverloadExpr *E) { if (!E->decls().empty()) return true; // handled by findExplicitReferences. diff --git a/clang-tools-extra/clangd/unittests/SemanticHighlightingTests.cpp b/clang-tools-extra/clangd/unittests/SemanticHighlightingTests.cpp index 02ab6bef0a817..a0212856427d6 100644 --- a/clang-tools-extra/clangd/unittests/SemanticHighlightingTests.cpp +++ b/clang-tools-extra/clangd/unittests/SemanticHighlightingTests.cpp @@ -696,11 +696,16 @@ sizeof...($TemplateParameter[[Elements]]); int $Field_decl[[_someProperty]]; } @property(nonatomic, assign) int $Field_decl[[someProperty]]; +@property(readonly, class) $Class[[Foo]] *$Field_decl_readonly_static[[sharedInstance]]; @end @implementation $Class_decl[[Foo]] @synthesize someProperty = _someProperty; +- (int)$Method_decl[[otherMethod]] { + return 0; +} - (int)$Method_decl[[doSomething]] { - self.$Field[[someProperty]] = self.$Field[[someProperty]] + 1; + $Class[[Foo]].$Field_static[[sharedInstance]].$Field[[someProperty]] = 1; + self.$Field[[someProperty]] = self.$Field[[someProperty]] + self.$Field[[otherMethod]] + 1; self->$Field[[_someProperty]] = $Field[[_someProperty]] + 1; } @end ___ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits