Impl new machinery for array

This commit is contained in:
tyranron 2022-06-21 15:25:06 +02:00
parent d648181b26
commit 17af7c5ac5
No known key found for this signature in database
GPG key ID: 762E144FB230A4F0
2 changed files with 231 additions and 48 deletions

View file

@ -1,118 +1,301 @@
//! GraphQL implementation for [array].
//!
//! [array]: primitive@std::array
//! [array]: prim@array
use std::{
mem::{self, MaybeUninit},
ptr,
};
use crate::{
behavior,
executor::{ExecutionResult, Executor, Registry},
graphql, reflect, resolve,
schema::meta::MetaType,
BoxFuture, Selection,
BoxFuture, FieldError, IntoFieldError, Selection,
};
use super::iter;
/*
impl<T, Info, S, const N: usize> resolve::Type<Info, S> for [T; N]
impl<T, TI, SV, BH, const N: usize> resolve::Type<TI, SV, BH> for [T; N]
where
T: resolve::Type<Info, S>,
Info: ?Sized,
T: resolve::Type<TI, SV, BH>,
TI: ?Sized,
BH: ?Sized,
{
fn meta<'r>(registry: &mut Registry<'r, S>, info: &Info) -> MetaType<'r, S>
fn meta<'r, 'ti: 'r>(registry: &mut Registry<'r, SV>, type_info: &'ti TI) -> MetaType<'r, SV>
where
S: 'r,
SV: 'r,
{
registry
.build_list_type_new::<T, _>(info, Some(N))
.into_meta()
registry.wrap_list::<behavior::Coerce<T, BH>, _>(type_info, None)
}
}
impl<T, Info, Ctx, S, const N: usize> resolve::Value<Info, Ctx, S> for [T; N]
impl<T, TI, CX, SV, BH, const N: usize> resolve::Value<TI, CX, SV, BH> for [T; N]
where
T: resolve::Value<Info, Ctx, S>,
Info: ?Sized,
Ctx: ?Sized,
T: resolve::Value<TI, CX, SV, BH>,
TI: ?Sized,
CX: ?Sized,
BH: ?Sized,
{
fn resolve_value(
&self,
selection_set: Option<&[Selection<'_, S>]>,
info: &Info,
executor: &Executor<Ctx, S>,
) -> ExecutionResult<S> {
iter::resolve_list(self.iter(), selection_set, info, executor)
selection_set: Option<&[Selection<'_, SV>]>,
type_info: &TI,
executor: &Executor<CX, SV>,
) -> ExecutionResult<SV> {
iter::resolve_list(self.iter(), selection_set, type_info, executor)
}
}
impl<T, Info, Ctx, S, const N: usize> resolve::ValueAsync<Info, Ctx, S> for [T; N]
impl<T, TI, CX, SV, BH, const N: usize> resolve::ValueAsync<TI, CX, SV, BH> for [T; N]
where
T: resolve::ValueAsync<Info, Ctx, S> + Sync,
Info: Sync + ?Sized,
Ctx: Sync + ?Sized,
S: Send + Sync,
T: resolve::ValueAsync<TI, CX, SV, BH> + Sync,
TI: Sync + ?Sized,
CX: Sync + ?Sized,
SV: Send + Sync,
BH: ?Sized + 'static, // TODO: Lift `'static` bound if possible.
{
fn resolve_value_async<'r>(
&'r self,
selection_set: Option<&'r [Selection<'_, S>]>,
info: &'r Info,
executor: &'r Executor<Ctx, S>,
) -> BoxFuture<'r, ExecutionResult<S>> {
selection_set: Option<&'r [Selection<'_, SV>]>,
type_info: &'r TI,
executor: &'r Executor<CX, SV>,
) -> BoxFuture<'r, ExecutionResult<SV>> {
Box::pin(iter::resolve_list_async(
self.iter(),
selection_set,
info,
type_info,
executor,
))
}
}
impl<T, S, const N: usize> resolve::ToInputValue<S> for [T; N]
impl<T, SV, BH, const N: usize> resolve::ToInputValue<SV, BH> for [T; N]
where
T: resolve::ToInputValue<S>,
T: resolve::ToInputValue<SV, BH>,
BH: ?Sized,
{
fn to_input_value(&self) -> graphql::InputValue<S> {
fn to_input_value(&self) -> graphql::InputValue<SV> {
graphql::InputValue::list(self.iter().map(T::to_input_value))
}
}
/*
impl<'i, T, Info, S, const N: usize> graphql::InputType<'i, Info, S> for [T; N]
impl<'i, T, SV, BH, const N: usize> resolve::InputValue<'i, SV, BH> for [T; N]
where
T: graphql::InputType<'i, Info, S>,
Info: ?Sized,
T: resolve::InputValue<'i, SV, BH>,
SV: 'i,
BH: ?Sized,
{
type Error = TryFromInputValueError<T::Error>;
fn try_from_input_value(v: &'i graphql::InputValue<SV>) -> Result<Self, Self::Error> {
struct PartiallyInitializedArray<T, const N: usize> {
arr: [MaybeUninit<T>; N],
init_len: usize,
no_drop: bool,
}
impl<T, const N: usize> Drop for PartiallyInitializedArray<T, N> {
fn drop(&mut self) {
if self.no_drop {
return;
}
// Dropping a `MaybeUninit` does nothing, thus we need to drop
// the initialized elements manually, otherwise we may introduce
// a memory/resource leak if `T: Drop`.
for elem in &mut self.arr[0..self.init_len] {
// SAFETY: This is safe, because `self.init_len` represents
// the number of the initialized elements exactly.
unsafe {
ptr::drop_in_place(elem.as_mut_ptr());
}
}
}
}
match v {
graphql::InputValue::List(ls) => {
if ls.len() != N {
return Err(TryFromInputValueError::WrongCount {
actual: ls.len(),
expected: N,
});
}
if N == 0 {
// TODO: Use `mem::transmute` instead of
// `mem::transmute_copy` below, once it's allowed
// for const generics:
// https://github.com/rust-lang/rust/issues/61956
// SAFETY: `mem::transmute_copy` is safe here, because we
// check `N` to be `0`. It's no-op, actually.
return Ok(unsafe { mem::transmute_copy::<[T; 0], Self>(&[]) });
}
// SAFETY: The reason we're using a wrapper struct implementing
// `Drop` here is to be panic safe:
// `T: resolve::InputValue` implementation is not
// controlled by us, so calling
// `T::try_from_input_value(&i.item)` below may cause a
// panic when our array is initialized only partially.
// In such situation we need to drop already initialized
// values to avoid possible memory/resource leaks if
// `T: Drop`.
let mut out = PartiallyInitializedArray::<T, N> {
// SAFETY: The `.assume_init()` here is safe, because the
// type we are claiming to have initialized here is
// a bunch of `MaybeUninit`s, which do not require
// any initialization.
arr: unsafe { MaybeUninit::uninit().assume_init() },
init_len: 0,
no_drop: false,
};
let mut items = ls.iter().map(|i| T::try_from_input_value(&i.item));
for elem in &mut out.arr[..] {
if let Some(i) = items
.next()
.transpose()
.map_err(TryFromInputValueError::Item)?
{
*elem = MaybeUninit::new(i);
out.init_len += 1;
}
}
// Do not drop collected `items`, because we're going to return
// them.
out.no_drop = true;
// TODO: Use `mem::transmute` instead of `mem::transmute_copy`
// below, once it's allowed for const generics:
// https://github.com/rust-lang/rust/issues/61956
// SAFETY: `mem::transmute_copy` is safe here, because we have
// exactly `N` initialized `items`.
// Also, despite `mem::transmute_copy` copies the value,
// we won't have a double-free when `T: Drop` here,
// because original array elements are `MaybeUninit`, so
// do nothing on `Drop`.
Ok(unsafe { mem::transmute_copy::<_, Self>(&out.arr) })
}
// See "Input Coercion" on List types:
// https://spec.graphql.org/October2021#sec-Combining-List-and-Non-Null
graphql::InputValue::Null => Err(TryFromInputValueError::IsNull),
other => T::try_from_input_value(other)
.map_err(TryFromInputValueError::Item)
.and_then(|e: T| {
// TODO: Use `mem::transmute` instead of
// `mem::transmute_copy` below, once it's allowed
// for const generics:
// https://github.com/rust-lang/rust/issues/61956
if N == 1 {
// SAFETY: `mem::transmute_copy` is safe here, because
// we check `N` to be `1`. Also, despite
// `mem::transmute_copy` copies the value, we
// won't have a double-free when `T: Drop` here,
// because original `e: T` value is wrapped into
// `mem::ManuallyDrop`, so does nothing on
// `Drop`.
Ok(unsafe { mem::transmute_copy::<_, Self>(&[mem::ManuallyDrop::new(e)]) })
} else {
Err(TryFromInputValueError::WrongCount {
actual: 1,
expected: N,
})
}
}),
}
}
}
impl<'i, T, TI, SV, BH, const N: usize> graphql::InputType<'i, TI, SV, BH> for [T; N]
where
T: graphql::InputType<'i, TI, SV, BH>,
TI: ?Sized,
SV: 'i,
BH: ?Sized,
{
fn assert_input_type() {
T::assert_input_type()
}
}
*/
impl<T, S, const N: usize> graphql::OutputType<S> for [T; N]
impl<T, TI, CX, SV, BH, const N: usize> graphql::OutputType<TI, CX, SV, BH> for [T; N]
where
T: graphql::OutputType<S>,
T: graphql::OutputType<TI, CX, SV, BH>,
TI: ?Sized,
CX: ?Sized,
BH: ?Sized,
Self: resolve::ValueAsync<TI, CX, SV, BH>,
{
fn assert_output_type() {
T::assert_output_type()
}
}
impl<T, S, const N: usize> reflect::BaseType<S> for [T; N]
impl<T, BH, const N: usize> reflect::BaseType<BH> for [T; N]
where
T: reflect::BaseType<S>,
T: reflect::BaseType<BH>,
BH: ?Sized,
{
const NAME: reflect::Type = T::NAME;
}
impl<T, S, const N: usize> reflect::BaseSubTypes<S> for [T; N]
impl<T, BH, const N: usize> reflect::BaseSubTypes<BH> for [T; N]
where
T: reflect::BaseSubTypes<S>,
T: reflect::BaseSubTypes<BH>,
BH: ?Sized,
{
const NAMES: reflect::Types = T::NAMES;
}
impl<T, S, const N: usize> reflect::WrappedType<S> for [T; N]
impl<T, BH, const N: usize> reflect::WrappedType<BH> for [T; N]
where
T: reflect::WrappedType<S>,
T: reflect::WrappedType<BH>,
BH: ?Sized,
{
const VALUE: reflect::WrappedValue = reflect::wrap::list(T::VALUE);
}
*/
/// Possible errors of converting a [`graphql::InputValue`] into an exact-size
/// [array](prim@array).
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum TryFromInputValueError<E> {
/// [`graphql::InputValue`] cannot be [`Null`].
///
/// See ["Combining List and Non-Null" section of spec][0].
///
/// [`Null`]: [`InputValue::Null`]
/// [0]: https://spec.graphql.org/October2021#sec-Combining-List-and-Non-Null
IsNull,
/// Wrong count of items.
WrongCount {
/// Actual count of items.
actual: usize,
/// Expected count of items ([array](prim@array) size).
expected: usize,
},
/// Error of converting a [`graphql::InputValue::List`]'s item.
Item(E),
}
impl<E, SV> IntoFieldError<SV> for TryFromInputValueError<E>
where
E: IntoFieldError<SV>,
{
fn into_field_error(self) -> FieldError<SV> {
const ERROR_PREFIX: &str = "Failed to convert into exact-size array";
match self {
Self::IsNull => format!("{}: Value cannot be `null`", ERROR_PREFIX).into(),
Self::WrongCount { actual, expected } => format!(
"{}: wrong elements count: {} instead of {}",
ERROR_PREFIX, actual, expected,
)
.into(),
Self::Item(s) => s.into_field_error(),
}
}
}

View file

@ -1,5 +1,5 @@
mod arc;
mod array;
pub mod array;
mod r#box;
pub mod iter;
mod nullable;