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