From: David Goldman Date: Fri, 4 Dec 2020 20:04:25 +0000 (-0500) Subject: Improve hover scopes for Objective-C code X-Git-Tag: llvmorg-14-init~15275 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=07c5a800dc1769f3f684d4a864f3903ac9ffa9f3;p=platform%2Fupstream%2Fllvm.git Improve hover scopes for Objective-C code - 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 --- diff --git a/clang-tools-extra/clangd/AST.cpp b/clang-tools-extra/clangd/AST.cpp index aecaf7e..abcfc1a 100644 --- a/clang-tools-extra/clangd/AST.cpp +++ b/clang-tools-extra/clangd/AST.cpp @@ -283,6 +283,52 @@ std::string printNamespaceScope(const DeclContext &DC) { return ""; } +static llvm::StringRef +getNameOrErrForObjCInterface(const ObjCInterfaceDecl *ID) { + return ID ? ID->getName() : "<>"; +} + +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(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(&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(&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)) diff --git a/clang-tools-extra/clangd/AST.h b/clang-tools-extra/clangd/AST.h index b603964..94984a9 100644 --- a/clang-tools-extra/clangd/AST.h +++ b/clang-tools-extra/clangd/AST.h @@ -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); diff --git a/clang-tools-extra/clangd/Hover.cpp b/clang-tools-extra/clangd/Hover.cpp index 6d707c8..42acebd 100644 --- a/clang-tools-extra/clangd/Hover.cpp +++ b/clang-tools-extra/clangd/Hover.cpp @@ -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 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(DC)) + return printObjCMethod(*MD); + else if (const ObjCContainerDecl *CD = dyn_cast(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(DC)) + return ""; + if (const TagDecl *TD = dyn_cast(DC)) return getNamespaceScope(TD); if (const FunctionDecl *FD = dyn_cast(DC)) diff --git a/clang-tools-extra/clangd/unittests/HoverTests.cpp b/clang-tools-extra/clangd/unittests/HoverTests.cpp index 9650940..deaa9c4 100644 --- a/clang-tools-extra/clangd/unittests/HoverTests.cpp +++ b/clang-tools-extra/clangd/unittests/HoverTests.cpp @@ -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 *"; }}, + { + 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 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.