diff --git a/clang-tools-extra/clangd/FindTarget.cpp b/clang-tools-extra/clangd/FindTarget.cpp --- a/clang-tools-extra/clangd/FindTarget.cpp +++ b/clang-tools-extra/clangd/FindTarget.cpp @@ -704,8 +704,23 @@ {OCID->getClassInterface()}}); Refs.push_back(ReferenceLoc{NestedNameSpecifierLoc(), OCID->getCategoryNameLoc(), - /*IsDecl=*/true, + /*IsDecl=*/false, {OCID->getCategoryDecl()}}); + Refs.push_back(ReferenceLoc{NestedNameSpecifierLoc(), + OCID->getCategoryNameLoc(), + /*IsDecl=*/true, + {OCID}}); + } + + void VisitObjCImplementationDecl(const ObjCImplementationDecl *OIMD) { + Refs.push_back(ReferenceLoc{NestedNameSpecifierLoc(), + OIMD->getLocation(), + /*IsDecl=*/false, + {OIMD->getClassInterface()}}); + Refs.push_back(ReferenceLoc{NestedNameSpecifierLoc(), + OIMD->getLocation(), + /*IsDecl=*/true, + {OIMD}}); } }; diff --git a/clang-tools-extra/clangd/SemanticHighlighting.cpp b/clang-tools-extra/clangd/SemanticHighlighting.cpp --- a/clang-tools-extra/clangd/SemanticHighlighting.cpp +++ b/clang-tools-extra/clangd/SemanticHighlighting.cpp @@ -128,7 +128,7 @@ return HighlightingKind::Class; if (isa(D)) return HighlightingKind::Interface; - if (isa(D)) + if (isa(D)) return HighlightingKind::Namespace; if (auto *MD = dyn_cast(D)) return MD->isStatic() ? HighlightingKind::StaticMethod diff --git a/clang-tools-extra/clangd/refactor/Rename.cpp b/clang-tools-extra/clangd/refactor/Rename.cpp --- a/clang-tools-extra/clangd/refactor/Rename.cpp +++ b/clang-tools-extra/clangd/refactor/Rename.cpp @@ -19,6 +19,7 @@ #include "clang/AST/ASTTypeTraits.h" #include "clang/AST/Decl.h" #include "clang/AST/DeclCXX.h" +#include "clang/AST/DeclObjC.h" #include "clang/AST/DeclTemplate.h" #include "clang/AST/ParentMapContext.h" #include "clang/AST/Stmt.h" @@ -137,6 +138,10 @@ if (const auto *TargetDecl = UD->getTargetDecl()) return canonicalRenameDecl(TargetDecl); } + if (const auto *CD = dyn_cast(D)) { + if (const auto CI = CD->getClassInterface()) + return canonicalRenameDecl(CI); + } return dyn_cast(D->getCanonicalDecl()); } @@ -278,6 +283,10 @@ if (Ref.Targets.empty()) return; for (const auto *Target : Ref.Targets) { + // Skip categories since we don't support renaming them, only + // the interface(s) which they reference. + if (isa(Target)) + continue; if (canonicalRenameDecl(Target) == &ND) { Results.push_back(Ref.NameLoc); return; @@ -773,6 +782,12 @@ if (Invalid) return makeError(std::move(*Invalid)); + // We don't support renaming the category name, only ObjC top level container + // names like class and protocol names. + if (const auto *CD = dyn_cast(&RenameDecl)) + if (CD->getName() != IdentifierToken->text(SM)) + return makeError(ReasonToReject::UnsupportedSymbol); + auto Reject = renameable(RenameDecl, RInputs.MainFilePath, RInputs.Index, Opts); if (Reject) diff --git a/clang-tools-extra/clangd/unittests/FindTargetTests.cpp b/clang-tools-extra/clangd/unittests/FindTargetTests.cpp --- a/clang-tools-extra/clangd/unittests/FindTargetTests.cpp +++ b/clang-tools-extra/clangd/unittests/FindTargetTests.cpp @@ -1054,6 +1054,16 @@ )cpp"; EXPECT_DECLS("ObjCCategoryImplDecl", "@interface Foo(Ext)"); + Code = R"cpp( + @interface Foo + @end + @interface Foo (Ext) + @end + @implementation Foo ([[Ext]]) + @end + )cpp"; + EXPECT_DECLS("ObjCCategoryImplDecl", "@interface Foo(Ext)"); + Code = R"cpp( void test(id p); )cpp"; @@ -1143,10 +1153,7 @@ std::string DumpedReferences; }; - /// Parses \p Code, finds function or namespace '::foo' and annotates its body - /// with results of findExplicitReferences. - /// See actual tests for examples of annotation format. - AllRefs annotateReferencesInFoo(llvm::StringRef Code) { + TestTU newTU(llvm::StringRef Code) { TestTU TU; TU.Code = std::string(Code); @@ -1155,30 +1162,11 @@ TU.ExtraArgs.push_back("-std=c++20"); TU.ExtraArgs.push_back("-xobjective-c++"); - auto AST = TU.build(); - auto *TestDecl = &findDecl(AST, "foo"); - if (auto *T = llvm::dyn_cast(TestDecl)) - TestDecl = T->getTemplatedDecl(); - - std::vector Refs; - if (const auto *Func = llvm::dyn_cast(TestDecl)) - findExplicitReferences( - Func->getBody(), - [&Refs](ReferenceLoc R) { Refs.push_back(std::move(R)); }, - AST.getHeuristicResolver()); - else if (const auto *NS = llvm::dyn_cast(TestDecl)) - findExplicitReferences( - NS, - [&Refs, &NS](ReferenceLoc R) { - // Avoid adding the namespace foo decl to the results. - if (R.Targets.size() == 1 && R.Targets.front() == NS) - return; - Refs.push_back(std::move(R)); - }, - AST.getHeuristicResolver()); - else - ADD_FAILURE() << "Failed to find ::foo decl for test"; + return TU; + } + AllRefs annotatedReferences(llvm::StringRef Code, ParsedAST &AST, + std::vector Refs) { auto &SM = AST.getSourceManager(); llvm::sort(Refs, [&](const ReferenceLoc &L, const ReferenceLoc &R) { return SM.isBeforeInTranslationUnit(L.NameLoc, R.NameLoc); @@ -1215,6 +1203,57 @@ return AllRefs{std::move(AnnotatedCode), std::move(DumpedReferences)}; } + + /// Parses \p Code, and annotates its body with results of + /// findExplicitReferences on all top level decls. + /// See actual tests for examples of annotation format. + AllRefs annotateAllReferences(llvm::StringRef Code) { + TestTU TU = newTU(Code); + auto AST = TU.build(); + + std::vector Refs; + for (auto *TopLevel : AST.getLocalTopLevelDecls()) + findExplicitReferences( + TopLevel, [&Refs](ReferenceLoc R) { Refs.push_back(std::move(R)); }, + AST.getHeuristicResolver()); + return annotatedReferences(Code, AST, std::move(Refs)); + } + + /// Parses \p Code, finds function or namespace '::foo' and annotates its body + /// with results of findExplicitReferences. + /// See actual tests for examples of annotation format. + AllRefs annotateReferencesInFoo(llvm::StringRef Code) { + TestTU TU = newTU(Code); + auto AST = TU.build(); + auto *TestDecl = &findDecl(AST, "foo"); + if (auto *T = llvm::dyn_cast(TestDecl)) + TestDecl = T->getTemplatedDecl(); + + std::vector Refs; + if (const auto *Func = llvm::dyn_cast(TestDecl)) + findExplicitReferences( + Func->getBody(), + [&Refs](ReferenceLoc R) { Refs.push_back(std::move(R)); }, + AST.getHeuristicResolver()); + else if (const auto *NS = llvm::dyn_cast(TestDecl)) + findExplicitReferences( + NS, + [&Refs, &NS](ReferenceLoc R) { + // Avoid adding the namespace foo decl to the results. + if (R.Targets.size() == 1 && R.Targets.front() == NS) + return; + Refs.push_back(std::move(R)); + }, + AST.getHeuristicResolver()); + else if (const auto *OC = llvm::dyn_cast(TestDecl)) + findExplicitReferences( + OC, [&Refs](ReferenceLoc R) { Refs.push_back(std::move(R)); }, + AST.getHeuristicResolver()); + else + ADD_FAILURE() << "Failed to find ::foo decl for test"; + + return annotatedReferences(Code, AST, std::move(Refs)); + } }; TEST_F(FindExplicitReferencesTest, All) { @@ -1947,6 +1986,41 @@ EXPECT_EQ(ExpectedCode, Actual.AnnotatedCode); EXPECT_EQ(ExpectedRefs, Actual.DumpedReferences) << ExpectedCode; } + + std::pair + AllRefsCases[] = {// Simple expressions. + {R"cpp( + @interface $0^MyClass + @end + @implementation $1^$2^MyClass + @end + )cpp", + "0: targets = {MyClass}, decl\n" + "1: targets = {MyClass}\n" + "2: targets = {MyClass}, decl\n"}, + {R"cpp( + @interface $0^MyClass + @end + @interface $1^MyClass ($2^Category) + @end + @implementation $3^MyClass ($4^$5^Category) + @end + )cpp", + "0: targets = {MyClass}, decl\n" + "1: targets = {MyClass}\n" + "2: targets = {Category}, decl\n" + "3: targets = {MyClass}\n" + "4: targets = {Category}\n" + "5: targets = {Category}, decl\n"}}; + + for (const auto &C : AllRefsCases) { + llvm::StringRef ExpectedCode = C.first; + llvm::StringRef ExpectedRefs = C.second; + + auto Actual = annotateAllReferences(llvm::Annotations(ExpectedCode).code()); + EXPECT_EQ(ExpectedCode, Actual.AnnotatedCode); + EXPECT_EQ(ExpectedRefs, Actual.DumpedReferences) << ExpectedCode; + } } } // namespace diff --git a/clang-tools-extra/clangd/unittests/RenameTests.cpp b/clang-tools-extra/clangd/unittests/RenameTests.cpp --- a/clang-tools-extra/clangd/unittests/RenameTests.cpp +++ b/clang-tools-extra/clangd/unittests/RenameTests.cpp @@ -840,6 +840,20 @@ foo('x'); } )cpp", + + // ObjC class with a category. + R"cpp( + @interface [[Fo^o]] + @end + @implementation [[F^oo]] + @end + @interface [[Fo^o]] (Category) + @end + @implementation [[F^oo]] (Category) + @end + + void func([[Fo^o]] *f) {} + )cpp", }; llvm::StringRef NewName = "NewName"; for (llvm::StringRef T : Tests) { @@ -890,6 +904,14 @@ )cpp", "not a supported kind", HeaderFile}, + {R"cpp(// disallow - category rename. + @interface Foo + @end + @interface Foo (Cate^gory) + @end + )cpp", + "not a supported kind", HeaderFile}, + { R"cpp( #define MACRO 1 @@ -1468,7 +1490,7 @@ TEST(CrossFileRenameTests, WithUpToDateIndex) { MockCompilationDatabase CDB; - CDB.ExtraClangFlags = {"-xc++"}; + CDB.ExtraClangFlags = {"-xobjective-c++"}; // rename is runnning on all "^" points in FooH, and "[[]]" ranges are the // expected rename occurrences. struct Case { @@ -1557,13 +1579,12 @@ } )cpp", }, - { - // virtual templated method - R"cpp( + {// virtual templated method + R"cpp( template class Foo { virtual void [[m]](); }; class Bar : Foo { void [[^m]]() override; }; )cpp", - R"cpp( + R"cpp( #include "foo.h" template void Foo::[[m]]() {} @@ -1571,8 +1592,7 @@ // the canonical Foo::m(). // https://github.com/clangd/clangd/issues/1325 class Baz : Foo { void m() override; }; - )cpp" - }, + )cpp"}, { // rename on constructor and destructor. R"cpp( @@ -1677,6 +1697,20 @@ } )cpp", }, + { + // Objective-C classes. + R"cpp( + @interface [[Fo^o]] + @end + )cpp", + R"cpp( + #include "foo.h" + @implementation [[F^oo]] + @end + + void func([[Foo]] *f) {} + )cpp", + }, }; trace::TestTracer Tracer;