/* * Copyright (c) Meta Platforms, Inc. and 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace apache { namespace thrift { namespace op { namespace detail { // Gets a (potentally const&) value representing the default. // // C++'s default for the underlying native type, is the default for all // unstructured types. template struct GetDefault { static_assert(!type::is_a_v, ""); constexpr auto operator()() const { return type::native_type{}; } }; // Gets a (potentally const&) value representing the **intrinsic** default. // // This is the same as the default for all non-structured types. template struct GetIntrinsicDefault : GetDefault { static_assert(!type::is_a_v, ""); }; // Clear the given value, setting it to it's intrinsic default. template struct Clear { using Tag = TagOrId; constexpr void operator()(type::native_type& val) const { clear(val, Tag{}); } private: template static void clear(T& val, type::structured_c) { thrift::clear(val); } template static void clear(T& val, type::container_c) { val.clear(); } template constexpr static void clear(T& val, type::all_c) { val = GetIntrinsicDefault{}(); } }; template struct Clear, type::if_not_thrift_type_tag> { template constexpr void operator()(T& val) const { Clear>{}(val); } }; template <> struct Clear { template constexpr void operator()(Id, T& val) const { Clear>{}(val); } template constexpr void operator()(T& val) const { Clear>{}(val); } }; // Checks if the given value is empty. template struct IsEmpty { constexpr bool operator()(const type::native_type& value) const { return empty(value, Tag{}); } private: template static bool empty(const T& val, type::structured_c) { return thrift::empty(val); } template static bool empty(const T& val, type::container_c) { return val.empty(); } template static bool empty(const T& val, type::string_c) { return val.empty(); } template constexpr static bool empty(const T& val, type::all_c) { return op::identical(val, GetIntrinsicDefault{}()); } template static bool empty(const std::unique_ptr& val, ATag tag) { return val == nullptr || empty(*val, tag); } }; template <> struct IsEmpty { template constexpr bool operator()(T& val) const { return IsEmpty>{}(val); } }; // Gets the intrinsic default via `thrift::clear` // // Useful for any type that supports custom defaults. template struct ThriftClearDefault { const auto& operator()() const { static const auto& obj = *[] { auto* p = new type::native_type(); SCOPE_FAIL { delete p; }; apache::thrift::clear(*p); return p; }(); return obj; } }; // Gets the intrinsic default via `op::create` template struct CreateDefault { FOLLY_EXPORT const auto& operator()() const { // If all nested fields does not have custom default (or default equals to // the intrinsic default), returns the reference to the intrinsic default. // This allows us to optimize for terse intern box so that the default // constructed field will be eligible for pointer comparison for empty // check. static const auto& id = GetIntrinsicDefault{}(); static const auto& d = op::equal({}, id) ? id : *(new type::native_type); return d; } }; // Structured types have statically addressable defaults ... template struct GetDefault> : CreateDefault> {}; template struct GetDefault> : CreateDefault> { }; template struct GetDefault> : CreateDefault> {}; // ... and need to be cleared to get the intrinsic default. template struct GetIntrinsicDefault> : ThriftClearDefault> {}; template struct GetIntrinsicDefault> : ThriftClearDefault> {}; // TODO(afuller): Is thrift::clear actually needed for union? template struct GetIntrinsicDefault> : ThriftClearDefault> {}; template using adapted_field_tag = type::field, FieldContext>; // Cache the result of op::create for adapters. template struct GetDefault> : CreateDefault> {}; template struct GetDefault< type::field, FieldContext>> { using Tag = type::field, FieldContext>; template const adapt_detail:: if_not_field_adapter, Struct>& operator()() const { return GetDefault>{}(); } template const adapt_detail:: if_field_adapter, Struct>& operator()() const { static const auto& obj = *[] { // TODO(afuller): Remove or move this logic to the adapter. auto* s = new Struct{}; SCOPE_FAIL { delete s; }; auto* adapted = new type::native_type(op::create(*s)); folly::lsan_ignore_object(s); return adapted; }(); return obj; } }; template struct GetIntrinsicDefault> { using Tag = type::adapted; const auto& operator()() const { // TODO(dokwon): Consider adding enforcement to striping reference in // 'Adapter::fromThrift' instead of copying. static const auto* p = new type::native_type( Adapter::fromThrift(folly::copy(GetIntrinsicDefault{}()))); return *p; } }; template struct GetIntrinsicDefault> { using Tag = adapted_field_tag; template const adapt_detail:: if_not_field_adapter, Struct>& operator()() const { return GetIntrinsicDefault>{}(); } template const adapt_detail:: if_field_adapter, Struct>& operator()() const { static const auto& obj = *[] { // TODO(afuller): Remove or move this logic to the adapter. auto* s = new Struct{}; SCOPE_FAIL { delete s; }; apache::thrift::clear(*s); auto adapted = new type::native_type(AdapterT::fromThriftField( folly::copy(GetIntrinsicDefault{}()), FieldContext{*s})); SCOPE_FAIL { delete adapted; }; adapt_detail::construct(adapted, *s); folly::lsan_ignore_object(s); return adapted; }(); return obj; } }; // Delegate to adapter. template struct Clear> { using Tag = type::adapted; constexpr void operator()(type::native_type& value) const { adapt_detail::clear(value); } }; template struct IsEmpty> { using Tag = type::adapted; template constexpr adapt_detail::if_is_empty_adapter> operator()(const type::native_type& value) const { return AdapterT::isEmpty(value); } // Delegate to op::identical. template constexpr adapt_detail:: if_not_is_empty_adapter> operator()(const type::native_type& value) const { return op::identical(value, GetIntrinsicDefault{}()); } }; template struct IsEmpty> { using Tag = adapted_field_tag; template constexpr adapt_detail::if_is_empty_adapter> operator()(const type::native_type& value) const { return AdapterT::isEmpty(value); } template constexpr adapt_detail:: if_not_is_empty_adapter> operator()(const type::native_type& value) const { return op::identical(value, GetIntrinsicDefault{}()); } }; // TODO(dokwon): Support field_ref types. template struct GetDefault> : GetDefault {}; template struct GetIntrinsicDefault> : GetIntrinsicDefault {}; template struct IsEmpty> : IsEmpty {}; struct ClearOptionalField { template void operator()(optional_boxed_field_ref field, Struct&) const { field.reset(); } template void operator()(optional_field_ref field, Struct&) const { field.reset(); } template void operator()(union_field_ref field, Union& u) const { if (field.has_value()) { thrift::clear(u); assert(!field.has_value()); } } template void operator()(terse_intern_boxed_field_ref field, Struct&) const { field.reset(); } }; template struct ClearField {}; template struct ClearField> : ClearOptionalField { using ClearOptionalField::operator(); template void operator()(required_field_ref field, Struct&) const { Clear{}(*field); } template void operator()(terse_field_ref field, Struct&) const { Clear{}(*field); } template void operator()(field_ref field, Struct&) const { Clear{}(*field); } template void operator()(std::shared_ptr& field, Struct&) const { if constexpr (apache::thrift::detail::qualifier::is_cpp_ref_field_optional< Struct, apache::thrift::field_id>::value) { field = nullptr; } else if (field) { Clear{}(*field); } } template void operator()(std::unique_ptr& field, Struct&) const { if constexpr (apache::thrift::detail::qualifier::is_cpp_ref_field_optional< Struct, apache::thrift::field_id>::value) { field = nullptr; } else if (field) { Clear{}(*field); } } }; template struct ClearField> : ClearOptionalField { using Tag = adapted_field_tag; static_assert(type::is_concrete_v, ""); using ClearOptionalField::operator(); template void operator()(required_field_ref field, Struct& s) const { ::apache::thrift::adapt_detail::clear(*field, s); } template void operator()(terse_field_ref field, Struct& s) const { ::apache::thrift::adapt_detail::clear(*field, s); } template void operator()(field_ref field, Struct& s) const { ::apache::thrift::adapt_detail::clear(*field, s); } template void operator()(std::shared_ptr& field, Struct& s) const { if constexpr (apache::thrift::detail::qualifier::is_cpp_ref_field_optional< Struct, apache::thrift::field_id>::value) { field = nullptr; } else if (field) { ::apache::thrift::adapt_detail::clear(*field, s); } } template void operator()(std::unique_ptr& field, Struct& s) const { if constexpr (apache::thrift::detail::qualifier::is_cpp_ref_field_optional< Struct, apache::thrift::field_id>::value) { field = nullptr; } else if (field) { ::apache::thrift::adapt_detail::clear(*field, s); } } }; template struct Clear> { static_assert(type::is_id_v, ""); using T = type::native_type; constexpr void operator()(T& val) const { using FieldTag = op::get_field_tag; ClearField{}(op::get(val), val); } }; } // namespace detail } // namespace op } // namespace thrift } // namespace apache