/*
 * SPDX-FileCopyrightText: 2023 Inria
 * SPDX-FileCopyrightText: 2023 Sebastiano Vigna
 *
 * SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later
 */

//! Traits to mark types as zero-copy or deep-copy.

use crate::{
    deser::DeserInner,
    prelude::PadTo,
    ser::SerInner,
    traits::{AlignHash, TypeHash},
};
use sealed::sealed;

/// Internal trait used to select whether a type is zero-copy
/// or deep-copy.
///
/// It has only two implementations, [`Zero`] and [`Deep`].
///
/// In the first case, the type can be serialized from memory and deserialized
/// to memory as a sequence of bytes; in the second case, one has to
/// (de)serialize the type field by field.
#[sealed]
pub trait CopySelector {
    /// `true` for [`Zero`], `false` for [`Deep`].
    const IS_ZERO_COPY: bool;
}
/// An implementation of [`CopySelector`] specifying that a type is zero-copy.
pub struct Zero {}

#[sealed]
impl CopySelector for Zero {
    const IS_ZERO_COPY: bool = true;
}

/// An implementation of [`CopySelector`] specifying that a type is deep-copy.
pub struct Deep {}

#[sealed]
impl CopySelector for Deep {
    const IS_ZERO_COPY: bool = false;
}

/// Marker trait specifying whether a type it is zero-copy or deep-copy.
///
/// The trait comes in two flavors: `CopyType<Copy=Zero>` and
/// `CopyType<Copy=Deep>`. To each of these flavors corresponds two dependent
/// traits, [`ZeroCopy`] and [`DeepCopy`], which are automatically implemented.
///
/// You should not implement this trait manually, but rather use the provided
/// [derive macro].
///
/// We use this trait to implement a different behavior for [`ZeroCopy`] and
/// [`DeepCopy`] types, in particular on arrays, vectors, and boxed slices,
/// [working around the bug that prevents the compiler from understanding that
/// implementations for the two flavors of `CopySelector` are mutually
/// exclusive].
///
/// For an array of elements of type `T` to be zero-copy serializable and
/// deserializable, `T` must implement `CopyType<Copy=Zero>`. The conditions for
/// this marker trait are that `T` is a [copy type], that it has a fixed memory
/// layout, and that it does not contain any reference (in particular, that it
/// has `'static` lifetime). If this happens vectors of `T` or boxed slices of
/// `T` can be ε-copy deserialized using a reference to a slice of `T`.
///
/// You can make zero-copy your own types, but you must ensure that they do not
/// contain references and that they have a fixed memory layout; for structures,
/// this requires `repr(C)`. ε-serde will track these conditions at compile time
/// and check them at runtime: in case of failure, serialization will panic.
///
/// Since we cannot use negative trait bounds, every type that is used as a
/// parameter of an array, vector or boxed slice must implement either
/// `CopyType<Copy=Zero>` or `CopyType<Copy=Deep>`. In the latter case,
/// slices will be deserialized element by element, and the result will be a
/// fully deserialized vector or boxed slice. If you do not implement either of
/// these traits, the type will not be serializable inside vectors or boxed
/// slices but error messages will be very unhelpful due to the contrived way we
/// have to implement mutually exclusive types.
///
/// If you use the provided derive macros all this logic will be hidden from
/// you. You'll just have to add `#[epserde(zero_copy)]` to your structures (if you want
/// them to be zero-copy) and ε-serde will do the rest.
///
/// # Safety
///
/// The trait is unsafe because the user must guarantee that zero-copy types do
/// not contain references, and this cannot be checked by the compiler.
///
/// [derive macro]: epserde_derive::Epserde
/// [working around the bug that prevents the compiler from understanding that implementations for the two flavors of `CopySelector` are mutually exclusive]: https://github.com/rust-lang/rfcs/pull/1672#issuecomment-1405377983
/// [copy type]: Copy
pub unsafe trait CopyType: Sized {
    /// [`Zero`] for zero-copy types, [`Deep`] for deep-copy types.
    type Copy: CopySelector;
}

/// Marker trait for zero-copy types. You cannot implement this trait directly:
/// you rather implement the traits on which it depends, and then it is
/// automatically implemented by a blanket implementation.
///
/// Note that we require `SerInner<SerType = Self>` but just `DeserInner` and
/// not, say, `DeserInner<DeserType<'_> = &Self>`, which would be natural for
/// user-defined zero-copy types, because primitive types and standard zero-copy
/// zero-sized types such as `()` and `PhantomData` have deserialization type
/// `Self`.
pub trait ZeroCopy:
    CopyType<Copy = Zero>
    + Copy
    + DeserInner
    + SerInner<SerType = Self>
    + TypeHash
    + AlignHash
    + PadTo
    + 'static
{
}

impl<
    T: CopyType<Copy = Zero>
        + Copy
        + SerInner<SerType = Self>
        + DeserInner
        + TypeHash
        + AlignHash
        + PadTo
        + 'static,
> ZeroCopy for T
{
}

/// Marker trait for deep-copy types. You cannot implement this trait directly:
/// you rather implement the traits on which it depends, and then it is automatically
/// implemented by a blanket implementation.
///
/// Note that we require `SerInner` but not `DeserInner` to make the
/// serialization of undeserializable types possible (for example, types
/// containing a `SerIter` field).
pub trait DeepCopy: CopyType<Copy = Deep> + SerInner<SerType: TypeHash + AlignHash> {}

impl<T: CopyType<Copy = Deep> + SerInner<SerType: TypeHash + AlignHash>> DeepCopy for T {}
