dgoldman created this revision.
dgoldman added a reviewer: sammccall.
Herald added subscribers: cfe-commits, usaxena95, kadircet, jfb, arphaman, 
jkorous, MaskRay, ilya-biryukov.
Herald added a project: clang.

- Instead of `AppDelegate::application:didFinishLaunchingWithOptions:` you will 
now see `-[AppDelegate application:didFinishLaunchingWithOptions:]`

- Also include support for Objective-C categories so you will see 
`Class(Category)` or `-[Class(Category) method]`


Repository:
  rCTE Clang Tools Extra

https://reviews.llvm.org/D68590

Files:
  clangd/XRefs.cpp
  clangd/unittests/XRefsTests.cpp

Index: clangd/unittests/XRefsTests.cpp
===================================================================
--- clangd/unittests/XRefsTests.cpp
+++ clangd/unittests/XRefsTests.cpp
@@ -1026,6 +1026,186 @@
   }
 } // namespace clang
 
+TEST(Hover, ObjectiveC) {
+  struct {
+    const char *const Code;
+    const std::function<void(HoverInfo &)> ExpectedBuilder;
+  } Cases[] = {
+      // Property reference: LocalScope resolution to interface.
+      {R"objc(
+        @interface Foo
+        @property(nonatomic, assign) int field;
+
+        + (instancetype)sharedInstance;
+        @end
+
+        int someFunction() {
+          Foo *foo = [Foo sharedInstance];
+          return foo.[[fie^ld]];
+        }
+        )objc",
+       [](HoverInfo &HI) {
+         HI.Name = "field";
+         HI.Definition = "@property(nonatomic, assign, unsafe_unretained, readwrite) int field;";
+         HI.Kind = SymbolKind::Property;
+         HI.LocalScope = "Foo::";
+         HI.NamespaceScope = "";
+       }},
+      // Property reference: LocalScope resolution to interface category.
+      {R"objc(
+        @interface Foo
+        + (instancetype)sharedInstance;
+        @end
+
+        @interface Foo (Private)
+        @property(nonatomic, assign) int privateField;
+        @end
+
+        int someFunction() {
+          Foo *foo = [Foo sharedInstance];
+          return foo.[[private^Field]];
+        }
+        )objc",
+       [](HoverInfo &HI) {
+         HI.Name = "privateField";
+         HI.Definition = "@property(nonatomic, assign, unsafe_unretained, readwrite) int privateField;";
+         HI.Kind = SymbolKind::Property;
+         HI.LocalScope = "Foo(Private)::";
+         HI.NamespaceScope = "";
+       }},
+      // Variable declaration in method: LocalScope resolution to implementation.
+      {R"objc(
+        @interface Foo
+        @property(nonatomic, assign) float someNumber;
+        @end
+
+        @implementation Foo
+        - (float)someMethod {
+          float [[res^ult]] = self.someNumber;
+          return result;
+        }
+        @end
+        )objc",
+       [](HoverInfo &HI) {
+         HI.Name = "result";
+         HI.Definition = "float result = self.someNumber";
+         HI.Kind = SymbolKind::Variable;
+         HI.Type = "float";
+         HI.LocalScope = "-[Foo someMethod]::";
+         HI.NamespaceScope = "";
+       }},
+      // Variable declaration in method: LocalScope resolution to category implementation.
+      {R"objc(
+        @interface Foo
+        @property(nonatomic, assign) int someNumber;
+        @end
+
+        @implementation Foo(Private)
+        + (int)somePrivateMethod {
+          int [[res^ult]] = 2;
+          return result;
+        }
+        @end
+        )objc",
+       [](HoverInfo &HI) {
+         HI.Name = "result";
+         HI.Definition = "int result = 2";
+         HI.Kind = SymbolKind::Variable;
+         HI.Type = "int";
+         HI.LocalScope = "+[Foo(Private) somePrivateMethod]::";
+         HI.NamespaceScope = "";
+         HI.Value = "2";
+       }},
+  };
+  for (const auto &Case : Cases) {
+    SCOPED_TRACE(Case.Code);
+
+    Annotations T(Case.Code);
+    TestTU TU = TestTU::withCode(T.code());
+    TU.Filename = "TestTU.m";
+    TU.ExtraArgs.push_back("-Wno-objc-root-class");
+    auto AST = TU.build();
+
+    ASSERT_TRUE(AST.getDiagnostics().empty())
+        << AST.getDiagnostics().begin()->Message;
+
+    auto H = getHover(AST, T.point(), format::getLLVMStyle(), nullptr);
+    ASSERT_TRUE(H);
+    HoverInfo Expected;
+    Expected.SymRange = T.range();
+    Case.ExpectedBuilder(Expected);
+
+    EXPECT_EQ(H->NamespaceScope, Expected.NamespaceScope);
+    EXPECT_EQ(H->LocalScope, Expected.LocalScope);
+    EXPECT_EQ(H->Name, Expected.Name);
+    EXPECT_EQ(H->Kind, Expected.Kind);
+    EXPECT_EQ(H->Documentation, Expected.Documentation);
+    EXPECT_EQ(H->Definition, Expected.Definition);
+    EXPECT_EQ(H->Type, Expected.Type);
+    EXPECT_EQ(H->ReturnType, Expected.ReturnType);
+    EXPECT_EQ(H->Parameters, Expected.Parameters);
+    EXPECT_EQ(H->TemplateParameters, Expected.TemplateParameters);
+    EXPECT_EQ(H->SymRange, Expected.SymRange);
+    EXPECT_EQ(H->Value, Expected.Value);
+  }
+}
+
+TEST(Hover, ObjectiveCInterfaceInferred) {
+  struct {
+    const char *const Code;
+    const std::function<void(HoverInfo &)> ExpectedBuilder;
+  } Cases[] = {
+      // No interface, variable declaration in method: LocalScope resolution to implementation.
+      {R"objc(
+        @implementation Foo
+        - (int)value {
+          int [[res^ult]] = 50;
+          return result;
+        }
+        @end
+        )objc",
+       [](HoverInfo &HI) {
+         HI.Name = "result";
+         HI.Definition = "int result = 50";
+         HI.Kind = SymbolKind::Variable;
+         HI.Type = "int";
+         HI.LocalScope = "-[Foo value]::";
+         HI.NamespaceScope = "";
+         HI.Value = "50";
+       }},
+  };
+  for (const auto &Case : Cases) {
+    SCOPED_TRACE(Case.Code);
+
+    Annotations T(Case.Code);
+    TestTU TU = TestTU::withCode(T.code());
+    TU.Filename = "TestTU.m";
+    TU.ExtraArgs.push_back("-Wno-objc-root-class");
+    auto AST = TU.build();
+
+    ASSERT_FALSE(AST.getDiagnostics().empty());
+
+    auto H = getHover(AST, T.point(), format::getLLVMStyle(), nullptr);
+    ASSERT_TRUE(H);
+    HoverInfo Expected;
+    Expected.SymRange = T.range();
+    Case.ExpectedBuilder(Expected);
+
+    EXPECT_EQ(H->NamespaceScope, Expected.NamespaceScope);
+    EXPECT_EQ(H->LocalScope, Expected.LocalScope);
+    EXPECT_EQ(H->Name, Expected.Name);
+    EXPECT_EQ(H->Kind, Expected.Kind);
+    EXPECT_EQ(H->Documentation, Expected.Documentation);
+    EXPECT_EQ(H->Definition, Expected.Definition);
+    EXPECT_EQ(H->Type, Expected.Type);
+    EXPECT_EQ(H->ReturnType, Expected.ReturnType);
+    EXPECT_EQ(H->Parameters, Expected.Parameters);
+    EXPECT_EQ(H->TemplateParameters, Expected.TemplateParameters);
+    EXPECT_EQ(H->SymRange, Expected.SymRange);
+    EXPECT_EQ(H->Value, Expected.Value);
+  }
+}
+
 TEST(Hover, All) {
   struct OneTest {
     StringRef Input;
Index: clangd/XRefs.cpp
===================================================================
--- clangd/XRefs.cpp
+++ clangd/XRefs.cpp
@@ -23,6 +23,7 @@
 #include "clang/AST/ASTContext.h"
 #include "clang/AST/Decl.h"
 #include "clang/AST/DeclCXX.h"
+#include "clang/AST/DeclObjC.h"
 #include "clang/AST/DeclTemplate.h"
 #include "clang/AST/ExprCXX.h"
 #include "clang/AST/PrettyPrinter.h"
@@ -451,6 +452,41 @@
   return Policy;
 }
 
+static std::string getNameForObjCMethod(const ObjCMethodDecl *Method) {
+  std::string Name;
+  llvm::raw_string_ostream OS(Name);
+  const ObjCInterfaceDecl *Class = Method->getClassInterface();
+  assert(Class && "Missing class interface for ObjCMethodDecl");
+
+  OS << (Method->isInstanceMethod() ? '-' : '+')
+     << '[' << Class->getName();
+  if (const ObjCCategoryImplDecl *CID =
+      dyn_cast<ObjCCategoryImplDecl>(Method->getDeclContext()))
+    OS << '(' << *CID << ')';
+  OS << ' ' << Method->getSelector().getAsString() << ']';
+  return Name;
+}
+
+static std::string getNameForObjCContainer(const ObjCContainerDecl *C) {
+  if (const ObjCCategoryDecl *Category = dyn_cast<ObjCCategoryDecl>(C)) {
+    std::string Name;
+    llvm::raw_string_ostream OS(Name);
+    const ObjCInterfaceDecl *Class = Category->getClassInterface();
+    assert(Class && "Missing class interface for ObjCCategoryDecl");
+    OS << Class->getName() << '(' << Category->getName() << ')';
+    return Name;
+  }
+  if (const ObjCCategoryImplDecl *CI = dyn_cast<ObjCCategoryImplDecl>(C)) {
+    std::string Name;
+    llvm::raw_string_ostream OS(Name);
+    const ObjCInterfaceDecl *Class = CI->getClassInterface();
+    assert(Class && "Missing class interface for ObjCCategoryDecl");
+    OS << Class->getName() << '(' << *CI << ')';
+    return Name;
+  }
+  return C->getNameAsString();
+}
+
 /// Given a declaration \p D, return a human-readable string representing the
 /// local scope in which it is declared, i.e. class(es) and method name. Returns
 /// an empty string if it is not local.
@@ -467,7 +503,18 @@
     return std::string("");
   };
   while (DC) {
-    if (const TypeDecl *TD = dyn_cast<TypeDecl>(DC))
+    if (const ObjCMethodDecl *MD = dyn_cast<ObjCMethodDecl>(DC)) {
+      Scopes.push_back(getNameForObjCMethod(MD));
+      DC = DC->getParent();
+      if (DC) {  // Skip over parent Objective-C container.
+        DC = DC->getParent();
+      }
+      continue;
+    }
+
+    if (const ObjCContainerDecl *CD = dyn_cast<ObjCContainerDecl>(DC))
+      Scopes.push_back(getNameForObjCContainer(CD));
+    else if (const TypeDecl *TD = dyn_cast<TypeDecl>(DC))
       Scopes.push_back(GetName(TD));
     else if (const FunctionDecl *FD = dyn_cast<FunctionDecl>(DC))
       Scopes.push_back(FD->getNameAsString());
@@ -482,10 +529,17 @@
 static std::string getNamespaceScope(const Decl *D) {
   const DeclContext *DC = D->getDeclContext();
 
+  // Don't consider Type/Function/ObjC decls to be a namespace. Instead, check
+  // where they themselves are declared by recursing.
   if (const TypeDecl *TD = dyn_cast<TypeDecl>(DC))
     return getNamespaceScope(TD);
   if (const FunctionDecl *FD = dyn_cast<FunctionDecl>(DC))
     return getNamespaceScope(FD);
+  if (const ObjCMethodDecl *MD = dyn_cast<ObjCMethodDecl>(DC))
+    return getNamespaceScope(MD);
+  if (const ObjCContainerDecl *CD = dyn_cast<ObjCContainerDecl>(DC))
+    return getNamespaceScope(CD);
+
   if (const NamedDecl *ND = dyn_cast<NamedDecl>(DC))
     return ND->getQualifiedNameAsString();
 
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to