Program Listing for File optional.h

Program Listing for File optional.h#

Return to documentation for file (tvm/ffi/optional.h)

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.
 */

#ifndef TVM_FFI_OPTIONAL_H_
#define TVM_FFI_OPTIONAL_H_

#include <tvm/ffi/any.h>
#include <tvm/ffi/error.h>
#include <tvm/ffi/object.h>
#include <tvm/ffi/string.h>

#include <optional>
#include <string>
#include <utility>

namespace tvm {
namespace ffi {

// Note: We place optional in tvm/ffi instead of tvm/ffi/container
// because optional itself is an inherent core component of the FFI system.
template <typename T>
inline constexpr bool is_optional_type_v = false;

template <typename T>
inline constexpr bool is_optional_type_v<Optional<T>> = true;

// Fallback specialization for types that do NOT enable Any storage
// (`TypeTraits<T>::storage_enabled == false`), such as non-owning view types
// that cannot be moved into an Any. These simply reuse std::optional<T>.
template <typename T>
class Optional<T, std::enable_if_t<!TypeTraits<T>::storage_enabled>> {
 public:
  // default constructors.
  Optional() = default;
  // NOLINTBEGIN(google-explicit-constructor)
  Optional(const Optional& other) = default;
  Optional(Optional&& other) noexcept = default;
  Optional(std::optional<T> other) : data_(std::move(other)) {}
  Optional(std::nullopt_t) {}
  Optional(T other) : data_(std::move(other)) {}
  // NOLINTEND(google-explicit-constructor)

  Optional& operator=(const Optional& other) = default;
  Optional& operator=(Optional&& other) noexcept = default;

  TVM_FFI_INLINE Optional& operator=(T other) {
    data_ = std::move(other);
    return *this;
  }

  TVM_FFI_INLINE Optional& operator=(std::nullopt_t) {
    data_ = std::nullopt;
    return *this;
  }

  TVM_FFI_INLINE const T& value() const& {
    if (TVM_FFI_PREDICT_FALSE(!data_.has_value())) {
      TVM_FFI_THROW(RuntimeError) << "Back optional access";
    }
    return *data_;
  }

  TVM_FFI_INLINE T&& value() && {
    if (TVM_FFI_PREDICT_FALSE(!data_.has_value())) {
      TVM_FFI_THROW(RuntimeError) << "Back optional access";
    }
    return *std::move(data_);
  }

  template <typename U = std::remove_cv_t<T>>
  TVM_FFI_INLINE T value_or(U&& default_value) const {
    return data_.value_or(std::forward<U>(default_value));
  }

  TVM_FFI_INLINE explicit operator bool() const noexcept { return data_.has_value(); }

  TVM_FFI_INLINE bool has_value() const noexcept { return data_.has_value(); }

  TVM_FFI_INLINE bool operator==(const Optional& other) const { return data_ == other.data_; }
  TVM_FFI_INLINE bool operator!=(const Optional& other) const { return data_ != other.data_; }
  template <typename U>
  TVM_FFI_INLINE bool operator==(const U& other) const {
    return data_ == other;
  }
  template <typename U>
  TVM_FFI_INLINE bool operator!=(const U& other) const {
    return data_ != other;
  }

  // NOLINTBEGIN(bugprone-unchecked-optional-access)
  TVM_FFI_INLINE T&& operator*() && noexcept { return *std::move(data_); }
  TVM_FFI_INLINE const T& operator*() const& noexcept { return *data_; }
  // NOLINTEND(bugprone-unchecked-optional-access)

 private:
  std::optional<T> data_;
};

template <typename T>
class Optional<T, std::enable_if_t<TypeTraits<T>::storage_enabled>> {
 public:
  Optional() = default;
  // NOLINTBEGIN(google-explicit-constructor)
  Optional(std::nullopt_t) {}
  Optional(const Optional& other) = default;
  Optional(Optional&& other) noexcept = default;
  Optional(const T& value) : data_(value) {}
  Optional(T&& value) : data_(std::move(value)) {}
  Optional(std::optional<T> other) {
    if (other.has_value()) {
      data_ = Any(*std::move(other));
    }
  }
  // NOLINTEND(google-explicit-constructor)

  Optional& operator=(const Optional& other) = default;
  Optional& operator=(Optional&& other) noexcept = default;

  TVM_FFI_INLINE Optional& operator=(T other) {
    data_ = Any(std::move(other));
    return *this;
  }

  TVM_FFI_INLINE Optional& operator=(std::nullopt_t) {
    data_.reset();
    return *this;
  }

  TVM_FFI_INLINE Optional& operator=(std::nullptr_t) {
    data_.reset();
    return *this;
  }

  TVM_FFI_INLINE T value() const& {
    if (TVM_FFI_PREDICT_FALSE(!has_value())) {
      TVM_FFI_THROW(RuntimeError) << "Back optional access";
    }
    // The invariant guarantees the stored value is exactly a T, so decode it
    // directly with the low-level after-check path (no conversion/cast).
    return details::AnyUnsafe::CopyFromAnyViewAfterCheck<T>(data_);
  }

  TVM_FFI_INLINE T value() && {
    if (TVM_FFI_PREDICT_FALSE(!has_value())) {
      TVM_FFI_THROW(RuntimeError) << "Back optional access";
    }
    return details::AnyUnsafe::MoveFromAnyAfterCheck<T>(std::move(data_));
  }

  template <typename U = std::remove_cv_t<T>>
  TVM_FFI_INLINE T value_or(U&& default_value) const {
    if (has_value()) {
      return details::AnyUnsafe::CopyFromAnyViewAfterCheck<T>(data_);
    }
    return T(std::forward<U>(default_value));
  }

  TVM_FFI_INLINE explicit operator bool() const noexcept { return has_value(); }

  TVM_FFI_INLINE bool has_value() const noexcept {
    return data_.type_index() != TypeIndex::kTVMFFINone;
  }

  template <typename U>
  TVM_FFI_INLINE auto as() const {
    return data_.template as<U>();
  }

  TVM_FFI_INLINE T operator*() const& {
    return details::AnyUnsafe::CopyFromAnyViewAfterCheck<T>(data_);
  }
  TVM_FFI_INLINE T operator*() && {
    return details::AnyUnsafe::MoveFromAnyAfterCheck<T>(std::move(data_));
  }

  // comparison with nullopt / nullptr
  TVM_FFI_INLINE bool operator==(std::nullopt_t) const noexcept { return !has_value(); }
  TVM_FFI_INLINE bool operator!=(std::nullopt_t) const noexcept { return has_value(); }
  TVM_FFI_INLINE bool operator==(std::nullptr_t) const noexcept { return !has_value(); }
  TVM_FFI_INLINE bool operator!=(std::nullptr_t) const noexcept { return has_value(); }

  // comparison with another Optional<T>
  TVM_FFI_INLINE auto operator==(const Optional& other) const {
    // support case where sub-class returns a symbolic ref type.
    using RetType = decltype(std::declval<T>() == std::declval<T>());
    if (data_.same_as(other.data_)) return RetType(true);
    if (has_value() && other.has_value()) return **this == *other;
    return RetType(false);
  }
  TVM_FFI_INLINE auto operator!=(const Optional& other) const {
    using RetType = decltype(std::declval<T>() != std::declval<T>());
    if (data_.same_as(other.data_)) return RetType(false);
    if (has_value() && other.has_value()) return **this != *other;
    return RetType(true);
  }

  // comparison with a std::optional<T>
  TVM_FFI_INLINE auto operator==(const std::optional<T>& other) const {
    using RetType = decltype(std::declval<T>() == std::declval<T>());
    if (has_value() && other.has_value()) return **this == *other;
    return RetType(!has_value() && !other.has_value());
  }
  TVM_FFI_INLINE auto operator!=(const std::optional<T>& other) const {
    using RetType = decltype(std::declval<T>() != std::declval<T>());
    if (has_value() && other.has_value()) return **this != *other;
    return RetType(has_value() != other.has_value());
  }

  // comparison with a value of another type U
  template <typename U, typename = std::enable_if_t<!is_optional_type_v<U> &&
                                                    !std::is_same_v<U, std::nullopt_t> &&
                                                    !std::is_same_v<U, std::nullptr_t>>>
  TVM_FFI_INLINE auto operator==(const U& other) const {
    using RetType = decltype(std::declval<T>() == std::declval<U>());
    if constexpr (std::is_base_of_v<ObjectRef, T> && std::is_base_of_v<ObjectRef, U>) {
      // support case where sub-class returns a symbolic ref type.
      if (data_.same_as(other)) return RetType(true);
    }
    if (!has_value()) return RetType(false);
    return **this == other;
  }
  template <typename U, typename = std::enable_if_t<!is_optional_type_v<U> &&
                                                    !std::is_same_v<U, std::nullopt_t> &&
                                                    !std::is_same_v<U, std::nullptr_t>>>
  TVM_FFI_INLINE auto operator!=(const U& other) const {
    using RetType = decltype(std::declval<T>() != std::declval<U>());
    if constexpr (std::is_base_of_v<ObjectRef, T> && std::is_base_of_v<ObjectRef, U>) {
      if (data_.same_as(other)) return RetType(false);
    }
    if (!has_value()) return RetType(true);
    return **this != other;
  }

  TVM_FFI_INLINE bool same_as(const Optional& other) const { return data_.same_as(other.data_); }

  template <typename U = T, typename = std::enable_if_t<std::is_base_of_v<ObjectRef, U>>>
  TVM_FFI_INLINE bool same_as(const U& other) const {
    return data_.same_as(other);
  }

 private:
  friend struct TypeTraits<Optional<T>>;
  // construct directly from an Any backing store.
  explicit Optional(Any data) : data_(std::move(data)) {}
  TVM_FFI_INLINE AnyView ToAnyView() const { return data_.operator AnyView(); }
  TVM_FFI_INLINE Any MoveToAny() && { return std::move(data_); }
  Any data_;
};

template <typename T>
inline constexpr bool use_default_type_traits_v<Optional<T>> = false;

template <typename T>
struct TypeTraits<Optional<T>> : public TypeTraitsBase {
  // storage_enabled propagates from T: Optional<T> can live in an Any exactly
  // when T can. This keeps nested Optional<Optional<T>> and Optional<T> used
  // inside Variant<...>/containers Any-backed iff T is storage-enabled.
  static constexpr bool storage_enabled = TypeTraits<T>::storage_enabled;

  TVM_FFI_INLINE static void CopyToAnyView(const Optional<T>& src, TVMFFIAny* result) {
    if constexpr (TypeTraits<T>::storage_enabled) {
      // Storage-enabled: the Any already holds the exact representation.
      *result = src.ToAnyView().CopyToTVMFFIAny();
    } else {
      if (src.has_value()) {
        TypeTraits<T>::CopyToAnyView(*src, result);
      } else {
        TypeTraits<std::nullptr_t>::CopyToAnyView(nullptr, result);
      }
    }
  }

  TVM_FFI_INLINE static void MoveToAny(Optional<T> src, TVMFFIAny* result) {
    if constexpr (TypeTraits<T>::storage_enabled) {
      *result = details::AnyUnsafe::MoveAnyToTVMFFIAny(std::move(src).MoveToAny());
    } else {
      if (src.has_value()) {
        TypeTraits<T>::MoveToAny(*std::move(src), result);
      } else {
        TypeTraits<std::nullptr_t>::CopyToAnyView(nullptr, result);
      }
    }
  }

  TVM_FFI_INLINE static bool CheckAnyStrict(const TVMFFIAny* src) {
    if (src->type_index == TypeIndex::kTVMFFINone) return true;
    return TypeTraits<T>::CheckAnyStrict(src);
  }

  TVM_FFI_INLINE static Optional<T> CopyFromAnyViewAfterCheck(const TVMFFIAny* src) {
    if constexpr (TypeTraits<T>::storage_enabled) {
      return Optional<T>(Any(AnyView::CopyFromTVMFFIAny(*src)));
    } else {
      if (src->type_index == TypeIndex::kTVMFFINone) return Optional<T>(std::nullopt);
      return Optional<T>(TypeTraits<T>::CopyFromAnyViewAfterCheck(src));
    }
  }

  TVM_FFI_INLINE static Optional<T> MoveFromAnyAfterCheck(TVMFFIAny* src) {
    if constexpr (TypeTraits<T>::storage_enabled) {
      return Optional<T>(details::AnyUnsafe::MoveTVMFFIAnyToAny(src));
    } else {
      if (src->type_index == TypeIndex::kTVMFFINone) return Optional<T>(std::nullopt);
      return Optional<T>(TypeTraits<T>::MoveFromAnyAfterCheck(src));
    }
  }

  TVM_FFI_INLINE static std::optional<Optional<T>> TryCastFromAnyView(const TVMFFIAny* src) {
    if (src->type_index == TypeIndex::kTVMFFINone) return Optional<T>(std::nullopt);
    if (std::optional<T> opt = TypeTraits<T>::TryCastFromAnyView(src)) {
      return Optional<T>(*std::move(opt));
    }
    // Important to be explicit here because nullopt can convert to
    // std::optional<T>(nullopt), which would incorrectly signal success.
    return std::optional<Optional<T>>(std::nullopt);
  }

  TVM_FFI_INLINE static std::string GetMismatchTypeInfo(const TVMFFIAny* src) {
    return TypeTraits<T>::GetMismatchTypeInfo(src);
  }

  TVM_FFI_INLINE static std::string TypeStr() {
    return "Optional<" + TypeTraits<T>::TypeStr() + ">";
  }
  TVM_FFI_INLINE static std::string TypeSchema() {
    return R"({"type":"Optional","args":[)" + details::TypeSchema<T>::v() + "]}";
  }
};
}  // namespace ffi
}  // namespace tvm
#endif  // TVM_FFI_OPTIONAL_H_