Improve hover scopes for Objective-C code
authorDavid Goldman <davg@google.com>
Fri, 4 Dec 2020 20:04:25 +0000 (15:04 -0500)
committerDavid Goldman <davg@google.com>
Fri, 12 Feb 2021 15:27:32 +0000 (10:27 -0500)
- Instead of `AppDelegate::application:didFinishLaunchingWithOptions:` you
will now see `-[AppDelegate application:didFinishLaunchingWithOptions:]`

- Also include categories in the name when printing the scopes, e.g. `Class(Category)` and `-[Class(Category) method]`

Differential Revision: https://reviews.llvm.org/D68590

clang-tools-extra/clangd/AST.cpp
clang-tools-extra/clangd/AST.h
clang-tools-extra/clangd/Hover.cpp
clang-tools-extra/clangd/unittests/HoverTests.cpp

index aecaf7e..abcfc1a 100644 (file)
@@ -283,6 +283,52 @@ std::string printNamespaceScope(const DeclContext &DC) {
   return "";
 }
 
+static llvm::StringRef
+getNameOrErrForObjCInterface(const ObjCInterfaceDecl *ID) {
+  return ID ? ID->getName() : "<<error-type>>";
+}
+
+std::string printObjCMethod(const ObjCMethodDecl &Method) {
+  std::string Name;
+  llvm::raw_string_ostream OS(Name);
+
+  OS << (Method.isInstanceMethod() ? '-' : '+') << '[';
+
+  // Should always be true.
+  if (const ObjCContainerDecl *C =
+          dyn_cast<ObjCContainerDecl>(Method.getDeclContext()))
+    OS << printObjCContainer(*C);
+
+  Method.getSelector().print(OS << ' ');
+  if (Method.isVariadic())
+    OS << ", ...";
+
+  OS << ']';
+  OS.flush();
+  return Name;
+}
+
+std::string printObjCContainer(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();
+    OS << getNameOrErrForObjCInterface(Class) << '(' << Category->getName()
+       << ')';
+    OS.flush();
+    return Name;
+  }
+  if (const ObjCCategoryImplDecl *CID = dyn_cast<ObjCCategoryImplDecl>(&C)) {
+    std::string Name;
+    llvm::raw_string_ostream OS(Name);
+    const ObjCInterfaceDecl *Class = CID->getClassInterface();
+    OS << getNameOrErrForObjCInterface(Class) << '(' << CID->getName() << ')';
+    OS.flush();
+    return Name;
+  }
+  return C.getNameAsString();
+}
+
 SymbolID getSymbolID(const Decl *D) {
   llvm::SmallString<128> USR;
   if (index::generateUSRForDecl(D, USR))
index b603964..94984a9 100644 (file)
@@ -15,6 +15,7 @@
 
 #include "index/SymbolID.h"
 #include "clang/AST/Decl.h"
+#include "clang/AST/DeclObjC.h"
 #include "clang/AST/NestedNameSpecifier.h"
 #include "clang/Basic/SourceLocation.h"
 #include "clang/Lex/MacroInfo.h"
@@ -64,6 +65,14 @@ std::string printName(const ASTContext &Ctx, const NamedDecl &ND);
 /// string if decl is not a template specialization.
 std::string printTemplateSpecializationArgs(const NamedDecl &ND);
 
+/// Print the Objective-C method name, including the full container name, e.g.
+/// `-[MyClass(Category) method:]`
+std::string printObjCMethod(const ObjCMethodDecl &Method);
+
+/// Print the Objective-C container name including categories, e.g. `MyClass`,
+// `MyClass()`, `MyClass(Category)`, and `MyProtocol`.
+std::string printObjCContainer(const ObjCContainerDecl &C);
+
 /// Gets the symbol ID for a declaration. Returned SymbolID might be null.
 SymbolID getSymbolID(const Decl *D);
 
index 6d707c8..42acebd 100644 (file)
@@ -22,6 +22,7 @@
 #include "clang/AST/Decl.h"
 #include "clang/AST/DeclBase.h"
 #include "clang/AST/DeclCXX.h"
+#include "clang/AST/DeclObjC.h"
 #include "clang/AST/DeclTemplate.h"
 #include "clang/AST/Expr.h"
 #include "clang/AST/ExprCXX.h"
@@ -64,6 +65,15 @@ PrintingPolicy getPrintingPolicy(PrintingPolicy Base) {
 std::string getLocalScope(const Decl *D) {
   std::vector<std::string> Scopes;
   const DeclContext *DC = D->getDeclContext();
+
+  // ObjC scopes won't have multiple components for us to join, instead:
+  // - Methods: "-[Class methodParam1:methodParam2]"
+  // - Classes, categories, and protocols: "MyClass(Category)"
+  if (const ObjCMethodDecl *MD = dyn_cast<ObjCMethodDecl>(DC))
+    return printObjCMethod(*MD);
+  else if (const ObjCContainerDecl *CD = dyn_cast<ObjCContainerDecl>(DC))
+    return printObjCContainer(*CD);
+
   auto GetName = [](const TypeDecl *D) {
     if (!D->getDeclName().isEmpty()) {
       PrintingPolicy Policy = D->getASTContext().getPrintingPolicy();
@@ -90,6 +100,11 @@ std::string getLocalScope(const Decl *D) {
 std::string getNamespaceScope(const Decl *D) {
   const DeclContext *DC = D->getDeclContext();
 
+  // ObjC does not have the concept of namespaces, so instead we support
+  // local scopes.
+  if (isa<ObjCMethodDecl, ObjCContainerDecl>(DC))
+    return "";
+
   if (const TagDecl *TD = dyn_cast<TagDecl>(DC))
     return getNamespaceScope(TD);
   if (const FunctionDecl *FD = dyn_cast<FunctionDecl>(DC))
index 9650940..deaa9c4 100644 (file)
@@ -2167,7 +2167,8 @@ TEST(Hover, All) {
             HI.Name = "data";
             HI.Type = "char";
             HI.Kind = index::SymbolKind::Field;
-            HI.NamespaceScope = "ObjC::"; // FIXME: fix it
+            HI.LocalScope = "ObjC::";
+            HI.NamespaceScope = "";
             HI.Definition = "char data";
           }},
       {
@@ -2260,6 +2261,86 @@ TEST(Hover, All) {
             HI.Name = "this";
             HI.Definition = "const Foo<int, F> *";
           }},
+      {
+          R"cpp(
+          @interface MYObject
+          @end
+          @interface MYObject (Private)
+          @property(nonatomic, assign) int privateField;
+          @end
+
+          int someFunction() {
+            MYObject *obj = [MYObject sharedInstance];
+            return obj.[[private^Field]];
+          }
+          )cpp",
+          [](HoverInfo &HI) {
+            HI.Name = "privateField";
+            HI.Kind = index::SymbolKind::InstanceProperty;
+            HI.LocalScope = "MYObject(Private)::";
+            HI.NamespaceScope = "";
+            HI.Definition = "@property(nonatomic, assign, unsafe_unretained, "
+                            "readwrite) int privateField;";
+          }},
+      {
+          R"cpp(
+          @protocol MYProtocol
+          @property(nonatomic, assign) int prop1;
+          @end
+
+          int someFunction() {
+            id<MYProtocol> obj = 0;
+            return obj.[[pro^p1]];
+          }
+          )cpp",
+          [](HoverInfo &HI) {
+            HI.Name = "prop1";
+            HI.Kind = index::SymbolKind::InstanceProperty;
+            HI.LocalScope = "MYProtocol::";
+            HI.NamespaceScope = "";
+            HI.Definition = "@property(nonatomic, assign, unsafe_unretained, "
+                            "readwrite) int prop1;";
+          }},
+      {R"objc(
+        @interface Foo
+        @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 = index::SymbolKind::Variable;
+         HI.Type = "int";
+         HI.LocalScope = "+[Foo(Private) somePrivateMethod]::";
+         HI.NamespaceScope = "";
+         HI.Value = "2";
+       }},
+      {R"objc(
+        @interface Foo
+        @end
+
+        @implementation Foo
+        - (int)variadicArgMethod:(id)first, ... {
+          int [[res^ult]] = 0;
+          return result;
+        }
+        @end
+        )objc",
+       [](HoverInfo &HI) {
+         HI.Name = "result";
+         HI.Definition = "int result = 0";
+         HI.Kind = index::SymbolKind::Variable;
+         HI.Type = "int";
+         HI.LocalScope = "-[Foo variadicArgMethod:, ...]::";
+         HI.NamespaceScope = "";
+         HI.Value = "0";
+       }},
   };
 
   // Create a tiny index, so tests above can verify documentation is fetched.