[clangd] Improve XRefs support for ObjCMethodDecl
authorDavid Goldman <davg@google.com>
Tue, 19 Jul 2022 16:10:28 +0000 (12:10 -0400)
committerDavid Goldman <davg@google.com>
Tue, 26 Jul 2022 16:11:26 +0000 (12:11 -0400)
- 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

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

index f7d526f..4ddfca3 100644 (file)
@@ -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<ObjCMethodDecl>(&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<ObjCInterfaceDecl>(D))
+    return ID->getImplementation();
+  if (const auto *CD = dyn_cast<ObjCCategoryDecl>(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;
index e0024c2..f313161 100644 (file)
@@ -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,
index c620b38..4682743 100644 (file)
@@ -83,32 +83,20 @@ const NamedDecl *getDefinition(const NamedDecl *D) {
   if (const auto *CTD = dyn_cast<ClassTemplateDecl>(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<ObjCInterfaceDecl>(D))
-    return ID->getImplementation();
-  if (const auto *CD = dyn_cast<ObjCCategoryDecl>(D)) {
-    if (CD->IsClassExtension()) {
-      if (const auto *ID = CD->getClassInterface())
-        return ID->getImplementation();
+  if (const auto *MD = dyn_cast<ObjCMethodDecl>(D)) {
+    if (MD->isThisDeclarationADefinition())
+      return MD;
+    // Look for the method definition inside the implementation decl.
+    auto *DeclCtx = cast<Decl>(MD->getDeclContext());
+    if (DeclCtx->isInvalidDecl())
       return nullptr;
-    }
-    return CD->getImplementation();
+
+    if (const auto *CD = dyn_cast<ObjCContainerDecl>(DeclCtx))
+      if (const auto *Impl = getCorrespondingObjCImpl(CD))
+        return Impl->getMethod(MD->getSelector(), MD->isInstanceMethod());
   }
+  if (const auto *CD = dyn_cast<ObjCContainerDecl>(D))
+    return getCorrespondingObjCImpl(CD);
   // Only a single declaration is allowed.
   if (isa<ValueDecl>(D) || isa<TemplateTypeParmDecl>(D) ||
       isa<TemplateTemplateParmDecl>(D)) // except cases above
index 9294aee..0f3a12b 100644 (file)
@@ -957,6 +957,46 @@ TEST(LocateSymbol, All) {
         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);