Skip to content

Commit

Permalink
add API to visit union
Browse files Browse the repository at this point in the history
Summary:
This diff added `visit_union` that applies the callable to active member of thrift union. Example:

  visit_union(thriftUnion, [](const ThriftField& meta, auto&& value) {
    LOG(INFO) << meta.name << " --> " << value;
  })

ThriftField schema is defined here: https://git.io/JJQpY

# `visit_union` vs `std::visit`

1. Parameter order is different. `visit_union` takes callable as last argument for API readability and consistency. [`boost::hana::unpack`](https://www.boost.org/doc/libs/1_74_0/libs/hana/doc/html/group__group-Foldable.html#ga7b0c23944364ce61136e10b978ae2170) chose the same design with explanation:
2. It doesn't allow callable to return a value. Reasons
    1. Prevent misusing when union is `__EMPTY__` (should it be default initialized, value initialized or throw exception?)
    2. This is redundant, user can use lambda-captured reference to return value from callable.
3. It doesn't allow multiple union objects. Using multiple objects are not common, and user can achieve it by nested lambda.

Reviewed By: vitaut

Differential Revision: D23252286

fbshipit-source-id: b053d67605dfaa408b9e07d281a3ea02018642c9
  • Loading branch information
Mizuchi authored and facebook-github-bot committed Aug 23, 2020
1 parent 76e4080 commit f38c234
Show file tree
Hide file tree
Showing 6 changed files with 340 additions and 3 deletions.
3 changes: 3 additions & 0 deletions thrift/compiler/generate/t_mstch_cpp2_generator.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2042,6 +2042,9 @@ void t_mstch_cpp2_generator::generate_visitation(const t_program* program) {
cache_->programs_[id],
"module_for_each_field.h",
name + "_for_each_field.h");

render_to_file(
cache_->programs_[id], "module_visit_union.h", name + "_visit_union.h");
}

void t_mstch_cpp2_generator::generate_structs(t_program const* program) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<%!

Copyright (c) Facebook, Inc. and its affiliates.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

%><% > Autogen%>
#pragma once

#include "<%program:include_prefix%><%program:name%>_metadata.h"
#include <thrift/lib/cpp2/visitation/visit_union.h>

namespace apache {
namespace thrift {
namespace detail {
<%#program:structs%>
<%#struct:union?%>
template <>
struct VisitUnion<<% > common/namespace_cpp2%><%struct:name%>> {
template <typename F, typename T>
void operator()(F&& f, T&& t) const {
using Union = std::remove_reference_t<T>;
constexpr auto get_metadata = get_field_metadata<<% > common/namespace_cpp2%><%struct:name%>>;
switch (t.getType()) {
<%#struct:fields%>
case Union::Type::<%field:cpp_name%>:
return f(get_metadata(<%field:index%>), *static_cast<T&&>(t).<%field:cpp_name%>_ref());
<%/struct:fields%>
case Union::Type::__EMPTY__: ;
}
}
};
<%/struct:union?%>
<%/program:structs%>
} // namespace detail
} // namespace thrift
} // namespace apache
163 changes: 163 additions & 0 deletions thrift/compiler/test/fixtures/visitation/gen-cpp2/module_visit_union.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
/**
* Autogenerated by Thrift
*
* DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
* @generated
*/
#pragma once

#include "thrift/compiler/test/fixtures/visitation/gen-cpp2/module_metadata.h"
#include <thrift/lib/cpp2/visitation/visit_union.h>

namespace apache {
namespace thrift {
namespace detail {

template <>
struct VisitUnion<::test_cpp2::cpp_reflection::union1> {
template <typename F, typename T>
void operator()(F&& f, T&& t) const {
using Union = std::remove_reference_t<T>;
constexpr auto get_metadata = get_field_metadata<::test_cpp2::cpp_reflection::union1>;
switch (t.getType()) {
case Union::Type::ui:
return f(get_metadata(0), *static_cast<T&&>(t).ui_ref());
case Union::Type::ud:
return f(get_metadata(1), *static_cast<T&&>(t).ud_ref());
case Union::Type::us:
return f(get_metadata(2), *static_cast<T&&>(t).us_ref());
case Union::Type::ue:
return f(get_metadata(3), *static_cast<T&&>(t).ue_ref());
case Union::Type::__EMPTY__: ;
}
}
};
template <>
struct VisitUnion<::test_cpp2::cpp_reflection::union2> {
template <typename F, typename T>
void operator()(F&& f, T&& t) const {
using Union = std::remove_reference_t<T>;
constexpr auto get_metadata = get_field_metadata<::test_cpp2::cpp_reflection::union2>;
switch (t.getType()) {
case Union::Type::ui_2:
return f(get_metadata(0), *static_cast<T&&>(t).ui_2_ref());
case Union::Type::ud_2:
return f(get_metadata(1), *static_cast<T&&>(t).ud_2_ref());
case Union::Type::us_2:
return f(get_metadata(2), *static_cast<T&&>(t).us_2_ref());
case Union::Type::ue_2:
return f(get_metadata(3), *static_cast<T&&>(t).ue_2_ref());
case Union::Type::__EMPTY__: ;
}
}
};
template <>
struct VisitUnion<::test_cpp2::cpp_reflection::union3> {
template <typename F, typename T>
void operator()(F&& f, T&& t) const {
using Union = std::remove_reference_t<T>;
constexpr auto get_metadata = get_field_metadata<::test_cpp2::cpp_reflection::union3>;
switch (t.getType()) {
case Union::Type::ui_3:
return f(get_metadata(0), *static_cast<T&&>(t).ui_3_ref());
case Union::Type::ud_3:
return f(get_metadata(1), *static_cast<T&&>(t).ud_3_ref());
case Union::Type::us_3:
return f(get_metadata(2), *static_cast<T&&>(t).us_3_ref());
case Union::Type::ue_3:
return f(get_metadata(3), *static_cast<T&&>(t).ue_3_ref());
case Union::Type::__EMPTY__: ;
}
}
};
template <>
struct VisitUnion<::test_cpp2::cpp_reflection::unionA> {
template <typename F, typename T>
void operator()(F&& f, T&& t) const {
using Union = std::remove_reference_t<T>;
constexpr auto get_metadata = get_field_metadata<::test_cpp2::cpp_reflection::unionA>;
switch (t.getType()) {
case Union::Type::i:
return f(get_metadata(0), *static_cast<T&&>(t).i_ref());
case Union::Type::d:
return f(get_metadata(1), *static_cast<T&&>(t).d_ref());
case Union::Type::s:
return f(get_metadata(2), *static_cast<T&&>(t).s_ref());
case Union::Type::e:
return f(get_metadata(3), *static_cast<T&&>(t).e_ref());
case Union::Type::a:
return f(get_metadata(4), *static_cast<T&&>(t).a_ref());
case Union::Type::__EMPTY__: ;
}
}
};
template <>
struct VisitUnion<::test_cpp2::cpp_reflection::union_with_special_names> {
template <typename F, typename T>
void operator()(F&& f, T&& t) const {
using Union = std::remove_reference_t<T>;
constexpr auto get_metadata = get_field_metadata<::test_cpp2::cpp_reflection::union_with_special_names>;
switch (t.getType()) {
case Union::Type::get:
return f(get_metadata(0), *static_cast<T&&>(t).get_ref());
case Union::Type::getter:
return f(get_metadata(1), *static_cast<T&&>(t).getter_ref());
case Union::Type::lists:
return f(get_metadata(2), *static_cast<T&&>(t).lists_ref());
case Union::Type::maps:
return f(get_metadata(3), *static_cast<T&&>(t).maps_ref());
case Union::Type::name:
return f(get_metadata(4), *static_cast<T&&>(t).name_ref());
case Union::Type::name_to_value:
return f(get_metadata(5), *static_cast<T&&>(t).name_to_value_ref());
case Union::Type::names:
return f(get_metadata(6), *static_cast<T&&>(t).names_ref());
case Union::Type::prefix_tree:
return f(get_metadata(7), *static_cast<T&&>(t).prefix_tree_ref());
case Union::Type::sets:
return f(get_metadata(8), *static_cast<T&&>(t).sets_ref());
case Union::Type::setter:
return f(get_metadata(9), *static_cast<T&&>(t).setter_ref());
case Union::Type::str:
return f(get_metadata(10), *static_cast<T&&>(t).str_ref());
case Union::Type::strings:
return f(get_metadata(11), *static_cast<T&&>(t).strings_ref());
case Union::Type::type:
return f(get_metadata(12), *static_cast<T&&>(t).type_ref());
case Union::Type::value:
return f(get_metadata(13), *static_cast<T&&>(t).value_ref());
case Union::Type::value_to_name:
return f(get_metadata(14), *static_cast<T&&>(t).value_to_name_ref());
case Union::Type::values:
return f(get_metadata(15), *static_cast<T&&>(t).values_ref());
case Union::Type::id:
return f(get_metadata(16), *static_cast<T&&>(t).id_ref());
case Union::Type::ids:
return f(get_metadata(17), *static_cast<T&&>(t).ids_ref());
case Union::Type::descriptor:
return f(get_metadata(18), *static_cast<T&&>(t).descriptor_ref());
case Union::Type::descriptors:
return f(get_metadata(19), *static_cast<T&&>(t).descriptors_ref());
case Union::Type::key:
return f(get_metadata(20), *static_cast<T&&>(t).key_ref());
case Union::Type::keys:
return f(get_metadata(21), *static_cast<T&&>(t).keys_ref());
case Union::Type::annotation:
return f(get_metadata(22), *static_cast<T&&>(t).annotation_ref());
case Union::Type::annotations:
return f(get_metadata(23), *static_cast<T&&>(t).annotations_ref());
case Union::Type::member:
return f(get_metadata(24), *static_cast<T&&>(t).member_ref());
case Union::Type::members:
return f(get_metadata(25), *static_cast<T&&>(t).members_ref());
case Union::Type::field:
return f(get_metadata(26), *static_cast<T&&>(t).field_ref());
case Union::Type::fields:
return f(get_metadata(27), *static_cast<T&&>(t).fields_ref());
case Union::Type::__EMPTY__: ;
}
}
};
} // namespace detail
} // namespace thrift
} // namespace apache
50 changes: 50 additions & 0 deletions thrift/lib/cpp2/visitation/visit_union.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#pragma once

#include <folly/Traits.h>
#include <thrift/lib/cpp2/visitation/metadata.h>

namespace apache {
namespace thrift {
namespace detail {
template <class T>
struct VisitUnion {
static_assert(sizeof(T) < 0, "Must include visitation header");
};
} // namespace detail

/**
* Applies the callable to active member of thrift union. Example:
*
* visit_union(thriftUnion, [](const ThriftField& meta, auto&& value) {
* LOG(INFO) << *meta.name_ref() << " --> " << value;
* })
*
* ThriftField schema is defined here: https://git.io/JJQpY
* If `no_metadata` thrift option is enabled, ThriftField will be empty.
* If union is empty, callable won't be called.
*
* @param t thrift union
* @param f a callable that accepts all member types from union
*/
template <typename T, typename F>
void visit_union(T&& t, F f) {
return detail::VisitUnion<folly::remove_cvref_t<T>>()(f, static_cast<T&&>(t));
}
} // namespace thrift
} // namespace apache
6 changes: 3 additions & 3 deletions thrift/test/UnionFieldRef.thrift
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@
namespace cpp2 apache.thrift.test

union Basic {
1: string str
2: i64 int64
3: list<i32> list_i32
2: string str
1: i64 int64
4: list<i32> list_i32
}

union DuplicateType {
Expand Down
73 changes: 73 additions & 0 deletions thrift/test/visitation_visit_union_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#include <folly/portability/GTest.h>
#include <thrift/test/gen-cpp2/UnionFieldRef_visit_union.h> // @manual=:union_field_ref-cpp2-visitation
using namespace std;

namespace apache {
namespace thrift {
namespace test {
TEST(UnionFieldTest, basic) {
Basic a;
visit_union(a, [&](auto&&, auto&&) { FAIL(); });

static const string str = "foo";
a.str_ref() = str;
visit_union(a, [](auto&& meta, auto&& v) {
EXPECT_EQ(meta.name, "str");
EXPECT_EQ(meta.type.getType(), meta.type.t_primitive);
EXPECT_EQ(meta.id, 2);
EXPECT_EQ(meta.is_optional, false);
if constexpr (std::is_same_v<decltype(v), string&>) {
EXPECT_EQ(v, str);
} else {
FAIL();
}
});

static const int64_t int64 = 42LL << 42;
a.int64_ref() = int64;
visit_union(a, [](auto&& meta, auto&& v) {
EXPECT_EQ(meta.name, "int64");
EXPECT_EQ(meta.type.getType(), meta.type.t_primitive);
EXPECT_EQ(meta.id, 1);
EXPECT_EQ(meta.is_optional, false);
EXPECT_EQ(typeid(v), typeid(int64_t));
if constexpr (std::is_same_v<decltype(v), int64_t&>) {
EXPECT_EQ(v, int64);
} else {
FAIL();
}
});

static const vector<int32_t> list_i32 = {3, 1, 2};
a.list_i32_ref() = list_i32;
visit_union(a, [](auto&& meta, auto&& v) {
EXPECT_EQ(meta.name, "list_i32");
EXPECT_EQ(meta.type.getType(), meta.type.t_list);
EXPECT_EQ(meta.id, 4);
EXPECT_EQ(meta.is_optional, false);
if constexpr (std::is_same_v<decltype(v), vector<int32_t>&>) {
EXPECT_EQ(v, list_i32);
} else {
FAIL();
}
});
}
} // namespace test
} // namespace thrift
} // namespace apache

0 comments on commit f38c234

Please sign in to comment.