From 97d2da581a2a7ff535f5b837775a74496a12ff35 Mon Sep 17 00:00:00 2001
From: tyranron <tyranron@gmail.com>
Date: Wed, 1 Jun 2022 17:31:02 +0200
Subject: [PATCH] Impl codegen for scalars (`resolve::ToInputValue` trait),
 vol.5

---
 juniper/src/ast.rs                        | 35 ++++++---
 juniper/src/macros/reflect.rs             |  4 +-
 juniper/src/resolve/mod.rs                |  4 +
 juniper/src/types/arc.rs                  | 17 ++++-
 juniper/src/types/array.rs                |  9 +++
 juniper/src/types/box.rs                  | 17 ++++-
 juniper/src/types/containers.rs           |  6 +-
 juniper/src/types/nullable.rs             | 12 +++
 juniper/src/types/option.rs               | 12 +++
 juniper/src/types/rc.rs                   | 17 ++++-
 juniper/src/types/ref.rs                  | 17 ++++-
 juniper/src/types/ref_mut.rs              |  9 +++
 juniper/src/types/slice.rs                |  9 +++
 juniper/src/types/str.rs                  | 17 ++++-
 juniper/src/types/vec.rs                  |  9 +++
 juniper/src/value/mod.rs                  | 22 ++++++
 juniper_codegen/src/graphql_scalar/mod.rs | 89 ++++++++++++++++++++++-
 17 files changed, 265 insertions(+), 40 deletions(-)

diff --git a/juniper/src/ast.rs b/juniper/src/ast.rs
index ea2c0fec..b535f921 100644
--- a/juniper/src/ast.rs
+++ b/juniper/src/ast.rs
@@ -256,13 +256,17 @@ impl<S> InputValue<S> {
         Self::Variable(v.as_ref().to_owned())
     }
 
-    /// Construct a [`Spanning::unlocated`] list.
+    /// Constructs a [`Spanning::unlocated`] [`InputValue::List`].
     ///
-    /// Convenience function to make each [`InputValue`] in the input vector
-    /// not contain any location information. Can be used from [`ToInputValue`]
-    /// implementations, where no source code position information is available.
-    pub fn list(l: Vec<Self>) -> Self {
-        Self::List(l.into_iter().map(Spanning::unlocated).collect())
+    /// Convenience function to make each [`InputValue`] in the input `list` to
+    /// not contain any location information.
+    ///
+    /// Intended for [`resolve::ToInputValue`] implementations, where no source
+    /// code position information is available.
+    ///
+    /// [`resolve::ToInputValue`]: juniper::resolve::ToInputValue
+    pub fn list(list: impl IntoIterator<Item = Self>) -> Self {
+        Self::List(list.into_iter().map(Spanning::unlocated).collect())
     }
 
     /// Construct a located list.
@@ -270,16 +274,25 @@ impl<S> InputValue<S> {
         Self::List(l)
     }
 
-    /// Construct aa [`Spanning::unlocated`] object.
+    /// Construct a [`Spanning::unlocated`] [`InputValue::Onject`].
     ///
-    /// Similarly to [`InputValue::list`] it makes each key and value in the
-    /// given hash map not contain any location information.
-    pub fn object<K>(o: IndexMap<K, Self>) -> Self
+    /// Similarly to [`InputValue::list()`] it makes each key and value in the
+    /// given `obj`ect to not contain any location information.
+    ///
+    /// Intended for [`resolve::ToInputValue`] implementations, where no source
+    /// code position information is available.
+    ///
+    /// [`resolve::ToInputValue`]: juniper::resolve::ToInputValue
+    // TODO: Use `impl IntoIterator<Item = (K, Self)>` argument once feature
+    //       `explicit_generic_args_with_impl_trait` hits stable:
+    //       https://github.com/rust-lang/rust/issues/83701
+    pub fn object<K, O>(obj: O) -> Self
     where
         K: AsRef<str> + Eq + Hash,
+        O: IntoIterator<Item = (K, Self)>,
     {
         Self::Object(
-            o.into_iter()
+            obj.into_iter()
                 .map(|(k, v)| {
                     (
                         Spanning::unlocated(k.as_ref().to_owned()),
diff --git a/juniper/src/macros/reflect.rs b/juniper/src/macros/reflect.rs
index f40a8c7d..b967d958 100644
--- a/juniper/src/macros/reflect.rs
+++ b/juniper/src/macros/reflect.rs
@@ -2,9 +2,7 @@
 
 use futures::future::BoxFuture;
 
-use crate::{
-    Arguments as FieldArguments, ExecutionResult, Executor, GraphQLValue, Nullable, ScalarValue,
-};
+use crate::{Arguments as FieldArguments, ExecutionResult, Executor, GraphQLValue, ScalarValue};
 
 /// Alias for a [GraphQL object][1], [scalar][2] or [interface][3] type's name
 /// in a GraphQL schema.
diff --git a/juniper/src/resolve/mod.rs b/juniper/src/resolve/mod.rs
index 39c693e3..87564555 100644
--- a/juniper/src/resolve/mod.rs
+++ b/juniper/src/resolve/mod.rs
@@ -101,3 +101,7 @@ pub trait InputValue<'input, S: 'input = DefaultScalarValue>: Sized {
 pub trait InputValueOwned<S = DefaultScalarValue>: for<'i> InputValue<'i, S> {}
 
 impl<T, S> InputValueOwned<S> for T where T: for<'i> InputValue<'i, S> {}
+
+pub trait ToInputValue<S> {
+    fn to_input_value(&self) -> graphql::InputValue<S>;
+}
diff --git a/juniper/src/types/arc.rs b/juniper/src/types/arc.rs
index 33804c3f..cb5a44ee 100644
--- a/juniper/src/types/arc.rs
+++ b/juniper/src/types/arc.rs
@@ -143,12 +143,12 @@ where
     }
 }
 
-impl<T, S> resolve::ScalarToken<S> for Arc<T>
+impl<T, S> resolve::ToInputValue<S> for Arc<T>
 where
-    T: resolve::ScalarToken<S> + ?Sized,
+    T: resolve::ToInputValue<S> + ?Sized,
 {
-    fn parse_scalar_token(token: ScalarToken<'_>) -> Result<S, ParseError<'_>> {
-        T::parse_scalar_token(token)
+    fn to_input_value(&self) -> graphql::InputValue<S> {
+        (**self).to_input_value()
     }
 }
 
@@ -194,6 +194,15 @@ where
     }
 }
 
+impl<T, S> resolve::ScalarToken<S> for Arc<T>
+where
+    T: resolve::ScalarToken<S> + ?Sized,
+{
+    fn parse_scalar_token(token: ScalarToken<'_>) -> Result<S, ParseError<'_>> {
+        T::parse_scalar_token(token)
+    }
+}
+
 impl<T, S> graphql::InputType<S> for Arc<T>
 where
     T: graphql::InputType<S> + ?Sized,
diff --git a/juniper/src/types/array.rs b/juniper/src/types/array.rs
index 4cd5856b..0f0197fa 100644
--- a/juniper/src/types/array.rs
+++ b/juniper/src/types/array.rs
@@ -64,6 +64,15 @@ where
     }
 }
 
+impl<T, S, const N: usize> resolve::ToInputValue<S> for [T; N]
+where
+    T: resolve::ToInputValue<S>,
+{
+    fn to_input_value(&self) -> graphql::InputValue<S> {
+        graphql::InputValue::list(self.iter().map(T::to_input_value))
+    }
+}
+
 impl<T, S, const N: usize> graphql::InputType<S> for [T; N]
 where
     T: graphql::InputType<S>,
diff --git a/juniper/src/types/box.rs b/juniper/src/types/box.rs
index 1237ed6e..be725c5d 100644
--- a/juniper/src/types/box.rs
+++ b/juniper/src/types/box.rs
@@ -141,12 +141,12 @@ where
     }
 }
 
-impl<T, S> resolve::ScalarToken<S> for Box<T>
+impl<T, S> resolve::ToInputValue<S> for Box<T>
 where
-    T: resolve::ScalarToken<S> + ?Sized,
+    T: resolve::ToInputValue<S> + ?Sized,
 {
-    fn parse_scalar_token(token: ScalarToken<'_>) -> Result<S, ParseError<'_>> {
-        T::parse_scalar_token(token)
+    fn to_input_value(&self) -> graphql::InputValue<S> {
+        (**self).to_input_value()
     }
 }
 
@@ -192,6 +192,15 @@ where
     }
 }
 
+impl<T, S> resolve::ScalarToken<S> for Box<T>
+where
+    T: resolve::ScalarToken<S> + ?Sized,
+{
+    fn parse_scalar_token(token: ScalarToken<'_>) -> Result<S, ParseError<'_>> {
+        T::parse_scalar_token(token)
+    }
+}
+
 impl<T, S> graphql::InputType<S> for Box<T>
 where
     T: graphql::InputType<S> + ?Sized,
diff --git a/juniper/src/types/containers.rs b/juniper/src/types/containers.rs
index ab5383a4..bda6d4db 100644
--- a/juniper/src/types/containers.rs
+++ b/juniper/src/types/containers.rs
@@ -183,7 +183,7 @@ where
     S: ScalarValue,
 {
     fn to_input_value(&self) -> InputValue<S> {
-        InputValue::list(self.iter().map(T::to_input_value).collect())
+        InputValue::list(self.iter().map(T::to_input_value))
     }
 }
 
@@ -283,7 +283,7 @@ where
     S: ScalarValue,
 {
     fn to_input_value(&self) -> InputValue<S> {
-        InputValue::list(self.iter().map(T::to_input_value).collect())
+        InputValue::list(self.iter().map(T::to_input_value))
     }
 }
 
@@ -481,7 +481,7 @@ where
     S: ScalarValue,
 {
     fn to_input_value(&self) -> InputValue<S> {
-        InputValue::list(self.iter().map(T::to_input_value).collect())
+        InputValue::list(self.iter().map(T::to_input_value))
     }
 }
 
diff --git a/juniper/src/types/nullable.rs b/juniper/src/types/nullable.rs
index 4113ea81..a8736781 100644
--- a/juniper/src/types/nullable.rs
+++ b/juniper/src/types/nullable.rs
@@ -324,6 +324,18 @@ where
     }
 }
 
+impl<T, S> resolve::ToInputValue<S> for Nullable<T>
+where
+    T: resolve::ToInputValue<S>,
+{
+    fn to_input_value(&self) -> graphql::InputValue<S> {
+        match self {
+            Self::Some(v) => v.to_input_value(),
+            Self::ExplicitNull | Self::ImplicitNull => graphql::InputValue::Null,
+        }
+    }
+}
+
 impl<'inp, T, S: 'inp> resolve::InputValue<'inp, S> for Nullable<T>
 where
     T: resolve::InputValue<'inp, S>,
diff --git a/juniper/src/types/option.rs b/juniper/src/types/option.rs
index 75320f82..4b475182 100644
--- a/juniper/src/types/option.rs
+++ b/juniper/src/types/option.rs
@@ -61,6 +61,18 @@ where
     }
 }
 
+impl<T, S> resolve::ToInputValue<S> for Option<T>
+where
+    T: resolve::ToInputValue<S>,
+{
+    fn to_input_value(&self) -> graphql::InputValue<S> {
+        match self {
+            Some(v) => v.to_input_value(),
+            None => graphql::InputValue::Null,
+        }
+    }
+}
+
 impl<'inp, T, S: 'inp> resolve::InputValue<'inp, S> for Option<T>
 where
     T: resolve::InputValue<'inp, S>,
diff --git a/juniper/src/types/rc.rs b/juniper/src/types/rc.rs
index 515290b1..76d148d7 100644
--- a/juniper/src/types/rc.rs
+++ b/juniper/src/types/rc.rs
@@ -143,12 +143,12 @@ where
     }
 }
 
-impl<T, S> resolve::ScalarToken<S> for Rc<T>
+impl<T, S> resolve::ToInputValue<S> for Rc<T>
 where
-    T: resolve::ScalarToken<S> + ?Sized,
+    T: resolve::ToInputValue<S> + ?Sized,
 {
-    fn parse_scalar_token(token: ScalarToken<'_>) -> Result<S, ParseError<'_>> {
-        T::parse_scalar_token(token)
+    fn to_input_value(&self) -> graphql::InputValue<S> {
+        (**self).to_input_value()
     }
 }
 
@@ -194,6 +194,15 @@ where
     }
 }
 
+impl<T, S> resolve::ScalarToken<S> for Rc<T>
+where
+    T: resolve::ScalarToken<S> + ?Sized,
+{
+    fn parse_scalar_token(token: ScalarToken<'_>) -> Result<S, ParseError<'_>> {
+        T::parse_scalar_token(token)
+    }
+}
+
 impl<T, S> graphql::InputType<S> for Rc<T>
 where
     T: graphql::InputType<S> + ?Sized,
diff --git a/juniper/src/types/ref.rs b/juniper/src/types/ref.rs
index 646b4566..55ed18be 100644
--- a/juniper/src/types/ref.rs
+++ b/juniper/src/types/ref.rs
@@ -143,12 +143,12 @@ where
     }
 }
 
-impl<'me, T, S> resolve::ScalarToken<S> for &'me T
+impl<'me, T, S> resolve::ToInputValue<S> for &'me T
 where
-    T: resolve::ScalarToken<S> + ?Sized,
+    T: resolve::ToInputValue<S> + ?Sized,
 {
-    fn parse_scalar_token(token: ScalarToken<'_>) -> Result<S, ParseError<'_>> {
-        T::parse_scalar_token(token)
+    fn to_input_value(&self) -> graphql::InputValue<S> {
+        (**self).to_input_value()
     }
 }
 
@@ -180,6 +180,15 @@ pub trait TryFromInputValue<S = DefaultScalarValue> {
     }
 }
 
+impl<'me, T, S> resolve::ScalarToken<S> for &'me T
+where
+    T: resolve::ScalarToken<S> + ?Sized,
+{
+    fn parse_scalar_token(token: ScalarToken<'_>) -> Result<S, ParseError<'_>> {
+        T::parse_scalar_token(token)
+    }
+}
+
 impl<'me, T, S> graphql::InputType<S> for &'me T
 where
     T: graphql::InputType<S> + ?Sized,
diff --git a/juniper/src/types/ref_mut.rs b/juniper/src/types/ref_mut.rs
index 36a576d7..607b4ba4 100644
--- a/juniper/src/types/ref_mut.rs
+++ b/juniper/src/types/ref_mut.rs
@@ -142,6 +142,15 @@ where
     }
 }
 
+impl<'me, T, S> resolve::ToInputValue<S> for &'me mut T
+where
+    T: resolve::ToInputValue<S> + ?Sized,
+{
+    fn to_input_value(&self) -> graphql::InputValue<S> {
+        (**self).to_input_value()
+    }
+}
+
 impl<'me, T, S> resolve::ScalarToken<S> for &'me mut T
 where
     T: resolve::ScalarToken<S> + ?Sized,
diff --git a/juniper/src/types/slice.rs b/juniper/src/types/slice.rs
index 5a5b6b52..7dcdfe35 100644
--- a/juniper/src/types/slice.rs
+++ b/juniper/src/types/slice.rs
@@ -62,6 +62,15 @@ where
     }
 }
 
+impl<T, S> resolve::ToInputValue<S> for [T]
+where
+    T: resolve::ToInputValue<S>,
+{
+    fn to_input_value(&self) -> graphql::InputValue<S> {
+        graphql::InputValue::list(self.iter().map(T::to_input_value))
+    }
+}
+
 impl<T, S> graphql::InputType<S> for [T]
 where
     T: graphql::InputType<S>,
diff --git a/juniper/src/types/str.rs b/juniper/src/types/str.rs
index b8f26985..65ebcf61 100644
--- a/juniper/src/types/str.rs
+++ b/juniper/src/types/str.rs
@@ -66,12 +66,12 @@ where
     }
 }
 
-impl<S> resolve::ScalarToken<S> for str
+impl<S> resolve::ToInputValue<S> for str
 where
-    String: resolve::ScalarToken<S>,
+    S: From<String>,
 {
-    fn parse_scalar_token(token: ScalarToken<'_>) -> Result<S, ParseError<'_>> {
-        <String as resolve::ScalarToken<S>>::parse_scalar_token(token)
+    fn to_input_value(&self) -> graphql::InputValue<S> {
+        graphql::InputValue::scalar(self.to_owned())
     }
 }
 
@@ -108,6 +108,15 @@ impl<'inp, S: ScalarValue> resolve::InputValueAsRc<'inp, S> for str {
     }
 }
 
+impl<S> resolve::ScalarToken<S> for str
+where
+    String: resolve::ScalarToken<S>,
+{
+    fn parse_scalar_token(token: ScalarToken<'_>) -> Result<S, ParseError<'_>> {
+        <String as resolve::ScalarToken<S>>::parse_scalar_token(token)
+    }
+}
+
 impl<S> graphql::InputType<S> for str {
     fn assert_input_type() {}
 }
diff --git a/juniper/src/types/vec.rs b/juniper/src/types/vec.rs
index 8a96f5ad..bf173512 100644
--- a/juniper/src/types/vec.rs
+++ b/juniper/src/types/vec.rs
@@ -60,6 +60,15 @@ where
     }
 }
 
+impl<T, S> resolve::ToInputValue<S> for Vec<T>
+where
+    T: resolve::ToInputValue<S>,
+{
+    fn to_input_value(&self) -> graphql::InputValue<S> {
+        graphql::InputValue::list(self.iter().map(T::to_input_value))
+    }
+}
+
 impl<T, S> graphql::InputType<S> for Vec<T>
 where
     T: graphql::InputType<S>,
diff --git a/juniper/src/value/mod.rs b/juniper/src/value/mod.rs
index e3457c8e..9d8ca2b3 100644
--- a/juniper/src/value/mod.rs
+++ b/juniper/src/value/mod.rs
@@ -6,6 +6,7 @@ use std::{any::TypeId, borrow::Cow, fmt, mem};
 use crate::{
     ast::{InputValue, ToInputValue},
     parser::Spanning,
+    resolve,
 };
 
 pub use self::{
@@ -190,6 +191,27 @@ impl<S: Clone> ToInputValue<S> for Value<S> {
     }
 }
 
+impl<S: Clone> resolve::ToInputValue<S> for Value<S> {
+    fn to_input_value(&self) -> InputValue<S> {
+        // TODO: Simplify recursive calls syntax, once old `ToInputValue` trait
+        //       is removed.
+        match self {
+            Self::Null => InputValue::Null,
+            Self::Scalar(s) => InputValue::Scalar(s.clone()),
+            Self::List(l) => InputValue::list(
+                l.iter()
+                    .map(<Self as resolve::ToInputValue<S>>::to_input_value),
+            ),
+            Self::Object(o) => InputValue::object(o.iter().map(|(k, v)| {
+                (
+                    k.clone(),
+                    <Self as resolve::ToInputValue<S>>::to_input_value(v),
+                )
+            })),
+        }
+    }
+}
+
 impl<S: ScalarValue> fmt::Display for Value<S> {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
         match self {
diff --git a/juniper_codegen/src/graphql_scalar/mod.rs b/juniper_codegen/src/graphql_scalar/mod.rs
index 17508bc0..daecafe8 100644
--- a/juniper_codegen/src/graphql_scalar/mod.rs
+++ b/juniper_codegen/src/graphql_scalar/mod.rs
@@ -334,6 +334,7 @@ impl ToTokens for Definition {
         self.impl_resolve_type_name().to_tokens(into);
         self.impl_resolve_value().to_tokens(into);
         self.impl_resolve_value_async().to_tokens(into);
+        self.impl_resolve_to_input_value().to_tokens(into);
         self.impl_resolve_input_value().to_tokens(into);
         self.impl_resolve_scalar_token().to_tokens(into);
         self.impl_input_and_output_type().to_tokens(into);
@@ -680,7 +681,7 @@ impl Definition {
     fn impl_to_input_value_tokens(&self) -> TokenStream {
         let scalar = &self.scalar;
 
-        let to_input_value = self.methods.expand_to_input_value(scalar);
+        let to_input_value = self.methods.expand_old_to_input_value(scalar);
 
         let (ty, generics) = self.impl_self_and_generics(false);
         let (impl_gens, _, where_clause) = generics.split_for_impl();
@@ -697,6 +698,34 @@ impl Definition {
         }
     }
 
+    /// Returns generated code implementing [`resolve::ToInputValue`] trait for
+    /// this [GraphQL scalar][0].
+    ///
+    /// [`resolve::ToInputValue`]: juniper::resolve::ToInputValue
+    /// [0]: https://spec.graphql.org/October2021#sec-Scalars
+    fn impl_resolve_to_input_value(&self) -> TokenStream {
+        let (ty, generics) = self.ty_and_generics();
+        let (sv, mut generics) = self.mix_scalar_value(generics);
+        generics
+            .make_where_clause()
+            .predicates
+            .push(self.methods.bound_to_input_value(sv));
+        let (impl_gens, _, where_clause) = generics.split_for_impl();
+
+        let body = self.methods.expand_to_input_value(sv);
+
+        quote! {
+            #[automatically_derived]
+            impl#impl_gens ::juniper::resolve::ToInputValue<#sv> for #ty
+                #where_clause
+            {
+                fn to_input_value(&self) -> ::juniper::graphql::InputValue<#sv> {
+                    #body
+                }
+            }
+        }
+    }
+
     /// Returns generated code implementing [`FromInputValue`] trait for this
     /// [GraphQL scalar][1].
     ///
@@ -1084,7 +1113,7 @@ impl Methods {
         }
     }
 
-    /// Expands [`resolve::Value::resolve_value()`][0] method.
+    /// Expands body of [`resolve::Value::resolve_value()`][0] method.
     ///
     /// [0]: juniper::resolve::Value::resolve_value
     fn expand_resolve_value(
@@ -1152,7 +1181,7 @@ impl Methods {
     /// Expands [`ToInputValue::to_input_value`] method.
     ///
     /// [`ToInputValue::to_input_value`]: juniper::ToInputValue::to_input_value
-    fn expand_to_input_value(&self, scalar: &scalar::Type) -> TokenStream {
+    fn expand_old_to_input_value(&self, scalar: &scalar::Type) -> TokenStream {
         match self {
             Self::Custom { to_output, .. }
             | Self::Delegated {
@@ -1172,6 +1201,60 @@ impl Methods {
         }
     }
 
+    /// Expands body of [`resolve::ToInputValue::to_input_value()`][0] method.
+    ///
+    /// [0]: juniper::resolve::ToInputValue::to_input_value
+    fn expand_to_input_value(&self, sv: &scalar::Type) -> TokenStream {
+        match self {
+            Self::Custom { to_output, .. }
+            | Self::Delegated {
+                to_output: Some(to_output),
+                ..
+            } => {
+                quote! {
+                    let v = #to_output(self);
+                    ::juniper::resolve::ToInputValue::<#sv>::to_input_value(&v)
+                }
+            }
+
+            Self::Delegated { field, .. } => {
+                let field_ty = field.ty();
+
+                quote! {
+                    <#field_ty as ::juniper::resolve::ToInputValue<#sv>>
+                        ::to_input_value(&self.#field)
+                }
+            }
+        }
+    }
+
+    /// Generates additional trait bounds for [`resolve::ToInputValue`]
+    /// implementation allowing to execute
+    /// [`resolve::ToInputValue::to_input_value()`][0] method.
+    ///
+    /// [`resolve::ToInputValue`]: juniper::resolve::ToInputValue
+    /// [0]: juniper::resolve::ToInputValue::to_input_value
+    fn bound_to_input_value(&self, sv: &scalar::Type) -> syn::WherePredicate {
+        match self {
+            Self::Custom { .. }
+            | Self::Delegated {
+                to_output: Some(_), ..
+            } => {
+                parse_quote! {
+                    #sv: ::juniper::ScalarValue
+                }
+            }
+
+            Self::Delegated { field, .. } => {
+                let field_ty = field.ty();
+
+                parse_quote! {
+                    #field_ty: ::juniper::resolve::ToInputValue<#sv>>
+                }
+            }
+        }
+    }
+
     /// Expands [`FromInputValue::from_input_value`][1] method.
     ///
     /// [1]: juniper::FromInputValue::from_input_value