|
17 | 17 | #include <thrift/lib/cpp2/schema/SyntaxGraph.h>
|
18 | 18 |
|
19 | 19 | #include <thrift/lib/cpp/util/EnumUtils.h>
|
| 20 | +#include <thrift/lib/cpp2/dynamic/TypeSystem.h> |
20 | 21 | #include <thrift/lib/cpp2/schema/detail/Resolver.h>
|
21 | 22 | #include <thrift/lib/cpp2/schema/detail/SchemaBackedResolver.h>
|
22 | 23 |
|
|
26 | 27 |
|
27 | 28 | #include <fmt/core.h>
|
28 | 29 |
|
29 |
| -#include <functional> |
30 |
| -#include <ostream> |
| 30 | +#include <queue> |
31 | 31 | #include <stdexcept>
|
32 | 32 |
|
33 | 33 | #ifdef THRIFT_SCHEMA_AVAILABLE
|
@@ -677,6 +677,262 @@ void SyntaxGraph::printTo(
|
677 | 677 | }
|
678 | 678 | }
|
679 | 679 |
|
| 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 | + |
680 | 936 | } // namespace apache::thrift::syntax_graph
|
681 | 937 |
|
682 | 938 | #endif // THRIFT_SCHEMA_AVAILABLE
|
0 commit comments