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 @@ -302,7 +302,20 @@ // Record the underlying decl instead, if allowed. D = USD->getTargetDecl(); Flags |= Rel::Underlying; // continue with the underlying decl. + } else if (const ObjCImplementationDecl *IID = + dyn_cast(D)) { + // Objective-C implementation should map back to its interface. + D = IID->getClassInterface(); + Flags |= Rel::Underlying; + } else if (const ObjCCategoryImplDecl *CID = + dyn_cast(D)) { + // Objective-C category implementation should map back to its category + // declaration. + D = CID->getCategoryDecl(); + Flags |= Rel::Underlying; } + 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 --- a/clang-tools-extra/clangd/XRefs.cpp +++ b/clang-tools-extra/clangd/XRefs.cpp @@ -78,6 +78,12 @@ return VD->getDefinition(); if (const auto *FD = dyn_cast(D)) return FD->getDefinition(); + if (const auto *PD = dyn_cast(D)) + return PD->getDefinition(); + if (const auto *ID = dyn_cast(D)) + return ID->getImplementation(); + if (const auto *CD = dyn_cast(D)) + return CD->getImplementation(); // Only a single declaration is allowed. if (isa(D) || isa(D) || isa(D)) // except cases above @@ -223,6 +229,30 @@ 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; +// - definition declaration: @interface MyClass ... @end +// - 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) { + D = llvm::cast(D->getCanonicalDecl()); + + // Prefer Objective-C class definitions over the forward declaration. + if (const auto *ID = dyn_cast(D)) + if (const auto *DefinitionID = ID->getDefinition()) + return DefinitionID; + + return D; +} + // Decls are more complicated. // The AST contains at least a declaration, maybe a definition. // These are up-to-date, and so generally preferred over index results. @@ -238,7 +268,7 @@ llvm::DenseMap ResultIndex; auto AddResultDecl = [&](const NamedDecl *D) { - D = llvm::cast(D->getCanonicalDecl()); + D = getPreferredDecl(D); auto Loc = makeLocation(AST.getASTContext(), nameLocation(*D, SM), MainFilePath); if (!Loc) 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 @@ -608,6 +608,25 @@ )cpp"; EXPECT_DECLS("ObjCInterfaceTypeLoc", "@interface Foo"); + Code = R"cpp( + @interface Foo + @end + @implementation [[Foo]] + @end + )cpp"; + EXPECT_DECLS("ObjCImplementationDecl", {"@interface Foo", Rel::Underlying}); + + Code = R"cpp( + @interface Foo + @end + @interface Foo (Ext) + @end + @implementation [[Foo]] (Ext) + @end + )cpp"; + EXPECT_DECLS("ObjCCategoryImplDecl", + {"@interface Foo(Ext)", Rel::Underlying}); + Code = R"cpp( @protocol Foo @end diff --git a/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp b/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp --- a/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp +++ b/clang-tools-extra/clangd/unittests/SymbolCollectorTests.cpp @@ -542,6 +542,29 @@ // Figure out why it's platform-dependent. } +TEST_F(SymbolCollectorTest, ObjCLocations) { + Annotations Header(R"( + // Declared in header, defined in main. + @interface $dogdecl[[Dog]] + @end + @interface $fluffydecl[[Dog]] (Fluffy) + @end + )"); + Annotations Main(R"( + @implementation $dogdef[[Dog]] + @end + @implementation $fluffydef[[Dog]] (Fluffy) + @end + )"); + runSymbolCollector(Header.code(), Main.code(), {"-xobjective-c++"}); + EXPECT_THAT(Symbols, + UnorderedElementsAre( + AllOf(QName("Dog"), DeclRange(Header.range("dogdecl")), + DefRange(Main.range("dogdef"))), + AllOf(QName("Fluffy"), DeclRange(Header.range("fluffydecl")), + DefRange(Main.range("fluffydef"))))); +} + TEST_F(SymbolCollectorTest, Locations) { Annotations Header(R"cpp( // Declared in header, defined in main. diff --git a/clang-tools-extra/clangd/unittests/XRefsTests.cpp b/clang-tools-extra/clangd/unittests/XRefsTests.cpp --- a/clang-tools-extra/clangd/unittests/XRefsTests.cpp +++ b/clang-tools-extra/clangd/unittests/XRefsTests.cpp @@ -674,7 +674,55 @@ enum class E { [[A]], B }; E e = E::A^; }; - )cpp"}; + )cpp", + + R"objc( + @protocol $decl[[Dog]]; + @protocol $def[[Dog]] + - (void)bark; + @end + id getDoggo() { + return 0; + } + )objc", + + R"objc( + @interface Cat + @end + @interface $decl[[Ca^t]] (Extension) + - (void)meow; + @end + @implementation $def[[Cat]] (Extension) + - (void)meow {} + @end + )objc", + + R"objc( + @class $decl[[Foo]]; + Fo^o * getFoo() { + return 0; + } + )objc", + + R"objc( + @class Foo; + @interface $decl[[Foo]] + @end + Fo^o * getFoo() { + return 0; + } + )objc", + + R"objc( + @class Foo; + @interface $decl[[Foo]] + @end + @implementation $def[[Foo]] + @end + Fo^o * getFoo() { + return 0; + } + )objc"}; for (const char *Test : Tests) { Annotations T(Test); llvm::Optional WantDecl; @@ -692,6 +740,7 @@ // FIXME: Auto-completion in a template requires disabling delayed template // parsing. TU.ExtraArgs.push_back("-fno-delayed-template-parsing"); + TU.ExtraArgs.push_back("-xobjective-c++"); auto AST = TU.build(); auto Results = locateSymbolAt(AST, T.point());