diff --git a/clang-tools-extra/clangd/index/remote/Client.cpp b/clang-tools-extra/clangd/index/remote/Client.cpp --- a/clang-tools-extra/clangd/index/remote/Client.cpp +++ b/clang-tools-extra/clangd/index/remote/Client.cpp @@ -91,11 +91,16 @@ return streamRPC(Request, &remote::SymbolIndex::Stub::Refs, Callback); } - // FIXME(kirillbobyrev): Implement this. void - relations(const clangd::RelationsRequest &, - llvm::function_ref) - const {} + relations(const clangd::RelationsRequest &Request, + llvm::function_ref + Callback) const { + streamRPC(Request, &remote::SymbolIndex::Stub::Relations, + // Unpack protobuf Relation. + [&](std::pair SubjectAndObject) { + Callback(SubjectAndObject.first, SubjectAndObject.second); + }); + } // IndexClient does not take any space since the data is stored on the // server. diff --git a/clang-tools-extra/clangd/index/remote/Index.proto b/clang-tools-extra/clangd/index/remote/Index.proto --- a/clang-tools-extra/clangd/index/remote/Index.proto +++ b/clang-tools-extra/clangd/index/remote/Index.proto @@ -18,6 +18,8 @@ rpc FuzzyFind(FuzzyFindRequest) returns (stream FuzzyFindReply) {} rpc Refs(RefsRequest) returns (stream RefsReply) {} + + rpc Relations(RelationsRequest) returns (stream RelationsReply) {} } message LookupRequest { repeated string ids = 1; } @@ -114,3 +116,25 @@ string header = 1; uint32 references = 2; } + +message RelationsRequest { + repeated string subjects = 1; + uint32 predicate = 2; + uint32 limit = 3; +} + +// The response is a stream of reference messages, and one terminating has_more +// message. +message RelationsReply { + oneof kind { + Relation stream_result = 1; + bool final_result = 2; // HasMore + } +} + +// This struct does not mirror clangd::Relation but rather the arguments of +// SymbolIndex::relations callback. +message Relation { + string subject_id = 1; + Symbol object = 2; +} diff --git a/clang-tools-extra/clangd/index/remote/marshalling/Marshalling.h b/clang-tools-extra/clangd/index/remote/marshalling/Marshalling.h --- a/clang-tools-extra/clangd/index/remote/marshalling/Marshalling.h +++ b/clang-tools-extra/clangd/index/remote/marshalling/Marshalling.h @@ -38,14 +38,19 @@ Marshaller() = delete; Marshaller(llvm::StringRef RemoteIndexRoot, llvm::StringRef LocalIndexRoot); + // FIXME(kirillbobyrev): Switch from Optional to Expected. llvm::Optional fromProtobuf(const Symbol &Message); llvm::Optional fromProtobuf(const Ref &Message); + llvm::Optional> + fromProtobuf(const Relation &Message); llvm::Expected fromProtobuf(const LookupRequest *Message); llvm::Expected fromProtobuf(const FuzzyFindRequest *Message); llvm::Expected fromProtobuf(const RefsRequest *Message); + llvm::Expected + fromProtobuf(const RelationsRequest *Message); /// toProtobuf() functions serialize native clangd types and strip IndexRoot /// from the file paths specific to indexing machine. fromProtobuf() functions @@ -54,9 +59,12 @@ LookupRequest toProtobuf(const clangd::LookupRequest &From); FuzzyFindRequest toProtobuf(const clangd::FuzzyFindRequest &From); RefsRequest toProtobuf(const clangd::RefsRequest &From); + RelationsRequest toProtobuf(const clangd::RelationsRequest &From); - llvm::Optional toProtobuf(const clangd::Ref &From); llvm::Optional toProtobuf(const clangd::Symbol &From); + llvm::Optional toProtobuf(const clangd::Ref &From); + llvm::Optional toProtobuf(const clangd::SymbolID &Subject, + const clangd::Symbol &Object); /// Translates \p RelativePath into the absolute path and builds URI for the /// user machine. This translation happens on the client side with the diff --git a/clang-tools-extra/clangd/index/remote/marshalling/Marshalling.cpp b/clang-tools-extra/clangd/index/remote/marshalling/Marshalling.cpp --- a/clang-tools-extra/clangd/index/remote/marshalling/Marshalling.cpp +++ b/clang-tools-extra/clangd/index/remote/marshalling/Marshalling.cpp @@ -10,6 +10,7 @@ #include "Headers.h" #include "Index.pb.h" #include "Protocol.h" +#include "index/Index.h" #include "index/Serialization.h" #include "index/Symbol.h" #include "index/SymbolID.h" @@ -33,10 +34,10 @@ namespace { -template -llvm::Expected> getIDs(MessageT *Message) { +template +llvm::Expected> getIDs(IDRange IDs) { llvm::DenseSet Result; - for (const auto &ID : Message->ids()) { + for (const auto &ID : IDs) { auto SID = SymbolID::fromStr(StringRef(ID)); if (!SID) return SID.takeError(); @@ -69,7 +70,7 @@ llvm::Expected Marshaller::fromProtobuf(const LookupRequest *Message) { clangd::LookupRequest Req; - auto IDs = getIDs(Message); + auto IDs = getIDs(Message->ids()); if (!IDs) return IDs.takeError(); Req.IDs = std::move(*IDs); @@ -100,7 +101,7 @@ llvm::Expected Marshaller::fromProtobuf(const RefsRequest *Message) { clangd::RefsRequest Req; - auto IDs = getIDs(Message); + auto IDs = getIDs(Message->ids()); if (!IDs) return IDs.takeError(); Req.IDs = std::move(*IDs); @@ -110,6 +111,19 @@ return Req; } +llvm::Expected +Marshaller::fromProtobuf(const RelationsRequest *Message) { + clangd::RelationsRequest Req; + auto IDs = getIDs(Message->subjects()); + if (!IDs) + return IDs.takeError(); + Req.Subjects = std::move(*IDs); + Req.Predicate = static_cast(Message->predicate()); + if (Message->limit()) + Req.Limit = Message->limit(); + return Req; +} + llvm::Optional Marshaller::fromProtobuf(const Symbol &Message) { if (!Message.has_info() || !Message.has_canonical_declaration()) { elog("Cannot convert Symbol from protobuf (missing info, definition or " @@ -178,6 +192,24 @@ return Result; } +llvm::Optional> +Marshaller::fromProtobuf(const Relation &Message) { + auto SubjectID = SymbolID::fromStr(Message.subject_id()); + if (!SubjectID) { + elog("Cannot convert Relation from protobuf (invalid Subject ID): {0}", + SubjectID.takeError()); + return llvm::None; + } + if (!Message.has_object()) { + elog("Cannot convert Relation from protobuf (missing Object): {0}"); + return llvm::None; + } + auto Object = fromProtobuf(Message.object()); + if (!Object) + return llvm::None; + return std::make_pair(*SubjectID, *Object); +} + LookupRequest Marshaller::toProtobuf(const clangd::LookupRequest &From) { LookupRequest RPCRequest; for (const auto &SymbolID : From.IDs) @@ -216,6 +248,16 @@ return RPCRequest; } +RelationsRequest Marshaller::toProtobuf(const clangd::RelationsRequest &From) { + RelationsRequest RPCRequest; + for (const auto &ID : From.Subjects) + RPCRequest.add_subjects(ID.str()); + RPCRequest.set_predicate(static_cast(From.Predicate)); + if (From.Limit) + RPCRequest.set_limit(*From.Limit); + return RPCRequest; +} + llvm::Optional Marshaller::toProtobuf(const clangd::Symbol &From) { Symbol Result; Result.set_id(From.ID.str()); @@ -274,6 +316,19 @@ return Result; } +llvm::Optional Marshaller::toProtobuf(const clangd::SymbolID &Subject, + const clangd::Symbol &Object) { + Relation Result; + *Result.mutable_subject_id() = Subject.str(); + auto SerializedObject = toProtobuf(Object); + if (!SerializedObject) { + elog("Can not convert Relation to protobuf (invalid symbol): {0}", Object); + return llvm::None; + } + *Result.mutable_object() = *SerializedObject; + return Result; +} + llvm::Optional Marshaller::relativePathToURI(llvm::StringRef RelativePath) { assert(LocalIndexRoot); diff --git a/clang-tools-extra/clangd/index/remote/server/Server.cpp b/clang-tools-extra/clangd/index/remote/server/Server.cpp --- a/clang-tools-extra/clangd/index/remote/server/Server.cpp +++ b/clang-tools-extra/clangd/index/remote/server/Server.cpp @@ -169,6 +169,38 @@ return grpc::Status::OK; } + grpc::Status Relations(grpc::ServerContext *Context, + const RelationsRequest *Request, + grpc::ServerWriter *Reply) override { + trace::Span Tracer(RelationsRequest::descriptor()->name()); + auto Req = ProtobufMarshaller->fromProtobuf(Request); + if (!Req) { + elog("Can not parse RelationsRequest from protobuf: {0}", + Req.takeError()); + return grpc::Status::CANCELLED; + } + unsigned Sent = 0; + unsigned FailedToSend = 0; + Index->relations( + *Req, [&](const SymbolID &Subject, const clangd::Symbol &Object) { + auto SerializedItem = ProtobufMarshaller->toProtobuf(Subject, Object); + if (!SerializedItem) { + ++FailedToSend; + return; + } + RelationsReply NextMessage; + *NextMessage.mutable_stream_result() = *SerializedItem; + Reply->Write(NextMessage); + ++Sent; + }); + RelationsReply LastMessage; + LastMessage.set_final_result(true); + Reply->Write(LastMessage); + SPAN_ATTACH(Tracer, "Sent", Sent); + SPAN_ATTACH(Tracer, "Failed to send", FailedToSend); + return grpc::Status::OK; + } + std::unique_ptr Index; std::unique_ptr ProtobufMarshaller; }; diff --git a/clang-tools-extra/clangd/unittests/remote/MarshallingTests.cpp b/clang-tools-extra/clangd/unittests/remote/MarshallingTests.cpp --- a/clang-tools-extra/clangd/unittests/remote/MarshallingTests.cpp +++ b/clang-tools-extra/clangd/unittests/remote/MarshallingTests.cpp @@ -10,6 +10,7 @@ #include "TestFS.h" #include "index/Index.h" #include "index/Ref.h" +#include "index/Relation.h" #include "index/Serialization.h" #include "index/Symbol.h" #include "index/SymbolID.h" @@ -18,6 +19,7 @@ #include "clang/Index/IndexSymbol.h" #include "llvm/ADT/SmallString.h" #include "llvm/ADT/StringRef.h" +#include "llvm/ADT/Twine.h" #include "llvm/Support/Error.h" #include "llvm/Support/Path.h" #include "llvm/Support/StringSaver.h" @@ -38,6 +40,51 @@ return Strings.save(URI.toString()).begin(); } +clangd::Symbol createSymbol(llvm::StringRef PathPrefix, + llvm::UniqueStringSaver &Strings) { + clangd::Symbol Sym; + Sym.ID = llvm::cantFail(SymbolID::fromStr("057557CEBF6E6B2D")); + + index::SymbolInfo Info; + Info.Kind = index::SymbolKind::Function; + Info.SubKind = index::SymbolSubKind::AccessorGetter; + Info.Lang = index::SymbolLanguage::CXX; + Info.Properties = static_cast( + index::SymbolProperty::TemplateSpecialization); + Sym.SymInfo = Info; + + Sym.Name = Strings.save("Foo"); + Sym.Scope = Strings.save("llvm::foo::bar::"); + + clangd::SymbolLocation Location; + Location.Start.setLine(1); + Location.Start.setColumn(15); + Location.End.setLine(3); + Location.End.setColumn(121); + Location.FileURI = testPathURI(PathPrefix.str() + "Definition.cpp", Strings); + Sym.Definition = Location; + + Location.Start.setLine(42); + Location.Start.setColumn(31); + Location.End.setLine(20); + Location.End.setColumn(400); + Location.FileURI = testPathURI(PathPrefix.str() + "Declaration.h", Strings); + Sym.CanonicalDeclaration = Location; + + Sym.References = 9000; + Sym.Origin = clangd::SymbolOrigin::Static; + Sym.Signature = Strings.save("(int X, char Y, Type T)"); + Sym.TemplateSpecializationArgs = Strings.save(""); + Sym.CompletionSnippetSuffix = + Strings.save("({1: int X}, {2: char Y}, {3: Type T})"); + Sym.Documentation = Strings.save("This is my amazing Foo constructor!"); + Sym.ReturnType = Strings.save("Foo"); + + Sym.Flags = clangd::Symbol::SymbolFlag::IndexedForCodeCompletion; + + return Sym; +} + TEST(RemoteMarshallingTest, URITranslation) { llvm::BumpPtrAllocator Arena; llvm::UniqueStringSaver Strings(Arena); @@ -86,52 +133,10 @@ } TEST(RemoteMarshallingTest, SymbolSerialization) { - clangd::Symbol Sym; - - auto ID = SymbolID::fromStr("057557CEBF6E6B2D"); - ASSERT_TRUE(bool(ID)); - Sym.ID = *ID; - - index::SymbolInfo Info; - Info.Kind = index::SymbolKind::Function; - Info.SubKind = index::SymbolSubKind::AccessorGetter; - Info.Lang = index::SymbolLanguage::CXX; - Info.Properties = static_cast( - index::SymbolProperty::TemplateSpecialization); - Sym.SymInfo = Info; - llvm::BumpPtrAllocator Arena; llvm::UniqueStringSaver Strings(Arena); - Sym.Name = Strings.save("Foo"); - Sym.Scope = Strings.save("llvm::foo::bar::"); - - clangd::SymbolLocation Location; - Location.Start.setLine(1); - Location.Start.setColumn(15); - Location.End.setLine(3); - Location.End.setColumn(121); - Location.FileURI = testPathURI("home/Definition.cpp", Strings); - Sym.Definition = Location; - - Location.Start.setLine(42); - Location.Start.setColumn(31); - Location.End.setLine(20); - Location.End.setColumn(400); - Location.FileURI = testPathURI("home/Declaration.h", Strings); - Sym.CanonicalDeclaration = Location; - - Sym.References = 9000; - Sym.Origin = clangd::SymbolOrigin::Static; - Sym.Signature = Strings.save("(int X, char Y, Type T)"); - Sym.TemplateSpecializationArgs = Strings.save(""); - Sym.CompletionSnippetSuffix = - Strings.save("({1: int X}, {2: char Y}, {3: Type T})"); - Sym.Documentation = Strings.save("This is my amazing Foo constructor!"); - Sym.ReturnType = Strings.save("Foo"); - - Sym.Flags = clangd::Symbol::SymbolFlag::IndexedForCodeCompletion; - + clangd::Symbol Sym = createSymbol("home/", Strings); Marshaller ProtobufMarshaller(testPath("home/"), testPath("home/")); // Check that symbols are exactly the same if the path to indexed project is @@ -160,20 +165,17 @@ EXPECT_FALSE(ProtobufMarshaller.fromProtobuf(*Serialized)); // Fail with an invalid URI. - Location.FileURI = "Not A URI"; - Sym.Definition = Location; + Sym.Definition.FileURI = "Not A URI"; EXPECT_FALSE(ProtobufMarshaller.toProtobuf(Sym)); // Schemes other than "file" can not be used. auto UnittestURI = URI::create(testPath("home/SomePath.h"), "unittest"); ASSERT_TRUE(bool(UnittestURI)); - Location.FileURI = Strings.save(UnittestURI->toString()).begin(); - Sym.Definition = Location; + Sym.Definition.FileURI = Strings.save(UnittestURI->toString()).begin(); EXPECT_FALSE(ProtobufMarshaller.toProtobuf(Sym)); // Passing root that is not prefix of the original file path. - Location.FileURI = testPathURI("home/File.h", Strings); - Sym.Definition = Location; + Sym.Definition.FileURI = testPathURI("home/File.h", Strings); // Check that the symbol is valid and passing the correct path works. Serialized = ProtobufMarshaller.toProtobuf(Sym); ASSERT_TRUE(Serialized); @@ -342,6 +344,53 @@ llvm::consumeError(Deserialized.takeError()); } +TEST(RemoteMarshallingTest, RelationsRequestSerialization) { + clangd::RelationsRequest Request; + Request.Subjects.insert( + llvm::cantFail(SymbolID::fromStr("0000000000000001"))); + Request.Subjects.insert( + llvm::cantFail(SymbolID::fromStr("0000000000000002"))); + + Request.Limit = 9000; + Request.Predicate = RelationKind::BaseOf; + + Marshaller ProtobufMarshaller(testPath("remote/"), testPath("local/")); + + auto Serialized = ProtobufMarshaller.toProtobuf(Request); + EXPECT_EQ(static_cast(Serialized.subjects_size()), + Request.Subjects.size()); + EXPECT_EQ(Serialized.limit(), Request.Limit); + EXPECT_EQ(static_cast(Serialized.predicate()), + Request.Predicate); + auto Deserialized = ProtobufMarshaller.fromProtobuf(&Serialized); + ASSERT_TRUE(bool(Deserialized)); + EXPECT_EQ(Deserialized->Subjects, Request.Subjects); + ASSERT_TRUE(Deserialized->Limit); + EXPECT_EQ(*Deserialized->Limit, Request.Limit); + EXPECT_EQ(Deserialized->Predicate, Request.Predicate); +} + +TEST(RemoteMarshallingTest, RelationsRequestFailingSerialization) { + RelationsRequest Serialized; + Serialized.add_subjects("ZZZZZZZZZZZZZZZZ"); + Marshaller ProtobufMarshaller(testPath("remote/"), testPath("local/")); + auto Deserialized = ProtobufMarshaller.fromProtobuf(&Serialized); + EXPECT_FALSE(Deserialized); + llvm::consumeError(Deserialized.takeError()); +} + +TEST(RemoteMarshallingTest, RelationsSerializion) { + llvm::BumpPtrAllocator Arena; + llvm::UniqueStringSaver Strings(Arena); + + clangd::Symbol Sym = createSymbol("remote/", Strings); + SymbolID ID = llvm::cantFail(SymbolID::fromStr("0000000000000002")); + Marshaller ProtobufMarshaller(testPath("remote/"), testPath("local/")); + auto Serialized = ProtobufMarshaller.toProtobuf(ID, Sym); + auto Deserialized = ProtobufMarshaller.fromProtobuf(*Serialized); + ASSERT_TRUE(Deserialized); +} + TEST(RemoteMarshallingTest, RelativePathToURITranslation) { Marshaller ProtobufMarshaller(/*RemoteIndexRoot=*/"", /*LocalIndexRoot=*/testPath("home/project/"));