From 23e01956b348cf2820c1366dcda330fb98412ec2 Mon Sep 17 00:00:00 2001
From: tyranron <tyranron@gmail.com>
Date: Thu, 23 Jun 2022 17:42:55 +0200
Subject: [PATCH] Impl `reflect` traits for objects and interfaces [skip ci]

---
 juniper/src/behavior.rs                       |  38 ++++++-
 juniper_codegen/src/graphql_interface/attr.rs |   2 +
 .../src/graphql_interface/derive.rs           |   1 +
 juniper_codegen/src/graphql_interface/mod.rs  |  97 ++++++++++++++++-
 juniper_codegen/src/graphql_object/attr.rs    |   1 +
 juniper_codegen/src/graphql_object/derive.rs  |   1 +
 juniper_codegen/src/graphql_object/mod.rs     | 100 +++++++++++++++++-
 juniper_codegen/src/graphql_scalar/mod.rs     |   8 +-
 8 files changed, 241 insertions(+), 7 deletions(-)

diff --git a/juniper/src/behavior.rs b/juniper/src/behavior.rs
index c98c959f..506cc384 100644
--- a/juniper/src/behavior.rs
+++ b/juniper/src/behavior.rs
@@ -6,7 +6,7 @@ use crate::{
     graphql,
     meta::MetaType,
     parser::{ParseError, ScalarToken},
-    resolve, Registry,
+    reflect, resolve, Registry,
 };
 
 /// Default standard behavior of GraphQL types implementation.
@@ -92,3 +92,39 @@ where
         T::parse_scalar_token(token)
     }
 }
+
+impl<T, B1, B2> reflect::BaseType<B1> for Coerce<T, B2>
+where
+    T: reflect::BaseType<B2> + ?Sized,
+    B1: ?Sized,
+    B2: ?Sized,
+{
+    const NAME: reflect::Type = T::NAME;
+}
+
+impl<T, B1, B2> reflect::BaseSubTypes<B1> for Coerce<T, B2>
+where
+    T: reflect::BaseSubTypes<B2> + ?Sized,
+    B1: ?Sized,
+    B2: ?Sized,
+{
+    const NAMES: reflect::Types = T::NAMES;
+}
+
+impl<T, B1, B2> reflect::WrappedType<B1> for Coerce<T, B2>
+where
+    T: reflect::WrappedType<B2> + ?Sized,
+    B1: ?Sized,
+    B2: ?Sized,
+{
+    const VALUE: reflect::WrappedValue = T::VALUE;
+}
+
+impl<T, B1, B2> reflect::Implements<B1> for Coerce<T, B2>
+where
+    T: reflect::Implements<B2> + ?Sized,
+    B1: ?Sized,
+    B2: ?Sized,
+{
+    const NAMES: reflect::Types = T::NAMES;
+}
diff --git a/juniper_codegen/src/graphql_interface/attr.rs b/juniper_codegen/src/graphql_interface/attr.rs
index 746b7af7..ba7a456d 100644
--- a/juniper_codegen/src/graphql_interface/attr.rs
+++ b/juniper_codegen/src/graphql_interface/attr.rs
@@ -123,6 +123,7 @@ fn expand_on_trait(
         description: attr.description.as_deref().cloned(),
         context,
         scalar,
+        behavior: attr.behavior.map(|bh| bh.into_inner()).unwrap_or_default(),
         fields,
         implemented_for: attr
             .implemented_for
@@ -304,6 +305,7 @@ fn expand_on_derive_input(
         description: attr.description.as_deref().cloned(),
         context,
         scalar,
+        behavior: attr.behavior.map(|bh| bh.into_inner()).unwrap_or_default(),
         fields,
         implemented_for: attr
             .implemented_for
diff --git a/juniper_codegen/src/graphql_interface/derive.rs b/juniper_codegen/src/graphql_interface/derive.rs
index e113bdce..bf60e5cf 100644
--- a/juniper_codegen/src/graphql_interface/derive.rs
+++ b/juniper_codegen/src/graphql_interface/derive.rs
@@ -96,6 +96,7 @@ pub fn expand(input: TokenStream) -> syn::Result<TokenStream> {
         description: attr.description.as_deref().cloned(),
         context,
         scalar,
+        behavior: attr.behavior.map(|bh| bh.into_inner()).unwrap_or_default(),
         fields,
         implemented_for: attr
             .implemented_for
diff --git a/juniper_codegen/src/graphql_interface/mod.rs b/juniper_codegen/src/graphql_interface/mod.rs
index 2114e2cf..fae35157 100644
--- a/juniper_codegen/src/graphql_interface/mod.rs
+++ b/juniper_codegen/src/graphql_interface/mod.rs
@@ -21,7 +21,7 @@ use syn::{
 
 use crate::{
     common::{
-        field, gen,
+        behavior, field, gen,
         parse::{
             attr::{err, OptionExt as _},
             GenericsExt as _, ParseBufferExt as _,
@@ -111,6 +111,17 @@ struct Attr {
     /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces
     scalar: Option<SpanContainer<scalar::AttrValue>>,
 
+    /// Explicitly specified type of the custom [`Behavior`] to parametrize this
+    /// [GraphQL interface][0] implementation with.
+    ///
+    /// If [`None`], then [`behavior::Standard`] will be used for the generated
+    /// code.
+    ///
+    /// [`Behavior`]: juniper::behavior
+    /// [`behavior::Standard`]: juniper::behavior::Standard
+    /// [0]: https://spec.graphql.org/October2021#sec-Interfaces
+    behavior: Option<SpanContainer<behavior::Type>>,
+
     /// Explicitly specified marker indicating that the Rust trait should be
     /// transformed into [`async_trait`].
     ///
@@ -173,6 +184,13 @@ impl Parse for Attr {
                         .replace(SpanContainer::new(ident.span(), Some(scl.span()), scl))
                         .none_or_else(|_| err::dup_arg(&ident))?
                 }
+                "behave" | "behavior" => {
+                    input.parse::<token::Eq>()?;
+                    let bh = input.parse::<behavior::Type>()?;
+                    out.behavior
+                        .replace(SpanContainer::new(ident.span(), Some(bh.span()), bh))
+                        .none_or_else(|_| err::dup_arg(&ident))?
+                }
                 "for" | "implementers" => {
                     input.parse::<token::Eq>()?;
                     for impler in input.parse_maybe_wrapped_and_punctuated::<
@@ -231,6 +249,7 @@ impl Attr {
             description: try_merge_opt!(description: self, another),
             context: try_merge_opt!(context: self, another),
             scalar: try_merge_opt!(scalar: self, another),
+            behavior: try_merge_opt!(behavior: self, another),
             implemented_for: try_merge_hashset!(implemented_for: self, another => span_joined),
             r#enum: try_merge_opt!(r#enum: self, another),
             asyncness: try_merge_opt!(asyncness: self, another),
@@ -309,6 +328,13 @@ struct Definition {
     /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces
     scalar: scalar::Type,
 
+    /// [`Behavior`] parametrization to generate code with for this
+    /// [GraphQL interface][0].
+    ///
+    /// [`Behavior`]: juniper::behavior
+    /// [0]: https://spec.graphql.org/October2021#sec-Interfaces
+    behavior: behavior::Type,
+
     /// Defined [GraphQL fields][2] of this [GraphQL interface][1].
     ///
     /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces
@@ -347,6 +373,8 @@ impl ToTokens for Definition {
         self.impl_field_meta_tokens().to_tokens(into);
         self.impl_field_tokens().to_tokens(into);
         self.impl_async_field_tokens().to_tokens(into);
+        ////////////////////////////////////////////////////////////////////////
+        self.impl_reflect().to_tokens(into);
     }
 }
 
@@ -847,6 +875,59 @@ impl Definition {
         }
     }
 
+    /// Returns generated code implementing [`reflect::BaseType`],
+    /// [`reflect::BaseSubTypes`], [`reflect::WrappedType`] and
+    /// [`reflect::Fields`] traits for this [GraphQL interface][0].
+    ///
+    /// [`reflect::BaseSubTypes`]: juniper::reflect::BaseSubTypes
+    /// [`reflect::BaseType`]: juniper::reflect::BaseType
+    /// [`reflect::Fields`]: juniper::reflect::Fields
+    /// [`reflect::WrappedType`]: juniper::reflect::WrappedType
+    /// [0]: https://spec.graphql.org/October2021#sec-Interfaces
+    fn impl_reflect(&self) -> TokenStream {
+        let bh = &self.behavior;
+        let (ty, generics) = self.ty_and_generics();
+        let (impl_gens, _, where_clause) = generics.split_for_impl();
+
+        let name = &self.name;
+        let implers = &self.implemented_for;
+        let fields = self.fields.iter().map(|f| &f.name);
+
+        quote! {
+            #[automatically_derived]
+            impl#impl_gens ::juniper::reflect::BaseType<#bh> for #ty
+                #where_clause
+            {
+                const NAME: ::juniper::reflect::Type = #name;
+            }
+
+            #[automatically_derived]
+            impl#impl_gens ::juniper::reflect::BaseSubTypes<#bh> for #ty
+                #where_clause
+            {
+                const NAMES: ::juniper::reflect::Types = &[
+                    <Self as ::juniper::reflect::BaseType<#bh>>::NAME,
+                    #( <#implers as ::juniper::reflect::BaseType<#bh>>::NAME ),*
+                ];
+            }
+
+            #[automatically_derived]
+            impl#impl_gens ::juniper::reflect::WrappedType<#bh> for #ty
+                #where_clause
+            {
+                const VALUE: ::juniper::reflect::WrappedValue =
+                    ::juniper::reflect::wrap::SINGULAR;
+            }
+
+            #[automatically_derived]
+            impl#impl_gens ::juniper::reflect::Fields<#bh> for #ty
+                #where_clause
+            {
+                const NAMES: ::juniper::reflect::Names = &[#( #fields ),*];
+            }
+        }
+    }
+
     /// Returns generated code implementing [`FieldMeta`] for each field of this
     /// [GraphQL interface][1].
     ///
@@ -1267,6 +1348,20 @@ impl Definition {
         generics
     }
 
+    /// Returns prepared self [`syn::Type`] and [`syn::Generics`] for a trait
+    /// implementation.
+    fn ty_and_generics(&self) -> (syn::Type, syn::Generics) {
+        let generics = self.generics.clone();
+
+        let ty = {
+            let ident = &self.enum_alias_ident;
+            let (_, ty_gen, _) = generics.split_for_impl();
+            parse_quote! { #ident#ty_gen }
+        };
+
+        (ty, generics)
+    }
+
     /// Indicates whether this enum has non-exhaustive phantom variant to hold
     /// type parameters.
     #[must_use]
diff --git a/juniper_codegen/src/graphql_object/attr.rs b/juniper_codegen/src/graphql_object/attr.rs
index 34a70c21..5461bf84 100644
--- a/juniper_codegen/src/graphql_object/attr.rs
+++ b/juniper_codegen/src/graphql_object/attr.rs
@@ -121,6 +121,7 @@ where
         description: attr.description.map(SpanContainer::into_inner),
         context,
         scalar,
+        behavior: attr.behavior.map(|bh| bh.into_inner()).unwrap_or_default(),
         fields,
         interfaces: attr
             .interfaces
diff --git a/juniper_codegen/src/graphql_object/derive.rs b/juniper_codegen/src/graphql_object/derive.rs
index 7cbe961b..e20f533c 100644
--- a/juniper_codegen/src/graphql_object/derive.rs
+++ b/juniper_codegen/src/graphql_object/derive.rs
@@ -98,6 +98,7 @@ fn expand_struct(ast: syn::DeriveInput) -> syn::Result<Definition<Query>> {
             .map(SpanContainer::into_inner)
             .unwrap_or_else(|| parse_quote! { () }),
         scalar,
+        behavior: attr.behavior.map(|bh| bh.into_inner()).unwrap_or_default(),
         fields,
         interfaces: attr
             .interfaces
diff --git a/juniper_codegen/src/graphql_object/mod.rs b/juniper_codegen/src/graphql_object/mod.rs
index 0f3b05ac..c51cab09 100644
--- a/juniper_codegen/src/graphql_object/mod.rs
+++ b/juniper_codegen/src/graphql_object/mod.rs
@@ -18,7 +18,7 @@ use syn::{
 
 use crate::{
     common::{
-        field, gen,
+        behavior, field, gen,
         parse::{
             attr::{err, OptionExt as _},
             GenericsExt as _, ParseBufferExt as _, TypeExt,
@@ -73,6 +73,17 @@ pub(crate) struct Attr {
     /// [1]: https://spec.graphql.org/June2018/#sec-Objects
     pub(crate) scalar: Option<SpanContainer<scalar::AttrValue>>,
 
+    /// Explicitly specified type of the custom [`Behavior`] to parametrize this
+    /// [GraphQL object][0] type implementation with.
+    ///
+    /// If [`None`], then [`behavior::Standard`] will be used for the generated
+    /// code.
+    ///
+    /// [`Behavior`]: juniper::behavior
+    /// [`behavior::Standard`]: juniper::behavior::Standard
+    /// [0]: https://spec.graphql.org/October2021#sec-Objects
+    pub(crate) behavior: Option<SpanContainer<behavior::Type>>,
+
     /// Explicitly specified [GraphQL interfaces][2] this [GraphQL object][1]
     /// type implements.
     ///
@@ -135,6 +146,13 @@ impl Parse for Attr {
                         .replace(SpanContainer::new(ident.span(), Some(scl.span()), scl))
                         .none_or_else(|_| err::dup_arg(&ident))?
                 }
+                "behave" | "behavior" => {
+                    input.parse::<token::Eq>()?;
+                    let bh = input.parse::<behavior::Type>()?;
+                    out.behavior
+                        .replace(SpanContainer::new(ident.span(), Some(bh.span()), bh))
+                        .none_or_else(|_| err::dup_arg(&ident))?
+                }
                 "impl" | "implements" | "interfaces" => {
                     input.parse::<token::Eq>()?;
                     for iface in input.parse_maybe_wrapped_and_punctuated::<
@@ -180,6 +198,7 @@ impl Attr {
             description: try_merge_opt!(description: self, another),
             context: try_merge_opt!(context: self, another),
             scalar: try_merge_opt!(scalar: self, another),
+            behavior: try_merge_opt!(behavior: self, another),
             interfaces: try_merge_hashset!(interfaces: self, another => span_joined),
             rename_fields: try_merge_opt!(rename_fields: self, another),
             is_internal: self.is_internal || another.is_internal,
@@ -245,6 +264,13 @@ pub(crate) struct Definition<Operation: ?Sized> {
     /// [1]: https://spec.graphql.org/June2018/#sec-Objects
     pub(crate) scalar: scalar::Type,
 
+    /// [`Behavior`] parametrization to generate code with for this
+    /// [GraphQL object][0].
+    ///
+    /// [`Behavior`]: juniper::behavior
+    /// [0]: https://spec.graphql.org/October2021#sec-Objects
+    pub(crate) behavior: behavior::Type,
+
     /// Defined [GraphQL fields][2] of this [GraphQL object][1].
     ///
     /// [1]: https://spec.graphql.org/June2018/#sec-Objects
@@ -329,6 +355,13 @@ impl<Operation: ?Sized + 'static> Definition<Operation> {
         (quote! { #impl_generics }, where_clause.cloned())
     }
 
+    /// Returns prepared self [`syn::Type`] and [`syn::Generics`] for a trait
+    /// implementation.
+    #[must_use]
+    fn ty_and_generics(&self) -> (&syn::Type, syn::Generics) {
+        (&self.ty, self.generics.clone())
+    }
+
     /// Returns generated code implementing [`marker::IsOutputType`] trait for
     /// this [GraphQL object][1].
     ///
@@ -423,6 +456,69 @@ impl<Operation: ?Sized + 'static> Definition<Operation> {
         }
     }
 
+    /// Returns generated code implementing [`reflect::BaseType`],
+    /// [`reflect::BaseSubTypes`], [`reflect::Implements`],
+    /// [`reflect::WrappedType`] and [`reflect::Fields`] traits for this
+    /// [GraphQL object][0].
+    ///
+    /// [`reflect::BaseSubTypes`]: juniper::reflect::BaseSubTypes
+    /// [`reflect::BaseType`]: juniper::reflect::BaseType
+    /// [`reflect::Fields`]: juniper::reflect::Fields
+    /// [`reflect::Implements`]: juniper::reflect::Implements
+    /// [`reflect::WrappedType`]: juniper::reflect::WrappedType
+    /// [0]: https://spec.graphql.org/October2021#sec-Objects
+    #[must_use]
+    pub(crate) fn impl_reflect(&self) -> TokenStream {
+        let bh = &self.behavior;
+        let (ty, generics) = self.ty_and_generics();
+        let (impl_gens, _, where_clause) = generics.split_for_impl();
+
+        let name = &self.name;
+        let interfaces = self.interfaces.iter();
+        let fields = self.fields.iter().map(|f| &f.name);
+
+        quote! {
+            #[automatically_derived]
+            impl#impl_gens ::juniper::reflect::BaseType<#bh> for #ty
+                #where_clause
+            {
+                const NAME: ::juniper::reflect::Type = #name;
+            }
+
+            #[automatically_derived]
+            impl#impl_gens ::juniper::reflect::BaseSubTypes<#bh> for #ty
+                #where_clause
+            {
+                const NAMES: ::juniper::reflect::Types =
+                    &[<Self as ::juniper::reflect::BaseType<#bh>>::NAME];
+            }
+
+            #[automatically_derived]
+            impl#impl_gens ::juniper::reflect::Implements<#bh> for #ty
+                #where_clause
+            {
+                const NAMES: ::juniper::reflect::Types = &[#(
+                    <#interfaces as ::juniper::reflect::BaseType<#bh>>::NAME
+                ),*];
+            }
+
+            #[automatically_derived]
+            impl#impl_gens ::juniper::reflect::WrappedType<#bh> for #ty
+                #where_clause
+            {
+                const VALUE: ::juniper::reflect::WrappedValue =
+                    ::juniper::reflect::wrap::SINGULAR;
+            }
+
+            #[automatically_derived]
+            impl#impl_gens ::juniper::reflect::Fields<#bh> for #ty
+                #where_clause
+            {
+                const NAMES: ::juniper::reflect::Names = &[#( #fields ),*];
+            }
+        }
+    }
+
     /// Returns generated code implementing [`GraphQLType`] trait for this
     /// [GraphQL object][1].
     ///
@@ -504,6 +600,8 @@ impl ToTokens for Definition<Query> {
         self.impl_field_meta_tokens().to_tokens(into);
         self.impl_field_tokens().to_tokens(into);
         self.impl_async_field_tokens().to_tokens(into);
+        ////////////////////////////////////////////////////////////////////////
+        self.impl_reflect().to_tokens(into);
     }
 }
 
diff --git a/juniper_codegen/src/graphql_scalar/mod.rs b/juniper_codegen/src/graphql_scalar/mod.rs
index 876ec9fa..64370796 100644
--- a/juniper_codegen/src/graphql_scalar/mod.rs
+++ b/juniper_codegen/src/graphql_scalar/mod.rs
@@ -971,9 +971,9 @@ impl Definition {
     /// [`reflect::BaseSubTypes`] and [`reflect::WrappedType`] traits for this
     /// [GraphQL scalar][0].
     ///
-    /// [`reflect::BaseSubTypes`]: juniper::reflection::BaseSubTypes
-    /// [`reflect::BaseType`]: juniper::reflection::BaseType
-    /// [`reflect::WrappedType`]: juniper::reflection::WrappedType
+    /// [`reflect::BaseSubTypes`]: juniper::reflect::BaseSubTypes
+    /// [`reflect::BaseType`]: juniper::reflect::BaseType
+    /// [`reflect::WrappedType`]: juniper::reflect::WrappedType
     /// [0]: https://spec.graphql.org/October2021#sec-Scalars
     fn impl_reflect(&self) -> TokenStream {
         let bh = &self.behavior;
@@ -1087,7 +1087,7 @@ impl Definition {
 
         let ty = {
             let ident = &self.ident;
-            let (_, ty_gen, _) = self.generics.split_for_impl();
+            let (_, ty_gen, _) = generics.split_for_impl();
             parse_quote! { #ident#ty_gen }
         };