
/*
 * Souffle - A Datalog Compiler
 * Copyright (c) 2021, The Souffle Developers. All rights reserved.
 * Licensed under the Universal Permissive License v 1.0 as shown at:
 * - https://opensource.org/licenses/UPL
 * - <souffle root>/licenses/SOUFFLE-UPL.txt
 */

/************************************************************************
 *
 * @file DynamicCasting.h
 *
 * Common utilities for dynamic casting.
 *
 ***********************************************************************/

#pragma once

#include "souffle/utility/Types.h"
#include <cassert>
#include <type_traits>

namespace souffle {

/**
 * This class is used to tell as<> that cross-casting is allowed.
 * I use a named type rather than just a bool to make the code stand out.
 */
class AllowCrossCast {};

namespace detail {
template <typename A>
constexpr bool is_valid_cross_cast_option = std::is_same_v<A, void> || std::is_same_v<A, AllowCrossCast>;
}

/**
 * Helpers for `dynamic_cast`ing without having to specify redundant type qualifiers.
 * e.g. `as<AstLiteral>(p)` instead of `as<AstLiteral>(p)`.
 */
template <typename B, typename CastType = void, typename A,
        typename = std::enable_if_t<detail::is_valid_cross_cast_option<CastType>>>
auto as(A* x) {
    if constexpr (!std::is_same_v<CastType, AllowCrossCast> &&
                  !std::is_base_of_v<std::remove_const_t<B>, std::remove_const_t<A>>) {
        static_assert(std::is_base_of_v<std::remove_const_t<A>, std::remove_const_t<B>>,
                "`as<B, A>` does not allow cross-type dyn casts. "
                "(i.e. `as<B, A>` where `B <: A` is not true.) "
                "Such a cast is likely a mistake or typo.");
    }
    return dynamic_cast<copy_const<A, B>*>(x);
}

template <typename B, typename CastType = void, typename A,
        typename = std::enable_if_t<std::is_same_v<CastType, AllowCrossCast> || std::is_base_of_v<A, B>>,
        typename = std::enable_if_t<std::is_class_v<A> && !is_pointer_like<A>>>
auto as(A& x) {
    return as<B, CastType>(&x);
}

template <typename B, typename CastType = void, typename A, typename = std::enable_if_t<is_pointer_like<A>>>
auto as(const A& x) {
    return as<B, CastType>(x.get());
}

template <typename B, typename CastType = void, typename A>
auto as(const std::reference_wrapper<A>& x) {
    return as<B, CastType>(x.get());
}

/**
 * Down-casts and checks the cast has succeeded
 */
template <typename B, typename CastType = void, typename A>
auto& asAssert(A&& a) {
    auto* cast = as<B, CastType>(std::forward<A>(a));
    assert(cast && "Invalid cast");
    return *cast;
}

template <typename B, typename CastType = void, typename A>
Own<B> UNSAFE_cast(Own<A> x) {
    if constexpr (std::is_assignable_v<Own<B>, Own<A>>) {
        return x;
    } else {
        if (!x) return {};

        auto y = Own<B>(as<B, CastType>(x));
        assert(y && "incorrect typed return");
        x.release();  // release after assert so dbgr can report `x` if it fails
        return y;
    }
}

/**
 * Checks if the object of type Source can be casted to type Destination.
 */
template <typename B, typename CastType = void, typename A>
// [[deprecated("Use `as` and implicit boolean conversion instead.")]]
bool isA(A&& src) {
    return as<B, CastType>(std::forward<A>(src));
}

}  // namespace souffle
