diff --git a/clang-tools-extra/clangd/AST.h b/clang-tools-extra/clangd/AST.h --- a/clang-tools-extra/clangd/AST.h +++ b/clang-tools-extra/clangd/AST.h @@ -93,6 +93,30 @@ 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/AST.cpp b/clang-tools-extra/clangd/AST.cpp --- a/clang-tools-extra/clangd/AST.cpp +++ b/clang-tools-extra/clangd/AST.cpp @@ -171,6 +171,9 @@ 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 @@ 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/XRefs.cpp b/clang-tools-extra/clangd/XRefs.cpp --- a/clang-tools-extra/clangd/XRefs.cpp +++ b/clang-tools-extra/clangd/XRefs.cpp @@ -83,32 +83,20 @@ if (const auto *CTD = dyn_cast(D)) if (const auto *RD = CTD->getTemplatedDecl()) return RD->getDefinition(); - // Objective-C classes can have three types of declarations: - // - // - forward declaration: @class MyClass; - // - true declaration (interface definition): @interface MyClass ... @end - // - true definition (implementation): @implementation MyClass ... @end - // - // Objective-C categories are extensions are on classes: - // - // - declaration: @interface MyClass (Ext) ... @end - // - definition: @implementation MyClass (Ext) ... @end - // - // With one special case, a class extension, which is normally used to keep - // some declarations internal to a file without exposing them in a header. - // - // - class extension declaration: @interface MyClass () ... @end - // - which really links to class 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(); + if (const auto *MD = dyn_cast(D)) { + if (MD->isThisDeclarationADefinition()) + return MD; + // Look for the method definition inside the implementation decl. + auto *DeclCtx = cast(MD->getDeclContext()); + if (DeclCtx->isInvalidDecl()) return nullptr; - } - return CD->getImplementation(); + + if (const auto *CD = dyn_cast(DeclCtx)) + if (const auto *Impl = getCorrespondingObjCImpl(CD)) + return Impl->getMethod(MD->getSelector(), MD->isInstanceMethod()); } + if (const auto *CD = dyn_cast(D)) + return getCorrespondingObjCImpl(CD); // Only a single declaration is allowed. if (isa(D) || isa(D) || isa(D)) // except cases above 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 @@ -957,6 +957,46 @@ Fo^o * getFoo() { return 0; } + )objc", + + R"objc(// Method decl and definition for ObjC class. + @interface Cat + - (void)$decl[[meow]]; + @end + @implementation Cat + - (void)$def[[meow]] {} + @end + void makeNoise(Cat *kitty) { + [kitty me^ow]; + } + )objc", + + R"objc(// Method decl and definition for ObjC category. + @interface Dog + @end + @interface Dog (Play) + - (void)$decl[[runAround]]; + @end + @implementation Dog (Play) + - (void)$def[[runAround]] {} + @end + void play(Dog *dog) { + [dog run^Around]; + } + )objc", + + R"objc(// Method decl and definition for ObjC class extension. + @interface Dog + @end + @interface Dog () + - (void)$decl[[howl]]; + @end + @implementation Dog + - (void)$def[[howl]] {} + @end + void play(Dog *dog) { + [dog ho^wl]; + } )objc"}; for (const char *Test : Tests) { Annotations T(Test);