Skip to content

Commit 892fe42

Browse files
iahsfacebook-github-bot
authored andcommitted
SyntaxGraph::asTypeSystem
Summary: Provides access to SyntaxGraph nodes as TypeSystem nodes. This approach avoids the need to duplicate the type resolution and incremental bundled data acess in TypeSystem while allowing all runtime operations to be defined solely in terms of the latter. There is some data duplication due to the parallel node hierarchies, but the copies are performed lazily as data is accessed and then cached. The basic workflow introduced in this diff is to convert the SyntaxGraph at the top-level to a TypeSystem and then look up nodes by URI there. Future diffs will allow converting individual nodes without taking this trip, and handle nodes without URIs. Reviewed By: pranavtbhat Differential Revision: D75173383 fbshipit-source-id: fafdcd767271f358b9d5f3eb4ff33c4144b37bd7
1 parent 9513f13 commit 892fe42

File tree

7 files changed

+358
-27
lines changed

7 files changed

+358
-27
lines changed

third-party/thrift/src/thrift/lib/cpp2/dynamic/TypeSystem.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,9 @@ class TypeSystem {
116116
* Resolves the definition of a user-defined type, and implicitly, all
117117
* transitively referenced types.
118118
*
119+
* NOTE: non-const, which means the caller must synchronize calls to this
120+
* method.
121+
*
119122
* Throws:
120123
* - InvalidTypeError if the type is not defined in the type system.
121124
*/

third-party/thrift/src/thrift/lib/cpp2/schema/SyntaxGraph.cpp

Lines changed: 258 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include <thrift/lib/cpp2/schema/SyntaxGraph.h>
1818

1919
#include <thrift/lib/cpp/util/EnumUtils.h>
20+
#include <thrift/lib/cpp2/dynamic/TypeSystem.h>
2021
#include <thrift/lib/cpp2/schema/detail/Resolver.h>
2122
#include <thrift/lib/cpp2/schema/detail/SchemaBackedResolver.h>
2223

@@ -26,8 +27,7 @@
2627

2728
#include <fmt/core.h>
2829

29-
#include <functional>
30-
#include <ostream>
30+
#include <queue>
3131
#include <stdexcept>
3232

3333
#ifdef THRIFT_SCHEMA_AVAILABLE
@@ -677,6 +677,262 @@ void SyntaxGraph::printTo(
677677
}
678678
}
679679

680+
namespace {
681+
class TypeSystemFacade final : public type_system::TypeSystem {
682+
using TSDefinition = std::variant<
683+
type_system::StructNode,
684+
type_system::UnionNode,
685+
type_system::EnumNode>;
686+
687+
public:
688+
explicit TypeSystemFacade(const detail::SchemaBackedResolver& graph)
689+
: resolver_(graph) {}
690+
691+
type_system::DefinitionRef getUserDefinedType(
692+
type_system::UriView uri) override {
693+
const DefinitionNode* def = resolver_.getDefinitionNodeByUri(uri);
694+
if (!def) {
695+
folly::throw_exception<type_system::InvalidTypeError>(
696+
fmt::format("Definition for uri '{}' was not found", uri));
697+
}
698+
699+
if (auto it = cache_.find(def); it != cache_.end()) {
700+
return folly::variant_match(it->second, [](auto& def) {
701+
return type_system::DefinitionRef{&def};
702+
});
703+
}
704+
705+
return convertUserDefinedType(def);
706+
}
707+
708+
folly::F14FastSet<type_system::Uri> getKnownUris() const override {
709+
// This is only used for serializing the type system, which is already not
710+
// guaranteed to be possible as we don't require URIs for all user-defined
711+
// types.
712+
return {};
713+
}
714+
715+
private:
716+
// Convert the definition to TypeSystem's representation.
717+
// The approach is:
718+
// 1. Traverse the root definition node's fields to gather the set of
719+
// definitions that need to be converted (as a structured definition node has
720+
// pointers to its fields' types). This is done using BFS to avoid unbounded
721+
// user-controlled recursion.
722+
// 2. Allocate a placeholder object for each structured definition in the set,
723+
// so that even in cases of circular references we still have a stable address
724+
// for the type of each field available during population. (Enum definitions
725+
// are populated immediately as they don't have outgoing edges). This
726+
// is done during the traversal in the first step.
727+
// 3. Populate the placeholder objects with the actual data.
728+
type_system::DefinitionRef convertUserDefinedType(
729+
const DefinitionNode* rootSgDef) {
730+
// This is the queue for the first traversal, performing steps 1 and 2.
731+
std::queue<const DefinitionNode*> initialAllocationQueue;
732+
initialAllocationQueue.push(rootSgDef);
733+
// This is the queue for the second traversal, performing step 3.
734+
// Nodes are added to it as they are processed by the first traversal.
735+
std::queue<const DefinitionNode*> populationQueue;
736+
while (!initialAllocationQueue.empty()) {
737+
const DefinitionNode* sgDef = initialAllocationQueue.front();
738+
initialAllocationQueue.pop();
739+
if (cache_.count(sgDef)) {
740+
continue;
741+
}
742+
743+
auto processStructuredType = [&](const StructuredNode& s) {
744+
for (const auto& field : s.fields()) {
745+
const DefinitionNode* fieldType =
746+
field.type().trueType().visit([](const auto& n) {
747+
if constexpr (std::is_base_of_v<
748+
detail::WithDefinition,
749+
decltype(n)>) {
750+
return &n.definition();
751+
}
752+
return nullptr;
753+
});
754+
if (fieldType && !cache_.count(fieldType)) {
755+
initialAllocationQueue.push(fieldType);
756+
}
757+
}
758+
759+
// Enqueue the current node to be populated later.
760+
populationQueue.push(sgDef);
761+
};
762+
763+
// We may encounter circular references, so we insert a placeholder object
764+
// into the map that we will later overwrite with the correct data.
765+
sgDef->visit(
766+
[&](const StructNode& s) {
767+
cache_.emplace(sgDef, type_system::StructNode{{}, {}, {}, {}});
768+
processStructuredType(s);
769+
},
770+
[&](const UnionNode& s) {
771+
cache_.emplace(sgDef, type_system::UnionNode{{}, {}, {}, {}});
772+
processStructuredType(s);
773+
},
774+
[](const ExceptionNode&) {
775+
folly::throw_exception<std::runtime_error>(
776+
"Exceptions aren't supported by TypeSystem");
777+
},
778+
[&](const EnumNode& e) {
779+
// Enums can be populated immediately.
780+
std::vector<type_system::EnumNode::Value> values;
781+
values.reserve(e.values().size());
782+
for (const auto& value : e.values()) {
783+
// TODO: annotations
784+
values.emplace_back(type_system::EnumNode::Value{
785+
std::string(value.name()),
786+
value.i32(),
787+
type_system::AnnotationsMap{}});
788+
}
789+
// TODO: annotations
790+
cache_.emplace(
791+
sgDef,
792+
type_system::EnumNode{
793+
type_system::Uri(e.uri()), std::move(values), {}});
794+
},
795+
[](const TypedefNode&) {
796+
folly::throw_exception<std::logic_error>(
797+
"Typedefs should have been resolved by trueType call");
798+
},
799+
[](const auto& n) {
800+
folly::throw_exception<std::logic_error>(fmt::format(
801+
"Encountered unexpected node type `{}`",
802+
folly::pretty_name<decltype(n)>()));
803+
});
804+
}
805+
806+
// Now that all types have been allocated, go back and populate structured
807+
// types.
808+
while (!populationQueue.empty()) {
809+
const DefinitionNode* sgDef = populationQueue.front();
810+
populationQueue.pop();
811+
TSDefinition& tsDef = cache_.at(sgDef);
812+
auto makeFields = [&](const StructuredNode& s) {
813+
std::vector<type_system::FieldNode> fields;
814+
fields.reserve(s.fields().size());
815+
for (const auto& field : s.fields()) {
816+
fields.emplace_back(
817+
type_system::FieldIdentity{field.id(), std::string(field.name())},
818+
// TODO: SyntaxGraph doesn't ever set this to terse but TypeSystem
819+
// does.
820+
static_cast<type_system::PresenceQualifier>(field.presence()),
821+
convertType(field.type()),
822+
// TODO: default value
823+
std::nullopt,
824+
// TODO: annotations
825+
type_system::AnnotationsMap{}
826+
827+
);
828+
}
829+
return fields;
830+
};
831+
sgDef->visit(
832+
[&](const StructNode& s) {
833+
std::get<type_system::StructNode>(tsDef) = type_system::StructNode{
834+
type_system::Uri(s.uri()),
835+
makeFields(s),
836+
false,
837+
type_system::AnnotationsMap{}};
838+
},
839+
[&](const UnionNode& s) {
840+
std::get<type_system::UnionNode>(tsDef) = type_system::UnionNode{
841+
type_system::Uri(s.uri()),
842+
makeFields(s),
843+
false,
844+
type_system::AnnotationsMap{}};
845+
},
846+
[](const auto& n) {
847+
folly::throw_exception<std::logic_error>(fmt::format(
848+
"Encountered unexpected node type `{}`",
849+
folly::pretty_name<decltype(n)>()));
850+
});
851+
}
852+
853+
return folly::variant_match(cache_.at(rootSgDef), [](auto& def) {
854+
return type_system::DefinitionRef{&def};
855+
});
856+
}
857+
858+
type_system::TypeRef convertType(const TypeRef& type) {
859+
return type.trueType().visit(
860+
[](const Primitive& primitive) {
861+
switch (primitive) {
862+
case Primitive::BOOL:
863+
return type_system::TypeRef{type_system::TypeRef::Bool{}};
864+
case Primitive::BYTE:
865+
return type_system::TypeRef{type_system::TypeRef::Byte{}};
866+
case Primitive::I16:
867+
return type_system::TypeRef{type_system::TypeRef::I16{}};
868+
case Primitive::I32:
869+
return type_system::TypeRef{type_system::TypeRef::I32{}};
870+
case Primitive::I64:
871+
return type_system::TypeRef{type_system::TypeRef::I64{}};
872+
case Primitive::FLOAT:
873+
return type_system::TypeRef{type_system::TypeRef::Float{}};
874+
case Primitive::DOUBLE:
875+
return type_system::TypeRef{type_system::TypeRef::Double{}};
876+
case Primitive::STRING:
877+
return type_system::TypeRef{type_system::TypeRef::String{}};
878+
case Primitive::BINARY:
879+
return type_system::TypeRef{type_system::TypeRef::Binary{}};
880+
}
881+
},
882+
[&](const StructNode& s) {
883+
return type_system::TypeRef{
884+
std::get<type_system::StructNode>(cache_.at(&s.definition()))};
885+
},
886+
[&](const UnionNode& u) {
887+
return type_system::TypeRef{
888+
std::get<type_system::UnionNode>(cache_.at(&u.definition()))};
889+
},
890+
[&](const ExceptionNode&) {
891+
folly::throw_exception<std::runtime_error>(
892+
"Exceptions aren't supported by TypeSystem");
893+
return type_system::TypeRef(type_system::TypeRef::Bool{});
894+
},
895+
[&](const EnumNode& e) {
896+
return type_system::TypeRef{
897+
std::get<type_system::EnumNode>(cache_.at(&e.definition()))};
898+
},
899+
[&](const TypedefNode&) {
900+
folly::throw_exception<std::logic_error>(
901+
"Typedefs should have been resolved by trueType call");
902+
return type_system::TypeRef(type_system::TypeRef::Bool{});
903+
},
904+
[&](const List& l) {
905+
return type_system::TypeRef{
906+
type_system::detail::ListTypeRef{convertType(l.elementType())}};
907+
},
908+
[&](const Set& s) {
909+
return type_system::TypeRef{
910+
type_system::detail::SetTypeRef{convertType(s.elementType())}};
911+
},
912+
[&](const Map& m) {
913+
return type_system::TypeRef{type_system::detail::MapTypeRef{
914+
convertType(m.keyType()), convertType(m.valueType())}};
915+
});
916+
}
917+
918+
const detail::SchemaBackedResolver& resolver_;
919+
folly::F14NodeMap<const DefinitionNode*, TSDefinition> cache_;
920+
};
921+
} // namespace
922+
923+
type_system::TypeSystem& SyntaxGraph::asTypeSystem() {
924+
if (typeSystemFacade_) {
925+
return *typeSystemFacade_;
926+
}
927+
if (auto* resolver = dynamic_cast<const detail::SchemaBackedResolver*>(
928+
resolver_.get().unwrap())) {
929+
typeSystemFacade_ = std::make_unique<TypeSystemFacade>(*resolver);
930+
return *typeSystemFacade_;
931+
}
932+
folly::throw_exception<std::runtime_error>(
933+
"SyntaxGraph instance does not support URI-based lookup");
934+
}
935+
680936
} // namespace apache::thrift::syntax_graph
681937

682938
#endif // THRIFT_SCHEMA_AVAILABLE

third-party/thrift/src/thrift/lib/cpp2/schema/SyntaxGraph.h

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,10 @@
4747
#include <thrift/common/tree_printer.h>
4848
#include <thrift/lib/cpp2/schema/gen-cpp2/syntax_graph_types.h>
4949

50+
namespace apache::thrift::type_system {
51+
class TypeSystem;
52+
}
53+
5054
namespace apache::thrift::syntax_graph {
5155

5256
/**
@@ -682,6 +686,7 @@ class EnumNode final : folly::MoveOnly,
682686
/**
683687
* A mapping of enum name to its i32 value.
684688
*/
689+
// TODO: these can also have annotations
685690
class Value : detail::WithName {
686691
public:
687692
Value(std::string_view name, std::int32_t i32)
@@ -1558,13 +1563,26 @@ class SyntaxGraph final : public detail::WithDebugPrinting<SyntaxGraph> {
15581563
*/
15591564
ProgramNode::IncludesList programs() const;
15601565

1566+
/**
1567+
* Provides a view of all definitions in the schema as a TypeSystem.
1568+
*
1569+
* This object must be kept alive for the lifetime of the returned object.
1570+
*
1571+
* NOTE: the returned object is NOT thread-safe.
1572+
*
1573+
* Throws `std::runtime_error` if this instance does not support URI-based
1574+
* lookup (which does not happen with any official Resolver implementations).
1575+
*/
1576+
type_system::TypeSystem& asTypeSystem();
1577+
15611578
explicit SyntaxGraph(std::unique_ptr<detail::Resolver> resolver);
15621579

15631580
void printTo(
15641581
tree_printer::scope& scope, detail::VisitationTracker& visited) const;
15651582

15661583
private:
15671584
folly::not_null_unique_ptr<const detail::Resolver> resolver_;
1585+
std::unique_ptr<type_system::TypeSystem> typeSystemFacade_;
15681586

15691587
friend const DefinitionNode& detail::lookUpDefinition(
15701588
const SyntaxGraph&, const apache::thrift::type::DefinitionKey&);

0 commit comments

Comments
 (0)