From 01d7cb8bd5e1a65051441b6c52ad058d1e62e864 Mon Sep 17 00:00:00 2001 From: ilslv Date: Mon, 20 Dec 2021 07:44:12 +0300 Subject: [PATCH 01/53] WIP --- .../src/codegen/interface_attr.rs | 518 ++++++++++++++++++ juniper/src/macros/helper/mod.rs | 19 + 2 files changed, 537 insertions(+) diff --git a/integration_tests/juniper_tests/src/codegen/interface_attr.rs b/integration_tests/juniper_tests/src/codegen/interface_attr.rs index d8869a04a..aaa213288 100644 --- a/integration_tests/juniper_tests/src/codegen/interface_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/interface_attr.rs @@ -31,6 +31,524 @@ where ) } +mod new { + use super::*; + + const fn fnv1a128(str: &str) -> u128 { + const FNV_OFFSET_BASIS: u128 = 0x6c62272e07bb014262b821756295c58d; + const FNV_PRIME: u128 = 0x0000000001000000000000000000013b; + + let bytes = str.as_bytes(); + let mut hash = FNV_OFFSET_BASIS; + let mut i = 0; + while i < bytes.len() { + hash ^= bytes[i] as u128; + hash = hash.wrapping_mul(FNV_PRIME); + i += 1; + } + hash + } + + const fn is_superset(superset: &[&str], set: &[&str]) -> bool { + const fn str_eq(l: &str, r: &str) -> bool { + let (l, r) = (l.as_bytes(), r.as_bytes()); + + if l.len() != r.len() { + return false; + } + + let mut i = 0; + while i < l.len() { + if l[i] != r[i] { + return false; + } + i += 1; + } + + true + } + + const fn find(set: &[&str], elem: &str) -> bool { + let mut i = 0; + while i < set.len() { + if str_eq(elem, set[i]) { + return true; + } + i += 1; + } + false + } + + if superset.len() < set.len() { + return false; + } + + let mut i = 0; + while i < set.len() { + if !find(superset, set[i]) { + return false; + } + i += 1; + } + + true + } + + trait Type { + const NAME: &'static str; + } + + trait PossibleFragments { + const POSSIBLE_FRAGMENTS: &'static [&'static str]; + } + + impl<'a, T: PossibleFragments> PossibleFragments for &T { + const POSSIBLE_FRAGMENTS: &'static [&'static str] = T::POSSIBLE_FRAGMENTS; + } + + impl Type for String { + const NAME: &'static str = "String"; + } + + impl PossibleFragments for String { + const POSSIBLE_FRAGMENTS: &'static [&'static str] = &[::NAME]; + } + + impl<'a> Type for &'a str { + const NAME: &'static str = "String"; + } + + impl<'a> PossibleFragments for &'a str { + const POSSIBLE_FRAGMENTS: &'static [&'static str] = &[::NAME]; + } + + trait Field { + type Context; + type TypeInfo; + const POSSIBLE_FRAGMENTS: &'static [&'static str]; + + fn call( + &self, + info: &Self::TypeInfo, + args: &::juniper::Arguments, + executor: &::juniper::Executor, + ) -> ::juniper::ExecutionResult; + } + + // #[derive(GraphQLInterface)] + // #[graphql(for(Human, Droid))] + struct Character { + id: String, + } + + impl Type for Character { + const NAME: &'static str = "Character"; + } + + impl PossibleFragments for Character { + const POSSIBLE_FRAGMENTS: &'static [&'static str] = &[ + ::NAME, + ::NAME, + ::NAME, + ]; + } + + enum CharacterEnumValue { + Human(I1), + Droid(I2), + } + + type CharacterValue = CharacterEnumValue; + + impl From for CharacterValue { + fn from(v: Human) -> Self { + Self::Human(v) + } + } + + impl From for CharacterValue { + fn from(v: Droid) -> Self { + Self::Droid(v) + } + } + + impl Field for CharacterValue { + type Context = (); + type TypeInfo = (); + const POSSIBLE_FRAGMENTS: &'static [&'static str] = + ::POSSIBLE_FRAGMENTS; + + fn call( + &self, + info: &Self::TypeInfo, + args: &juniper::Arguments, + executor: &juniper::Executor, + ) -> juniper::ExecutionResult { + match self { + CharacterValue::Human(v) => { + const _: () = assert!(is_superset( + ::POSSIBLE_FRAGMENTS, + >::POSSIBLE_FRAGMENTS, + )); + + <_ as Field>::call(v, info, args, executor) + } + CharacterValue::Droid(v) => { + const _: () = assert!(is_superset( + ::POSSIBLE_FRAGMENTS, + >::POSSIBLE_FRAGMENTS, + )); + + <_ as Field>::call(v, info, args, executor) + } + } + } + } + + #[automatically_derived] + impl<__S> ::juniper::marker::GraphQLInterface<__S> for CharacterValue + where + __S: ::juniper::ScalarValue, + { + fn mark() { + const _: fn() = || { + trait MutuallyExclusive {} + impl MutuallyExclusive for Droid {} + impl MutuallyExclusive for Human {} + }; + } + } + + #[automatically_derived] + impl<__S> ::juniper::marker::IsOutputType<__S> for CharacterValue + where + __S: ::juniper::ScalarValue, + { + fn mark() { + <<&str as ::juniper::IntoResolvable< + '_, + __S, + _, + >::Context, + >>::Type as ::juniper::marker::IsOutputType<__S>>::mark(); + >::mark(); + >::mark(); + } + } + + #[automatically_derived] + impl<__S> ::juniper::GraphQLType<__S> for CharacterValue + where + __S: ::juniper::ScalarValue, + { + fn name(_: &Self::TypeInfo) -> Option<&'static str> { + Some("Character") + } + fn meta<'r>( + info: &Self::TypeInfo, + registry: &mut ::juniper::Registry<'r, __S>, + ) -> ::juniper::meta::MetaType<'r, __S> + where + __S: 'r, + { + let _ = registry.get_type::(info); + let _ = registry.get_type::(info); + let fields = [registry.field_convert::<&str, _, Self::Context>("id", info)]; + registry + .build_interface_type::(info, &fields) + .into_meta() + } + } + + #[allow(deprecated)] + #[automatically_derived] + impl<__S> ::juniper::GraphQLValue<__S> for CharacterValue + where + __S: ::juniper::ScalarValue, + { + type Context = (); + type TypeInfo = (); + + fn type_name<'__i>(&self, info: &'__i Self::TypeInfo) -> Option<&'__i str> { + >::name(info) + } + fn resolve_field( + &self, + info: &Self::TypeInfo, + field: &str, + args: &::juniper::Arguments<__S>, + executor: &::juniper::Executor, + ) -> ::juniper::ExecutionResult<__S> { + match field { + "id" => <_ as Field<__S, { fnv1a128("id") }>>::call(self, info, args, executor), + _ => { + return Err(::juniper::FieldError::from({ + format!("Field `{}` not found on type `{}`", field, "CharacterValue",) + })) + } + } + } + fn concrete_type_name(&self, context: &Self::Context, info: &Self::TypeInfo) -> String { + match self { + Self::Human(v) => { + >::concrete_type_name(v, context, info) + } + Self::Droid(v) => { + >::concrete_type_name(v, context, info) + } + } + } + fn resolve_into_type( + &self, + info: &Self::TypeInfo, + type_name: &str, + _: Option<&[::juniper::Selection<__S>]>, + executor: &::juniper::Executor, + ) -> ::juniper::ExecutionResult<__S> { + match self { + Self::Human(res) => ::juniper::IntoResolvable::into(res, executor.context()) + .and_then(|res| match res { + Some((ctx, r)) => executor.replaced_context(ctx).resolve_with_ctx(info, &r), + None => Ok(::juniper::Value::null()), + }), + Self::Droid(res) => ::juniper::IntoResolvable::into(res, executor.context()) + .and_then(|res| match res { + Some((ctx, r)) => executor.replaced_context(ctx).resolve_with_ctx(info, &r), + None => Ok(::juniper::Value::null()), + }), + } + } + } + + #[allow(deprecated, non_snake_case)] + #[automatically_derived] + impl<__S> ::juniper::GraphQLValueAsync<__S> for CharacterValue + where + __S: ::juniper::ScalarValue, + Self: Sync, + __S: Send + Sync, + { + fn resolve_field_async<'b>( + &'b self, + info: &'b Self::TypeInfo, + field: &'b str, + args: &'b ::juniper::Arguments<__S>, + executor: &'b ::juniper::Executor, + ) -> ::juniper::BoxFuture<'b, ::juniper::ExecutionResult<__S>> { + match field { + "id" => Box::pin(::juniper::futures::future::ready(<_ as Field< + __S, + { fnv1a128("id") }, + >>::call( + self, info, args, executor, + ))), + _ => Box::pin(async move { + return Err(::juniper::FieldError::from({ + format!("Field `{}` not found on type `{}`", field, "CharacterValue",) + })); + }), + } + } + fn resolve_into_type_async<'b>( + &'b self, + info: &'b Self::TypeInfo, + type_name: &str, + _: Option<&'b [::juniper::Selection<'b, __S>]>, + executor: &'b ::juniper::Executor<'b, 'b, Self::Context, __S>, + ) -> ::juniper::BoxFuture<'b, ::juniper::ExecutionResult<__S>> { + match self { + Self::Droid(v) => { + let fut = ::juniper::futures::future::ready(v); + Box::pin(::juniper::futures::FutureExt::then( + fut, + move |res| async move { + match ::juniper::IntoResolvable::into(res, executor.context())? { + Some((ctx, r)) => { + let subexec = executor.replaced_context(ctx); + subexec.resolve_with_ctx_async(info, &r).await + } + None => Ok(::juniper::Value::null()), + } + }, + )) + } + Self::Human(v) => { + let fut = ::juniper::futures::future::ready(v); + Box::pin(::juniper::futures::FutureExt::then( + fut, + move |res| async move { + match ::juniper::IntoResolvable::into(res, executor.context())? { + Some((ctx, r)) => { + let subexec = executor.replaced_context(ctx); + subexec.resolve_with_ctx_async(info, &r).await + } + None => Ok(::juniper::Value::null()), + } + }, + )) + } + } + } + } + + // --------- + + #[derive(GraphQLObject)] + #[graphql(impl = CharacterValue)] + struct Human { + id: String, + home_planet: String, + } + + impl Type for Human { + const NAME: &'static str = "Human"; + } + + impl PossibleFragments for Human { + const POSSIBLE_FRAGMENTS: &'static [&'static str] = &[::NAME]; + } + + impl Field for Human { + type Context = (); + type TypeInfo = (); + const POSSIBLE_FRAGMENTS: &'static [&'static str] = + ::POSSIBLE_FRAGMENTS; + + fn call( + &self, + info: &Self::TypeInfo, + _: &juniper::Arguments, + executor: &juniper::Executor, + ) -> juniper::ExecutionResult { + let res = &self.id; + + ::juniper::IntoResolvable::into(res, executor.context()).and_then(|res| match res { + Some((ctx, r)) => executor.replaced_context(ctx).resolve_with_ctx(info, &r), + None => Ok(::juniper::Value::null()), + }) + } + } + + // #[derive(GraphQLObject)] + // #[graphql(impl = CharacterValue)] + struct Droid { + id: String, + primary_function: String, + } + + #[graphql_object(impl = CharacterValue)] + impl Droid { + fn id(&self) -> &str { + &self.id + } + + fn primary_function(&self) -> &str { + &self.primary_function + } + } + + impl Type for Droid { + const NAME: &'static str = "Droid"; + } + + impl PossibleFragments for Droid { + const POSSIBLE_FRAGMENTS: &'static [&'static str] = &[::NAME]; + } + + impl Field for Droid { + type Context = (); + type TypeInfo = (); + const POSSIBLE_FRAGMENTS: &'static [&'static str] = + <&str as PossibleFragments>::POSSIBLE_FRAGMENTS; + + fn call( + &self, + info: &Self::TypeInfo, + _: &juniper::Arguments, + executor: &juniper::Executor, + ) -> juniper::ExecutionResult { + let res = self.id(); + + ::juniper::IntoResolvable::into(res, executor.context()).and_then(|res| match res { + Some((ctx, r)) => executor.replaced_context(ctx).resolve_with_ctx(info, &r), + None => Ok(::juniper::Value::null()), + }) + } + } + + // ----- + + #[derive(Clone, Copy)] + enum QueryRoot { + Human, + Droid, + } + + #[graphql_object(scalar = S: ScalarValue + Send + Sync)] + impl QueryRoot { + fn character(&self) -> CharacterValue { + match self { + Self::Human => Human { + id: "human-32".to_string(), + home_planet: "earth".to_string(), + } + .into(), + Self::Droid => Droid { + id: "droid-99".to_string(), + primary_function: "run".to_string(), + } + .into(), + } + } + } + + // -------------- + + #[tokio::test] + async fn enum_resolves_human() { + const DOC: &str = r#"{ + character { + ... on Human { + humanId: id + homePlanet + } + } + }"#; + + let schema = schema(QueryRoot::Human); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"character": {"humanId": "human-32", "homePlanet": "earth"}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn enum_resolves_droid() { + const DOC: &str = r#"{ + character { + ... on Droid { + droidId: id + primaryFunction + } + } + }"#; + + let schema = schema(QueryRoot::Droid); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}), + vec![], + )), + ); + } +} + mod no_implers { use super::*; diff --git a/juniper/src/macros/helper/mod.rs b/juniper/src/macros/helper/mod.rs index 0f81018da..147b3a15e 100644 --- a/juniper/src/macros/helper/mod.rs +++ b/juniper/src/macros/helper/mod.rs @@ -83,3 +83,22 @@ where { Box::pin(future::err(err_unnamed_type(name))) } + +/// Non-cryptographic hash with good dispersion to use [`str`](prim@str) in +/// const generics. See [spec] for more info. +/// +/// [spec]: https://datatracker.ietf.org/doc/html/draft-eastlake-fnv-17.html +pub const fn fnv1a128(str: &str) -> u128 { + const FNV_OFFSET_BASIS: u128 = 0x6c62272e07bb014262b821756295c58d; + const FNV_PRIME: u128 = 0x0000000001000000000000000000013b; + + let bytes = str.as_bytes(); + let mut hash = FNV_OFFSET_BASIS; + let mut i = 0; + while i < bytes.len() { + hash ^= bytes[i] as u128; + hash = hash.wrapping_mul(FNV_PRIME); + i += 1; + } + hash +} From 205cfcd993269409d5fd1dff0b52e1ac23e12008 Mon Sep 17 00:00:00 2001 From: ilslv Date: Mon, 20 Dec 2021 10:53:06 +0300 Subject: [PATCH 02/53] WIP --- .../src/codegen/interface_attr.rs | 76 ++++++------------- juniper/src/macros/helper/mod.rs | 44 ++++++++++- juniper/src/types/scalars.rs | 8 ++ juniper_codegen/src/derive_scalar_value.rs | 9 +++ juniper_codegen/src/impl_scalar.rs | 9 +++ 5 files changed, 92 insertions(+), 54 deletions(-) diff --git a/integration_tests/juniper_tests/src/codegen/interface_attr.rs b/integration_tests/juniper_tests/src/codegen/interface_attr.rs index aaa213288..92cd9a9e7 100644 --- a/integration_tests/juniper_tests/src/codegen/interface_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/interface_attr.rs @@ -94,38 +94,10 @@ mod new { true } - trait Type { - const NAME: &'static str; - } - - trait PossibleFragments { - const POSSIBLE_FRAGMENTS: &'static [&'static str]; - } - - impl<'a, T: PossibleFragments> PossibleFragments for &T { - const POSSIBLE_FRAGMENTS: &'static [&'static str] = T::POSSIBLE_FRAGMENTS; - } - - impl Type for String { - const NAME: &'static str = "String"; - } - - impl PossibleFragments for String { - const POSSIBLE_FRAGMENTS: &'static [&'static str] = &[::NAME]; - } - - impl<'a> Type for &'a str { - const NAME: &'static str = "String"; - } - - impl<'a> PossibleFragments for &'a str { - const POSSIBLE_FRAGMENTS: &'static [&'static str] = &[::NAME]; - } - trait Field { type Context; type TypeInfo; - const POSSIBLE_FRAGMENTS: &'static [&'static str]; + const SUBTYPES: &'static [&'static str]; fn call( &self, @@ -141,15 +113,15 @@ mod new { id: String, } - impl Type for Character { + impl juniper::macros::helper::Type for Character { const NAME: &'static str = "Character"; } - impl PossibleFragments for Character { - const POSSIBLE_FRAGMENTS: &'static [&'static str] = &[ - ::NAME, - ::NAME, - ::NAME, + impl juniper::macros::helper::SubTypes for Character { + const NAMES: &'static [&'static str] = &[ + >::NAME, + >::NAME, + >::NAME, ]; } @@ -175,8 +147,8 @@ mod new { impl Field for CharacterValue { type Context = (); type TypeInfo = (); - const POSSIBLE_FRAGMENTS: &'static [&'static str] = - ::POSSIBLE_FRAGMENTS; + const SUBTYPES: &'static [&'static str] = + >::NAMES; fn call( &self, @@ -187,16 +159,16 @@ mod new { match self { CharacterValue::Human(v) => { const _: () = assert!(is_superset( - ::POSSIBLE_FRAGMENTS, - >::POSSIBLE_FRAGMENTS, + ::NAMES, + >::SUBTYPES, )); <_ as Field>::call(v, info, args, executor) } CharacterValue::Droid(v) => { const _: () = assert!(is_superset( - ::POSSIBLE_FRAGMENTS, - >::POSSIBLE_FRAGMENTS, + ::NAMES, + >::SUBTYPES, )); <_ as Field>::call(v, info, args, executor) @@ -400,19 +372,19 @@ mod new { home_planet: String, } - impl Type for Human { + impl juniper::macros::helper::Type for Human { const NAME: &'static str = "Human"; } - impl PossibleFragments for Human { - const POSSIBLE_FRAGMENTS: &'static [&'static str] = &[::NAME]; + impl juniper::macros::helper::SubTypes for Human { + const NAMES: &'static [&'static str] = &[>::NAME]; } impl Field for Human { type Context = (); type TypeInfo = (); - const POSSIBLE_FRAGMENTS: &'static [&'static str] = - ::POSSIBLE_FRAGMENTS; + const SUBTYPES: &'static [&'static str] = + >::NAMES; fn call( &self, @@ -429,8 +401,6 @@ mod new { } } - // #[derive(GraphQLObject)] - // #[graphql(impl = CharacterValue)] struct Droid { id: String, primary_function: String, @@ -447,19 +417,19 @@ mod new { } } - impl Type for Droid { + impl juniper::macros::helper::Type for Droid { const NAME: &'static str = "Droid"; } - impl PossibleFragments for Droid { - const POSSIBLE_FRAGMENTS: &'static [&'static str] = &[::NAME]; + impl juniper::macros::helper::SubTypes for Droid { + const NAMES: &'static [&'static str] = &[::NAME]; } impl Field for Droid { type Context = (); type TypeInfo = (); - const POSSIBLE_FRAGMENTS: &'static [&'static str] = - <&str as PossibleFragments>::POSSIBLE_FRAGMENTS; + const SUBTYPES: &'static [&'static str] = + <&str as juniper::macros::helper::SubTypes>::NAMES; fn call( &self, diff --git a/juniper/src/macros/helper/mod.rs b/juniper/src/macros/helper/mod.rs index 147b3a15e..9d5cc0fc1 100644 --- a/juniper/src/macros/helper/mod.rs +++ b/juniper/src/macros/helper/mod.rs @@ -2,7 +2,7 @@ pub mod subscription; -use std::fmt; +use std::{fmt, rc::Rc, sync::Arc}; use futures::future::{self, BoxFuture}; @@ -102,3 +102,45 @@ pub const fn fnv1a128(str: &str) -> u128 { } hash } + +/// TODO +pub trait Type { + const NAME: &'static str; +} + +impl<'a, S, T: Type + ?Sized> Type for &'a T { + const NAME: &'static str = T::NAME; +} + +impl + ?Sized> Type for Box { + const NAME: &'static str = T::NAME; +} + +impl + ?Sized> Type for Arc { + const NAME: &'static str = T::NAME; +} + +impl + ?Sized> Type for Rc { + const NAME: &'static str = T::NAME; +} + +/// TODO +pub trait SubTypes { + const NAMES: &'static [&'static str]; +} + +impl<'a, S, T: SubTypes + ?Sized> SubTypes for &'a T { + const NAMES: &'static [&'static str] = T::NAMES; +} + +impl + ?Sized> SubTypes for Box { + const NAMES: &'static [&'static str] = T::NAMES; +} + +impl + ?Sized> SubTypes for Arc { + const NAMES: &'static [&'static str] = T::NAMES; +} + +impl + ?Sized> SubTypes for Rc { + const NAMES: &'static [&'static str] = T::NAMES; +} diff --git a/juniper/src/types/scalars.rs b/juniper/src/types/scalars.rs index d55c6ac3d..443240251 100644 --- a/juniper/src/types/scalars.rs +++ b/juniper/src/types/scalars.rs @@ -202,6 +202,14 @@ where }) } +impl crate::macros::helper::Type for str { + const NAME: &'static str = "String"; +} + +impl crate::macros::helper::SubTypes for str { + const NAMES: &'static [&'static str] = &[::NAME]; +} + impl GraphQLType for str where S: ScalarValue, diff --git a/juniper_codegen/src/derive_scalar_value.rs b/juniper_codegen/src/derive_scalar_value.rs index a3c87c674..3ea24dcec 100644 --- a/juniper_codegen/src/derive_scalar_value.rs +++ b/juniper_codegen/src/derive_scalar_value.rs @@ -224,6 +224,15 @@ fn impl_scalar_struct( impl#impl_generics ::juniper::marker::IsInputType<#scalar> for #ident where #scalar: ::juniper::ScalarValue, { } + + impl#impl_generics ::juniper::macros::helper::Type<#scalar> for #ident { + const NAME: &'static str = #name; + } + + impl#impl_generics ::juniper::macros::helper::SubTypes<#scalar> for #ident { + const NAMES: &'static [&'static str] = + &[>::NAME]; + } ); Ok(content) diff --git a/juniper_codegen/src/impl_scalar.rs b/juniper_codegen/src/impl_scalar.rs index 94c8481c3..d4676350c 100644 --- a/juniper_codegen/src/impl_scalar.rs +++ b/juniper_codegen/src/impl_scalar.rs @@ -325,6 +325,15 @@ pub fn build_scalar( #from_str_body } } + + impl#generic_type_decl ::juniper::macros::helper::Type<#generic_type> for #impl_for_type { + const NAME: &'static str = #name; + } + + impl#generic_type_decl ::juniper::macros::helper::SubTypes<#generic_type> for #impl_for_type { + const NAMES: &'static [&'static str] = + &[>::NAME]; + } ); Ok(content) From 928cb9c07d6ad9b3629613c6da531d218312f20e Mon Sep 17 00:00:00 2001 From: ilslv Date: Mon, 20 Dec 2021 14:26:16 +0300 Subject: [PATCH 03/53] WIP (with IsSubtype) --- .../src/codegen/interface_attr.rs | 110 ++++++++++++------ 1 file changed, 72 insertions(+), 38 deletions(-) diff --git a/integration_tests/juniper_tests/src/codegen/interface_attr.rs b/integration_tests/juniper_tests/src/codegen/interface_attr.rs index 92cd9a9e7..279973990 100644 --- a/integration_tests/juniper_tests/src/codegen/interface_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/interface_attr.rs @@ -94,10 +94,54 @@ mod new { true } + trait IsSubtype { + fn mark() {} + } + + impl IsSubtype for String {} + impl IsSubtype<&'static str> for String {} + impl IsSubtype for &'static str {} + impl IsSubtype<&'static str> for &'static str {} + + impl IsSubtype> for Vec where T: IsSubtype {} + impl IsSubtype> for T where T: IsSubtype {} + + impl IsSubtype<()> for () {} + impl IsSubtype<(Option,)> for () {} + impl IsSubtype<(Option, Option)> for () {} + impl IsSubtype<(Option, Option, Option)> for () {} + + impl IsSubtype<(S1,)> for (T1,) where T1: IsSubtype {} + impl IsSubtype<(S1, Option)> for (T1,) where T1: IsSubtype {} + impl IsSubtype<(S1, Option, Option)> for (T1,) where T1: IsSubtype {} + + impl IsSubtype<(S1, S2)> for (T1, T2) + where + T1: IsSubtype, + T2: IsSubtype, + { + } + impl IsSubtype<(S1, S2, Option)> for (T1, T2) + where + T1: IsSubtype, + T2: IsSubtype, + { + } + + impl IsSubtype<(S1, S2, S3)> for (T1, T2, T3) + where + T1: IsSubtype, + T2: IsSubtype, + T3: IsSubtype, + { + } + trait Field { type Context; type TypeInfo; - const SUBTYPES: &'static [&'static str]; + type Ret; + type ArgTypes; + const ARG_NAMES: &'static [&'static str]; fn call( &self, @@ -113,17 +157,8 @@ mod new { id: String, } - impl juniper::macros::helper::Type for Character { - const NAME: &'static str = "Character"; - } - - impl juniper::macros::helper::SubTypes for Character { - const NAMES: &'static [&'static str] = &[ - >::NAME, - >::NAME, - >::NAME, - ]; - } + impl IsSubtype for Character {} + impl IsSubtype for Character {} enum CharacterEnumValue { Human(I1), @@ -147,8 +182,9 @@ mod new { impl Field for CharacterValue { type Context = (); type TypeInfo = (); - const SUBTYPES: &'static [&'static str] = - >::NAMES; + type Ret = String; + type ArgTypes = (); + const ARG_NAMES: &'static [&'static str] = &[]; fn call( &self, @@ -158,17 +194,29 @@ mod new { ) -> juniper::ExecutionResult { match self { CharacterValue::Human(v) => { + let _ = >::Ret, + >>::mark; + let _ = >::ArgTypes, + >>::mark; const _: () = assert!(is_superset( - ::NAMES, - >::SUBTYPES, + >::ARG_NAMES, + >::ARG_NAMES, )); <_ as Field>::call(v, info, args, executor) } CharacterValue::Droid(v) => { + let _ = >::Ret, + >>::mark; + let _ = >::ArgTypes, + >>::mark; const _: () = assert!(is_superset( - ::NAMES, - >::SUBTYPES, + >::ARG_NAMES, + >::ARG_NAMES, )); <_ as Field>::call(v, info, args, executor) @@ -372,19 +420,12 @@ mod new { home_planet: String, } - impl juniper::macros::helper::Type for Human { - const NAME: &'static str = "Human"; - } - - impl juniper::macros::helper::SubTypes for Human { - const NAMES: &'static [&'static str] = &[>::NAME]; - } - impl Field for Human { type Context = (); type TypeInfo = (); - const SUBTYPES: &'static [&'static str] = - >::NAMES; + type Ret = String; + type ArgTypes = (Option,); + const ARG_NAMES: &'static [&'static str] = &["optional"]; fn call( &self, @@ -417,19 +458,12 @@ mod new { } } - impl juniper::macros::helper::Type for Droid { - const NAME: &'static str = "Droid"; - } - - impl juniper::macros::helper::SubTypes for Droid { - const NAMES: &'static [&'static str] = &[::NAME]; - } - impl Field for Droid { type Context = (); type TypeInfo = (); - const SUBTYPES: &'static [&'static str] = - <&str as juniper::macros::helper::SubTypes>::NAMES; + type Ret = &'static str; + type ArgTypes = (); + const ARG_NAMES: &'static [&'static str] = &[]; fn call( &self, From 3dad1622b209c8b8e1d7d6dd4143fe55080e4d56 Mon Sep 17 00:00:00 2001 From: ilslv Date: Mon, 20 Dec 2021 16:20:22 +0300 Subject: [PATCH 04/53] WIP (It looks like we're onto something) --- .../src/codegen/interface_attr.rs | 130 ++++++++---------- 1 file changed, 55 insertions(+), 75 deletions(-) diff --git a/integration_tests/juniper_tests/src/codegen/interface_attr.rs b/integration_tests/juniper_tests/src/codegen/interface_attr.rs index 279973990..28fbf3e7e 100644 --- a/integration_tests/juniper_tests/src/codegen/interface_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/interface_attr.rs @@ -49,55 +49,12 @@ mod new { hash } - const fn is_superset(superset: &[&str], set: &[&str]) -> bool { - const fn str_eq(l: &str, r: &str) -> bool { - let (l, r) = (l.as_bytes(), r.as_bytes()); - - if l.len() != r.len() { - return false; - } - - let mut i = 0; - while i < l.len() { - if l[i] != r[i] { - return false; - } - i += 1; - } - - true - } - - const fn find(set: &[&str], elem: &str) -> bool { - let mut i = 0; - while i < set.len() { - if str_eq(elem, set[i]) { - return true; - } - i += 1; - } - false - } - - if superset.len() < set.len() { - return false; - } - - let mut i = 0; - while i < set.len() { - if !find(superset, set[i]) { - return false; - } - i += 1; - } - - true - } - trait IsSubtype { fn mark() {} } + struct Parameter(T); + impl IsSubtype for String {} impl IsSubtype<&'static str> for String {} impl IsSubtype for &'static str {} @@ -107,32 +64,64 @@ mod new { impl IsSubtype> for T where T: IsSubtype {} impl IsSubtype<()> for () {} - impl IsSubtype<(Option,)> for () {} - impl IsSubtype<(Option, Option)> for () {} - impl IsSubtype<(Option, Option, Option)> for () {} - - impl IsSubtype<(S1,)> for (T1,) where T1: IsSubtype {} - impl IsSubtype<(S1, Option)> for (T1,) where T1: IsSubtype {} - impl IsSubtype<(S1, Option, Option)> for (T1,) where T1: IsSubtype {} + impl IsSubtype<(Option>,)> for () {} + impl + IsSubtype<(Option>, Option>)> for () + { + } + impl + IsSubtype<( + Option>, + Option>, + Option>, + )> for () + { + } - impl IsSubtype<(S1, S2)> for (T1, T2) + impl IsSubtype<(Parameter,)> for (Parameter,) where + T1: IsSubtype + { + } + impl + IsSubtype<(Parameter, Option>)> for (Parameter,) where T1: IsSubtype, - T2: IsSubtype, { } - impl IsSubtype<(S1, S2, Option)> for (T1, T2) + impl + IsSubtype<(Option>, Parameter)> for (Parameter,) where T1: IsSubtype, - T2: IsSubtype, { } - - impl IsSubtype<(S1, S2, S3)> for (T1, T2, T3) + impl + IsSubtype<( + Parameter, + Option>, + Option>, + )> for (Parameter,) + where + T1: IsSubtype, + { + } + impl + IsSubtype<( + Option>, + Parameter, + Option>, + )> for (Parameter,) + where + T1: IsSubtype, + { + } + impl + IsSubtype<( + Option>, + Option>, + Parameter, + )> for (Parameter,) where T1: IsSubtype, - T2: IsSubtype, - T3: IsSubtype, { } @@ -141,7 +130,6 @@ mod new { type TypeInfo; type Ret; type ArgTypes; - const ARG_NAMES: &'static [&'static str]; fn call( &self, @@ -183,8 +171,7 @@ mod new { type Context = (); type TypeInfo = (); type Ret = String; - type ArgTypes = (); - const ARG_NAMES: &'static [&'static str] = &[]; + type ArgTypes = (Parameter,); fn call( &self, @@ -200,10 +187,6 @@ mod new { let _ = >::ArgTypes, >>::mark; - const _: () = assert!(is_superset( - >::ARG_NAMES, - >::ARG_NAMES, - )); <_ as Field>::call(v, info, args, executor) } @@ -214,10 +197,6 @@ mod new { let _ = >::ArgTypes, >>::mark; - const _: () = assert!(is_superset( - >::ARG_NAMES, - >::ARG_NAMES, - )); <_ as Field>::call(v, info, args, executor) } @@ -424,8 +403,10 @@ mod new { type Context = (); type TypeInfo = (); type Ret = String; - type ArgTypes = (Option,); - const ARG_NAMES: &'static [&'static str] = &["optional"]; + type ArgTypes = ( + Parameter<&'static str, { fnv1a128("required") }>, + Option>, + ); fn call( &self, @@ -462,8 +443,7 @@ mod new { type Context = (); type TypeInfo = (); type Ret = &'static str; - type ArgTypes = (); - const ARG_NAMES: &'static [&'static str] = &[]; + type ArgTypes = (Parameter,); fn call( &self, From af0448693f5edec766a30f6532f967c17b30b66e Mon Sep 17 00:00:00 2001 From: ilslv Date: Tue, 21 Dec 2021 12:35:11 +0300 Subject: [PATCH 05/53] WIP (return to const assertions) --- .../src/codegen/interface_attr.rs | 613 +++++++++++++----- .../juniper_tests/src/codegen/object_attr.rs | 2 +- 2 files changed, 441 insertions(+), 174 deletions(-) diff --git a/integration_tests/juniper_tests/src/codegen/interface_attr.rs b/integration_tests/juniper_tests/src/codegen/interface_attr.rs index 28fbf3e7e..c2f7a3c62 100644 --- a/integration_tests/juniper_tests/src/codegen/interface_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/interface_attr.rs @@ -34,176 +34,6 @@ where mod new { use super::*; - const fn fnv1a128(str: &str) -> u128 { - const FNV_OFFSET_BASIS: u128 = 0x6c62272e07bb014262b821756295c58d; - const FNV_PRIME: u128 = 0x0000000001000000000000000000013b; - - let bytes = str.as_bytes(); - let mut hash = FNV_OFFSET_BASIS; - let mut i = 0; - while i < bytes.len() { - hash ^= bytes[i] as u128; - hash = hash.wrapping_mul(FNV_PRIME); - i += 1; - } - hash - } - - trait IsSubtype { - fn mark() {} - } - - struct Parameter(T); - - impl IsSubtype for String {} - impl IsSubtype<&'static str> for String {} - impl IsSubtype for &'static str {} - impl IsSubtype<&'static str> for &'static str {} - - impl IsSubtype> for Vec where T: IsSubtype {} - impl IsSubtype> for T where T: IsSubtype {} - - impl IsSubtype<()> for () {} - impl IsSubtype<(Option>,)> for () {} - impl - IsSubtype<(Option>, Option>)> for () - { - } - impl - IsSubtype<( - Option>, - Option>, - Option>, - )> for () - { - } - - impl IsSubtype<(Parameter,)> for (Parameter,) where - T1: IsSubtype - { - } - impl - IsSubtype<(Parameter, Option>)> for (Parameter,) - where - T1: IsSubtype, - { - } - impl - IsSubtype<(Option>, Parameter)> for (Parameter,) - where - T1: IsSubtype, - { - } - impl - IsSubtype<( - Parameter, - Option>, - Option>, - )> for (Parameter,) - where - T1: IsSubtype, - { - } - impl - IsSubtype<( - Option>, - Parameter, - Option>, - )> for (Parameter,) - where - T1: IsSubtype, - { - } - impl - IsSubtype<( - Option>, - Option>, - Parameter, - )> for (Parameter,) - where - T1: IsSubtype, - { - } - - trait Field { - type Context; - type TypeInfo; - type Ret; - type ArgTypes; - - fn call( - &self, - info: &Self::TypeInfo, - args: &::juniper::Arguments, - executor: &::juniper::Executor, - ) -> ::juniper::ExecutionResult; - } - - // #[derive(GraphQLInterface)] - // #[graphql(for(Human, Droid))] - struct Character { - id: String, - } - - impl IsSubtype for Character {} - impl IsSubtype for Character {} - - enum CharacterEnumValue { - Human(I1), - Droid(I2), - } - - type CharacterValue = CharacterEnumValue; - - impl From for CharacterValue { - fn from(v: Human) -> Self { - Self::Human(v) - } - } - - impl From for CharacterValue { - fn from(v: Droid) -> Self { - Self::Droid(v) - } - } - - impl Field for CharacterValue { - type Context = (); - type TypeInfo = (); - type Ret = String; - type ArgTypes = (Parameter,); - - fn call( - &self, - info: &Self::TypeInfo, - args: &juniper::Arguments, - executor: &juniper::Executor, - ) -> juniper::ExecutionResult { - match self { - CharacterValue::Human(v) => { - let _ = >::Ret, - >>::mark; - let _ = >::ArgTypes, - >>::mark; - - <_ as Field>::call(v, info, args, executor) - } - CharacterValue::Droid(v) => { - let _ = >::Ret, - >>::mark; - let _ = >::ArgTypes, - >>::mark; - - <_ as Field>::call(v, info, args, executor) - } - } - } - } - #[automatically_derived] impl<__S> ::juniper::marker::GraphQLInterface<__S> for CharacterValue where @@ -390,6 +220,419 @@ mod new { } } + impl IsSubtype for String {} + impl IsSubtype<&'static str> for String {} + impl IsSubtype for &'static str {} + impl IsSubtype<&'static str> for &'static str {} + + impl IsSubtype> for Vec where T: IsSubtype {} + impl IsSubtype> for T where T: IsSubtype {} + + impl IsSubtype<()> for () {} + impl IsSubtype<(Option>,)> for () {} + impl + IsSubtype<(Option>, Option>)> for () + { + } + impl + IsSubtype<( + Option>, + Option>, + Option>, + )> for () + { + } + + impl IsSubtype<(Argument,)> for (Argument,) where + T1: IsSubtype + { + } + impl + IsSubtype<(Argument, Option>)> for (Argument,) + where + T1: IsSubtype, + { + } + impl + IsSubtype<(Option>, Argument)> for (Argument,) + where + T1: IsSubtype, + { + } + impl + IsSubtype<( + Argument, + Option>, + Option>, + )> for (Argument,) + where + T1: IsSubtype, + { + } + impl + IsSubtype<( + Option>, + Argument, + Option>, + )> for (Argument,) + where + T1: IsSubtype, + { + } + impl + IsSubtype<( + Option>, + Option>, + Argument, + )> for (Argument,) + where + T1: IsSubtype, + { + } + + const _: () = assert!(is_subtype(::N, ::N)); + const _: () = assert!(is_subtype( + as WrappedType>::N, + ::N, + )); + const _: () = assert!(!is_subtype( + > as WrappedType>::N, + ::N, + )); + const _: () = assert!(is_subtype( + > as WrappedType>::N, + as WrappedType>::N, + )); + const _: () = assert!(is_subtype( + >>> as WrappedType>::N, + > as WrappedType>::N, + )); + + const fn fnv1a128(str: &str) -> u128 { + const FNV_OFFSET_BASIS: u128 = 0x6c62272e07bb014262b821756295c58d; + const FNV_PRIME: u128 = 0x0000000001000000000000000000013b; + + let bytes = str.as_bytes(); + let mut hash = FNV_OFFSET_BASIS; + let mut i = 0; + while i < bytes.len() { + hash ^= bytes[i] as u128; + hash = hash.wrapping_mul(FNV_PRIME); + i += 1; + } + hash + } + + const fn str_eq(l: &str, r: &str) -> bool { + let (l, r) = (l.as_bytes(), r.as_bytes()); + + if l.len() != r.len() { + return false; + } + + let mut i = 0; + while i < l.len() { + if l[i] != r[i] { + return false; + } + i += 1; + } + + true + } + + const fn is_superset(superset: &[&str], set: &[&str]) -> bool { + const fn find(set: &[&str], elem: &str) -> bool { + let mut i = 0; + while i < set.len() { + if str_eq(elem, set[i]) { + return true; + } + i += 1; + } + false + } + + if superset.len() < set.len() { + return false; + } + + let mut i = 0; + while i < set.len() { + if !find(superset, set[i]) { + return false; + } + i += 1; + } + + true + } + + const fn is_valid_field_args( + base_interface: &'static [(&'static str, &'static str, u128)], + implementation: &'static [(&'static str, &'static str, u128)], + ) -> bool { + const fn find( + base_interface: &'static [(&'static str, &'static str, u128)], + impl_name: &'static str, + impl_ty: &'static str, + impl_wrap_val: u128, + ) -> bool { + let mut i = 0; + while i < base_interface.len() { + let (base_name, base_ty, base_wrap_val) = base_interface[i]; + if str_eq(impl_name, base_name) { + return str_eq(base_ty, impl_ty) && impl_wrap_val == base_wrap_val; + } + i += 1; + } + false + } + + if base_interface.len() > implementation.len() { + return false; + } + + let mut i = 0; + let mut successfully_implemented_fields = 0; + while i < implementation.len() { + let (impl_name, impl_ty, impl_wrap_val) = implementation[i]; + if find(base_interface, impl_name, impl_ty, impl_wrap_val) { + successfully_implemented_fields += 1; + } else if impl_wrap_val % 10 != 2 { + // Not an optional field. + return false; + } + i += 1; + } + + successfully_implemented_fields == base_interface.len() + } + + const fn is_subtype(ty: u128, subtype: u128) -> bool { + let ty_current = ty % 10; + let subtype_current = subtype % 10; + + if ty_current == subtype_current { + if ty_current == 1 { + true + } else { + is_subtype(ty / 10, subtype / 10) + } + } else if ty_current == 2 { + is_subtype(ty / 10, subtype) + } else { + false + } + } + + trait WrappedType { + /// NonNull - 1 + /// Nullable - 2 + /// List - 3 + /// + /// `[[Int]!] - >>> as WrappedType>::N = 12332` + const N: u128; + } + + impl WrappedType for i32 { + const N: u128 = 1; + } + + impl WrappedType for Option { + const N: u128 = T::N * 10 + 2; + } + + impl WrappedType for Vec { + const N: u128 = T::N * 10 + 3; + } + + impl WrappedType for String { + const N: u128 = 1; + } + + impl<'a> WrappedType for &'a str { + const N: u128 = 1; + } + + trait Type { + const NAME: &'static str; + } + + impl Type for Option { + const NAME: &'static str = T::NAME; + } + + impl Type for Vec { + const NAME: &'static str = T::NAME; + } + + impl Type for String { + const NAME: &'static str = "String"; + } + + impl<'a> Type for &'a str { + const NAME: &'static str = "String"; + } + + impl Type for i32 { + const NAME: &'static str = "Int"; + } + + trait SubTypes { + const NAMES: &'static [&'static str]; + } + + impl SubTypes for Option { + const NAMES: &'static [&'static str] = T::NAMES; + } + + impl SubTypes for Vec { + const NAMES: &'static [&'static str] = T::NAMES; + } + + impl SubTypes for String { + const NAMES: &'static [&'static str] = &[::NAME]; + } + + impl<'a> SubTypes for &'a str { + const NAMES: &'static [&'static str] = &[::NAME]; + } + + impl<'a> SubTypes for i32 { + const NAMES: &'static [&'static str] = &[::NAME]; + } + + trait IsSubtype { + fn mark() {} + } + + struct Argument(T); + + trait Field { + type Context; + type TypeInfo; + type Ret; + type ArgTypes; + const SUB_TYPES: &'static [&'static str]; + const WRAPPED_VALUE: u128; + const ARGUMENTS: &'static [(&'static str, &'static str, u128)]; + + fn call( + &self, + info: &Self::TypeInfo, + args: &::juniper::Arguments, + executor: &::juniper::Executor, + ) -> ::juniper::ExecutionResult; + } + + // #[derive(GraphQLInterface)] + // #[graphql(for(Human, Droid))] + struct Character { + id: Option, + } + + impl Type for CharacterValue { + const NAME: &'static str = "Character"; + } + + impl SubTypes for CharacterValue { + const NAMES: &'static [&'static str] = &[ + ::NAME, + ::NAME, + ::NAME, + ]; + } + + impl IsSubtype for Character {} + impl IsSubtype for Character {} + + enum CharacterEnumValue { + Human(I1), + Droid(I2), + } + + type CharacterValue = CharacterEnumValue; + + impl From for CharacterValue { + fn from(v: Human) -> Self { + Self::Human(v) + } + } + + impl From for CharacterValue { + fn from(v: Droid) -> Self { + Self::Droid(v) + } + } + + impl Field for CharacterValue { + type Context = (); + type TypeInfo = (); + type Ret = String; + type ArgTypes = (Argument,); + const SUB_TYPES: &'static [&'static str] = as SubTypes>::NAMES; + const WRAPPED_VALUE: u128 = as WrappedType>::N; + const ARGUMENTS: &'static [(&'static str, &'static str, u128)] = + &[("required", "String", 1)]; + + fn call( + &self, + info: &Self::TypeInfo, + args: &juniper::Arguments, + executor: &juniper::Executor, + ) -> juniper::ExecutionResult { + match self { + CharacterValue::Human(v) => { + let _ = >::Ret, + >>::mark; + let _ = >::ArgTypes, + >>::mark; + + const _: () = assert!(is_superset( + as SubTypes>::NAMES, + >::SUB_TYPES, + )); + const _: () = assert!(is_subtype( + as WrappedType>::N, + >::WRAPPED_VALUE, + )); + const _: () = assert!(is_valid_field_args( + >::ARGUMENTS, + >::ARGUMENTS, + )); + + <_ as Field>::call(v, info, args, executor) + } + CharacterValue::Droid(v) => { + let _ = >::Ret, + >>::mark; + let _ = >::ArgTypes, + >>::mark; + + const _: () = assert!(is_superset( + as SubTypes>::NAMES, + >::SUB_TYPES, + )); + const _: () = assert!(is_subtype( + as WrappedType>::N, + >::WRAPPED_VALUE, + )); + const _: () = assert!(is_valid_field_args( + >::ARGUMENTS, + >::ARGUMENTS, + )); + + <_ as Field>::call(v, info, args, executor) + } + } + } + } + // --------- #[derive(GraphQLObject)] @@ -399,14 +642,26 @@ mod new { home_planet: String, } + impl Type for Human { + const NAME: &'static str = "Human"; + } + + impl SubTypes for Human { + const NAMES: &'static [&'static str] = &[::NAME]; + } + impl Field for Human { type Context = (); type TypeInfo = (); type Ret = String; type ArgTypes = ( - Parameter<&'static str, { fnv1a128("required") }>, - Option>, + Argument<&'static str, { fnv1a128("required") }>, + Option>, ); + const SUB_TYPES: &'static [&'static str] = ::NAMES; + const WRAPPED_VALUE: u128 = ::N; + const ARGUMENTS: &'static [(&'static str, &'static str, u128)] = + &[("required", "String", 1), ("optional", "String", 12)]; fn call( &self, @@ -439,11 +694,23 @@ mod new { } } + impl Type for Droid { + const NAME: &'static str = "Droid"; + } + + impl SubTypes for Droid { + const NAMES: &'static [&'static str] = &[::NAME]; + } + impl Field for Droid { type Context = (); type TypeInfo = (); type Ret = &'static str; - type ArgTypes = (Parameter,); + type ArgTypes = (Argument,); + const SUB_TYPES: &'static [&'static str] = as SubTypes>::NAMES; + const WRAPPED_VALUE: u128 = as WrappedType>::N; + const ARGUMENTS: &'static [(&'static str, &'static str, u128)] = + &[("required", "String", 1), ("optional", "Int", 12)]; fn call( &self, diff --git a/integration_tests/juniper_tests/src/codegen/object_attr.rs b/integration_tests/juniper_tests/src/codegen/object_attr.rs index 9161cc55d..304ba34c5 100644 --- a/integration_tests/juniper_tests/src/codegen/object_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/object_attr.rs @@ -1901,7 +1901,7 @@ mod executor { #[graphql_object(scalar = S: ScalarValue)] impl Human { - async fn id<'e, S>(&self, executor: &'e Executor<'_, '_, (), S>) -> &'e str + async fn id<'e, 'r, 'a, S>(&self, executor: &'e Executor<'r, 'a, (), S>) -> &'e str where S: ScalarValue, { From 15e527fb564096f2c1acab11f6f2eba378c891a6 Mon Sep 17 00:00:00 2001 From: ilslv Date: Fri, 24 Dec 2021 09:51:56 +0300 Subject: [PATCH 06/53] It's alive! --- .../src/codegen/interface_attr.rs | 527 ++++-------------- juniper/src/macros/helper/mod.rs | 282 +++++++++- juniper/src/types/scalars.rs | 10 +- juniper_codegen/src/common/field/mod.rs | 107 ++++ juniper_codegen/src/derive_scalar_value.rs | 16 +- juniper_codegen/src/graphql_interface/mod.rs | 34 ++ juniper_codegen/src/graphql_object/mod.rs | 49 ++ juniper_codegen/src/graphql_union/mod.rs | 36 ++ juniper_codegen/src/impl_scalar.rs | 16 +- juniper_codegen/src/util/mod.rs | 41 ++ 10 files changed, 670 insertions(+), 448 deletions(-) diff --git a/integration_tests/juniper_tests/src/codegen/interface_attr.rs b/integration_tests/juniper_tests/src/codegen/interface_attr.rs index c2f7a3c62..6b8a32877 100644 --- a/integration_tests/juniper_tests/src/codegen/interface_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/interface_attr.rs @@ -109,7 +109,10 @@ mod new { executor: &::juniper::Executor, ) -> ::juniper::ExecutionResult<__S> { match field { - "id" => <_ as Field<__S, { fnv1a128("id") }>>::call(self, info, args, executor), + "id" => <_ as juniper::macros::helper::Field< + __S, + { juniper::macros::helper::fnv1a128("id") }, + >>::call(self, info, args, executor), _ => { return Err(::juniper::FieldError::from({ format!("Field `{}` not found on type `{}`", field, "CharacterValue",) @@ -165,12 +168,12 @@ mod new { executor: &'b ::juniper::Executor, ) -> ::juniper::BoxFuture<'b, ::juniper::ExecutionResult<__S>> { match field { - "id" => Box::pin(::juniper::futures::future::ready(<_ as Field< - __S, - { fnv1a128("id") }, - >>::call( - self, info, args, executor, - ))), + "id" => Box::pin(::juniper::futures::future::ready( + <_ as juniper::macros::helper::Field< + __S, + { juniper::macros::helper::fnv1a128("id") }, + >>::call(self, info, args, executor), + )), _ => Box::pin(async move { return Err(::juniper::FieldError::from({ format!("Field `{}` not found on type `{}`", field, "CharacterValue",) @@ -220,132 +223,30 @@ mod new { } } - impl IsSubtype for String {} - impl IsSubtype<&'static str> for String {} - impl IsSubtype for &'static str {} - impl IsSubtype<&'static str> for &'static str {} - - impl IsSubtype> for Vec where T: IsSubtype {} - impl IsSubtype> for T where T: IsSubtype {} - - impl IsSubtype<()> for () {} - impl IsSubtype<(Option>,)> for () {} - impl - IsSubtype<(Option>, Option>)> for () - { - } - impl - IsSubtype<( - Option>, - Option>, - Option>, - )> for () - { + enum CharacterEnumValue { + Human(I1), + Droid(I2), } - impl IsSubtype<(Argument,)> for (Argument,) where - T1: IsSubtype - { - } - impl - IsSubtype<(Argument, Option>)> for (Argument,) - where - T1: IsSubtype, - { - } - impl - IsSubtype<(Option>, Argument)> for (Argument,) - where - T1: IsSubtype, - { - } - impl - IsSubtype<( - Argument, - Option>, - Option>, - )> for (Argument,) - where - T1: IsSubtype, - { - } - impl - IsSubtype<( - Option>, - Argument, - Option>, - )> for (Argument,) - where - T1: IsSubtype, - { - } - impl - IsSubtype<( - Option>, - Option>, - Argument, - )> for (Argument,) - where - T1: IsSubtype, - { - } + type CharacterValue = CharacterEnumValue; - const _: () = assert!(is_subtype(::N, ::N)); - const _: () = assert!(is_subtype( - as WrappedType>::N, - ::N, - )); - const _: () = assert!(!is_subtype( - > as WrappedType>::N, - ::N, - )); - const _: () = assert!(is_subtype( - > as WrappedType>::N, - as WrappedType>::N, - )); - const _: () = assert!(is_subtype( - >>> as WrappedType>::N, - > as WrappedType>::N, - )); - - const fn fnv1a128(str: &str) -> u128 { - const FNV_OFFSET_BASIS: u128 = 0x6c62272e07bb014262b821756295c58d; - const FNV_PRIME: u128 = 0x0000000001000000000000000000013b; - - let bytes = str.as_bytes(); - let mut hash = FNV_OFFSET_BASIS; - let mut i = 0; - while i < bytes.len() { - hash ^= bytes[i] as u128; - hash = hash.wrapping_mul(FNV_PRIME); - i += 1; + impl From for CharacterValue { + fn from(v: Human) -> Self { + Self::Human(v) } - hash } - const fn str_eq(l: &str, r: &str) -> bool { - let (l, r) = (l.as_bytes(), r.as_bytes()); - - if l.len() != r.len() { - return false; - } - - let mut i = 0; - while i < l.len() { - if l[i] != r[i] { - return false; - } - i += 1; + impl From for CharacterValue { + fn from(v: Droid) -> Self { + Self::Droid(v) } - - true } const fn is_superset(superset: &[&str], set: &[&str]) -> bool { const fn find(set: &[&str], elem: &str) -> bool { let mut i = 0; while i < set.len() { - if str_eq(elem, set[i]) { + if juniper::macros::helper::str_eq(elem, set[i]) { return true; } i += 1; @@ -368,163 +269,7 @@ mod new { true } - const fn is_valid_field_args( - base_interface: &'static [(&'static str, &'static str, u128)], - implementation: &'static [(&'static str, &'static str, u128)], - ) -> bool { - const fn find( - base_interface: &'static [(&'static str, &'static str, u128)], - impl_name: &'static str, - impl_ty: &'static str, - impl_wrap_val: u128, - ) -> bool { - let mut i = 0; - while i < base_interface.len() { - let (base_name, base_ty, base_wrap_val) = base_interface[i]; - if str_eq(impl_name, base_name) { - return str_eq(base_ty, impl_ty) && impl_wrap_val == base_wrap_val; - } - i += 1; - } - false - } - - if base_interface.len() > implementation.len() { - return false; - } - - let mut i = 0; - let mut successfully_implemented_fields = 0; - while i < implementation.len() { - let (impl_name, impl_ty, impl_wrap_val) = implementation[i]; - if find(base_interface, impl_name, impl_ty, impl_wrap_val) { - successfully_implemented_fields += 1; - } else if impl_wrap_val % 10 != 2 { - // Not an optional field. - return false; - } - i += 1; - } - - successfully_implemented_fields == base_interface.len() - } - - const fn is_subtype(ty: u128, subtype: u128) -> bool { - let ty_current = ty % 10; - let subtype_current = subtype % 10; - - if ty_current == subtype_current { - if ty_current == 1 { - true - } else { - is_subtype(ty / 10, subtype / 10) - } - } else if ty_current == 2 { - is_subtype(ty / 10, subtype) - } else { - false - } - } - - trait WrappedType { - /// NonNull - 1 - /// Nullable - 2 - /// List - 3 - /// - /// `[[Int]!] - >>> as WrappedType>::N = 12332` - const N: u128; - } - - impl WrappedType for i32 { - const N: u128 = 1; - } - - impl WrappedType for Option { - const N: u128 = T::N * 10 + 2; - } - - impl WrappedType for Vec { - const N: u128 = T::N * 10 + 3; - } - - impl WrappedType for String { - const N: u128 = 1; - } - - impl<'a> WrappedType for &'a str { - const N: u128 = 1; - } - - trait Type { - const NAME: &'static str; - } - - impl Type for Option { - const NAME: &'static str = T::NAME; - } - - impl Type for Vec { - const NAME: &'static str = T::NAME; - } - - impl Type for String { - const NAME: &'static str = "String"; - } - - impl<'a> Type for &'a str { - const NAME: &'static str = "String"; - } - - impl Type for i32 { - const NAME: &'static str = "Int"; - } - - trait SubTypes { - const NAMES: &'static [&'static str]; - } - - impl SubTypes for Option { - const NAMES: &'static [&'static str] = T::NAMES; - } - - impl SubTypes for Vec { - const NAMES: &'static [&'static str] = T::NAMES; - } - - impl SubTypes for String { - const NAMES: &'static [&'static str] = &[::NAME]; - } - - impl<'a> SubTypes for &'a str { - const NAMES: &'static [&'static str] = &[::NAME]; - } - - impl<'a> SubTypes for i32 { - const NAMES: &'static [&'static str] = &[::NAME]; - } - - trait IsSubtype { - fn mark() {} - } - - struct Argument(T); - - trait Field { - type Context; - type TypeInfo; - type Ret; - type ArgTypes; - const SUB_TYPES: &'static [&'static str]; - const WRAPPED_VALUE: u128; - const ARGUMENTS: &'static [(&'static str, &'static str, u128)]; - - fn call( - &self, - info: &Self::TypeInfo, - args: &::juniper::Arguments, - executor: &::juniper::Executor, - ) -> ::juniper::ExecutionResult; - } + // -------------- // #[derive(GraphQLInterface)] // #[graphql(for(Human, Droid))] @@ -532,49 +277,45 @@ mod new { id: Option, } - impl Type for CharacterValue { - const NAME: &'static str = "Character"; + impl juniper::macros::helper::BaseType for CharacterValue + where + S: ScalarValue, + { + const NAME: juniper::macros::helper::Type = "Character"; } - impl SubTypes for CharacterValue { - const NAMES: &'static [&'static str] = &[ - ::NAME, - ::NAME, - ::NAME, + impl juniper::macros::helper::BaseSubTypes for CharacterValue + where + S: ScalarValue, + { + const NAMES: juniper::macros::helper::Types = &[ + >::NAME, + >::NAME, + >::NAME, ]; } - impl IsSubtype for Character {} - impl IsSubtype for Character {} - - enum CharacterEnumValue { - Human(I1), - Droid(I2), + impl juniper::macros::helper::WrappedType for CharacterValue { + const VALUE: u128 = 1; } - type CharacterValue = CharacterEnumValue; - - impl From for CharacterValue { - fn from(v: Human) -> Self { - Self::Human(v) - } - } - - impl From for CharacterValue { - fn from(v: Droid) -> Self { - Self::Droid(v) - } - } - - impl Field for CharacterValue { + impl + juniper::macros::helper::Field + for CharacterValue + { type Context = (); type TypeInfo = (); - type Ret = String; - type ArgTypes = (Argument,); - const SUB_TYPES: &'static [&'static str] = as SubTypes>::NAMES; - const WRAPPED_VALUE: u128 = as WrappedType>::N; - const ARGUMENTS: &'static [(&'static str, &'static str, u128)] = - &[("required", "String", 1)]; + const TYPE: juniper::macros::helper::Type = + as juniper::macros::helper::BaseType>::NAME; + const SUB_TYPES: juniper::macros::helper::Types = + as juniper::macros::helper::BaseSubTypes>::NAMES; + const WRAPPED_VALUE: juniper::macros::helper::WrappedValue = + as juniper::macros::helper::WrappedType>::VALUE; + const ARGUMENTS: &'static [( + juniper::macros::helper::Name, + juniper::macros::helper::Type, + juniper::macros::helper::WrappedValue, + )] = &[("required", "String", 1)]; fn call( &self, @@ -584,50 +325,62 @@ mod new { ) -> juniper::ExecutionResult { match self { CharacterValue::Human(v) => { - let _ = >::Ret, - >>::mark; - let _ = >::ArgTypes, - >>::mark; - - const _: () = assert!(is_superset( - as SubTypes>::NAMES, - >::SUB_TYPES, - )); - const _: () = assert!(is_subtype( - as WrappedType>::N, - >::WRAPPED_VALUE, + const _: () = assert!(juniper::macros::helper::is_subtype( + as juniper::macros::helper::BaseSubTypes>::NAMES, + as juniper::macros::helper::WrappedType>::VALUE, + >::TYPE, + >::WRAPPED_VALUE, )); - const _: () = assert!(is_valid_field_args( - >::ARGUMENTS, - >::ARGUMENTS, + const _: () = assert!(juniper::macros::helper::is_valid_field_args( + >::ARGUMENTS, + >::ARGUMENTS, )); - <_ as Field>::call(v, info, args, executor) + <_ as juniper::macros::helper::Field< + S, + { juniper::macros::helper::fnv1a128("id") }, + >>::call(v, info, args, executor) } CharacterValue::Droid(v) => { - let _ = >::Ret, - >>::mark; - let _ = >::ArgTypes, - >>::mark; - - const _: () = assert!(is_superset( - as SubTypes>::NAMES, - >::SUB_TYPES, + const _: () = assert!(juniper::macros::helper::is_subtype( + as juniper::macros::helper::BaseSubTypes>::NAMES, + as juniper::macros::helper::WrappedType>::VALUE, + >::TYPE, + >::WRAPPED_VALUE, )); - const _: () = assert!(is_subtype( - as WrappedType>::N, - >::WRAPPED_VALUE, - )); - const _: () = assert!(is_valid_field_args( - >::ARGUMENTS, - >::ARGUMENTS, + const _: () = assert!(juniper::macros::helper::is_valid_field_args( + >::ARGUMENTS, + >::ARGUMENTS, )); - <_ as Field>::call(v, info, args, executor) + <_ as juniper::macros::helper::Field< + S, + { juniper::macros::helper::fnv1a128("id") }, + >>::call(v, info, args, executor) } } } @@ -635,46 +388,19 @@ mod new { // --------- - #[derive(GraphQLObject)] - #[graphql(impl = CharacterValue)] struct Human { id: String, home_planet: String, } - impl Type for Human { - const NAME: &'static str = "Human"; - } - - impl SubTypes for Human { - const NAMES: &'static [&'static str] = &[::NAME]; - } - - impl Field for Human { - type Context = (); - type TypeInfo = (); - type Ret = String; - type ArgTypes = ( - Argument<&'static str, { fnv1a128("required") }>, - Option>, - ); - const SUB_TYPES: &'static [&'static str] = ::NAMES; - const WRAPPED_VALUE: u128 = ::N; - const ARGUMENTS: &'static [(&'static str, &'static str, u128)] = - &[("required", "String", 1), ("optional", "String", 12)]; - - fn call( - &self, - info: &Self::TypeInfo, - _: &juniper::Arguments, - executor: &juniper::Executor, - ) -> juniper::ExecutionResult { - let res = &self.id; + #[graphql_object(impl = CharacterValue)] + impl Human { + fn id(&self, _required: String, _optional: Option) -> &str { + &self.id + } - ::juniper::IntoResolvable::into(res, executor.context()).and_then(|res| match res { - Some((ctx, r)) => executor.replaced_context(ctx).resolve_with_ctx(info, &r), - None => Ok(::juniper::Value::null()), - }) + fn home_planet(&self) -> &str { + &self.home_planet } } @@ -685,7 +411,7 @@ mod new { #[graphql_object(impl = CharacterValue)] impl Droid { - fn id(&self) -> &str { + fn id(&self, _required: String) -> &str { &self.id } @@ -694,39 +420,6 @@ mod new { } } - impl Type for Droid { - const NAME: &'static str = "Droid"; - } - - impl SubTypes for Droid { - const NAMES: &'static [&'static str] = &[::NAME]; - } - - impl Field for Droid { - type Context = (); - type TypeInfo = (); - type Ret = &'static str; - type ArgTypes = (Argument,); - const SUB_TYPES: &'static [&'static str] = as SubTypes>::NAMES; - const WRAPPED_VALUE: u128 = as WrappedType>::N; - const ARGUMENTS: &'static [(&'static str, &'static str, u128)] = - &[("required", "String", 1), ("optional", "Int", 12)]; - - fn call( - &self, - info: &Self::TypeInfo, - _: &juniper::Arguments, - executor: &juniper::Executor, - ) -> juniper::ExecutionResult { - let res = self.id(); - - ::juniper::IntoResolvable::into(res, executor.context()).and_then(|res| match res { - Some((ctx, r)) => executor.replaced_context(ctx).resolve_with_ctx(info, &r), - None => Ok(::juniper::Value::null()), - }) - } - } - // ----- #[derive(Clone, Copy)] @@ -760,7 +453,7 @@ mod new { const DOC: &str = r#"{ character { ... on Human { - humanId: id + humanId: id(required: "test") homePlanet } } @@ -782,7 +475,7 @@ mod new { const DOC: &str = r#"{ character { ... on Droid { - droidId: id + droidId: id(required: "test") primaryFunction } } diff --git a/juniper/src/macros/helper/mod.rs b/juniper/src/macros/helper/mod.rs index 9d5cc0fc1..284f9c78a 100644 --- a/juniper/src/macros/helper/mod.rs +++ b/juniper/src/macros/helper/mod.rs @@ -6,7 +6,10 @@ use std::{fmt, rc::Rc, sync::Arc}; use futures::future::{self, BoxFuture}; -use crate::{DefaultScalarValue, DynGraphQLValue, DynGraphQLValueAsync, FieldError, ScalarValue}; +use crate::{ + Arguments, DefaultScalarValue, DynGraphQLValue, DynGraphQLValueAsync, ExecutionResult, + Executor, FieldError, Nullable, ScalarValue, +}; /// Conversion of a [`GraphQLValue`] to its [trait object][1]. /// @@ -84,11 +87,17 @@ where Box::pin(future::err(err_unnamed_type(name))) } +pub type Type = &'static str; +pub type Types = &'static [Type]; +pub type Name = &'static str; +pub type FieldName = u128; +pub type WrappedValue = u128; + /// Non-cryptographic hash with good dispersion to use [`str`](prim@str) in /// const generics. See [spec] for more info. /// /// [spec]: https://datatracker.ietf.org/doc/html/draft-eastlake-fnv-17.html -pub const fn fnv1a128(str: &str) -> u128 { +pub const fn fnv1a128(str: Name) -> u128 { const FNV_OFFSET_BASIS: u128 = 0x6c62272e07bb014262b821756295c58d; const FNV_PRIME: u128 = 0x0000000001000000000000000000013b; @@ -103,44 +112,273 @@ pub const fn fnv1a128(str: &str) -> u128 { hash } +pub const fn str_eq(l: &str, r: &str) -> bool { + let (l, r) = (l.as_bytes(), r.as_bytes()); + + if l.len() != r.len() { + return false; + } + + let mut i = 0; + while i < l.len() { + if l[i] != r[i] { + return false; + } + i += 1; + } + + true +} + +pub const fn is_valid_field_args( + base_interface: &[(Name, Type, WrappedValue)], + implementation: &[(Name, Type, WrappedValue)], +) -> bool { + const fn find( + base_interface: &[(Name, Type, WrappedValue)], + impl_name: Name, + impl_ty: Type, + impl_wrap_val: WrappedValue, + ) -> bool { + let mut i = 0; + while i < base_interface.len() { + let (base_name, base_ty, base_wrap_val) = base_interface[i]; + if str_eq(impl_name, base_name) { + return str_eq(base_ty, impl_ty) && impl_wrap_val == base_wrap_val; + } + i += 1; + } + false + } + + if base_interface.len() > implementation.len() { + return false; + } + + let mut i = 0; + let mut successfully_implemented_fields = 0; + while i < implementation.len() { + let (impl_name, impl_ty, impl_wrap_val) = implementation[i]; + if find(base_interface, impl_name, impl_ty, impl_wrap_val) { + successfully_implemented_fields += 1; + } else if impl_wrap_val % 10 != 2 { + // Not an optional field. + return false; + } + i += 1; + } + + successfully_implemented_fields == base_interface.len() +} + +pub const fn can_be_subtype(ty: WrappedValue, subtype: WrappedValue) -> bool { + let ty_current = ty % 10; + let subtype_current = subtype % 10; + + if ty_current == subtype_current { + if ty_current == 1 { + true + } else { + can_be_subtype(ty / 10, subtype / 10) + } + } else if ty_current == 2 { + can_be_subtype(ty / 10, subtype) + } else { + false + } +} + +pub const fn exists(val: Type, arr: Types) -> bool { + let mut i = 0; + while i < arr.len() { + if str_eq(val, arr[i]) { + return true; + } + i += 1; + } + false +} + +pub const fn is_subtype( + possible_subtypes: Types, + wrapped_type: WrappedValue, + subtype: Type, + wrapped_subtype: WrappedValue, +) -> bool { + exists(subtype, possible_subtypes) && can_be_subtype(wrapped_type, wrapped_subtype) +} + +/// TODO +pub trait BaseType { + const NAME: Type; +} + +impl<'a, S, T: BaseType + ?Sized> BaseType for &'a T { + const NAME: Type = T::NAME; +} + +// TODO: Reconsider +impl, Ctx> BaseType for (Ctx, T) { + const NAME: Type = T::NAME; +} + +impl> BaseType for Option { + const NAME: Type = T::NAME; +} + +impl> BaseType for Nullable { + const NAME: Type = T::NAME; +} + +// TODO: Should Err be trait bounded somehow? +impl, E> BaseType for Result { + const NAME: Type = T::NAME; +} + +impl> BaseType for Vec { + const NAME: Type = T::NAME; +} + +impl> BaseType for [T] { + const NAME: Type = T::NAME; +} + +impl, const N: usize> BaseType for [T; N] { + const NAME: Type = T::NAME; +} + +impl + ?Sized> BaseType for Box { + const NAME: Type = T::NAME; +} + +impl + ?Sized> BaseType for Arc { + const NAME: Type = T::NAME; +} + +impl + ?Sized> BaseType for Rc { + const NAME: Type = T::NAME; +} + /// TODO -pub trait Type { - const NAME: &'static str; +pub trait BaseSubTypes { + const NAMES: Types; +} + +impl<'a, S, T: BaseSubTypes + ?Sized> BaseSubTypes for &'a T { + const NAMES: Types = T::NAMES; +} + +// TODO: Reconsider +impl, Ctx> BaseSubTypes for (Ctx, T) { + const NAMES: Types = T::NAMES; +} + +impl> BaseSubTypes for Option { + const NAMES: Types = T::NAMES; +} + +impl> BaseSubTypes for Nullable { + const NAMES: Types = T::NAMES; +} + +// TODO: Should Err be trait bounded somehow? +impl, E> BaseSubTypes for Result { + const NAMES: Types = T::NAMES; +} + +impl> BaseSubTypes for Vec { + const NAMES: Types = T::NAMES; } -impl<'a, S, T: Type + ?Sized> Type for &'a T { - const NAME: &'static str = T::NAME; +impl> BaseSubTypes for [T] { + const NAMES: Types = T::NAMES; } -impl + ?Sized> Type for Box { - const NAME: &'static str = T::NAME; +impl, const N: usize> BaseSubTypes for [T; N] { + const NAMES: Types = T::NAMES; } -impl + ?Sized> Type for Arc { - const NAME: &'static str = T::NAME; +impl + ?Sized> BaseSubTypes for Box { + const NAMES: Types = T::NAMES; } -impl + ?Sized> Type for Rc { - const NAME: &'static str = T::NAME; +impl + ?Sized> BaseSubTypes for Arc { + const NAMES: Types = T::NAMES; +} + +impl + ?Sized> BaseSubTypes for Rc { + const NAMES: Types = T::NAMES; } /// TODO -pub trait SubTypes { - const NAMES: &'static [&'static str]; +pub trait WrappedType { + /// NonNull - 1 + /// Nullable - 2 + /// List - 3 + /// + /// `[[Int]!] - >>> as WrappedType>::N = 12332` + const VALUE: u128; +} + +// TODO: Reconsider +impl, Ctx> WrappedType for (Ctx, T) { + const VALUE: u128 = T::VALUE; +} + +impl> WrappedType for Option { + const VALUE: u128 = T::VALUE * 10 + 2; +} + +impl> WrappedType for Nullable { + const VALUE: u128 = T::VALUE * 10 + 2; +} + +// TODO: Should Err be trait bounded somehow? +// And should `VALUE` be `T::VALUE` or `T::VALUE * 10 + 2`? +impl, E> WrappedType for Result { + const VALUE: u128 = T::VALUE; } -impl<'a, S, T: SubTypes + ?Sized> SubTypes for &'a T { - const NAMES: &'static [&'static str] = T::NAMES; +impl> WrappedType for Vec { + const VALUE: u128 = T::VALUE * 10 + 3; } -impl + ?Sized> SubTypes for Box { - const NAMES: &'static [&'static str] = T::NAMES; +impl> WrappedType for [T] { + const VALUE: u128 = T::VALUE * 10 + 3; } -impl + ?Sized> SubTypes for Arc { - const NAMES: &'static [&'static str] = T::NAMES; +impl, const N: usize> WrappedType for [T; N] { + const VALUE: u128 = T::VALUE * 10 + 3; } -impl + ?Sized> SubTypes for Rc { - const NAMES: &'static [&'static str] = T::NAMES; +impl<'a, S, T: WrappedType + ?Sized> WrappedType for &'a T { + const VALUE: u128 = T::VALUE; +} + +impl + ?Sized> WrappedType for Box { + const VALUE: u128 = T::VALUE; +} + +impl + ?Sized> WrappedType for Arc { + const VALUE: u128 = T::VALUE; +} + +impl + ?Sized> WrappedType for Rc { + const VALUE: u128 = T::VALUE; +} + +pub trait Field { + type Context; + type TypeInfo; + const TYPE: Type; + const SUB_TYPES: Types; + const WRAPPED_VALUE: WrappedValue; + const ARGUMENTS: &'static [(Name, Type, WrappedValue)]; + + fn call( + &self, + info: &Self::TypeInfo, + args: &Arguments, + executor: &Executor, + ) -> ExecutionResult; } diff --git a/juniper/src/types/scalars.rs b/juniper/src/types/scalars.rs index 443240251..5d84bd6f8 100644 --- a/juniper/src/types/scalars.rs +++ b/juniper/src/types/scalars.rs @@ -202,12 +202,16 @@ where }) } -impl crate::macros::helper::Type for str { +impl crate::macros::helper::WrappedType for str { + const VALUE: u128 = 1; +} + +impl crate::macros::helper::BaseType for str { const NAME: &'static str = "String"; } -impl crate::macros::helper::SubTypes for str { - const NAMES: &'static [&'static str] = &[::NAME]; +impl crate::macros::helper::BaseSubTypes for str { + const NAMES: &'static [&'static str] = &[>::NAME]; } impl GraphQLType for str diff --git a/juniper_codegen/src/common/field/mod.rs b/juniper_codegen/src/common/field/mod.rs index 0437f97f3..72361cf72 100644 --- a/juniper_codegen/src/common/field/mod.rs +++ b/juniper_codegen/src/common/field/mod.rs @@ -441,6 +441,113 @@ impl Definition { }) } + /// TODO + #[must_use] + pub(crate) fn impl_field( + &self, + impl_ty: &syn::Type, + impl_generics: &TokenStream, + where_clause: Option<&syn::WhereClause>, + scalar: &scalar::Type, + trait_ty: Option<&syn::Type>, + context: &syn::Type, + ) -> Option { + if self.is_async { + return None; + } + + let (name, ty, mut res_ty, ident) = + (&self.name, self.ty.clone(), self.ty.clone(), &self.ident); + + // if matches!( + // scalar, + // scalar::Type::ExplicitGeneric(_) | scalar::Type::ImplicitGeneric(_), + // ) { + // impl_generics + // .params + // .push(parse_quote!( #scalar: ::juniper::ScalarValue )); + // } + // + // let (impl_gens, ty_gens, where_clause) = impl_generics.split_for_impl(); + + let res = if self.is_method() { + let args = self + .arguments + .as_ref() + .unwrap() + .iter() + .map(|arg| arg.method_resolve_field_tokens(scalar, false)); + + let rcv = self.has_receiver.then(|| { + quote! { self, } + }); + + if trait_ty.is_some() { + quote! { ::#ident(#rcv #( #args ),*) } + } else { + quote! { Self::#ident(#rcv #( #args ),*) } + } + } else { + res_ty = parse_quote! { _ }; + quote! { &self.#ident } + }; + + let arguments = self + .arguments + .as_ref() + .iter() + .flat_map(|vec| vec.iter()) + .filter_map(|arg| match arg { + MethodArgument::Regular(arg) => { + let (name, ty) = (&arg.name, &arg.ty); + + Some(quote! {( + #name, + <#ty as ::juniper::macros::helper::BaseType<#scalar>>::NAME, + <#ty as ::juniper::macros::helper::WrappedType<#scalar>>::VALUE, + )}) + } + MethodArgument::Executor | MethodArgument::Context(_) => None, + }) + .collect::>(); + + let resolving_code = gen::sync_resolving_code(); + + Some(quote! { + #[automatically_derived] + impl #impl_generics ::juniper::macros::helper::Field< + #scalar, + { ::juniper::macros::helper::fnv1a128(#name) } + > for #impl_ty + #where_clause + { + type Context = #context; + type TypeInfo = (); + const TYPE: ::juniper::macros::helper::Type = + <#ty as ::juniper::macros::helper::BaseType<#scalar>>::NAME; + const SUB_TYPES: ::juniper::macros::helper::Types = + <#ty as ::juniper::macros::helper::BaseSubTypes<#scalar>>::NAMES; + const WRAPPED_VALUE: juniper::macros::helper::WrappedValue = + <#ty as ::juniper::macros::helper::WrappedType<#scalar>>::VALUE; + const ARGUMENTS: &'static [( + ::juniper::macros::helper::Name, + ::juniper::macros::helper::Type, + ::juniper::macros::helper::WrappedValue, + )] = &[#(#arguments,)*]; + + fn call( + &self, + info: &Self::TypeInfo, + args: &::juniper::Arguments<#scalar>, + executor: &::juniper::Executor, + ) -> ::juniper::ExecutionResult<#scalar> { + let res: #res_ty = #res; + #resolving_code + } + } + }) + } + /// Returns generated code for the /// [`GraphQLValueAsync::resolve_field_async`][0] method, which resolves /// this [GraphQL field][1] asynchronously. diff --git a/juniper_codegen/src/derive_scalar_value.rs b/juniper_codegen/src/derive_scalar_value.rs index 3ea24dcec..db8e3f3f6 100644 --- a/juniper_codegen/src/derive_scalar_value.rs +++ b/juniper_codegen/src/derive_scalar_value.rs @@ -225,13 +225,23 @@ fn impl_scalar_struct( where #scalar: ::juniper::ScalarValue, { } - impl#impl_generics ::juniper::macros::helper::Type<#scalar> for #ident { + impl#impl_generics ::juniper::macros::helper::BaseType<#scalar> for #ident + where #scalar: ::juniper::ScalarValue, + { const NAME: &'static str = #name; } - impl#impl_generics ::juniper::macros::helper::SubTypes<#scalar> for #ident { + impl#impl_generics ::juniper::macros::helper::BaseSubTypes<#scalar> for #ident + where #scalar: ::juniper::ScalarValue, + { const NAMES: &'static [&'static str] = - &[>::NAME]; + &[>::NAME]; + } + + impl#impl_generics ::juniper::macros::helper::WrappedType<#scalar> for #ident + where #scalar: ::juniper::ScalarValue, + { + const VALUE: u128 = 1; } ); diff --git a/juniper_codegen/src/graphql_interface/mod.rs b/juniper_codegen/src/graphql_interface/mod.rs index 0e4ff0665..89e24b75f 100644 --- a/juniper_codegen/src/graphql_interface/mod.rs +++ b/juniper_codegen/src/graphql_interface/mod.rs @@ -441,6 +441,7 @@ impl ToTokens for Definition { self.impl_graphql_type_tokens().to_tokens(into); self.impl_graphql_value_tokens().to_tokens(into); self.impl_graphql_value_async_tokens().to_tokens(into); + self.impl_traits_for_const_assertions().to_tokens(into); } } @@ -713,6 +714,36 @@ impl Definition { } } } + + /// TODO + #[must_use] + pub(crate) fn impl_traits_for_const_assertions(&self) -> TokenStream { + let scalar = &self.scalar; + let name = &self.name; + let (impl_generics, where_clause) = self.ty.impl_generics(false); + let ty = self.ty.ty_tokens(); + + quote! { + impl #impl_generics ::juniper::macros::helper::BaseType<#scalar> for #ty + #where_clause + { + const NAME: ::juniper::macros::helper::Type = #name; + } + + impl #impl_generics ::juniper::macros::helper::BaseSubTypes<#scalar> for #ty + #where_clause + { + const NAMES: ::juniper::macros::helper::Types = + &[>::NAME]; + } + + impl #impl_generics ::juniper::macros::helper::WrappedType<#scalar> for #ty + #where_clause + { + const VALUE: ::juniper::macros::helper::WrappedValue = 1; + } + } + } } /// Representation of custom downcast into an [`Implementer`] from a @@ -902,6 +933,7 @@ impl Implementer { /// code generation. /// /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces +#[derive(Debug)] struct EnumType { /// Name of this [`EnumType`] to generate it with. ident: syn::Ident, @@ -1405,6 +1437,7 @@ impl EnumType { /// /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces /// [2]: https://doc.rust-lang.org/reference/types/trait-object.html +#[derive(Debug)] struct TraitObjectType { /// Name of this [`TraitObjectType`] to generate it with. ident: syn::Ident, @@ -1637,6 +1670,7 @@ impl ToTokens for TraitObjectType { /// type for code generation. /// /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces +#[derive(Debug)] enum Type { /// [GraphQL interface][1] type implementation as Rust enum. /// diff --git a/juniper_codegen/src/graphql_object/mod.rs b/juniper_codegen/src/graphql_object/mod.rs index 171e00a3d..28bffa938 100644 --- a/juniper_codegen/src/graphql_object/mod.rs +++ b/juniper_codegen/src/graphql_object/mod.rs @@ -361,6 +361,42 @@ impl Definition { } } + /// TODO + #[must_use] + pub(crate) fn impl_traits_for_const_assertions(&self) -> TokenStream { + let scalar = &self.scalar; + let name = &self.name; + let (impl_generics, where_clause) = self.impl_generics(false); + let ty = &self.ty; + + quote! { + #[automatically_derived] + impl#impl_generics ::juniper::macros::helper::BaseType<#scalar> + for #ty + #where_clause + { + const NAME: ::juniper::macros::helper::Type = #name; + } + + #[automatically_derived] + impl#impl_generics ::juniper::macros::helper::BaseSubTypes<#scalar> + for #ty + #where_clause + { + const NAMES: ::juniper::macros::helper::Types = + &[>::NAME]; + } + + #[automatically_derived] + impl#impl_generics ::juniper::macros::helper::WrappedType<#scalar> + for #ty + #where_clause + { + const VALUE: ::juniper::macros::helper::WrappedValue = 1; + } + } + } + /// Returns generated code implementing [`GraphQLType`] trait for this /// [GraphQL object][1]. /// @@ -439,6 +475,7 @@ impl ToTokens for Definition { self.impl_graphql_value_tokens().to_tokens(into); self.impl_graphql_value_async_tokens().to_tokens(into); self.impl_as_dyn_graphql_value_tokens().to_tokens(into); + self.impl_traits_for_const_assertions().to_tokens(into); } } @@ -494,6 +531,16 @@ impl Definition { .fields .iter() .filter_map(|f| f.method_resolve_field_tokens(scalar, None)); + let field_impls = self.fields.iter().filter_map(|f| { + f.impl_field( + ty, + &impl_generics, + where_clause.as_ref(), + scalar, + None, + context, + ) + }); let async_fields_err = { let names = self .fields @@ -543,6 +590,8 @@ impl Definition { #name.to_string() } } + + #(#field_impls)* } } diff --git a/juniper_codegen/src/graphql_union/mod.rs b/juniper_codegen/src/graphql_union/mod.rs index 00ba528e5..9883aeed1 100644 --- a/juniper_codegen/src/graphql_union/mod.rs +++ b/juniper_codegen/src/graphql_union/mod.rs @@ -320,6 +320,7 @@ impl ToTokens for Definition { self.impl_graphql_type_tokens().to_tokens(into); self.impl_graphql_value_tokens().to_tokens(into); self.impl_graphql_value_async_tokens().to_tokens(into); + self.impl_traits_for_const_assertions().to_tokens(into); } } @@ -609,6 +610,41 @@ impl Definition { } } } + + /// TODO + #[must_use] + pub(crate) fn impl_traits_for_const_assertions(&self) -> TokenStream { + let scalar = &self.scalar; + let name = &self.name; + let (impl_generics, ty, where_clause) = self.impl_generics(false); + + quote! { + #[automatically_derived] + impl#impl_generics ::juniper::macros::helper::BaseType<#scalar> + for #ty + #where_clause + { + const NAME: ::juniper::macros::helper::Type = #name; + } + + #[automatically_derived] + impl#impl_generics ::juniper::macros::helper::BaseSubTypes<#scalar> + for #ty + #where_clause + { + const NAMES: ::juniper::macros::helper::Types = + &[>::NAME]; + } + + #[automatically_derived] + impl#impl_generics ::juniper::macros::helper::WrappedType<#scalar> + for #ty + #where_clause + { + const VALUE: ::juniper::macros::helper::WrappedValue = 1; + } + } + } } /// Definition of [GraphQL union][1] variant for code generation. diff --git a/juniper_codegen/src/impl_scalar.rs b/juniper_codegen/src/impl_scalar.rs index d4676350c..7bdbf2d3e 100644 --- a/juniper_codegen/src/impl_scalar.rs +++ b/juniper_codegen/src/impl_scalar.rs @@ -326,13 +326,23 @@ pub fn build_scalar( } } - impl#generic_type_decl ::juniper::macros::helper::Type<#generic_type> for #impl_for_type { + impl#generic_type_decl ::juniper::macros::helper::BaseType<#generic_type> for #impl_for_type + #generic_type_bound + { const NAME: &'static str = #name; } - impl#generic_type_decl ::juniper::macros::helper::SubTypes<#generic_type> for #impl_for_type { + impl#generic_type_decl ::juniper::macros::helper::BaseSubTypes<#generic_type> for #impl_for_type + #generic_type_bound + { const NAMES: &'static [&'static str] = - &[>::NAME]; + &[>::NAME]; + } + + impl#generic_type_decl ::juniper::macros::helper::WrappedType<#generic_type> for #impl_for_type + #generic_type_bound + { + const VALUE: u128 = 1; } ); diff --git a/juniper_codegen/src/util/mod.rs b/juniper_codegen/src/util/mod.rs index d8002f512..df132e56a 100644 --- a/juniper_codegen/src/util/mod.rs +++ b/juniper_codegen/src/util/mod.rs @@ -881,6 +881,25 @@ impl GraphQLTypeDefiniton { } } } + + impl#impl_generics ::juniper::macros::helper::BaseType<#scalar> for #ty + #where_clause + { + const NAME: &'static str = #name; + } + + impl#impl_generics ::juniper::macros::helper::BaseSubTypes<#scalar> for #ty + #where_clause + { + const NAMES: &'static [&'static str] = + &[>::NAME]; + } + + impl#impl_generics ::juniper::macros::helper::WrappedType<#scalar> for #ty + #where_clause + { + const VALUE: u128 = 1; + } ); if !self.no_async { @@ -1128,6 +1147,28 @@ impl GraphQLTypeDefiniton { ].into_iter().collect()) } } + + impl#impl_generics ::juniper::macros::helper::BaseType<#scalar> + for #ty #type_generics_tokens + #where_clause + { + const NAME: &'static str = #name; + } + + impl#impl_generics ::juniper::macros::helper::BaseSubTypes<#scalar> + for #ty #type_generics_tokens + #where_clause + { + const NAMES: &'static [&'static str] = + &[>::NAME]; + } + + impl#impl_generics ::juniper::macros::helper::WrappedType<#scalar> + for #ty #type_generics_tokens + #where_clause + { + const VALUE: u128 = 1; + } ); if !self.no_async { From 7a78f9eab4676fdb979881dec4f4aba552f63802 Mon Sep 17 00:00:00 2001 From: ilslv Date: Fri, 24 Dec 2021 10:27:52 +0300 Subject: [PATCH 07/53] Use Field inside object's field resolver --- juniper_codegen/src/common/field/mod.rs | 11 ----------- juniper_codegen/src/graphql_object/mod.rs | 18 ++++++++++++++---- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/juniper_codegen/src/common/field/mod.rs b/juniper_codegen/src/common/field/mod.rs index 72361cf72..e538417b3 100644 --- a/juniper_codegen/src/common/field/mod.rs +++ b/juniper_codegen/src/common/field/mod.rs @@ -459,17 +459,6 @@ impl Definition { let (name, ty, mut res_ty, ident) = (&self.name, self.ty.clone(), self.ty.clone(), &self.ident); - // if matches!( - // scalar, - // scalar::Type::ExplicitGeneric(_) | scalar::Type::ImplicitGeneric(_), - // ) { - // impl_generics - // .params - // .push(parse_quote!( #scalar: ::juniper::ScalarValue )); - // } - // - // let (impl_gens, ty_gens, where_clause) = impl_generics.split_for_impl(); - let res = if self.is_method() { let args = self .arguments diff --git a/juniper_codegen/src/graphql_object/mod.rs b/juniper_codegen/src/graphql_object/mod.rs index 28bffa938..6fe408a8b 100644 --- a/juniper_codegen/src/graphql_object/mod.rs +++ b/juniper_codegen/src/graphql_object/mod.rs @@ -527,10 +527,20 @@ impl Definition { let name = &self.name; - let fields_resolvers = self - .fields - .iter() - .filter_map(|f| f.method_resolve_field_tokens(scalar, None)); + let fields_resolvers = self.fields.iter().filter_map(|f| { + (!f.is_async).then(|| { + let name = &f.name; + quote! { + #name => { + ::juniper::macros::helper::Field::< + #scalar, + { ::juniper::macros::helper::fnv1a128(#name) } + >::call(self, info, args, executor) + }, + } + }) + }); + let field_impls = self.fields.iter().filter_map(|f| { f.impl_field( ty, From 5c2087c159ef8697a8ebba279f2f8326062b85bb Mon Sep 17 00:00:00 2001 From: ilslv Date: Fri, 24 Dec 2021 12:57:11 +0300 Subject: [PATCH 08/53] Add async object fields --- juniper/src/macros/helper/mod.rs | 16 +++++++ juniper_codegen/src/common/field/mod.rs | 55 +++++++++++++++++------ juniper_codegen/src/graphql_object/mod.rs | 34 +++++++++++--- juniper_codegen/src/graphql_union/mod.rs | 7 ++- 4 files changed, 91 insertions(+), 21 deletions(-) diff --git a/juniper/src/macros/helper/mod.rs b/juniper/src/macros/helper/mod.rs index 284f9c78a..8b05ffbc7 100644 --- a/juniper/src/macros/helper/mod.rs +++ b/juniper/src/macros/helper/mod.rs @@ -382,3 +382,19 @@ pub trait Field { executor: &Executor, ) -> ExecutionResult; } + +pub trait AsyncField { + type Context; + type TypeInfo; + const TYPE: Type; + const SUB_TYPES: Types; + const WRAPPED_VALUE: WrappedValue; + const ARGUMENTS: &'static [(Name, Type, WrappedValue)]; + + fn call<'b>( + &'b self, + info: &'b Self::TypeInfo, + args: &'b Arguments, + executor: &'b Executor, + ) -> BoxFuture<'b, ExecutionResult>; +} diff --git a/juniper_codegen/src/common/field/mod.rs b/juniper_codegen/src/common/field/mod.rs index e538417b3..6dba69e32 100644 --- a/juniper_codegen/src/common/field/mod.rs +++ b/juniper_codegen/src/common/field/mod.rs @@ -451,21 +451,22 @@ impl Definition { scalar: &scalar::Type, trait_ty: Option<&syn::Type>, context: &syn::Type, + for_async: bool, ) -> Option { - if self.is_async { + if !for_async && self.is_async { return None; } let (name, ty, mut res_ty, ident) = (&self.name, self.ty.clone(), self.ty.clone(), &self.ident); - let res = if self.is_method() { + let mut res = if self.is_method() { let args = self .arguments .as_ref() .unwrap() .iter() - .map(|arg| arg.method_resolve_field_tokens(scalar, false)); + .map(|arg| arg.method_resolve_field_tokens(scalar, for_async)); let rcv = self.has_receiver.then(|| { quote! { self, } @@ -480,6 +481,9 @@ impl Definition { res_ty = parse_quote! { _ }; quote! { &self.#ident } }; + if for_async && !self.is_async { + res = quote! { ::juniper::futures::future::ready(#res) }; + } let arguments = self .arguments @@ -500,11 +504,42 @@ impl Definition { }) .collect::>(); - let resolving_code = gen::sync_resolving_code(); + let (call, trait_name) = if for_async { + let resolving_code = gen::async_resolving_code(Some(&res_ty)); + let call = quote! { + fn call<'b>( + &'b self, + info: &'b Self::TypeInfo, + args: &'b ::juniper::Arguments<#scalar>, + executor: &'b ::juniper::Executor, + ) -> ::juniper::BoxFuture<'b, ::juniper::ExecutionResult<#scalar>> { + let fut = #res; + #resolving_code + } + }; + + (call, quote! { AsyncField }) + } else { + let resolving_code = gen::sync_resolving_code(); + let call = quote! { + fn call( + &self, + info: &Self::TypeInfo, + args: &::juniper::Arguments<#scalar>, + executor: &::juniper::Executor, + ) -> ::juniper::ExecutionResult<#scalar> { + let res: #res_ty = #res; + #resolving_code + } + }; + + (call, quote! { Field }) + }; Some(quote! { + #[allow(deprecated, non_snake_case)] #[automatically_derived] - impl #impl_generics ::juniper::macros::helper::Field< + impl #impl_generics ::juniper::macros::helper::#trait_name< #scalar, { ::juniper::macros::helper::fnv1a128(#name) } > for #impl_ty @@ -524,15 +559,7 @@ impl Definition { ::juniper::macros::helper::WrappedValue, )] = &[#(#arguments,)*]; - fn call( - &self, - info: &Self::TypeInfo, - args: &::juniper::Arguments<#scalar>, - executor: &::juniper::Executor, - ) -> ::juniper::ExecutionResult<#scalar> { - let res: #res_ty = #res; - #resolving_code - } + #call } }) } diff --git a/juniper_codegen/src/graphql_object/mod.rs b/juniper_codegen/src/graphql_object/mod.rs index 6fe408a8b..86940f9f0 100644 --- a/juniper_codegen/src/graphql_object/mod.rs +++ b/juniper_codegen/src/graphql_object/mod.rs @@ -536,7 +536,7 @@ impl Definition { #scalar, { ::juniper::macros::helper::fnv1a128(#name) } >::call(self, info, args, executor) - }, + } } }) }); @@ -549,6 +549,7 @@ impl Definition { scalar, None, context, + false, ) }); let async_fields_err = { @@ -613,15 +614,36 @@ impl Definition { #[must_use] fn impl_graphql_value_async_tokens(&self) -> TokenStream { let scalar = &self.scalar; + let context = &self.context; let (impl_generics, where_clause) = self.impl_generics(true); let ty = &self.ty; let ty_name = ty.to_token_stream().to_string(); - let fields_resolvers = self - .fields - .iter() - .map(|f| f.method_resolve_field_async_tokens(scalar, None)); + let fields_resolvers = self.fields.iter().map(|f| { + let name = &f.name; + quote! { + #name => { + ::juniper::macros::helper::AsyncField::< + #scalar, + { ::juniper::macros::helper::fnv1a128(#name) } + >::call(self, info, args, executor) + } + } + }); + + let field_impls = self.fields.iter().filter_map(|f| { + f.impl_field( + ty, + &impl_generics, + where_clause.as_ref(), + scalar, + None, + context, + true, + ) + }); + let no_field_err = field::Definition::method_resolve_field_err_no_field_tokens(scalar, &ty_name); @@ -643,6 +665,8 @@ impl Definition { } } } + + #(#field_impls)* } } diff --git a/juniper_codegen/src/graphql_union/mod.rs b/juniper_codegen/src/graphql_union/mod.rs index 9883aeed1..952889782 100644 --- a/juniper_codegen/src/graphql_union/mod.rs +++ b/juniper_codegen/src/graphql_union/mod.rs @@ -616,6 +616,7 @@ impl Definition { pub(crate) fn impl_traits_for_const_assertions(&self) -> TokenStream { let scalar = &self.scalar; let name = &self.name; + let variants = self.variants.iter().map(|var| &var.ty); let (impl_generics, ty, where_clause) = self.impl_generics(false); quote! { @@ -632,8 +633,10 @@ impl Definition { for #ty #where_clause { - const NAMES: ::juniper::macros::helper::Types = - &[>::NAME]; + const NAMES: ::juniper::macros::helper::Types = &[ + >::NAME, + #(<#variants as ::juniper::macros::helper::BaseType<#scalar>>::NAME),* + ]; } #[automatically_derived] From e9c6046b4f724512ca179e64f15bd08248947843 Mon Sep 17 00:00:00 2001 From: ilslv Date: Fri, 24 Dec 2021 15:58:15 +0300 Subject: [PATCH 09/53] Fix --- juniper_codegen/src/graphql_interface/mod.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/juniper_codegen/src/graphql_interface/mod.rs b/juniper_codegen/src/graphql_interface/mod.rs index 89e24b75f..7ca048e69 100644 --- a/juniper_codegen/src/graphql_interface/mod.rs +++ b/juniper_codegen/src/graphql_interface/mod.rs @@ -722,6 +722,7 @@ impl Definition { let name = &self.name; let (impl_generics, where_clause) = self.ty.impl_generics(false); let ty = self.ty.ty_tokens(); + let implementors = self.implementers.iter().map(|i| &i.ty); quote! { impl #impl_generics ::juniper::macros::helper::BaseType<#scalar> for #ty @@ -733,8 +734,10 @@ impl Definition { impl #impl_generics ::juniper::macros::helper::BaseSubTypes<#scalar> for #ty #where_clause { - const NAMES: ::juniper::macros::helper::Types = - &[>::NAME]; + const NAMES: ::juniper::macros::helper::Types = &[ + >::NAME, + #(<#implementors as ::juniper::macros::helper::BaseType<#scalar>>::NAME),* + ]; } impl #impl_generics ::juniper::macros::helper::WrappedType<#scalar> for #ty From 0739c3e654ba5a560f176792d2586a09a8a0df20 Mon Sep 17 00:00:00 2001 From: ilslv Date: Mon, 27 Dec 2021 11:59:30 +0300 Subject: [PATCH 10/53] Fix Wrapped type for tuple with context --- juniper/src/macros/helper/mod.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/juniper/src/macros/helper/mod.rs b/juniper/src/macros/helper/mod.rs index 8b05ffbc7..f5b4619aa 100644 --- a/juniper/src/macros/helper/mod.rs +++ b/juniper/src/macros/helper/mod.rs @@ -320,8 +320,11 @@ pub trait WrappedType { const VALUE: u128; } -// TODO: Reconsider -impl, Ctx> WrappedType for (Ctx, T) { +impl<'a, S, T: WrappedType> WrappedType for (&'a T::Context, T) +where + S: ScalarValue, + T: crate::GraphQLValue, +{ const VALUE: u128 = T::VALUE; } From 263d544db8bde9f5db43bc09cc92ad51fe9d5a9a Mon Sep 17 00:00:00 2001 From: ilslv Date: Tue, 28 Dec 2021 11:04:21 +0300 Subject: [PATCH 11/53] WIP (Make graphql_interface_new attribute work) --- .../src/codegen/interface_attr.rs | 29 +- .../juniper_tests/src/codegen/mod.rs | 1 + .../src/codegen/new_interface.rs | 139 +++ juniper/src/lib.rs | 5 +- juniper_codegen/src/graphql_interface/attr.rs | 220 +++- juniper_codegen/src/graphql_interface/mod.rs | 1 + juniper_codegen/src/graphql_interface/new.rs | 1028 +++++++++++++++++ juniper_codegen/src/lib.rs | 8 + 8 files changed, 1401 insertions(+), 30 deletions(-) create mode 100644 integration_tests/juniper_tests/src/codegen/new_interface.rs create mode 100644 juniper_codegen/src/graphql_interface/new.rs diff --git a/integration_tests/juniper_tests/src/codegen/interface_attr.rs b/integration_tests/juniper_tests/src/codegen/interface_attr.rs index 6b8a32877..bc8ea3081 100644 --- a/integration_tests/juniper_tests/src/codegen/interface_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/interface_attr.rs @@ -34,6 +34,8 @@ where mod new { use super::*; + // ------- Implemented ------- + #[automatically_derived] impl<__S> ::juniper::marker::GraphQLInterface<__S> for CharacterValue where @@ -242,33 +244,6 @@ mod new { } } - const fn is_superset(superset: &[&str], set: &[&str]) -> bool { - const fn find(set: &[&str], elem: &str) -> bool { - let mut i = 0; - while i < set.len() { - if juniper::macros::helper::str_eq(elem, set[i]) { - return true; - } - i += 1; - } - false - } - - if superset.len() < set.len() { - return false; - } - - let mut i = 0; - while i < set.len() { - if !find(superset, set[i]) { - return false; - } - i += 1; - } - - true - } - // -------------- // #[derive(GraphQLInterface)] diff --git a/integration_tests/juniper_tests/src/codegen/mod.rs b/integration_tests/juniper_tests/src/codegen/mod.rs index 6348a66c8..1abc91e4d 100644 --- a/integration_tests/juniper_tests/src/codegen/mod.rs +++ b/integration_tests/juniper_tests/src/codegen/mod.rs @@ -4,6 +4,7 @@ mod derive_object_with_raw_idents; mod derive_scalar; mod impl_scalar; mod interface_attr; +mod new_interface; mod object_attr; mod object_derive; mod scalar_value_transparent; diff --git a/integration_tests/juniper_tests/src/codegen/new_interface.rs b/integration_tests/juniper_tests/src/codegen/new_interface.rs new file mode 100644 index 000000000..c1f7579a4 --- /dev/null +++ b/integration_tests/juniper_tests/src/codegen/new_interface.rs @@ -0,0 +1,139 @@ +use juniper::{ + execute, graphql_interface, graphql_interface_new, graphql_object, graphql_value, graphql_vars, + DefaultScalarValue, EmptyMutation, EmptySubscription, Executor, FieldError, FieldResult, + GraphQLInputObject, GraphQLObject, GraphQLType, IntoFieldError, RootNode, ScalarValue, +}; + +#[graphql_interface_new(for = [Human, Droid])] +trait Character { + fn id(&self, required: String) -> String; +} + +struct Human { + id: String, + home_planet: String, +} + +#[graphql_object(impl = CharacterValue)] +impl Human { + fn id(&self, _required: String, _optional: Option) -> &str { + &self.id + } + + fn home_planet(&self) -> &str { + &self.home_planet + } +} + +struct Droid { + id: String, + primary_function: String, +} + +#[graphql_object(impl = CharacterValue)] +impl Droid { + fn id(&self, _required: String) -> &str { + &self.id + } + + fn primary_function(&self) -> &str { + &self.primary_function + } +} + +// ----- + +#[derive(Clone, Copy)] +enum QueryRoot { + Human, + Droid, +} + +#[graphql_object(scalar = S: ScalarValue + Send + Sync)] +impl QueryRoot { + fn character(&self) -> CharacterValue { + match self { + Self::Human => Human { + id: "human-32".to_string(), + home_planet: "earth".to_string(), + } + .into(), + Self::Droid => Droid { + id: "droid-99".to_string(), + primary_function: "run".to_string(), + } + .into(), + } + } +} + +// -------------- + +fn schema<'q, C, Q>(query_root: Q) -> RootNode<'q, Q, EmptyMutation, EmptySubscription> +where + Q: GraphQLType + 'q, +{ + RootNode::new( + query_root, + EmptyMutation::::new(), + EmptySubscription::::new(), + ) +} + +fn schema_with_scalar<'q, S, C, Q>( + query_root: Q, +) -> RootNode<'q, Q, EmptyMutation, EmptySubscription, S> +where + Q: GraphQLType + 'q, + S: ScalarValue + 'q, +{ + RootNode::new_with_scalar_value( + query_root, + EmptyMutation::::new(), + EmptySubscription::::new(), + ) +} + +#[tokio::test] +async fn enum_resolves_human() { + const DOC: &str = r#"{ + character { + ... on Human { + humanId: id(required: "test") + homePlanet + } + } + }"#; + + let schema = schema(QueryRoot::Human); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"character": {"humanId": "human-32", "homePlanet": "earth"}}), + vec![], + )), + ); +} + +#[tokio::test] +async fn enum_resolves_droid() { + const DOC: &str = r#"{ + character { + ... on Droid { + droidId: id(required: "test") + primaryFunction + } + } + }"#; + + let schema = schema(QueryRoot::Droid); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}), + vec![], + )), + ); +} diff --git a/juniper/src/lib.rs b/juniper/src/lib.rs index 406879726..c4ce64163 100644 --- a/juniper/src/lib.rs +++ b/juniper/src/lib.rs @@ -114,8 +114,9 @@ pub use futures::future::{BoxFuture, LocalBoxFuture}; // This allows users to just depend on juniper and get the derive // functionality automatically. pub use juniper_codegen::{ - graphql_interface, graphql_object, graphql_scalar, graphql_subscription, graphql_union, - GraphQLEnum, GraphQLInputObject, GraphQLObject, GraphQLScalarValue, GraphQLUnion, + graphql_interface, graphql_interface_new, graphql_object, graphql_scalar, graphql_subscription, + graphql_union, GraphQLEnum, GraphQLInputObject, GraphQLObject, GraphQLScalarValue, + GraphQLUnion, }; #[doc(hidden)] diff --git a/juniper_codegen/src/graphql_interface/attr.rs b/juniper_codegen/src/graphql_interface/attr.rs index 2aedbb2ad..475ea3eaf 100644 --- a/juniper_codegen/src/graphql_interface/attr.rs +++ b/juniper_codegen/src/graphql_interface/attr.rs @@ -17,7 +17,7 @@ use crate::{ }; use super::{ - inject_async_trait, Definition, EnumType, ImplAttr, Implementer, ImplementerDowncast, + inject_async_trait, new, Definition, EnumType, ImplAttr, Implementer, ImplementerDowncast, TraitAttr, TraitObjectType, Type, }; @@ -45,6 +45,21 @@ pub fn expand(attr_args: TokenStream, body: TokenStream) -> syn::Result syn::Result { + if let Ok(mut ast) = syn::parse2::(body.clone()) { + let trait_attrs = parse::attr::unite(("graphql_interface_new", &attr_args), &ast.attrs); + ast.attrs = parse::attr::strip("graphql_interface_new", ast.attrs); + return expand_on_trait_new(trait_attrs, ast); + } + + Err(syn::Error::new( + Span::call_site(), + "#[graphql_interface_new] attribute is applicable to trait definitions and trait \ + implementations only", + )) +} + /// Expands `#[graphql_interface]` macro placed on trait definition. fn expand_on_trait( attrs: Vec, @@ -245,6 +260,209 @@ fn expand_on_trait( }) } +/// Expands `#[graphql_interface_new]` macro placed on trait definition. +fn expand_on_trait_new( + attrs: Vec, + mut ast: syn::ItemTrait, +) -> syn::Result { + let attr = new::TraitAttr::from_attrs("graphql_interface_new", &attrs)?; + + let trait_ident = &ast.ident; + let trait_span = ast.span(); + + let name = attr + .name + .clone() + .map(SpanContainer::into_inner) + .unwrap_or_else(|| trait_ident.unraw().to_string()); + if !attr.is_internal && name.starts_with("__") { + ERR.no_double_underscore( + attr.name + .as_ref() + .map(SpanContainer::span_ident) + .unwrap_or_else(|| trait_ident.span()), + ); + } + + let scalar = scalar::Type::parse(attr.scalar.as_deref(), &ast.generics); + + // let mut implementers: Vec<_> = attr + // .implementers + // .iter() + // .map(|ty| Implementer { + // ty: ty.as_ref().clone(), + // downcast: None, + // context: None, + // scalar: scalar.clone(), + // }) + // .collect(); + // for (ty, downcast) in &attr.external_downcasts { + // match implementers.iter_mut().find(|i| &i.ty == ty) { + // Some(impler) => { + // impler.downcast = Some(ImplementerDowncast::External { + // path: downcast.inner().clone(), + // }); + // } + // None => err_only_implementer_downcast(&downcast.span_joined()), + // } + // } + + proc_macro_error::abort_if_dirty(); + + let renaming = attr + .rename_fields + .as_deref() + .copied() + .unwrap_or(RenameRule::CamelCase); + + let mut fields = vec![]; + for item in &mut ast.items { + if let syn::TraitItem::Method(m) = item { + match TraitMethod::parse(m, &renaming) { + Some(TraitMethod::Field(f)) => fields.push(f), + Some(TraitMethod::Downcast(_d)) => { + unimplemented!(); + // match implementers.iter_mut().find(|i| i.ty == d.ty) { + // Some(impler) => { + // if let Some(external) = &impler.downcast { + // err_duplicate_downcast(m, external, &impler.ty); + // } else { + // impler.downcast = d.downcast; + // impler.context = d.context; + // } + // } + // None => err_only_implementer_downcast(&m.sig), + // } + } + _ => {} + } + } + } + + proc_macro_error::abort_if_dirty(); + + if fields.is_empty() { + ERR.emit_custom(trait_span, "must have at least one field"); + } + if !field::all_different(&fields) { + ERR.emit_custom(trait_span, "must have a different name for each field"); + } + + proc_macro_error::abort_if_dirty(); + + let context = attr + .context + .as_deref() + .cloned() + .or_else(|| { + fields.iter().find_map(|f| { + f.arguments.as_ref().and_then(|f| { + f.iter() + .find_map(field::MethodArgument::context_ty) + .cloned() + }) + }) + }) + // .or_else(|| { + // implementers + // .iter() + // .find_map(|impler| impler.context.as_ref()) + // .cloned() + // }) + .unwrap_or_else(|| parse_quote! { () }); + + // let is_trait_object = attr.r#dyn.is_some(); + + let is_async_trait = attr.asyncness.is_some() + || ast + .items + .iter() + .find_map(|item| match item { + syn::TraitItem::Method(m) => m.sig.asyncness, + _ => None, + }) + .is_some(); + let has_default_async_methods = ast.items.iter().any(|item| match item { + syn::TraitItem::Method(m) => m.sig.asyncness.and(m.default.as_ref()).is_some(), + _ => false, + }); + + // let ty = if is_trait_object { + // Type::TraitObject(Box::new(TraitObjectType::new( + // &ast, + // &attr, + // scalar.clone(), + // context.clone(), + // ))) + // } else { + // Type::Enum(Box::new(EnumType::new( + // &ast, + // &attr, + // &implementers, + // scalar.clone(), + // ))) + // }; + + let description = attr.description.as_ref().map(|c| c.inner().clone()); + let generated_code = new::Definition { + attrs: attr, + ident: trait_ident.clone(), + vis: ast.vis.clone(), + trait_generics: ast.generics.clone(), + name, + description, + + context, + scalar: scalar.clone(), + + fields, + }; + + // Attach the `juniper::AsDynGraphQLValue` on top of the trait if dynamic dispatch is used. + // if is_trait_object { + // ast.attrs.push(parse_quote! { + // #[allow(unused_qualifications, clippy::type_repetition_in_bounds)] + // }); + // + // let scalar_ty = scalar.generic_ty(); + // if !scalar.is_explicit_generic() { + // let default_ty = scalar.default_ty(); + // ast.generics + // .params + // .push(parse_quote! { #scalar_ty = #default_ty }); + // } + // ast.generics + // .make_where_clause() + // .predicates + // .push(parse_quote! { #scalar_ty: ::juniper::ScalarValue }); + // ast.supertraits + // .push(parse_quote! { ::juniper::AsDynGraphQLValue<#scalar_ty> }); + // } + + if is_async_trait { + if has_default_async_methods { + // Hack for object safety. See details: https://docs.rs/async-trait/#dyn-traits + ast.supertraits.push(parse_quote! { Sync }); + } + inject_async_trait( + &mut ast.attrs, + ast.items.iter_mut().filter_map(|i| { + if let syn::TraitItem::Method(m) = i { + Some(&mut m.sig) + } else { + None + } + }), + &ast.generics, + ); + } + + Ok(quote! { + #ast + #generated_code + }) +} + /// Expands `#[graphql_interface]` macro placed on a trait implementation block. fn expand_on_impl(attrs: Vec, mut ast: syn::ItemImpl) -> syn::Result { let attr = ImplAttr::from_attrs("graphql_interface", &attrs)?; diff --git a/juniper_codegen/src/graphql_interface/mod.rs b/juniper_codegen/src/graphql_interface/mod.rs index 7ca048e69..2671b0ca7 100644 --- a/juniper_codegen/src/graphql_interface/mod.rs +++ b/juniper_codegen/src/graphql_interface/mod.rs @@ -3,6 +3,7 @@ //! [1]: https://spec.graphql.org/June2018/#sec-Interfaces pub mod attr; +pub mod new; use std::{ collections::{HashMap, HashSet}, diff --git a/juniper_codegen/src/graphql_interface/new.rs b/juniper_codegen/src/graphql_interface/new.rs new file mode 100644 index 000000000..2e0b733dd --- /dev/null +++ b/juniper_codegen/src/graphql_interface/new.rs @@ -0,0 +1,1028 @@ +//! Code generation for [GraphQL interface][1]. +//! +//! [1]: https://spec.graphql.org/June2018/#sec-Interfaces + +use std::{ + any::TypeId, + collections::{HashMap, HashSet}, + convert::TryInto as _, +}; + +use proc_macro2::TokenStream; +use quote::{format_ident, quote, ToTokens, TokenStreamExt as _}; +use syn::{ + ext::IdentExt as _, + parse::{Parse, ParseStream}, + parse_quote, + spanned::Spanned as _, + token, +}; + +use crate::{ + common::{ + field, gen, + parse::{ + attr::{err, OptionExt as _}, + GenericsExt as _, ParseBufferExt as _, + }, + scalar, + }, + util::{filter_attrs, get_doc_comment, span_container::SpanContainer, RenameRule}, +}; + +/// Available arguments behind `#[graphql_interface]` attribute placed on a +/// trait definition, when generating code for [GraphQL interface][1] type. +/// +/// [1]: https://spec.graphql.org/June2018/#sec-Interfaces +#[derive(Debug, Default)] +pub(crate) struct TraitAttr { + /// Explicitly specified name of [GraphQL interface][1] type. + /// + /// If [`None`], then Rust trait name is used by default. + /// + /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces + pub(crate) name: Option>, + + /// Explicitly specified [description][2] of [GraphQL interface][1] type. + /// + /// If [`None`], then Rust doc comment is used as [description][2], if any. + /// + /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces + /// [2]: https://spec.graphql.org/June2018/#sec-Descriptions + pub(crate) description: Option>, + + /// Explicitly specified identifier of the enum Rust type behind the trait, + /// being an actual implementation of a [GraphQL interface][1] type. + /// + /// If [`None`], then `{trait_name}Value` identifier will be used. + /// + /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces + pub(crate) r#enum: Option>, + + /// Explicitly specified Rust types of [GraphQL objects][2] implementing + /// this [GraphQL interface][1] type. + /// + /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces + /// [2]: https://spec.graphql.org/June2018/#sec-Objects + pub(crate) implementers: HashSet>, + + /// Explicitly specified type of [`Context`] to use for resolving this + /// [GraphQL interface][1] type with. + /// + /// If [`None`], then unit type `()` is assumed as a type of [`Context`]. + /// + /// [`Context`]: juniper::Context + /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces + pub(crate) context: Option>, + + /// Explicitly specified type (or type parameter with its bounds) of + /// [`ScalarValue`] to resolve this [GraphQL interface][1] type with. + /// + /// If [`None`], then generated code will be generic over any + /// [`ScalarValue`] type, which, in turn, requires all [interface][1] + /// implementers to be generic over any [`ScalarValue`] type too. That's why + /// this type should be specified only if one of the implementers implements + /// [`GraphQLType`] in a non-generic way over [`ScalarValue`] type. + /// + /// [`GraphQLType`]: juniper::GraphQLType + /// [`ScalarValue`]: juniper::ScalarValue + /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces + pub(crate) scalar: Option>, + + /// Explicitly specified marker indicating that the Rust trait should be + /// transformed into [`async_trait`]. + /// + /// If [`None`], then trait will be transformed into [`async_trait`] only if + /// it contains async methods. + pub(crate) asyncness: Option>, + + /// Explicitly specified [`RenameRule`] for all fields of this + /// [GraphQL interface][1] type. + /// + /// If [`None`] then the default rule will be [`RenameRule::CamelCase`]. + /// + /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces + pub(crate) rename_fields: Option>, + + /// Indicator whether the generated code is intended to be used only inside + /// the [`juniper`] library. + pub(crate) is_internal: bool, +} + +impl Parse for TraitAttr { + fn parse(input: ParseStream<'_>) -> syn::Result { + let mut out = Self::default(); + while !input.is_empty() { + let ident = input.parse_any_ident()?; + match ident.to_string().as_str() { + "name" => { + input.parse::()?; + let name = input.parse::()?; + out.name + .replace(SpanContainer::new( + ident.span(), + Some(name.span()), + name.value(), + )) + .none_or_else(|_| err::dup_arg(&ident))? + } + "desc" | "description" => { + input.parse::()?; + let desc = input.parse::()?; + out.description + .replace(SpanContainer::new( + ident.span(), + Some(desc.span()), + desc.value(), + )) + .none_or_else(|_| err::dup_arg(&ident))? + } + "ctx" | "context" | "Context" => { + input.parse::()?; + let ctx = input.parse::()?; + out.context + .replace(SpanContainer::new(ident.span(), Some(ctx.span()), ctx)) + .none_or_else(|_| err::dup_arg(&ident))? + } + "scalar" | "Scalar" | "ScalarValue" => { + input.parse::()?; + let scl = input.parse::()?; + out.scalar + .replace(SpanContainer::new(ident.span(), Some(scl.span()), scl)) + .none_or_else(|_| err::dup_arg(&ident))? + } + "for" | "implementers" => { + input.parse::()?; + for impler in input.parse_maybe_wrapped_and_punctuated::< + syn::TypePath, token::Bracket, token::Comma, + >()? { + let impler_span = impler.span(); + out + .implementers + .replace(SpanContainer::new(ident.span(), Some(impler_span), impler)) + .none_or_else(|_| err::dup_arg(impler_span))?; + } + } + "enum" => { + input.parse::()?; + let alias = input.parse::()?; + out.r#enum + .replace(SpanContainer::new(ident.span(), Some(alias.span()), alias)) + .none_or_else(|_| err::dup_arg(&ident))? + } + "async" => { + let span = ident.span(); + out.asyncness + .replace(SpanContainer::new(span, Some(span), ident)) + .none_or_else(|_| err::dup_arg(span))?; + } + "rename_all" => { + input.parse::()?; + let val = input.parse::()?; + out.rename_fields + .replace(SpanContainer::new( + ident.span(), + Some(val.span()), + val.try_into()?, + )) + .none_or_else(|_| err::dup_arg(&ident))?; + } + "internal" => { + out.is_internal = true; + } + name => { + return Err(err::unknown_arg(&ident, name)); + } + } + input.try_parse::()?; + } + Ok(out) + } +} + +impl TraitAttr { + /// Tries to merge two [`TraitAttr`]s into a single one, reporting about + /// duplicates, if any. + fn try_merge(self, mut another: Self) -> syn::Result { + Ok(Self { + name: try_merge_opt!(name: self, another), + description: try_merge_opt!(description: self, another), + context: try_merge_opt!(context: self, another), + scalar: try_merge_opt!(scalar: self, another), + implementers: try_merge_hashset!(implementers: self, another => span_joined), + r#enum: try_merge_opt!(r#enum: self, another), + asyncness: try_merge_opt!(asyncness: self, another), + rename_fields: try_merge_opt!(rename_fields: self, another), + is_internal: self.is_internal || another.is_internal, + }) + } + + /// Parses [`TraitAttr`] from the given multiple `name`d [`syn::Attribute`]s + /// placed on a trait definition. + pub(crate) fn from_attrs(name: &str, attrs: &[syn::Attribute]) -> syn::Result { + let mut attr = filter_attrs(name, attrs) + .map(|attr| attr.parse_args()) + .try_fold(Self::default(), |prev, curr| prev.try_merge(curr?))?; + + if attr.description.is_none() { + attr.description = get_doc_comment(attrs); + } + + Ok(attr) + } + + /// TODO + fn enum_alias_ident(&self, trait_name: &syn::Ident) -> SpanContainer { + self.r#enum.clone().unwrap_or_else(|| { + SpanContainer::new( + trait_name.span(), + Some(trait_name.span()), + format_ident!("{}Value", trait_name.to_string()), + ) + }) + } +} + +/// Definition of [GraphQL interface][1] for code generation. +/// +/// [1]: https://spec.graphql.org/June2018/#sec-Interfaces +pub(crate) struct Definition { + /// TODO + pub(crate) attrs: TraitAttr, + + pub(crate) ident: syn::Ident, + + pub(crate) vis: syn::Visibility, + + pub(crate) trait_generics: syn::Generics, + + // /// Rust type that this [GraphQL interface][1] is represented with. + // /// + // /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces + // ty: syn::Type, + /// Name of this [GraphQL interface][1] in GraphQL schema. + /// + /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces + pub(crate) name: String, + + /// Description of this [GraphQL interface][1] to put into GraphQL schema. + /// + /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces + pub(crate) description: Option, + + /// Rust type of [`Context`] to generate [`GraphQLType`] implementation with + /// for this [GraphQL interface][1]. + /// + /// [`GraphQLType`]: juniper::GraphQLType + /// [`Context`]: juniper::Context + /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces + pub(crate) context: syn::Type, + + /// [`ScalarValue`] parametrization to generate [`GraphQLType`] + /// implementation with for this [GraphQL interface][1]. + /// + /// [`GraphQLType`]: juniper::GraphQLType + /// [`ScalarValue`]: juniper::ScalarValue + /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces + pub(crate) scalar: scalar::Type, + + /// Defined [GraphQL fields][2] of this [GraphQL interface][1]. + /// + /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces + /// [2]: https://spec.graphql.org/June2018/#sec-Language.Fields + pub(crate) fields: Vec, + // /// Defined [`Implementer`]s of this [GraphQL interface][1]. + // /// + // /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces + // implementers: Vec, +} + +impl ToTokens for Definition { + fn to_tokens(&self, into: &mut TokenStream) { + self.generate_enum().to_tokens(into); + self.impl_graphql_interface_tokens().to_tokens(into); + self.impl_output_type_tokens().to_tokens(into); + self.impl_graphql_type_tokens().to_tokens(into); + self.impl_graphql_value_tokens().to_tokens(into); + self.impl_graphql_value_async_tokens().to_tokens(into); + self.impl_traits_for_const_assertions().to_tokens(into); + self.impl_fields(false).to_tokens(into); + self.impl_fields(true).to_tokens(into); + } +} + +impl Definition { + fn generate_enum(&self) -> TokenStream { + let vis = &self.vis; + let trait_gens = &self.trait_generics; + let (trait_impl_gens, trait_ty_gens, trait_where_clause) = + self.trait_generics.split_for_impl(); + + let ty_params = self.trait_generics.params.iter().map(|p| { + let ty = match p { + syn::GenericParam::Type(ty) => { + let ident = &ty.ident; + quote! { #ident } + } + syn::GenericParam::Lifetime(lt) => { + let lifetime = <.lifetime; + quote! { &#lifetime () } + } + syn::GenericParam::Const(_) => unimplemented!(), + }; + quote! { + ::std::marker::PhantomData<::std::sync::atomic::AtomicPtr>> + } + }); + let phantom_variant = + (!self.trait_generics.params.is_empty()).then(|| quote! { __Phantom(#(#ty_params,)*) }); + + let alias_ident = self.attrs.enum_alias_ident(&self.ident); + let enum_ident = self.attrs.r#enum.as_ref().map_or_else( + || format_ident!("{}ValueEnum", self.ident.to_string()), + |c| format_ident!("{}Enum", c.inner().to_string()), + ); + + let variants_generics = self + .attrs + .implementers + .iter() + .enumerate() + .map(|(id, ty)| format_ident!("I{}", id)); + + let variants_idents = self + .attrs + .implementers + .iter() + .filter_map(|ty| ty.path.segments.last().map(|seg| &seg.ident)); + + let enum_generics = self + .trait_generics + .params + .iter() + .map(ToTokens::to_token_stream) + .chain(variants_generics.clone().map(ToTokens::into_token_stream)); + let enum_to_alias_generics = self + .trait_generics + .params + .iter() + .map(ToTokens::to_token_stream) + .chain( + self.attrs + .implementers + .iter() + .map(ToTokens::to_token_stream), + ); + + let from_impls = self + .attrs + .implementers + .iter() + .zip(variants_idents.clone()) + .map(|(ty, ident)| { + quote! { + impl#trait_impl_gens ::std::convert::From<#ty> for #alias_ident#trait_ty_gens + #trait_where_clause + { + fn from(v: #ty) -> Self { + Self::#ident(v) + } + } + } + }); + + quote! { + #[derive(Clone, Copy, Debug)] + #vis enum #enum_ident<#(#enum_generics,)*> { + #(#variants_idents(#variants_generics)),* + #phantom_variant + } + + #vis type #alias_ident#trait_gens = + #enum_ident<#(#enum_to_alias_generics,)*>; + + #(#from_impls)* + } + } + + /// Returns generated code implementing [`GraphQLInterface`] trait for this + /// [GraphQL interface][1]. + /// + /// [`GraphQLInterface`]: juniper::GraphQLInterface + /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces + #[must_use] + fn impl_graphql_interface_tokens(&self) -> TokenStream { + let scalar = &self.scalar; + + let gens = self.impl_generics(false); + let (impl_generics, _, where_clause) = gens.split_for_impl(); + let (_, ty_generics, _) = self.trait_generics.split_for_impl(); + let ty = self.attrs.enum_alias_ident(&self.ident); + + let impler_tys = self.attrs.implementers.iter().collect::>(); + let all_implers_unique = (impler_tys.len() > 1).then(|| { + quote! { ::juniper::sa::assert_type_ne_all!(#( #impler_tys ),*); } + }); + + quote! { + #[automatically_derived] + impl#impl_generics ::juniper::marker::GraphQLInterface<#scalar> for + #ty#ty_generics + #where_clause + { + fn mark() { + #all_implers_unique + #( <#impler_tys as ::juniper::marker::GraphQLObject<#scalar>>::mark(); )* + } + } + } + } + + /// Returns generated code implementing [`marker::IsOutputType`] trait for + /// this [GraphQL interface][1]. + /// + /// [`marker::IsOutputType`]: juniper::marker::IsOutputType + /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces + #[must_use] + fn impl_output_type_tokens(&self) -> TokenStream { + let scalar = &self.scalar; + + let generics = self.impl_generics(false); + let (impl_generics, _, where_clause) = generics.split_for_impl(); + let (_, ty_generics, _) = self.trait_generics.split_for_impl(); + let ty = self.attrs.enum_alias_ident(&self.ident); + + let fields_marks = self + .fields + .iter() + .map(|f| f.method_mark_tokens(false, scalar)); + + let impler_tys = self.attrs.implementers.iter(); + + quote! { + #[automatically_derived] + impl#impl_generics ::juniper::marker::IsOutputType<#scalar> for + #ty#ty_generics + #where_clause + { + fn mark() { + #( #fields_marks )* + #( <#impler_tys as ::juniper::marker::IsOutputType<#scalar>>::mark(); )* + } + } + } + } + + /// Returns generated code implementing [`GraphQLType`] trait for this + /// [GraphQL interface][1]. + /// + /// [`GraphQLType`]: juniper::GraphQLType + /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces + #[must_use] + fn impl_graphql_type_tokens(&self) -> TokenStream { + let scalar = &self.scalar; + + let generics = self.impl_generics(false); + let (impl_generics, _, where_clause) = generics.split_for_impl(); + let (_, ty_generics, _) = self.trait_generics.split_for_impl(); + let ty = self.attrs.enum_alias_ident(&self.ident); + + let name = &self.name; + let description = self + .description + .as_ref() + .map(|desc| quote! { .description(#desc) }); + + // Sorting is required to preserve/guarantee the order of implementers registered in schema. + let mut impler_tys = self.attrs.implementers.iter().collect::>(); + impler_tys.sort_unstable_by(|a, b| { + let (a, b) = (quote!(#a).to_string(), quote!(#b).to_string()); + a.cmp(&b) + }); + + let fields_meta = self.fields.iter().map(|f| f.method_meta_tokens(None)); + + quote! { + #[automatically_derived] + impl#impl_generics ::juniper::GraphQLType<#scalar> + for #ty#ty_generics + #where_clause + { + fn name(_ : &Self::TypeInfo) -> Option<&'static str> { + Some(#name) + } + + fn meta<'r>( + info: &Self::TypeInfo, + registry: &mut ::juniper::Registry<'r, #scalar> + ) -> ::juniper::meta::MetaType<'r, #scalar> + where #scalar: 'r, + { + // Ensure all implementer types are registered. + #( let _ = registry.get_type::<#impler_tys>(info); )* + + let fields = [ + #( #fields_meta, )* + ]; + registry.build_interface_type::<#ty>(info, &fields) + #description + .into_meta() + } + } + } + } + + /// Returns generated code implementing [`GraphQLValue`] trait for this + /// [GraphQL interface][1]. + /// + /// [`GraphQLValue`]: juniper::GraphQLValue + /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces + #[must_use] + fn impl_graphql_value_tokens(&self) -> TokenStream { + let scalar = &self.scalar; + let context = &self.context; + + let generics = self.impl_generics(false); + let (impl_generics, _, where_clause) = generics.split_for_impl(); + let (_, ty_generics, _) = self.trait_generics.split_for_impl(); + let ty = self.attrs.enum_alias_ident(&self.ident); + let trait_name = &self.name; + + let fields_resolvers = self.fields.iter().filter_map(|f| { + (!f.is_async).then(|| { + let name = &f.name; + quote! { + #name => { + ::juniper::macros::helper::Field::< + #scalar, + { ::juniper::macros::helper::fnv1a128(#name) } + >::call(self, info, args, executor) + } + } + }) + }); + let async_fields_err = { + let names = self + .fields + .iter() + .filter_map(|f| f.is_async.then(|| f.name.as_str())) + .collect::>(); + (!names.is_empty()).then(|| { + field::Definition::method_resolve_field_err_async_field_tokens( + &names, scalar, trait_name, + ) + }) + }; + let no_field_err = + field::Definition::method_resolve_field_err_no_field_tokens(scalar, trait_name); + + let downcast_check = self.method_concrete_type_name_tokens(); + + let downcast = self.method_resolve_into_type_tokens(); + + quote! { + #[allow(deprecated)] + #[automatically_derived] + impl#impl_generics ::juniper::GraphQLValue<#scalar> for #ty#ty_generics + #where_clause + { + type Context = #context; + type TypeInfo = (); + + fn type_name<'__i>(&self, info: &'__i Self::TypeInfo) -> Option<&'__i str> { + >::name(info) + } + + fn resolve_field( + &self, + info: &Self::TypeInfo, + field: &str, + args: &::juniper::Arguments<#scalar>, + executor: &::juniper::Executor, + ) -> ::juniper::ExecutionResult<#scalar> { + match field { + #( #fields_resolvers )* + #async_fields_err + _ => #no_field_err, + } + } + + fn concrete_type_name( + &self, + context: &Self::Context, + info: &Self::TypeInfo, + ) -> String { + #downcast_check + } + + fn resolve_into_type( + &self, + info: &Self::TypeInfo, + type_name: &str, + _: Option<&[::juniper::Selection<#scalar>]>, + executor: &::juniper::Executor, + ) -> ::juniper::ExecutionResult<#scalar> { + #downcast + } + } + } + } + + /// Returns generated code implementing [`GraphQLValueAsync`] trait for this + /// [GraphQL interface][1]. + /// + /// [`GraphQLValueAsync`]: juniper::GraphQLValueAsync + /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces + #[must_use] + fn impl_graphql_value_async_tokens(&self) -> TokenStream { + let scalar = &self.scalar; + + let generics = self.impl_generics(true); + let (impl_generics, _, where_clause) = generics.split_for_impl(); + let (_, ty_generics, _) = self.trait_generics.split_for_impl(); + let ty = &self.attrs.enum_alias_ident(&self.ident); + let trait_name = &self.name; + + let fields_resolvers = self.fields.iter().map(|f| { + let name = &f.name; + quote! { + #name => { + ::juniper::macros::helper::AsyncField::< + #scalar, + { ::juniper::macros::helper::fnv1a128(#name) } + >::call(self, info, args, executor) + } + } + }); + let no_field_err = + field::Definition::method_resolve_field_err_no_field_tokens(scalar, trait_name); + + let downcast = self.method_resolve_into_type_async_tokens(); + + quote! { + #[allow(deprecated, non_snake_case)] + #[automatically_derived] + impl#impl_generics ::juniper::GraphQLValueAsync<#scalar> for #ty#ty_generics + #where_clause + { + fn resolve_field_async<'b>( + &'b self, + info: &'b Self::TypeInfo, + field: &'b str, + args: &'b ::juniper::Arguments<#scalar>, + executor: &'b ::juniper::Executor, + ) -> ::juniper::BoxFuture<'b, ::juniper::ExecutionResult<#scalar>> { + match field { + #( #fields_resolvers )* + _ => Box::pin(async move { #no_field_err }), + } + } + + fn resolve_into_type_async<'b>( + &'b self, + info: &'b Self::TypeInfo, + type_name: &str, + _: Option<&'b [::juniper::Selection<'b, #scalar>]>, + executor: &'b ::juniper::Executor<'b, 'b, Self::Context, #scalar> + ) -> ::juniper::BoxFuture<'b, ::juniper::ExecutionResult<#scalar>> { + #downcast + } + } + } + } + + /// TODO + #[must_use] + pub(crate) fn impl_traits_for_const_assertions(&self) -> TokenStream { + let scalar = &self.scalar; + let name = &self.name; + let generics = self.impl_generics(false); + let (impl_generics, _, where_clause) = generics.split_for_impl(); + let (_, ty_generics, _) = self.trait_generics.split_for_impl(); + let ty = self.attrs.enum_alias_ident(&self.ident); + let implementers = self.attrs.implementers.iter(); + + quote! { + #[automatically_derived] + impl#impl_generics ::juniper::macros::helper::BaseType<#scalar> + for #ty#ty_generics + #where_clause + { + const NAME: ::juniper::macros::helper::Type = #name; + } + + #[automatically_derived] + impl#impl_generics ::juniper::macros::helper::BaseSubTypes<#scalar> + for #ty#ty_generics + #where_clause + { + const NAMES: ::juniper::macros::helper::Types = &[ + >::NAME, + #(<#implementers as ::juniper::macros::helper::BaseType<#scalar>>::NAME,)* + ]; + } + + #[automatically_derived] + impl#impl_generics ::juniper::macros::helper::WrappedType<#scalar> + for #ty#ty_generics + #where_clause + { + const VALUE: ::juniper::macros::helper::WrappedValue = 1; + } + } + } + + /// TODO + fn impl_fields(&self, for_async: bool) -> TokenStream { + let scalar = &self.scalar; + let const_scalar = match scalar { + scalar::Type::Concrete(ty) => ty.to_token_stream(), + scalar::Type::ExplicitGeneric(_) | scalar::Type::ImplicitGeneric(_) => { + quote! { ::juniper::DefaultScalarValue } + } + }; + + let ty = self.attrs.enum_alias_ident(&self.ident); + let context = &self.context; + let impl_tys = self.attrs.implementers.iter().collect::>(); + let impl_idents = self + .attrs + .implementers + .iter() + .filter_map(|ty| ty.path.segments.last().map(|seg| &seg.ident)) + .collect::>(); + + let generics = self.impl_generics(for_async); + let (impl_generics, _, where_clause) = generics.split_for_impl(); + let (_, ty_generics, _) = self.trait_generics.split_for_impl(); + + self.fields.iter().filter_map(|field| { + if field.is_async && !for_async { + return None; + } + let (trait_name, call_sig) = if for_async { + ( + quote! { AsyncField }, + quote! { + fn call<'b>( + &'b self, + info: &'b Self::TypeInfo, + args: &'b ::juniper::Arguments<#scalar>, + executor: &'b ::juniper::Executor, + ) -> ::juniper::BoxFuture<'b, ::juniper::ExecutionResult<#scalar>> + }, + ) + } else { + ( + quote! { Field }, + quote! { + fn call( + &self, + info: &Self::TypeInfo, + args: &::juniper::Arguments<#scalar>, + executor: &::juniper::Executor, + ) -> ::juniper::ExecutionResult<#scalar> + }, + ) + }; + + let name = &field.name; + let return_ty = &field.ty; + + let (args_tys, args_names): (Vec<_>, Vec<_>) = field + .arguments + .iter() + .flat_map(|vec| vec.iter()) + .filter_map(|arg| { + match arg { + field::MethodArgument::Regular(arg) => { + Some((&arg.ty, &arg.name)) + } + _ => None, + } + }) + .unzip(); + + Some(quote! { + impl#impl_generics ::juniper::macros::helper::#trait_name< + #scalar, + { ::juniper::macros::helper::fnv1a128(#name) } + > for #ty#ty_generics #where_clause { + type Context = #context; + type TypeInfo = (); + const TYPE: ::juniper::macros::helper::Type = + <#return_ty as ::juniper::macros::helper::BaseType<#scalar>>::NAME; + const SUB_TYPES: ::juniper::macros::helper::Types = + <#return_ty as ::juniper::macros::helper::BaseSubTypes<#scalar>>::NAMES; + const WRAPPED_VALUE: ::juniper::macros::helper::WrappedValue = + <#return_ty as ::juniper::macros::helper::WrappedType<#scalar>>::VALUE; + const ARGUMENTS: &'static [( + ::juniper::macros::helper::Name, + ::juniper::macros::helper::Type, + ::juniper::macros::helper::WrappedValue, + )] = &[#(( + #args_names, + <#args_tys as ::juniper::macros::helper::BaseType<#scalar>>::NAME, + <#args_tys as ::juniper::macros::helper::WrappedType<#scalar>>::VALUE, + )),*]; + + #call_sig { + match self { + #(#ty::#impl_idents(v) => { + const _: () = ::std::assert!(::juniper::macros::helper::is_subtype( + <#return_ty as ::juniper::macros::helper::BaseSubTypes>::NAMES, + <#return_ty as ::juniper::macros::helper::WrappedType>::VALUE, + <#impl_tys as ::juniper::macros::helper::#trait_name< + #const_scalar, + { ::juniper::macros::helper::fnv1a128(#name) }, + >>::TYPE, + <#impl_tys as ::juniper::macros::helper::#trait_name< + #const_scalar, + { ::juniper::macros::helper::fnv1a128(#name) }, + >>::WRAPPED_VALUE, + )); + const _: () = ::std::assert!(::juniper::macros::helper::is_valid_field_args( + <#ty as ::juniper::macros::helper::#trait_name< + #const_scalar, + { ::juniper::macros::helper::fnv1a128(#name) }, + >>::ARGUMENTS, + <#impl_tys as ::juniper::macros::helper::#trait_name< + #const_scalar, + { ::juniper::macros::helper::fnv1a128(#name) }, + >>::ARGUMENTS, + )); + + <_ as ::juniper::macros::helper::#trait_name< + #scalar, + { ::juniper::macros::helper::fnv1a128(#name) }, + >>::call(v, info, args, executor) + })* + } + } + } + }) + }) + .collect() + } + + /// Returns generated code for the [`GraphQLValue::concrete_type_name`][0] + /// method, which returns name of the underlying [`Implementer`] GraphQL + /// type contained in this [`EnumType`]. + /// + /// [0]: juniper::GraphQLValue::concrete_type_name + #[must_use] + fn method_concrete_type_name_tokens(&self) -> TokenStream { + let scalar = &self.scalar; + + let match_arms = self + .attrs + .implementers + .iter() + .filter_map(|ty| ty.path.segments.last().map(|seg| (&seg.ident, ty))) + .map(|(ident, ty)| { + quote! { + Self::#ident(v) => < + #ty as ::juniper::GraphQLValue<#scalar> + >::concrete_type_name(v, context, info), + } + }); + + let non_exhaustive_match_arm = (!self.trait_generics.params.is_empty() + || self.attrs.implementers.is_empty()) + .then(|| { + quote! { _ => unreachable!(), } + }); + + quote! { + match self { + #( #match_arms )* + #non_exhaustive_match_arm + } + } + } + + /// Returns generated code for the + /// [`GraphQLValueAsync::resolve_into_type_async`][0] method, which + /// downcasts this [`EnumType`] into its underlying [`Implementer`] type + /// asynchronously. + /// + /// [0]: juniper::GraphQLValueAsync::resolve_into_type_async + #[must_use] + fn method_resolve_into_type_async_tokens(&self) -> TokenStream { + let resolving_code = gen::async_resolving_code(None); + + let match_arms = self.attrs.implementers.iter().filter_map(|ty| { + ty.path.segments.last().map(|ident| { + quote! { + Self::#ident(v) => { + let fut = ::juniper::futures::future::ready(v); + #resolving_code + } + } + }) + }); + let non_exhaustive_match_arm = (!self.trait_generics.params.is_empty() + || self.attrs.implementers.is_empty()) + .then(|| { + quote! { _ => unreachable!(), } + }); + + quote! { + match self { + #( #match_arms )* + #non_exhaustive_match_arm + } + } + } + + /// Returns generated code for the [`GraphQLValue::resolve_into_type`][0] + /// method, which downcasts this [`EnumType`] into its underlying + /// [`Implementer`] type synchronously. + /// + /// [0]: juniper::GraphQLValue::resolve_into_type + #[must_use] + fn method_resolve_into_type_tokens(&self) -> TokenStream { + let resolving_code = gen::sync_resolving_code(); + + let match_arms = self.attrs.implementers.iter().filter_map(|ty| { + ty.path.segments.last().map(|ident| { + quote! { + Self::#ident(res) => #resolving_code, + } + }) + }); + + let non_exhaustive_match_arm = (!self.trait_generics.params.is_empty() + || self.attrs.implementers.is_empty()) + .then(|| { + quote! { _ => unreachable!(), } + }); + + quote! { + match self { + #( #match_arms )* + #non_exhaustive_match_arm + } + } + } + + /// Returns prepared [`syn::Generics`] for [`GraphQLType`] trait (and + /// similar) implementation of this [`EnumType`]. + /// + /// If `for_async` is `true`, then additional predicates are added to suit + /// the [`GraphQLAsyncValue`] trait (and similar) requirements. + /// + /// [`GraphQLAsyncValue`]: juniper::GraphQLAsyncValue + /// [`GraphQLType`]: juniper::GraphQLType + #[must_use] + fn impl_generics(&self, for_async: bool) -> syn::Generics { + let mut generics = self.trait_generics.clone(); + + let scalar = &self.scalar; + if scalar.is_implicit_generic() { + generics.params.push(parse_quote! { #scalar }); + } + if scalar.is_generic() { + generics + .make_where_clause() + .predicates + .push(parse_quote! { #scalar: ::juniper::ScalarValue }); + } + if let Some(bound) = scalar.bounds() { + generics.make_where_clause().predicates.push(bound); + } + + if for_async { + let self_ty = if self.trait_generics.lifetimes().next().is_some() { + // Modify lifetime names to omit "lifetime name `'a` shadows a + // lifetime name that is already in scope" error. + let mut generics = self.trait_generics.clone(); + for lt in generics.lifetimes_mut() { + let ident = lt.lifetime.ident.unraw(); + lt.lifetime.ident = format_ident!("__fa__{}", ident); + } + + let lifetimes = generics.lifetimes().map(|lt| <.lifetime); + let ty = &self.ident; + let (_, ty_generics, _) = generics.split_for_impl(); + + quote! { for<#( #lifetimes ),*> #ty#ty_generics } + } else { + quote! { Self } + }; + generics + .make_where_clause() + .predicates + .push(parse_quote! { #self_ty: Sync }); + + if scalar.is_generic() { + generics + .make_where_clause() + .predicates + .push(parse_quote! { #scalar: Send + Sync }); + } + } + + generics + } +} diff --git a/juniper_codegen/src/lib.rs b/juniper_codegen/src/lib.rs index 1e426af3d..0b30d10c0 100644 --- a/juniper_codegen/src/lib.rs +++ b/juniper_codegen/src/lib.rs @@ -673,6 +673,14 @@ pub fn graphql_interface(attr: TokenStream, body: TokenStream) -> TokenStream { .into() } +#[proc_macro_error] +#[proc_macro_attribute] +pub fn graphql_interface_new(attr: TokenStream, body: TokenStream) -> TokenStream { + self::graphql_interface::attr::expand_new(attr.into(), body.into()) + .unwrap_or_abort() + .into() +} + /// `#[derive(GraphQLObject)]` macro for deriving a [GraphQL object][1] /// implementation for structs. /// From f4c48a6f9a24ccb5663164886fa9d305924f62a3 Mon Sep 17 00:00:00 2001 From: ilslv Date: Tue, 28 Dec 2021 15:48:52 +0300 Subject: [PATCH 12/53] WIP (move to the new tests volume 1) --- .../src/codegen/interface_attr.rs | 1803 ++++------------- .../src/codegen/new_interface.rs | 95 +- juniper_codegen/src/graphql_interface/new.rs | 76 +- 3 files changed, 437 insertions(+), 1537 deletions(-) diff --git a/integration_tests/juniper_tests/src/codegen/interface_attr.rs b/integration_tests/juniper_tests/src/codegen/interface_attr.rs index bc8ea3081..18115e77f 100644 --- a/integration_tests/juniper_tests/src/codegen/interface_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/interface_attr.rs @@ -1,9 +1,9 @@ //! Tests for `#[graphql_interface]` macro. use juniper::{ - execute, graphql_interface, graphql_object, graphql_value, graphql_vars, DefaultScalarValue, - EmptyMutation, EmptySubscription, Executor, FieldError, FieldResult, GraphQLInputObject, - GraphQLObject, GraphQLType, IntoFieldError, RootNode, ScalarValue, + execute, graphql_interface, graphql_interface_new, graphql_object, graphql_value, graphql_vars, + DefaultScalarValue, EmptyMutation, EmptySubscription, Executor, FieldError, FieldResult, + GraphQLInputObject, GraphQLObject, GraphQLType, IntoFieldError, RootNode, ScalarValue, }; fn schema<'q, C, Q>(query_root: Q) -> RootNode<'q, Q, EmptyMutation, EmptySubscription> @@ -31,372 +31,94 @@ where ) } -mod new { +mod no_implers { use super::*; - // ------- Implemented ------- - - #[automatically_derived] - impl<__S> ::juniper::marker::GraphQLInterface<__S> for CharacterValue - where - __S: ::juniper::ScalarValue, - { - fn mark() { - const _: fn() = || { - trait MutuallyExclusive {} - impl MutuallyExclusive for Droid {} - impl MutuallyExclusive for Human {} - }; - } - } - - #[automatically_derived] - impl<__S> ::juniper::marker::IsOutputType<__S> for CharacterValue - where - __S: ::juniper::ScalarValue, - { - fn mark() { - <<&str as ::juniper::IntoResolvable< - '_, - __S, - _, - >::Context, - >>::Type as ::juniper::marker::IsOutputType<__S>>::mark(); - >::mark(); - >::mark(); - } - } - - #[automatically_derived] - impl<__S> ::juniper::GraphQLType<__S> for CharacterValue - where - __S: ::juniper::ScalarValue, - { - fn name(_: &Self::TypeInfo) -> Option<&'static str> { - Some("Character") - } - fn meta<'r>( - info: &Self::TypeInfo, - registry: &mut ::juniper::Registry<'r, __S>, - ) -> ::juniper::meta::MetaType<'r, __S> - where - __S: 'r, - { - let _ = registry.get_type::(info); - let _ = registry.get_type::(info); - let fields = [registry.field_convert::<&str, _, Self::Context>("id", info)]; - registry - .build_interface_type::(info, &fields) - .into_meta() - } + #[graphql_interface_new] + trait Character { + fn id(&self) -> &str; } - #[allow(deprecated)] - #[automatically_derived] - impl<__S> ::juniper::GraphQLValue<__S> for CharacterValue - where - __S: ::juniper::ScalarValue, - { - type Context = (); - type TypeInfo = (); + struct QueryRoot; - fn type_name<'__i>(&self, info: &'__i Self::TypeInfo) -> Option<&'__i str> { - >::name(info) - } - fn resolve_field( - &self, - info: &Self::TypeInfo, - field: &str, - args: &::juniper::Arguments<__S>, - executor: &::juniper::Executor, - ) -> ::juniper::ExecutionResult<__S> { - match field { - "id" => <_ as juniper::macros::helper::Field< - __S, - { juniper::macros::helper::fnv1a128("id") }, - >>::call(self, info, args, executor), - _ => { - return Err(::juniper::FieldError::from({ - format!("Field `{}` not found on type `{}`", field, "CharacterValue",) - })) - } - } - } - fn concrete_type_name(&self, context: &Self::Context, info: &Self::TypeInfo) -> String { - match self { - Self::Human(v) => { - >::concrete_type_name(v, context, info) - } - Self::Droid(v) => { - >::concrete_type_name(v, context, info) - } - } - } - fn resolve_into_type( - &self, - info: &Self::TypeInfo, - type_name: &str, - _: Option<&[::juniper::Selection<__S>]>, - executor: &::juniper::Executor, - ) -> ::juniper::ExecutionResult<__S> { - match self { - Self::Human(res) => ::juniper::IntoResolvable::into(res, executor.context()) - .and_then(|res| match res { - Some((ctx, r)) => executor.replaced_context(ctx).resolve_with_ctx(info, &r), - None => Ok(::juniper::Value::null()), - }), - Self::Droid(res) => ::juniper::IntoResolvable::into(res, executor.context()) - .and_then(|res| match res { - Some((ctx, r)) => executor.replaced_context(ctx).resolve_with_ctx(info, &r), - None => Ok(::juniper::Value::null()), - }), - } - } - } - - #[allow(deprecated, non_snake_case)] - #[automatically_derived] - impl<__S> ::juniper::GraphQLValueAsync<__S> for CharacterValue - where - __S: ::juniper::ScalarValue, - Self: Sync, - __S: Send + Sync, - { - fn resolve_field_async<'b>( - &'b self, - info: &'b Self::TypeInfo, - field: &'b str, - args: &'b ::juniper::Arguments<__S>, - executor: &'b ::juniper::Executor, - ) -> ::juniper::BoxFuture<'b, ::juniper::ExecutionResult<__S>> { - match field { - "id" => Box::pin(::juniper::futures::future::ready( - <_ as juniper::macros::helper::Field< - __S, - { juniper::macros::helper::fnv1a128("id") }, - >>::call(self, info, args, executor), - )), - _ => Box::pin(async move { - return Err(::juniper::FieldError::from({ - format!("Field `{}` not found on type `{}`", field, "CharacterValue",) - })); - }), - } - } - fn resolve_into_type_async<'b>( - &'b self, - info: &'b Self::TypeInfo, - type_name: &str, - _: Option<&'b [::juniper::Selection<'b, __S>]>, - executor: &'b ::juniper::Executor<'b, 'b, Self::Context, __S>, - ) -> ::juniper::BoxFuture<'b, ::juniper::ExecutionResult<__S>> { - match self { - Self::Droid(v) => { - let fut = ::juniper::futures::future::ready(v); - Box::pin(::juniper::futures::FutureExt::then( - fut, - move |res| async move { - match ::juniper::IntoResolvable::into(res, executor.context())? { - Some((ctx, r)) => { - let subexec = executor.replaced_context(ctx); - subexec.resolve_with_ctx_async(info, &r).await - } - None => Ok(::juniper::Value::null()), - } - }, - )) - } - Self::Human(v) => { - let fut = ::juniper::futures::future::ready(v); - Box::pin(::juniper::futures::FutureExt::then( - fut, - move |res| async move { - match ::juniper::IntoResolvable::into(res, executor.context())? { - Some((ctx, r)) => { - let subexec = executor.replaced_context(ctx); - subexec.resolve_with_ctx_async(info, &r).await - } - None => Ok(::juniper::Value::null()), - } - }, - )) - } - } + #[graphql_object(scalar = S: ScalarValue + Send + Sync)] + impl QueryRoot { + fn character(&self) -> CharacterValue { + unimplemented!() } } - enum CharacterEnumValue { - Human(I1), - Droid(I2), - } + #[tokio::test] + async fn is_graphql_interface() { + let schema = schema(QueryRoot); - type CharacterValue = CharacterEnumValue; + let doc = r#"{ + __type(name: "Character") { + kind + } + }"#; - impl From for CharacterValue { - fn from(v: Human) -> Self { - Self::Human(v) - } + assert_eq!( + execute(&doc, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"kind": "INTERFACE"}}), vec![])), + ); } - impl From for CharacterValue { - fn from(v: Droid) -> Self { - Self::Droid(v) - } - } + #[tokio::test] + async fn uses_trait_name() { + let schema = schema(QueryRoot); - // -------------- + let doc = r#"{ + __type(name: "Character") { + name + } + }"#; - // #[derive(GraphQLInterface)] - // #[graphql(for(Human, Droid))] - struct Character { - id: Option, + assert_eq!( + execute(&doc, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"name": "Character"}}), vec![])), + ); } - impl juniper::macros::helper::BaseType for CharacterValue - where - S: ScalarValue, - { - const NAME: juniper::macros::helper::Type = "Character"; - } + #[tokio::test] + async fn has_no_description() { + let schema = schema(QueryRoot); - impl juniper::macros::helper::BaseSubTypes for CharacterValue - where - S: ScalarValue, - { - const NAMES: juniper::macros::helper::Types = &[ - >::NAME, - >::NAME, - >::NAME, - ]; - } + let doc = r#"{ + __type(name: "Character") { + description + } + }"#; - impl juniper::macros::helper::WrappedType for CharacterValue { - const VALUE: u128 = 1; + assert_eq!( + execute(&doc, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"description": null}}), vec![])), + ); } +} - impl - juniper::macros::helper::Field - for CharacterValue - { - type Context = (); - type TypeInfo = (); - const TYPE: juniper::macros::helper::Type = - as juniper::macros::helper::BaseType>::NAME; - const SUB_TYPES: juniper::macros::helper::Types = - as juniper::macros::helper::BaseSubTypes>::NAMES; - const WRAPPED_VALUE: juniper::macros::helper::WrappedValue = - as juniper::macros::helper::WrappedType>::VALUE; - const ARGUMENTS: &'static [( - juniper::macros::helper::Name, - juniper::macros::helper::Type, - juniper::macros::helper::WrappedValue, - )] = &[("required", "String", 1)]; +mod trivial { + use super::*; - fn call( - &self, - info: &Self::TypeInfo, - args: &juniper::Arguments, - executor: &juniper::Executor, - ) -> juniper::ExecutionResult { - match self { - CharacterValue::Human(v) => { - const _: () = assert!(juniper::macros::helper::is_subtype( - as juniper::macros::helper::BaseSubTypes>::NAMES, - as juniper::macros::helper::WrappedType>::VALUE, - >::TYPE, - >::WRAPPED_VALUE, - )); - const _: () = assert!(juniper::macros::helper::is_valid_field_args( - >::ARGUMENTS, - >::ARGUMENTS, - )); - - <_ as juniper::macros::helper::Field< - S, - { juniper::macros::helper::fnv1a128("id") }, - >>::call(v, info, args, executor) - } - CharacterValue::Droid(v) => { - const _: () = assert!(juniper::macros::helper::is_subtype( - as juniper::macros::helper::BaseSubTypes>::NAMES, - as juniper::macros::helper::WrappedType>::VALUE, - >::TYPE, - >::WRAPPED_VALUE, - )); - const _: () = assert!(juniper::macros::helper::is_valid_field_args( - >::ARGUMENTS, - >::ARGUMENTS, - )); - - <_ as juniper::macros::helper::Field< - S, - { juniper::macros::helper::fnv1a128("id") }, - >>::call(v, info, args, executor) - } - } - } + #[graphql_interface_new(for = [Human, Droid])] + trait Character { + fn id(&self) -> &str; } - // --------- - + #[derive(GraphQLObject)] + #[graphql(impl = CharacterValue)] struct Human { id: String, home_planet: String, } - #[graphql_object(impl = CharacterValue)] - impl Human { - fn id(&self, _required: String, _optional: Option) -> &str { - &self.id - } - - fn home_planet(&self) -> &str { - &self.home_planet - } - } - + #[derive(GraphQLObject)] + #[graphql(impl = CharacterValue)] struct Droid { id: String, primary_function: String, } - #[graphql_object(impl = CharacterValue)] - impl Droid { - fn id(&self, _required: String) -> &str { - &self.id - } - - fn primary_function(&self) -> &str { - &self.primary_function - } - } - - // ----- - #[derive(Clone, Copy)] enum QueryRoot { Human, @@ -421,14 +143,12 @@ mod new { } } - // -------------- - #[tokio::test] async fn enum_resolves_human() { const DOC: &str = r#"{ character { ... on Human { - humanId: id(required: "test") + humanId: id homePlanet } } @@ -450,7 +170,7 @@ mod new { const DOC: &str = r#"{ character { ... on Droid { - droidId: id(required: "test") + droidId: id primaryFunction } } @@ -466,135 +186,155 @@ mod new { )), ); } -} -mod no_implers { - use super::*; + #[tokio::test] + async fn enum_resolves_id_field() { + const DOC: &str = r#"{ + character { + id + } + }"#; - #[graphql_interface] - trait Character { - fn id(&self) -> &str; + for (root, expected_id) in &[ + (QueryRoot::Human, "human-32"), + (QueryRoot::Droid, "droid-99"), + ] { + let schema = schema(*root); + + let expected_id: &str = *expected_id; + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"character": {"id": expected_id}}), vec![])), + ); + } } - #[graphql_interface(dyn = DynHero)] - trait Hero { - fn info(&self) -> &str; + #[tokio::test] + async fn is_graphql_interface() { + let schema = schema(QueryRoot::Human); + + let doc = r#"{ + __type(name: "Character") { + kind + } + }"#; + + assert_eq!( + execute(&doc, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"kind": "INTERFACE"}}), vec![])), + ); } - struct QueryRoot; + #[tokio::test] + async fn registers_all_implementers() { + let schema = schema(QueryRoot::Human); - #[graphql_object(scalar = S: ScalarValue + Send + Sync)] - impl QueryRoot { - fn character(&self) -> CharacterValue { - unimplemented!() - } + let doc = r#"{ + __type(name: "Character") { + possibleTypes { + kind + name + } + } + }"#; - fn hero(&self) -> Box> { - unimplemented!() - } + assert_eq!( + execute(&doc, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": {"possibleTypes": [ + {"kind": "OBJECT", "name": "Droid"}, + {"kind": "OBJECT", "name": "Human"}, + ]}}), + vec![], + )), + ); } #[tokio::test] - async fn is_graphql_interface() { - let schema = schema(QueryRoot); + async fn registers_itself_in_implementers() { + let schema = schema(QueryRoot::Human); - for interface in &["Character", "Hero"] { + for object in &["Human", "Droid"] { let doc = format!( r#"{{ - __type(name: "{}") {{ - kind - }} + __type(name: "{}") {{ + interfaces {{ + kind + name + }} + }} }}"#, - interface, + object, ); assert_eq!( execute(&doc, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"__type": {"kind": "INTERFACE"}}), vec![])), + Ok(( + graphql_value!({"__type": {"interfaces": [ + {"kind": "INTERFACE", "name": "Character"}, + ]}}), + vec![], + )), ); } } #[tokio::test] async fn uses_trait_name() { - let schema = schema(QueryRoot); + let schema = schema(QueryRoot::Human); - for interface in &["Character", "Hero"] { - let doc = format!( - r#"{{ - __type(name: "{}") {{ - name - }} - }}"#, - interface, - ); + let doc = r#"{ + __type(name: "Character") { + name + } + }"#; - let expected_name: &str = *interface; - assert_eq!( - execute(&doc, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"__type": {"name": expected_name}}), vec![])), - ); - } + assert_eq!( + execute(&doc, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"name": "Character"}}), vec![])), + ); } #[tokio::test] async fn has_no_description() { - let schema = schema(QueryRoot); + let schema = schema(QueryRoot::Human); - for interface in &["Character", "Hero"] { - let doc = format!( - r#"{{ - __type(name: "{}") {{ - description - }} - }}"#, - interface, - ); + let doc = r#"{ + __type(name: "Character") { + description + } + }"#; - assert_eq!( - execute(&doc, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"__type": {"description": null}}), vec![])), - ); - } + assert_eq!( + execute(&doc, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"description": null}}), vec![])), + ); } } -mod trivial { +mod trivial_with_trait_imp { use super::*; - #[graphql_interface(for = [Human, Droid])] + #[graphql_interface_new(for = [Human, Droid])] trait Character { fn id(&self) -> &str; } - #[graphql_interface(dyn = DynHero, for = [Human, Droid])] - trait Hero { - fn info(&self) -> &str; - } - #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue, DynHero<__S>])] + #[graphql(impl = CharacterValue)] struct Human { id: String, home_planet: String, } - #[graphql_interface] impl Character for Human { fn id(&self) -> &str { &self.id } } - #[graphql_interface(dyn)] - impl Hero for Human { - fn info(&self) -> &str { - &self.home_planet - } - } - #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue, DynHero<__S>])] + #[graphql(impl = CharacterValue)] struct Droid { id: String, primary_function: String, @@ -607,13 +347,6 @@ mod trivial { } } - #[graphql_interface(dyn)] - impl Hero for Droid { - fn info(&self) -> &str { - &self.primary_function - } - } - #[derive(Clone, Copy)] enum QueryRoot { Human, @@ -633,73 +366,15 @@ mod trivial { id: "droid-99".to_string(), primary_function: "run".to_string(), } - .into(), - } - } - - fn hero(&self) -> Box> { - let ch: Box> = match self { - Self::Human => Box::new(Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), - }), - Self::Droid => Box::new(Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), - }), - }; - ch - } - } - - #[tokio::test] - async fn enum_resolves_human() { - const DOC: &str = r#"{ - character { - ... on Human { - humanId: id - homePlanet - } - } - }"#; - - let schema = schema(QueryRoot::Human); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"character": {"humanId": "human-32", "homePlanet": "earth"}}), - vec![], - )), - ); - } - - #[tokio::test] - async fn enum_resolves_droid() { - const DOC: &str = r#"{ - character { - ... on Droid { - droidId: id - primaryFunction - } - } - }"#; - - let schema = schema(QueryRoot::Droid); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}), - vec![], - )), - ); + .into(), + } + } } #[tokio::test] - async fn dyn_resolves_human() { + async fn enum_resolves_human() { const DOC: &str = r#"{ - hero { + character { ... on Human { humanId: id homePlanet @@ -712,16 +387,16 @@ mod trivial { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( - graphql_value!({"hero": {"humanId": "human-32", "homePlanet": "earth"}}), + graphql_value!({"character": {"humanId": "human-32", "homePlanet": "earth"}}), vec![], )), ); } #[tokio::test] - async fn dyn_resolves_droid() { + async fn enum_resolves_droid() { const DOC: &str = r#"{ - hero { + character { ... on Droid { droidId: id primaryFunction @@ -734,7 +409,7 @@ mod trivial { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( - graphql_value!({"hero": {"droidId": "droid-99", "primaryFunction": "run"}}), + graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}), vec![], )), ); @@ -762,74 +437,45 @@ mod trivial { } } - #[tokio::test] - async fn dyn_resolves_info_field() { - const DOC: &str = r#"{ - hero { - info - } - }"#; - - for (root, expected_info) in &[(QueryRoot::Human, "earth"), (QueryRoot::Droid, "run")] { - let schema = schema(*root); - - let expected_info: &str = *expected_info; - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"hero": {"info": expected_info}}), vec![])), - ); - } - } - #[tokio::test] async fn is_graphql_interface() { let schema = schema(QueryRoot::Human); - for interface in &["Character", "Hero"] { - let doc = format!( - r#"{{ - __type(name: "{}") {{ - kind - }} - }}"#, - interface, - ); + let doc = r#"{ + __type(name: "Character") { + kind + } + }"#; - assert_eq!( - execute(&doc, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"__type": {"kind": "INTERFACE"}}), vec![])), - ); - } + assert_eq!( + execute(&doc, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"kind": "INTERFACE"}}), vec![])), + ); } #[tokio::test] async fn registers_all_implementers() { let schema = schema(QueryRoot::Human); - for interface in &["Character", "Hero"] { - let doc = format!( - r#"{{ - __type(name: "{}") {{ - possibleTypes {{ - kind - name - }} - }} - }}"#, - interface, - ); + let doc = r#"{ + __type(name: "Character") { + possibleTypes { + kind + name + } + } + }"#; - assert_eq!( - execute(&doc, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"__type": {"possibleTypes": [ - {"kind": "OBJECT", "name": "Droid"}, - {"kind": "OBJECT", "name": "Human"}, - ]}}), - vec![], - )), - ); - } + assert_eq!( + execute(&doc, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": {"possibleTypes": [ + {"kind": "OBJECT", "name": "Droid"}, + {"kind": "OBJECT", "name": "Human"}, + ]}}), + vec![], + )), + ); } #[tokio::test] @@ -854,7 +500,6 @@ mod trivial { Ok(( graphql_value!({"__type": {"interfaces": [ {"kind": "INTERFACE", "name": "Character"}, - {"kind": "INTERFACE", "name": "Hero"}, ]}}), vec![], )), @@ -866,50 +511,39 @@ mod trivial { async fn uses_trait_name() { let schema = schema(QueryRoot::Human); - for interface in &["Character", "Hero"] { - let doc = format!( - r#"{{ - __type(name: "{}") {{ - name - }} - }}"#, - interface, - ); + let doc = r#"{ + __type(name: "Character") { + name + } + }"#; - let expected_name: &str = *interface; - assert_eq!( - execute(&doc, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"__type": {"name": expected_name}}), vec![])), - ); - } + assert_eq!( + execute(&doc, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"name": "Character"}}), vec![])), + ); } #[tokio::test] async fn has_no_description() { let schema = schema(QueryRoot::Human); - for interface in &["Character", "Hero"] { - let doc = format!( - r#"{{ - __type(name: "{}") {{ - description - }} - }}"#, - interface, - ); + let doc = r#"{ + __type(name: "Character") { + description + } + }"#; - assert_eq!( - execute(&doc, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"__type": {"description": null}}), vec![])), - ); - } + assert_eq!( + execute(&doc, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"description": null}}), vec![])), + ); } } mod explicit_alias { use super::*; - #[graphql_interface(enum = CharacterEnum, for = [Human, Droid])] + #[graphql_interface_new(enum = CharacterEnum, for = [Human, Droid])] trait Character { fn id(&self) -> &str; } @@ -921,13 +555,6 @@ mod explicit_alias { home_planet: String, } - #[graphql_interface] - impl Character for Human { - fn id(&self) -> &str { - &self.id - } - } - #[derive(GraphQLObject)] #[graphql(impl = CharacterEnum)] struct Droid { @@ -935,13 +562,6 @@ mod explicit_alias { primary_function: String, } - #[graphql_interface] - impl Character for Droid { - fn id(&self) -> &str { - &self.id - } - } - #[derive(Clone, Copy)] enum QueryRoot { Human, @@ -1084,58 +704,25 @@ mod explicit_alias { mod trivial_async { use super::*; - #[graphql_interface(for = [Human, Droid])] + #[graphql_interface_new(for = [Human, Droid])] trait Character { async fn id(&self) -> &str; } - #[graphql_interface(dyn = DynHero, for = [Human, Droid])] - trait Hero { - async fn info(&self) -> &str; - } - #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue, DynHero<__S>])] + #[graphql(impl = CharacterValue)] struct Human { id: String, home_planet: String, } - #[graphql_interface] - impl Character for Human { - async fn id(&self) -> &str { - &self.id - } - } - - #[graphql_interface(dyn)] - impl Hero for Human { - async fn info(&self) -> &str { - &self.home_planet - } - } - #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue, DynHero<__S>])] + #[graphql(impl = CharacterValue)] struct Droid { id: String, primary_function: String, } - #[graphql_interface] - impl Character for Droid { - async fn id(&self) -> &str { - &self.id - } - } - - #[graphql_interface(dyn)] - impl Hero for Droid { - async fn info(&self) -> &str { - &self.primary_function - } - } - #[derive(Clone, Copy)] enum QueryRoot { Human, @@ -1158,20 +745,6 @@ mod trivial_async { .into(), } } - - fn hero(&self) -> Box> { - let ch: Box> = match self { - Self::Human => Box::new(Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), - }), - Self::Droid => Box::new(Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), - }), - }; - ch - } } #[tokio::test] @@ -1219,86 +792,23 @@ mod trivial_async { } #[tokio::test] - async fn dyn_resolves_human() { - const DOC: &str = r#"{ - hero { - ... on Human { - humanId: id - homePlanet - } - } - }"#; - - let schema = schema(QueryRoot::Human); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"hero": {"humanId": "human-32", "homePlanet": "earth"}}), - vec![], - )), - ); - } - - #[tokio::test] - async fn dyn_resolves_droid() { - const DOC: &str = r#"{ - hero { - ... on Droid { - droidId: id - primaryFunction - } - } - }"#; - - let schema = schema(QueryRoot::Droid); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"hero": {"droidId": "droid-99", "primaryFunction": "run"}}), - vec![], - )), - ); - } - - #[tokio::test] - async fn enum_resolves_id_field() { - const DOC: &str = r#"{ - character { - id - } - }"#; - - for (root, expected_id) in &[ - (QueryRoot::Human, "human-32"), - (QueryRoot::Droid, "droid-99"), - ] { - let schema = schema(*root); - - let expected_id: &str = *expected_id; - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"character": {"id": expected_id}}), vec![])), - ); - } - } - - #[tokio::test] - async fn dyn_resolves_info_field() { + async fn enum_resolves_id_field() { const DOC: &str = r#"{ - hero { - info + character { + id } }"#; - for (root, expected_info) in &[(QueryRoot::Human, "earth"), (QueryRoot::Droid, "run")] { + for (root, expected_id) in &[ + (QueryRoot::Human, "human-32"), + (QueryRoot::Droid, "droid-99"), + ] { let schema = schema(*root); - let expected_info: &str = *expected_info; + let expected_id: &str = *expected_id; assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"hero": {"info": expected_info}}), vec![])), + Ok((graphql_value!({"character": {"id": expected_id}}), vec![])), ); } } @@ -1307,51 +817,41 @@ mod trivial_async { async fn is_graphql_interface() { let schema = schema(QueryRoot::Human); - for interface in &["Character", "Hero"] { - let doc = format!( - r#"{{ - __type(name: "{}") {{ - kind - }} - }}"#, - interface, - ); + let doc = r#"{ + __type(name: "Character") { + kind + } + }"#; - assert_eq!( - execute(&doc, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"__type": {"kind": "INTERFACE"}}), vec![])), - ); - } + assert_eq!( + execute(&doc, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"kind": "INTERFACE"}}), vec![])), + ); } #[tokio::test] async fn registers_all_implementers() { let schema = schema(QueryRoot::Human); - for interface in &["Character", "Hero"] { - let doc = format!( - r#"{{ - __type(name: "{}") {{ - possibleTypes {{ - kind - name - }} - }} - }}"#, - interface, - ); + let doc = r#"{ + __type(name: "Character") { + possibleTypes { + kind + name + } + } + }"#; - assert_eq!( - execute(&doc, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"__type": {"possibleTypes": [ - {"kind": "OBJECT", "name": "Droid"}, - {"kind": "OBJECT", "name": "Human"}, - ]}}), - vec![], - )), - ); - } + assert_eq!( + execute(&doc, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": {"possibleTypes": [ + {"kind": "OBJECT", "name": "Droid"}, + {"kind": "OBJECT", "name": "Human"}, + ]}}), + vec![], + )), + ); } #[tokio::test] @@ -1376,7 +876,6 @@ mod trivial_async { Ok(( graphql_value!({"__type": {"interfaces": [ {"kind": "INTERFACE", "name": "Character"}, - {"kind": "INTERFACE", "name": "Hero"}, ]}}), vec![], )), @@ -1388,113 +887,69 @@ mod trivial_async { async fn uses_trait_name() { let schema = schema(QueryRoot::Human); - for interface in &["Character", "Hero"] { - let doc = format!( - r#"{{ - __type(name: "{}") {{ - name - }} - }}"#, - interface, - ); + let doc = r#"{ + __type(name: "Character") { + name + } + }"#; - let expected_name: &str = *interface; - assert_eq!( - execute(&doc, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"__type": {"name": expected_name}}), vec![])), - ); - } + assert_eq!( + execute(&doc, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"name": "Character"}}), vec![])), + ); } #[tokio::test] async fn has_no_description() { let schema = schema(QueryRoot::Human); - for interface in &["Character", "Hero"] { - let doc = format!( - r#"{{ - __type(name: "{}") {{ - description - }} - }}"#, - interface, - ); + let doc = r#"{ + __type(name: "Character") { + description + } + }"#; - assert_eq!( - execute(&doc, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"__type": {"description": null}}), vec![])), - ); - } + assert_eq!( + execute(&doc, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"description": null}}), vec![])), + ); } } mod explicit_async { use super::*; - #[graphql_interface(for = [Human, Droid])] + #[graphql_interface_new(for = [Human, Droid])] trait Character { fn id(&self) -> &str; - async fn info(&self) -> String { - "None available".to_owned() - } - } - - #[graphql_interface(dyn = DynHero, for = [Human, Droid])] - trait Hero { - async fn id(&self) -> &str { - "Non-identified" - } - - fn info(&self) -> &str; + async fn info(&self) -> String; } #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue, DynHero<__S>])] + #[graphql(impl = CharacterValue)] struct Human { id: String, + info: String, home_planet: String, } - #[graphql_interface] - impl Character for Human { - fn id(&self) -> &str { - &self.id - } - - async fn info(&self) -> String { - format!("Home planet is {}", &self.home_planet) - } - } - - #[graphql_interface(async, dyn)] - impl Hero for Human { - fn info(&self) -> &str { - &self.home_planet - } - } - - #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue, DynHero<__S>])] struct Droid { id: String, primary_function: String, } - #[graphql_interface(async)] - impl Character for Droid { + #[graphql_object(impl = CharacterValue)] + impl Droid { fn id(&self) -> &str { &self.id } - } - #[graphql_interface(dyn)] - impl Hero for Droid { - async fn id(&self) -> &str { - &self.id + async fn info(&self) -> String { + format!("Primary function is {}", &self.primary_function) } - fn info(&self) -> &str { + fn primary_function(&self) -> &str { &self.primary_function } } @@ -1511,6 +966,7 @@ mod explicit_async { match self { Self::Human => Human { id: "human-32".to_string(), + info: "Home planet is earth".to_string(), home_planet: "earth".to_string(), } .into(), @@ -1521,20 +977,6 @@ mod explicit_async { .into(), } } - - fn hero(&self) -> Box> { - let ch: Box> = match self { - Self::Human => Box::new(Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), - }), - Self::Droid => Box::new(Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), - }), - }; - ch - } } #[tokio::test] @@ -1581,50 +1023,6 @@ mod explicit_async { ); } - #[tokio::test] - async fn dyn_resolves_human() { - const DOC: &str = r#"{ - hero { - ... on Human { - humanId: id - homePlanet - } - } - }"#; - - let schema = schema(QueryRoot::Human); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"hero": {"humanId": "human-32", "homePlanet": "earth"}}), - vec![], - )), - ); - } - - #[tokio::test] - async fn dyn_resolves_droid() { - const DOC: &str = r#"{ - hero { - ... on Droid { - droidId: id - primaryFunction - } - } - }"#; - - let schema = schema(QueryRoot::Droid); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"hero": {"droidId": "droid-99", "primaryFunction": "run"}}), - vec![], - )), - ); - } - #[tokio::test] async fn enum_resolves_fields() { const DOC: &str = r#"{ @@ -1636,7 +1034,7 @@ mod explicit_async { for (root, expected_id, expected_info) in &[ (QueryRoot::Human, "human-32", "Home planet is earth"), - (QueryRoot::Droid, "droid-99", "None available"), + (QueryRoot::Droid, "droid-99", "Primary function is run"), ] { let schema = schema(*root); @@ -1654,36 +1052,6 @@ mod explicit_async { ); } } - - #[tokio::test] - async fn dyn_resolves_fields() { - const DOC: &str = r#"{ - hero { - id - info - } - }"#; - - for (root, expected_id, expected_info) in &[ - (QueryRoot::Human, "Non-identified", "earth"), - (QueryRoot::Droid, "droid-99", "run"), - ] { - let schema = schema(*root); - - let expected_id: &str = *expected_id; - let expected_info: &str = *expected_info; - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"hero": { - "id": expected_id, - "info": expected_info, - }}), - vec![], - )), - ); - } - } } mod fallible_field { @@ -1697,55 +1065,31 @@ mod fallible_field { } } - #[graphql_interface(for = [Human, Droid])] - trait Character { - fn id(&self) -> Result<&str, CustomError>; - } - - #[graphql_interface(dyn = DynHero, for = [Human, Droid])] - trait Hero { - fn info(&self) -> Result<&str, CustomError>; - } - - #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue, DynHero<__S>])] - struct Human { - id: String, - home_planet: String, - } - - #[graphql_interface] - impl Character for Human { - fn id(&self) -> Result<&str, CustomError> { - Ok(&self.id) - } - } - - #[graphql_interface(dyn)] - impl Hero for Human { - fn info(&self) -> Result<&str, CustomError> { - Ok(&self.home_planet) - } + #[graphql_interface_new(for = [Human, Droid])] + trait Character { + fn id(&self) -> Result<&str, CustomError>; } #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue, DynHero<__S>])] + #[graphql(impl = CharacterValue)] + struct Human { + id: String, + home_planet: String, + } + struct Droid { id: String, primary_function: String, } - #[graphql_interface] - impl Character for Droid { - fn id(&self) -> Result<&str, CustomError> { - Ok(&self.id) + #[graphql_object(impl = CharacterValue)] + impl Droid { + fn id(&self) -> String { + self.id.clone() } - } - #[graphql_interface(dyn)] - impl Hero for Droid { - fn info(&self) -> Result<&str, CustomError> { - Ok(&self.primary_function) + fn primary_function(&self) -> &str { + &self.primary_function } } @@ -1771,20 +1115,6 @@ mod fallible_field { .into(), } } - - fn hero(&self) -> Box> { - let ch: Box> = match self { - Self::Human => Box::new(Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), - }), - Self::Droid => Box::new(Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), - }), - }; - ch - } } #[tokio::test] @@ -1831,50 +1161,6 @@ mod fallible_field { ); } - #[tokio::test] - async fn dyn_resolves_human() { - const DOC: &str = r#"{ - hero { - ... on Human { - humanId: id - homePlanet - } - } - }"#; - - let schema = schema(QueryRoot::Human); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"hero": {"humanId": "human-32", "homePlanet": "earth"}}), - vec![], - )), - ); - } - - #[tokio::test] - async fn dyn_resolves_droid() { - const DOC: &str = r#"{ - hero { - ... on Droid { - droidId: id - primaryFunction - } - } - }"#; - - let schema = schema(QueryRoot::Droid); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"hero": {"droidId": "droid-99", "primaryFunction": "run"}}), - vec![], - )), - ); - } - #[tokio::test] async fn enum_resolves_id_field() { const DOC: &str = r#"{ @@ -1897,120 +1183,70 @@ mod fallible_field { } } - #[tokio::test] - async fn dyn_resolves_info_field() { - const DOC: &str = r#"{ - hero { - info - } - }"#; - - for (root, expected_info) in &[(QueryRoot::Human, "earth"), (QueryRoot::Droid, "run")] { - let schema = schema(*root); - - let expected_info: &str = *expected_info; - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"hero": {"info": expected_info}}), vec![])), - ); - } - } - #[tokio::test] async fn has_correct_graphql_type() { let schema = schema(QueryRoot::Human); - for (interface, field) in &[("Character", "id"), ("Hero", "info")] { - let doc = format!( - r#"{{ - __type(name: "{}") {{ - name + let doc = r#"{ + __type(name: "Character") { + name + kind + fields { + name + type { kind - fields {{ + ofType { name - type {{ - kind - ofType {{ - name - }} - }} - }} - }} - }}"#, - interface, - ); + } + } + } + } + }"#; - let expected_name: &str = *interface; - let expected_field_name: &str = *field; - assert_eq!( - execute(&doc, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"__type": { - "name": expected_name, - "kind": "INTERFACE", - "fields": [{ - "name": expected_field_name, - "type": {"kind": "NON_NULL", "ofType": {"name": "String"}}, - }] - }}), - vec![], - )), - ); - } + assert_eq!( + execute(&doc, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": { + "name": "Character", + "kind": "INTERFACE", + "fields": [{ + "name": "id", + "type": {"kind": "NON_NULL", "ofType": {"name": "String"}}, + }] + }}), + vec![], + )), + ); } } mod generic { use super::*; - #[graphql_interface(for = [Human, Droid])] + #[graphql_interface_new(for = [Human, Droid])] trait Character { fn id(&self) -> &str; } - #[graphql_interface(dyn = DynHero, for = [Human, Droid])] - trait Hero { - fn info(&self) -> &str; - } - #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue, DynHero])] + #[graphql(impl = CharacterValue)] struct Human { id: String, home_planet: String, } - #[graphql_interface] - impl Character for Human { - fn id(&self) -> &str { - &self.id - } - } - - #[graphql_interface(dyn)] - impl Hero for Human { - fn info(&self) -> &str { - &self.home_planet - } - } - - #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue<(), u8>, DynHero])] struct Droid { id: String, primary_function: String, } - #[graphql_interface] - impl Character for Droid { + #[graphql_object(impl = CharacterValue<(), u8>)] + impl Droid { fn id(&self) -> &str { &self.id } - } - #[graphql_interface(dyn)] - impl Hero for Droid { - fn info(&self) -> &str { + fn primary_function(&self) -> &str { &self.primary_function } } @@ -2037,20 +1273,6 @@ mod generic { .into(), } } - - fn hero(&self) -> Box> { - let ch: Box> = match self { - Self::Human => Box::new(Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), - }), - Self::Droid => Box::new(Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), - }), - }; - ch - } } #[tokio::test] @@ -2097,50 +1319,6 @@ mod generic { ); } - #[tokio::test] - async fn dyn_resolves_human() { - const DOC: &str = r#"{ - hero { - ... on Human { - humanId: id - homePlanet - } - } - }"#; - - let schema = schema(QueryRoot::Human); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"hero": {"humanId": "human-32", "homePlanet": "earth"}}), - vec![], - )), - ); - } - - #[tokio::test] - async fn dyn_resolves_droid() { - const DOC: &str = r#"{ - hero { - ... on Droid { - droidId: id - primaryFunction - } - } - }"#; - - let schema = schema(QueryRoot::Droid); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"hero": {"droidId": "droid-99", "primaryFunction": "run"}}), - vec![], - )), - ); - } - #[tokio::test] async fn enum_resolves_id_field() { const DOC: &str = r#"{ @@ -2158,104 +1336,55 @@ mod generic { let expected_id: &str = *expected_id; assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"character": {"id": expected_id}}), vec![])), - ); - } - } - - #[tokio::test] - async fn dyn_resolves_info_field() { - const DOC: &str = r#"{ - hero { - info - } - }"#; - - for (root, expected_info) in &[(QueryRoot::Human, "earth"), (QueryRoot::Droid, "run")] { - let schema = schema(*root); - - let expected_info: &str = *expected_info; - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"hero": {"info": expected_info}}), vec![])), - ); - } - } - - #[tokio::test] - async fn uses_trait_name_without_type_params() { - for interface in &["Character", "Hero"] { - let doc = format!( - r#"{{ - __type(name: "{}") {{ - name - }} - }}"#, - interface, - ); - - let schema = schema(QueryRoot::Human); - - let expected_name: &str = *interface; - assert_eq!( - execute(&doc, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"__type": {"name": expected_name}}), vec![])), + Ok((graphql_value!({"character": {"id": expected_id}}), vec![])), ); } } + + #[tokio::test] + async fn uses_trait_name_without_type_params() { + let doc = r#"{ + __type(name: "Character") { + name + } + }"#; + + let schema = schema(QueryRoot::Human); + + assert_eq!( + execute(&doc, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"name": "Character"}}), vec![])), + ); + } } mod generic_async { use super::*; - #[graphql_interface(for = [Human, Droid])] + #[graphql_interface_new(for = [Human, Droid])] trait Character { async fn id(&self) -> &str; } - #[graphql_interface(dyn = DynHero, for = [Human, Droid])] - trait Hero { - async fn info(&self) -> &str; - } - #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue, DynHero])] + #[graphql(impl = CharacterValue)] struct Human { id: String, home_planet: String, } - #[graphql_interface] - impl Character for Human { - async fn id(&self) -> &str { - &self.id - } - } - - #[graphql_interface(dyn)] - impl Hero for Human { - async fn info(&self) -> &str { - &self.home_planet - } - } - - #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue<(), u8>, DynHero])] struct Droid { id: String, primary_function: String, } - #[graphql_interface] - impl Character for Droid { - async fn id(&self) -> &str { + #[graphql_object(impl = CharacterValue<(), u8>)] + impl Droid { + fn id(&self) -> &str { &self.id } - } - #[graphql_interface(dyn)] - impl Hero for Droid { - async fn info(&self) -> &str { + fn primary_function(&self) -> &str { &self.primary_function } } @@ -2282,20 +1411,6 @@ mod generic_async { .into(), } } - - fn hero(&self) -> Box> { - let ch: Box> = match self { - Self::Human => Box::new(Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), - }), - Self::Droid => Box::new(Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), - }), - }; - ch - } } #[tokio::test] @@ -2342,50 +1457,6 @@ mod generic_async { ); } - #[tokio::test] - async fn dyn_resolves_human() { - const DOC: &str = r#"{ - hero { - ... on Human { - humanId: id - homePlanet - } - } - }"#; - - let schema = schema(QueryRoot::Human); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"hero": {"humanId": "human-32", "homePlanet": "earth"}}), - vec![], - )), - ); - } - - #[tokio::test] - async fn dyn_resolves_droid() { - const DOC: &str = r#"{ - hero { - ... on Droid { - droidId: id - primaryFunction - } - } - }"#; - - let schema = schema(QueryRoot::Droid); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"hero": {"droidId": "droid-99", "primaryFunction": "run"}}), - vec![], - )), - ); - } - #[tokio::test] async fn enum_resolves_id_field() { const DOC: &str = r#"{ @@ -2409,98 +1480,49 @@ mod generic_async { } #[tokio::test] - async fn dyn_resolves_info_field() { - const DOC: &str = r#"{ - hero { - info + async fn uses_trait_name_without_type_params() { + let doc = r#"{ + __type(name: "Character") { + name } }"#; - for (root, expected_info) in &[(QueryRoot::Human, "earth"), (QueryRoot::Droid, "run")] { - let schema = schema(*root); - - let expected_info: &str = *expected_info; - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"hero": {"info": expected_info}}), vec![])), - ); - } - } - - #[tokio::test] - async fn uses_trait_name_without_type_params() { - for interface in &["Character", "Hero"] { - let doc = format!( - r#"{{ - __type(name: "{}") {{ - name - }} - }}"#, - interface, - ); - - let schema = schema(QueryRoot::Human); + let schema = schema(QueryRoot::Human); - let expected_name: &str = *interface; - assert_eq!( - execute(&doc, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"__type": {"name": expected_name}}), vec![])), - ); - } + assert_eq!( + execute(&doc, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"name": "Character"}}), vec![])), + ); } } mod generic_lifetime_async { use super::*; - #[graphql_interface(for = [Human, Droid])] + #[graphql_interface_new(for = [Human, Droid])] trait Character<'me, A> { async fn id<'a>(&'a self) -> &'a str; } - #[graphql_interface(dyn = DynHero, for = [Human, Droid])] - trait Hero<'me, A> { - async fn info(&self) -> &str; - } - #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue<()>, DynHero<(), __S>])] + #[graphql(impl = CharacterValue<()>)] struct Human { id: String, home_planet: String, } - #[graphql_interface] - impl<'me, A> Character<'me, A> for Human { - async fn id<'a>(&'a self) -> &'a str { - &self.id - } - } - - #[graphql_interface(dyn)] - impl<'me, A> Hero<'me, A> for Human { - async fn info(&self) -> &str { - &self.home_planet - } - } - - #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue<()>, DynHero<(), __S>])] struct Droid { id: String, primary_function: String, } - #[graphql_interface] - impl<'me, A> Character<'me, A> for Droid { - async fn id<'a>(&'a self) -> &'a str { + #[graphql_object(impl = CharacterValue<()>)] + impl Droid { + fn id(&self) -> &str { &self.id } - } - #[graphql_interface(dyn)] - impl<'me, A> Hero<'me, A> for Droid { - async fn info(&self) -> &str { + fn primary_function(&self) -> &str { &self.primary_function } } @@ -2527,20 +1549,6 @@ mod generic_lifetime_async { .into(), } } - - fn hero(&self) -> Box> { - let ch: Box> = match self { - Self::Human => Box::new(Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), - }), - Self::Droid => Box::new(Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), - }), - }; - ch - } } #[tokio::test] @@ -2587,50 +1595,6 @@ mod generic_lifetime_async { ); } - #[tokio::test] - async fn dyn_resolves_human() { - const DOC: &str = r#"{ - hero { - ... on Human { - humanId: id - homePlanet - } - } - }"#; - - let schema = schema(QueryRoot::Human); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"hero": {"humanId": "human-32", "homePlanet": "earth"}}), - vec![], - )), - ); - } - - #[tokio::test] - async fn dyn_resolves_droid() { - const DOC: &str = r#"{ - hero { - ... on Droid { - droidId: id - primaryFunction - } - } - }"#; - - let schema = schema(QueryRoot::Droid); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"hero": {"droidId": "droid-99", "primaryFunction": "run"}}), - vec![], - )), - ); - } - #[tokio::test] async fn enum_resolves_id_field() { const DOC: &str = r#"{ @@ -2654,44 +1618,19 @@ mod generic_lifetime_async { } #[tokio::test] - async fn dyn_resolves_info_field() { - const DOC: &str = r#"{ - hero { - info + async fn uses_trait_name_without_type_params() { + let doc = r#"{ + __type(name: "Character") { + name } }"#; - for (root, expected_info) in &[(QueryRoot::Human, "earth"), (QueryRoot::Droid, "run")] { - let schema = schema(*root); - - let expected_info: &str = *expected_info; - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"hero": {"info": expected_info}}), vec![])), - ); - } - } - - #[tokio::test] - async fn uses_trait_name_without_type_params() { - for interface in &["Character", "Hero"] { - let doc = format!( - r#"{{ - __type(name: "{}") {{ - name - }} - }}"#, - interface, - ); - - let schema = schema(QueryRoot::Human); + let schema = schema(QueryRoot::Human); - let expected_name: &str = *interface; - assert_eq!( - execute(&doc, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"__type": {"name": expected_name}}), vec![])), - ); - } + assert_eq!( + execute(&doc, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"__type": {"name": "Character"}}), vec![])), + ); } } diff --git a/integration_tests/juniper_tests/src/codegen/new_interface.rs b/integration_tests/juniper_tests/src/codegen/new_interface.rs index c1f7579a4..95c0d465d 100644 --- a/integration_tests/juniper_tests/src/codegen/new_interface.rs +++ b/integration_tests/juniper_tests/src/codegen/new_interface.rs @@ -4,35 +4,28 @@ use juniper::{ GraphQLInputObject, GraphQLObject, GraphQLType, IntoFieldError, RootNode, ScalarValue, }; -#[graphql_interface_new(for = [Human, Droid])] -trait Character { - fn id(&self, required: String) -> String; +// -------------------------- + +#[graphql_interface(for = [Human, Droid])] +trait Character<'me, A> { + async fn id<'a>(&'a self) -> &'a str; } +#[derive(GraphQLObject)] +#[graphql(impl = CharacterValue<()>)] struct Human { id: String, home_planet: String, } -#[graphql_object(impl = CharacterValue)] -impl Human { - fn id(&self, _required: String, _optional: Option) -> &str { - &self.id - } - - fn home_planet(&self) -> &str { - &self.home_planet - } -} - struct Droid { id: String, primary_function: String, } -#[graphql_object(impl = CharacterValue)] +#[graphql_object(impl = CharacterValue<()>)] impl Droid { - fn id(&self, _required: String) -> &str { + fn id(&self) -> &str { &self.id } @@ -41,32 +34,6 @@ impl Droid { } } -// ----- - -#[derive(Clone, Copy)] -enum QueryRoot { - Human, - Droid, -} - -#[graphql_object(scalar = S: ScalarValue + Send + Sync)] -impl QueryRoot { - fn character(&self) -> CharacterValue { - match self { - Self::Human => Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), - } - .into(), - Self::Droid => Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), - } - .into(), - } - } -} - // -------------- fn schema<'q, C, Q>(query_root: Q) -> RootNode<'q, Q, EmptyMutation, EmptySubscription> @@ -93,47 +60,3 @@ where EmptySubscription::::new(), ) } - -#[tokio::test] -async fn enum_resolves_human() { - const DOC: &str = r#"{ - character { - ... on Human { - humanId: id(required: "test") - homePlanet - } - } - }"#; - - let schema = schema(QueryRoot::Human); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"character": {"humanId": "human-32", "homePlanet": "earth"}}), - vec![], - )), - ); -} - -#[tokio::test] -async fn enum_resolves_droid() { - const DOC: &str = r#"{ - character { - ... on Droid { - droidId: id(required: "test") - primaryFunction - } - } - }"#; - - let schema = schema(QueryRoot::Droid); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}), - vec![], - )), - ); -} diff --git a/juniper_codegen/src/graphql_interface/new.rs b/juniper_codegen/src/graphql_interface/new.rs index 2e0b733dd..3056b7175 100644 --- a/juniper_codegen/src/graphql_interface/new.rs +++ b/juniper_codegen/src/graphql_interface/new.rs @@ -14,6 +14,7 @@ use syn::{ ext::IdentExt as _, parse::{Parse, ParseStream}, parse_quote, + punctuated::Punctuated, spanned::Spanned as _, token, }; @@ -343,12 +344,28 @@ impl Definition { |c| format_ident!("{}Enum", c.inner().to_string()), ); + let enum_alias_generics = { + let mut enum_alias_generics = trait_gens.clone(); + let enum_generics = std::mem::take(&mut enum_alias_generics.params); + enum_alias_generics.params = enum_generics + .into_iter() + .map(|gen| match gen { + syn::GenericParam::Type(mut ty) => { + ty.bounds = Punctuated::new(); + ty.into() + } + rest => rest, + }) + .collect(); + enum_alias_generics + }; + let variants_generics = self .attrs .implementers .iter() .enumerate() - .map(|(id, ty)| format_ident!("I{}", id)); + .map(|(id, _)| format_ident!("I{}", id)); let variants_idents = self .attrs @@ -356,23 +373,38 @@ impl Definition { .iter() .filter_map(|ty| ty.path.segments.last().map(|seg| &seg.ident)); - let enum_generics = self - .trait_generics - .params - .iter() - .map(ToTokens::to_token_stream) - .chain(variants_generics.clone().map(ToTokens::into_token_stream)); + let enum_generics = { + let mut enum_generics = self.trait_generics.clone(); + let enum_generic_params = std::mem::take(&mut enum_generics.params); + let (mut enum_generic_params_lifetimes, enum_generic_params_rest) = enum_generic_params + .into_iter() + .partition::, _>(|par| { + matches!(par, syn::GenericParam::Lifetime(_)) + }); + + let variants = variants_generics + .clone() + .map::(|var| parse_quote! { #var }) + .collect::>(); + enum_generic_params_lifetimes.extend(variants); + enum_generic_params_lifetimes.extend(enum_generic_params_rest); + // variants.extend(enum_generic_params_rest); + enum_generics.params = enum_generic_params_lifetimes; + enum_generics + }; + let enum_to_alias_generics = self - .trait_generics - .params + .attrs + .implementers .iter() .map(ToTokens::to_token_stream) - .chain( - self.attrs - .implementers - .iter() - .map(ToTokens::to_token_stream), - ); + .chain(self.trait_generics.params.iter().map(|par| match par { + syn::GenericParam::Type(ty) => { + let par_ident = &ty.ident; + quote! { #par_ident } + } + rest => quote! { #rest }, + })); let from_impls = self .attrs @@ -393,12 +425,12 @@ impl Definition { quote! { #[derive(Clone, Copy, Debug)] - #vis enum #enum_ident<#(#enum_generics,)*> { - #(#variants_idents(#variants_generics)),* + #vis enum #enum_ident#enum_generics { + #(#variants_idents(#variants_generics),)* #phantom_variant } - #vis type #alias_ident#trait_gens = + #vis type #alias_ident#enum_alias_generics = #enum_ident<#(#enum_to_alias_generics,)*>; #(#from_impls)* @@ -803,6 +835,10 @@ impl Definition { }) .unzip(); + let unreachable_arm = (self.attrs.implementers.is_empty() || !self.trait_generics.params.is_empty()).then(|| { + quote! { _ => unreachable!() } + }); + Some(quote! { impl#impl_generics ::juniper::macros::helper::#trait_name< #scalar, @@ -857,6 +893,7 @@ impl Definition { { ::juniper::macros::helper::fnv1a128(#name) }, >>::call(v, info, args, executor) })* + #unreachable_arm } } } @@ -1003,7 +1040,8 @@ impl Definition { } let lifetimes = generics.lifetimes().map(|lt| <.lifetime); - let ty = &self.ident; + let ty = self.attrs.enum_alias_ident(&self.ident); + // let ty = &self.ident; let (_, ty_generics, _) = generics.split_for_impl(); quote! { for<#( #lifetimes ),*> #ty#ty_generics } From a1df3873c948bd6953061e0f11c07506583082ce Mon Sep 17 00:00:00 2001 From: ilslv Date: Wed, 29 Dec 2021 14:31:06 +0300 Subject: [PATCH 13/53] WIP (move to the new tests volume 2) --- .../src/codegen/interface_attr.rs | 315 +++++++----------- .../src/codegen/new_interface.rs | 52 ++- juniper_codegen/Cargo.toml | 2 +- juniper_codegen/src/graphql_interface/new.rs | 79 ++++- 4 files changed, 224 insertions(+), 224 deletions(-) diff --git a/integration_tests/juniper_tests/src/codegen/interface_attr.rs b/integration_tests/juniper_tests/src/codegen/interface_attr.rs index 18115e77f..108d61cc7 100644 --- a/integration_tests/juniper_tests/src/codegen/interface_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/interface_attr.rs @@ -1637,29 +1637,30 @@ mod generic_lifetime_async { mod argument { use super::*; - #[graphql_interface(for = Human)] + #[graphql_interface_new(for = Human)] trait Character { fn id_wide(&self, is_number: bool) -> &str; async fn id_wide2(&self, is_number: bool, r#async: Option) -> &str; } - #[graphql_interface(dyn = DynHero, for = Human)] - trait Hero { - fn info_wide(&self, is_planet: bool) -> &str; - - async fn info_wide2(&self, is_planet: bool, r#async: Option) -> &str; - } - - #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue, DynHero<__S>])] + // #[derive(GraphQLObject)] + // #[graphql(impl = CharacterValue)] struct Human { id: String, home_planet: String, } - #[graphql_interface] - impl Character for Human { + #[graphql_object(impl = CharacterValue)] + impl Human { + fn id(&self) -> &str { + &self.id + } + + fn home_planet(&self) -> &str { + &self.home_planet + } + fn id_wide(&self, is_number: bool) -> &str { if is_number { &self.id @@ -1668,7 +1669,7 @@ mod argument { } } - async fn id_wide2(&self, is_number: bool, _: Option) -> &str { + async fn id_wide2(&self, is_number: bool, _async: Option) -> &str { if is_number { &self.id } else { @@ -1677,25 +1678,6 @@ mod argument { } } - #[graphql_interface(dyn)] - impl Hero for Human { - fn info_wide(&self, is_planet: bool) -> &str { - if is_planet { - &self.home_planet - } else { - &self.id - } - } - - async fn info_wide2(&self, is_planet: bool, _: Option) -> &str { - if is_planet { - &self.home_planet - } else { - &self.id - } - } - } - struct QueryRoot; #[graphql_object(scalar = S: ScalarValue + Send + Sync)] @@ -1707,13 +1689,6 @@ mod argument { } .into() } - - fn hero(&self) -> Box> { - Box::new(Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), - }) - } } #[tokio::test] @@ -1745,141 +1720,90 @@ mod argument { } } - #[tokio::test] - async fn dyn_resolves_info_field() { - let schema = schema(QueryRoot); - - for (input, expected) in &[ - ( - "{ hero { infoWide(isPlanet: true), infoWide2(isPlanet: true) } }", - "earth", - ), - ( - "{ hero { infoWide(isPlanet: false), infoWide2(isPlanet: false, async: 3) } }", - "human-32", - ), - ] { - let expected: &str = *expected; - - assert_eq!( - execute(*input, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"hero": { - "infoWide": expected, - "infoWide2": expected, - }}), - vec![], - )), - ); - } - } - #[tokio::test] async fn camelcases_name() { let schema = schema(QueryRoot); - for (interface, field, arg) in &[ - ("Character", "idWide", "isNumber"), - ("Hero", "infoWide", "isPlanet"), - ] { - let doc = format!( - r#"{{ - __type(name: "{}") {{ - fields {{ - name - args {{ - name - }} - }} - }} - }}"#, - interface, - ); - - let expected_field_name: &str = *field; - let expected_field_name2: &str = &format!("{}2", field); - let expected_arg_name: &str = *arg; - assert_eq!( - execute(&doc, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"__type": {"fields": [{ - "name": expected_field_name, - "args": [ - {"name": expected_arg_name}, - ], - }, { - "name": expected_field_name2, - "args": [ - {"name": expected_arg_name}, - {"name": "async"}, - ], - }]}}), - vec![], - )), - ); - } + let doc = r#"{ + __type(name: "Character") { + fields { + name + args { + name + } + } + } + }"#; + assert_eq!( + execute(&doc, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": {"fields": [{ + "name": "idWide", + "args": [ + {"name": "isNumber"}, + ], + },{ + "name": "idWide2", + "args": [ + {"name": "isNumber"}, + {"name": "async"} + ], + }]}}), + vec![], + )), + ); } #[tokio::test] async fn has_no_description() { let schema = schema(QueryRoot); - for interface in &["Character", "Hero"] { - let doc = format!( - r#"{{ - __type(name: "{}") {{ - fields {{ - args {{ - description - }} - }} - }} - }}"#, - interface, - ); + let doc = r#"{ + __type(name: "Character") { + fields { + args { + description + } + } + } + }"#; - assert_eq!( - execute(&doc, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"__type": {"fields": [ - {"args": [{"description": null}]}, - {"args": [{"description": null}, {"description": null}]}, - ]}}), - vec![], - )), - ); - } + assert_eq!( + execute(&doc, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": {"fields": [ + {"args": [{"description": null}]}, + {"args": [{"description": null}, {"description": null}]}, + ]}}), + vec![], + )), + ); } #[tokio::test] async fn has_no_defaults() { let schema = schema(QueryRoot); - for interface in &["Character", "Hero"] { - let doc = format!( - r#"{{ - __type(name: "{}") {{ - fields {{ - args {{ - defaultValue - }} - }} - }} - }}"#, - interface, - ); + let doc = r#"{ + __type(name: "Character") { + fields { + args { + defaultValue + } + } + } + }"#; - assert_eq!( - execute(&doc, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"__type": {"fields": [ - {"args": [{"defaultValue": null}]}, - {"args": [{"defaultValue": null}, {"defaultValue": null}]}, - ]}}), - vec![], - )), - ); - } + assert_eq!( + execute(&doc, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"__type": {"fields": [ + {"args": [{"defaultValue": null}]}, + {"args": [{"defaultValue": null}, {"defaultValue": null}]}, + ]}}), + vec![], + )), + ); } } @@ -1891,7 +1815,7 @@ mod default_argument { x: i32, } - #[graphql_interface(for = Human)] + #[graphql_interface_new(for = Human)] trait Character { async fn id( &self, @@ -1905,15 +1829,17 @@ mod default_argument { } } - #[derive(GraphQLObject)] - #[graphql(impl = CharacterValue)] struct Human { id: String, info: i32, } - #[graphql_interface] - impl Character for Human { + #[graphql_object(impl = CharacterValue)] + impl Human { + fn info(&self, coord: Point) -> i32 { + coord.x + } + async fn id(&self, first: String, second: String, third: String) -> String { format!("{}|{}&{}", first, second, third) } @@ -2030,7 +1956,7 @@ mod description_from_doc_comment { use super::*; /// Rust docs. - #[graphql_interface(for = Human)] + #[graphql_interface_new(for = Human)] trait Character { /// Rust `id` docs. /// Long. @@ -2044,13 +1970,6 @@ mod description_from_doc_comment { home_planet: String, } - #[graphql_interface] - impl Character for Human { - fn id(&self) -> &str { - &self.id - } - } - struct QueryRoot; #[graphql_object] @@ -2093,33 +2012,39 @@ mod description_from_doc_comment { mod deprecation_from_attr { use super::*; - #[graphql_interface(for = Human)] + #[graphql_interface_new(for = Human)] trait Character { fn id(&self) -> &str; #[deprecated] - fn a(&self) -> &str { - "a" - } + fn a(&self) -> &str; #[deprecated(note = "Use `id`.")] - fn b(&self) -> &str { - "b" - } + fn b(&self) -> &str; } - #[derive(GraphQLObject)] - #[graphql(impl = CharacterValue)] struct Human { id: String, home_planet: String, } - #[graphql_interface] - impl Character for Human { + #[graphql_object(impl = CharacterValue)] + impl Human { fn id(&self) -> &str { &self.id } + + fn human_planet(&self) -> &str { + &self.home_planet + } + + fn a() -> &'static str { + "a" + } + + fn b() -> String { + "b".to_owned() + } } struct QueryRoot; @@ -2225,7 +2150,7 @@ mod explicit_name_description_and_deprecation { use super::*; /// Rust docs. - #[graphql_interface(name = "MyChar", desc = "My character.", for = Human)] + #[graphql_interface_new(name = "MyChar", desc = "My character.", for = Human)] trait Character { /// Rust `id` docs. #[graphql(name = "myId", desc = "My character ID.", deprecated = "Not used.")] @@ -2234,27 +2159,33 @@ mod explicit_name_description_and_deprecation { #[graphql(deprecated)] #[deprecated(note = "Should be omitted.")] - fn a(&self) -> &str { - "a" - } + fn a(&self) -> &str; - fn b(&self) -> &str { - "b" - } + fn b(&self) -> &str; } - #[derive(GraphQLObject)] - #[graphql(impl = CharacterValue)] struct Human { id: String, home_planet: String, } - #[graphql_interface] - impl Character for Human { - fn id(&self, _: Option) -> &str { + #[graphql_object(impl = CharacterValue)] + impl Human { + fn my_id(&self, #[graphql(name = "myName")] _: Option) -> &str { &self.id } + + fn home_planet(&self) -> &str { + &self.home_planet + } + + fn a() -> String { + "a".to_owned() + } + + fn b() -> &'static str { + "b" + } } struct QueryRoot; diff --git a/integration_tests/juniper_tests/src/codegen/new_interface.rs b/integration_tests/juniper_tests/src/codegen/new_interface.rs index 95c0d465d..24117e331 100644 --- a/integration_tests/juniper_tests/src/codegen/new_interface.rs +++ b/integration_tests/juniper_tests/src/codegen/new_interface.rs @@ -6,31 +6,51 @@ use juniper::{ // -------------------------- -#[graphql_interface(for = [Human, Droid])] -trait Character<'me, A> { - async fn id<'a>(&'a self) -> &'a str; +#[derive(GraphQLInputObject, Debug)] +struct Point { + x: i32, } -#[derive(GraphQLObject)] -#[graphql(impl = CharacterValue<()>)] -struct Human { - id: String, - home_planet: String, +#[graphql_interface_new(for = Human)] +trait Character { + async fn id( + &self, + #[graphql(default)] first: String, + #[graphql(default = "second".to_string())] second: String, + #[graphql(default = "t")] third: String, + ) -> String; + + fn info(&self, #[graphql(default = Point { x: 1 })] coord: Point) -> i32 { + coord.x + } } -struct Droid { +struct Human { id: String, - primary_function: String, + info: i32, } -#[graphql_object(impl = CharacterValue<()>)] -impl Droid { - fn id(&self) -> &str { - &self.id +#[graphql_object(impl = CharacterValue)] +impl Human { + fn info(&self, _coord: Point) -> i32 { + self.info } - fn primary_function(&self) -> &str { - &self.primary_function + async fn id(&self, first: String, second: String, third: String) -> String { + format!("{}|{}&{}", first, second, third) + } +} + +struct QueryRoot; + +#[graphql_object] +impl QueryRoot { + fn character(&self) -> CharacterValue { + Human { + id: "human-32".to_string(), + info: 0, + } + .into() } } diff --git a/juniper_codegen/Cargo.toml b/juniper_codegen/Cargo.toml index acd1a33ee..190c91129 100644 --- a/juniper_codegen/Cargo.toml +++ b/juniper_codegen/Cargo.toml @@ -21,7 +21,7 @@ proc-macro = true proc-macro-error = "1.0.2" proc-macro2 = "1.0.1" quote = "1.0.3" -syn = { version = "1.0.60", features = ["extra-traits", "full", "parsing"], default-features = false } +syn = { version = "1.0.60", features = ["extra-traits", "full", "parsing", "visit"], default-features = false } url = "2.0" [dev-dependencies] diff --git a/juniper_codegen/src/graphql_interface/new.rs b/juniper_codegen/src/graphql_interface/new.rs index 3056b7175..7f159fb21 100644 --- a/juniper_codegen/src/graphql_interface/new.rs +++ b/juniper_codegen/src/graphql_interface/new.rs @@ -8,7 +8,7 @@ use std::{ convert::TryInto as _, }; -use proc_macro2::TokenStream; +use proc_macro2::{Ident, Span, TokenStream}; use quote::{format_ident, quote, ToTokens, TokenStreamExt as _}; use syn::{ ext::IdentExt as _, @@ -17,6 +17,7 @@ use syn::{ punctuated::Punctuated, spanned::Spanned as _, token, + visit::Visit, }; use crate::{ @@ -393,18 +394,36 @@ impl Definition { enum_generics }; - let enum_to_alias_generics = self - .attrs - .implementers - .iter() - .map(ToTokens::to_token_stream) - .chain(self.trait_generics.params.iter().map(|par| match par { - syn::GenericParam::Type(ty) => { - let par_ident = &ty.ident; - quote! { #par_ident } - } - rest => quote! { #rest }, - })); + let enum_to_alias_generics = { + let (lifetimes, rest) = self + .trait_generics + .params + .iter() + .partition::, _>(|par| matches!(par, syn::GenericParam::Lifetime(_))); + + lifetimes + .into_iter() + .map(|par| match par { + syn::GenericParam::Lifetime(def) => { + let lifetime = &def.lifetime; + quote! { #lifetime } + } + rest => quote! { #rest }, + }) + .chain( + self.attrs + .implementers + .iter() + .map(ToTokens::to_token_stream), + ) + .chain(rest.into_iter().map(|par| match par { + syn::GenericParam::Type(ty) => { + let par_ident = &ty.ident; + quote! { #par_ident } + } + rest => quote! { #rest }, + })) + }; let from_impls = self .attrs @@ -556,7 +575,7 @@ impl Definition { let fields = [ #( #fields_meta, )* ]; - registry.build_interface_type::<#ty>(info, &fields) + registry.build_interface_type::<#ty#ty_generics>(info, &fields) #description .into_meta() } @@ -766,6 +785,30 @@ impl Definition { /// TODO fn impl_fields(&self, for_async: bool) -> TokenStream { + struct ReplaceGenericsForConst(syn::AngleBracketedGenericArguments); + + impl Visit<'_> for ReplaceGenericsForConst { + fn visit_generic_param(&mut self, param: &syn::GenericParam) { + match param { + syn::GenericParam::Lifetime(_) => self.0.args.push(parse_quote!( 'static )), + syn::GenericParam::Type(ty) => { + if ty.default.is_none() { + self.0.args.push(parse_quote!(())); + } + + // let ty = ty + // .default + // .as_ref() + // .map_or_else(|| parse_quote!(()), |def| parse_quote!( #def )); + // self.0.args.push(ty); + } + syn::GenericParam::Const(_) => { + unimplemented!() + } + } + } + } + let scalar = &self.scalar; let const_scalar = match scalar { scalar::Type::Concrete(ty) => ty.to_token_stream(), @@ -835,6 +878,12 @@ impl Definition { }) .unzip(); + let const_ty_generics = { + let mut visitor = ReplaceGenericsForConst(parse_quote!( <> )); + visitor.visit_generics(&self.trait_generics); + visitor.0 + }; + let unreachable_arm = (self.attrs.implementers.is_empty() || !self.trait_generics.params.is_empty()).then(|| { quote! { _ => unreachable!() } }); @@ -878,7 +927,7 @@ impl Definition { >>::WRAPPED_VALUE, )); const _: () = ::std::assert!(::juniper::macros::helper::is_valid_field_args( - <#ty as ::juniper::macros::helper::#trait_name< + <#ty#const_ty_generics as ::juniper::macros::helper::#trait_name< #const_scalar, { ::juniper::macros::helper::fnv1a128(#name) }, >>::ARGUMENTS, From 5040d688d417890b2ad8b8d7bc5cfa1118c898b1 Mon Sep 17 00:00:00 2001 From: ilslv Date: Wed, 29 Dec 2021 15:28:50 +0300 Subject: [PATCH 14/53] WIP (move to the new tests volume 3) --- .../src/codegen/interface_attr.rs | 473 +----------------- .../src/codegen/new_interface.rs | 51 +- .../juniper_tests/src/codegen/object_attr.rs | 2 +- juniper_codegen/Cargo.toml | 2 +- juniper_codegen/src/graphql_interface/new.rs | 49 +- 5 files changed, 66 insertions(+), 511 deletions(-) diff --git a/integration_tests/juniper_tests/src/codegen/interface_attr.rs b/integration_tests/juniper_tests/src/codegen/interface_attr.rs index 108d61cc7..3d1a3dc3a 100644 --- a/integration_tests/juniper_tests/src/codegen/interface_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/interface_attr.rs @@ -2336,7 +2336,7 @@ mod explicit_name_description_and_deprecation { mod renamed_all_fields_and_args { use super::*; - #[graphql_interface(rename_all = "none", for = Human)] + #[graphql_interface_new(rename_all = "none", for = Human)] trait Character { fn id(&self) -> &str { "human-32" @@ -2368,9 +2368,6 @@ mod renamed_all_fields_and_args { } } - #[graphql_interface] - impl Character for Human {} - struct QueryRoot; #[graphql_object] @@ -2437,60 +2434,26 @@ mod renamed_all_fields_and_args { mod explicit_scalar { use super::*; - #[graphql_interface(for = [Human, Droid])] - #[graphql_interface(scalar = DefaultScalarValue)] + #[graphql_interface_new(for = [Human, Droid])] + #[graphql_interface_new(scalar = DefaultScalarValue)] trait Character { fn id(&self) -> &str; } - #[graphql_interface(dyn = DynHero, for = [Human, Droid])] - #[graphql_interface(scalar = DefaultScalarValue)] - trait Hero { - async fn info(&self) -> &str; - } - #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue, DynHero], scalar = DefaultScalarValue)] + #[graphql(impl = CharacterValue, scalar = DefaultScalarValue)] struct Human { id: String, home_planet: String, } - #[graphql_interface(scalar = DefaultScalarValue)] - impl Character for Human { - fn id(&self) -> &str { - &self.id - } - } - - #[graphql_interface(dyn, scalar = DefaultScalarValue)] - impl Hero for Human { - async fn info(&self) -> &str { - &self.home_planet - } - } - #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue, DynHero], scalar = DefaultScalarValue)] + #[graphql(impl = CharacterValue, scalar = DefaultScalarValue)] struct Droid { id: String, primary_function: String, } - #[graphql_interface(scalar = DefaultScalarValue)] - impl Character for Droid { - fn id(&self) -> &str { - &self.id - } - } - - #[graphql_interface(dyn, scalar = DefaultScalarValue)] - impl Hero for Droid { - async fn info(&self) -> &str { - &self.primary_function - } - } - #[derive(Clone, Copy)] enum QueryRoot { Human, @@ -2513,20 +2476,6 @@ mod explicit_scalar { .into(), } } - - fn hero(&self) -> Box> { - let ch: Box> = match self { - Self::Human => Box::new(Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), - }), - Self::Droid => Box::new(Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), - }), - }; - ch - } } #[tokio::test] @@ -2573,50 +2522,6 @@ mod explicit_scalar { ); } - #[tokio::test] - async fn dyn_resolves_human() { - const DOC: &str = r#"{ - hero { - ... on Human { - humanId: id - homePlanet - } - } - }"#; - - let schema = schema(QueryRoot::Human); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"hero": {"humanId": "human-32", "homePlanet": "earth"}}), - vec![], - )), - ); - } - - #[tokio::test] - async fn dyn_resolves_droid() { - const DOC: &str = r#"{ - hero { - ... on Droid { - droidId: id - primaryFunction - } - } - }"#; - - let schema = schema(QueryRoot::Droid); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"hero": {"droidId": "droid-99", "primaryFunction": "run"}}), - vec![], - )), - ); - } - #[tokio::test] async fn enum_resolves_id_field() { const DOC: &str = r#"{ @@ -2638,25 +2543,6 @@ mod explicit_scalar { ); } } - - #[tokio::test] - async fn dyn_resolves_info_field() { - const DOC: &str = r#"{ - hero { - info - } - }"#; - - for (root, expected_info) in &[(QueryRoot::Human, "earth"), (QueryRoot::Droid, "run")] { - let schema = schema(*root); - - let expected_info: &str = *expected_info; - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"hero": {"info": expected_info}}), vec![])), - ); - } - } } mod custom_scalar { @@ -2664,59 +2550,25 @@ mod custom_scalar { use super::*; - #[graphql_interface(for = [Human, Droid], scalar = MyScalarValue)] + #[graphql_interface_new(for = [Human, Droid], scalar = MyScalarValue)] trait Character { async fn id(&self) -> &str; } - #[graphql_interface(dyn = DynHero, for = [Human, Droid])] - #[graphql_interface(scalar = MyScalarValue)] - trait Hero { - async fn info(&self) -> &str; - } - #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue, DynHero], scalar = MyScalarValue)] + #[graphql(impl = CharacterValue, scalar = MyScalarValue)] struct Human { id: String, home_planet: String, } - #[graphql_interface(scalar = MyScalarValue)] - impl Character for Human { - async fn id(&self) -> &str { - &self.id - } - } - - #[graphql_interface(dyn, scalar = MyScalarValue)] - impl Hero for Human { - async fn info(&self) -> &str { - &self.home_planet - } - } - #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue, DynHero], scalar = MyScalarValue)] + #[graphql(impl = CharacterValue, scalar = MyScalarValue)] struct Droid { id: String, primary_function: String, } - #[graphql_interface(scalar = MyScalarValue)] - impl Character for Droid { - async fn id(&self) -> &str { - &self.id - } - } - - #[graphql_interface(dyn, scalar = MyScalarValue)] - impl Hero for Droid { - async fn info(&self) -> &str { - &self.primary_function - } - } - #[derive(Clone, Copy)] enum QueryRoot { Human, @@ -2739,20 +2591,6 @@ mod custom_scalar { .into(), } } - - fn hero(&self) -> Box> { - let ch: Box> = match self { - Self::Human => Box::new(Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), - }), - Self::Droid => Box::new(Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), - }), - }; - ch - } } #[tokio::test] @@ -2799,50 +2637,6 @@ mod custom_scalar { ); } - #[tokio::test] - async fn dyn_resolves_human() { - const DOC: &str = r#"{ - hero { - ... on Human { - humanId: id - homePlanet - } - } - }"#; - - let schema = schema_with_scalar::(QueryRoot::Human); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"hero": {"humanId": "human-32", "homePlanet": "earth"}}), - vec![], - )), - ); - } - - #[tokio::test] - async fn dyn_resolves_droid() { - const DOC: &str = r#"{ - hero { - ... on Droid { - droidId: id - primaryFunction - } - } - }"#; - - let schema = schema_with_scalar::(QueryRoot::Droid); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"hero": {"droidId": "droid-99", "primaryFunction": "run"}}), - vec![], - )), - ); - } - #[tokio::test] async fn enum_resolves_id_field() { const DOC: &str = r#"{ @@ -2864,82 +2658,30 @@ mod custom_scalar { ); } } - - #[tokio::test] - async fn dyn_resolves_info_field() { - const DOC: &str = r#"{ - hero { - info - } - }"#; - - for (root, expected_info) in &[(QueryRoot::Human, "earth"), (QueryRoot::Droid, "run")] { - let schema = schema_with_scalar::(*root); - - let expected_info: &str = *expected_info; - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"hero": {"info": expected_info}}), vec![])), - ); - } - } } mod explicit_generic_scalar { use super::*; - #[graphql_interface(for = [Human, Droid], scalar = S)] + #[graphql_interface_new(for = [Human, Droid], scalar = S)] trait Character { fn id(&self) -> FieldResult<&str, S>; } - #[graphql_interface(dyn = DynHero, for = [Human, Droid], scalar = S)] - trait Hero { - async fn info(&self) -> FieldResult<&str, S>; - } - #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue<__S>, DynHero<__S>])] + #[graphql(impl = CharacterValue<__S>)] struct Human { id: String, home_planet: String, } - #[graphql_interface(scalar = S)] - impl Character for Human { - fn id(&self) -> FieldResult<&str, S> { - Ok(&self.id) - } - } - - #[graphql_interface(dyn, scalar = S)] - impl Hero for Human { - async fn info(&self) -> FieldResult<&str, S> { - Ok(&self.home_planet) - } - } - #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue<__S>, DynHero<__S>])] + #[graphql(impl = CharacterValue<__S>)] struct Droid { id: String, primary_function: String, } - #[graphql_interface(scalar = S)] - impl Character for Droid { - fn id(&self) -> FieldResult<&str, S> { - Ok(&self.id) - } - } - - #[graphql_interface(dyn, scalar = S)] - impl Hero for Droid { - async fn info(&self) -> FieldResult<&str, S> { - Ok(&self.primary_function) - } - } - #[derive(Clone, Copy)] enum QueryRoot { Human, @@ -2962,20 +2704,6 @@ mod explicit_generic_scalar { .into(), } } - - fn hero(&self) -> Box> { - let ch: Box> = match self { - Self::Human => Box::new(Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), - }), - Self::Droid => Box::new(Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), - }), - }; - ch - } } #[tokio::test] @@ -3022,50 +2750,6 @@ mod explicit_generic_scalar { ); } - #[tokio::test] - async fn dyn_resolves_human() { - const DOC: &str = r#"{ - hero { - ... on Human { - humanId: id - homePlanet - } - } - }"#; - - let schema = schema(QueryRoot::Human); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"hero": {"humanId": "human-32", "homePlanet": "earth"}}), - vec![], - )), - ); - } - - #[tokio::test] - async fn dyn_resolves_droid() { - const DOC: &str = r#"{ - hero { - ... on Droid { - droidId: id - primaryFunction - } - } - }"#; - - let schema = schema(QueryRoot::Droid); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"hero": {"droidId": "droid-99", "primaryFunction": "run"}}), - vec![], - )), - ); - } - #[tokio::test] async fn enum_resolves_id_field() { const DOC: &str = r#"{ @@ -3087,82 +2771,30 @@ mod explicit_generic_scalar { ); } } - - #[tokio::test] - async fn dyn_resolves_info_field() { - const DOC: &str = r#"{ - hero { - info - } - }"#; - - for (root, expected_info) in &[(QueryRoot::Human, "earth"), (QueryRoot::Droid, "run")] { - let schema = schema(*root); - - let expected_info: &str = *expected_info; - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"hero": {"info": expected_info}}), vec![])), - ); - } - } } mod bounded_generic_scalar { use super::*; - #[graphql_interface(for = [Human, Droid], scalar = S: ScalarValue + Clone)] + #[graphql_interface_new(for = [Human, Droid], scalar = S: ScalarValue + Clone)] trait Character { fn id(&self) -> &str; } - #[graphql_interface(dyn = DynHero, for = [Human, Droid], scalar = S: ScalarValue + Clone)] - trait Hero { - async fn info(&self) -> &str; - } - #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue, DynHero], scalar = S: ScalarValue + Clone)] + #[graphql(impl = CharacterValue, scalar = S: ScalarValue + Clone)] struct Human { id: String, home_planet: String, } - #[graphql_interface(scalar = S: ScalarValue + Clone)] - impl Character for Human { - fn id(&self) -> &str { - &self.id - } - } - - #[graphql_interface(dyn, scalar = S: ScalarValue + Clone)] - impl Hero for Human { - async fn info(&self) -> &str { - &self.home_planet - } - } - #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue, DynHero<__S>])] + #[graphql(impl = CharacterValue)] struct Droid { id: String, primary_function: String, } - #[graphql_interface(scalar = S: ScalarValue + Clone)] - impl Character for Droid { - fn id(&self) -> &str { - &self.id - } - } - - #[graphql_interface(dyn, scalar = S: ScalarValue + Clone)] - impl Hero for Droid { - async fn info(&self) -> &str { - &self.primary_function - } - } - #[derive(Clone, Copy)] enum QueryRoot { Human, @@ -3185,20 +2817,6 @@ mod bounded_generic_scalar { .into(), } } - - fn hero(&self) -> Box> { - let ch: Box> = match self { - Self::Human => Box::new(Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), - }), - Self::Droid => Box::new(Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), - }), - }; - ch - } } #[tokio::test] @@ -3245,50 +2863,6 @@ mod bounded_generic_scalar { ); } - #[tokio::test] - async fn dyn_resolves_human() { - const DOC: &str = r#"{ - hero { - ... on Human { - humanId: id - homePlanet - } - } - }"#; - - let schema = schema(QueryRoot::Human); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"hero": {"humanId": "human-32", "homePlanet": "earth"}}), - vec![], - )), - ); - } - - #[tokio::test] - async fn dyn_resolves_droid() { - const DOC: &str = r#"{ - hero { - ... on Droid { - droidId: id - primaryFunction - } - } - }"#; - - let schema = schema(QueryRoot::Droid); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"hero": {"droidId": "droid-99", "primaryFunction": "run"}}), - vec![], - )), - ); - } - #[tokio::test] async fn enum_resolves_id_field() { const DOC: &str = r#"{ @@ -3310,25 +2884,6 @@ mod bounded_generic_scalar { ); } } - - #[tokio::test] - async fn dyn_resolves_info_field() { - const DOC: &str = r#"{ - hero { - info - } - }"#; - - for (root, expected_info) in &[(QueryRoot::Human, "earth"), (QueryRoot::Droid, "run")] { - let schema = schema(*root); - - let expected_info: &str = *expected_info; - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"hero": {"info": expected_info}}), vec![])), - ); - } - } } mod explicit_custom_context { diff --git a/integration_tests/juniper_tests/src/codegen/new_interface.rs b/integration_tests/juniper_tests/src/codegen/new_interface.rs index 24117e331..a642822d2 100644 --- a/integration_tests/juniper_tests/src/codegen/new_interface.rs +++ b/integration_tests/juniper_tests/src/codegen/new_interface.rs @@ -6,52 +6,23 @@ use juniper::{ // -------------------------- -#[derive(GraphQLInputObject, Debug)] -struct Point { - x: i32, -} - -#[graphql_interface_new(for = Human)] -trait Character { - async fn id( - &self, - #[graphql(default)] first: String, - #[graphql(default = "second".to_string())] second: String, - #[graphql(default = "t")] third: String, - ) -> String; - - fn info(&self, #[graphql(default = Point { x: 1 })] coord: Point) -> i32 { - coord.x - } +#[graphql_interface_new(for = [Human, Droid], scalar = S)] +trait Character { + fn id(&self) -> FieldResult<&str, S>; } +#[derive(GraphQLObject)] +#[graphql(impl = CharacterValue<__S>)] struct Human { id: String, - info: i32, -} - -#[graphql_object(impl = CharacterValue)] -impl Human { - fn info(&self, _coord: Point) -> i32 { - self.info - } - - async fn id(&self, first: String, second: String, third: String) -> String { - format!("{}|{}&{}", first, second, third) - } + home_planet: String, } -struct QueryRoot; - -#[graphql_object] -impl QueryRoot { - fn character(&self) -> CharacterValue { - Human { - id: "human-32".to_string(), - info: 0, - } - .into() - } +#[derive(GraphQLObject)] +#[graphql(impl = CharacterValue<__S>)] +struct Droid { + id: String, + primary_function: String, } // -------------- diff --git a/integration_tests/juniper_tests/src/codegen/object_attr.rs b/integration_tests/juniper_tests/src/codegen/object_attr.rs index 304ba34c5..76a1c8542 100644 --- a/integration_tests/juniper_tests/src/codegen/object_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/object_attr.rs @@ -2022,7 +2022,7 @@ mod switched_context { async fn switch_res_opt<'e, S: ScalarValue>( &self, - executor: &'e Executor<'_, '_, CustomContext, S>, + executor: &'e Executor<'e, 'e, CustomContext, S>, ) -> FieldResult> { Ok(Some((executor.context(), Droid { id: 3 }))) } diff --git a/juniper_codegen/Cargo.toml b/juniper_codegen/Cargo.toml index 190c91129..746c54944 100644 --- a/juniper_codegen/Cargo.toml +++ b/juniper_codegen/Cargo.toml @@ -21,7 +21,7 @@ proc-macro = true proc-macro-error = "1.0.2" proc-macro2 = "1.0.1" quote = "1.0.3" -syn = { version = "1.0.60", features = ["extra-traits", "full", "parsing", "visit"], default-features = false } +syn = { version = "1.0.60", features = ["extra-traits", "full", "parsing", "visit", "visit-mut"], default-features = false } url = "2.0" [dev-dependencies] diff --git a/juniper_codegen/src/graphql_interface/new.rs b/juniper_codegen/src/graphql_interface/new.rs index 7f159fb21..8e5e386a8 100644 --- a/juniper_codegen/src/graphql_interface/new.rs +++ b/juniper_codegen/src/graphql_interface/new.rs @@ -18,6 +18,7 @@ use syn::{ spanned::Spanned as _, token, visit::Visit, + visit_mut::VisitMut, }; use crate::{ @@ -785,9 +786,9 @@ impl Definition { /// TODO fn impl_fields(&self, for_async: bool) -> TokenStream { - struct ReplaceGenericsForConst(syn::AngleBracketedGenericArguments); + struct GenericsForConst(syn::AngleBracketedGenericArguments); - impl Visit<'_> for ReplaceGenericsForConst { + impl Visit<'_> for GenericsForConst { fn visit_generic_param(&mut self, param: &syn::GenericParam) { match param { syn::GenericParam::Lifetime(_) => self.0.args.push(parse_quote!( 'static )), @@ -795,12 +796,6 @@ impl Definition { if ty.default.is_none() { self.0.args.push(parse_quote!(())); } - - // let ty = ty - // .default - // .as_ref() - // .map_or_else(|| parse_quote!(()), |def| parse_quote!( #def )); - // self.0.args.push(ty); } syn::GenericParam::Const(_) => { unimplemented!() @@ -809,6 +804,39 @@ impl Definition { } } + struct ReplaceGenericsForConst<'a>(&'a syn::Generics); + + impl<'a> VisitMut for ReplaceGenericsForConst<'a> { + fn visit_generic_argument_mut(&mut self, arg: &mut syn::GenericArgument) { + match arg { + syn::GenericArgument::Lifetime(lf) => { + *lf = parse_quote!( 'static ); + } + syn::GenericArgument::Type(ty) => { + let is_generic = self + .0 + .params + .iter() + .filter_map(|par| match par { + syn::GenericParam::Type(ty) => Some(&ty.ident), + _ => None, + }) + .any(|par| { + let par = quote! { #par }.to_string(); + let ty = quote! { #ty }.to_string(); + println!("{} == {}", par, ty); + par == ty + }); + + if is_generic { + *ty = parse_quote!(()); + } + } + _ => {} + } + } + } + let scalar = &self.scalar; let const_scalar = match scalar { scalar::Type::Concrete(ty) => ty.to_token_stream(), @@ -862,7 +890,8 @@ impl Definition { }; let name = &field.name; - let return_ty = &field.ty; + let mut return_ty = field.ty.clone(); + ReplaceGenericsForConst(&generics).visit_type_mut(&mut return_ty); let (args_tys, args_names): (Vec<_>, Vec<_>) = field .arguments @@ -879,7 +908,7 @@ impl Definition { .unzip(); let const_ty_generics = { - let mut visitor = ReplaceGenericsForConst(parse_quote!( <> )); + let mut visitor = GenericsForConst(parse_quote!( <> )); visitor.visit_generics(&self.trait_generics); visitor.0 }; From f5533e3ec5971cd16e956e5cb12e398e74e6f67a Mon Sep 17 00:00:00 2001 From: ilslv Date: Wed, 29 Dec 2021 16:48:04 +0300 Subject: [PATCH 15/53] WIP (move to the new tests volume 4) --- .../src/codegen/interface_attr.rs | 848 +++--------------- .../src/codegen/new_interface.rs | 50 +- .../juniper_tests/src/codegen/object_attr.rs | 2 +- juniper_codegen/src/graphql_interface/new.rs | 7 +- 4 files changed, 193 insertions(+), 714 deletions(-) diff --git a/integration_tests/juniper_tests/src/codegen/interface_attr.rs b/integration_tests/juniper_tests/src/codegen/interface_attr.rs index 3d1a3dc3a..4fc7dfa49 100644 --- a/integration_tests/juniper_tests/src/codegen/interface_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/interface_attr.rs @@ -2893,7 +2893,7 @@ mod explicit_custom_context { impl juniper::Context for CustomContext {} - #[graphql_interface(for = [Human, Droid], context = CustomContext)] + #[graphql_interface_new(for = [Human, Droid], context = CustomContext)] trait Character { async fn id<'a>(&'a self, context: &CustomContext) -> &'a str; @@ -2902,86 +2902,50 @@ mod explicit_custom_context { fn more<'c>(&'c self, #[graphql(context)] custom: &CustomContext) -> &'c str; } - #[graphql_interface(dyn = DynHero, for = [Human, Droid])] - #[graphql_interface(context = CustomContext)] - trait Hero { - async fn id<'a>(&'a self, context: &CustomContext) -> &'a str; - - async fn info<'b>(&'b self, ctx: &()) -> &'b str; - - fn more<'c>(&'c self, #[graphql(context)] custom: &CustomContext) -> &'c str; - } - - #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue, DynHero<__S>], context = CustomContext)] struct Human { id: String, home_planet: String, } - #[graphql_interface] - impl Character for Human { - async fn id<'a>(&'a self, _: &CustomContext) -> &'a str { + #[graphql_object(impl = CharacterValue, context = CustomContext)] + impl Human { + fn id<'a>(&'a self, _context: &CustomContext) -> &'a str { &self.id } - async fn info<'b>(&'b self, _: &()) -> &'b str { + fn home_planet(&self) -> &str { &self.home_planet } - fn more(&self, _: &CustomContext) -> &'static str { - "human" - } - } - - #[graphql_interface(dyn)] - impl Hero for Human { - async fn id<'a>(&'a self, _: &CustomContext) -> &'a str { - &self.id - } - - async fn info<'b>(&'b self, _: &()) -> &'b str { + fn info<'b>(&'b self, _ctx: &()) -> &'b str { &self.home_planet } - fn more(&self, _: &CustomContext) -> &'static str { + fn more(&self, #[graphql(context)] _: &CustomContext) -> &'static str { "human" } } - #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue, DynHero<__S>], context = CustomContext)] struct Droid { id: String, primary_function: String, } - #[graphql_interface] - impl Character for Droid { - async fn id<'a>(&'a self, _: &CustomContext) -> &'a str { + #[graphql_object(impl = CharacterValue, context = CustomContext)] + impl Droid { + async fn id<'a>(&'a self) -> &'a str { &self.id } - async fn info<'b>(&'b self, _: &()) -> &'b str { + fn primary_function(&self) -> &str { &self.primary_function } - fn more(&self, _: &CustomContext) -> &'static str { - "droid" - } - } - - #[graphql_interface(dyn)] - impl Hero for Droid { - async fn id<'a>(&'a self, _: &CustomContext) -> &'a str { - &self.id - } - - async fn info<'b>(&'b self, _: &()) -> &'b str { + async fn info<'b>(&'b self) -> &'b str { &self.primary_function } - fn more(&self, _: &CustomContext) -> &'static str { + fn more(&self) -> &'static str { "droid" } } @@ -3008,20 +2972,6 @@ mod explicit_custom_context { .into(), } } - - fn hero(&self) -> Box> { - let ch: Box> = match self { - Self::Human => Box::new(Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), - }), - Self::Droid => Box::new(Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), - }), - }; - ch - } } #[tokio::test] @@ -3069,86 +3019,35 @@ mod explicit_custom_context { } #[tokio::test] - async fn dyn_resolves_human() { - const DOC: &str = r#"{ - hero { - ... on Human { - humanId: id - homePlanet - } - } - }"#; - - let schema = schema(QueryRoot::Human); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &CustomContext).await, - Ok(( - graphql_value!({"hero": {"humanId": "human-32", "homePlanet": "earth"}}), - vec![], - )), - ); - } - - #[tokio::test] - async fn dyn_resolves_droid() { - const DOC: &str = r#"{ - hero { - ... on Droid { - droidId: id - primaryFunction - } + async fn resolves_fields() { + let doc = r#"{ + character { + id + info + more } }"#; - let schema = schema(QueryRoot::Droid); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &CustomContext).await, - Ok(( - graphql_value!({"hero": {"droidId": "droid-99", "primaryFunction": "run"}}), - vec![], - )), - ); - } + for (root, expected_id, expected_info, expexted_more) in &[ + (QueryRoot::Human, "human-32", "earth", "human"), + (QueryRoot::Droid, "droid-99", "run", "droid"), + ] { + let schema = schema(*root); - #[tokio::test] - async fn resolves_fields() { - for interface in &["character", "hero"] { - let doc = format!( - r#"{{ - {} {{ - id - info - more - }} - }}"#, - interface, + let expected_id: &str = *expected_id; + let expected_info: &str = *expected_info; + let expexted_more: &str = *expexted_more; + assert_eq!( + execute(&doc, None, &schema, &graphql_vars! {}, &CustomContext).await, + Ok(( + graphql_value!({"character": { + "id": expected_id, + "info": expected_info, + "more": expexted_more, + }}), + vec![], + )), ); - - let expected_interface: &str = *interface; - - for (root, expected_id, expected_info, expexted_more) in &[ - (QueryRoot::Human, "human-32", "earth", "human"), - (QueryRoot::Droid, "droid-99", "run", "droid"), - ] { - let schema = schema(*root); - - let expected_id: &str = *expected_id; - let expected_info: &str = *expected_info; - let expexted_more: &str = *expexted_more; - assert_eq!( - execute(&doc, None, &schema, &graphql_vars! {}, &CustomContext).await, - Ok(( - graphql_value!({expected_interface: { - "id": expected_id, - "info": expected_info, - "more": expexted_more, - }}), - vec![], - )), - ); - } } } } @@ -3160,74 +3059,49 @@ mod inferred_custom_context_from_field { impl juniper::Context for CustomContext {} - #[graphql_interface(for = [Human, Droid])] + #[graphql_interface_new(for = [Human, Droid])] trait Character { fn id<'a>(&self, context: &'a CustomContext) -> &'a str; fn info<'b>(&'b self, context: &()) -> &'b str; } - #[graphql_interface(dyn = DynHero, for = [Human, Droid])] - trait Hero { - async fn id<'a>(&self, context: &'a CustomContext) -> &'a str; - - async fn info<'b>(&'b self, context: &()) -> &'b str; - } - - #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue, DynHero<__S>], context = CustomContext)] struct Human { id: String, home_planet: String, } - #[graphql_interface] - impl Character for Human { + #[graphql_object(impl = CharacterValue, context = CustomContext)] + impl Human { fn id<'a>(&self, ctx: &'a CustomContext) -> &'a str { &ctx.0 } - fn info<'b>(&'b self, _: &()) -> &'b str { + fn home_planet(&self) -> &str { &self.home_planet } - } - - #[graphql_interface(dyn)] - impl Hero for Human { - async fn id<'a>(&self, ctx: &'a CustomContext) -> &'a str { - &ctx.0 - } - async fn info<'b>(&'b self, _: &()) -> &'b str { + fn info<'b>(&'b self, _context: &()) -> &'b str { &self.home_planet } } - #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue, DynHero<__S>], context = CustomContext)] struct Droid { id: String, primary_function: String, } - #[graphql_interface] - impl Character for Droid { + #[graphql_object(impl = CharacterValue, context = CustomContext)] + impl Droid { fn id<'a>(&self, ctx: &'a CustomContext) -> &'a str { &ctx.0 } - fn info<'b>(&'b self, _: &()) -> &'b str { + fn primary_function(&self) -> &str { &self.primary_function } - } - - #[graphql_interface(dyn)] - impl Hero for Droid { - async fn id<'a>(&self, ctx: &'a CustomContext) -> &'a str { - &ctx.0 - } - async fn info<'b>(&'b self, _: &()) -> &'b str { + fn info<'b>(&'b self) -> &'b str { &self.primary_function } } @@ -3254,20 +3128,6 @@ mod inferred_custom_context_from_field { .into(), } } - - fn hero(&self) -> Box> { - let ch: Box> = match self { - Self::Human => Box::new(Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), - }), - Self::Droid => Box::new(Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), - }), - }; - ch - } } #[tokio::test] @@ -3287,7 +3147,7 @@ mod inferred_custom_context_from_field { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &ctx).await, Ok(( - graphql_value!({"character": {"humanId": "human-32", "homePlanet": "earth"}}), + graphql_value!({"character": {"humanId": "in-ctx", "homePlanet": "earth"}}), vec![], )), ); @@ -3310,189 +3170,120 @@ mod inferred_custom_context_from_field { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &ctx).await, Ok(( - graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}), + graphql_value!({"character": {"droidId": "in-droid", "primaryFunction": "run"}}), vec![], )), ); } #[tokio::test] - async fn dyn_resolves_human() { - const DOC: &str = r#"{ - hero { - ... on Human { - humanId: id - homePlanet - } + async fn resolves_fields() { + let doc = r#"{ + character { + id + info } }"#; - let schema = schema(QueryRoot::Human); - let ctx = CustomContext("in-ctx".into()); + for (root, expected_id, expected_info) in &[ + (QueryRoot::Human, "human-ctx", "earth"), + (QueryRoot::Droid, "droid-ctx", "run"), + ] { + let schema = schema(*root); + let ctx = CustomContext(expected_id.to_string()); - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &ctx).await, - Ok(( - graphql_value!({"hero": {"humanId": "human-32", "homePlanet": "earth"}}), - vec![], - )), - ); + let expected_id: &str = *expected_id; + let expected_info: &str = *expected_info; + assert_eq!( + execute(&doc, None, &schema, &graphql_vars! {}, &ctx).await, + Ok(( + graphql_value!({"character": { + "id": expected_id, + "info": expected_info, + }}), + vec![], + )), + ); + } } +} - #[tokio::test] - async fn dyn_resolves_droid() { - const DOC: &str = r#"{ - hero { - ... on Droid { - droidId: id - primaryFunction - } - } - }"#; +mod executor { + use juniper::LookAheadMethods as _; - let schema = schema(QueryRoot::Droid); - let ctx = CustomContext("in-droid".into()); + use super::*; - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &ctx).await, - Ok(( - graphql_value!({"hero": {"droidId": "droid-99", "primaryFunction": "run"}}), - vec![], - )), - ); + #[graphql_interface_new(for = [Human, Droid], scalar = S)] + trait Character { + async fn id<'a>(&self, executor: &'a Executor<'_, '_, (), S>) -> &'a str + where + S: Send + Sync, + { + executor.look_ahead().field_name() + } + + async fn info<'b>( + &'b self, + arg: Option, + #[graphql(executor)] another: &Executor<'_, '_, (), S>, + ) -> &'b str + where + S: Send + Sync; } - #[tokio::test] - async fn resolves_fields() { - for interface in &["character", "hero"] { - let doc = format!( - r#"{{ - {} {{ - id - info - }} - }}"#, - interface, - ); + struct Human { + id: String, + home_planet: String, + } - let expected_interface: &str = *interface; - - for (root, expected_id, expected_info) in &[ - (QueryRoot::Human, "human-ctx", "earth"), - (QueryRoot::Droid, "droid-ctx", "run"), - ] { - let schema = schema(*root); - let ctx = CustomContext(expected_id.to_string()); - - let expected_id: &str = *expected_id; - let expected_info: &str = *expected_info; - assert_eq!( - execute(&doc, None, &schema, &graphql_vars! {}, &ctx).await, - Ok(( - graphql_value!({expected_interface: { - "id": expected_id, - "info": expected_info, - }}), - vec![], - )), - ); - } + #[graphql_object(impl = CharacterValue<__S>)] + impl Human { + async fn id<'a, S: ScalarValue>(&self, executor: &'a Executor<'_, '_, (), S>) -> &'a str { + executor.look_ahead().field_name() } - } -} -mod inferred_custom_context_from_downcast { - use super::*; + fn home_planet(&self) -> &str { + &self.home_planet + } - struct Database { - droid: Option, + async fn info<'b>(&'b self, _arg: Option) -> &'b str { + &self.home_planet + } } - impl juniper::Context for Database {} - - #[graphql_interface(for = [Human, Droid])] - trait Character { - #[graphql(downcast)] - fn as_human<'s>(&'s self, _: &Database) -> Option<&'s Human>; - - async fn id(&self) -> &str; - } - - #[graphql_interface(dyn = DynHero, for = [Human, Droid])] - trait Hero { - #[graphql(downcast)] - fn as_droid<'db>(&self, db: &'db Database) -> Option<&'db Droid>; - - async fn info(&self) -> &str; - } - - #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue, DynHero<__S>], context = Database)] - struct Human { - id: String, - home_planet: String, - } - - #[graphql_interface] - impl Character for Human { - fn as_human<'s>(&'s self, _: &Database) -> Option<&'s Human> { - Some(self) - } - - async fn id(&self) -> &str { - &self.id - } - } - - #[graphql_interface(dyn)] - impl Hero for Human { - fn as_droid<'db>(&self, _: &'db Database) -> Option<&'db Droid> { - None - } - - async fn info(&self) -> &str { - &self.home_planet - } - } - - #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue, DynHero<__S>], context = Database)] struct Droid { id: String, primary_function: String, } - #[graphql_interface] - impl Character for Droid { - fn as_human<'s>(&'s self, _: &Database) -> Option<&'s Human> { - None - } - - async fn id(&self) -> &str { - &self.id + #[graphql_object(impl = CharacterValue<__S>)] + impl Droid { + fn id<'a, S: ScalarValue>(&self, executor: &'a Executor<'_, '_, (), S>) -> &'a str { + executor.look_ahead().field_name() } - } - #[graphql_interface(dyn)] - impl Hero for Droid { - fn as_droid<'db>(&self, db: &'db Database) -> Option<&'db Droid> { - db.droid.as_ref() + fn primary_function(&self) -> &str { + &self.primary_function } - async fn info(&self) -> &str { + async fn info<'b, S: ScalarValue>( + &'b self, + _arg: Option, + _executor: &Executor<'_, '_, (), S>, + ) -> &'b str { &self.primary_function } } - #[derive(Clone)] + #[derive(Clone, Copy)] enum QueryRoot { Human, Droid, } - #[graphql_object(context = Database, scalar = S: ScalarValue + Send + Sync)] + #[graphql_object(scalar = DefaultScalarValue)] impl QueryRoot { - fn character(&self) -> CharacterValue { + fn character(&self) -> CharacterValue { match self { Self::Human => Human { id: "human-32".to_string(), @@ -3506,20 +3297,6 @@ mod inferred_custom_context_from_downcast { .into(), } } - - fn hero(&self) -> Box> { - let ch: Box> = match self { - Self::Human => Box::new(Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), - }), - Self::Droid => Box::new(Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), - }), - }; - ch - } } #[tokio::test] @@ -3534,12 +3311,11 @@ mod inferred_custom_context_from_downcast { }"#; let schema = schema(QueryRoot::Human); - let db = Database { droid: None }; assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &db).await, + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( - graphql_value!({"character": {"humanId": "human-32", "homePlanet": "earth"}}), + graphql_value!({"character": {"humanId": "humanId", "homePlanet": "earth"}}), vec![], )), ); @@ -3557,301 +3333,48 @@ mod inferred_custom_context_from_downcast { }"#; let schema = schema(QueryRoot::Droid); - let db = Database { - droid: Some(Droid { - id: "droid-88".to_string(), - primary_function: "sit".to_string(), - }), - }; - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &db).await, - Ok(( - graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}), - vec![], - )), - ); - } - - #[tokio::test] - async fn dyn_resolves_human() { - const DOC: &str = r#"{ - hero { - ... on Human { - humanId: id - homePlanet - } - } - }"#; - - let schema = schema(QueryRoot::Human); - let db = Database { droid: None }; - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &db).await, - Ok(( - graphql_value!({"hero": {"humanId": "human-32", "homePlanet": "earth"}}), - vec![], - )), - ); - } - - #[tokio::test] - async fn dyn_resolves_droid() { - const DOC: &str = r#"{ - hero { - ... on Droid { - droidId: id - primaryFunction - } - } - }"#; - - let schema = schema(QueryRoot::Droid); - let db = Database { - droid: Some(Droid { - id: "droid-88".to_string(), - primary_function: "sit".to_string(), - }), - }; assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &db).await, + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( - graphql_value!({"hero": {"droidId": "droid-88", "primaryFunction": "sit"}}), + graphql_value!({"character": {"droidId": "droidId", "primaryFunction": "run"}}), vec![], )), ); } #[tokio::test] - async fn enum_resolves_id_field() { - const DOC: &str = r#"{ + async fn resolves_fields() { + let doc = r#"{ character { id - } - }"#; - - for (root, expected_id) in &[ - (QueryRoot::Human, "human-32"), - (QueryRoot::Droid, "droid-99"), - ] { - let schema = schema(root.clone()); - let db = Database { droid: None }; - - let expected_id: &str = *expected_id; - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &db).await, - Ok((graphql_value!({"character": {"id": expected_id}}), vec![])), - ); - } - } - - #[tokio::test] - async fn dyn_resolves_info_field() { - const DOC: &str = r#"{ - hero { info } }"#; for (root, expected_info) in &[(QueryRoot::Human, "earth"), (QueryRoot::Droid, "run")] { - let schema = schema(root.clone()); - let db = Database { droid: None }; + let schema = schema(*root); let expected_info: &str = *expected_info; assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &db).await, - Ok((graphql_value!({"hero": {"info": expected_info}}), vec![])), + execute(&doc, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"character": {"id": "id", "info": expected_info}}), + vec![], + )), ); } } -} - -mod executor { - use juniper::LookAheadMethods as _; - - use super::*; - - #[graphql_interface(for = [Human, Droid], scalar = S)] - trait Character { - async fn id<'a>(&self, executor: &'a Executor<'_, '_, (), S>) -> &'a str - where - S: Send + Sync, - { - executor.look_ahead().field_name() - } - - async fn info<'b>( - &'b self, - arg: Option, - #[graphql(executor)] another: &Executor<'_, '_, (), S>, - ) -> &'b str - where - S: Send + Sync; - } - - #[graphql_interface(dyn = DynHero, for = [Human, Droid], scalar = S)] - trait Hero { - async fn id<'a>(&self, executor: &'a Executor<'_, '_, (), S>) -> &'a str - where - S: Send + Sync, - { - executor.look_ahead().field_name() - } - - async fn info<'b>( - &'b self, - arg: Option, - #[graphql(executor)] another: &Executor<'_, '_, (), S>, - ) -> &'b str - where - S: Send + Sync; - } - - #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue<__S>, DynHero<__S>])] - struct Human { - id: String, - home_planet: String, - } - - #[graphql_interface(scalar = S)] - impl Character for Human { - async fn info<'b>(&'b self, _: Option, _: &Executor<'_, '_, (), S>) -> &'b str - where - S: Send + Sync, - { - &self.home_planet - } - } - - #[graphql_interface(dyn, scalar = S)] - impl Hero for Human { - async fn info<'b>(&'b self, _: Option, _: &Executor<'_, '_, (), S>) -> &'b str - where - S: Send + Sync, - { - &self.home_planet - } - } - - #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue<__S>, DynHero<__S>])] - struct Droid { - id: String, - primary_function: String, - } - - #[graphql_interface(scalar = S)] - impl Character for Droid { - async fn info<'b>(&'b self, _: Option, _: &Executor<'_, '_, (), S>) -> &'b str - where - S: Send + Sync, - { - &self.primary_function - } - } - - #[graphql_interface(dyn, scalar = S)] - impl Hero for Droid { - async fn info<'b>(&'b self, _: Option, _: &Executor<'_, '_, (), S>) -> &'b str - where - S: Send + Sync, - { - &self.primary_function - } - } - - #[derive(Clone, Copy)] - enum QueryRoot { - Human, - Droid, - } - - #[graphql_object(scalar = DefaultScalarValue)] - impl QueryRoot { - fn character(&self) -> CharacterValue { - match self { - Self::Human => Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), - } - .into(), - Self::Droid => Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), - } - .into(), - } - } - - fn hero(&self) -> Box> { - let ch: Box> = match self { - Self::Human => Box::new(Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), - }), - Self::Droid => Box::new(Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), - }), - }; - ch - } - } #[tokio::test] - async fn enum_resolves_human() { - const DOC: &str = r#"{ - character { - ... on Human { - humanId: id - homePlanet - } - } - }"#; - - let schema = schema(QueryRoot::Human); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"character": {"humanId": "human-32", "homePlanet": "earth"}}), - vec![], - )), - ); - } - - #[tokio::test] - async fn enum_resolves_droid() { - const DOC: &str = r#"{ - character { - ... on Droid { - droidId: id - primaryFunction - } - } - }"#; - - let schema = schema(QueryRoot::Droid); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}), - vec![], - )), - ); - } - - #[tokio::test] - async fn dyn_resolves_human() { - const DOC: &str = r#"{ - hero { - ... on Human { - humanId: id - homePlanet + async fn not_arg() { + let doc = r#"{ + __type(name: "Character") { + fields { + name + args { + name + } } } }"#; @@ -3859,97 +3382,16 @@ mod executor { let schema = schema(QueryRoot::Human); assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"hero": {"humanId": "human-32", "homePlanet": "earth"}}), - vec![], - )), - ); - } - - #[tokio::test] - async fn dyn_resolves_droid() { - const DOC: &str = r#"{ - hero { - ... on Droid { - droidId: id - primaryFunction - } - } - }"#; - - let schema = schema(QueryRoot::Droid); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + execute(&doc, None, &schema, &graphql_vars! {}, &()).await, Ok(( - graphql_value!({"hero": {"droidId": "droid-99", "primaryFunction": "run"}}), + graphql_value!({"__type": {"fields": [ + {"name": "id", "args": []}, + {"name": "info", "args": [{"name": "arg"}]}, + ]}}), vec![], )), ); } - - #[tokio::test] - async fn resolves_fields() { - for interface in &["character", "hero"] { - let doc = format!( - r#"{{ - {} {{ - id - info - }} - }}"#, - interface, - ); - - let expected_interface: &str = *interface; - - for (root, expected_info) in &[(QueryRoot::Human, "earth"), (QueryRoot::Droid, "run")] { - let schema = schema(*root); - - let expected_info: &str = *expected_info; - assert_eq!( - execute(&doc, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({expected_interface: {"id": "id", "info": expected_info}}), - vec![], - )), - ); - } - } - } - - #[tokio::test] - async fn not_arg() { - for interface in &["Character", "Hero"] { - let doc = format!( - r#"{{ - __type(name: "{}") {{ - fields {{ - name - args {{ - name - }} - }} - }} - }}"#, - interface, - ); - - let schema = schema(QueryRoot::Human); - - assert_eq!( - execute(&doc, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"__type": {"fields": [ - {"name": "id", "args": []}, - {"name": "info", "args": [{"name": "arg"}]}, - ]}}), - vec![], - )), - ); - } - } } mod ignored_method { diff --git a/integration_tests/juniper_tests/src/codegen/new_interface.rs b/integration_tests/juniper_tests/src/codegen/new_interface.rs index a642822d2..ca3a92922 100644 --- a/integration_tests/juniper_tests/src/codegen/new_interface.rs +++ b/integration_tests/juniper_tests/src/codegen/new_interface.rs @@ -1,30 +1,66 @@ use juniper::{ execute, graphql_interface, graphql_interface_new, graphql_object, graphql_value, graphql_vars, DefaultScalarValue, EmptyMutation, EmptySubscription, Executor, FieldError, FieldResult, - GraphQLInputObject, GraphQLObject, GraphQLType, IntoFieldError, RootNode, ScalarValue, + GraphQLInputObject, GraphQLObject, GraphQLType, IntoFieldError, LookAheadMethods, RootNode, + ScalarValue, }; // -------------------------- #[graphql_interface_new(for = [Human, Droid], scalar = S)] -trait Character { - fn id(&self) -> FieldResult<&str, S>; +trait Character { + async fn id<'a>(&self, executor: &'a Executor<'_, '_, (), S>) -> &'a str + where + S: Send + Sync, + { + executor.look_ahead().field_name() + } + + async fn info<'b>( + &'b self, + arg: Option, + #[graphql(executor)] another: &Executor<'_, '_, (), S>, + ) -> &'b str + where + S: Send + Sync; } -#[derive(GraphQLObject)] -#[graphql(impl = CharacterValue<__S>)] struct Human { id: String, home_planet: String, } -#[derive(GraphQLObject)] -#[graphql(impl = CharacterValue<__S>)] +#[graphql_object(impl = CharacterValue<__S>)] +impl Human { + async fn id<'a, S: ScalarValue>(&self, executor: &'a Executor<'_, '_, (), S>) -> &'a str { + executor.look_ahead().field_name() + } + + async fn info<'b>(&'b self, _arg: Option) -> &'b str { + &self.home_planet + } +} + struct Droid { id: String, primary_function: String, } +#[graphql_object(impl = CharacterValue<__S>)] +impl Droid { + fn id<'a, S: ScalarValue>(&self, executor: &'a Executor<'_, '_, (), S>) -> &'a str { + executor.look_ahead().field_name() + } + + async fn info<'b, S: ScalarValue>( + &'b self, + _arg: Option, + _executor: &Executor<'_, '_, (), S>, + ) -> &'b str { + &self.primary_function + } +} + // -------------- fn schema<'q, C, Q>(query_root: Q) -> RootNode<'q, Q, EmptyMutation, EmptySubscription> diff --git a/integration_tests/juniper_tests/src/codegen/object_attr.rs b/integration_tests/juniper_tests/src/codegen/object_attr.rs index 76a1c8542..304ba34c5 100644 --- a/integration_tests/juniper_tests/src/codegen/object_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/object_attr.rs @@ -2022,7 +2022,7 @@ mod switched_context { async fn switch_res_opt<'e, S: ScalarValue>( &self, - executor: &'e Executor<'e, 'e, CustomContext, S>, + executor: &'e Executor<'_, '_, CustomContext, S>, ) -> FieldResult> { Ok(Some((executor.context(), Droid { id: 3 }))) } diff --git a/juniper_codegen/src/graphql_interface/new.rs b/juniper_codegen/src/graphql_interface/new.rs index 8e5e386a8..5321477a7 100644 --- a/juniper_codegen/src/graphql_interface/new.rs +++ b/juniper_codegen/src/graphql_interface/new.rs @@ -794,7 +794,9 @@ impl Definition { syn::GenericParam::Lifetime(_) => self.0.args.push(parse_quote!( 'static )), syn::GenericParam::Type(ty) => { if ty.default.is_none() { - self.0.args.push(parse_quote!(())); + self.0 + .args + .push(parse_quote!(::juniper::DefaultScalarValue)); } } syn::GenericParam::Const(_) => { @@ -824,12 +826,11 @@ impl Definition { .any(|par| { let par = quote! { #par }.to_string(); let ty = quote! { #ty }.to_string(); - println!("{} == {}", par, ty); par == ty }); if is_generic { - *ty = parse_quote!(()); + *ty = parse_quote!(::juniper::DefaultScalarValue); } } _ => {} From 527325c85a65130a4a489daed0845124d121e9dc Mon Sep 17 00:00:00 2001 From: ilslv Date: Wed, 29 Dec 2021 16:52:07 +0300 Subject: [PATCH 16/53] WIP (move to the new tests volume 5) --- .../src/codegen/interface_attr.rs | 553 +----------------- .../juniper_tests/src/codegen/object_attr.rs | 2 +- 2 files changed, 2 insertions(+), 553 deletions(-) diff --git a/integration_tests/juniper_tests/src/codegen/interface_attr.rs b/integration_tests/juniper_tests/src/codegen/interface_attr.rs index 4fc7dfa49..0a76fd315 100644 --- a/integration_tests/juniper_tests/src/codegen/interface_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/interface_attr.rs @@ -3397,7 +3397,7 @@ mod executor { mod ignored_method { use super::*; - #[graphql_interface(for = Human)] + #[graphql_interface_new(for = Human)] trait Character { fn id(&self) -> &str; @@ -3417,13 +3417,6 @@ mod ignored_method { home_planet: String, } - #[graphql_interface] - impl Character for Human { - fn id(&self) -> &str { - &self.id - } - } - struct QueryRoot; #[graphql_object] @@ -3496,547 +3489,3 @@ mod ignored_method { ); } } - -mod downcast_method { - use super::*; - - #[graphql_interface(for = [Human, Droid])] - trait Character { - fn id(&self) -> &str; - - #[graphql(downcast)] - fn as_human(&self) -> Option<&Human> { - None - } - } - - #[graphql_interface(dyn = DynHero, for = [Human, Droid])] - trait Hero { - fn info(&self) -> &str; - - #[graphql(downcast)] - fn as_droid(&self) -> Option<&Droid> { - None - } - } - - #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue, DynHero<__S>])] - struct Human { - id: String, - home_planet: String, - } - - #[graphql_interface] - impl Character for Human { - fn id(&self) -> &str { - &self.id - } - - fn as_human(&self) -> Option<&Human> { - Some(self) - } - } - - #[graphql_interface(dyn)] - impl Hero for Human { - fn info(&self) -> &str { - &self.home_planet - } - } - - #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue, DynHero<__S>])] - struct Droid { - id: String, - primary_function: String, - } - - #[graphql_interface] - impl Character for Droid { - fn id(&self) -> &str { - &self.id - } - } - - #[graphql_interface(dyn)] - impl Hero for Droid { - fn info(&self) -> &str { - &self.primary_function - } - - fn as_droid(&self) -> Option<&Droid> { - Some(self) - } - } - - #[derive(Clone)] - enum QueryRoot { - Human, - Droid, - } - - #[graphql_object(scalar = S: ScalarValue + Send + Sync)] - impl QueryRoot { - fn character(&self) -> CharacterValue { - match self { - Self::Human => Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), - } - .into(), - Self::Droid => Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), - } - .into(), - } - } - - fn hero(&self) -> Box> { - let ch: Box> = match self { - Self::Human => Box::new(Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), - }), - Self::Droid => Box::new(Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), - }), - }; - ch - } - } - - #[tokio::test] - async fn enum_resolves_human() { - const DOC: &str = r#"{ - character { - ... on Human { - humanId: id - homePlanet - } - } - }"#; - - let schema = schema(QueryRoot::Human); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"character": {"humanId": "human-32", "homePlanet": "earth"}}), - vec![], - )), - ); - } - - #[tokio::test] - async fn enum_resolves_droid() { - const DOC: &str = r#"{ - character { - ... on Droid { - droidId: id - primaryFunction - } - } - }"#; - - let schema = schema(QueryRoot::Droid); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}), - vec![], - )), - ); - } - - #[tokio::test] - async fn dyn_resolves_human() { - const DOC: &str = r#"{ - hero { - ... on Human { - humanId: id - homePlanet - } - } - }"#; - - let schema = schema(QueryRoot::Human); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"hero": {"humanId": "human-32", "homePlanet": "earth"}}), - vec![], - )), - ); - } - - #[tokio::test] - async fn dyn_resolves_droid() { - const DOC: &str = r#"{ - hero { - ... on Droid { - droidId: id - primaryFunction - } - } - }"#; - - let schema = schema(QueryRoot::Droid); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"hero": {"droidId": "droid-99", "primaryFunction": "run"}}), - vec![], - )), - ); - } - - #[tokio::test] - async fn enum_resolves_id_field() { - const DOC: &str = r#"{ - character { - id - } - }"#; - - for (root, expected_id) in &[ - (QueryRoot::Human, "human-32"), - (QueryRoot::Droid, "droid-99"), - ] { - let schema = schema(root.clone()); - - let expected_id: &str = *expected_id; - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"character": {"id": expected_id}}), vec![])), - ); - } - } - - #[tokio::test] - async fn dyn_resolves_info_field() { - const DOC: &str = r#"{ - hero { - info - } - }"#; - - for (root, expected_info) in &[(QueryRoot::Human, "earth"), (QueryRoot::Droid, "run")] { - let schema = schema(root.clone()); - - let expected_info: &str = *expected_info; - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"hero": {"info": expected_info}}), vec![])), - ); - } - } - - #[tokio::test] - async fn is_not_field() { - let schema = schema(QueryRoot::Human); - - for (doc, field) in &[ - (r#"{__type(name: "Character") { fields { name } } }"#, "id"), - (r#"{__type(name: "Hero") { fields { name } } }"#, "info"), - ] { - let expected_field: &str = *field; - - assert_eq!( - execute(*doc, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"__type": {"fields": [{"name": expected_field}]}}), - vec![], - )), - ); - } - } -} - -mod external_downcast { - use super::*; - - struct Database { - human: Option, - droid: Option, - } - - impl juniper::Context for Database {} - - #[graphql_interface(for = [Human, Droid])] - #[graphql_interface(context = Database, on Human = CharacterValue::as_human)] - trait Character { - fn id(&self) -> &str; - } - - impl CharacterValue { - fn as_human<'db>(&self, db: &'db Database) -> Option<&'db Human> { - db.human.as_ref() - } - } - - #[graphql_interface(dyn = DynHero, for = [Human, Droid])] - #[graphql_interface(context = Database)] - #[graphql_interface(on Droid = DynHero::as_droid)] - trait Hero { - fn info(&self) -> &str; - } - - impl<'a, S: ScalarValue> DynHero<'a, S> { - fn as_droid<'db>(&self, db: &'db Database) -> Option<&'db Droid> { - db.droid.as_ref() - } - } - - #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue, DynHero<__S>], context = Database)] - struct Human { - id: String, - home_planet: String, - } - - #[graphql_interface] - impl Character for Human { - fn id(&self) -> &str { - &self.id - } - } - - #[graphql_interface(dyn)] - impl Hero for Human { - fn info(&self) -> &str { - &self.home_planet - } - } - - #[derive(GraphQLObject)] - #[graphql(impl = [CharacterValue, DynHero<__S>], context = Database)] - struct Droid { - id: String, - primary_function: String, - } - - #[graphql_interface] - impl Character for Droid { - fn id(&self) -> &str { - &self.id - } - } - - #[graphql_interface(dyn)] - impl Hero for Droid { - fn info(&self) -> &str { - &self.primary_function - } - } - - #[derive(Clone)] - enum QueryRoot { - Human, - Droid, - } - - #[graphql_object(context = Database, scalar = S: ScalarValue + Send + Sync)] - impl QueryRoot { - fn character(&self) -> CharacterValue { - match self { - Self::Human => Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), - } - .into(), - Self::Droid => Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), - } - .into(), - } - } - - fn hero(&self) -> Box> { - let ch: Box> = match self { - Self::Human => Box::new(Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), - }), - Self::Droid => Box::new(Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), - }), - }; - ch - } - } - - #[tokio::test] - async fn enum_resolves_human() { - const DOC: &str = r#"{ - character { - ... on Human { - humanId: id - homePlanet - } - } - }"#; - - let schema = schema(QueryRoot::Human); - let db = Database { - human: Some(Human { - id: "human-64".to_string(), - home_planet: "mars".to_string(), - }), - droid: None, - }; - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &db).await, - Ok(( - graphql_value!({"character": {"humanId": "human-64", "homePlanet": "mars"}}), - vec![], - )), - ); - } - - #[tokio::test] - async fn enum_resolves_droid() { - const DOC: &str = r#"{ - character { - ... on Droid { - droidId: id - primaryFunction - } - } - }"#; - - let schema = schema(QueryRoot::Droid); - let db = Database { - human: None, - droid: None, - }; - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &db).await, - Ok(( - graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}), - vec![], - )), - ); - } - - #[tokio::test] - async fn dyn_resolves_human() { - const DOC: &str = r#"{ - hero { - ... on Human { - humanId: id - homePlanet - } - } - }"#; - - let schema = schema(QueryRoot::Human); - let db = Database { - human: None, - droid: None, - }; - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &db).await, - Ok(( - graphql_value!({"hero": {"humanId": "human-32", "homePlanet": "earth"}}), - vec![], - )), - ); - } - - #[tokio::test] - async fn dyn_resolves_droid() { - const DOC: &str = r#"{ - hero { - ... on Droid { - droidId: id - primaryFunction - } - } - }"#; - - let schema = schema(QueryRoot::Droid); - let db = Database { - human: None, - droid: Some(Droid { - id: "droid-01".to_string(), - primary_function: "swim".to_string(), - }), - }; - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &db).await, - Ok(( - graphql_value!({"hero": {"droidId": "droid-01", "primaryFunction": "swim"}}), - vec![], - )), - ); - } - - #[tokio::test] - async fn enum_resolves_id_field() { - const DOC: &str = r#"{ - character { - id - } - }"#; - - let db = Database { - human: Some(Human { - id: "human-64".to_string(), - home_planet: "mars".to_string(), - }), - droid: None, - }; - - for (root, expected_id) in &[ - (QueryRoot::Human, "human-32"), - (QueryRoot::Droid, "droid-99"), - ] { - let schema = schema(root.clone()); - - let expected_id: &str = *expected_id; - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &db).await, - Ok((graphql_value!({"character": {"id": expected_id}}), vec![])), - ); - } - } - - #[tokio::test] - async fn dyn_resolves_info_field() { - const DOC: &str = r#"{ - hero { - info - } - }"#; - - let db = Database { - human: None, - droid: Some(Droid { - id: "droid-01".to_string(), - primary_function: "swim".to_string(), - }), - }; - - for (root, expected_info) in &[(QueryRoot::Human, "earth"), (QueryRoot::Droid, "run")] { - let schema = schema(root.clone()); - - let expected_info: &str = *expected_info; - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &db).await, - Ok((graphql_value!({"hero": {"info": expected_info}}), vec![])), - ); - } - } -} diff --git a/integration_tests/juniper_tests/src/codegen/object_attr.rs b/integration_tests/juniper_tests/src/codegen/object_attr.rs index 304ba34c5..76a1c8542 100644 --- a/integration_tests/juniper_tests/src/codegen/object_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/object_attr.rs @@ -2022,7 +2022,7 @@ mod switched_context { async fn switch_res_opt<'e, S: ScalarValue>( &self, - executor: &'e Executor<'_, '_, CustomContext, S>, + executor: &'e Executor<'e, 'e, CustomContext, S>, ) -> FieldResult> { Ok(Some((executor.context(), Droid { id: 3 }))) } From 704f07937766901a973f7ee7543eb353e7a0d1c5 Mon Sep 17 00:00:00 2001 From: ilslv Date: Wed, 29 Dec 2021 17:04:54 +0300 Subject: [PATCH 17/53] WIP (move to the new tests volume 5) --- juniper_codegen/src/graphql_interface/new.rs | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/juniper_codegen/src/graphql_interface/new.rs b/juniper_codegen/src/graphql_interface/new.rs index 5321477a7..e61c10ffb 100644 --- a/juniper_codegen/src/graphql_interface/new.rs +++ b/juniper_codegen/src/graphql_interface/new.rs @@ -2,14 +2,10 @@ //! //! [1]: https://spec.graphql.org/June2018/#sec-Interfaces -use std::{ - any::TypeId, - collections::{HashMap, HashSet}, - convert::TryInto as _, -}; +use std::{collections::HashSet, convert::TryInto as _}; -use proc_macro2::{Ident, Span, TokenStream}; -use quote::{format_ident, quote, ToTokens, TokenStreamExt as _}; +use proc_macro2::TokenStream; +use quote::{format_ident, quote, ToTokens}; use syn::{ ext::IdentExt as _, parse::{Parse, ParseStream}, @@ -26,7 +22,7 @@ use crate::{ field, gen, parse::{ attr::{err, OptionExt as _}, - GenericsExt as _, ParseBufferExt as _, + ParseBufferExt as _, }, scalar, }, From b84009e627ae6fa65a7449cf2a3ff2fa05365f0a Mon Sep 17 00:00:00 2001 From: ilslv Date: Thu, 30 Dec 2021 12:59:11 +0300 Subject: [PATCH 18/53] WIP (refactor) --- juniper_codegen/src/graphql_interface/attr.rs | 25 +- juniper_codegen/src/graphql_interface/new.rs | 444 +++++++++--------- 2 files changed, 244 insertions(+), 225 deletions(-) diff --git a/juniper_codegen/src/graphql_interface/attr.rs b/juniper_codegen/src/graphql_interface/attr.rs index 475ea3eaf..33dce4f74 100644 --- a/juniper_codegen/src/graphql_interface/attr.rs +++ b/juniper_codegen/src/graphql_interface/attr.rs @@ -3,7 +3,7 @@ use std::mem; use proc_macro2::{Span, TokenStream}; -use quote::{quote, ToTokens as _}; +use quote::{format_ident, quote, ToTokens as _}; use syn::{ext::IdentExt as _, parse_quote, spanned::Spanned}; use crate::{ @@ -403,19 +403,32 @@ fn expand_on_trait_new( // ))) // }; + let enum_alias_ident = attr + .r#enum + .as_deref() + .cloned() + .unwrap_or_else(|| format_ident!("{}Value", trait_ident.to_string())); + let enum_ident = attr.r#enum.as_ref().map_or_else( + || format_ident!("{}ValueEnum", trait_ident.to_string()), + |c| format_ident!("{}Enum", c.inner().to_string()), + ); + let description = attr.description.as_ref().map(|c| c.inner().clone()); let generated_code = new::Definition { - attrs: attr, - ident: trait_ident.clone(), - vis: ast.vis.clone(), trait_generics: ast.generics.clone(), + vis: ast.vis.clone(), + enum_ident, + enum_alias_ident, name, description, - context, scalar: scalar.clone(), - fields, + implementers: attr + .implementers + .iter() + .map(|c| c.inner().clone()) + .collect(), }; // Attach the `juniper::AsDynGraphQLValue` on top of the trait if dynamic dispatch is used. diff --git a/juniper_codegen/src/graphql_interface/new.rs b/juniper_codegen/src/graphql_interface/new.rs index e61c10ffb..cb929b704 100644 --- a/juniper_codegen/src/graphql_interface/new.rs +++ b/juniper_codegen/src/graphql_interface/new.rs @@ -22,7 +22,7 @@ use crate::{ field, gen, parse::{ attr::{err, OptionExt as _}, - ParseBufferExt as _, + GenericsExt as _, ParseBufferExt as _, }, scalar, }, @@ -229,36 +229,35 @@ impl TraitAttr { Ok(attr) } - - /// TODO - fn enum_alias_ident(&self, trait_name: &syn::Ident) -> SpanContainer { - self.r#enum.clone().unwrap_or_else(|| { - SpanContainer::new( - trait_name.span(), - Some(trait_name.span()), - format_ident!("{}Value", trait_name.to_string()), - ) - }) - } } /// Definition of [GraphQL interface][1] for code generation. /// /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces pub(crate) struct Definition { - /// TODO - pub(crate) attrs: TraitAttr, - - pub(crate) ident: syn::Ident, + /// [`syn::Generics`] of the trait describing the [GraphQL interface][1]. + /// + /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces + pub(crate) trait_generics: syn::Generics, + /// [`syn::Visibility`] of the trait describing the [GraphQL interface][1]. + /// + /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces pub(crate) vis: syn::Visibility, - pub(crate) trait_generics: syn::Generics, + /// Name of the generic enum describing all [`implementers`]. It's generic + /// to derive [`Clone`], [`Copy`] and [`Debug`] on it. + /// + /// [`implementers`]: Self::implementers + /// [`Debug`]: std::fmt::Debug + pub(crate) enum_ident: syn::Ident, + + /// Name of the type alias for [`enum_ident`] with [`implementers`]. + /// + /// [`enum_ident`]: Self::enum_ident + /// [`implementers`]: Self::implementers + pub(crate) enum_alias_ident: syn::Ident, - // /// Rust type that this [GraphQL interface][1] is represented with. - // /// - // /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - // ty: syn::Type, /// Name of this [GraphQL interface][1] in GraphQL schema. /// /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces @@ -290,10 +289,11 @@ pub(crate) struct Definition { /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces /// [2]: https://spec.graphql.org/June2018/#sec-Language.Fields pub(crate) fields: Vec, - // /// Defined [`Implementer`]s of this [GraphQL interface][1]. - // /// - // /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - // implementers: Vec, + + /// Defined [`Implementer`]s of this [GraphQL interface][1]. + /// + /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces + pub(crate) implementers: Vec, } impl ToTokens for Definition { @@ -304,101 +304,64 @@ impl ToTokens for Definition { self.impl_graphql_type_tokens().to_tokens(into); self.impl_graphql_value_tokens().to_tokens(into); self.impl_graphql_value_async_tokens().to_tokens(into); - self.impl_traits_for_const_assertions().to_tokens(into); + self.impl_traits_for_reflection().to_tokens(into); self.impl_fields(false).to_tokens(into); self.impl_fields(true).to_tokens(into); } } impl Definition { + /// Generates enum describing all [`implementers`]. + /// + /// [`implementers`]: Self::implementers + #[must_use] fn generate_enum(&self) -> TokenStream { let vis = &self.vis; - let trait_gens = &self.trait_generics; - let (trait_impl_gens, trait_ty_gens, trait_where_clause) = - self.trait_generics.split_for_impl(); + let enum_ident = &self.enum_ident; + let alias_ident = &self.enum_alias_ident; - let ty_params = self.trait_generics.params.iter().map(|p| { - let ty = match p { - syn::GenericParam::Type(ty) => { - let ident = &ty.ident; - quote! { #ident } - } - syn::GenericParam::Lifetime(lt) => { - let lifetime = <.lifetime; - quote! { &#lifetime () } - } - syn::GenericParam::Const(_) => unimplemented!(), - }; - quote! { - ::std::marker::PhantomData<::std::sync::atomic::AtomicPtr>> - } - }); - let phantom_variant = - (!self.trait_generics.params.is_empty()).then(|| quote! { __Phantom(#(#ty_params,)*) }); - - let alias_ident = self.attrs.enum_alias_ident(&self.ident); - let enum_ident = self.attrs.r#enum.as_ref().map_or_else( - || format_ident!("{}ValueEnum", self.ident.to_string()), - |c| format_ident!("{}Enum", c.inner().to_string()), - ); - - let enum_alias_generics = { - let mut enum_alias_generics = trait_gens.clone(); - let enum_generics = std::mem::take(&mut enum_alias_generics.params); - enum_alias_generics.params = enum_generics - .into_iter() - .map(|gen| match gen { - syn::GenericParam::Type(mut ty) => { - ty.bounds = Punctuated::new(); - ty.into() - } - rest => rest, - }) - .collect(); - enum_alias_generics - }; - - let variants_generics = self - .attrs + let variant_gens_pars = self .implementers .iter() .enumerate() - .map(|(id, _)| format_ident!("I{}", id)); + .map::(|(id, _)| { + let par = format_ident!("__I{}", id); + parse_quote!( #par ) + }); let variants_idents = self - .attrs .implementers .iter() .filter_map(|ty| ty.path.segments.last().map(|seg| &seg.ident)); - let enum_generics = { - let mut enum_generics = self.trait_generics.clone(); - let enum_generic_params = std::mem::take(&mut enum_generics.params); - let (mut enum_generic_params_lifetimes, enum_generic_params_rest) = enum_generic_params - .into_iter() - .partition::, _>(|par| { - matches!(par, syn::GenericParam::Lifetime(_)) - }); + let trait_gens = &self.trait_generics; + let (trait_impl_gens, trait_ty_gens, trait_where_clause) = + self.trait_generics.split_for_impl(); - let variants = variants_generics - .clone() - .map::(|var| parse_quote! { #var }) - .collect::>(); - enum_generic_params_lifetimes.extend(variants); - enum_generic_params_lifetimes.extend(enum_generic_params_rest); - // variants.extend(enum_generic_params_rest); - enum_generics.params = enum_generic_params_lifetimes; - enum_generics + let (trait_gens_lifetimes, trait_gens_tys) = trait_gens + .params + .clone() + .into_iter() + .partition::, _>(|par| { + matches!(par, syn::GenericParam::Lifetime(_)) + }); + + let enum_gens = { + let mut enum_gens = trait_gens.clone(); + enum_gens.params = trait_gens_lifetimes.clone(); + enum_gens.params.extend(variant_gens_pars.clone()); + enum_gens.params.extend(trait_gens_tys.clone()); + enum_gens }; - let enum_to_alias_generics = { - let (lifetimes, rest) = self - .trait_generics - .params - .iter() - .partition::, _>(|par| matches!(par, syn::GenericParam::Lifetime(_))); + let enum_alias_gens = { + let mut enum_alias_gens = trait_gens.clone(); + enum_alias_gens.move_bounds_to_where_clause(); + enum_alias_gens + }; - lifetimes + let enum_to_alias_gens = { + trait_gens_lifetimes .into_iter() .map(|par| match par { syn::GenericParam::Lifetime(def) => { @@ -407,13 +370,8 @@ impl Definition { } rest => quote! { #rest }, }) - .chain( - self.attrs - .implementers - .iter() - .map(ToTokens::to_token_stream), - ) - .chain(rest.into_iter().map(|par| match par { + .chain(self.implementers.iter().map(ToTokens::to_token_stream)) + .chain(trait_gens_tys.into_iter().map(|par| match par { syn::GenericParam::Type(ty) => { let par_ident = &ty.ident; quote! { #par_ident } @@ -422,12 +380,28 @@ impl Definition { })) }; - let from_impls = self - .attrs - .implementers - .iter() - .zip(variants_idents.clone()) - .map(|(ty, ident)| { + let phantom_variant = self.has_phantom_variant().then(|| { + let phantom_params = trait_gens.params.iter().filter_map(|p| { + let ty = match p { + syn::GenericParam::Type(ty) => { + let ident = &ty.ident; + quote! { #ident } + } + syn::GenericParam::Lifetime(lt) => { + let lifetime = <.lifetime; + quote! { &#lifetime () } + } + syn::GenericParam::Const(_) => return None, + }; + Some(quote! { + ::std::marker::PhantomData<::std::sync::atomic::AtomicPtr>> + }) + }); + quote! { __Phantom(#(#phantom_params),*) } + }); + + let from_impls = self.implementers.iter().zip(variants_idents.clone()).map( + |(ty, ident)| { quote! { impl#trait_impl_gens ::std::convert::From<#ty> for #alias_ident#trait_ty_gens #trait_where_clause @@ -437,17 +411,18 @@ impl Definition { } } } - }); + }, + ); quote! { #[derive(Clone, Copy, Debug)] - #vis enum #enum_ident#enum_generics { - #(#variants_idents(#variants_generics),)* + #vis enum #enum_ident#enum_gens { + #(#variants_idents(#variant_gens_pars),)* #phantom_variant } - #vis type #alias_ident#enum_alias_generics = - #enum_ident<#(#enum_to_alias_generics,)*>; + #vis type #alias_ident#enum_alias_gens = + #enum_ident<#(#enum_to_alias_gens),*>; #(#from_impls)* } @@ -460,14 +435,14 @@ impl Definition { /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces #[must_use] fn impl_graphql_interface_tokens(&self) -> TokenStream { + let ty = &self.enum_alias_ident; let scalar = &self.scalar; let gens = self.impl_generics(false); let (impl_generics, _, where_clause) = gens.split_for_impl(); let (_, ty_generics, _) = self.trait_generics.split_for_impl(); - let ty = self.attrs.enum_alias_ident(&self.ident); - let impler_tys = self.attrs.implementers.iter().collect::>(); + let impler_tys = &self.implementers; let all_implers_unique = (impler_tys.len() > 1).then(|| { quote! { ::juniper::sa::assert_type_ne_all!(#( #impler_tys ),*); } }); @@ -493,19 +468,19 @@ impl Definition { /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces #[must_use] fn impl_output_type_tokens(&self) -> TokenStream { + let ty = &self.enum_alias_ident; let scalar = &self.scalar; let generics = self.impl_generics(false); let (impl_generics, _, where_clause) = generics.split_for_impl(); let (_, ty_generics, _) = self.trait_generics.split_for_impl(); - let ty = self.attrs.enum_alias_ident(&self.ident); let fields_marks = self .fields .iter() .map(|f| f.method_mark_tokens(false, scalar)); - let impler_tys = self.attrs.implementers.iter(); + let impler_tys = self.implementers.iter(); quote! { #[automatically_derived] @@ -528,12 +503,12 @@ impl Definition { /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces #[must_use] fn impl_graphql_type_tokens(&self) -> TokenStream { + let ty = &self.enum_alias_ident; let scalar = &self.scalar; let generics = self.impl_generics(false); let (impl_generics, _, where_clause) = generics.split_for_impl(); let (_, ty_generics, _) = self.trait_generics.split_for_impl(); - let ty = self.attrs.enum_alias_ident(&self.ident); let name = &self.name; let description = self @@ -542,7 +517,7 @@ impl Definition { .map(|desc| quote! { .description(#desc) }); // Sorting is required to preserve/guarantee the order of implementers registered in schema. - let mut impler_tys = self.attrs.implementers.iter().collect::>(); + let mut impler_tys = self.implementers.clone(); impler_tys.sort_unstable_by(|a, b| { let (a, b) = (quote!(#a).to_string(), quote!(#b).to_string()); a.cmp(&b) @@ -587,14 +562,14 @@ impl Definition { /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces #[must_use] fn impl_graphql_value_tokens(&self) -> TokenStream { + let ty = &self.enum_alias_ident; + let trait_name = &self.name; let scalar = &self.scalar; let context = &self.context; let generics = self.impl_generics(false); let (impl_generics, _, where_clause) = generics.split_for_impl(); let (_, ty_generics, _) = self.trait_generics.split_for_impl(); - let ty = self.attrs.enum_alias_ident(&self.ident); - let trait_name = &self.name; let fields_resolvers = self.fields.iter().filter_map(|f| { (!f.is_async).then(|| { @@ -683,13 +658,13 @@ impl Definition { /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces #[must_use] fn impl_graphql_value_async_tokens(&self) -> TokenStream { + let ty = &self.enum_alias_ident; + let trait_name = &self.name; let scalar = &self.scalar; let generics = self.impl_generics(true); let (impl_generics, _, where_clause) = generics.split_for_impl(); let (_, ty_generics, _) = self.trait_generics.split_for_impl(); - let ty = &self.attrs.enum_alias_ident(&self.ident); - let trait_name = &self.name; let fields_resolvers = self.fields.iter().map(|f| { let name = &f.name; @@ -739,16 +714,23 @@ impl Definition { } } - /// TODO + /// Returns generated code implementing [`BaseType`], [`BaseSubTypes`] and + /// [`WrappedType`] traits for this [GraphQL interface][1]. + /// + /// [`BaseSubTypes`]: juniper::macros::helper::BaseSubTypes + /// [`BaseType`]: juniper::macros::helper::BaseType + /// [`WrappedType`]: juniper::macros::helper::WrappedType + /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces #[must_use] - pub(crate) fn impl_traits_for_const_assertions(&self) -> TokenStream { + pub(crate) fn impl_traits_for_reflection(&self) -> TokenStream { + let ty = &self.enum_alias_ident; + let implementers = &self.implementers; let scalar = &self.scalar; let name = &self.name; + let generics = self.impl_generics(false); let (impl_generics, _, where_clause) = generics.split_for_impl(); let (_, ty_generics, _) = self.trait_generics.split_for_impl(); - let ty = self.attrs.enum_alias_ident(&self.ident); - let implementers = self.attrs.implementers.iter(); quote! { #[automatically_derived] @@ -780,73 +762,25 @@ impl Definition { } } - /// TODO + /// Returns generated code implementing [`Field`] or [`AsyncField`] trait + /// for this [GraphQL interface][1]. + /// + /// [`AsyncField`]: juniper::macros::helper::AsyncField + /// [`Field`]: juniper::macros::helper::Field + /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces fn impl_fields(&self, for_async: bool) -> TokenStream { - struct GenericsForConst(syn::AngleBracketedGenericArguments); - - impl Visit<'_> for GenericsForConst { - fn visit_generic_param(&mut self, param: &syn::GenericParam) { - match param { - syn::GenericParam::Lifetime(_) => self.0.args.push(parse_quote!( 'static )), - syn::GenericParam::Type(ty) => { - if ty.default.is_none() { - self.0 - .args - .push(parse_quote!(::juniper::DefaultScalarValue)); - } - } - syn::GenericParam::Const(_) => { - unimplemented!() - } - } - } - } - - struct ReplaceGenericsForConst<'a>(&'a syn::Generics); - - impl<'a> VisitMut for ReplaceGenericsForConst<'a> { - fn visit_generic_argument_mut(&mut self, arg: &mut syn::GenericArgument) { - match arg { - syn::GenericArgument::Lifetime(lf) => { - *lf = parse_quote!( 'static ); - } - syn::GenericArgument::Type(ty) => { - let is_generic = self - .0 - .params - .iter() - .filter_map(|par| match par { - syn::GenericParam::Type(ty) => Some(&ty.ident), - _ => None, - }) - .any(|par| { - let par = quote! { #par }.to_string(); - let ty = quote! { #ty }.to_string(); - par == ty - }); - - if is_generic { - *ty = parse_quote!(::juniper::DefaultScalarValue); - } - } - _ => {} - } - } - } - + let ty = &self.enum_alias_ident; + let context = &self.context; let scalar = &self.scalar; let const_scalar = match scalar { - scalar::Type::Concrete(ty) => ty.to_token_stream(), + scalar::Type::Concrete(ty) => ty.clone(), scalar::Type::ExplicitGeneric(_) | scalar::Type::ImplicitGeneric(_) => { - quote! { ::juniper::DefaultScalarValue } + parse_quote! { ::juniper::DefaultScalarValue } } }; - let ty = self.attrs.enum_alias_ident(&self.ident); - let context = &self.context; - let impl_tys = self.attrs.implementers.iter().collect::>(); + let impl_tys = self.implementers.iter().collect::>(); let impl_idents = self - .attrs .implementers .iter() .filter_map(|ty| ty.path.segments.last().map(|seg| &seg.ident)) @@ -860,6 +794,11 @@ impl Definition { if field.is_async && !for_async { return None; } + + let name = &field.name; + let mut return_ty = field.ty.clone(); + Self::replace_generics_for_const(&mut return_ty, &generics); + let (trait_name, call_sig) = if for_async { ( quote! { AsyncField }, @@ -886,10 +825,6 @@ impl Definition { ) }; - let name = &field.name; - let mut return_ty = field.ty.clone(); - ReplaceGenericsForConst(&generics).visit_type_mut(&mut return_ty); - let (args_tys, args_names): (Vec<_>, Vec<_>) = field .arguments .iter() @@ -904,17 +839,14 @@ impl Definition { }) .unzip(); - let const_ty_generics = { - let mut visitor = GenericsForConst(parse_quote!( <> )); - visitor.visit_generics(&self.trait_generics); - visitor.0 - }; + let const_ty_generics = self.const_trait_generics(); - let unreachable_arm = (self.attrs.implementers.is_empty() || !self.trait_generics.params.is_empty()).then(|| { + let unreachable_arm = (self.implementers.is_empty() || !self.trait_generics.params.is_empty()).then(|| { quote! { _ => unreachable!() } }); Some(quote! { + #[allow(non_snake_case)] impl#impl_generics ::juniper::macros::helper::#trait_name< #scalar, { ::juniper::macros::helper::fnv1a128(#name) } @@ -987,7 +919,6 @@ impl Definition { let scalar = &self.scalar; let match_arms = self - .attrs .implementers .iter() .filter_map(|ty| ty.path.segments.last().map(|seg| (&seg.ident, ty))) @@ -999,11 +930,10 @@ impl Definition { } }); - let non_exhaustive_match_arm = (!self.trait_generics.params.is_empty() - || self.attrs.implementers.is_empty()) - .then(|| { - quote! { _ => unreachable!(), } - }); + let non_exhaustive_match_arm = + (!self.trait_generics.params.is_empty() || self.implementers.is_empty()).then(|| { + quote! { _ => unreachable!(), } + }); quote! { match self { @@ -1023,7 +953,7 @@ impl Definition { fn method_resolve_into_type_async_tokens(&self) -> TokenStream { let resolving_code = gen::async_resolving_code(None); - let match_arms = self.attrs.implementers.iter().filter_map(|ty| { + let match_arms = self.implementers.iter().filter_map(|ty| { ty.path.segments.last().map(|ident| { quote! { Self::#ident(v) => { @@ -1033,11 +963,10 @@ impl Definition { } }) }); - let non_exhaustive_match_arm = (!self.trait_generics.params.is_empty() - || self.attrs.implementers.is_empty()) - .then(|| { - quote! { _ => unreachable!(), } - }); + let non_exhaustive_match_arm = + (!self.trait_generics.params.is_empty() || self.implementers.is_empty()).then(|| { + quote! { _ => unreachable!(), } + }); quote! { match self { @@ -1056,7 +985,7 @@ impl Definition { fn method_resolve_into_type_tokens(&self) -> TokenStream { let resolving_code = gen::sync_resolving_code(); - let match_arms = self.attrs.implementers.iter().filter_map(|ty| { + let match_arms = self.implementers.iter().filter_map(|ty| { ty.path.segments.last().map(|ident| { quote! { Self::#ident(res) => #resolving_code, @@ -1064,11 +993,10 @@ impl Definition { }) }); - let non_exhaustive_match_arm = (!self.trait_generics.params.is_empty() - || self.attrs.implementers.is_empty()) - .then(|| { - quote! { _ => unreachable!(), } - }); + let non_exhaustive_match_arm = + (!self.trait_generics.params.is_empty() || self.implementers.is_empty()).then(|| { + quote! { _ => unreachable!(), } + }); quote! { match self { @@ -1078,6 +1006,78 @@ impl Definition { } } + /// Returns trait generics replaced with default values for usage in `const` + /// context. + #[must_use] + fn const_trait_generics(&self) -> syn::PathArguments { + struct GenericsForConst(syn::AngleBracketedGenericArguments); + + impl Visit<'_> for GenericsForConst { + fn visit_generic_param(&mut self, param: &syn::GenericParam) { + let arg = match param { + syn::GenericParam::Lifetime(_) => parse_quote!( 'static ), + syn::GenericParam::Type(ty) => { + if ty.default.is_none() { + parse_quote!(::juniper::DefaultScalarValue) + } else { + return; + } + } + syn::GenericParam::Const(_) => { + // This hack works because only `min_const_generics` are + // enabled for now. + // TODO: replace this once full `const_generics` are + // available. + // Maybe with `<_ as Default>::default()`? + parse_quote!({ 0_u8 as _ }) + } + }; + self.0.args.push(arg) + } + } + + let mut visitor = GenericsForConst(parse_quote!( <> )); + visitor.visit_generics(&self.trait_generics); + syn::PathArguments::AngleBracketed(visitor.0) + } + + /// Replaces `generics` in `ty` for usage in `const` context. + fn replace_generics_for_const(ty: &mut syn::Type, generics: &syn::Generics) { + struct ReplaceGenericsForConst<'a>(&'a syn::Generics); + + impl<'a> VisitMut for ReplaceGenericsForConst<'a> { + fn visit_generic_argument_mut(&mut self, arg: &mut syn::GenericArgument) { + match arg { + syn::GenericArgument::Lifetime(lf) => { + *lf = parse_quote!( 'static ); + } + syn::GenericArgument::Type(ty) => { + let is_generic = self + .0 + .params + .iter() + .filter_map(|par| match par { + syn::GenericParam::Type(ty) => Some(&ty.ident), + _ => None, + }) + .any(|par| { + let par = quote! { #par }.to_string(); + let ty = quote! { #ty }.to_string(); + par == ty + }); + + if is_generic { + *ty = parse_quote!(()); + } + } + _ => {} + } + } + } + + ReplaceGenericsForConst(&generics).visit_type_mut(ty) + } + /// Returns prepared [`syn::Generics`] for [`GraphQLType`] trait (and /// similar) implementation of this [`EnumType`]. /// @@ -1115,8 +1115,7 @@ impl Definition { } let lifetimes = generics.lifetimes().map(|lt| <.lifetime); - let ty = self.attrs.enum_alias_ident(&self.ident); - // let ty = &self.ident; + let ty = &self.enum_alias_ident; let (_, ty_generics, _) = generics.split_for_impl(); quote! { for<#( #lifetimes ),*> #ty#ty_generics } @@ -1138,4 +1137,11 @@ impl Definition { generics } + + /// Indicates whether this [`EnumType`] has non-exhaustive phantom variant + /// to hold type parameters. + #[must_use] + fn has_phantom_variant(&self) -> bool { + !self.trait_generics.params.is_empty() + } } From 11345f7b9178b339507b1f0b70bbbf947c227b39 Mon Sep 17 00:00:00 2001 From: ilslv Date: Thu, 30 Dec 2021 13:01:29 +0300 Subject: [PATCH 19/53] WIP (refactor) --- juniper_codegen/src/graphql_interface/attr.rs | 77 ------------------- 1 file changed, 77 deletions(-) diff --git a/juniper_codegen/src/graphql_interface/attr.rs b/juniper_codegen/src/graphql_interface/attr.rs index 33dce4f74..511a2d175 100644 --- a/juniper_codegen/src/graphql_interface/attr.rs +++ b/juniper_codegen/src/graphql_interface/attr.rs @@ -286,27 +286,6 @@ fn expand_on_trait_new( let scalar = scalar::Type::parse(attr.scalar.as_deref(), &ast.generics); - // let mut implementers: Vec<_> = attr - // .implementers - // .iter() - // .map(|ty| Implementer { - // ty: ty.as_ref().clone(), - // downcast: None, - // context: None, - // scalar: scalar.clone(), - // }) - // .collect(); - // for (ty, downcast) in &attr.external_downcasts { - // match implementers.iter_mut().find(|i| &i.ty == ty) { - // Some(impler) => { - // impler.downcast = Some(ImplementerDowncast::External { - // path: downcast.inner().clone(), - // }); - // } - // None => err_only_implementer_downcast(&downcast.span_joined()), - // } - // } - proc_macro_error::abort_if_dirty(); let renaming = attr @@ -322,17 +301,6 @@ fn expand_on_trait_new( Some(TraitMethod::Field(f)) => fields.push(f), Some(TraitMethod::Downcast(_d)) => { unimplemented!(); - // match implementers.iter_mut().find(|i| i.ty == d.ty) { - // Some(impler) => { - // if let Some(external) = &impler.downcast { - // err_duplicate_downcast(m, external, &impler.ty); - // } else { - // impler.downcast = d.downcast; - // impler.context = d.context; - // } - // } - // None => err_only_implementer_downcast(&m.sig), - // } } _ => {} } @@ -363,16 +331,8 @@ fn expand_on_trait_new( }) }) }) - // .or_else(|| { - // implementers - // .iter() - // .find_map(|impler| impler.context.as_ref()) - // .cloned() - // }) .unwrap_or_else(|| parse_quote! { () }); - // let is_trait_object = attr.r#dyn.is_some(); - let is_async_trait = attr.asyncness.is_some() || ast .items @@ -387,22 +347,6 @@ fn expand_on_trait_new( _ => false, }); - // let ty = if is_trait_object { - // Type::TraitObject(Box::new(TraitObjectType::new( - // &ast, - // &attr, - // scalar.clone(), - // context.clone(), - // ))) - // } else { - // Type::Enum(Box::new(EnumType::new( - // &ast, - // &attr, - // &implementers, - // scalar.clone(), - // ))) - // }; - let enum_alias_ident = attr .r#enum .as_deref() @@ -431,27 +375,6 @@ fn expand_on_trait_new( .collect(), }; - // Attach the `juniper::AsDynGraphQLValue` on top of the trait if dynamic dispatch is used. - // if is_trait_object { - // ast.attrs.push(parse_quote! { - // #[allow(unused_qualifications, clippy::type_repetition_in_bounds)] - // }); - // - // let scalar_ty = scalar.generic_ty(); - // if !scalar.is_explicit_generic() { - // let default_ty = scalar.default_ty(); - // ast.generics - // .params - // .push(parse_quote! { #scalar_ty = #default_ty }); - // } - // ast.generics - // .make_where_clause() - // .predicates - // .push(parse_quote! { #scalar_ty: ::juniper::ScalarValue }); - // ast.supertraits - // .push(parse_quote! { ::juniper::AsDynGraphQLValue<#scalar_ty> }); - // } - if is_async_trait { if has_default_async_methods { // Hack for object safety. See details: https://docs.rs/async-trait/#dyn-traits From 5e7230f95ede63acde71005f7b2959ad1cb13421 Mon Sep 17 00:00:00 2001 From: ilslv Date: Thu, 30 Dec 2021 16:02:14 +0300 Subject: [PATCH 20/53] WIP (moving to readable const asserts) --- juniper/src/macros/helper/mod.rs | 175 ++++++++++++++++++++++++++++++- 1 file changed, 174 insertions(+), 1 deletion(-) diff --git a/juniper/src/macros/helper/mod.rs b/juniper/src/macros/helper/mod.rs index f5b4619aa..15bead5d1 100644 --- a/juniper/src/macros/helper/mod.rs +++ b/juniper/src/macros/helper/mod.rs @@ -2,7 +2,7 @@ pub mod subscription; -use std::{fmt, rc::Rc, sync::Arc}; +use std::{fmt, mem, rc::Rc, sync::Arc}; use futures::future::{self, BoxFuture}; @@ -130,6 +130,179 @@ pub const fn str_eq(l: &str, r: &str) -> bool { true } +pub const fn number_of_digits(n: u128) -> usize { + if n == 0 { + return 1; + } + + let mut len = 0; + let mut current = n; + while current % 10 != 0 { + len += 1; + current /= 10; + } + len +} + +pub const fn str_len_from_wrapped_val(ty: Type, v: WrappedValue) -> usize { + let mut len = ty.len() + 1; // Type! + + let mut current_wrap_val = v; + while current_wrap_val % 10 != 0 { + match current_wrap_val % 10 { + 2 => len -= 1, // remove ! + 3 => len += 3, // [Type]! + _ => {} + } + + current_wrap_val /= 10; + } + + len +} + +const _: () = check_valid_field_args(); + +pub const fn check_valid_field_args() { + const BASE: &[(Name, Type, WrappedValue)] = &[("test", "String", 1)]; + const IMPL: &[(Name, Type, WrappedValue)] = &[("test", "String", 12323)]; + + const OPENING_BRACKET: &str = "["; + const CLOSING_BRACKET: &str = "]"; + const BANG: &str = "!"; + + const fn find_len() -> usize { + let mut base_i = 0; + while base_i < BASE.len() { + let (base_name, base_type, base_wrap_val) = BASE[base_i]; + + let mut impl_i = 0; + while impl_i < IMPL.len() { + let (impl_name, impl_type, impl_wrap_val) = IMPL[impl_i]; + + if str_eq(base_name, impl_name) { + if !(str_eq(base_type, impl_type) && base_wrap_val == impl_wrap_val) { + return str_len_from_wrapped_val(impl_type, impl_wrap_val); + } + } + impl_i += 1; + } + base_i += 1; + } + 0 + } + + let mut base_i = 0; + while base_i < BASE.len() { + let (base_name, base_type, base_wrap_val) = BASE[base_i]; + + let mut impl_i = 0; + let mut was_found = false; + while impl_i < IMPL.len() { + let (impl_name, impl_type, impl_wrap_val) = IMPL[impl_i]; + + if str_eq(base_name, impl_name) { + if str_eq(base_type, impl_type) && base_wrap_val == impl_wrap_val { + was_found = true; + } else { + const IMPL_TYPE_LEN: usize = find_len(); + let mut impl_type_arr: [u8; IMPL_TYPE_LEN] = [0; IMPL_TYPE_LEN]; + + let mut current_start = 0; + let mut current_end = IMPL_TYPE_LEN - 1; + let mut current_wrap_val = impl_wrap_val; + let mut is_null = false; + while current_wrap_val % 10 != 0 { + match current_wrap_val % 10 { + 2 => is_null = true, + 3 => { + if is_null { + let mut i = 0; + while i < OPENING_BRACKET.as_bytes().len() { + impl_type_arr[current_start + i] = + OPENING_BRACKET.as_bytes()[i]; + i += 1; + } + current_start += i; + i = 0; + while i < CLOSING_BRACKET.as_bytes().len() { + impl_type_arr[current_end + - CLOSING_BRACKET.as_bytes().len() + + i + + 1] = CLOSING_BRACKET.as_bytes()[i]; + i += 1; + } + current_end -= i; + } else { + let mut i = 0; + while i < OPENING_BRACKET.as_bytes().len() { + impl_type_arr[current_start + i] = + OPENING_BRACKET.as_bytes()[i]; + i += 1; + } + current_start += i; + i = 0; + while i < BANG.as_bytes().len() { + impl_type_arr + [current_end - BANG.as_bytes().len() + i + 1] = + BANG.as_bytes()[i]; + i += 1; + } + current_end -= i; + i = 0; + while i < CLOSING_BRACKET.as_bytes().len() { + impl_type_arr[current_end + - CLOSING_BRACKET.as_bytes().len() + + i + + 1] = CLOSING_BRACKET.as_bytes()[i]; + i += 1; + } + current_end -= i; + } + is_null = false; + } + _ => {} + } + + current_wrap_val /= 10; + } + + let mut i = 0; + while i < impl_type.as_bytes().len() { + impl_type_arr[current_start + i] = impl_type.as_bytes()[i]; + i += 1; + } + i = 0; + if !is_null { + while i < BANG.as_bytes().len() { + impl_type_arr[current_end - BANG.as_bytes().len() + i + 1] = + BANG.as_bytes()[i]; + i += 1; + } + } + + let impl_type_str: &str = unsafe { mem::transmute(impl_type_arr.as_slice()) }; + + // TODO: format incorrect field type error. + panic!(impl_type_str); + } + } else if impl_wrap_val % 10 != 2 { + // TODO: format field must be nullable error. + panic!(impl_name); + } + + impl_i += 1; + } + + if !was_found { + // TODO: format field not found error. + panic!(base_name); + } + + base_i += 1; + } +} + pub const fn is_valid_field_args( base_interface: &[(Name, Type, WrappedValue)], implementation: &[(Name, Type, WrappedValue)], From d17ba4fa03917d2d59f6cb42d25591ccf0a76a0f Mon Sep 17 00:00:00 2001 From: ilslv Date: Thu, 30 Dec 2021 17:47:25 +0300 Subject: [PATCH 21/53] WIP (finally good const assert message) --- juniper/src/macros/helper/mod.rs | 366 +++++++++++++++++++++++-------- 1 file changed, 272 insertions(+), 94 deletions(-) diff --git a/juniper/src/macros/helper/mod.rs b/juniper/src/macros/helper/mod.rs index 15bead5d1..32b6a9e56 100644 --- a/juniper/src/macros/helper/mod.rs +++ b/juniper/src/macros/helper/mod.rs @@ -161,145 +161,323 @@ pub const fn str_len_from_wrapped_val(ty: Type, v: WrappedValue) -> usize { len } +macro_rules! const_concat { + ($($s:expr),* $(,)?) => {{ + const LEN: usize = 0 $(+ $s.len())*; + const CNT: usize = [$($s),*].len(); + const fn concat(input: [&str; CNT]) -> [u8; LEN] { + let mut bytes = [0; LEN]; + let (mut i, mut byte) = (0, 0); + while i < CNT { + let mut b = 0; + while b < input[i].len() { + bytes[byte] = input[i].as_bytes()[b]; + byte += 1; + b += 1; + } + i += 1; + } + bytes + } + const CON: [u8; LEN] = concat([$($s),*]); + unsafe { std::str::from_utf8_unchecked(&CON) } + }}; +} + const _: () = check_valid_field_args(); pub const fn check_valid_field_args() { - const BASE: &[(Name, Type, WrappedValue)] = &[("test", "String", 1)]; - const IMPL: &[(Name, Type, WrappedValue)] = &[("test", "String", 12323)]; + const BASE: &[(Name, Type, WrappedValue)] = &[("test", "String", 13)]; + const IMPL: &[(Name, Type, WrappedValue)] = + &[("test", "String", 123), ("additional", "String", 12)]; const OPENING_BRACKET: &str = "["; const CLOSING_BRACKET: &str = "]"; const BANG: &str = "!"; - const fn find_len() -> usize { + struct Error { + base: (Name, Type, WrappedValue), + implementation: (Name, Type, WrappedValue), + cause: Cause, + } + + enum Cause { + RequiredField, + AdditionalNonNullableField, + TypeMismatch, + } + + const fn unwrap_error(v: Result<(), Error>) -> Error { + match v { + Ok(()) => Error { + base: ("empty", "empty", 1), + implementation: ("empty", "empty", 1), + cause: Cause::TypeMismatch, + }, + Err(err) => err, + } + } + + const fn check() -> Result<(), Error> { let mut base_i = 0; while base_i < BASE.len() { let (base_name, base_type, base_wrap_val) = BASE[base_i]; let mut impl_i = 0; + let mut was_found = false; while impl_i < IMPL.len() { let (impl_name, impl_type, impl_wrap_val) = IMPL[impl_i]; if str_eq(base_name, impl_name) { - if !(str_eq(base_type, impl_type) && base_wrap_val == impl_wrap_val) { - return str_len_from_wrapped_val(impl_type, impl_wrap_val); + if str_eq(base_type, impl_type) && base_wrap_val == impl_wrap_val { + was_found = true; + } else { + return Err(Error { + base: (base_name, base_type, base_wrap_val), + implementation: (impl_name, impl_type, impl_wrap_val), + cause: Cause::TypeMismatch, + }); } } + impl_i += 1; } + + if !was_found { + return Err(Error { + base: (base_name, base_type, base_wrap_val), + implementation: (base_name, base_type, base_wrap_val), + cause: Cause::RequiredField, + }); + } + base_i += 1; } - 0 - } - - let mut base_i = 0; - while base_i < BASE.len() { - let (base_name, base_type, base_wrap_val) = BASE[base_i]; let mut impl_i = 0; - let mut was_found = false; while impl_i < IMPL.len() { - let (impl_name, impl_type, impl_wrap_val) = IMPL[impl_i]; + let (impl_name, impl_type, impl_wrapped_val) = IMPL[impl_i]; + impl_i += 1; - if str_eq(base_name, impl_name) { - if str_eq(base_type, impl_type) && base_wrap_val == impl_wrap_val { + if impl_wrapped_val % 10 == 2 { + continue; + } + + let mut base_i = 0; + let mut was_found = false; + while base_i < BASE.len() { + let (base_name, _, _) = BASE[base_i]; + if str_eq(base_name, impl_name) { was_found = true; - } else { - const IMPL_TYPE_LEN: usize = find_len(); - let mut impl_type_arr: [u8; IMPL_TYPE_LEN] = [0; IMPL_TYPE_LEN]; - - let mut current_start = 0; - let mut current_end = IMPL_TYPE_LEN - 1; - let mut current_wrap_val = impl_wrap_val; - let mut is_null = false; - while current_wrap_val % 10 != 0 { - match current_wrap_val % 10 { - 2 => is_null = true, - 3 => { - if is_null { - let mut i = 0; - while i < OPENING_BRACKET.as_bytes().len() { - impl_type_arr[current_start + i] = - OPENING_BRACKET.as_bytes()[i]; - i += 1; - } - current_start += i; - i = 0; - while i < CLOSING_BRACKET.as_bytes().len() { - impl_type_arr[current_end - - CLOSING_BRACKET.as_bytes().len() - + i - + 1] = CLOSING_BRACKET.as_bytes()[i]; - i += 1; - } - current_end -= i; - } else { - let mut i = 0; - while i < OPENING_BRACKET.as_bytes().len() { - impl_type_arr[current_start + i] = - OPENING_BRACKET.as_bytes()[i]; - i += 1; - } - current_start += i; - i = 0; - while i < BANG.as_bytes().len() { - impl_type_arr - [current_end - BANG.as_bytes().len() + i + 1] = - BANG.as_bytes()[i]; - i += 1; - } - current_end -= i; - i = 0; - while i < CLOSING_BRACKET.as_bytes().len() { - impl_type_arr[current_end - - CLOSING_BRACKET.as_bytes().len() - + i - + 1] = CLOSING_BRACKET.as_bytes()[i]; - i += 1; - } - current_end -= i; - } - is_null = false; - } - _ => {} - } + } + base_i += 1; + } + if !was_found { + return Err(Error { + base: (impl_name, impl_type, impl_wrapped_val), + implementation: (impl_name, impl_type, impl_wrapped_val), + cause: Cause::AdditionalNonNullableField, + }); + } + } - current_wrap_val /= 10; - } + Ok(()) + } - let mut i = 0; - while i < impl_type.as_bytes().len() { - impl_type_arr[current_start + i] = impl_type.as_bytes()[i]; - i += 1; - } - i = 0; - if !is_null { + const RES: Result<(), Error> = check(); + if RES.is_ok() { + return; + } + + const ERROR: Error = unwrap_error(RES); + + const IMPL_NAME: &str = ERROR.implementation.0; + const IMPL_TYPE_LEN: usize = + str_len_from_wrapped_val(ERROR.implementation.1, ERROR.implementation.2); + + const BASE_NAME: &str = ERROR.base.0; + const BASE_TYPE_LEN: usize = str_len_from_wrapped_val(ERROR.base.1, ERROR.base.2); + + const fn format_impl_type() -> [u8; IMPL_TYPE_LEN] { + let (_, impl_type, impl_wrap_val) = ERROR.implementation; + let mut impl_type_arr: [u8; IMPL_TYPE_LEN] = [0; IMPL_TYPE_LEN]; + + let mut current_start = 0; + let mut current_end = IMPL_TYPE_LEN - 1; + let mut current_wrap_val = impl_wrap_val; + let mut is_null = false; + while current_wrap_val % 10 != 0 { + match current_wrap_val % 10 { + 2 => is_null = true, + 3 => { + if is_null { + let mut i = 0; + while i < OPENING_BRACKET.as_bytes().len() { + impl_type_arr[current_start + i] = OPENING_BRACKET.as_bytes()[i]; + i += 1; + } + current_start += i; + i = 0; + while i < CLOSING_BRACKET.as_bytes().len() { + impl_type_arr[current_end - CLOSING_BRACKET.as_bytes().len() + i + 1] = + CLOSING_BRACKET.as_bytes()[i]; + i += 1; + } + current_end -= i; + } else { + let mut i = 0; + while i < OPENING_BRACKET.as_bytes().len() { + impl_type_arr[current_start + i] = OPENING_BRACKET.as_bytes()[i]; + i += 1; + } + current_start += i; + i = 0; while i < BANG.as_bytes().len() { impl_type_arr[current_end - BANG.as_bytes().len() + i + 1] = BANG.as_bytes()[i]; i += 1; } + current_end -= i; + i = 0; + while i < CLOSING_BRACKET.as_bytes().len() { + impl_type_arr[current_end - CLOSING_BRACKET.as_bytes().len() + i + 1] = + CLOSING_BRACKET.as_bytes()[i]; + i += 1; + } + current_end -= i; } + is_null = false; + } + _ => {} + } - let impl_type_str: &str = unsafe { mem::transmute(impl_type_arr.as_slice()) }; + current_wrap_val /= 10; + } - // TODO: format incorrect field type error. - panic!(impl_type_str); + let mut i = 0; + while i < impl_type.as_bytes().len() { + impl_type_arr[current_start + i] = impl_type.as_bytes()[i]; + i += 1; + } + i = 0; + if !is_null { + while i < BANG.as_bytes().len() { + impl_type_arr[current_end - BANG.as_bytes().len() + i + 1] = BANG.as_bytes()[i]; + i += 1; + } + } + + impl_type_arr + } + + const IMPL_TYPE_ARR: [u8; IMPL_TYPE_LEN] = format_impl_type(); + const IMPL_TYPE_FORMATTED: &str = unsafe { mem::transmute(IMPL_TYPE_ARR.as_slice()) }; + + const fn format_base_type() -> [u8; BASE_TYPE_LEN] { + let (_, base_type, base_wrap_val) = ERROR.base; + let mut base_type_arr: [u8; BASE_TYPE_LEN] = [0; BASE_TYPE_LEN]; + + let mut current_start = 0; + let mut current_end = BASE_TYPE_LEN - 1; + let mut current_wrap_val = base_wrap_val; + let mut is_null = false; + while current_wrap_val % 10 != 0 { + match current_wrap_val % 10 { + 2 => is_null = true, + 3 => { + if is_null { + let mut i = 0; + while i < OPENING_BRACKET.as_bytes().len() { + base_type_arr[current_start + i] = OPENING_BRACKET.as_bytes()[i]; + i += 1; + } + current_start += i; + i = 0; + while i < CLOSING_BRACKET.as_bytes().len() { + base_type_arr[current_end - CLOSING_BRACKET.as_bytes().len() + i + 1] = + CLOSING_BRACKET.as_bytes()[i]; + i += 1; + } + current_end -= i; + } else { + let mut i = 0; + while i < OPENING_BRACKET.as_bytes().len() { + base_type_arr[current_start + i] = OPENING_BRACKET.as_bytes()[i]; + i += 1; + } + current_start += i; + i = 0; + while i < BANG.as_bytes().len() { + base_type_arr[current_end - BANG.as_bytes().len() + i + 1] = + BANG.as_bytes()[i]; + i += 1; + } + current_end -= i; + i = 0; + while i < CLOSING_BRACKET.as_bytes().len() { + base_type_arr[current_end - CLOSING_BRACKET.as_bytes().len() + i + 1] = + CLOSING_BRACKET.as_bytes()[i]; + i += 1; + } + current_end -= i; + } + is_null = false; } - } else if impl_wrap_val % 10 != 2 { - // TODO: format field must be nullable error. - panic!(impl_name); + _ => {} } - impl_i += 1; + current_wrap_val /= 10; } - if !was_found { - // TODO: format field not found error. - panic!(base_name); + let mut i = 0; + while i < base_type.as_bytes().len() { + base_type_arr[current_start + i] = base_type.as_bytes()[i]; + i += 1; + } + i = 0; + if !is_null { + while i < BANG.as_bytes().len() { + base_type_arr[current_end - BANG.as_bytes().len() + i + 1] = BANG.as_bytes()[i]; + i += 1; + } } - base_i += 1; + base_type_arr + } + + const BASE_TYPE_ARR: [u8; BASE_TYPE_LEN] = format_base_type(); + const BASE_TYPE_FORMATTED: &str = unsafe { mem::transmute(BASE_TYPE_ARR.as_slice()) }; + + match ERROR.cause { + Cause::TypeMismatch => { + panic!(const_concat!( + "Field `", + BASE_NAME, + "`: expected type `", + BASE_TYPE_FORMATTED, + "`, found: `", + IMPL_TYPE_FORMATTED, + "`.", + )); + } + Cause::RequiredField => { + panic!(const_concat!( + "Field `", + BASE_NAME, + "` of type `", + BASE_TYPE_FORMATTED, + "` was expected, but not found." + )); + } + Cause::AdditionalNonNullableField => { + panic!(const_concat!( + "Field `", + IMPL_NAME, + "` of type `", + IMPL_TYPE_FORMATTED, + "` not present on the interface and so has to be nullable." + )); + } } } From 8cb9b8127bdb3e9cc3e7eef9f41995a4da38f30a Mon Sep 17 00:00:00 2001 From: ilslv Date: Thu, 30 Dec 2021 18:09:10 +0300 Subject: [PATCH 22/53] WIP (corrections) --- juniper/src/macros/helper/mod.rs | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/juniper/src/macros/helper/mod.rs b/juniper/src/macros/helper/mod.rs index 32b6a9e56..309cd7892 100644 --- a/juniper/src/macros/helper/mod.rs +++ b/juniper/src/macros/helper/mod.rs @@ -145,13 +145,13 @@ pub const fn number_of_digits(n: u128) -> usize { } pub const fn str_len_from_wrapped_val(ty: Type, v: WrappedValue) -> usize { - let mut len = ty.len() + 1; // Type! + let mut len = ty.as_bytes().len() + "!".as_bytes().len(); // Type! let mut current_wrap_val = v; while current_wrap_val % 10 != 0 { match current_wrap_val % 10 { - 2 => len -= 1, // remove ! - 3 => len += 3, // [Type]! + 2 => len -= "!".as_bytes().len(), // remove ! + 3 => len += "[]!".as_bytes().len(), // [Type]! _ => {} } @@ -231,6 +231,7 @@ pub const fn check_valid_field_args() { if str_eq(base_name, impl_name) { if str_eq(base_type, impl_type) && base_wrap_val == impl_wrap_val { was_found = true; + break; } else { return Err(Error { base: (base_name, base_type, base_wrap_val), @@ -269,6 +270,7 @@ pub const fn check_valid_field_args() { let (base_name, _, _) = BASE[base_i]; if str_eq(base_name, impl_name) { was_found = true; + break; } base_i += 1; } @@ -450,7 +452,7 @@ pub const fn check_valid_field_args() { match ERROR.cause { Cause::TypeMismatch => { - panic!(const_concat!( + const MSG: &str = const_concat!( "Field `", BASE_NAME, "`: expected type `", @@ -458,25 +460,28 @@ pub const fn check_valid_field_args() { "`, found: `", IMPL_TYPE_FORMATTED, "`.", - )); + ); + panic!("{}", MSG); } Cause::RequiredField => { - panic!(const_concat!( + const MSG: &str = const_concat!( "Field `", BASE_NAME, "` of type `", BASE_TYPE_FORMATTED, "` was expected, but not found." - )); + ); + panic!("{}", MSG); } Cause::AdditionalNonNullableField => { - panic!(const_concat!( + const MSG: &str = const_concat!( "Field `", IMPL_NAME, "` of type `", IMPL_TYPE_FORMATTED, "` not present on the interface and so has to be nullable." - )); + ); + panic!("{}", MSG); } } } From 116cdd9b324e7e2c1e9db823f6b9706bae1be692 Mon Sep 17 00:00:00 2001 From: ilslv Date: Mon, 3 Jan 2022 09:43:08 +0300 Subject: [PATCH 23/53] WIP (pretty field arguments check) --- .../src/codegen/interface_attr.rs | 1 - .../juniper_tests/src/codegen/object_attr.rs | 2 +- juniper/src/macros/helper/mod.rs | 554 ++++++++---------- juniper/src/macros/mod.rs | 1 + juniper_codegen/src/graphql_interface/new.rs | 38 +- 5 files changed, 266 insertions(+), 330 deletions(-) diff --git a/integration_tests/juniper_tests/src/codegen/interface_attr.rs b/integration_tests/juniper_tests/src/codegen/interface_attr.rs index 0a76fd315..56b6fa3d2 100644 --- a/integration_tests/juniper_tests/src/codegen/interface_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/interface_attr.rs @@ -340,7 +340,6 @@ mod trivial_with_trait_imp { primary_function: String, } - #[graphql_interface] impl Character for Droid { fn id(&self) -> &str { &self.id diff --git a/integration_tests/juniper_tests/src/codegen/object_attr.rs b/integration_tests/juniper_tests/src/codegen/object_attr.rs index 76a1c8542..304ba34c5 100644 --- a/integration_tests/juniper_tests/src/codegen/object_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/object_attr.rs @@ -2022,7 +2022,7 @@ mod switched_context { async fn switch_res_opt<'e, S: ScalarValue>( &self, - executor: &'e Executor<'e, 'e, CustomContext, S>, + executor: &'e Executor<'_, '_, CustomContext, S>, ) -> FieldResult> { Ok(Some((executor.context(), Droid { id: 3 }))) } diff --git a/juniper/src/macros/helper/mod.rs b/juniper/src/macros/helper/mod.rs index 309cd7892..1d54c349f 100644 --- a/juniper/src/macros/helper/mod.rs +++ b/juniper/src/macros/helper/mod.rs @@ -2,7 +2,7 @@ pub mod subscription; -use std::{fmt, mem, rc::Rc, sync::Arc}; +use std::{fmt, rc::Rc, sync::Arc}; use futures::future::{self, BoxFuture}; @@ -161,6 +161,7 @@ pub const fn str_len_from_wrapped_val(ty: Type, v: WrappedValue) -> usize { len } +#[macro_export] macro_rules! const_concat { ($($s:expr),* $(,)?) => {{ const LEN: usize = 0 $(+ $s.len())*; @@ -184,347 +185,274 @@ macro_rules! const_concat { }}; } -const _: () = check_valid_field_args(); - -pub const fn check_valid_field_args() { - const BASE: &[(Name, Type, WrappedValue)] = &[("test", "String", 13)]; - const IMPL: &[(Name, Type, WrappedValue)] = - &[("test", "String", 123), ("additional", "String", 12)]; - - const OPENING_BRACKET: &str = "["; - const CLOSING_BRACKET: &str = "]"; - const BANG: &str = "!"; - - struct Error { - base: (Name, Type, WrappedValue), - implementation: (Name, Type, WrappedValue), - cause: Cause, - } - - enum Cause { - RequiredField, - AdditionalNonNullableField, - TypeMismatch, - } - - const fn unwrap_error(v: Result<(), Error>) -> Error { - match v { - Ok(()) => Error { - base: ("empty", "empty", 1), - implementation: ("empty", "empty", 1), - cause: Cause::TypeMismatch, - }, - Err(err) => err, - } - } - - const fn check() -> Result<(), Error> { - let mut base_i = 0; - while base_i < BASE.len() { - let (base_name, base_type, base_wrap_val) = BASE[base_i]; - - let mut impl_i = 0; - let mut was_found = false; - while impl_i < IMPL.len() { - let (impl_name, impl_type, impl_wrap_val) = IMPL[impl_i]; - - if str_eq(base_name, impl_name) { - if str_eq(base_type, impl_type) && base_wrap_val == impl_wrap_val { - was_found = true; - break; - } else { - return Err(Error { - base: (base_name, base_type, base_wrap_val), - implementation: (impl_name, impl_type, impl_wrap_val), - cause: Cause::TypeMismatch, - }); +#[macro_export] +macro_rules! format_type { + ($ty: expr) => {{ + const TYPE: ( + $crate::macros::helper::Name, + $crate::macros::helper::Type, + $crate::macros::helper::WrappedValue, + ) = $ty; + const RES_LEN: usize = $crate::macros::helper::str_len_from_wrapped_val(TYPE.1, TYPE.2); + + const OPENING_BRACKET: &str = "["; + const CLOSING_BRACKET: &str = "]"; + const BANG: &str = "!"; + + const fn format_type_arr() -> [u8; RES_LEN] { + let (_, ty, wrap_val) = TYPE; + let mut type_arr: [u8; RES_LEN] = [0; RES_LEN]; + + let mut current_start = 0; + let mut current_end = RES_LEN - 1; + let mut current_wrap_val = wrap_val; + let mut is_null = false; + while current_wrap_val % 10 != 0 { + match current_wrap_val % 10 { + 2 => is_null = true, + 3 => { + if is_null { + let mut i = 0; + while i < OPENING_BRACKET.as_bytes().len() { + type_arr[current_start + i] = OPENING_BRACKET.as_bytes()[i]; + i += 1; + } + current_start += i; + i = 0; + while i < CLOSING_BRACKET.as_bytes().len() { + type_arr[current_end - CLOSING_BRACKET.as_bytes().len() + i + 1] = + CLOSING_BRACKET.as_bytes()[i]; + i += 1; + } + current_end -= i; + } else { + let mut i = 0; + while i < OPENING_BRACKET.as_bytes().len() { + type_arr[current_start + i] = OPENING_BRACKET.as_bytes()[i]; + i += 1; + } + current_start += i; + i = 0; + while i < BANG.as_bytes().len() { + type_arr[current_end - BANG.as_bytes().len() + i + 1] = + BANG.as_bytes()[i]; + i += 1; + } + current_end -= i; + i = 0; + while i < CLOSING_BRACKET.as_bytes().len() { + type_arr[current_end - CLOSING_BRACKET.as_bytes().len() + i + 1] = + CLOSING_BRACKET.as_bytes()[i]; + i += 1; + } + current_end -= i; + } + is_null = false; } + _ => {} } - impl_i += 1; + current_wrap_val /= 10; } - if !was_found { - return Err(Error { - base: (base_name, base_type, base_wrap_val), - implementation: (base_name, base_type, base_wrap_val), - cause: Cause::RequiredField, - }); + let mut i = 0; + while i < ty.as_bytes().len() { + type_arr[current_start + i] = ty.as_bytes()[i]; + i += 1; + } + i = 0; + if !is_null { + while i < BANG.as_bytes().len() { + type_arr[current_end - BANG.as_bytes().len() + i + 1] = BANG.as_bytes()[i]; + i += 1; + } } - base_i += 1; + type_arr } - let mut impl_i = 0; - while impl_i < IMPL.len() { - let (impl_name, impl_type, impl_wrapped_val) = IMPL[impl_i]; - impl_i += 1; + const TYPE_ARR: [u8; RES_LEN] = format_type_arr(); + // SAFETY: This is safe, as `TYPE_ARR` was formatted from `TYPE.Name`, + // `[`, `]` and `!`. + const TYPE_FORMATTED: &str = unsafe { ::std::mem::transmute(TYPE_ARR.as_slice()) }; + TYPE_FORMATTED + }}; +} + +#[macro_export] +macro_rules! check_field_args { + ( + $field_name: expr, + ( + $base_name: expr, + $base_args: expr $(,)? + ), ( + $impl_name: expr, + $impl_args: expr $(,)? + ) $(,)?) => { + const _: () = { + type FullArg = ( + $crate::macros::helper::Name, + $crate::macros::helper::Type, + $crate::macros::helper::WrappedValue, + ); - if impl_wrapped_val % 10 == 2 { - continue; + const FIELD_NAME: &str = $field_name; + const BASE_NAME: &str = $base_name; + const IMPL_NAME: &str = $impl_name; + const BASE_ARGS: &[FullArg] = $base_args; + const IMPL_ARGS: &[FullArg] = $impl_args; + + struct Error { + cause: Cause, + base: FullArg, + implementation: FullArg, } - let mut base_i = 0; - let mut was_found = false; - while base_i < BASE.len() { - let (base_name, _, _) = BASE[base_i]; - if str_eq(base_name, impl_name) { - was_found = true; - break; - } - base_i += 1; + enum Cause { + RequiredField, + AdditionalNonNullableField, + TypeMismatch, } - if !was_found { - return Err(Error { - base: (impl_name, impl_type, impl_wrapped_val), - implementation: (impl_name, impl_type, impl_wrapped_val), - cause: Cause::AdditionalNonNullableField, - }); + + const fn unwrap_error(v: ::std::result::Result<(), Error>) -> Error { + match v { + Ok(()) => Error { + cause: Cause::RequiredField, + base: ("unreachable", "unreachable", 1), + implementation: ("unreachable", "unreachable", 1), + }, + Err(err) => err, + } } - } - Ok(()) - } + const fn check() -> Result<(), Error> { + let mut base_i = 0; + while base_i < BASE_ARGS.len() { + let (base_name, base_type, base_wrap_val) = BASE_ARGS[base_i]; + + let mut impl_i = 0; + let mut was_found = false; + while impl_i < IMPL_ARGS.len() { + let (impl_name, impl_type, impl_wrap_val) = IMPL_ARGS[impl_i]; + + if $crate::macros::helper::str_eq(base_name, impl_name) { + if $crate::macros::helper::str_eq(base_type, impl_type) + && base_wrap_val == impl_wrap_val + { + was_found = true; + break; + } else { + return Err(Error { + cause: Cause::TypeMismatch, + base: (base_name, base_type, base_wrap_val), + implementation: (impl_name, impl_type, impl_wrap_val), + }); + } + } - const RES: Result<(), Error> = check(); - if RES.is_ok() { - return; - } + impl_i += 1; + } - const ERROR: Error = unwrap_error(RES); - - const IMPL_NAME: &str = ERROR.implementation.0; - const IMPL_TYPE_LEN: usize = - str_len_from_wrapped_val(ERROR.implementation.1, ERROR.implementation.2); - - const BASE_NAME: &str = ERROR.base.0; - const BASE_TYPE_LEN: usize = str_len_from_wrapped_val(ERROR.base.1, ERROR.base.2); - - const fn format_impl_type() -> [u8; IMPL_TYPE_LEN] { - let (_, impl_type, impl_wrap_val) = ERROR.implementation; - let mut impl_type_arr: [u8; IMPL_TYPE_LEN] = [0; IMPL_TYPE_LEN]; - - let mut current_start = 0; - let mut current_end = IMPL_TYPE_LEN - 1; - let mut current_wrap_val = impl_wrap_val; - let mut is_null = false; - while current_wrap_val % 10 != 0 { - match current_wrap_val % 10 { - 2 => is_null = true, - 3 => { - if is_null { - let mut i = 0; - while i < OPENING_BRACKET.as_bytes().len() { - impl_type_arr[current_start + i] = OPENING_BRACKET.as_bytes()[i]; - i += 1; - } - current_start += i; - i = 0; - while i < CLOSING_BRACKET.as_bytes().len() { - impl_type_arr[current_end - CLOSING_BRACKET.as_bytes().len() + i + 1] = - CLOSING_BRACKET.as_bytes()[i]; - i += 1; - } - current_end -= i; - } else { - let mut i = 0; - while i < OPENING_BRACKET.as_bytes().len() { - impl_type_arr[current_start + i] = OPENING_BRACKET.as_bytes()[i]; - i += 1; - } - current_start += i; - i = 0; - while i < BANG.as_bytes().len() { - impl_type_arr[current_end - BANG.as_bytes().len() + i + 1] = - BANG.as_bytes()[i]; - i += 1; - } - current_end -= i; - i = 0; - while i < CLOSING_BRACKET.as_bytes().len() { - impl_type_arr[current_end - CLOSING_BRACKET.as_bytes().len() + i + 1] = - CLOSING_BRACKET.as_bytes()[i]; - i += 1; - } - current_end -= i; + if !was_found { + return Err(Error { + cause: Cause::RequiredField, + base: (base_name, base_type, base_wrap_val), + implementation: (base_name, base_type, base_wrap_val), + }); } - is_null = false; + + base_i += 1; } - _ => {} - } - current_wrap_val /= 10; - } + let mut impl_i = 0; + while impl_i < IMPL_ARGS.len() { + let (impl_name, impl_type, impl_wrapped_val) = IMPL_ARGS[impl_i]; + impl_i += 1; - let mut i = 0; - while i < impl_type.as_bytes().len() { - impl_type_arr[current_start + i] = impl_type.as_bytes()[i]; - i += 1; - } - i = 0; - if !is_null { - while i < BANG.as_bytes().len() { - impl_type_arr[current_end - BANG.as_bytes().len() + i + 1] = BANG.as_bytes()[i]; - i += 1; - } - } - - impl_type_arr - } + if impl_wrapped_val % 10 == 2 { + continue; + } - const IMPL_TYPE_ARR: [u8; IMPL_TYPE_LEN] = format_impl_type(); - const IMPL_TYPE_FORMATTED: &str = unsafe { mem::transmute(IMPL_TYPE_ARR.as_slice()) }; - - const fn format_base_type() -> [u8; BASE_TYPE_LEN] { - let (_, base_type, base_wrap_val) = ERROR.base; - let mut base_type_arr: [u8; BASE_TYPE_LEN] = [0; BASE_TYPE_LEN]; - - let mut current_start = 0; - let mut current_end = BASE_TYPE_LEN - 1; - let mut current_wrap_val = base_wrap_val; - let mut is_null = false; - while current_wrap_val % 10 != 0 { - match current_wrap_val % 10 { - 2 => is_null = true, - 3 => { - if is_null { - let mut i = 0; - while i < OPENING_BRACKET.as_bytes().len() { - base_type_arr[current_start + i] = OPENING_BRACKET.as_bytes()[i]; - i += 1; - } - current_start += i; - i = 0; - while i < CLOSING_BRACKET.as_bytes().len() { - base_type_arr[current_end - CLOSING_BRACKET.as_bytes().len() + i + 1] = - CLOSING_BRACKET.as_bytes()[i]; - i += 1; - } - current_end -= i; - } else { - let mut i = 0; - while i < OPENING_BRACKET.as_bytes().len() { - base_type_arr[current_start + i] = OPENING_BRACKET.as_bytes()[i]; - i += 1; - } - current_start += i; - i = 0; - while i < BANG.as_bytes().len() { - base_type_arr[current_end - BANG.as_bytes().len() + i + 1] = - BANG.as_bytes()[i]; - i += 1; + let mut base_i = 0; + let mut was_found = false; + while base_i < BASE_ARGS.len() { + let (base_name, _, _) = BASE_ARGS[base_i]; + if $crate::macros::helper::str_eq(base_name, impl_name) { + was_found = true; + break; } - current_end -= i; - i = 0; - while i < CLOSING_BRACKET.as_bytes().len() { - base_type_arr[current_end - CLOSING_BRACKET.as_bytes().len() + i + 1] = - CLOSING_BRACKET.as_bytes()[i]; - i += 1; - } - current_end -= i; + base_i += 1; + } + if !was_found { + return Err(Error { + cause: Cause::AdditionalNonNullableField, + base: (impl_name, impl_type, impl_wrapped_val), + implementation: (impl_name, impl_type, impl_wrapped_val), + }); } - is_null = false; } - _ => {} - } - current_wrap_val /= 10; - } - - let mut i = 0; - while i < base_type.as_bytes().len() { - base_type_arr[current_start + i] = base_type.as_bytes()[i]; - i += 1; - } - i = 0; - if !is_null { - while i < BANG.as_bytes().len() { - base_type_arr[current_end - BANG.as_bytes().len() + i + 1] = BANG.as_bytes()[i]; - i += 1; + Ok(()) } - } - - base_type_arr - } - - const BASE_TYPE_ARR: [u8; BASE_TYPE_LEN] = format_base_type(); - const BASE_TYPE_FORMATTED: &str = unsafe { mem::transmute(BASE_TYPE_ARR.as_slice()) }; - - match ERROR.cause { - Cause::TypeMismatch => { - const MSG: &str = const_concat!( - "Field `", - BASE_NAME, - "`: expected type `", - BASE_TYPE_FORMATTED, - "`, found: `", - IMPL_TYPE_FORMATTED, - "`.", - ); - panic!("{}", MSG); - } - Cause::RequiredField => { - const MSG: &str = const_concat!( - "Field `", - BASE_NAME, - "` of type `", - BASE_TYPE_FORMATTED, - "` was expected, but not found." - ); - panic!("{}", MSG); - } - Cause::AdditionalNonNullableField => { - const MSG: &str = const_concat!( - "Field `", - IMPL_NAME, - "` of type `", - IMPL_TYPE_FORMATTED, - "` not present on the interface and so has to be nullable." - ); - panic!("{}", MSG); - } - } -} -pub const fn is_valid_field_args( - base_interface: &[(Name, Type, WrappedValue)], - implementation: &[(Name, Type, WrappedValue)], -) -> bool { - const fn find( - base_interface: &[(Name, Type, WrappedValue)], - impl_name: Name, - impl_ty: Type, - impl_wrap_val: WrappedValue, - ) -> bool { - let mut i = 0; - while i < base_interface.len() { - let (base_name, base_ty, base_wrap_val) = base_interface[i]; - if str_eq(impl_name, base_name) { - return str_eq(base_ty, impl_ty) && impl_wrap_val == base_wrap_val; + const RES: ::std::result::Result<(), Error> = check(); + if RES.is_err() { + const ERROR: Error = unwrap_error(RES); + + const BASE_ARG_NAME: &str = ERROR.base.0; + const IMPL_ARG_NAME: &str = ERROR.implementation.0; + + const BASE_TYPE_FORMATTED: &str = $crate::format_type!(ERROR.base); + const IMPL_TYPE_FORMATTED: &str = $crate::format_type!(ERROR.implementation); + + const PREFIX: &str = $crate::const_concat!( + "Failed to implement interface `", + BASE_NAME, + "` on object `", + IMPL_NAME, + "`: Field `", + FIELD_NAME, + "`: ", + ); + const MSG: &str = match ERROR.cause { + Cause::TypeMismatch => { + $crate::const_concat!( + PREFIX, + "Argument `", + BASE_ARG_NAME, + "`: expected type `", + BASE_TYPE_FORMATTED, + "`, found: `", + IMPL_TYPE_FORMATTED, + "`.", + ) + } + Cause::RequiredField => { + $crate::const_concat!( + PREFIX, + "Argument `", + BASE_ARG_NAME, + "` of type `", + BASE_TYPE_FORMATTED, + "` was expected, but not found." + ) + } + Cause::AdditionalNonNullableField => { + $crate::const_concat!( + PREFIX, + "Argument `", + IMPL_ARG_NAME, + "` of type `", + IMPL_TYPE_FORMATTED, + "` not present on the interface and so has to be nullable." + ) + } + }; + ::std::panic!("{}", MSG); } - i += 1; - } - false - } - - if base_interface.len() > implementation.len() { - return false; - } - - let mut i = 0; - let mut successfully_implemented_fields = 0; - while i < implementation.len() { - let (impl_name, impl_ty, impl_wrap_val) = implementation[i]; - if find(base_interface, impl_name, impl_ty, impl_wrap_val) { - successfully_implemented_fields += 1; - } else if impl_wrap_val % 10 != 2 { - // Not an optional field. - return false; - } - i += 1; - } - - successfully_implemented_fields == base_interface.len() + }; + }; } pub const fn can_be_subtype(ty: WrappedValue, subtype: WrappedValue) -> bool { diff --git a/juniper/src/macros/mod.rs b/juniper/src/macros/mod.rs index cecfee388..591c4c411 100644 --- a/juniper/src/macros/mod.rs +++ b/juniper/src/macros/mod.rs @@ -1,6 +1,7 @@ //! Declarative macros and helper definitions for procedural macros. #[doc(hidden)] +#[macro_use] pub mod helper; #[macro_use] diff --git a/juniper_codegen/src/graphql_interface/new.rs b/juniper_codegen/src/graphql_interface/new.rs index cb929b704..a74ffd838 100644 --- a/juniper_codegen/src/graphql_interface/new.rs +++ b/juniper_codegen/src/graphql_interface/new.rs @@ -770,6 +770,7 @@ impl Definition { /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces fn impl_fields(&self, for_async: bool) -> TokenStream { let ty = &self.enum_alias_ident; + let name = &self.name; let context = &self.context; let scalar = &self.scalar; let const_scalar = match scalar { @@ -795,7 +796,7 @@ impl Definition { return None; } - let name = &field.name; + let field_name = &field.name; let mut return_ty = field.ty.clone(); Self::replace_generics_for_const(&mut return_ty, &generics); @@ -849,7 +850,7 @@ impl Definition { #[allow(non_snake_case)] impl#impl_generics ::juniper::macros::helper::#trait_name< #scalar, - { ::juniper::macros::helper::fnv1a128(#name) } + { ::juniper::macros::helper::fnv1a128(#field_name) } > for #ty#ty_generics #where_clause { type Context = #context; type TypeInfo = (); @@ -877,27 +878,34 @@ impl Definition { <#return_ty as ::juniper::macros::helper::WrappedType>::VALUE, <#impl_tys as ::juniper::macros::helper::#trait_name< #const_scalar, - { ::juniper::macros::helper::fnv1a128(#name) }, + { ::juniper::macros::helper::fnv1a128(#field_name) }, >>::TYPE, <#impl_tys as ::juniper::macros::helper::#trait_name< #const_scalar, - { ::juniper::macros::helper::fnv1a128(#name) }, + { ::juniper::macros::helper::fnv1a128(#field_name) }, >>::WRAPPED_VALUE, )); - const _: () = ::std::assert!(::juniper::macros::helper::is_valid_field_args( - <#ty#const_ty_generics as ::juniper::macros::helper::#trait_name< - #const_scalar, - { ::juniper::macros::helper::fnv1a128(#name) }, - >>::ARGUMENTS, - <#impl_tys as ::juniper::macros::helper::#trait_name< - #const_scalar, - { ::juniper::macros::helper::fnv1a128(#name) }, - >>::ARGUMENTS, - )); + ::juniper::check_field_args!( + #field_name, + ( + #name, + <#ty#const_ty_generics as ::juniper::macros::helper::#trait_name< + #const_scalar, + { ::juniper::macros::helper::fnv1a128(#field_name) }, + >>::ARGUMENTS, + ), + ( + <#impl_tys as ::juniper::macros::helper::BaseType<#const_scalar>>::NAME, + <#impl_tys as ::juniper::macros::helper::#trait_name< + #const_scalar, + { ::juniper::macros::helper::fnv1a128(#field_name) }, + >>::ARGUMENTS, + ), + ); <_ as ::juniper::macros::helper::#trait_name< #scalar, - { ::juniper::macros::helper::fnv1a128(#name) }, + { ::juniper::macros::helper::fnv1a128(#field_name) }, >>::call(v, info, args, executor) })* #unreachable_arm From 7bd8c734d504d498c0b998071027c9ae2f5f5b26 Mon Sep 17 00:00:00 2001 From: ilslv Date: Mon, 3 Jan 2022 10:38:42 +0300 Subject: [PATCH 24/53] WIP (pretty subtype check) --- .../src/codegen/interface_attr.rs | 19 +------ juniper/src/macros/helper/mod.rs | 54 +++++++++++++++---- juniper_codegen/src/graphql_interface/new.rs | 24 +++++++-- 3 files changed, 67 insertions(+), 30 deletions(-) diff --git a/integration_tests/juniper_tests/src/codegen/interface_attr.rs b/integration_tests/juniper_tests/src/codegen/interface_attr.rs index 56b6fa3d2..2ea00b554 100644 --- a/integration_tests/juniper_tests/src/codegen/interface_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/interface_attr.rs @@ -1828,10 +1828,7 @@ mod default_argument { } } - struct Human { - id: String, - info: i32, - } + struct Human; #[graphql_object(impl = CharacterValue)] impl Human { @@ -1849,11 +1846,7 @@ mod default_argument { #[graphql_object] impl QueryRoot { fn character(&self) -> CharacterValue { - Human { - id: "human-32".to_string(), - info: 0, - } - .into() + Human.into() } } @@ -3066,7 +3059,6 @@ mod inferred_custom_context_from_field { } struct Human { - id: String, home_planet: String, } @@ -3086,7 +3078,6 @@ mod inferred_custom_context_from_field { } struct Droid { - id: String, primary_function: String, } @@ -3116,12 +3107,10 @@ mod inferred_custom_context_from_field { fn character(&self) -> CharacterValue { match self { Self::Human => Human { - id: "human-32".to_string(), home_planet: "earth".to_string(), } .into(), Self::Droid => Droid { - id: "droid-99".to_string(), primary_function: "run".to_string(), } .into(), @@ -3231,7 +3220,6 @@ mod executor { } struct Human { - id: String, home_planet: String, } @@ -3251,7 +3239,6 @@ mod executor { } struct Droid { - id: String, primary_function: String, } @@ -3285,12 +3272,10 @@ mod executor { fn character(&self) -> CharacterValue { match self { Self::Human => Human { - id: "human-32".to_string(), home_planet: "earth".to_string(), } .into(), Self::Droid => Droid { - id: "droid-99".to_string(), primary_function: "run".to_string(), } .into(), diff --git a/juniper/src/macros/helper/mod.rs b/juniper/src/macros/helper/mod.rs index 1d54c349f..62d6e1377 100644 --- a/juniper/src/macros/helper/mod.rs +++ b/juniper/src/macros/helper/mod.rs @@ -187,20 +187,19 @@ macro_rules! const_concat { #[macro_export] macro_rules! format_type { - ($ty: expr) => {{ + ($ty: expr, $wrapped_value: expr $(,)?) => {{ const TYPE: ( - $crate::macros::helper::Name, $crate::macros::helper::Type, $crate::macros::helper::WrappedValue, - ) = $ty; - const RES_LEN: usize = $crate::macros::helper::str_len_from_wrapped_val(TYPE.1, TYPE.2); + ) = ($ty, $wrapped_value); + const RES_LEN: usize = $crate::macros::helper::str_len_from_wrapped_val(TYPE.0, TYPE.1); const OPENING_BRACKET: &str = "["; const CLOSING_BRACKET: &str = "]"; const BANG: &str = "!"; const fn format_type_arr() -> [u8; RES_LEN] { - let (_, ty, wrap_val) = TYPE; + let (ty, wrap_val) = TYPE; let mut type_arr: [u8; RES_LEN] = [0; RES_LEN]; let mut current_start = 0; @@ -272,8 +271,7 @@ macro_rules! format_type { } const TYPE_ARR: [u8; RES_LEN] = format_type_arr(); - // SAFETY: This is safe, as `TYPE_ARR` was formatted from `TYPE.Name`, - // `[`, `]` and `!`. + // SAFETY: This is safe, as `TYPE_ARR` was formatted from `Type`, `[`, `]` and `!`. const TYPE_FORMATTED: &str = unsafe { ::std::mem::transmute(TYPE_ARR.as_slice()) }; TYPE_FORMATTED }}; @@ -403,13 +401,14 @@ macro_rules! check_field_args { const BASE_ARG_NAME: &str = ERROR.base.0; const IMPL_ARG_NAME: &str = ERROR.implementation.0; - const BASE_TYPE_FORMATTED: &str = $crate::format_type!(ERROR.base); - const IMPL_TYPE_FORMATTED: &str = $crate::format_type!(ERROR.implementation); + const BASE_TYPE_FORMATTED: &str = $crate::format_type!(ERROR.base.1, ERROR.base.2,); + const IMPL_TYPE_FORMATTED: &str = + $crate::format_type!(ERROR.implementation.1, ERROR.implementation.2,); const PREFIX: &str = $crate::const_concat!( "Failed to implement interface `", BASE_NAME, - "` on object `", + "` on `", IMPL_NAME, "`: Field `", FIELD_NAME, @@ -492,6 +491,41 @@ pub const fn is_subtype( exists(subtype, possible_subtypes) && can_be_subtype(wrapped_type, wrapped_subtype) } +#[macro_export] +macro_rules! check_subtype { + ( + $field_name: expr, + $base_name: expr, + $base_ty: expr, + $base_wrapped_val: expr, + $possible_subtypes: expr, + $impl_name: expr, + $impl_ty: expr, + $impl_wrapped_val: expr $(,)? + ) => { + const _: () = { + let is_subtype = $crate::macros::helper::exists($impl_ty, $possible_subtypes) + && $crate::macros::helper::can_be_subtype($base_wrapped_val, $impl_wrapped_val); + if !is_subtype { + const MSG: &str = $crate::const_concat!( + "Failed to implement interface `", + $base_name, + "` on `", + $impl_name, + "`: Field `", + $field_name, + "` return object is `", + $crate::format_type!($base_ty, $base_wrapped_val), + "`, which is not a subtype of `", + $crate::format_type!($impl_ty, $impl_wrapped_val), + "`.", + ); + ::std::panic!("{}", MSG); + } + }; + }; +} + /// TODO pub trait BaseType { const NAME: Type; diff --git a/juniper_codegen/src/graphql_interface/new.rs b/juniper_codegen/src/graphql_interface/new.rs index a74ffd838..8e6c2290e 100644 --- a/juniper_codegen/src/graphql_interface/new.rs +++ b/juniper_codegen/src/graphql_interface/new.rs @@ -873,9 +873,27 @@ impl Definition { #call_sig { match self { #(#ty::#impl_idents(v) => { - const _: () = ::std::assert!(::juniper::macros::helper::is_subtype( + // const _: () = ::std::assert!(::juniper::macros::helper::is_subtype( + // <#return_ty as ::juniper::macros::helper::BaseSubTypes>::NAMES, + // <#return_ty as ::juniper::macros::helper::WrappedType>::VALUE, + // <#impl_tys as ::juniper::macros::helper::#trait_name< + // #const_scalar, + // { ::juniper::macros::helper::fnv1a128(#field_name) }, + // >>::TYPE, + // <#impl_tys as ::juniper::macros::helper::#trait_name< + // #const_scalar, + // { ::juniper::macros::helper::fnv1a128(#field_name) }, + // >>::WRAPPED_VALUE, + // )); + ::juniper::check_subtype!( + #field_name, + + #name, + <#return_ty as ::juniper::macros::helper::BaseType<#const_scalar>>::NAME, + <#return_ty as ::juniper::macros::helper::WrappedType<#const_scalar>>::VALUE, <#return_ty as ::juniper::macros::helper::BaseSubTypes>::NAMES, - <#return_ty as ::juniper::macros::helper::WrappedType>::VALUE, + + <#impl_tys as ::juniper::macros::helper::BaseType<#const_scalar>>::NAME, <#impl_tys as ::juniper::macros::helper::#trait_name< #const_scalar, { ::juniper::macros::helper::fnv1a128(#field_name) }, @@ -884,7 +902,7 @@ impl Definition { #const_scalar, { ::juniper::macros::helper::fnv1a128(#field_name) }, >>::WRAPPED_VALUE, - )); + ); ::juniper::check_field_args!( #field_name, ( From 3ebc8fd332f93cddbff9a4a890408afed1f2eaf4 Mon Sep 17 00:00:00 2001 From: ilslv Date: Mon, 3 Jan 2022 12:29:04 +0300 Subject: [PATCH 25/53] WIP (corrections) --- juniper/src/macros/helper/mod.rs | 334 ++++++++++--------- juniper_codegen/src/graphql_interface/new.rs | 222 +++++------- 2 files changed, 269 insertions(+), 287 deletions(-) diff --git a/juniper/src/macros/helper/mod.rs b/juniper/src/macros/helper/mod.rs index 62d6e1377..2e01f8e66 100644 --- a/juniper/src/macros/helper/mod.rs +++ b/juniper/src/macros/helper/mod.rs @@ -93,74 +93,6 @@ pub type Name = &'static str; pub type FieldName = u128; pub type WrappedValue = u128; -/// Non-cryptographic hash with good dispersion to use [`str`](prim@str) in -/// const generics. See [spec] for more info. -/// -/// [spec]: https://datatracker.ietf.org/doc/html/draft-eastlake-fnv-17.html -pub const fn fnv1a128(str: Name) -> u128 { - const FNV_OFFSET_BASIS: u128 = 0x6c62272e07bb014262b821756295c58d; - const FNV_PRIME: u128 = 0x0000000001000000000000000000013b; - - let bytes = str.as_bytes(); - let mut hash = FNV_OFFSET_BASIS; - let mut i = 0; - while i < bytes.len() { - hash ^= bytes[i] as u128; - hash = hash.wrapping_mul(FNV_PRIME); - i += 1; - } - hash -} - -pub const fn str_eq(l: &str, r: &str) -> bool { - let (l, r) = (l.as_bytes(), r.as_bytes()); - - if l.len() != r.len() { - return false; - } - - let mut i = 0; - while i < l.len() { - if l[i] != r[i] { - return false; - } - i += 1; - } - - true -} - -pub const fn number_of_digits(n: u128) -> usize { - if n == 0 { - return 1; - } - - let mut len = 0; - let mut current = n; - while current % 10 != 0 { - len += 1; - current /= 10; - } - len -} - -pub const fn str_len_from_wrapped_val(ty: Type, v: WrappedValue) -> usize { - let mut len = ty.as_bytes().len() + "!".as_bytes().len(); // Type! - - let mut current_wrap_val = v; - while current_wrap_val % 10 != 0 { - match current_wrap_val % 10 { - 2 => len -= "!".as_bytes().len(), // remove ! - 3 => len += "[]!".as_bytes().len(), // [Type]! - _ => {} - } - - current_wrap_val /= 10; - } - - len -} - #[macro_export] macro_rules! const_concat { ($($s:expr),* $(,)?) => {{ @@ -278,16 +210,92 @@ macro_rules! format_type { } #[macro_export] -macro_rules! check_field_args { +macro_rules! assert_field { + ( + $base_ty: ty, + $impl_ty: ty, + $scalar: ty, + $field_name: expr $(,)? + ) => { + $crate::assert_field_args!($base_ty, $impl_ty, $scalar, $field_name); + $crate::assert_subtype!($base_ty, $impl_ty, $scalar, $field_name); + }; +} + +#[macro_export] +macro_rules! assert_subtype { ( - $field_name: expr, - ( - $base_name: expr, - $base_args: expr $(,)? - ), ( - $impl_name: expr, - $impl_args: expr $(,)? - ) $(,)?) => { + $base_ty: ty, + $impl_ty: ty, + $scalar: ty, + $field_name: expr $(,)? + ) => { + const _: () = { + const FIELD_NAME: $crate::macros::helper::Name = + $field_name; + const BASE_RETURN_WRAPPED_VAL: $crate::macros::helper::WrappedValue = + <$base_ty as $crate::macros::helper::AsyncField< + $scalar, + { $crate::macros::helper::fnv1a128(FIELD_NAME) }, + >>::WRAPPED_VALUE; + const IMPL_RETURN_WRAPPED_VAL: $crate::macros::helper::WrappedValue = + <$impl_ty as $crate::macros::helper::AsyncField< + $scalar, + { $crate::macros::helper::fnv1a128(FIELD_NAME) }, + >>::WRAPPED_VALUE; + + const BASE_TY: $crate::macros::helper::Type = + <$base_ty as $crate::macros::helper::BaseType<$scalar>>::NAME; + const IMPL_TY: $crate::macros::helper::Type = + <$impl_ty as $crate::macros::helper::BaseType<$scalar>>::NAME; + + const BASE_RETURN_SUB_TYPES: $crate::macros::helper::Types = + <$base_ty as $crate::macros::helper::AsyncField< + $scalar, + { $crate::macros::helper::fnv1a128(FIELD_NAME) }, + >>::SUB_TYPES; + + const BASE_RETURN_TY: $crate::macros::helper::Type = + <$base_ty as $crate::macros::helper::AsyncField< + $scalar, + { $crate::macros::helper::fnv1a128(FIELD_NAME) }, + >>::TYPE; + const IMPL_RETURN_TY: $crate::macros::helper::Type = + <$impl_ty as $crate::macros::helper::AsyncField< + $scalar, + { $crate::macros::helper::fnv1a128(FIELD_NAME) }, + >>::TYPE; + + let is_subtype = $crate::macros::helper::exists(IMPL_RETURN_TY, BASE_RETURN_SUB_TYPES) + && $crate::macros::helper::can_be_subtype(BASE_RETURN_WRAPPED_VAL, IMPL_RETURN_WRAPPED_VAL); + if !is_subtype { + const MSG: &str = $crate::const_concat!( + "Failed to implement interface `", + BASE_TY, + "` on `", + IMPL_TY, + "`: Field `", + FIELD_NAME, + "`: implementor is expected to return a subtype of interface's return object: `", + $crate::format_type!(IMPL_RETURN_TY, IMPL_RETURN_WRAPPED_VAL), + "` is not a subtype of `", + $crate::format_type!(BASE_RETURN_TY, BASE_RETURN_WRAPPED_VAL), + "`.", + ); + ::std::panic!("{}", MSG); + } + }; + }; +} + +#[macro_export] +macro_rules! assert_field_args { + ( + $base_ty: ty, + $impl_ty: ty, + $scalar: ty, + $field_name: expr $(,)? + ) => { const _: () = { type FullArg = ( $crate::macros::helper::Name, @@ -296,10 +304,16 @@ macro_rules! check_field_args { ); const FIELD_NAME: &str = $field_name; - const BASE_NAME: &str = $base_name; - const IMPL_NAME: &str = $impl_name; - const BASE_ARGS: &[FullArg] = $base_args; - const IMPL_ARGS: &[FullArg] = $impl_args; + const BASE_NAME: &str = <$base_ty as $crate::macros::helper::BaseType<$scalar>>::NAME; + const IMPL_NAME: &str = <$impl_ty as $crate::macros::helper::BaseType<$scalar>>::NAME; + const BASE_ARGS: &[FullArg] = <$base_ty as $crate::macros::helper::AsyncField< + $scalar, + { $crate::macros::helper::fnv1a128(FIELD_NAME) }, + >>::ARGUMENTS; + const IMPL_ARGS: &[FullArg] = <$impl_ty as $crate::macros::helper::AsyncField< + $scalar, + { $crate::macros::helper::fnv1a128(FIELD_NAME) }, + >>::ARGUMENTS; struct Error { cause: Cause, @@ -401,9 +415,9 @@ macro_rules! check_field_args { const BASE_ARG_NAME: &str = ERROR.base.0; const IMPL_ARG_NAME: &str = ERROR.implementation.0; - const BASE_TYPE_FORMATTED: &str = $crate::format_type!(ERROR.base.1, ERROR.base.2,); + const BASE_TYPE_FORMATTED: &str = $crate::format_type!(ERROR.base.1, ERROR.base.2); const IMPL_TYPE_FORMATTED: &str = - $crate::format_type!(ERROR.implementation.1, ERROR.implementation.2,); + $crate::format_type!(ERROR.implementation.1, ERROR.implementation.2); const PREFIX: &str = $crate::const_concat!( "Failed to implement interface `", @@ -454,78 +468,6 @@ macro_rules! check_field_args { }; } -pub const fn can_be_subtype(ty: WrappedValue, subtype: WrappedValue) -> bool { - let ty_current = ty % 10; - let subtype_current = subtype % 10; - - if ty_current == subtype_current { - if ty_current == 1 { - true - } else { - can_be_subtype(ty / 10, subtype / 10) - } - } else if ty_current == 2 { - can_be_subtype(ty / 10, subtype) - } else { - false - } -} - -pub const fn exists(val: Type, arr: Types) -> bool { - let mut i = 0; - while i < arr.len() { - if str_eq(val, arr[i]) { - return true; - } - i += 1; - } - false -} - -pub const fn is_subtype( - possible_subtypes: Types, - wrapped_type: WrappedValue, - subtype: Type, - wrapped_subtype: WrappedValue, -) -> bool { - exists(subtype, possible_subtypes) && can_be_subtype(wrapped_type, wrapped_subtype) -} - -#[macro_export] -macro_rules! check_subtype { - ( - $field_name: expr, - $base_name: expr, - $base_ty: expr, - $base_wrapped_val: expr, - $possible_subtypes: expr, - $impl_name: expr, - $impl_ty: expr, - $impl_wrapped_val: expr $(,)? - ) => { - const _: () = { - let is_subtype = $crate::macros::helper::exists($impl_ty, $possible_subtypes) - && $crate::macros::helper::can_be_subtype($base_wrapped_val, $impl_wrapped_val); - if !is_subtype { - const MSG: &str = $crate::const_concat!( - "Failed to implement interface `", - $base_name, - "` on `", - $impl_name, - "`: Field `", - $field_name, - "` return object is `", - $crate::format_type!($base_ty, $base_wrapped_val), - "`, which is not a subtype of `", - $crate::format_type!($impl_ty, $impl_wrapped_val), - "`.", - ); - ::std::panic!("{}", MSG); - } - }; - }; -} - /// TODO pub trait BaseType { const NAME: Type; @@ -719,3 +661,85 @@ pub trait AsyncField { executor: &'b Executor, ) -> BoxFuture<'b, ExecutionResult>; } + +/// Non-cryptographic hash with good dispersion to use [`str`](prim@str) in +/// const generics. See [spec] for more info. +/// +/// [spec]: https://datatracker.ietf.org/doc/html/draft-eastlake-fnv-17.html +pub const fn fnv1a128(str: Name) -> u128 { + const FNV_OFFSET_BASIS: u128 = 0x6c62272e07bb014262b821756295c58d; + const FNV_PRIME: u128 = 0x0000000001000000000000000000013b; + + let bytes = str.as_bytes(); + let mut hash = FNV_OFFSET_BASIS; + let mut i = 0; + while i < bytes.len() { + hash ^= bytes[i] as u128; + hash = hash.wrapping_mul(FNV_PRIME); + i += 1; + } + hash +} + +pub const fn str_eq(l: &str, r: &str) -> bool { + let (l, r) = (l.as_bytes(), r.as_bytes()); + + if l.len() != r.len() { + return false; + } + + let mut i = 0; + while i < l.len() { + if l[i] != r[i] { + return false; + } + i += 1; + } + + true +} + +pub const fn str_len_from_wrapped_val(ty: Type, v: WrappedValue) -> usize { + let mut len = ty.as_bytes().len() + "!".as_bytes().len(); // Type! + + let mut current_wrap_val = v; + while current_wrap_val % 10 != 0 { + match current_wrap_val % 10 { + 2 => len -= "!".as_bytes().len(), // remove ! + 3 => len += "[]!".as_bytes().len(), // [Type]! + _ => {} + } + + current_wrap_val /= 10; + } + + len +} + +pub const fn can_be_subtype(ty: WrappedValue, subtype: WrappedValue) -> bool { + let ty_current = ty % 10; + let subtype_current = subtype % 10; + + if ty_current == subtype_current { + if ty_current == 1 { + true + } else { + can_be_subtype(ty / 10, subtype / 10) + } + } else if ty_current == 2 { + can_be_subtype(ty / 10, subtype) + } else { + false + } +} + +pub const fn exists(val: Type, arr: Types) -> bool { + let mut i = 0; + while i < arr.len() { + if str_eq(val, arr[i]) { + return true; + } + i += 1; + } + false +} diff --git a/juniper_codegen/src/graphql_interface/new.rs b/juniper_codegen/src/graphql_interface/new.rs index 8e6c2290e..64a8ee22e 100644 --- a/juniper_codegen/src/graphql_interface/new.rs +++ b/juniper_codegen/src/graphql_interface/new.rs @@ -770,7 +770,6 @@ impl Definition { /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces fn impl_fields(&self, for_async: bool) -> TokenStream { let ty = &self.enum_alias_ident; - let name = &self.name; let context = &self.context; let scalar = &self.scalar; let const_scalar = match scalar { @@ -791,148 +790,107 @@ impl Definition { let (impl_generics, _, where_clause) = generics.split_for_impl(); let (_, ty_generics, _) = self.trait_generics.split_for_impl(); - self.fields.iter().filter_map(|field| { - if field.is_async && !for_async { - return None; - } + self.fields + .iter() + .filter_map(|field| { + if field.is_async && !for_async { + return None; + } - let field_name = &field.name; - let mut return_ty = field.ty.clone(); - Self::replace_generics_for_const(&mut return_ty, &generics); - - let (trait_name, call_sig) = if for_async { - ( - quote! { AsyncField }, - quote! { - fn call<'b>( - &'b self, - info: &'b Self::TypeInfo, - args: &'b ::juniper::Arguments<#scalar>, - executor: &'b ::juniper::Executor, - ) -> ::juniper::BoxFuture<'b, ::juniper::ExecutionResult<#scalar>> - }, - ) - } else { - ( - quote! { Field }, - quote! { - fn call( - &self, - info: &Self::TypeInfo, - args: &::juniper::Arguments<#scalar>, - executor: &::juniper::Executor, - ) -> ::juniper::ExecutionResult<#scalar> - }, - ) - }; + let field_name = &field.name; + let mut return_ty = field.ty.clone(); + Self::replace_generics_for_const(&mut return_ty, &generics); + + let (trait_name, call_sig) = if for_async { + ( + quote! { AsyncField }, + quote! { + fn call<'b>( + &'b self, + info: &'b Self::TypeInfo, + args: &'b ::juniper::Arguments<#scalar>, + executor: &'b ::juniper::Executor, + ) -> ::juniper::BoxFuture<'b, ::juniper::ExecutionResult<#scalar>> + }, + ) + } else { + ( + quote! { Field }, + quote! { + fn call( + &self, + info: &Self::TypeInfo, + args: &::juniper::Arguments<#scalar>, + executor: &::juniper::Executor, + ) -> ::juniper::ExecutionResult<#scalar> + }, + ) + }; - let (args_tys, args_names): (Vec<_>, Vec<_>) = field - .arguments - .iter() - .flat_map(|vec| vec.iter()) - .filter_map(|arg| { - match arg { - field::MethodArgument::Regular(arg) => { - Some((&arg.ty, &arg.name)) - } + let (args_tys, args_names): (Vec<_>, Vec<_>) = field + .arguments + .iter() + .flat_map(|vec| vec.iter()) + .filter_map(|arg| match arg { + field::MethodArgument::Regular(arg) => Some((&arg.ty, &arg.name)), _ => None, - } - }) - .unzip(); + }) + .unzip(); - let const_ty_generics = self.const_trait_generics(); + let const_ty_generics = self.const_trait_generics(); - let unreachable_arm = (self.implementers.is_empty() || !self.trait_generics.params.is_empty()).then(|| { - quote! { _ => unreachable!() } - }); + let unreachable_arm = (self.implementers.is_empty() + || !self.trait_generics.params.is_empty()) + .then(|| { + quote! { _ => unreachable!() } + }); - Some(quote! { - #[allow(non_snake_case)] - impl#impl_generics ::juniper::macros::helper::#trait_name< - #scalar, - { ::juniper::macros::helper::fnv1a128(#field_name) } - > for #ty#ty_generics #where_clause { - type Context = #context; - type TypeInfo = (); - const TYPE: ::juniper::macros::helper::Type = - <#return_ty as ::juniper::macros::helper::BaseType<#scalar>>::NAME; - const SUB_TYPES: ::juniper::macros::helper::Types = - <#return_ty as ::juniper::macros::helper::BaseSubTypes<#scalar>>::NAMES; - const WRAPPED_VALUE: ::juniper::macros::helper::WrappedValue = - <#return_ty as ::juniper::macros::helper::WrappedType<#scalar>>::VALUE; - const ARGUMENTS: &'static [( - ::juniper::macros::helper::Name, - ::juniper::macros::helper::Type, - ::juniper::macros::helper::WrappedValue, - )] = &[#(( - #args_names, - <#args_tys as ::juniper::macros::helper::BaseType<#scalar>>::NAME, - <#args_tys as ::juniper::macros::helper::WrappedType<#scalar>>::VALUE, - )),*]; - - #call_sig { - match self { - #(#ty::#impl_idents(v) => { - // const _: () = ::std::assert!(::juniper::macros::helper::is_subtype( - // <#return_ty as ::juniper::macros::helper::BaseSubTypes>::NAMES, - // <#return_ty as ::juniper::macros::helper::WrappedType>::VALUE, - // <#impl_tys as ::juniper::macros::helper::#trait_name< - // #const_scalar, - // { ::juniper::macros::helper::fnv1a128(#field_name) }, - // >>::TYPE, - // <#impl_tys as ::juniper::macros::helper::#trait_name< - // #const_scalar, - // { ::juniper::macros::helper::fnv1a128(#field_name) }, - // >>::WRAPPED_VALUE, - // )); - ::juniper::check_subtype!( - #field_name, - - #name, - <#return_ty as ::juniper::macros::helper::BaseType<#const_scalar>>::NAME, - <#return_ty as ::juniper::macros::helper::WrappedType<#const_scalar>>::VALUE, - <#return_ty as ::juniper::macros::helper::BaseSubTypes>::NAMES, - - <#impl_tys as ::juniper::macros::helper::BaseType<#const_scalar>>::NAME, - <#impl_tys as ::juniper::macros::helper::#trait_name< - #const_scalar, - { ::juniper::macros::helper::fnv1a128(#field_name) }, - >>::TYPE, - <#impl_tys as ::juniper::macros::helper::#trait_name< + Some(quote! { + #[allow(non_snake_case)] + impl#impl_generics ::juniper::macros::helper::#trait_name< + #scalar, + { ::juniper::macros::helper::fnv1a128(#field_name) } + > for #ty#ty_generics #where_clause { + type Context = #context; + type TypeInfo = (); + const TYPE: ::juniper::macros::helper::Type = + <#return_ty as ::juniper::macros::helper::BaseType<#scalar>>::NAME; + const SUB_TYPES: ::juniper::macros::helper::Types = + <#return_ty as ::juniper::macros::helper::BaseSubTypes<#scalar>>::NAMES; + const WRAPPED_VALUE: ::juniper::macros::helper::WrappedValue = + <#return_ty as ::juniper::macros::helper::WrappedType<#scalar>>::VALUE; + const ARGUMENTS: &'static [( + ::juniper::macros::helper::Name, + ::juniper::macros::helper::Type, + ::juniper::macros::helper::WrappedValue, + )] = &[#(( + #args_names, + <#args_tys as ::juniper::macros::helper::BaseType<#scalar>>::NAME, + <#args_tys as ::juniper::macros::helper::WrappedType<#scalar>>::VALUE, + )),*]; + + #call_sig { + match self { + #(#ty::#impl_idents(v) => { + ::juniper::assert_field!( + #ty#const_ty_generics, + #impl_tys, #const_scalar, + #field_name, + ); + + <_ as ::juniper::macros::helper::#trait_name< + #scalar, { ::juniper::macros::helper::fnv1a128(#field_name) }, - >>::WRAPPED_VALUE, - ); - ::juniper::check_field_args!( - #field_name, - ( - #name, - <#ty#const_ty_generics as ::juniper::macros::helper::#trait_name< - #const_scalar, - { ::juniper::macros::helper::fnv1a128(#field_name) }, - >>::ARGUMENTS, - ), - ( - <#impl_tys as ::juniper::macros::helper::BaseType<#const_scalar>>::NAME, - <#impl_tys as ::juniper::macros::helper::#trait_name< - #const_scalar, - { ::juniper::macros::helper::fnv1a128(#field_name) }, - >>::ARGUMENTS, - ), - ); - - <_ as ::juniper::macros::helper::#trait_name< - #scalar, - { ::juniper::macros::helper::fnv1a128(#field_name) }, - >>::call(v, info, args, executor) - })* - #unreachable_arm + >>::call(v, info, args, executor) + })* + #unreachable_arm + } } } - } + }) }) - }) - .collect() + .collect() } /// Returns generated code for the [`GraphQLValue::concrete_type_name`][0] From ce249b03e82488ca29ed1324802199d31b8de9ff Mon Sep 17 00:00:00 2001 From: ilslv Date: Mon, 3 Jan 2022 13:55:24 +0300 Subject: [PATCH 26/53] WIP (FieldMeta trait) --- .../juniper_tests/src/codegen/object_attr.rs | 2 +- juniper/src/macros/helper/mod.rs | 27 +++--- juniper_codegen/src/common/field/mod.rs | 94 +++++++++++------- juniper_codegen/src/graphql_interface/new.rs | 95 +++++++++++++------ juniper_codegen/src/graphql_object/mod.rs | 9 +- 5 files changed, 140 insertions(+), 87 deletions(-) diff --git a/integration_tests/juniper_tests/src/codegen/object_attr.rs b/integration_tests/juniper_tests/src/codegen/object_attr.rs index 304ba34c5..76a1c8542 100644 --- a/integration_tests/juniper_tests/src/codegen/object_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/object_attr.rs @@ -2022,7 +2022,7 @@ mod switched_context { async fn switch_res_opt<'e, S: ScalarValue>( &self, - executor: &'e Executor<'_, '_, CustomContext, S>, + executor: &'e Executor<'e, 'e, CustomContext, S>, ) -> FieldResult> { Ok(Some((executor.context(), Droid { id: 3 }))) } diff --git a/juniper/src/macros/helper/mod.rs b/juniper/src/macros/helper/mod.rs index 2e01f8e66..b65c69680 100644 --- a/juniper/src/macros/helper/mod.rs +++ b/juniper/src/macros/helper/mod.rs @@ -234,12 +234,12 @@ macro_rules! assert_subtype { const FIELD_NAME: $crate::macros::helper::Name = $field_name; const BASE_RETURN_WRAPPED_VAL: $crate::macros::helper::WrappedValue = - <$base_ty as $crate::macros::helper::AsyncField< + <$base_ty as $crate::macros::helper::FieldMeta< $scalar, { $crate::macros::helper::fnv1a128(FIELD_NAME) }, >>::WRAPPED_VALUE; const IMPL_RETURN_WRAPPED_VAL: $crate::macros::helper::WrappedValue = - <$impl_ty as $crate::macros::helper::AsyncField< + <$impl_ty as $crate::macros::helper::FieldMeta< $scalar, { $crate::macros::helper::fnv1a128(FIELD_NAME) }, >>::WRAPPED_VALUE; @@ -250,18 +250,18 @@ macro_rules! assert_subtype { <$impl_ty as $crate::macros::helper::BaseType<$scalar>>::NAME; const BASE_RETURN_SUB_TYPES: $crate::macros::helper::Types = - <$base_ty as $crate::macros::helper::AsyncField< + <$base_ty as $crate::macros::helper::FieldMeta< $scalar, { $crate::macros::helper::fnv1a128(FIELD_NAME) }, >>::SUB_TYPES; const BASE_RETURN_TY: $crate::macros::helper::Type = - <$base_ty as $crate::macros::helper::AsyncField< + <$base_ty as $crate::macros::helper::FieldMeta< $scalar, { $crate::macros::helper::fnv1a128(FIELD_NAME) }, >>::TYPE; const IMPL_RETURN_TY: $crate::macros::helper::Type = - <$impl_ty as $crate::macros::helper::AsyncField< + <$impl_ty as $crate::macros::helper::FieldMeta< $scalar, { $crate::macros::helper::fnv1a128(FIELD_NAME) }, >>::TYPE; @@ -306,11 +306,11 @@ macro_rules! assert_field_args { const FIELD_NAME: &str = $field_name; const BASE_NAME: &str = <$base_ty as $crate::macros::helper::BaseType<$scalar>>::NAME; const IMPL_NAME: &str = <$impl_ty as $crate::macros::helper::BaseType<$scalar>>::NAME; - const BASE_ARGS: &[FullArg] = <$base_ty as $crate::macros::helper::AsyncField< + const BASE_ARGS: &[FullArg] = <$base_ty as $crate::macros::helper::FieldMeta< $scalar, { $crate::macros::helper::fnv1a128(FIELD_NAME) }, >>::ARGUMENTS; - const IMPL_ARGS: &[FullArg] = <$impl_ty as $crate::macros::helper::AsyncField< + const IMPL_ARGS: &[FullArg] = <$impl_ty as $crate::macros::helper::FieldMeta< $scalar, { $crate::macros::helper::fnv1a128(FIELD_NAME) }, >>::ARGUMENTS; @@ -630,14 +630,16 @@ impl + ?Sized> WrappedType for Rc { const VALUE: u128 = T::VALUE; } -pub trait Field { +pub trait FieldMeta { type Context; type TypeInfo; const TYPE: Type; const SUB_TYPES: Types; const WRAPPED_VALUE: WrappedValue; const ARGUMENTS: &'static [(Name, Type, WrappedValue)]; +} +pub trait Field: FieldMeta { fn call( &self, info: &Self::TypeInfo, @@ -646,14 +648,7 @@ pub trait Field { ) -> ExecutionResult; } -pub trait AsyncField { - type Context; - type TypeInfo; - const TYPE: Type; - const SUB_TYPES: Types; - const WRAPPED_VALUE: WrappedValue; - const ARGUMENTS: &'static [(Name, Type, WrappedValue)]; - +pub trait AsyncField: FieldMeta { fn call<'b>( &'b self, info: &'b Self::TypeInfo, diff --git a/juniper_codegen/src/common/field/mod.rs b/juniper_codegen/src/common/field/mod.rs index 6dba69e32..385ed22d4 100644 --- a/juniper_codegen/src/common/field/mod.rs +++ b/juniper_codegen/src/common/field/mod.rs @@ -441,6 +441,63 @@ impl Definition { }) } + /// TODO + #[must_use] + pub(crate) fn impl_field_meta( + &self, + impl_ty: &syn::Type, + impl_generics: &TokenStream, + where_clause: Option<&syn::WhereClause>, + scalar: &scalar::Type, + context: &syn::Type, + ) -> TokenStream { + let (name, ty) = (&self.name, self.ty.clone()); + + let arguments = self + .arguments + .as_ref() + .iter() + .flat_map(|vec| vec.iter()) + .filter_map(|arg| match arg { + MethodArgument::Regular(arg) => { + let (name, ty) = (&arg.name, &arg.ty); + + Some(quote! {( + #name, + <#ty as ::juniper::macros::helper::BaseType<#scalar>>::NAME, + <#ty as ::juniper::macros::helper::WrappedType<#scalar>>::VALUE, + )}) + } + MethodArgument::Executor | MethodArgument::Context(_) => None, + }) + .collect::>(); + + quote! { + #[allow(deprecated, non_snake_case)] + #[automatically_derived] + impl #impl_generics ::juniper::macros::helper::FieldMeta< + #scalar, + { ::juniper::macros::helper::fnv1a128(#name) } + > for #impl_ty + #where_clause + { + type Context = #context; + type TypeInfo = (); + const TYPE: ::juniper::macros::helper::Type = + <#ty as ::juniper::macros::helper::BaseType<#scalar>>::NAME; + const SUB_TYPES: ::juniper::macros::helper::Types = + <#ty as ::juniper::macros::helper::BaseSubTypes<#scalar>>::NAMES; + const WRAPPED_VALUE: juniper::macros::helper::WrappedValue = + <#ty as ::juniper::macros::helper::WrappedType<#scalar>>::VALUE; + const ARGUMENTS: &'static [( + ::juniper::macros::helper::Name, + ::juniper::macros::helper::Type, + ::juniper::macros::helper::WrappedValue, + )] = &[#(#arguments,)*]; + } + } + } + /// TODO #[must_use] pub(crate) fn impl_field( @@ -450,15 +507,13 @@ impl Definition { where_clause: Option<&syn::WhereClause>, scalar: &scalar::Type, trait_ty: Option<&syn::Type>, - context: &syn::Type, for_async: bool, ) -> Option { if !for_async && self.is_async { return None; } - let (name, ty, mut res_ty, ident) = - (&self.name, self.ty.clone(), self.ty.clone(), &self.ident); + let (name, mut res_ty, ident) = (&self.name, self.ty.clone(), &self.ident); let mut res = if self.is_method() { let args = self @@ -485,25 +540,6 @@ impl Definition { res = quote! { ::juniper::futures::future::ready(#res) }; } - let arguments = self - .arguments - .as_ref() - .iter() - .flat_map(|vec| vec.iter()) - .filter_map(|arg| match arg { - MethodArgument::Regular(arg) => { - let (name, ty) = (&arg.name, &arg.ty); - - Some(quote! {( - #name, - <#ty as ::juniper::macros::helper::BaseType<#scalar>>::NAME, - <#ty as ::juniper::macros::helper::WrappedType<#scalar>>::VALUE, - )}) - } - MethodArgument::Executor | MethodArgument::Context(_) => None, - }) - .collect::>(); - let (call, trait_name) = if for_async { let resolving_code = gen::async_resolving_code(Some(&res_ty)); let call = quote! { @@ -545,20 +581,6 @@ impl Definition { > for #impl_ty #where_clause { - type Context = #context; - type TypeInfo = (); - const TYPE: ::juniper::macros::helper::Type = - <#ty as ::juniper::macros::helper::BaseType<#scalar>>::NAME; - const SUB_TYPES: ::juniper::macros::helper::Types = - <#ty as ::juniper::macros::helper::BaseSubTypes<#scalar>>::NAMES; - const WRAPPED_VALUE: juniper::macros::helper::WrappedValue = - <#ty as ::juniper::macros::helper::WrappedType<#scalar>>::VALUE; - const ARGUMENTS: &'static [( - ::juniper::macros::helper::Name, - ::juniper::macros::helper::Type, - ::juniper::macros::helper::WrappedValue, - )] = &[#(#arguments,)*]; - #call } }) diff --git a/juniper_codegen/src/graphql_interface/new.rs b/juniper_codegen/src/graphql_interface/new.rs index 64a8ee22e..f92e8729e 100644 --- a/juniper_codegen/src/graphql_interface/new.rs +++ b/juniper_codegen/src/graphql_interface/new.rs @@ -50,8 +50,9 @@ pub(crate) struct TraitAttr { /// [2]: https://spec.graphql.org/June2018/#sec-Descriptions pub(crate) description: Option>, - /// Explicitly specified identifier of the enum Rust type behind the trait, - /// being an actual implementation of a [GraphQL interface][1] type. + /// Explicitly specified identifier of the type alias of Rust enum type + /// behind the trait, being an actual implementation of a + /// [GraphQL interface][1] type. /// /// If [`None`], then `{trait_name}Value` identifier will be used. /// @@ -305,6 +306,7 @@ impl ToTokens for Definition { self.impl_graphql_value_tokens().to_tokens(into); self.impl_graphql_value_async_tokens().to_tokens(into); self.impl_traits_for_reflection().to_tokens(into); + self.impl_fields_meta().to_tokens(into); self.impl_fields(false).to_tokens(into); self.impl_fields(true).to_tokens(into); } @@ -762,6 +764,66 @@ impl Definition { } } + /// Returns generated code implementing [`FieldMeta`] for this + /// [GraphQL interface][1]. + /// + /// [`FieldMeta`]: juniper::macros::helper::FieldMeta + /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces + fn impl_fields_meta(&self) -> TokenStream { + let ty = &self.enum_alias_ident; + let context = &self.context; + let scalar = &self.scalar; + + let generics = self.impl_generics(false); + let (impl_generics, _, where_clause) = generics.split_for_impl(); + let (_, ty_generics, _) = self.trait_generics.split_for_impl(); + + self.fields + .iter() + .map(|field| { + let field_name = &field.name; + let mut return_ty = field.ty.clone(); + Self::replace_generics_for_const(&mut return_ty, &generics); + + let (args_tys, args_names): (Vec<_>, Vec<_>) = field + .arguments + .iter() + .flat_map(|vec| vec.iter()) + .filter_map(|arg| match arg { + field::MethodArgument::Regular(arg) => Some((&arg.ty, &arg.name)), + _ => None, + }) + .unzip(); + + quote! { + #[allow(non_snake_case)] + impl#impl_generics ::juniper::macros::helper::FieldMeta< + #scalar, + { ::juniper::macros::helper::fnv1a128(#field_name) } + > for #ty#ty_generics #where_clause { + type Context = #context; + type TypeInfo = (); + const TYPE: ::juniper::macros::helper::Type = + <#return_ty as ::juniper::macros::helper::BaseType<#scalar>>::NAME; + const SUB_TYPES: ::juniper::macros::helper::Types = + <#return_ty as ::juniper::macros::helper::BaseSubTypes<#scalar>>::NAMES; + const WRAPPED_VALUE: ::juniper::macros::helper::WrappedValue = + <#return_ty as ::juniper::macros::helper::WrappedType<#scalar>>::VALUE; + const ARGUMENTS: &'static [( + ::juniper::macros::helper::Name, + ::juniper::macros::helper::Type, + ::juniper::macros::helper::WrappedValue, + )] = &[#(( + #args_names, + <#args_tys as ::juniper::macros::helper::BaseType<#scalar>>::NAME, + <#args_tys as ::juniper::macros::helper::WrappedType<#scalar>>::VALUE, + )),*]; + } + } + }) + .collect() + } + /// Returns generated code implementing [`Field`] or [`AsyncField`] trait /// for this [GraphQL interface][1]. /// @@ -770,7 +832,6 @@ impl Definition { /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces fn impl_fields(&self, for_async: bool) -> TokenStream { let ty = &self.enum_alias_ident; - let context = &self.context; let scalar = &self.scalar; let const_scalar = match scalar { scalar::Type::Concrete(ty) => ty.clone(), @@ -827,16 +888,6 @@ impl Definition { ) }; - let (args_tys, args_names): (Vec<_>, Vec<_>) = field - .arguments - .iter() - .flat_map(|vec| vec.iter()) - .filter_map(|arg| match arg { - field::MethodArgument::Regular(arg) => Some((&arg.ty, &arg.name)), - _ => None, - }) - .unzip(); - let const_ty_generics = self.const_trait_generics(); let unreachable_arm = (self.implementers.is_empty() @@ -851,24 +902,6 @@ impl Definition { #scalar, { ::juniper::macros::helper::fnv1a128(#field_name) } > for #ty#ty_generics #where_clause { - type Context = #context; - type TypeInfo = (); - const TYPE: ::juniper::macros::helper::Type = - <#return_ty as ::juniper::macros::helper::BaseType<#scalar>>::NAME; - const SUB_TYPES: ::juniper::macros::helper::Types = - <#return_ty as ::juniper::macros::helper::BaseSubTypes<#scalar>>::NAMES; - const WRAPPED_VALUE: ::juniper::macros::helper::WrappedValue = - <#return_ty as ::juniper::macros::helper::WrappedType<#scalar>>::VALUE; - const ARGUMENTS: &'static [( - ::juniper::macros::helper::Name, - ::juniper::macros::helper::Type, - ::juniper::macros::helper::WrappedValue, - )] = &[#(( - #args_names, - <#args_tys as ::juniper::macros::helper::BaseType<#scalar>>::NAME, - <#args_tys as ::juniper::macros::helper::WrappedType<#scalar>>::VALUE, - )),*]; - #call_sig { match self { #(#ty::#impl_idents(v) => { diff --git a/juniper_codegen/src/graphql_object/mod.rs b/juniper_codegen/src/graphql_object/mod.rs index 86940f9f0..e99c24614 100644 --- a/juniper_codegen/src/graphql_object/mod.rs +++ b/juniper_codegen/src/graphql_object/mod.rs @@ -541,6 +541,10 @@ impl Definition { }) }); + let field_meta_impls = self + .fields + .iter() + .map(|f| f.impl_field_meta(ty, &impl_generics, where_clause.as_ref(), scalar, context)); let field_impls = self.fields.iter().filter_map(|f| { f.impl_field( ty, @@ -548,7 +552,6 @@ impl Definition { where_clause.as_ref(), scalar, None, - context, false, ) }); @@ -602,6 +605,8 @@ impl Definition { } } + #(#field_meta_impls)* + #(#field_impls)* } } @@ -614,7 +619,6 @@ impl Definition { #[must_use] fn impl_graphql_value_async_tokens(&self) -> TokenStream { let scalar = &self.scalar; - let context = &self.context; let (impl_generics, where_clause) = self.impl_generics(true); let ty = &self.ty; @@ -639,7 +643,6 @@ impl Definition { where_clause.as_ref(), scalar, None, - context, true, ) }); From d9971a05e2d6f4f7dfc6b0aad369670cd4a005f4 Mon Sep 17 00:00:00 2001 From: ilslv Date: Mon, 3 Jan 2022 15:19:35 +0300 Subject: [PATCH 27/53] WIP (Refactor graphql_interface_new a bit) --- juniper_codegen/src/common/parse/mod.rs | 41 ++++ juniper_codegen/src/graphql_interface/new.rs | 196 +++++++++++-------- 2 files changed, 154 insertions(+), 83 deletions(-) diff --git a/juniper_codegen/src/common/parse/mod.rs b/juniper_codegen/src/common/parse/mod.rs index cae19a015..f74e75b12 100644 --- a/juniper_codegen/src/common/parse/mod.rs +++ b/juniper_codegen/src/common/parse/mod.rs @@ -11,12 +11,14 @@ use std::{ }; use proc_macro2::Span; +use quote::quote; use syn::{ ext::IdentExt as _, parse::{Parse, ParseBuffer}, parse_quote, punctuated::Punctuated, token::{self, Token}, + visit_mut::VisitMut, }; /// Extension of [`ParseBuffer`] providing common function widely used by this crate for parsing. @@ -250,6 +252,9 @@ pub(crate) trait GenericsExt { /// Moves all trait and lifetime bounds of these [`syn::Generics`] to its [`syn::WhereClause`]. fn move_bounds_to_where_clause(&mut self); + + /// Replaces generic parameters in `ty` found in `self` with default ones. + fn replace_type_with_defaults(&self, ty: &mut syn::Type); } impl GenericsExt for syn::Generics { @@ -299,4 +304,40 @@ impl GenericsExt for syn::Generics { } } } + + fn replace_type_with_defaults(&self, ty: &mut syn::Type) { + struct Replace<'a>(&'a syn::Generics); + + impl<'a> VisitMut for Replace<'a> { + fn visit_generic_argument_mut(&mut self, arg: &mut syn::GenericArgument) { + match arg { + syn::GenericArgument::Lifetime(lf) => { + *lf = parse_quote!( 'static ); + } + syn::GenericArgument::Type(ty) => { + let is_generic = self + .0 + .params + .iter() + .filter_map(|par| match par { + syn::GenericParam::Type(ty) => Some(&ty.ident), + _ => None, + }) + .any(|par| { + let par = quote! { #par }.to_string(); + let ty = quote! { #ty }.to_string(); + par == ty + }); + + if is_generic { + *ty = parse_quote!(()); + } + } + _ => {} + } + } + } + + Replace(self).visit_type_mut(ty) + } } diff --git a/juniper_codegen/src/graphql_interface/new.rs b/juniper_codegen/src/graphql_interface/new.rs index f92e8729e..491b8b705 100644 --- a/juniper_codegen/src/graphql_interface/new.rs +++ b/juniper_codegen/src/graphql_interface/new.rs @@ -306,9 +306,9 @@ impl ToTokens for Definition { self.impl_graphql_value_tokens().to_tokens(into); self.impl_graphql_value_async_tokens().to_tokens(into); self.impl_traits_for_reflection().to_tokens(into); - self.impl_fields_meta().to_tokens(into); - self.impl_fields(false).to_tokens(into); - self.impl_fields(true).to_tokens(into); + self.impl_field_meta().to_tokens(into); + self.impl_field().to_tokens(into); + self.impl_async_field().to_tokens(into); } } @@ -750,7 +750,7 @@ impl Definition { { const NAMES: ::juniper::macros::helper::Types = &[ >::NAME, - #(<#implementers as ::juniper::macros::helper::BaseType<#scalar>>::NAME,)* + #(<#implementers as ::juniper::macros::helper::BaseType<#scalar>>::NAME),* ]; } @@ -769,7 +769,7 @@ impl Definition { /// /// [`FieldMeta`]: juniper::macros::helper::FieldMeta /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - fn impl_fields_meta(&self) -> TokenStream { + fn impl_field_meta(&self) -> TokenStream { let ty = &self.enum_alias_ident; let context = &self.context; let scalar = &self.scalar; @@ -783,7 +783,7 @@ impl Definition { .map(|field| { let field_name = &field.name; let mut return_ty = field.ty.clone(); - Self::replace_generics_for_const(&mut return_ty, &generics); + generics.replace_type_with_defaults(&mut return_ty); let (args_tys, args_names): (Vec<_>, Vec<_>) = field .arguments @@ -830,15 +830,10 @@ impl Definition { /// [`AsyncField`]: juniper::macros::helper::AsyncField /// [`Field`]: juniper::macros::helper::Field /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - fn impl_fields(&self, for_async: bool) -> TokenStream { + fn impl_field(&self) -> TokenStream { let ty = &self.enum_alias_ident; let scalar = &self.scalar; - let const_scalar = match scalar { - scalar::Type::Concrete(ty) => ty.clone(), - scalar::Type::ExplicitGeneric(_) | scalar::Type::ImplicitGeneric(_) => { - parse_quote! { ::juniper::DefaultScalarValue } - } - }; + let const_scalar = self.const_scalar(); let impl_tys = self.implementers.iter().collect::>(); let impl_idents = self @@ -854,39 +849,13 @@ impl Definition { self.fields .iter() .filter_map(|field| { - if field.is_async && !for_async { + if field.is_async { return None; } let field_name = &field.name; let mut return_ty = field.ty.clone(); - Self::replace_generics_for_const(&mut return_ty, &generics); - - let (trait_name, call_sig) = if for_async { - ( - quote! { AsyncField }, - quote! { - fn call<'b>( - &'b self, - info: &'b Self::TypeInfo, - args: &'b ::juniper::Arguments<#scalar>, - executor: &'b ::juniper::Executor, - ) -> ::juniper::BoxFuture<'b, ::juniper::ExecutionResult<#scalar>> - }, - ) - } else { - ( - quote! { Field }, - quote! { - fn call( - &self, - info: &Self::TypeInfo, - args: &::juniper::Arguments<#scalar>, - executor: &::juniper::Executor, - ) -> ::juniper::ExecutionResult<#scalar> - }, - ) - }; + generics.replace_type_with_defaults(&mut return_ty); let const_ty_generics = self.const_trait_generics(); @@ -898,11 +867,16 @@ impl Definition { Some(quote! { #[allow(non_snake_case)] - impl#impl_generics ::juniper::macros::helper::#trait_name< + impl#impl_generics ::juniper::macros::helper::Field< #scalar, { ::juniper::macros::helper::fnv1a128(#field_name) } > for #ty#ty_generics #where_clause { - #call_sig { + fn call( + &self, + info: &Self::TypeInfo, + args: &::juniper::Arguments<#scalar>, + executor: &::juniper::Executor, + ) -> ::juniper::ExecutionResult<#scalar> { match self { #(#ty::#impl_idents(v) => { ::juniper::assert_field!( @@ -926,6 +900,77 @@ impl Definition { .collect() } + /// Returns generated code implementing [`AsyncField`] trait for this + /// [GraphQL interface][1]. + /// + /// [`AsyncField`]: juniper::macros::helper::AsyncField + /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces + fn impl_async_field(&self) -> TokenStream { + let ty = &self.enum_alias_ident; + let scalar = &self.scalar; + let const_scalar = self.const_scalar(); + + let impl_tys = self.implementers.iter().collect::>(); + let impl_idents = self + .implementers + .iter() + .filter_map(|ty| ty.path.segments.last().map(|seg| &seg.ident)) + .collect::>(); + + let generics = self.impl_generics(for_async); + let (impl_generics, _, where_clause) = generics.split_for_impl(); + let (_, ty_generics, _) = self.trait_generics.split_for_impl(); + + self.fields + .iter() + .map(|field| { + let field_name = &field.name; + let mut return_ty = field.ty.clone(); + generics.replace_type_with_defaults(&mut return_ty); + + let const_ty_generics = self.const_trait_generics(); + + let unreachable_arm = (self.implementers.is_empty() + || !self.trait_generics.params.is_empty()) + .then(|| { + quote! { _ => unreachable!() } + }); + + quote! { + #[allow(non_snake_case)] + impl#impl_generics ::juniper::macros::helper::AsyncField< + #scalar, + { ::juniper::macros::helper::fnv1a128(#field_name) } + > for #ty#ty_generics #where_clause { + fn call<'b>( + &'b self, + info: &'b Self::TypeInfo, + args: &'b ::juniper::Arguments<#scalar>, + executor: &'b ::juniper::Executor, + ) -> ::juniper::BoxFuture<'b, ::juniper::ExecutionResult<#scalar>> { + match self { + #(#ty::#impl_idents(v) => { + ::juniper::assert_field!( + #ty#const_ty_generics, + #impl_tys, + #const_scalar, + #field_name, + ); + + <_ as ::juniper::macros::helper::#trait_name< + #scalar, + { ::juniper::macros::helper::fnv1a128(#field_name) }, + >>::call(v, info, args, executor) + })* + #unreachable_arm + } + } + } + } + }) + .collect() + } + /// Returns generated code for the [`GraphQLValue::concrete_type_name`][0] /// method, which returns name of the underlying [`Implementer`] GraphQL /// type contained in this [`EnumType`]. @@ -1040,13 +1085,17 @@ impl Definition { return; } } - syn::GenericParam::Const(_) => { - // This hack works because only `min_const_generics` are - // enabled for now. - // TODO: replace this once full `const_generics` are - // available. - // Maybe with `<_ as Default>::default()`? - parse_quote!({ 0_u8 as _ }) + syn::GenericParam::Const(c) => { + if c.default.is_none() { + // This hack works because only `min_const_generics` + // are enabled for now. + // TODO: replace this once full `const_generics` are + // available. + // Maybe with `<_ as Default>::default()`? + parse_quote!({ 0_u8 as _ }) + } else { + return; + } } }; self.0.args.push(arg) @@ -1058,41 +1107,22 @@ impl Definition { syn::PathArguments::AngleBracketed(visitor.0) } - /// Replaces `generics` in `ty` for usage in `const` context. - fn replace_generics_for_const(ty: &mut syn::Type, generics: &syn::Generics) { - struct ReplaceGenericsForConst<'a>(&'a syn::Generics); - - impl<'a> VisitMut for ReplaceGenericsForConst<'a> { - fn visit_generic_argument_mut(&mut self, arg: &mut syn::GenericArgument) { - match arg { - syn::GenericArgument::Lifetime(lf) => { - *lf = parse_quote!( 'static ); - } - syn::GenericArgument::Type(ty) => { - let is_generic = self - .0 - .params - .iter() - .filter_map(|par| match par { - syn::GenericParam::Type(ty) => Some(&ty.ident), - _ => None, - }) - .any(|par| { - let par = quote! { #par }.to_string(); - let ty = quote! { #ty }.to_string(); - par == ty - }); - - if is_generic { - *ty = parse_quote!(()); - } - } - _ => {} - } + /// Returns [`scalar`] replaced with [`DefaultScalarValue`] in case it's + /// [`ExplicitGeneric`] or [`ImplicitGeneric`] for using [`scalar`] in + /// `const` context. + /// + /// [`scalar`]: Self::scalar + /// [`DefaultScalarValue`]: juniper::DefaultScalarValue + /// [`ExplicitGeneric`]: scalar::Type::ExplicitGeneric + /// [`ImplicitGeneric`]: scalar::Type::ImplicitGeneric + #[must_use] + fn const_scalar(&self) -> syn::Type { + match &self.scalar { + scalar::Type::Concrete(ty) => ty.clone(), + scalar::Type::ExplicitGeneric(_) | scalar::Type::ImplicitGeneric(_) => { + parse_quote! { ::juniper::DefaultScalarValue } } } - - ReplaceGenericsForConst(&generics).visit_type_mut(ty) } /// Returns prepared [`syn::Generics`] for [`GraphQLType`] trait (and From 8a966fbe1f7cc218076c38b483656cfcca99f3ed Mon Sep 17 00:00:00 2001 From: ilslv Date: Mon, 3 Jan 2022 15:58:48 +0300 Subject: [PATCH 28/53] WIP (Corrections) --- juniper_codegen/src/graphql_interface/new.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/juniper_codegen/src/graphql_interface/new.rs b/juniper_codegen/src/graphql_interface/new.rs index 491b8b705..2ab8c6e51 100644 --- a/juniper_codegen/src/graphql_interface/new.rs +++ b/juniper_codegen/src/graphql_interface/new.rs @@ -14,7 +14,6 @@ use syn::{ spanned::Spanned as _, token, visit::Visit, - visit_mut::VisitMut, }; use crate::{ @@ -842,7 +841,7 @@ impl Definition { .filter_map(|ty| ty.path.segments.last().map(|seg| &seg.ident)) .collect::>(); - let generics = self.impl_generics(for_async); + let generics = self.impl_generics(false); let (impl_generics, _, where_clause) = generics.split_for_impl(); let (_, ty_generics, _) = self.trait_generics.split_for_impl(); @@ -886,7 +885,7 @@ impl Definition { #field_name, ); - <_ as ::juniper::macros::helper::#trait_name< + <_ as ::juniper::macros::helper::Field< #scalar, { ::juniper::macros::helper::fnv1a128(#field_name) }, >>::call(v, info, args, executor) @@ -917,7 +916,7 @@ impl Definition { .filter_map(|ty| ty.path.segments.last().map(|seg| &seg.ident)) .collect::>(); - let generics = self.impl_generics(for_async); + let generics = self.impl_generics(true); let (impl_generics, _, where_clause) = generics.split_for_impl(); let (_, ty_generics, _) = self.trait_generics.split_for_impl(); @@ -957,7 +956,7 @@ impl Definition { #field_name, ); - <_ as ::juniper::macros::helper::#trait_name< + <_ as ::juniper::macros::helper::AsyncField< #scalar, { ::juniper::macros::helper::fnv1a128(#field_name) }, >>::call(v, info, args, executor) From 21f4940d6c861325694d963aad9142ad40648421 Mon Sep 17 00:00:00 2001 From: ilslv Date: Tue, 4 Jan 2022 09:18:17 +0300 Subject: [PATCH 29/53] WIP (Prettify non-existent Field assertions) --- juniper/src/macros/helper/mod.rs | 136 +++++++++++-------- juniper_codegen/src/graphql_interface/new.rs | 9 ++ juniper_codegen/src/graphql_object/mod.rs | 9 ++ 3 files changed, 95 insertions(+), 59 deletions(-) diff --git a/juniper/src/macros/helper/mod.rs b/juniper/src/macros/helper/mod.rs index b65c69680..684043e72 100644 --- a/juniper/src/macros/helper/mod.rs +++ b/juniper/src/macros/helper/mod.rs @@ -90,6 +90,7 @@ where pub type Type = &'static str; pub type Types = &'static [Type]; pub type Name = &'static str; +pub type Names = &'static [Name]; pub type FieldName = u128; pub type WrappedValue = u128; @@ -117,6 +118,31 @@ macro_rules! const_concat { }}; } +#[macro_export] +macro_rules! hash { + ($field_name: expr, $impl_ty: ty, $scalar: ty $(, $prefix: expr)? $(,)?) => {{ + let exists = $crate::macros::helper::exists( + $field_name, + <$impl_ty as $crate::macros::helper::Fields<$scalar>>::NAMES, + ); + if exists { + $crate::macros::helper::fnv1a128(FIELD_NAME) + } else { + ::std::panic!( + "{}", + $crate::const_concat!( + $($prefix,)? + "Field `", + $field_name, + "` isn't implemented on `", + <$impl_ty as $crate::macros::helper::BaseType<$scalar>>::NAME, + "`." + ) + ) + } + }}; +} + #[macro_export] macro_rules! format_type { ($ty: expr, $wrapped_value: expr $(,)?) => {{ @@ -142,27 +168,13 @@ macro_rules! format_type { match current_wrap_val % 10 { 2 => is_null = true, 3 => { - if is_null { - let mut i = 0; - while i < OPENING_BRACKET.as_bytes().len() { - type_arr[current_start + i] = OPENING_BRACKET.as_bytes()[i]; - i += 1; - } - current_start += i; - i = 0; - while i < CLOSING_BRACKET.as_bytes().len() { - type_arr[current_end - CLOSING_BRACKET.as_bytes().len() + i + 1] = - CLOSING_BRACKET.as_bytes()[i]; - i += 1; - } - current_end -= i; - } else { - let mut i = 0; - while i < OPENING_BRACKET.as_bytes().len() { - type_arr[current_start + i] = OPENING_BRACKET.as_bytes()[i]; - i += 1; - } - current_start += i; + let mut i = 0; + while i < OPENING_BRACKET.as_bytes().len() { + type_arr[current_start + i] = OPENING_BRACKET.as_bytes()[i]; + i += 1; + } + current_start += i; + if !is_null { i = 0; while i < BANG.as_bytes().len() { type_arr[current_end - BANG.as_bytes().len() + i + 1] = @@ -170,14 +182,14 @@ macro_rules! format_type { i += 1; } current_end -= i; - i = 0; - while i < CLOSING_BRACKET.as_bytes().len() { - type_arr[current_end - CLOSING_BRACKET.as_bytes().len() + i + 1] = - CLOSING_BRACKET.as_bytes()[i]; - i += 1; - } - current_end -= i; } + i = 0; + while i < CLOSING_BRACKET.as_bytes().len() { + type_arr[current_end - CLOSING_BRACKET.as_bytes().len() + i + 1] = + CLOSING_BRACKET.as_bytes()[i]; + i += 1; + } + current_end -= i; is_null = false; } _ => {} @@ -231,50 +243,54 @@ macro_rules! assert_subtype { $field_name: expr $(,)? ) => { const _: () = { + const BASE_TY: $crate::macros::helper::Type = + <$base_ty as $crate::macros::helper::BaseType<$scalar>>::NAME; + const IMPL_TY: $crate::macros::helper::Type = + <$impl_ty as $crate::macros::helper::BaseType<$scalar>>::NAME; + const ERR_PREFIX: &str = $crate::const_concat!( + "Failed to implement interface `", + BASE_TY, + "` on `", + IMPL_TY, + "`: ", + ); + const FIELD_NAME: $crate::macros::helper::Name = $field_name; const BASE_RETURN_WRAPPED_VAL: $crate::macros::helper::WrappedValue = <$base_ty as $crate::macros::helper::FieldMeta< $scalar, - { $crate::macros::helper::fnv1a128(FIELD_NAME) }, + { $crate::hash!(FIELD_NAME, $base_ty, $scalar, ERR_PREFIX) }, >>::WRAPPED_VALUE; const IMPL_RETURN_WRAPPED_VAL: $crate::macros::helper::WrappedValue = <$impl_ty as $crate::macros::helper::FieldMeta< $scalar, - { $crate::macros::helper::fnv1a128(FIELD_NAME) }, + { $crate::hash!(FIELD_NAME, $impl_ty, $scalar, ERR_PREFIX) }, >>::WRAPPED_VALUE; - const BASE_TY: $crate::macros::helper::Type = - <$base_ty as $crate::macros::helper::BaseType<$scalar>>::NAME; - const IMPL_TY: $crate::macros::helper::Type = - <$impl_ty as $crate::macros::helper::BaseType<$scalar>>::NAME; - const BASE_RETURN_SUB_TYPES: $crate::macros::helper::Types = <$base_ty as $crate::macros::helper::FieldMeta< $scalar, - { $crate::macros::helper::fnv1a128(FIELD_NAME) }, + { $crate::hash!(FIELD_NAME, $base_ty, $scalar, ERR_PREFIX) }, >>::SUB_TYPES; const BASE_RETURN_TY: $crate::macros::helper::Type = <$base_ty as $crate::macros::helper::FieldMeta< $scalar, - { $crate::macros::helper::fnv1a128(FIELD_NAME) }, + { $crate::hash!(FIELD_NAME, $base_ty, $scalar, ERR_PREFIX) }, >>::TYPE; const IMPL_RETURN_TY: $crate::macros::helper::Type = <$impl_ty as $crate::macros::helper::FieldMeta< $scalar, - { $crate::macros::helper::fnv1a128(FIELD_NAME) }, + { $crate::hash!(FIELD_NAME, $impl_ty, $scalar, ERR_PREFIX) }, >>::TYPE; let is_subtype = $crate::macros::helper::exists(IMPL_RETURN_TY, BASE_RETURN_SUB_TYPES) && $crate::macros::helper::can_be_subtype(BASE_RETURN_WRAPPED_VAL, IMPL_RETURN_WRAPPED_VAL); if !is_subtype { const MSG: &str = $crate::const_concat!( - "Failed to implement interface `", - BASE_TY, - "` on `", - IMPL_TY, - "`: Field `", + ERR_PREFIX, + "Field `", FIELD_NAME, "`: implementor is expected to return a subtype of interface's return object: `", $crate::format_type!(IMPL_RETURN_TY, IMPL_RETURN_WRAPPED_VAL), @@ -303,16 +319,24 @@ macro_rules! assert_field_args { $crate::macros::helper::WrappedValue, ); - const FIELD_NAME: &str = $field_name; const BASE_NAME: &str = <$base_ty as $crate::macros::helper::BaseType<$scalar>>::NAME; const IMPL_NAME: &str = <$impl_ty as $crate::macros::helper::BaseType<$scalar>>::NAME; + const ERR_PREFIX: &str = $crate::const_concat!( + "Failed to implement interface `", + BASE_NAME, + "` on `", + IMPL_NAME, + "`: ", + ); + + const FIELD_NAME: &str = $field_name; const BASE_ARGS: &[FullArg] = <$base_ty as $crate::macros::helper::FieldMeta< $scalar, - { $crate::macros::helper::fnv1a128(FIELD_NAME) }, + { $crate::hash!(FIELD_NAME, $base_ty, $scalar, ERR_PREFIX) }, >>::ARGUMENTS; const IMPL_ARGS: &[FullArg] = <$impl_ty as $crate::macros::helper::FieldMeta< $scalar, - { $crate::macros::helper::fnv1a128(FIELD_NAME) }, + { $crate::hash!(FIELD_NAME, $impl_ty, $scalar, ERR_PREFIX) }, >>::ARGUMENTS; struct Error { @@ -419,19 +443,9 @@ macro_rules! assert_field_args { const IMPL_TYPE_FORMATTED: &str = $crate::format_type!(ERROR.implementation.1, ERROR.implementation.2); - const PREFIX: &str = $crate::const_concat!( - "Failed to implement interface `", - BASE_NAME, - "` on `", - IMPL_NAME, - "`: Field `", - FIELD_NAME, - "`: ", - ); const MSG: &str = match ERROR.cause { Cause::TypeMismatch => { $crate::const_concat!( - PREFIX, "Argument `", BASE_ARG_NAME, "`: expected type `", @@ -443,7 +457,6 @@ macro_rules! assert_field_args { } Cause::RequiredField => { $crate::const_concat!( - PREFIX, "Argument `", BASE_ARG_NAME, "` of type `", @@ -453,7 +466,6 @@ macro_rules! assert_field_args { } Cause::AdditionalNonNullableField => { $crate::const_concat!( - PREFIX, "Argument `", IMPL_ARG_NAME, "` of type `", @@ -462,7 +474,9 @@ macro_rules! assert_field_args { ) } }; - ::std::panic!("{}", MSG); + const ERROR_MSG: &str = + $crate::const_concat!(ERR_PREFIX, "Field `", FIELD_NAME, "`: ", MSG); + ::std::panic!("{}", ERROR_MSG); } }; }; @@ -630,6 +644,10 @@ impl + ?Sized> WrappedType for Rc { const VALUE: u128 = T::VALUE; } +pub trait Fields { + const NAMES: Names; +} + pub trait FieldMeta { type Context; type TypeInfo; diff --git a/juniper_codegen/src/graphql_interface/new.rs b/juniper_codegen/src/graphql_interface/new.rs index 2ab8c6e51..d58917b5a 100644 --- a/juniper_codegen/src/graphql_interface/new.rs +++ b/juniper_codegen/src/graphql_interface/new.rs @@ -728,6 +728,7 @@ impl Definition { let implementers = &self.implementers; let scalar = &self.scalar; let name = &self.name; + let fields = self.fields.iter().map(|f| &f.name); let generics = self.impl_generics(false); let (impl_generics, _, where_clause) = generics.split_for_impl(); @@ -760,6 +761,14 @@ impl Definition { { const VALUE: ::juniper::macros::helper::WrappedValue = 1; } + + #[automatically_derived] + impl#impl_generics ::juniper::macros::helper::Fields<#scalar> + for #ty#ty_generics + #where_clause + { + const NAMES: ::juniper::macros::helper::Names = &[#(#fields),*]; + } } } diff --git a/juniper_codegen/src/graphql_object/mod.rs b/juniper_codegen/src/graphql_object/mod.rs index e99c24614..ac838bc06 100644 --- a/juniper_codegen/src/graphql_object/mod.rs +++ b/juniper_codegen/src/graphql_object/mod.rs @@ -368,6 +368,7 @@ impl Definition { let name = &self.name; let (impl_generics, where_clause) = self.impl_generics(false); let ty = &self.ty; + let fields = self.fields.iter().map(|f| &f.name); quote! { #[automatically_derived] @@ -394,6 +395,14 @@ impl Definition { { const VALUE: ::juniper::macros::helper::WrappedValue = 1; } + + #[automatically_derived] + impl#impl_generics ::juniper::macros::helper::Fields<#scalar> + for #ty + #where_clause + { + const NAMES: ::juniper::macros::helper::Names = &[#(#fields),*]; + } } } From e03901b12bab3a87a1d8a43084de03a1200f1a31 Mon Sep 17 00:00:00 2001 From: ilslv Date: Tue, 4 Jan 2022 09:39:36 +0300 Subject: [PATCH 30/53] WIP (Move to macros::reflection module) --- juniper/src/macros/helper/mod.rs | 677 +----------------- juniper/src/macros/mod.rs | 3 + juniper/src/macros/reflection.rs | 679 +++++++++++++++++++ juniper/src/types/scalars.rs | 9 +- juniper_codegen/src/common/field/mod.rs | 30 +- juniper_codegen/src/derive_scalar_value.rs | 8 +- juniper_codegen/src/graphql_interface/mod.rs | 16 +- juniper_codegen/src/graphql_interface/new.rs | 84 +-- juniper_codegen/src/graphql_object/mod.rs | 26 +- juniper_codegen/src/graphql_union/mod.rs | 16 +- juniper_codegen/src/impl_scalar.rs | 8 +- juniper_codegen/src/util/mod.rs | 16 +- 12 files changed, 791 insertions(+), 781 deletions(-) create mode 100644 juniper/src/macros/reflection.rs diff --git a/juniper/src/macros/helper/mod.rs b/juniper/src/macros/helper/mod.rs index 684043e72..0f81018da 100644 --- a/juniper/src/macros/helper/mod.rs +++ b/juniper/src/macros/helper/mod.rs @@ -2,14 +2,11 @@ pub mod subscription; -use std::{fmt, rc::Rc, sync::Arc}; +use std::fmt; use futures::future::{self, BoxFuture}; -use crate::{ - Arguments, DefaultScalarValue, DynGraphQLValue, DynGraphQLValueAsync, ExecutionResult, - Executor, FieldError, Nullable, ScalarValue, -}; +use crate::{DefaultScalarValue, DynGraphQLValue, DynGraphQLValueAsync, FieldError, ScalarValue}; /// Conversion of a [`GraphQLValue`] to its [trait object][1]. /// @@ -86,673 +83,3 @@ where { Box::pin(future::err(err_unnamed_type(name))) } - -pub type Type = &'static str; -pub type Types = &'static [Type]; -pub type Name = &'static str; -pub type Names = &'static [Name]; -pub type FieldName = u128; -pub type WrappedValue = u128; - -#[macro_export] -macro_rules! const_concat { - ($($s:expr),* $(,)?) => {{ - const LEN: usize = 0 $(+ $s.len())*; - const CNT: usize = [$($s),*].len(); - const fn concat(input: [&str; CNT]) -> [u8; LEN] { - let mut bytes = [0; LEN]; - let (mut i, mut byte) = (0, 0); - while i < CNT { - let mut b = 0; - while b < input[i].len() { - bytes[byte] = input[i].as_bytes()[b]; - byte += 1; - b += 1; - } - i += 1; - } - bytes - } - const CON: [u8; LEN] = concat([$($s),*]); - unsafe { std::str::from_utf8_unchecked(&CON) } - }}; -} - -#[macro_export] -macro_rules! hash { - ($field_name: expr, $impl_ty: ty, $scalar: ty $(, $prefix: expr)? $(,)?) => {{ - let exists = $crate::macros::helper::exists( - $field_name, - <$impl_ty as $crate::macros::helper::Fields<$scalar>>::NAMES, - ); - if exists { - $crate::macros::helper::fnv1a128(FIELD_NAME) - } else { - ::std::panic!( - "{}", - $crate::const_concat!( - $($prefix,)? - "Field `", - $field_name, - "` isn't implemented on `", - <$impl_ty as $crate::macros::helper::BaseType<$scalar>>::NAME, - "`." - ) - ) - } - }}; -} - -#[macro_export] -macro_rules! format_type { - ($ty: expr, $wrapped_value: expr $(,)?) => {{ - const TYPE: ( - $crate::macros::helper::Type, - $crate::macros::helper::WrappedValue, - ) = ($ty, $wrapped_value); - const RES_LEN: usize = $crate::macros::helper::str_len_from_wrapped_val(TYPE.0, TYPE.1); - - const OPENING_BRACKET: &str = "["; - const CLOSING_BRACKET: &str = "]"; - const BANG: &str = "!"; - - const fn format_type_arr() -> [u8; RES_LEN] { - let (ty, wrap_val) = TYPE; - let mut type_arr: [u8; RES_LEN] = [0; RES_LEN]; - - let mut current_start = 0; - let mut current_end = RES_LEN - 1; - let mut current_wrap_val = wrap_val; - let mut is_null = false; - while current_wrap_val % 10 != 0 { - match current_wrap_val % 10 { - 2 => is_null = true, - 3 => { - let mut i = 0; - while i < OPENING_BRACKET.as_bytes().len() { - type_arr[current_start + i] = OPENING_BRACKET.as_bytes()[i]; - i += 1; - } - current_start += i; - if !is_null { - i = 0; - while i < BANG.as_bytes().len() { - type_arr[current_end - BANG.as_bytes().len() + i + 1] = - BANG.as_bytes()[i]; - i += 1; - } - current_end -= i; - } - i = 0; - while i < CLOSING_BRACKET.as_bytes().len() { - type_arr[current_end - CLOSING_BRACKET.as_bytes().len() + i + 1] = - CLOSING_BRACKET.as_bytes()[i]; - i += 1; - } - current_end -= i; - is_null = false; - } - _ => {} - } - - current_wrap_val /= 10; - } - - let mut i = 0; - while i < ty.as_bytes().len() { - type_arr[current_start + i] = ty.as_bytes()[i]; - i += 1; - } - i = 0; - if !is_null { - while i < BANG.as_bytes().len() { - type_arr[current_end - BANG.as_bytes().len() + i + 1] = BANG.as_bytes()[i]; - i += 1; - } - } - - type_arr - } - - const TYPE_ARR: [u8; RES_LEN] = format_type_arr(); - // SAFETY: This is safe, as `TYPE_ARR` was formatted from `Type`, `[`, `]` and `!`. - const TYPE_FORMATTED: &str = unsafe { ::std::mem::transmute(TYPE_ARR.as_slice()) }; - TYPE_FORMATTED - }}; -} - -#[macro_export] -macro_rules! assert_field { - ( - $base_ty: ty, - $impl_ty: ty, - $scalar: ty, - $field_name: expr $(,)? - ) => { - $crate::assert_field_args!($base_ty, $impl_ty, $scalar, $field_name); - $crate::assert_subtype!($base_ty, $impl_ty, $scalar, $field_name); - }; -} - -#[macro_export] -macro_rules! assert_subtype { - ( - $base_ty: ty, - $impl_ty: ty, - $scalar: ty, - $field_name: expr $(,)? - ) => { - const _: () = { - const BASE_TY: $crate::macros::helper::Type = - <$base_ty as $crate::macros::helper::BaseType<$scalar>>::NAME; - const IMPL_TY: $crate::macros::helper::Type = - <$impl_ty as $crate::macros::helper::BaseType<$scalar>>::NAME; - const ERR_PREFIX: &str = $crate::const_concat!( - "Failed to implement interface `", - BASE_TY, - "` on `", - IMPL_TY, - "`: ", - ); - - const FIELD_NAME: $crate::macros::helper::Name = - $field_name; - const BASE_RETURN_WRAPPED_VAL: $crate::macros::helper::WrappedValue = - <$base_ty as $crate::macros::helper::FieldMeta< - $scalar, - { $crate::hash!(FIELD_NAME, $base_ty, $scalar, ERR_PREFIX) }, - >>::WRAPPED_VALUE; - const IMPL_RETURN_WRAPPED_VAL: $crate::macros::helper::WrappedValue = - <$impl_ty as $crate::macros::helper::FieldMeta< - $scalar, - { $crate::hash!(FIELD_NAME, $impl_ty, $scalar, ERR_PREFIX) }, - >>::WRAPPED_VALUE; - - const BASE_RETURN_SUB_TYPES: $crate::macros::helper::Types = - <$base_ty as $crate::macros::helper::FieldMeta< - $scalar, - { $crate::hash!(FIELD_NAME, $base_ty, $scalar, ERR_PREFIX) }, - >>::SUB_TYPES; - - const BASE_RETURN_TY: $crate::macros::helper::Type = - <$base_ty as $crate::macros::helper::FieldMeta< - $scalar, - { $crate::hash!(FIELD_NAME, $base_ty, $scalar, ERR_PREFIX) }, - >>::TYPE; - const IMPL_RETURN_TY: $crate::macros::helper::Type = - <$impl_ty as $crate::macros::helper::FieldMeta< - $scalar, - { $crate::hash!(FIELD_NAME, $impl_ty, $scalar, ERR_PREFIX) }, - >>::TYPE; - - let is_subtype = $crate::macros::helper::exists(IMPL_RETURN_TY, BASE_RETURN_SUB_TYPES) - && $crate::macros::helper::can_be_subtype(BASE_RETURN_WRAPPED_VAL, IMPL_RETURN_WRAPPED_VAL); - if !is_subtype { - const MSG: &str = $crate::const_concat!( - ERR_PREFIX, - "Field `", - FIELD_NAME, - "`: implementor is expected to return a subtype of interface's return object: `", - $crate::format_type!(IMPL_RETURN_TY, IMPL_RETURN_WRAPPED_VAL), - "` is not a subtype of `", - $crate::format_type!(BASE_RETURN_TY, BASE_RETURN_WRAPPED_VAL), - "`.", - ); - ::std::panic!("{}", MSG); - } - }; - }; -} - -#[macro_export] -macro_rules! assert_field_args { - ( - $base_ty: ty, - $impl_ty: ty, - $scalar: ty, - $field_name: expr $(,)? - ) => { - const _: () = { - type FullArg = ( - $crate::macros::helper::Name, - $crate::macros::helper::Type, - $crate::macros::helper::WrappedValue, - ); - - const BASE_NAME: &str = <$base_ty as $crate::macros::helper::BaseType<$scalar>>::NAME; - const IMPL_NAME: &str = <$impl_ty as $crate::macros::helper::BaseType<$scalar>>::NAME; - const ERR_PREFIX: &str = $crate::const_concat!( - "Failed to implement interface `", - BASE_NAME, - "` on `", - IMPL_NAME, - "`: ", - ); - - const FIELD_NAME: &str = $field_name; - const BASE_ARGS: &[FullArg] = <$base_ty as $crate::macros::helper::FieldMeta< - $scalar, - { $crate::hash!(FIELD_NAME, $base_ty, $scalar, ERR_PREFIX) }, - >>::ARGUMENTS; - const IMPL_ARGS: &[FullArg] = <$impl_ty as $crate::macros::helper::FieldMeta< - $scalar, - { $crate::hash!(FIELD_NAME, $impl_ty, $scalar, ERR_PREFIX) }, - >>::ARGUMENTS; - - struct Error { - cause: Cause, - base: FullArg, - implementation: FullArg, - } - - enum Cause { - RequiredField, - AdditionalNonNullableField, - TypeMismatch, - } - - const fn unwrap_error(v: ::std::result::Result<(), Error>) -> Error { - match v { - Ok(()) => Error { - cause: Cause::RequiredField, - base: ("unreachable", "unreachable", 1), - implementation: ("unreachable", "unreachable", 1), - }, - Err(err) => err, - } - } - - const fn check() -> Result<(), Error> { - let mut base_i = 0; - while base_i < BASE_ARGS.len() { - let (base_name, base_type, base_wrap_val) = BASE_ARGS[base_i]; - - let mut impl_i = 0; - let mut was_found = false; - while impl_i < IMPL_ARGS.len() { - let (impl_name, impl_type, impl_wrap_val) = IMPL_ARGS[impl_i]; - - if $crate::macros::helper::str_eq(base_name, impl_name) { - if $crate::macros::helper::str_eq(base_type, impl_type) - && base_wrap_val == impl_wrap_val - { - was_found = true; - break; - } else { - return Err(Error { - cause: Cause::TypeMismatch, - base: (base_name, base_type, base_wrap_val), - implementation: (impl_name, impl_type, impl_wrap_val), - }); - } - } - - impl_i += 1; - } - - if !was_found { - return Err(Error { - cause: Cause::RequiredField, - base: (base_name, base_type, base_wrap_val), - implementation: (base_name, base_type, base_wrap_val), - }); - } - - base_i += 1; - } - - let mut impl_i = 0; - while impl_i < IMPL_ARGS.len() { - let (impl_name, impl_type, impl_wrapped_val) = IMPL_ARGS[impl_i]; - impl_i += 1; - - if impl_wrapped_val % 10 == 2 { - continue; - } - - let mut base_i = 0; - let mut was_found = false; - while base_i < BASE_ARGS.len() { - let (base_name, _, _) = BASE_ARGS[base_i]; - if $crate::macros::helper::str_eq(base_name, impl_name) { - was_found = true; - break; - } - base_i += 1; - } - if !was_found { - return Err(Error { - cause: Cause::AdditionalNonNullableField, - base: (impl_name, impl_type, impl_wrapped_val), - implementation: (impl_name, impl_type, impl_wrapped_val), - }); - } - } - - Ok(()) - } - - const RES: ::std::result::Result<(), Error> = check(); - if RES.is_err() { - const ERROR: Error = unwrap_error(RES); - - const BASE_ARG_NAME: &str = ERROR.base.0; - const IMPL_ARG_NAME: &str = ERROR.implementation.0; - - const BASE_TYPE_FORMATTED: &str = $crate::format_type!(ERROR.base.1, ERROR.base.2); - const IMPL_TYPE_FORMATTED: &str = - $crate::format_type!(ERROR.implementation.1, ERROR.implementation.2); - - const MSG: &str = match ERROR.cause { - Cause::TypeMismatch => { - $crate::const_concat!( - "Argument `", - BASE_ARG_NAME, - "`: expected type `", - BASE_TYPE_FORMATTED, - "`, found: `", - IMPL_TYPE_FORMATTED, - "`.", - ) - } - Cause::RequiredField => { - $crate::const_concat!( - "Argument `", - BASE_ARG_NAME, - "` of type `", - BASE_TYPE_FORMATTED, - "` was expected, but not found." - ) - } - Cause::AdditionalNonNullableField => { - $crate::const_concat!( - "Argument `", - IMPL_ARG_NAME, - "` of type `", - IMPL_TYPE_FORMATTED, - "` not present on the interface and so has to be nullable." - ) - } - }; - const ERROR_MSG: &str = - $crate::const_concat!(ERR_PREFIX, "Field `", FIELD_NAME, "`: ", MSG); - ::std::panic!("{}", ERROR_MSG); - } - }; - }; -} - -/// TODO -pub trait BaseType { - const NAME: Type; -} - -impl<'a, S, T: BaseType + ?Sized> BaseType for &'a T { - const NAME: Type = T::NAME; -} - -// TODO: Reconsider -impl, Ctx> BaseType for (Ctx, T) { - const NAME: Type = T::NAME; -} - -impl> BaseType for Option { - const NAME: Type = T::NAME; -} - -impl> BaseType for Nullable { - const NAME: Type = T::NAME; -} - -// TODO: Should Err be trait bounded somehow? -impl, E> BaseType for Result { - const NAME: Type = T::NAME; -} - -impl> BaseType for Vec { - const NAME: Type = T::NAME; -} - -impl> BaseType for [T] { - const NAME: Type = T::NAME; -} - -impl, const N: usize> BaseType for [T; N] { - const NAME: Type = T::NAME; -} - -impl + ?Sized> BaseType for Box { - const NAME: Type = T::NAME; -} - -impl + ?Sized> BaseType for Arc { - const NAME: Type = T::NAME; -} - -impl + ?Sized> BaseType for Rc { - const NAME: Type = T::NAME; -} - -/// TODO -pub trait BaseSubTypes { - const NAMES: Types; -} - -impl<'a, S, T: BaseSubTypes + ?Sized> BaseSubTypes for &'a T { - const NAMES: Types = T::NAMES; -} - -// TODO: Reconsider -impl, Ctx> BaseSubTypes for (Ctx, T) { - const NAMES: Types = T::NAMES; -} - -impl> BaseSubTypes for Option { - const NAMES: Types = T::NAMES; -} - -impl> BaseSubTypes for Nullable { - const NAMES: Types = T::NAMES; -} - -// TODO: Should Err be trait bounded somehow? -impl, E> BaseSubTypes for Result { - const NAMES: Types = T::NAMES; -} - -impl> BaseSubTypes for Vec { - const NAMES: Types = T::NAMES; -} - -impl> BaseSubTypes for [T] { - const NAMES: Types = T::NAMES; -} - -impl, const N: usize> BaseSubTypes for [T; N] { - const NAMES: Types = T::NAMES; -} - -impl + ?Sized> BaseSubTypes for Box { - const NAMES: Types = T::NAMES; -} - -impl + ?Sized> BaseSubTypes for Arc { - const NAMES: Types = T::NAMES; -} - -impl + ?Sized> BaseSubTypes for Rc { - const NAMES: Types = T::NAMES; -} - -/// TODO -pub trait WrappedType { - /// NonNull - 1 - /// Nullable - 2 - /// List - 3 - /// - /// `[[Int]!] - >>> as WrappedType>::N = 12332` - const VALUE: u128; -} - -impl<'a, S, T: WrappedType> WrappedType for (&'a T::Context, T) -where - S: ScalarValue, - T: crate::GraphQLValue, -{ - const VALUE: u128 = T::VALUE; -} - -impl> WrappedType for Option { - const VALUE: u128 = T::VALUE * 10 + 2; -} - -impl> WrappedType for Nullable { - const VALUE: u128 = T::VALUE * 10 + 2; -} - -// TODO: Should Err be trait bounded somehow? -// And should `VALUE` be `T::VALUE` or `T::VALUE * 10 + 2`? -impl, E> WrappedType for Result { - const VALUE: u128 = T::VALUE; -} - -impl> WrappedType for Vec { - const VALUE: u128 = T::VALUE * 10 + 3; -} - -impl> WrappedType for [T] { - const VALUE: u128 = T::VALUE * 10 + 3; -} - -impl, const N: usize> WrappedType for [T; N] { - const VALUE: u128 = T::VALUE * 10 + 3; -} - -impl<'a, S, T: WrappedType + ?Sized> WrappedType for &'a T { - const VALUE: u128 = T::VALUE; -} - -impl + ?Sized> WrappedType for Box { - const VALUE: u128 = T::VALUE; -} - -impl + ?Sized> WrappedType for Arc { - const VALUE: u128 = T::VALUE; -} - -impl + ?Sized> WrappedType for Rc { - const VALUE: u128 = T::VALUE; -} - -pub trait Fields { - const NAMES: Names; -} - -pub trait FieldMeta { - type Context; - type TypeInfo; - const TYPE: Type; - const SUB_TYPES: Types; - const WRAPPED_VALUE: WrappedValue; - const ARGUMENTS: &'static [(Name, Type, WrappedValue)]; -} - -pub trait Field: FieldMeta { - fn call( - &self, - info: &Self::TypeInfo, - args: &Arguments, - executor: &Executor, - ) -> ExecutionResult; -} - -pub trait AsyncField: FieldMeta { - fn call<'b>( - &'b self, - info: &'b Self::TypeInfo, - args: &'b Arguments, - executor: &'b Executor, - ) -> BoxFuture<'b, ExecutionResult>; -} - -/// Non-cryptographic hash with good dispersion to use [`str`](prim@str) in -/// const generics. See [spec] for more info. -/// -/// [spec]: https://datatracker.ietf.org/doc/html/draft-eastlake-fnv-17.html -pub const fn fnv1a128(str: Name) -> u128 { - const FNV_OFFSET_BASIS: u128 = 0x6c62272e07bb014262b821756295c58d; - const FNV_PRIME: u128 = 0x0000000001000000000000000000013b; - - let bytes = str.as_bytes(); - let mut hash = FNV_OFFSET_BASIS; - let mut i = 0; - while i < bytes.len() { - hash ^= bytes[i] as u128; - hash = hash.wrapping_mul(FNV_PRIME); - i += 1; - } - hash -} - -pub const fn str_eq(l: &str, r: &str) -> bool { - let (l, r) = (l.as_bytes(), r.as_bytes()); - - if l.len() != r.len() { - return false; - } - - let mut i = 0; - while i < l.len() { - if l[i] != r[i] { - return false; - } - i += 1; - } - - true -} - -pub const fn str_len_from_wrapped_val(ty: Type, v: WrappedValue) -> usize { - let mut len = ty.as_bytes().len() + "!".as_bytes().len(); // Type! - - let mut current_wrap_val = v; - while current_wrap_val % 10 != 0 { - match current_wrap_val % 10 { - 2 => len -= "!".as_bytes().len(), // remove ! - 3 => len += "[]!".as_bytes().len(), // [Type]! - _ => {} - } - - current_wrap_val /= 10; - } - - len -} - -pub const fn can_be_subtype(ty: WrappedValue, subtype: WrappedValue) -> bool { - let ty_current = ty % 10; - let subtype_current = subtype % 10; - - if ty_current == subtype_current { - if ty_current == 1 { - true - } else { - can_be_subtype(ty / 10, subtype / 10) - } - } else if ty_current == 2 { - can_be_subtype(ty / 10, subtype) - } else { - false - } -} - -pub const fn exists(val: Type, arr: Types) -> bool { - let mut i = 0; - while i < arr.len() { - if str_eq(val, arr[i]) { - return true; - } - i += 1; - } - false -} diff --git a/juniper/src/macros/mod.rs b/juniper/src/macros/mod.rs index 591c4c411..182bd2055 100644 --- a/juniper/src/macros/mod.rs +++ b/juniper/src/macros/mod.rs @@ -3,6 +3,9 @@ #[doc(hidden)] #[macro_use] pub mod helper; +#[doc(hidden)] +#[macro_use] +pub mod reflection; #[macro_use] mod graphql_input_value; diff --git a/juniper/src/macros/reflection.rs b/juniper/src/macros/reflection.rs new file mode 100644 index 000000000..a9a12109c --- /dev/null +++ b/juniper/src/macros/reflection.rs @@ -0,0 +1,679 @@ +//! Helper traits and macros for compile-time reflection. + +use std::{rc::Rc, sync::Arc}; + +use futures::future::BoxFuture; + +use crate::{Arguments, DefaultScalarValue, ExecutionResult, Executor, Nullable, ScalarValue}; + +pub type Type = &'static str; +pub type Types = &'static [Type]; +pub type Name = &'static str; +pub type Names = &'static [Name]; +pub type FieldName = u128; +pub type WrappedValue = u128; + +/// TODO +pub trait BaseType { + const NAME: Type; +} + +impl<'a, S, T: BaseType + ?Sized> BaseType for &'a T { + const NAME: Type = T::NAME; +} + +// TODO: Reconsider +impl, Ctx> BaseType for (Ctx, T) { + const NAME: Type = T::NAME; +} + +impl> BaseType for Option { + const NAME: Type = T::NAME; +} + +impl> BaseType for Nullable { + const NAME: Type = T::NAME; +} + +// TODO: Should Err be trait bounded somehow? +impl, E> BaseType for Result { + const NAME: Type = T::NAME; +} + +impl> BaseType for Vec { + const NAME: Type = T::NAME; +} + +impl> BaseType for [T] { + const NAME: Type = T::NAME; +} + +impl, const N: usize> BaseType for [T; N] { + const NAME: Type = T::NAME; +} + +impl + ?Sized> BaseType for Box { + const NAME: Type = T::NAME; +} + +impl + ?Sized> BaseType for Arc { + const NAME: Type = T::NAME; +} + +impl + ?Sized> BaseType for Rc { + const NAME: Type = T::NAME; +} + +/// TODO +pub trait BaseSubTypes { + const NAMES: Types; +} + +impl<'a, S, T: BaseSubTypes + ?Sized> BaseSubTypes for &'a T { + const NAMES: Types = T::NAMES; +} + +// TODO: Reconsider +impl, Ctx> BaseSubTypes for (Ctx, T) { + const NAMES: Types = T::NAMES; +} + +impl> BaseSubTypes for Option { + const NAMES: Types = T::NAMES; +} + +impl> BaseSubTypes for Nullable { + const NAMES: Types = T::NAMES; +} + +// TODO: Should Err be trait bounded somehow? +impl, E> BaseSubTypes for Result { + const NAMES: Types = T::NAMES; +} + +impl> BaseSubTypes for Vec { + const NAMES: Types = T::NAMES; +} + +impl> BaseSubTypes for [T] { + const NAMES: Types = T::NAMES; +} + +impl, const N: usize> BaseSubTypes for [T; N] { + const NAMES: Types = T::NAMES; +} + +impl + ?Sized> BaseSubTypes for Box { + const NAMES: Types = T::NAMES; +} + +impl + ?Sized> BaseSubTypes for Arc { + const NAMES: Types = T::NAMES; +} + +impl + ?Sized> BaseSubTypes for Rc { + const NAMES: Types = T::NAMES; +} + +/// TODO +pub trait WrappedType { + /// NonNull - 1 + /// Nullable - 2 + /// List - 3 + /// + /// `[[Int]!] - >>> as WrappedType>::N = 12332` + const VALUE: u128; +} + +impl<'a, S, T: WrappedType> WrappedType for (&'a T::Context, T) +where + S: ScalarValue, + T: crate::GraphQLValue, +{ + const VALUE: u128 = T::VALUE; +} + +impl> WrappedType for Option { + const VALUE: u128 = T::VALUE * 10 + 2; +} + +impl> WrappedType for Nullable { + const VALUE: u128 = T::VALUE * 10 + 2; +} + +// TODO: Should Err be trait bounded somehow? +// And should `VALUE` be `T::VALUE` or `T::VALUE * 10 + 2`? +impl, E> WrappedType for Result { + const VALUE: u128 = T::VALUE; +} + +impl> WrappedType for Vec { + const VALUE: u128 = T::VALUE * 10 + 3; +} + +impl> WrappedType for [T] { + const VALUE: u128 = T::VALUE * 10 + 3; +} + +impl, const N: usize> WrappedType for [T; N] { + const VALUE: u128 = T::VALUE * 10 + 3; +} + +impl<'a, S, T: WrappedType + ?Sized> WrappedType for &'a T { + const VALUE: u128 = T::VALUE; +} + +impl + ?Sized> WrappedType for Box { + const VALUE: u128 = T::VALUE; +} + +impl + ?Sized> WrappedType for Arc { + const VALUE: u128 = T::VALUE; +} + +impl + ?Sized> WrappedType for Rc { + const VALUE: u128 = T::VALUE; +} + +pub trait Fields { + const NAMES: Names; +} + +pub trait FieldMeta { + type Context; + type TypeInfo; + const TYPE: Type; + const SUB_TYPES: Types; + const WRAPPED_VALUE: WrappedValue; + const ARGUMENTS: &'static [(Name, Type, WrappedValue)]; +} + +pub trait Field: FieldMeta { + fn call( + &self, + info: &Self::TypeInfo, + args: &Arguments, + executor: &Executor, + ) -> ExecutionResult; +} + +pub trait AsyncField: FieldMeta { + fn call<'b>( + &'b self, + info: &'b Self::TypeInfo, + args: &'b Arguments, + executor: &'b Executor, + ) -> BoxFuture<'b, ExecutionResult>; +} + +/// Non-cryptographic hash with good dispersion to use [`str`](prim@str) in +/// const generics. See [spec] for more info. +/// +/// [spec]: https://datatracker.ietf.org/doc/html/draft-eastlake-fnv-17.html +pub const fn fnv1a128(str: Name) -> u128 { + const FNV_OFFSET_BASIS: u128 = 0x6c62272e07bb014262b821756295c58d; + const FNV_PRIME: u128 = 0x0000000001000000000000000000013b; + + let bytes = str.as_bytes(); + let mut hash = FNV_OFFSET_BASIS; + let mut i = 0; + while i < bytes.len() { + hash ^= bytes[i] as u128; + hash = hash.wrapping_mul(FNV_PRIME); + i += 1; + } + hash +} + +pub const fn str_eq(l: &str, r: &str) -> bool { + let (l, r) = (l.as_bytes(), r.as_bytes()); + + if l.len() != r.len() { + return false; + } + + let mut i = 0; + while i < l.len() { + if l[i] != r[i] { + return false; + } + i += 1; + } + + true +} + +pub const fn str_len_from_wrapped_val(ty: Type, v: WrappedValue) -> usize { + let mut len = ty.as_bytes().len() + "!".as_bytes().len(); // Type! + + let mut current_wrap_val = v; + while current_wrap_val % 10 != 0 { + match current_wrap_val % 10 { + 2 => len -= "!".as_bytes().len(), // remove ! + 3 => len += "[]!".as_bytes().len(), // [Type]! + _ => {} + } + + current_wrap_val /= 10; + } + + len +} + +pub const fn can_be_subtype(ty: WrappedValue, subtype: WrappedValue) -> bool { + let ty_current = ty % 10; + let subtype_current = subtype % 10; + + if ty_current == subtype_current { + if ty_current == 1 { + true + } else { + can_be_subtype(ty / 10, subtype / 10) + } + } else if ty_current == 2 { + can_be_subtype(ty / 10, subtype) + } else { + false + } +} + +pub const fn exists(val: Type, arr: Types) -> bool { + let mut i = 0; + while i < arr.len() { + if str_eq(val, arr[i]) { + return true; + } + i += 1; + } + false +} + +#[macro_export] +macro_rules! assert_field { + ( + $base_ty: ty, + $impl_ty: ty, + $scalar: ty, + $field_name: expr $(,)? + ) => { + $crate::assert_field_args!($base_ty, $impl_ty, $scalar, $field_name); + $crate::assert_subtype!($base_ty, $impl_ty, $scalar, $field_name); + }; +} + +#[macro_export] +macro_rules! assert_subtype { + ( + $base_ty: ty, + $impl_ty: ty, + $scalar: ty, + $field_name: expr $(,)? + ) => { + const _: () = { + const BASE_TY: $crate::macros::reflection::Type = + <$base_ty as $crate::macros::reflection::BaseType<$scalar>>::NAME; + const IMPL_TY: $crate::macros::reflection::Type = + <$impl_ty as $crate::macros::reflection::BaseType<$scalar>>::NAME; + const ERR_PREFIX: &str = $crate::const_concat!( + "Failed to implement interface `", + BASE_TY, + "` on `", + IMPL_TY, + "`: ", + ); + + const FIELD_NAME: $crate::macros::reflection::Name = + $field_name; + const BASE_RETURN_WRAPPED_VAL: $crate::macros::reflection::WrappedValue = + <$base_ty as $crate::macros::reflection::FieldMeta< + $scalar, + { $crate::hash!(FIELD_NAME, $base_ty, $scalar, ERR_PREFIX) }, + >>::WRAPPED_VALUE; + const IMPL_RETURN_WRAPPED_VAL: $crate::macros::reflection::WrappedValue = + <$impl_ty as $crate::macros::reflection::FieldMeta< + $scalar, + { $crate::hash!(FIELD_NAME, $impl_ty, $scalar, ERR_PREFIX) }, + >>::WRAPPED_VALUE; + + const BASE_RETURN_SUB_TYPES: $crate::macros::reflection::Types = + <$base_ty as $crate::macros::reflection::FieldMeta< + $scalar, + { $crate::hash!(FIELD_NAME, $base_ty, $scalar, ERR_PREFIX) }, + >>::SUB_TYPES; + + const BASE_RETURN_TY: $crate::macros::reflection::Type = + <$base_ty as $crate::macros::reflection::FieldMeta< + $scalar, + { $crate::hash!(FIELD_NAME, $base_ty, $scalar, ERR_PREFIX) }, + >>::TYPE; + const IMPL_RETURN_TY: $crate::macros::reflection::Type = + <$impl_ty as $crate::macros::reflection::FieldMeta< + $scalar, + { $crate::hash!(FIELD_NAME, $impl_ty, $scalar, ERR_PREFIX) }, + >>::TYPE; + + let is_subtype = $crate::macros::reflection::exists(IMPL_RETURN_TY, BASE_RETURN_SUB_TYPES) + && $crate::macros::reflection::can_be_subtype(BASE_RETURN_WRAPPED_VAL, IMPL_RETURN_WRAPPED_VAL); + if !is_subtype { + const MSG: &str = $crate::const_concat!( + ERR_PREFIX, + "Field `", + FIELD_NAME, + "`: implementor is expected to return a subtype of interface's return object: `", + $crate::format_type!(IMPL_RETURN_TY, IMPL_RETURN_WRAPPED_VAL), + "` is not a subtype of `", + $crate::format_type!(BASE_RETURN_TY, BASE_RETURN_WRAPPED_VAL), + "`.", + ); + ::std::panic!("{}", MSG); + } + }; + }; +} + +#[macro_export] +macro_rules! assert_field_args { + ( + $base_ty: ty, + $impl_ty: ty, + $scalar: ty, + $field_name: expr $(,)? + ) => { + const _: () = { + type FullArg = ( + $crate::macros::reflection::Name, + $crate::macros::reflection::Type, + $crate::macros::reflection::WrappedValue, + ); + + const BASE_NAME: &str = + <$base_ty as $crate::macros::reflection::BaseType<$scalar>>::NAME; + const IMPL_NAME: &str = + <$impl_ty as $crate::macros::reflection::BaseType<$scalar>>::NAME; + const ERR_PREFIX: &str = $crate::const_concat!( + "Failed to implement interface `", + BASE_NAME, + "` on `", + IMPL_NAME, + "`: ", + ); + + const FIELD_NAME: &str = $field_name; + const BASE_ARGS: &[FullArg] = <$base_ty as $crate::macros::reflection::FieldMeta< + $scalar, + { $crate::hash!(FIELD_NAME, $base_ty, $scalar, ERR_PREFIX) }, + >>::ARGUMENTS; + const IMPL_ARGS: &[FullArg] = <$impl_ty as $crate::macros::reflection::FieldMeta< + $scalar, + { $crate::hash!(FIELD_NAME, $impl_ty, $scalar, ERR_PREFIX) }, + >>::ARGUMENTS; + + struct Error { + cause: Cause, + base: FullArg, + implementation: FullArg, + } + + enum Cause { + RequiredField, + AdditionalNonNullableField, + TypeMismatch, + } + + const fn unwrap_error(v: ::std::result::Result<(), Error>) -> Error { + match v { + Ok(()) => Error { + cause: Cause::RequiredField, + base: ("unreachable", "unreachable", 1), + implementation: ("unreachable", "unreachable", 1), + }, + Err(err) => err, + } + } + + const fn check() -> Result<(), Error> { + let mut base_i = 0; + while base_i < BASE_ARGS.len() { + let (base_name, base_type, base_wrap_val) = BASE_ARGS[base_i]; + + let mut impl_i = 0; + let mut was_found = false; + while impl_i < IMPL_ARGS.len() { + let (impl_name, impl_type, impl_wrap_val) = IMPL_ARGS[impl_i]; + + if $crate::macros::reflection::str_eq(base_name, impl_name) { + if $crate::macros::reflection::str_eq(base_type, impl_type) + && base_wrap_val == impl_wrap_val + { + was_found = true; + break; + } else { + return Err(Error { + cause: Cause::TypeMismatch, + base: (base_name, base_type, base_wrap_val), + implementation: (impl_name, impl_type, impl_wrap_val), + }); + } + } + + impl_i += 1; + } + + if !was_found { + return Err(Error { + cause: Cause::RequiredField, + base: (base_name, base_type, base_wrap_val), + implementation: (base_name, base_type, base_wrap_val), + }); + } + + base_i += 1; + } + + let mut impl_i = 0; + while impl_i < IMPL_ARGS.len() { + let (impl_name, impl_type, impl_wrapped_val) = IMPL_ARGS[impl_i]; + impl_i += 1; + + if impl_wrapped_val % 10 == 2 { + continue; + } + + let mut base_i = 0; + let mut was_found = false; + while base_i < BASE_ARGS.len() { + let (base_name, _, _) = BASE_ARGS[base_i]; + if $crate::macros::reflection::str_eq(base_name, impl_name) { + was_found = true; + break; + } + base_i += 1; + } + if !was_found { + return Err(Error { + cause: Cause::AdditionalNonNullableField, + base: (impl_name, impl_type, impl_wrapped_val), + implementation: (impl_name, impl_type, impl_wrapped_val), + }); + } + } + + Ok(()) + } + + const RES: ::std::result::Result<(), Error> = check(); + if RES.is_err() { + const ERROR: Error = unwrap_error(RES); + + const BASE_ARG_NAME: &str = ERROR.base.0; + const IMPL_ARG_NAME: &str = ERROR.implementation.0; + + const BASE_TYPE_FORMATTED: &str = $crate::format_type!(ERROR.base.1, ERROR.base.2); + const IMPL_TYPE_FORMATTED: &str = + $crate::format_type!(ERROR.implementation.1, ERROR.implementation.2); + + const MSG: &str = match ERROR.cause { + Cause::TypeMismatch => { + $crate::const_concat!( + "Argument `", + BASE_ARG_NAME, + "`: expected type `", + BASE_TYPE_FORMATTED, + "`, found: `", + IMPL_TYPE_FORMATTED, + "`.", + ) + } + Cause::RequiredField => { + $crate::const_concat!( + "Argument `", + BASE_ARG_NAME, + "` of type `", + BASE_TYPE_FORMATTED, + "` was expected, but not found." + ) + } + Cause::AdditionalNonNullableField => { + $crate::const_concat!( + "Argument `", + IMPL_ARG_NAME, + "` of type `", + IMPL_TYPE_FORMATTED, + "` not present on the interface and so has to be nullable." + ) + } + }; + const ERROR_MSG: &str = + $crate::const_concat!(ERR_PREFIX, "Field `", FIELD_NAME, "`: ", MSG); + ::std::panic!("{}", ERROR_MSG); + } + }; + }; +} + +#[macro_export] +macro_rules! const_concat { + ($($s:expr),* $(,)?) => {{ + const LEN: usize = 0 $(+ $s.len())*; + const CNT: usize = [$($s),*].len(); + const fn concat(input: [&str; CNT]) -> [u8; LEN] { + let mut bytes = [0; LEN]; + let (mut i, mut byte) = (0, 0); + while i < CNT { + let mut b = 0; + while b < input[i].len() { + bytes[byte] = input[i].as_bytes()[b]; + byte += 1; + b += 1; + } + i += 1; + } + bytes + } + const CON: [u8; LEN] = concat([$($s),*]); + unsafe { std::str::from_utf8_unchecked(&CON) } + }}; +} + +#[macro_export] +macro_rules! hash { + ($field_name: expr, $impl_ty: ty, $scalar: ty $(, $prefix: expr)? $(,)?) => {{ + let exists = $crate::macros::reflection::exists( + $field_name, + <$impl_ty as $crate::macros::reflection::Fields<$scalar>>::NAMES, + ); + if exists { + $crate::macros::reflection::fnv1a128(FIELD_NAME) + } else { + ::std::panic!( + "{}", + $crate::const_concat!( + $($prefix,)? + "Field `", + $field_name, + "` isn't implemented on `", + <$impl_ty as $crate::macros::reflection::BaseType<$scalar>>::NAME, + "`." + ) + ) + } + }}; +} + +#[macro_export] +macro_rules! format_type { + ($ty: expr, $wrapped_value: expr $(,)?) => {{ + const TYPE: ( + $crate::macros::reflection::Type, + $crate::macros::reflection::WrappedValue, + ) = ($ty, $wrapped_value); + const RES_LEN: usize = $crate::macros::reflection::str_len_from_wrapped_val(TYPE.0, TYPE.1); + + const OPENING_BRACKET: &str = "["; + const CLOSING_BRACKET: &str = "]"; + const BANG: &str = "!"; + + const fn format_type_arr() -> [u8; RES_LEN] { + let (ty, wrap_val) = TYPE; + let mut type_arr: [u8; RES_LEN] = [0; RES_LEN]; + + let mut current_start = 0; + let mut current_end = RES_LEN - 1; + let mut current_wrap_val = wrap_val; + let mut is_null = false; + while current_wrap_val % 10 != 0 { + match current_wrap_val % 10 { + 2 => is_null = true, + 3 => { + let mut i = 0; + while i < OPENING_BRACKET.as_bytes().len() { + type_arr[current_start + i] = OPENING_BRACKET.as_bytes()[i]; + i += 1; + } + current_start += i; + if !is_null { + i = 0; + while i < BANG.as_bytes().len() { + type_arr[current_end - BANG.as_bytes().len() + i + 1] = + BANG.as_bytes()[i]; + i += 1; + } + current_end -= i; + } + i = 0; + while i < CLOSING_BRACKET.as_bytes().len() { + type_arr[current_end - CLOSING_BRACKET.as_bytes().len() + i + 1] = + CLOSING_BRACKET.as_bytes()[i]; + i += 1; + } + current_end -= i; + is_null = false; + } + _ => {} + } + + current_wrap_val /= 10; + } + + let mut i = 0; + while i < ty.as_bytes().len() { + type_arr[current_start + i] = ty.as_bytes()[i]; + i += 1; + } + i = 0; + if !is_null { + while i < BANG.as_bytes().len() { + type_arr[current_end - BANG.as_bytes().len() + i + 1] = BANG.as_bytes()[i]; + i += 1; + } + } + + type_arr + } + + const TYPE_ARR: [u8; RES_LEN] = format_type_arr(); + // SAFETY: This is safe, as `TYPE_ARR` was formatted from `Type`, `[`, `]` and `!`. + const TYPE_FORMATTED: &str = unsafe { ::std::mem::transmute(TYPE_ARR.as_slice()) }; + TYPE_FORMATTED + }}; +} diff --git a/juniper/src/types/scalars.rs b/juniper/src/types/scalars.rs index 5d84bd6f8..3fd3fcf18 100644 --- a/juniper/src/types/scalars.rs +++ b/juniper/src/types/scalars.rs @@ -202,16 +202,17 @@ where }) } -impl crate::macros::helper::WrappedType for str { +impl crate::macros::reflection::WrappedType for str { const VALUE: u128 = 1; } -impl crate::macros::helper::BaseType for str { +impl crate::macros::reflection::BaseType for str { const NAME: &'static str = "String"; } -impl crate::macros::helper::BaseSubTypes for str { - const NAMES: &'static [&'static str] = &[>::NAME]; +impl crate::macros::reflection::BaseSubTypes for str { + const NAMES: &'static [&'static str] = + &[>::NAME]; } impl GraphQLType for str diff --git a/juniper_codegen/src/common/field/mod.rs b/juniper_codegen/src/common/field/mod.rs index 385ed22d4..1f89dd296 100644 --- a/juniper_codegen/src/common/field/mod.rs +++ b/juniper_codegen/src/common/field/mod.rs @@ -464,8 +464,8 @@ impl Definition { Some(quote! {( #name, - <#ty as ::juniper::macros::helper::BaseType<#scalar>>::NAME, - <#ty as ::juniper::macros::helper::WrappedType<#scalar>>::VALUE, + <#ty as ::juniper::macros::reflection::BaseType<#scalar>>::NAME, + <#ty as ::juniper::macros::reflection::WrappedType<#scalar>>::VALUE, )}) } MethodArgument::Executor | MethodArgument::Context(_) => None, @@ -475,24 +475,24 @@ impl Definition { quote! { #[allow(deprecated, non_snake_case)] #[automatically_derived] - impl #impl_generics ::juniper::macros::helper::FieldMeta< + impl #impl_generics ::juniper::macros::reflection::FieldMeta< #scalar, - { ::juniper::macros::helper::fnv1a128(#name) } + { ::juniper::macros::reflection::fnv1a128(#name) } > for #impl_ty #where_clause { type Context = #context; type TypeInfo = (); - const TYPE: ::juniper::macros::helper::Type = - <#ty as ::juniper::macros::helper::BaseType<#scalar>>::NAME; - const SUB_TYPES: ::juniper::macros::helper::Types = - <#ty as ::juniper::macros::helper::BaseSubTypes<#scalar>>::NAMES; - const WRAPPED_VALUE: juniper::macros::helper::WrappedValue = - <#ty as ::juniper::macros::helper::WrappedType<#scalar>>::VALUE; + const TYPE: ::juniper::macros::reflection::Type = + <#ty as ::juniper::macros::reflection::BaseType<#scalar>>::NAME; + const SUB_TYPES: ::juniper::macros::reflection::Types = + <#ty as ::juniper::macros::reflection::BaseSubTypes<#scalar>>::NAMES; + const WRAPPED_VALUE: juniper::macros::reflection::WrappedValue = + <#ty as ::juniper::macros::reflection::WrappedType<#scalar>>::VALUE; const ARGUMENTS: &'static [( - ::juniper::macros::helper::Name, - ::juniper::macros::helper::Type, - ::juniper::macros::helper::WrappedValue, + ::juniper::macros::reflection::Name, + ::juniper::macros::reflection::Type, + ::juniper::macros::reflection::WrappedValue, )] = &[#(#arguments,)*]; } } @@ -575,9 +575,9 @@ impl Definition { Some(quote! { #[allow(deprecated, non_snake_case)] #[automatically_derived] - impl #impl_generics ::juniper::macros::helper::#trait_name< + impl #impl_generics ::juniper::macros::reflection::#trait_name< #scalar, - { ::juniper::macros::helper::fnv1a128(#name) } + { ::juniper::macros::reflection::fnv1a128(#name) } > for #impl_ty #where_clause { diff --git a/juniper_codegen/src/derive_scalar_value.rs b/juniper_codegen/src/derive_scalar_value.rs index 724a616d8..9e2698d74 100644 --- a/juniper_codegen/src/derive_scalar_value.rs +++ b/juniper_codegen/src/derive_scalar_value.rs @@ -238,20 +238,20 @@ fn impl_scalar_struct( where #scalar: ::juniper::ScalarValue, { } - impl#impl_generics ::juniper::macros::helper::BaseType<#scalar> for #ident + impl#impl_generics ::juniper::macros::reflection::BaseType<#scalar> for #ident where #scalar: ::juniper::ScalarValue, { const NAME: &'static str = #name; } - impl#impl_generics ::juniper::macros::helper::BaseSubTypes<#scalar> for #ident + impl#impl_generics ::juniper::macros::reflection::BaseSubTypes<#scalar> for #ident where #scalar: ::juniper::ScalarValue, { const NAMES: &'static [&'static str] = - &[>::NAME]; + &[>::NAME]; } - impl#impl_generics ::juniper::macros::helper::WrappedType<#scalar> for #ident + impl#impl_generics ::juniper::macros::reflection::WrappedType<#scalar> for #ident where #scalar: ::juniper::ScalarValue, { const VALUE: u128 = 1; diff --git a/juniper_codegen/src/graphql_interface/mod.rs b/juniper_codegen/src/graphql_interface/mod.rs index 2671b0ca7..c5c7aaca5 100644 --- a/juniper_codegen/src/graphql_interface/mod.rs +++ b/juniper_codegen/src/graphql_interface/mod.rs @@ -726,25 +726,25 @@ impl Definition { let implementors = self.implementers.iter().map(|i| &i.ty); quote! { - impl #impl_generics ::juniper::macros::helper::BaseType<#scalar> for #ty + impl #impl_generics ::juniper::macros::reflection::BaseType<#scalar> for #ty #where_clause { - const NAME: ::juniper::macros::helper::Type = #name; + const NAME: ::juniper::macros::reflection::Type = #name; } - impl #impl_generics ::juniper::macros::helper::BaseSubTypes<#scalar> for #ty + impl #impl_generics ::juniper::macros::reflection::BaseSubTypes<#scalar> for #ty #where_clause { - const NAMES: ::juniper::macros::helper::Types = &[ - >::NAME, - #(<#implementors as ::juniper::macros::helper::BaseType<#scalar>>::NAME),* + const NAMES: ::juniper::macros::reflection::Types = &[ + >::NAME, + #(<#implementors as ::juniper::macros::reflection::BaseType<#scalar>>::NAME),* ]; } - impl #impl_generics ::juniper::macros::helper::WrappedType<#scalar> for #ty + impl #impl_generics ::juniper::macros::reflection::WrappedType<#scalar> for #ty #where_clause { - const VALUE: ::juniper::macros::helper::WrappedValue = 1; + const VALUE: ::juniper::macros::reflection::WrappedValue = 1; } } } diff --git a/juniper_codegen/src/graphql_interface/new.rs b/juniper_codegen/src/graphql_interface/new.rs index d58917b5a..0b21cec40 100644 --- a/juniper_codegen/src/graphql_interface/new.rs +++ b/juniper_codegen/src/graphql_interface/new.rs @@ -577,9 +577,9 @@ impl Definition { let name = &f.name; quote! { #name => { - ::juniper::macros::helper::Field::< + ::juniper::macros::reflection::Field::< #scalar, - { ::juniper::macros::helper::fnv1a128(#name) } + { ::juniper::macros::reflection::fnv1a128(#name) } >::call(self, info, args, executor) } } @@ -671,9 +671,9 @@ impl Definition { let name = &f.name; quote! { #name => { - ::juniper::macros::helper::AsyncField::< + ::juniper::macros::reflection::AsyncField::< #scalar, - { ::juniper::macros::helper::fnv1a128(#name) } + { ::juniper::macros::reflection::fnv1a128(#name) } >::call(self, info, args, executor) } } @@ -718,9 +718,9 @@ impl Definition { /// Returns generated code implementing [`BaseType`], [`BaseSubTypes`] and /// [`WrappedType`] traits for this [GraphQL interface][1]. /// - /// [`BaseSubTypes`]: juniper::macros::helper::BaseSubTypes - /// [`BaseType`]: juniper::macros::helper::BaseType - /// [`WrappedType`]: juniper::macros::helper::WrappedType + /// [`BaseSubTypes`]: juniper::macros::reflection::BaseSubTypes + /// [`BaseType`]: juniper::macros::reflection::BaseType + /// [`WrappedType`]: juniper::macros::reflection::WrappedType /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces #[must_use] pub(crate) fn impl_traits_for_reflection(&self) -> TokenStream { @@ -736,38 +736,38 @@ impl Definition { quote! { #[automatically_derived] - impl#impl_generics ::juniper::macros::helper::BaseType<#scalar> + impl#impl_generics ::juniper::macros::reflection::BaseType<#scalar> for #ty#ty_generics #where_clause { - const NAME: ::juniper::macros::helper::Type = #name; + const NAME: ::juniper::macros::reflection::Type = #name; } #[automatically_derived] - impl#impl_generics ::juniper::macros::helper::BaseSubTypes<#scalar> + impl#impl_generics ::juniper::macros::reflection::BaseSubTypes<#scalar> for #ty#ty_generics #where_clause { - const NAMES: ::juniper::macros::helper::Types = &[ - >::NAME, - #(<#implementers as ::juniper::macros::helper::BaseType<#scalar>>::NAME),* + const NAMES: ::juniper::macros::reflection::Types = &[ + >::NAME, + #(<#implementers as ::juniper::macros::reflection::BaseType<#scalar>>::NAME),* ]; } #[automatically_derived] - impl#impl_generics ::juniper::macros::helper::WrappedType<#scalar> + impl#impl_generics ::juniper::macros::reflection::WrappedType<#scalar> for #ty#ty_generics #where_clause { - const VALUE: ::juniper::macros::helper::WrappedValue = 1; + const VALUE: ::juniper::macros::reflection::WrappedValue = 1; } #[automatically_derived] - impl#impl_generics ::juniper::macros::helper::Fields<#scalar> + impl#impl_generics ::juniper::macros::reflection::Fields<#scalar> for #ty#ty_generics #where_clause { - const NAMES: ::juniper::macros::helper::Names = &[#(#fields),*]; + const NAMES: ::juniper::macros::reflection::Names = &[#(#fields),*]; } } } @@ -775,7 +775,7 @@ impl Definition { /// Returns generated code implementing [`FieldMeta`] for this /// [GraphQL interface][1]. /// - /// [`FieldMeta`]: juniper::macros::helper::FieldMeta + /// [`FieldMeta`]: juniper::macros::reflection::FieldMeta /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces fn impl_field_meta(&self) -> TokenStream { let ty = &self.enum_alias_ident; @@ -805,26 +805,26 @@ impl Definition { quote! { #[allow(non_snake_case)] - impl#impl_generics ::juniper::macros::helper::FieldMeta< + impl#impl_generics ::juniper::macros::reflection::FieldMeta< #scalar, - { ::juniper::macros::helper::fnv1a128(#field_name) } + { ::juniper::macros::reflection::fnv1a128(#field_name) } > for #ty#ty_generics #where_clause { type Context = #context; type TypeInfo = (); - const TYPE: ::juniper::macros::helper::Type = - <#return_ty as ::juniper::macros::helper::BaseType<#scalar>>::NAME; - const SUB_TYPES: ::juniper::macros::helper::Types = - <#return_ty as ::juniper::macros::helper::BaseSubTypes<#scalar>>::NAMES; - const WRAPPED_VALUE: ::juniper::macros::helper::WrappedValue = - <#return_ty as ::juniper::macros::helper::WrappedType<#scalar>>::VALUE; + const TYPE: ::juniper::macros::reflection::Type = + <#return_ty as ::juniper::macros::reflection::BaseType<#scalar>>::NAME; + const SUB_TYPES: ::juniper::macros::reflection::Types = + <#return_ty as ::juniper::macros::reflection::BaseSubTypes<#scalar>>::NAMES; + const WRAPPED_VALUE: ::juniper::macros::reflection::WrappedValue = + <#return_ty as ::juniper::macros::reflection::WrappedType<#scalar>>::VALUE; const ARGUMENTS: &'static [( - ::juniper::macros::helper::Name, - ::juniper::macros::helper::Type, - ::juniper::macros::helper::WrappedValue, + ::juniper::macros::reflection::Name, + ::juniper::macros::reflection::Type, + ::juniper::macros::reflection::WrappedValue, )] = &[#(( #args_names, - <#args_tys as ::juniper::macros::helper::BaseType<#scalar>>::NAME, - <#args_tys as ::juniper::macros::helper::WrappedType<#scalar>>::VALUE, + <#args_tys as ::juniper::macros::reflection::BaseType<#scalar>>::NAME, + <#args_tys as ::juniper::macros::reflection::WrappedType<#scalar>>::VALUE, )),*]; } } @@ -835,8 +835,8 @@ impl Definition { /// Returns generated code implementing [`Field`] or [`AsyncField`] trait /// for this [GraphQL interface][1]. /// - /// [`AsyncField`]: juniper::macros::helper::AsyncField - /// [`Field`]: juniper::macros::helper::Field + /// [`AsyncField`]: juniper::macros::reflection::AsyncField + /// [`Field`]: juniper::macros::reflection::Field /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces fn impl_field(&self) -> TokenStream { let ty = &self.enum_alias_ident; @@ -875,9 +875,9 @@ impl Definition { Some(quote! { #[allow(non_snake_case)] - impl#impl_generics ::juniper::macros::helper::Field< + impl#impl_generics ::juniper::macros::reflection::Field< #scalar, - { ::juniper::macros::helper::fnv1a128(#field_name) } + { ::juniper::macros::reflection::fnv1a128(#field_name) } > for #ty#ty_generics #where_clause { fn call( &self, @@ -894,9 +894,9 @@ impl Definition { #field_name, ); - <_ as ::juniper::macros::helper::Field< + <_ as ::juniper::macros::reflection::Field< #scalar, - { ::juniper::macros::helper::fnv1a128(#field_name) }, + { ::juniper::macros::reflection::fnv1a128(#field_name) }, >>::call(v, info, args, executor) })* #unreachable_arm @@ -911,7 +911,7 @@ impl Definition { /// Returns generated code implementing [`AsyncField`] trait for this /// [GraphQL interface][1]. /// - /// [`AsyncField`]: juniper::macros::helper::AsyncField + /// [`AsyncField`]: juniper::macros::reflection::AsyncField /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces fn impl_async_field(&self) -> TokenStream { let ty = &self.enum_alias_ident; @@ -946,9 +946,9 @@ impl Definition { quote! { #[allow(non_snake_case)] - impl#impl_generics ::juniper::macros::helper::AsyncField< + impl#impl_generics ::juniper::macros::reflection::AsyncField< #scalar, - { ::juniper::macros::helper::fnv1a128(#field_name) } + { ::juniper::macros::reflection::fnv1a128(#field_name) } > for #ty#ty_generics #where_clause { fn call<'b>( &'b self, @@ -965,9 +965,9 @@ impl Definition { #field_name, ); - <_ as ::juniper::macros::helper::AsyncField< + <_ as ::juniper::macros::reflection::AsyncField< #scalar, - { ::juniper::macros::helper::fnv1a128(#field_name) }, + { ::juniper::macros::reflection::fnv1a128(#field_name) }, >>::call(v, info, args, executor) })* #unreachable_arm diff --git a/juniper_codegen/src/graphql_object/mod.rs b/juniper_codegen/src/graphql_object/mod.rs index ac838bc06..c1caa2e17 100644 --- a/juniper_codegen/src/graphql_object/mod.rs +++ b/juniper_codegen/src/graphql_object/mod.rs @@ -372,36 +372,36 @@ impl Definition { quote! { #[automatically_derived] - impl#impl_generics ::juniper::macros::helper::BaseType<#scalar> + impl#impl_generics ::juniper::macros::reflection::BaseType<#scalar> for #ty #where_clause { - const NAME: ::juniper::macros::helper::Type = #name; + const NAME: ::juniper::macros::reflection::Type = #name; } #[automatically_derived] - impl#impl_generics ::juniper::macros::helper::BaseSubTypes<#scalar> + impl#impl_generics ::juniper::macros::reflection::BaseSubTypes<#scalar> for #ty #where_clause { - const NAMES: ::juniper::macros::helper::Types = - &[>::NAME]; + const NAMES: ::juniper::macros::reflection::Types = + &[>::NAME]; } #[automatically_derived] - impl#impl_generics ::juniper::macros::helper::WrappedType<#scalar> + impl#impl_generics ::juniper::macros::reflection::WrappedType<#scalar> for #ty #where_clause { - const VALUE: ::juniper::macros::helper::WrappedValue = 1; + const VALUE: ::juniper::macros::reflection::WrappedValue = 1; } #[automatically_derived] - impl#impl_generics ::juniper::macros::helper::Fields<#scalar> + impl#impl_generics ::juniper::macros::reflection::Fields<#scalar> for #ty #where_clause { - const NAMES: ::juniper::macros::helper::Names = &[#(#fields),*]; + const NAMES: ::juniper::macros::reflection::Names = &[#(#fields),*]; } } } @@ -541,9 +541,9 @@ impl Definition { let name = &f.name; quote! { #name => { - ::juniper::macros::helper::Field::< + ::juniper::macros::reflection::Field::< #scalar, - { ::juniper::macros::helper::fnv1a128(#name) } + { ::juniper::macros::reflection::fnv1a128(#name) } >::call(self, info, args, executor) } } @@ -637,9 +637,9 @@ impl Definition { let name = &f.name; quote! { #name => { - ::juniper::macros::helper::AsyncField::< + ::juniper::macros::reflection::AsyncField::< #scalar, - { ::juniper::macros::helper::fnv1a128(#name) } + { ::juniper::macros::reflection::fnv1a128(#name) } >::call(self, info, args, executor) } } diff --git a/juniper_codegen/src/graphql_union/mod.rs b/juniper_codegen/src/graphql_union/mod.rs index 952889782..14048cd0c 100644 --- a/juniper_codegen/src/graphql_union/mod.rs +++ b/juniper_codegen/src/graphql_union/mod.rs @@ -621,30 +621,30 @@ impl Definition { quote! { #[automatically_derived] - impl#impl_generics ::juniper::macros::helper::BaseType<#scalar> + impl#impl_generics ::juniper::macros::reflection::BaseType<#scalar> for #ty #where_clause { - const NAME: ::juniper::macros::helper::Type = #name; + const NAME: ::juniper::macros::reflection::Type = #name; } #[automatically_derived] - impl#impl_generics ::juniper::macros::helper::BaseSubTypes<#scalar> + impl#impl_generics ::juniper::macros::reflection::BaseSubTypes<#scalar> for #ty #where_clause { - const NAMES: ::juniper::macros::helper::Types = &[ - >::NAME, - #(<#variants as ::juniper::macros::helper::BaseType<#scalar>>::NAME),* + const NAMES: ::juniper::macros::reflection::Types = &[ + >::NAME, + #(<#variants as ::juniper::macros::reflection::BaseType<#scalar>>::NAME),* ]; } #[automatically_derived] - impl#impl_generics ::juniper::macros::helper::WrappedType<#scalar> + impl#impl_generics ::juniper::macros::reflection::WrappedType<#scalar> for #ty #where_clause { - const VALUE: ::juniper::macros::helper::WrappedValue = 1; + const VALUE: ::juniper::macros::reflection::WrappedValue = 1; } } } diff --git a/juniper_codegen/src/impl_scalar.rs b/juniper_codegen/src/impl_scalar.rs index 681620eae..b28aabedc 100644 --- a/juniper_codegen/src/impl_scalar.rs +++ b/juniper_codegen/src/impl_scalar.rs @@ -328,20 +328,20 @@ pub fn build_scalar( } } - impl#generic_type_decl ::juniper::macros::helper::BaseType<#generic_type> for #impl_for_type + impl#generic_type_decl ::juniper::macros::reflection::BaseType<#generic_type> for #impl_for_type #generic_type_bound { const NAME: &'static str = #name; } - impl#generic_type_decl ::juniper::macros::helper::BaseSubTypes<#generic_type> for #impl_for_type + impl#generic_type_decl ::juniper::macros::reflection::BaseSubTypes<#generic_type> for #impl_for_type #generic_type_bound { const NAMES: &'static [&'static str] = - &[>::NAME]; + &[>::NAME]; } - impl#generic_type_decl ::juniper::macros::helper::WrappedType<#generic_type> for #impl_for_type + impl#generic_type_decl ::juniper::macros::reflection::WrappedType<#generic_type> for #impl_for_type #generic_type_bound { const VALUE: u128 = 1; diff --git a/juniper_codegen/src/util/mod.rs b/juniper_codegen/src/util/mod.rs index d88eeee3d..1a301717c 100644 --- a/juniper_codegen/src/util/mod.rs +++ b/juniper_codegen/src/util/mod.rs @@ -907,20 +907,20 @@ impl GraphQLTypeDefiniton { } } - impl#impl_generics ::juniper::macros::helper::BaseType<#scalar> for #ty + impl#impl_generics ::juniper::macros::reflection::BaseType<#scalar> for #ty #where_clause { const NAME: &'static str = #name; } - impl#impl_generics ::juniper::macros::helper::BaseSubTypes<#scalar> for #ty + impl#impl_generics ::juniper::macros::reflection::BaseSubTypes<#scalar> for #ty #where_clause { const NAMES: &'static [&'static str] = - &[>::NAME]; + &[>::NAME]; } - impl#impl_generics ::juniper::macros::helper::WrappedType<#scalar> for #ty + impl#impl_generics ::juniper::macros::reflection::WrappedType<#scalar> for #ty #where_clause { const VALUE: u128 = 1; @@ -1173,22 +1173,22 @@ impl GraphQLTypeDefiniton { } } - impl#impl_generics ::juniper::macros::helper::BaseType<#scalar> + impl#impl_generics ::juniper::macros::reflection::BaseType<#scalar> for #ty #type_generics_tokens #where_clause { const NAME: &'static str = #name; } - impl#impl_generics ::juniper::macros::helper::BaseSubTypes<#scalar> + impl#impl_generics ::juniper::macros::reflection::BaseSubTypes<#scalar> for #ty #type_generics_tokens #where_clause { const NAMES: &'static [&'static str] = - &[>::NAME]; + &[>::NAME]; } - impl#impl_generics ::juniper::macros::helper::WrappedType<#scalar> + impl#impl_generics ::juniper::macros::reflection::WrappedType<#scalar> for #ty #type_generics_tokens #where_clause { From 3ad83990adc1e0448ac37a455d98e4f9204dba1f Mon Sep 17 00:00:00 2001 From: ilslv Date: Tue, 4 Jan 2022 11:39:35 +0300 Subject: [PATCH 31/53] WIP (Docs vol. 1) --- .../src/codegen/interface_attr.rs | 2 +- juniper/src/macros/reflection.rs | 260 +++++++++++++++--- 2 files changed, 220 insertions(+), 42 deletions(-) diff --git a/integration_tests/juniper_tests/src/codegen/interface_attr.rs b/integration_tests/juniper_tests/src/codegen/interface_attr.rs index 2ea00b554..8dbc45920 100644 --- a/integration_tests/juniper_tests/src/codegen/interface_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/interface_attr.rs @@ -1,7 +1,7 @@ //! Tests for `#[graphql_interface]` macro. use juniper::{ - execute, graphql_interface, graphql_interface_new, graphql_object, graphql_value, graphql_vars, + execute, graphql_interface_new, graphql_object, graphql_value, graphql_vars, DefaultScalarValue, EmptyMutation, EmptySubscription, Executor, FieldError, FieldResult, GraphQLInputObject, GraphQLObject, GraphQLType, IntoFieldError, RootNode, ScalarValue, }; diff --git a/juniper/src/macros/reflection.rs b/juniper/src/macros/reflection.rs index a9a12109c..673f01430 100644 --- a/juniper/src/macros/reflection.rs +++ b/juniper/src/macros/reflection.rs @@ -4,17 +4,66 @@ use std::{rc::Rc, sync::Arc}; use futures::future::BoxFuture; -use crate::{Arguments, DefaultScalarValue, ExecutionResult, Executor, Nullable, ScalarValue}; +use crate::{ + Arguments as FieldArguments, DefaultScalarValue, ExecutionResult, Executor, GraphQLValue, + Nullable, ScalarValue, +}; +/// Type alias for GraphQL [`object`][1], [`scalar`][2] or [`interface`][3] type +/// name. +/// +/// [1]: https://spec.graphql.org/October2021/#sec-Objects +/// [2]: https://spec.graphql.org/October2021/#sec-Scalars +/// [3]: https://spec.graphql.org/October2021/#sec-Interfaces pub type Type = &'static str; + +/// Type alias for slice of [`Type`]s. See [`BaseType`] for more info. pub type Types = &'static [Type]; + +/// Type alias for GraphQL [`object`][1] or [`interface`][2] +/// [`field argument`][3] name. +/// +/// [1]: https://spec.graphql.org/October2021/#sec-Objects +/// [2]: https://spec.graphql.org/October2021/#sec-Interfaces +/// [3]: https://spec.graphql.org/October2021/#sec-Language.Arguments pub type Name = &'static str; + +/// Type alias for slice of [`Name`]s. pub type Names = &'static [Name]; -pub type FieldName = u128; + +/// Type alias for value of [`WrappedType`]. pub type WrappedValue = u128; -/// TODO -pub trait BaseType { +/// Type alias for [`Field argument`][1]s [`Name`], [`Type`] and +/// [`WrappedValue`]. +/// +/// [1]: https://spec.graphql.org/October2021/#sec-Language.Arguments +pub type Argument = (Name, Type, WrappedValue); + +/// Type alias for [`Field argument`][1]s [`Name`], [`Type`] and +/// [`WrappedValue`]. +/// +/// [1]: https://spec.graphql.org/October2021/#sec-Language.Arguments +pub type Arguments = &'static [(Name, Type, WrappedValue)]; + +/// Type alias for constantly hashed [`Name`] for usage in const generics. +pub type FieldName = u128; + +/// GraphQL [`object`][1], [`scalar`][2] or [`interface`][3] [`Type`] name. +/// This trait is transparent to the [`Option`], [`Vec`] and other containers, +/// so to fully represent GraphQL [`object`][1] we additionally use +/// [`WrappedType`]. +/// +/// [1]: https://spec.graphql.org/October2021/#sec-Objects +/// [2]: https://spec.graphql.org/October2021/#sec-Scalars +/// [3]: https://spec.graphql.org/October2021/#sec-Interfaces +pub trait BaseType { + /// [`Type`] of the GraphQL [`object`][1], [`scalar`][2] or + /// [`interface`][3]. + /// + /// [1]: https://spec.graphql.org/October2021/#sec-Objects + /// [2]: https://spec.graphql.org/October2021/#sec-Scalars + /// [3]: https://spec.graphql.org/October2021/#sec-Interfaces const NAME: Type; } @@ -22,8 +71,11 @@ impl<'a, S, T: BaseType + ?Sized> BaseType for &'a T { const NAME: Type = T::NAME; } -// TODO: Reconsider -impl, Ctx> BaseType for (Ctx, T) { +impl<'ctx, S, T> BaseType for (&'ctx T::Context, T) +where + S: ScalarValue, + T: BaseType + GraphQLValue, +{ const NAME: Type = T::NAME; } @@ -35,7 +87,6 @@ impl> BaseType for Nullable { const NAME: Type = T::NAME; } -// TODO: Should Err be trait bounded somehow? impl, E> BaseType for Result { const NAME: Type = T::NAME; } @@ -64,8 +115,16 @@ impl + ?Sized> BaseType for Rc { const NAME: Type = T::NAME; } -/// TODO -pub trait BaseSubTypes { +/// GraphQL [`object`][1] [`sub-types`][2]. This trait is transparent to the +/// [`Option`], [`Vec`] and other containers. +/// +/// [1]: https://spec.graphql.org/October2021/#sec-Objects +/// [2]: https://spec.graphql.org/October2021/#sel-JAHZhCHCDEJDAAAEEFDBtzC +pub trait BaseSubTypes { + /// [`Types`] for the GraphQL [`object`][1]s [`sub-types`][2]. + /// + /// [1]: https://spec.graphql.org/October2021/#sec-Objects + /// [2]: https://spec.graphql.org/October2021/#sel-JAHZhCHCDEJDAAAEEFDBtzC const NAMES: Types; } @@ -73,8 +132,11 @@ impl<'a, S, T: BaseSubTypes + ?Sized> BaseSubTypes for &'a T { const NAMES: Types = T::NAMES; } -// TODO: Reconsider -impl, Ctx> BaseSubTypes for (Ctx, T) { +impl<'ctx, S, T> BaseSubTypes for (&'ctx T::Context, T) +where + S: ScalarValue, + T: BaseSubTypes + GraphQLValue, +{ const NAMES: Types = T::NAMES; } @@ -86,7 +148,6 @@ impl> BaseSubTypes for Nullable { const NAMES: Types = T::NAMES; } -// TODO: Should Err be trait bounded somehow? impl, E> BaseSubTypes for Result { const NAMES: Types = T::NAMES; } @@ -115,20 +176,52 @@ impl + ?Sized> BaseSubTypes for Rc { const NAMES: Types = T::NAMES; } -/// TODO -pub trait WrappedType { - /// NonNull - 1 - /// Nullable - 2 - /// List - 3 - /// - /// `[[Int]!] - >>> as WrappedType>::N = 12332` - const VALUE: u128; +/// To fully represent GraphQL [`object`][1] it's not enough to use [`Type`], +/// because of the [`wrapping types`][2]. To work around this we use +/// [`WrappedValue`] which is represented with [`u128`]. +/// +/// - In base case of non-nullable [`object`] [`VALUE`] is `1`. +/// - To represent nullability we "append" `2` to the [`VALUE`], so +/// [`Option`]`<`[`object`][1]`>` has [`VALUE`] of `12`. +/// - To represent list we "append" `3` to the [`VALUE`], so +/// [`Vec`]`<`[`object`][1]`>` has [`VALUE`] of `13`. +/// +/// This approach allows us to uniquely represent any GraphQL [`object`] with +/// combination of [`Type`] and [`WrappedValue`] and even constantly format it +/// with [`format_type`] macro. +/// +/// # Examples +/// +/// ```rust +/// # use juniper::{macros::reflection::{WrappedType, BaseType, WrappedValue, Type}, DefaultScalarValue, format_type}; +/// # +/// assert_eq!( as WrappedType>::VALUE, 12); +/// assert_eq!( as WrappedType>::VALUE, 13); +/// assert_eq!(> as WrappedType>::VALUE, 123); +/// assert_eq!(> as WrappedType>::VALUE, 132); +/// assert_eq!(>> as WrappedType>::VALUE, 1232); +/// +/// const TYPE_STRING: Type = >> as BaseType>::NAME; +/// const WRAP_VAL_STRING: WrappedValue = >> as WrappedType>::VALUE; +/// assert_eq!(format_type!(TYPE_STRING, WRAP_VAL_STRING), "[String]"); +/// +/// const TYPE_STR: Type = >> as BaseType>::NAME; +/// const WRAP_VAL_STR: WrappedValue = >> as WrappedType>::VALUE; +/// assert_eq!(format_type!(TYPE_STRING, WRAP_VAL_STRING), "[String]"); +/// ``` +/// +/// [1]: https://spec.graphql.org/October2021/#sec-Objects +/// [2]: https://spec.graphql.org/October2021/#sec-Wrapping-Types +/// [`VALUE`]: Self::VALUE +pub trait WrappedType { + /// [`WrappedValue`] of this type. + const VALUE: WrappedValue; } -impl<'a, S, T: WrappedType> WrappedType for (&'a T::Context, T) +impl<'ctx, S, T: WrappedType> WrappedType for (&'ctx T::Context, T) where S: ScalarValue, - T: crate::GraphQLValue, + T: GraphQLValue, { const VALUE: u128 = T::VALUE; } @@ -141,8 +234,6 @@ impl> WrappedType for Nullable { const VALUE: u128 = T::VALUE * 10 + 2; } -// TODO: Should Err be trait bounded somehow? -// And should `VALUE` be `T::VALUE` or `T::VALUE * 10 + 2`? impl, E> WrappedType for Result { const VALUE: u128 = T::VALUE; } @@ -175,33 +266,103 @@ impl + ?Sized> WrappedType for Rc { const VALUE: u128 = T::VALUE; } -pub trait Fields { +/// GraphQL [`object`][1] or [`interface`][2] [`Field arguments`][3] [`Names`]. +/// +/// [1]: https://spec.graphql.org/October2021/#sec-Objects +/// [2]: https://spec.graphql.org/October2021/#sec-Interfaces +/// [3]: https://spec.graphql.org/October2021/#sec-Language.Arguments +pub trait Fields { + /// [`Names`] of the GraphQL [`object`][1] or [`interface`][2] + /// [`Field arguments`][3]. + /// + /// [1]: https://spec.graphql.org/October2021/#sec-Objects + /// [2]: https://spec.graphql.org/October2021/#sec-Interfaces + /// [3]: https://spec.graphql.org/October2021/#sec-Language.Arguments const NAMES: Names; } +/// Stores meta information of GraphQL [`Fields`][1]: +/// - [`Context`] and [`TypeInfo`]. +/// - Return type's [`TYPE`], [`SUB_TYPES`] and [`WRAPPED_VALUE`]. +/// - [`ARGUMENTS`]. +/// +/// [1]: https://spec.graphql.org/October2021/#sec-Language.Fields +/// [`Context`]: Self::Context +/// [`TypeInfo`]: Self::TypeInfo +/// [`TYPE`]: Self::TYPE +/// [`SUB_TYPES`]: Self::SUB_TYPES +/// [`WRAPPED_VALUE`]: Self::WRAPPED_VALUE +/// [`ARGUMENTS`]: Self::ARGUMENTS pub trait FieldMeta { + /// [`GraphQLValue::Context`] of this [`Field`][1]. + /// + /// [1]: https://spec.graphql.org/October2021/#sec-Language.Fields type Context; + + /// [`GraphQLValue::TypeInfo`] of this [`Field`][1]. + /// + /// [1]: https://spec.graphql.org/October2021/#sec-Language.Fields type TypeInfo; + + /// [`Types`] of [`Field`][1]'s return type. + /// + /// [1]: https://spec.graphql.org/October2021/#sec-Language.Fields const TYPE: Type; + + /// Sub-[`Types`] of [`Field`][1]'s return type. + /// + /// [1]: https://spec.graphql.org/October2021/#sec-Language.Fields const SUB_TYPES: Types; + + /// [`WrappedValue`] of [`Field`][1]'s return type. + /// + /// [1]: https://spec.graphql.org/October2021/#sec-Language.Fields const WRAPPED_VALUE: WrappedValue; - const ARGUMENTS: &'static [(Name, Type, WrappedValue)]; + + /// [`Field`][1]'s [`Arguments`]. + /// + /// [1]: https://spec.graphql.org/October2021/#sec-Language.Fields + const ARGUMENTS: Arguments; } +/// Synchronous field of a GraphQL [`object`][1] or [`interface`][2]. +/// +/// [1]: https://spec.graphql.org/October2021/#sec-Objects +/// [2]: https://spec.graphql.org/October2021/#sec-Interfaces pub trait Field: FieldMeta { + /// Resolves the [`Value`] of this synchronous [`Field`]. + /// + /// The `arguments` object contains all the specified arguments, with + /// default values being substituted for the ones not provided by the query. + /// + /// The `executor` can be used to drive selections into sub-[`objects`][1]. + /// + /// [1]: https://spec.graphql.org/October2021/#sec-Objects fn call( &self, info: &Self::TypeInfo, - args: &Arguments, + args: &FieldArguments, executor: &Executor, ) -> ExecutionResult; } +/// Asynchronous field of a GraphQL [`object`][1] or [`interface`][2]. +/// +/// [1]: https://spec.graphql.org/October2021/#sec-Objects +/// [2]: https://spec.graphql.org/October2021/#sec-Interfaces pub trait AsyncField: FieldMeta { + /// Resolves the [`Value`] of this asynchronous [`Field`]. + /// + /// The `arguments` object contains all the specified arguments, with + /// default values being substituted for the ones not provided by the query. + /// + /// The `executor` can be used to drive selections into sub-[`objects`][1]. + /// + /// [1]: https://spec.graphql.org/October2021/#sec-Objects fn call<'b>( &'b self, info: &'b Self::TypeInfo, - args: &'b Arguments, + args: &'b FieldArguments, executor: &'b Executor, ) -> BoxFuture<'b, ExecutionResult>; } @@ -225,6 +386,13 @@ pub const fn fnv1a128(str: Name) -> u128 { hash } +/// Compares strings in `const` context. +/// +/// As there is no `const impl Trait` and `l == r` calls [`Eq`], we have to +/// write custom comparison function. +/// +/// [`Eq`]: std::cmp::Eq +// TODO: Remove once `Eq` trait is allowed in `const` context. pub const fn str_eq(l: &str, r: &str) -> bool { let (l, r) = (l.as_bytes(), r.as_bytes()); @@ -243,7 +411,8 @@ pub const fn str_eq(l: &str, r: &str) -> bool { true } -pub const fn str_len_from_wrapped_val(ty: Type, v: WrappedValue) -> usize { +/// Length of the [`format_type`] macro result __in bytes__. +pub const fn type_len_with_wrapped_val(ty: Type, v: WrappedValue) -> usize { let mut len = ty.as_bytes().len() + "!".as_bytes().len(); // Type! let mut current_wrap_val = v; @@ -260,6 +429,13 @@ pub const fn str_len_from_wrapped_val(ty: Type, v: WrappedValue) -> usize { len } +/// Based on the [`WrappedValue`] checks whether GraphQL [`objects`][1] can be +/// subtypes. +/// +/// To fully determine sub-typing relation [`Type`] should be one of the +/// [`BaseSubTypes::NAMES`]. +/// +/// [1]: https://spec.graphql.org/October2021/#sec-Objects pub const fn can_be_subtype(ty: WrappedValue, subtype: WrappedValue) -> bool { let ty_current = ty % 10; let subtype_current = subtype % 10; @@ -277,7 +453,8 @@ pub const fn can_be_subtype(ty: WrappedValue, subtype: WrappedValue) -> bool { } } -pub const fn exists(val: Type, arr: Types) -> bool { +/// Checks whether `val` exists in `arr`. +pub const fn str_exists_in_arr(val: &str, arr: &[&str]) -> bool { let mut i = 0; while i < arr.len() { if str_eq(val, arr[i]) { @@ -327,32 +504,32 @@ macro_rules! assert_subtype { const BASE_RETURN_WRAPPED_VAL: $crate::macros::reflection::WrappedValue = <$base_ty as $crate::macros::reflection::FieldMeta< $scalar, - { $crate::hash!(FIELD_NAME, $base_ty, $scalar, ERR_PREFIX) }, + { $crate::checked_hash!(FIELD_NAME, $base_ty, $scalar, ERR_PREFIX) }, >>::WRAPPED_VALUE; const IMPL_RETURN_WRAPPED_VAL: $crate::macros::reflection::WrappedValue = <$impl_ty as $crate::macros::reflection::FieldMeta< $scalar, - { $crate::hash!(FIELD_NAME, $impl_ty, $scalar, ERR_PREFIX) }, + { $crate::checked_hash!(FIELD_NAME, $impl_ty, $scalar, ERR_PREFIX) }, >>::WRAPPED_VALUE; const BASE_RETURN_SUB_TYPES: $crate::macros::reflection::Types = <$base_ty as $crate::macros::reflection::FieldMeta< $scalar, - { $crate::hash!(FIELD_NAME, $base_ty, $scalar, ERR_PREFIX) }, + { $crate::checked_hash!(FIELD_NAME, $base_ty, $scalar, ERR_PREFIX) }, >>::SUB_TYPES; const BASE_RETURN_TY: $crate::macros::reflection::Type = <$base_ty as $crate::macros::reflection::FieldMeta< $scalar, - { $crate::hash!(FIELD_NAME, $base_ty, $scalar, ERR_PREFIX) }, + { $crate::checked_hash!(FIELD_NAME, $base_ty, $scalar, ERR_PREFIX) }, >>::TYPE; const IMPL_RETURN_TY: $crate::macros::reflection::Type = <$impl_ty as $crate::macros::reflection::FieldMeta< $scalar, - { $crate::hash!(FIELD_NAME, $impl_ty, $scalar, ERR_PREFIX) }, + { $crate::checked_hash!(FIELD_NAME, $impl_ty, $scalar, ERR_PREFIX) }, >>::TYPE; - let is_subtype = $crate::macros::reflection::exists(IMPL_RETURN_TY, BASE_RETURN_SUB_TYPES) + let is_subtype = $crate::macros::reflection::str_exists_in_arr(IMPL_RETURN_TY, BASE_RETURN_SUB_TYPES) && $crate::macros::reflection::can_be_subtype(BASE_RETURN_WRAPPED_VAL, IMPL_RETURN_WRAPPED_VAL); if !is_subtype { const MSG: &str = $crate::const_concat!( @@ -401,11 +578,11 @@ macro_rules! assert_field_args { const FIELD_NAME: &str = $field_name; const BASE_ARGS: &[FullArg] = <$base_ty as $crate::macros::reflection::FieldMeta< $scalar, - { $crate::hash!(FIELD_NAME, $base_ty, $scalar, ERR_PREFIX) }, + { $crate::checked_hash!(FIELD_NAME, $base_ty, $scalar, ERR_PREFIX) }, >>::ARGUMENTS; const IMPL_ARGS: &[FullArg] = <$impl_ty as $crate::macros::reflection::FieldMeta< $scalar, - { $crate::hash!(FIELD_NAME, $impl_ty, $scalar, ERR_PREFIX) }, + { $crate::checked_hash!(FIELD_NAME, $impl_ty, $scalar, ERR_PREFIX) }, >>::ARGUMENTS; struct Error { @@ -576,9 +753,9 @@ macro_rules! const_concat { } #[macro_export] -macro_rules! hash { +macro_rules! checked_hash { ($field_name: expr, $impl_ty: ty, $scalar: ty $(, $prefix: expr)? $(,)?) => {{ - let exists = $crate::macros::reflection::exists( + let exists = $crate::macros::reflection::str_exists_in_arr( $field_name, <$impl_ty as $crate::macros::reflection::Fields<$scalar>>::NAMES, ); @@ -607,7 +784,8 @@ macro_rules! format_type { $crate::macros::reflection::Type, $crate::macros::reflection::WrappedValue, ) = ($ty, $wrapped_value); - const RES_LEN: usize = $crate::macros::reflection::str_len_from_wrapped_val(TYPE.0, TYPE.1); + const RES_LEN: usize = + $crate::macros::reflection::type_len_with_wrapped_val(TYPE.0, TYPE.1); const OPENING_BRACKET: &str = "["; const CLOSING_BRACKET: &str = "]"; From 463bbb8b6f2c9cec4c53bbed9b5b3ab9adc01f37 Mon Sep 17 00:00:00 2001 From: ilslv Date: Tue, 4 Jan 2022 12:23:56 +0300 Subject: [PATCH 32/53] WIP (Docs vol. 2) --- juniper/src/macros/reflection.rs | 113 ++++++++++++++++++++----------- 1 file changed, 75 insertions(+), 38 deletions(-) diff --git a/juniper/src/macros/reflection.rs b/juniper/src/macros/reflection.rs index 673f01430..61a92418c 100644 --- a/juniper/src/macros/reflection.rs +++ b/juniper/src/macros/reflection.rs @@ -5,8 +5,7 @@ use std::{rc::Rc, sync::Arc}; use futures::future::BoxFuture; use crate::{ - Arguments as FieldArguments, DefaultScalarValue, ExecutionResult, Executor, GraphQLValue, - Nullable, ScalarValue, + Arguments as FieldArguments, ExecutionResult, Executor, GraphQLValue, Nullable, ScalarValue, }; /// Type alias for GraphQL [`object`][1], [`scalar`][2] or [`interface`][3] type @@ -465,6 +464,11 @@ pub const fn str_exists_in_arr(val: &str, arr: &[&str]) -> bool { false } +/// Asserts validness of the [`Field`] [`Arguments`] and return [`Type`]. This +/// assertion is a combination of [`assert_subtype`] and [`assert_field_args`]. +/// See [spec][1] for more info. +/// +/// [1]: https://spec.graphql.org/October2021/#IsValidImplementation() #[macro_export] macro_rules! assert_field { ( @@ -478,6 +482,10 @@ macro_rules! assert_field { }; } +/// Asserts validness of the [`Field`]s return type. See [spec][1] for more +/// info. +/// +/// [1]: https://spec.graphql.org/October2021/#IsValidImplementationFieldType() #[macro_export] macro_rules! assert_subtype { ( @@ -501,6 +509,7 @@ macro_rules! assert_subtype { const FIELD_NAME: $crate::macros::reflection::Name = $field_name; + const BASE_RETURN_WRAPPED_VAL: $crate::macros::reflection::WrappedValue = <$base_ty as $crate::macros::reflection::FieldMeta< $scalar, @@ -512,12 +521,6 @@ macro_rules! assert_subtype { { $crate::checked_hash!(FIELD_NAME, $impl_ty, $scalar, ERR_PREFIX) }, >>::WRAPPED_VALUE; - const BASE_RETURN_SUB_TYPES: $crate::macros::reflection::Types = - <$base_ty as $crate::macros::reflection::FieldMeta< - $scalar, - { $crate::checked_hash!(FIELD_NAME, $base_ty, $scalar, ERR_PREFIX) }, - >>::SUB_TYPES; - const BASE_RETURN_TY: $crate::macros::reflection::Type = <$base_ty as $crate::macros::reflection::FieldMeta< $scalar, @@ -529,6 +532,12 @@ macro_rules! assert_subtype { { $crate::checked_hash!(FIELD_NAME, $impl_ty, $scalar, ERR_PREFIX) }, >>::TYPE; + const BASE_RETURN_SUB_TYPES: $crate::macros::reflection::Types = + <$base_ty as $crate::macros::reflection::FieldMeta< + $scalar, + { $crate::checked_hash!(FIELD_NAME, $base_ty, $scalar, ERR_PREFIX) }, + >>::SUB_TYPES; + let is_subtype = $crate::macros::reflection::str_exists_in_arr(IMPL_RETURN_TY, BASE_RETURN_SUB_TYPES) && $crate::macros::reflection::can_be_subtype(BASE_RETURN_WRAPPED_VAL, IMPL_RETURN_WRAPPED_VAL); if !is_subtype { @@ -548,6 +557,10 @@ macro_rules! assert_subtype { }; } +/// Asserts validness of the [`Field`]s arguments. See [spec][1] for more +/// info. +/// +/// [1]: https://spec.graphql.org/October2021/#sel-IAHZhCHCDEEFAAADHD8Cxob #[macro_export] macro_rules! assert_field_args { ( @@ -557,12 +570,6 @@ macro_rules! assert_field_args { $field_name: expr $(,)? ) => { const _: () = { - type FullArg = ( - $crate::macros::reflection::Name, - $crate::macros::reflection::Type, - $crate::macros::reflection::WrappedValue, - ); - const BASE_NAME: &str = <$base_ty as $crate::macros::reflection::BaseType<$scalar>>::NAME; const IMPL_NAME: &str = @@ -576,19 +583,22 @@ macro_rules! assert_field_args { ); const FIELD_NAME: &str = $field_name; - const BASE_ARGS: &[FullArg] = <$base_ty as $crate::macros::reflection::FieldMeta< - $scalar, - { $crate::checked_hash!(FIELD_NAME, $base_ty, $scalar, ERR_PREFIX) }, - >>::ARGUMENTS; - const IMPL_ARGS: &[FullArg] = <$impl_ty as $crate::macros::reflection::FieldMeta< - $scalar, - { $crate::checked_hash!(FIELD_NAME, $impl_ty, $scalar, ERR_PREFIX) }, - >>::ARGUMENTS; + + const BASE_ARGS: ::juniper::macros::reflection::Arguments = + <$base_ty as $crate::macros::reflection::FieldMeta< + $scalar, + { $crate::checked_hash!(FIELD_NAME, $base_ty, $scalar, ERR_PREFIX) }, + >>::ARGUMENTS; + const IMPL_ARGS: ::juniper::macros::reflection::Arguments = + <$impl_ty as $crate::macros::reflection::FieldMeta< + $scalar, + { $crate::checked_hash!(FIELD_NAME, $impl_ty, $scalar, ERR_PREFIX) }, + >>::ARGUMENTS; struct Error { cause: Cause, - base: FullArg, - implementation: FullArg, + base: ::juniper::macros::reflection::Argument, + implementation: ::juniper::macros::reflection::Argument, } enum Cause { @@ -599,6 +609,8 @@ macro_rules! assert_field_args { const fn unwrap_error(v: ::std::result::Result<(), Error>) -> Error { match v { + // Unfortunately we can't use `unreachable!()` here, as this + // branch will be executed either way. Ok(()) => Error { cause: Cause::RequiredField, base: ("unreachable", "unreachable", 1), @@ -636,6 +648,7 @@ macro_rules! assert_field_args { impl_i += 1; } + // TODO: Maybe that's ok? if !was_found { return Err(Error { cause: Cause::RequiredField, @@ -728,10 +741,11 @@ macro_rules! assert_field_args { }; } +/// Concatenates const [`str`](prim@str)s in const context. #[macro_export] macro_rules! const_concat { ($($s:expr),* $(,)?) => {{ - const LEN: usize = 0 $(+ $s.len())*; + const LEN: usize = 0 $(+ $s.as_bytes().len())*; const CNT: usize = [$($s),*].len(); const fn concat(input: [&str; CNT]) -> [u8; LEN] { let mut bytes = [0; LEN]; @@ -748,10 +762,16 @@ macro_rules! const_concat { bytes } const CON: [u8; LEN] = concat([$($s),*]); + + // SAFETY: this is safe, as we concatenate multiple UTF-8 strings one + // after the other byte by byte. + #[allow(unsafe_code)] unsafe { std::str::from_utf8_unchecked(&CON) } }}; } +/// Before executing [`fnv1a128`] checks whether `impl_ty` has corresponding +/// [`Field`] impl and panics with understandable message. #[macro_export] macro_rules! checked_hash { ($field_name: expr, $impl_ty: ty, $scalar: ty $(, $prefix: expr)? $(,)?) => {{ @@ -762,21 +782,29 @@ macro_rules! checked_hash { if exists { $crate::macros::reflection::fnv1a128(FIELD_NAME) } else { - ::std::panic!( - "{}", - $crate::const_concat!( - $($prefix,)? - "Field `", - $field_name, - "` isn't implemented on `", - <$impl_ty as $crate::macros::reflection::BaseType<$scalar>>::NAME, - "`." - ) - ) + const MSG: &str = $crate::const_concat!( + $($prefix,)? + "Field `", + $field_name, + "` isn't implemented on `", + <$impl_ty as $crate::macros::reflection::BaseType<$scalar>>::NAME, + "`." + ); + ::std::panic!("{}", MSG) } }}; } +/// Formats [`Type`] and [`WrappedValue`] into GraphQL type. +/// +/// # Examples +/// +/// ``` +/// # use juniper::format_type; +/// # +/// assert_eq!(format_type!("String", 123), "[String]!"); +/// assert_eq!(format_type!("🦀", 123), "[🦀]!"); +/// ``` #[macro_export] macro_rules! format_type { ($ty: expr, $wrapped_value: expr $(,)?) => {{ @@ -801,8 +829,9 @@ macro_rules! format_type { let mut is_null = false; while current_wrap_val % 10 != 0 { match current_wrap_val % 10 { - 2 => is_null = true, + 2 => is_null = true, // Skips writing BANG later. 3 => { + // Write OPENING_BRACKET at current_start. let mut i = 0; while i < OPENING_BRACKET.as_bytes().len() { type_arr[current_start + i] = OPENING_BRACKET.as_bytes()[i]; @@ -810,6 +839,7 @@ macro_rules! format_type { } current_start += i; if !is_null { + // Write BANG at current_end. i = 0; while i < BANG.as_bytes().len() { type_arr[current_end - BANG.as_bytes().len() + i + 1] = @@ -818,6 +848,7 @@ macro_rules! format_type { } current_end -= i; } + // Write CLOSING_BRACKET at current_end. i = 0; while i < CLOSING_BRACKET.as_bytes().len() { type_arr[current_end - CLOSING_BRACKET.as_bytes().len() + i + 1] = @@ -833,6 +864,7 @@ macro_rules! format_type { current_wrap_val /= 10; } + // Writes Type at current_start. let mut i = 0; while i < ty.as_bytes().len() { type_arr[current_start + i] = ty.as_bytes()[i]; @@ -840,6 +872,7 @@ macro_rules! format_type { } i = 0; if !is_null { + // Writes BANG at current_end. while i < BANG.as_bytes().len() { type_arr[current_end - BANG.as_bytes().len() + i + 1] = BANG.as_bytes()[i]; i += 1; @@ -850,8 +883,12 @@ macro_rules! format_type { } const TYPE_ARR: [u8; RES_LEN] = format_type_arr(); - // SAFETY: This is safe, as `TYPE_ARR` was formatted from `Type`, `[`, `]` and `!`. + + // SAFETY: this is safe, as we concatenate multiple UTF-8 strings one + // after the other byte by byte. + #[allow(unsafe_code)] const TYPE_FORMATTED: &str = unsafe { ::std::mem::transmute(TYPE_ARR.as_slice()) }; + TYPE_FORMATTED }}; } From 26ab27e3d0103a333ed054521105e97ed379e465 Mon Sep 17 00:00:00 2001 From: ilslv Date: Tue, 4 Jan 2022 14:09:10 +0300 Subject: [PATCH 33/53] WIP (Refactoring) --- juniper_codegen/src/common/field/mod.rs | 145 ------------ juniper_codegen/src/graphql_interface/new.rs | 28 +-- juniper_codegen/src/graphql_object/mod.rs | 237 ++++++++++++++++--- 3 files changed, 216 insertions(+), 194 deletions(-) diff --git a/juniper_codegen/src/common/field/mod.rs b/juniper_codegen/src/common/field/mod.rs index 1f89dd296..0437f97f3 100644 --- a/juniper_codegen/src/common/field/mod.rs +++ b/juniper_codegen/src/common/field/mod.rs @@ -441,151 +441,6 @@ impl Definition { }) } - /// TODO - #[must_use] - pub(crate) fn impl_field_meta( - &self, - impl_ty: &syn::Type, - impl_generics: &TokenStream, - where_clause: Option<&syn::WhereClause>, - scalar: &scalar::Type, - context: &syn::Type, - ) -> TokenStream { - let (name, ty) = (&self.name, self.ty.clone()); - - let arguments = self - .arguments - .as_ref() - .iter() - .flat_map(|vec| vec.iter()) - .filter_map(|arg| match arg { - MethodArgument::Regular(arg) => { - let (name, ty) = (&arg.name, &arg.ty); - - Some(quote! {( - #name, - <#ty as ::juniper::macros::reflection::BaseType<#scalar>>::NAME, - <#ty as ::juniper::macros::reflection::WrappedType<#scalar>>::VALUE, - )}) - } - MethodArgument::Executor | MethodArgument::Context(_) => None, - }) - .collect::>(); - - quote! { - #[allow(deprecated, non_snake_case)] - #[automatically_derived] - impl #impl_generics ::juniper::macros::reflection::FieldMeta< - #scalar, - { ::juniper::macros::reflection::fnv1a128(#name) } - > for #impl_ty - #where_clause - { - type Context = #context; - type TypeInfo = (); - const TYPE: ::juniper::macros::reflection::Type = - <#ty as ::juniper::macros::reflection::BaseType<#scalar>>::NAME; - const SUB_TYPES: ::juniper::macros::reflection::Types = - <#ty as ::juniper::macros::reflection::BaseSubTypes<#scalar>>::NAMES; - const WRAPPED_VALUE: juniper::macros::reflection::WrappedValue = - <#ty as ::juniper::macros::reflection::WrappedType<#scalar>>::VALUE; - const ARGUMENTS: &'static [( - ::juniper::macros::reflection::Name, - ::juniper::macros::reflection::Type, - ::juniper::macros::reflection::WrappedValue, - )] = &[#(#arguments,)*]; - } - } - } - - /// TODO - #[must_use] - pub(crate) fn impl_field( - &self, - impl_ty: &syn::Type, - impl_generics: &TokenStream, - where_clause: Option<&syn::WhereClause>, - scalar: &scalar::Type, - trait_ty: Option<&syn::Type>, - for_async: bool, - ) -> Option { - if !for_async && self.is_async { - return None; - } - - let (name, mut res_ty, ident) = (&self.name, self.ty.clone(), &self.ident); - - let mut res = if self.is_method() { - let args = self - .arguments - .as_ref() - .unwrap() - .iter() - .map(|arg| arg.method_resolve_field_tokens(scalar, for_async)); - - let rcv = self.has_receiver.then(|| { - quote! { self, } - }); - - if trait_ty.is_some() { - quote! { ::#ident(#rcv #( #args ),*) } - } else { - quote! { Self::#ident(#rcv #( #args ),*) } - } - } else { - res_ty = parse_quote! { _ }; - quote! { &self.#ident } - }; - if for_async && !self.is_async { - res = quote! { ::juniper::futures::future::ready(#res) }; - } - - let (call, trait_name) = if for_async { - let resolving_code = gen::async_resolving_code(Some(&res_ty)); - let call = quote! { - fn call<'b>( - &'b self, - info: &'b Self::TypeInfo, - args: &'b ::juniper::Arguments<#scalar>, - executor: &'b ::juniper::Executor, - ) -> ::juniper::BoxFuture<'b, ::juniper::ExecutionResult<#scalar>> { - let fut = #res; - #resolving_code - } - }; - - (call, quote! { AsyncField }) - } else { - let resolving_code = gen::sync_resolving_code(); - let call = quote! { - fn call( - &self, - info: &Self::TypeInfo, - args: &::juniper::Arguments<#scalar>, - executor: &::juniper::Executor, - ) -> ::juniper::ExecutionResult<#scalar> { - let res: #res_ty = #res; - #resolving_code - } - }; - - (call, quote! { Field }) - }; - - Some(quote! { - #[allow(deprecated, non_snake_case)] - #[automatically_derived] - impl #impl_generics ::juniper::macros::reflection::#trait_name< - #scalar, - { ::juniper::macros::reflection::fnv1a128(#name) } - > for #impl_ty - #where_clause - { - #call - } - }) - } - /// Returns generated code for the /// [`GraphQLValueAsync::resolve_field_async`][0] method, which resolves /// this [GraphQL field][1] asynchronously. diff --git a/juniper_codegen/src/graphql_interface/new.rs b/juniper_codegen/src/graphql_interface/new.rs index 0b21cec40..f6d10c81e 100644 --- a/juniper_codegen/src/graphql_interface/new.rs +++ b/juniper_codegen/src/graphql_interface/new.rs @@ -298,16 +298,16 @@ pub(crate) struct Definition { impl ToTokens for Definition { fn to_tokens(&self, into: &mut TokenStream) { - self.generate_enum().to_tokens(into); + self.generate_enum_tokens().to_tokens(into); self.impl_graphql_interface_tokens().to_tokens(into); self.impl_output_type_tokens().to_tokens(into); self.impl_graphql_type_tokens().to_tokens(into); self.impl_graphql_value_tokens().to_tokens(into); self.impl_graphql_value_async_tokens().to_tokens(into); - self.impl_traits_for_reflection().to_tokens(into); - self.impl_field_meta().to_tokens(into); - self.impl_field().to_tokens(into); - self.impl_async_field().to_tokens(into); + self.impl_traits_for_reflection_tokens().to_tokens(into); + self.impl_field_meta_tokens().to_tokens(into); + self.impl_field_tokens().to_tokens(into); + self.impl_async_field_tokens().to_tokens(into); } } @@ -316,7 +316,7 @@ impl Definition { /// /// [`implementers`]: Self::implementers #[must_use] - fn generate_enum(&self) -> TokenStream { + fn generate_enum_tokens(&self) -> TokenStream { let vis = &self.vis; let enum_ident = &self.enum_ident; let alias_ident = &self.enum_alias_ident; @@ -723,7 +723,7 @@ impl Definition { /// [`WrappedType`]: juniper::macros::reflection::WrappedType /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces #[must_use] - pub(crate) fn impl_traits_for_reflection(&self) -> TokenStream { + pub(crate) fn impl_traits_for_reflection_tokens(&self) -> TokenStream { let ty = &self.enum_alias_ident; let implementers = &self.implementers; let scalar = &self.scalar; @@ -777,7 +777,7 @@ impl Definition { /// /// [`FieldMeta`]: juniper::macros::reflection::FieldMeta /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - fn impl_field_meta(&self) -> TokenStream { + fn impl_field_meta_tokens(&self) -> TokenStream { let ty = &self.enum_alias_ident; let context = &self.context; let scalar = &self.scalar; @@ -832,13 +832,13 @@ impl Definition { .collect() } - /// Returns generated code implementing [`Field`] or [`AsyncField`] trait - /// for this [GraphQL interface][1]. + /// Returns generated code implementing [`Field`] trait for each field of + /// this [GraphQL interface][1]. /// /// [`AsyncField`]: juniper::macros::reflection::AsyncField /// [`Field`]: juniper::macros::reflection::Field /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - fn impl_field(&self) -> TokenStream { + fn impl_field_tokens(&self) -> TokenStream { let ty = &self.enum_alias_ident; let scalar = &self.scalar; let const_scalar = self.const_scalar(); @@ -908,12 +908,12 @@ impl Definition { .collect() } - /// Returns generated code implementing [`AsyncField`] trait for this - /// [GraphQL interface][1]. + /// Returns generated code implementing [`AsyncField`] trait for each field + /// of this [GraphQL interface][1]. /// /// [`AsyncField`]: juniper::macros::reflection::AsyncField /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - fn impl_async_field(&self) -> TokenStream { + fn impl_async_field_tokens(&self) -> TokenStream { let ty = &self.enum_alias_ident; let scalar = &self.scalar; let const_scalar = self.const_scalar(); diff --git a/juniper_codegen/src/graphql_object/mod.rs b/juniper_codegen/src/graphql_object/mod.rs index c1caa2e17..48d4a9f30 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, + field, gen, parse::{ attr::{err, OptionExt as _}, ParseBufferExt as _, TypeExt, @@ -361,9 +361,15 @@ impl Definition { } } - /// TODO + /// Returns generated code implementing [`BaseType`], [`BaseSubTypes`] and + /// [`WrappedType`] traits for this [GraphQL object][1]. + /// + /// [`BaseSubTypes`]: juniper::macros::reflection::BaseSubTypes + /// [`BaseType`]: juniper::macros::reflection::BaseType + /// [`WrappedType`]: juniper::macros::reflection::WrappedType + /// [1]: https://spec.graphql.org/June2018/#sec-Objects #[must_use] - pub(crate) fn impl_traits_for_const_assertions(&self) -> TokenStream { + pub(crate) fn impl_traits_for_reflection_tokens(&self) -> TokenStream { let scalar = &self.scalar; let name = &self.name; let (impl_generics, where_clause) = self.impl_generics(false); @@ -484,7 +490,10 @@ impl ToTokens for Definition { self.impl_graphql_value_tokens().to_tokens(into); self.impl_graphql_value_async_tokens().to_tokens(into); self.impl_as_dyn_graphql_value_tokens().to_tokens(into); - self.impl_traits_for_const_assertions().to_tokens(into); + self.impl_traits_for_reflection_tokens().to_tokens(into); + self.impl_field_meta_tokens().to_tokens(into); + self.impl_field_tokens().to_tokens(into); + self.impl_async_field_tokens().to_tokens(into); } } @@ -520,6 +529,195 @@ impl Definition { } } + /// Returns generated code implementing [`FieldMeta`] traits for each field + /// of this [GraphQL object][1]. + /// + /// [`FieldMeta`]: juniper::FieldMeta + /// [1]: https://spec.graphql.org/June2018/#sec-Objects + #[must_use] + fn impl_field_meta_tokens(&self) -> TokenStream { + let impl_ty = &self.ty; + let scalar = &self.scalar; + let context = &self.context; + let (impl_generics, where_clause) = self.impl_generics(false); + + self.fields + .iter() + .map(|field| { + let (name, ty) = (&field.name, field.ty.clone()); + + let arguments = field + .arguments + .as_ref() + .iter() + .flat_map(|vec| vec.iter()) + .filter_map(|arg| match arg { + field::MethodArgument::Regular(arg) => { + let (name, ty) = (&arg.name, &arg.ty); + + Some(quote! {( + #name, + <#ty as ::juniper::macros::reflection::BaseType<#scalar>>::NAME, + <#ty as ::juniper::macros::reflection::WrappedType<#scalar>>::VALUE, + )}) + } + field::MethodArgument::Executor | field::MethodArgument::Context(_) => None, + }) + .collect::>(); + + quote! { + #[allow(deprecated, non_snake_case)] + #[automatically_derived] + impl #impl_generics ::juniper::macros::reflection::FieldMeta< + #scalar, + { ::juniper::macros::reflection::fnv1a128(#name) } + > for #impl_ty + #where_clause + { + type Context = #context; + type TypeInfo = (); + const TYPE: ::juniper::macros::reflection::Type = + <#ty as ::juniper::macros::reflection::BaseType<#scalar>>::NAME; + const SUB_TYPES: ::juniper::macros::reflection::Types = + <#ty as ::juniper::macros::reflection::BaseSubTypes<#scalar>>::NAMES; + const WRAPPED_VALUE: juniper::macros::reflection::WrappedValue = + <#ty as ::juniper::macros::reflection::WrappedType<#scalar>>::VALUE; + const ARGUMENTS: &'static [( + ::juniper::macros::reflection::Name, + ::juniper::macros::reflection::Type, + ::juniper::macros::reflection::WrappedValue, + )] = &[#(#arguments,)*]; + } + } + }) + .collect() + } + + /// Returns generated code implementing [`Field`] trait for each field of + /// this [GraphQL object][1]. + /// + /// [`Field`]: juniper::Field + /// [1]: https://spec.graphql.org/June2018/#sec-Objects + #[must_use] + fn impl_field_tokens(&self) -> TokenStream { + let (impl_ty, scalar) = (&self.ty, &self.scalar); + let (impl_generics, where_clause) = self.impl_generics(false); + + self.fields + .iter() + .filter_map(|field| { + if field.is_async { + return None; + } + + let (name, mut res_ty, ident) = (&field.name, field.ty.clone(), &field.ident); + + let res = if field.is_method() { + let args = field + .arguments + .as_ref() + .unwrap() + .iter() + .map(|arg| arg.method_resolve_field_tokens(scalar, false)); + + let rcv = field.has_receiver.then(|| { + quote! { self, } + }); + + quote! { Self::#ident(#rcv #( #args ),*) } + } else { + res_ty = parse_quote! { _ }; + quote! { &self.#ident } + }; + + let resolving_code = gen::sync_resolving_code(); + + Some(quote! { + #[allow(deprecated, non_snake_case)] + #[automatically_derived] + impl #impl_generics ::juniper::macros::reflection::Field< + #scalar, + { ::juniper::macros::reflection::fnv1a128(#name) } + > for #impl_ty + #where_clause + { + fn call( + &self, + info: &Self::TypeInfo, + args: &::juniper::Arguments<#scalar>, + executor: &::juniper::Executor, + ) -> ::juniper::ExecutionResult<#scalar> { + let res: #res_ty = #res; + #resolving_code + } + } + }) + }) + .collect() + } + + /// Returns generated code implementing [`AsyncField`] trait for each field + /// of this [GraphQL object][1]. + /// + /// [`AsyncField`]: juniper::AsyncField + /// [1]: https://spec.graphql.org/June2018/#sec-Objects + #[must_use] + fn impl_async_field_tokens(&self) -> TokenStream { + let (impl_ty, scalar) = (&self.ty, &self.scalar); + let (impl_generics, where_clause) = self.impl_generics(true); + + self.fields + .iter() + .map(|field| { + let (name, mut res_ty, ident) = (&field.name, field.ty.clone(), &field.ident); + + let mut res = if field.is_method() { + let args = field + .arguments + .as_ref() + .unwrap() + .iter() + .map(|arg| arg.method_resolve_field_tokens(scalar, true)); + + let rcv = field.has_receiver.then(|| { + quote! { self, } + }); + + quote! { Self::#ident(#rcv #( #args ),*) } + } else { + res_ty = parse_quote! { _ }; + quote! { &self.#ident } + }; + if !field.is_async { + res = quote! { ::juniper::futures::future::ready(#res) }; + } + + let resolving_code = gen::async_resolving_code(Some(&res_ty)); + + quote! { + #[allow(deprecated, non_snake_case)] + #[automatically_derived] + impl #impl_generics ::juniper::macros::reflection::AsyncField< + #scalar, + { ::juniper::macros::reflection::fnv1a128(#name) } + > for #impl_ty + #where_clause + { + fn call<'b>( + &'b self, + info: &'b Self::TypeInfo, + args: &'b ::juniper::Arguments<#scalar>, + executor: &'b ::juniper::Executor, + ) -> ::juniper::BoxFuture<'b, ::juniper::ExecutionResult<#scalar>> { + let fut = #res; + #resolving_code + } + } + } + }) + .collect() + } + /// Returns generated code implementing [`GraphQLValue`] trait for this /// [GraphQL object][1]. /// @@ -550,20 +748,6 @@ impl Definition { }) }); - let field_meta_impls = self - .fields - .iter() - .map(|f| f.impl_field_meta(ty, &impl_generics, where_clause.as_ref(), scalar, context)); - let field_impls = self.fields.iter().filter_map(|f| { - f.impl_field( - ty, - &impl_generics, - where_clause.as_ref(), - scalar, - None, - false, - ) - }); let async_fields_err = { let names = self .fields @@ -613,10 +797,6 @@ impl Definition { #name.to_string() } } - - #(#field_meta_impls)* - - #(#field_impls)* } } @@ -645,17 +825,6 @@ impl Definition { } }); - let field_impls = self.fields.iter().filter_map(|f| { - f.impl_field( - ty, - &impl_generics, - where_clause.as_ref(), - scalar, - None, - true, - ) - }); - let no_field_err = field::Definition::method_resolve_field_err_no_field_tokens(scalar, &ty_name); @@ -677,8 +846,6 @@ impl Definition { } } } - - #(#field_impls)* } } From 997774656e3c916da93317e9714b3abca73f9289 Mon Sep 17 00:00:00 2001 From: ilslv Date: Tue, 4 Jan 2022 14:48:09 +0300 Subject: [PATCH 34/53] WIP (Refactoring) --- .../juniper_tests/src/codegen/object_attr.rs | 2 +- juniper/src/macros/reflection.rs | 138 +++++++++--------- juniper/src/types/scalars.rs | 10 +- juniper_codegen/src/graphql_interface/mod.rs | 13 +- juniper_codegen/src/graphql_interface/new.rs | 28 ++-- juniper_codegen/src/graphql_object/mod.rs | 25 ++-- juniper_codegen/src/graphql_union/mod.rs | 8 +- 7 files changed, 119 insertions(+), 105 deletions(-) diff --git a/integration_tests/juniper_tests/src/codegen/object_attr.rs b/integration_tests/juniper_tests/src/codegen/object_attr.rs index 76a1c8542..9f2f7d528 100644 --- a/integration_tests/juniper_tests/src/codegen/object_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/object_attr.rs @@ -1901,7 +1901,7 @@ mod executor { #[graphql_object(scalar = S: ScalarValue)] impl Human { - async fn id<'e, 'r, 'a, S>(&self, executor: &'e Executor<'r, 'a, (), S>) -> &'e str + async fn id<'e, S>(&self, executor: &'e Executor<'_, '_, (), S>) -> &'e str where S: ScalarValue, { diff --git a/juniper/src/macros/reflection.rs b/juniper/src/macros/reflection.rs index 61a92418c..d0b304d8d 100644 --- a/juniper/src/macros/reflection.rs +++ b/juniper/src/macros/reflection.rs @@ -8,57 +8,58 @@ use crate::{ Arguments as FieldArguments, ExecutionResult, Executor, GraphQLValue, Nullable, ScalarValue, }; -/// Type alias for GraphQL [`object`][1], [`scalar`][2] or [`interface`][3] type -/// name. +/// Type alias for [GraphQL object][1], [scalar][2] or [interface][3] type name. +/// See [`BaseType`] for more info. /// /// [1]: https://spec.graphql.org/October2021/#sec-Objects /// [2]: https://spec.graphql.org/October2021/#sec-Scalars /// [3]: https://spec.graphql.org/October2021/#sec-Interfaces pub type Type = &'static str; -/// Type alias for slice of [`Type`]s. See [`BaseType`] for more info. +/// Type alias for slice of [`Type`]s. See [`BaseSubTypes`] for more info. pub type Types = &'static [Type]; -/// Type alias for GraphQL [`object`][1] or [`interface`][2] -/// [`field argument`][3] name. +/// Type alias for [GraphQL object][1] or [interface][2] [field argument][3] +/// name. /// /// [1]: https://spec.graphql.org/October2021/#sec-Objects /// [2]: https://spec.graphql.org/October2021/#sec-Interfaces /// [3]: https://spec.graphql.org/October2021/#sec-Language.Arguments pub type Name = &'static str; -/// Type alias for slice of [`Name`]s. +/// Type alias for slice of [`Name`]s. See [`Fields`] for more info. pub type Names = &'static [Name]; /// Type alias for value of [`WrappedType`]. pub type WrappedValue = u128; -/// Type alias for [`Field argument`][1]s [`Name`], [`Type`] and -/// [`WrappedValue`]. +/// Type alias for [field argument][1]s [`Name`], [`Type`] and [`WrappedValue`]. /// /// [1]: https://spec.graphql.org/October2021/#sec-Language.Arguments pub type Argument = (Name, Type, WrappedValue); -/// Type alias for [`Field argument`][1]s [`Name`], [`Type`] and +/// Type alias for [field argument][1]s [`Name`], [`Type`] and /// [`WrappedValue`]. /// /// [1]: https://spec.graphql.org/October2021/#sec-Language.Arguments pub type Arguments = &'static [(Name, Type, WrappedValue)]; -/// Type alias for constantly hashed [`Name`] for usage in const generics. +/// Type alias for constantly hashed [`Name`] for usage in const context. pub type FieldName = u128; -/// GraphQL [`object`][1], [`scalar`][2] or [`interface`][3] [`Type`] name. -/// This trait is transparent to the [`Option`], [`Vec`] and other containers, -/// so to fully represent GraphQL [`object`][1] we additionally use -/// [`WrappedType`]. +/// [GraphQL object][1], [scalar][2] or [interface][3] [`Type`] name. This trait +/// is transparent to the [`Option`], [`Vec`] and other containers, so to fully +/// represent [GraphQL object][1] we additionally use [`WrappedType`]. +/// +/// Different Rust type may have the same [`NAME`]. For example [`String`] and +/// &[`str`](prim@str). /// +/// [`NAME`]: Self::NAME /// [1]: https://spec.graphql.org/October2021/#sec-Objects /// [2]: https://spec.graphql.org/October2021/#sec-Scalars /// [3]: https://spec.graphql.org/October2021/#sec-Interfaces pub trait BaseType { - /// [`Type`] of the GraphQL [`object`][1], [`scalar`][2] or - /// [`interface`][3]. + /// [`Type`] of the [GraphQL object][1], [scalar][2] or [interface][3]. /// /// [1]: https://spec.graphql.org/October2021/#sec-Objects /// [2]: https://spec.graphql.org/October2021/#sec-Scalars @@ -114,13 +115,13 @@ impl + ?Sized> BaseType for Rc { const NAME: Type = T::NAME; } -/// GraphQL [`object`][1] [`sub-types`][2]. This trait is transparent to the +/// [GraphQL object][1] [sub-types][2]. This trait is transparent to the /// [`Option`], [`Vec`] and other containers. /// /// [1]: https://spec.graphql.org/October2021/#sec-Objects /// [2]: https://spec.graphql.org/October2021/#sel-JAHZhCHCDEJDAAAEEFDBtzC pub trait BaseSubTypes { - /// [`Types`] for the GraphQL [`object`][1]s [`sub-types`][2]. + /// [`Types`] for the [GraphQL object][1]s [sub-types][2]. /// /// [1]: https://spec.graphql.org/October2021/#sec-Objects /// [2]: https://spec.graphql.org/October2021/#sel-JAHZhCHCDEJDAAAEEFDBtzC @@ -175,17 +176,17 @@ impl + ?Sized> BaseSubTypes for Rc { const NAMES: Types = T::NAMES; } -/// To fully represent GraphQL [`object`][1] it's not enough to use [`Type`], -/// because of the [`wrapping types`][2]. To work around this we use +/// To fully represent [GraphQL object][1] it's not enough to use [`Type`], +/// because of the [wrapping types][2]. To work around this we use /// [`WrappedValue`] which is represented with [`u128`]. /// -/// - In base case of non-nullable [`object`] [`VALUE`] is `1`. +/// - In base case of non-nullable [object] [`VALUE`] is `1`. /// - To represent nullability we "append" `2` to the [`VALUE`], so -/// [`Option`]`<`[`object`][1]`>` has [`VALUE`] of `12`. +/// [`Option`]`<`[object][1]`>` has [`VALUE`] of `12`. /// - To represent list we "append" `3` to the [`VALUE`], so -/// [`Vec`]`<`[`object`][1]`>` has [`VALUE`] of `13`. +/// [`Vec`]`<`[object][1]`>` has [`VALUE`] of `13`. /// -/// This approach allows us to uniquely represent any GraphQL [`object`] with +/// This approach allows us to uniquely represent any [GraphQL object][1] with /// combination of [`Type`] and [`WrappedValue`] and even constantly format it /// with [`format_type`] macro. /// @@ -265,14 +266,14 @@ impl + ?Sized> WrappedType for Rc { const VALUE: u128 = T::VALUE; } -/// GraphQL [`object`][1] or [`interface`][2] [`Field arguments`][3] [`Names`]. +/// [GraphQL object][1] or [interface][2] [field arguments][3] [`Names`]. /// /// [1]: https://spec.graphql.org/October2021/#sec-Objects /// [2]: https://spec.graphql.org/October2021/#sec-Interfaces /// [3]: https://spec.graphql.org/October2021/#sec-Language.Arguments pub trait Fields { - /// [`Names`] of the GraphQL [`object`][1] or [`interface`][2] - /// [`Field arguments`][3]. + /// [`Names`] of the [GraphQL object][1] or [interface][2] + /// [field arguments][3]. /// /// [1]: https://spec.graphql.org/October2021/#sec-Objects /// [2]: https://spec.graphql.org/October2021/#sec-Interfaces @@ -280,51 +281,52 @@ pub trait Fields { const NAMES: Names; } -/// Stores meta information of GraphQL [`Fields`][1]: +/// Stores meta information of [GraphQL fields][1]: /// - [`Context`] and [`TypeInfo`]. /// - Return type's [`TYPE`], [`SUB_TYPES`] and [`WRAPPED_VALUE`]. /// - [`ARGUMENTS`]. /// -/// [1]: https://spec.graphql.org/October2021/#sec-Language.Fields +/// [`ARGUMENTS`]: Self::ARGUMENTS /// [`Context`]: Self::Context -/// [`TypeInfo`]: Self::TypeInfo -/// [`TYPE`]: Self::TYPE /// [`SUB_TYPES`]: Self::SUB_TYPES +/// [`TYPE`]: Self::TYPE +/// [`TypeInfo`]: Self::TypeInfo /// [`WRAPPED_VALUE`]: Self::WRAPPED_VALUE -/// [`ARGUMENTS`]: Self::ARGUMENTS +/// [1]: https://spec.graphql.org/October2021/#sec-Language.Fields pub trait FieldMeta { /// [`GraphQLValue::Context`] of this [`Field`][1]. /// /// [1]: https://spec.graphql.org/October2021/#sec-Language.Fields type Context; - /// [`GraphQLValue::TypeInfo`] of this [`Field`][1]. + /// [`GraphQLValue::TypeInfo`] of this [GraphQL field][1]. /// /// [1]: https://spec.graphql.org/October2021/#sec-Language.Fields type TypeInfo; - /// [`Types`] of [`Field`][1]'s return type. + /// [`Types`] of [GraphQL field's][1] return type. /// /// [1]: https://spec.graphql.org/October2021/#sec-Language.Fields const TYPE: Type; - /// Sub-[`Types`] of [`Field`][1]'s return type. + /// [Sub-Types][1] of [GraphQL field's][2] return type. /// - /// [1]: https://spec.graphql.org/October2021/#sec-Language.Fields + /// [1]: BaseSubTypes + /// [2]: https://spec.graphql.org/October2021/#sec-Language.Fields const SUB_TYPES: Types; - /// [`WrappedValue`] of [`Field`][1]'s return type. + /// [`WrappedValue`] of [GraphQL field's][1] return type. /// /// [1]: https://spec.graphql.org/October2021/#sec-Language.Fields const WRAPPED_VALUE: WrappedValue; - /// [`Field`][1]'s [`Arguments`]. + /// [GraphQL field's][1] [`Arguments`]. /// /// [1]: https://spec.graphql.org/October2021/#sec-Language.Fields const ARGUMENTS: Arguments; } -/// Synchronous field of a GraphQL [`object`][1] or [`interface`][2]. +/// Synchronous field of a [GraphQL object][1] or [interface][2]. /// /// [1]: https://spec.graphql.org/October2021/#sec-Objects /// [2]: https://spec.graphql.org/October2021/#sec-Interfaces @@ -334,8 +336,9 @@ pub trait Field: FieldMeta { /// The `arguments` object contains all the specified arguments, with /// default values being substituted for the ones not provided by the query. /// - /// The `executor` can be used to drive selections into sub-[`objects`][1]. + /// The `executor` can be used to drive selections into sub-[objects][1]. /// + /// [`Value`]: crate::Value /// [1]: https://spec.graphql.org/October2021/#sec-Objects fn call( &self, @@ -350,12 +353,12 @@ pub trait Field: FieldMeta { /// [1]: https://spec.graphql.org/October2021/#sec-Objects /// [2]: https://spec.graphql.org/October2021/#sec-Interfaces pub trait AsyncField: FieldMeta { - /// Resolves the [`Value`] of this asynchronous [`Field`]. + /// Resolves the [`Value`] of this asynchronous [`AsyncField`]. /// /// The `arguments` object contains all the specified arguments, with /// default values being substituted for the ones not provided by the query. /// - /// The `executor` can be used to drive selections into sub-[`objects`][1]. + /// The `executor` can be used to drive selections into sub-[objects][1]. /// /// [1]: https://spec.graphql.org/October2021/#sec-Objects fn call<'b>( @@ -385,31 +388,6 @@ pub const fn fnv1a128(str: Name) -> u128 { hash } -/// Compares strings in `const` context. -/// -/// As there is no `const impl Trait` and `l == r` calls [`Eq`], we have to -/// write custom comparison function. -/// -/// [`Eq`]: std::cmp::Eq -// TODO: Remove once `Eq` trait is allowed in `const` context. -pub const fn str_eq(l: &str, r: &str) -> bool { - let (l, r) = (l.as_bytes(), r.as_bytes()); - - if l.len() != r.len() { - return false; - } - - let mut i = 0; - while i < l.len() { - if l[i] != r[i] { - return false; - } - i += 1; - } - - true -} - /// Length of the [`format_type`] macro result __in bytes__. pub const fn type_len_with_wrapped_val(ty: Type, v: WrappedValue) -> usize { let mut len = ty.as_bytes().len() + "!".as_bytes().len(); // Type! @@ -464,6 +442,31 @@ pub const fn str_exists_in_arr(val: &str, arr: &[&str]) -> bool { false } +/// Compares strings in `const` context. +/// +/// As there is no `const impl Trait` and `l == r` calls [`Eq`], we have to +/// write custom comparison function. +/// +/// [`Eq`]: std::cmp::Eq +// TODO: Remove once `Eq` trait is allowed in `const` context. +pub const fn str_eq(l: &str, r: &str) -> bool { + let (l, r) = (l.as_bytes(), r.as_bytes()); + + if l.len() != r.len() { + return false; + } + + let mut i = 0; + while i < l.len() { + if l[i] != r[i] { + return false; + } + i += 1; + } + + true +} + /// Asserts validness of the [`Field`] [`Arguments`] and return [`Type`]. This /// assertion is a combination of [`assert_subtype`] and [`assert_field_args`]. /// See [spec][1] for more info. @@ -648,7 +651,6 @@ macro_rules! assert_field_args { impl_i += 1; } - // TODO: Maybe that's ok? if !was_found { return Err(Error { cause: Cause::RequiredField, diff --git a/juniper/src/types/scalars.rs b/juniper/src/types/scalars.rs index 3fd3fcf18..87c293d7b 100644 --- a/juniper/src/types/scalars.rs +++ b/juniper/src/types/scalars.rs @@ -7,6 +7,7 @@ use serde::{Deserialize, Serialize}; use crate::{ ast::{InputValue, Selection, ToInputValue}, executor::{ExecutionResult, Executor, Registry}, + macros::reflection, parser::{LexerError, ParseError, ScalarToken, Token}, schema::meta::MetaType, types::{ @@ -202,17 +203,16 @@ where }) } -impl crate::macros::reflection::WrappedType for str { +impl reflection::WrappedType for str { const VALUE: u128 = 1; } -impl crate::macros::reflection::BaseType for str { +impl reflection::BaseType for str { const NAME: &'static str = "String"; } -impl crate::macros::reflection::BaseSubTypes for str { - const NAMES: &'static [&'static str] = - &[>::NAME]; +impl reflection::BaseSubTypes for str { + const NAMES: &'static [&'static str] = &[>::NAME]; } impl GraphQLType for str diff --git a/juniper_codegen/src/graphql_interface/mod.rs b/juniper_codegen/src/graphql_interface/mod.rs index c5c7aaca5..8b3deefaa 100644 --- a/juniper_codegen/src/graphql_interface/mod.rs +++ b/juniper_codegen/src/graphql_interface/mod.rs @@ -442,7 +442,7 @@ impl ToTokens for Definition { self.impl_graphql_type_tokens().to_tokens(into); self.impl_graphql_value_tokens().to_tokens(into); self.impl_graphql_value_async_tokens().to_tokens(into); - self.impl_traits_for_const_assertions().to_tokens(into); + self.impl_traits_for_reflection_tokens().to_tokens(into); } } @@ -716,9 +716,13 @@ impl Definition { } } - /// TODO + /// Returns generated code implementing [`BaseType`], [`BaseSubTypes`] and + /// [`WrappedType`] traits for this [GraphQL interface][1]. + /// + /// [`GraphQLValueAsync`]: juniper::GraphQLValueAsync + /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces #[must_use] - pub(crate) fn impl_traits_for_const_assertions(&self) -> TokenStream { + pub(crate) fn impl_traits_for_reflection_tokens(&self) -> TokenStream { let scalar = &self.scalar; let name = &self.name; let (impl_generics, where_clause) = self.ty.impl_generics(false); @@ -937,7 +941,6 @@ impl Implementer { /// code generation. /// /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces -#[derive(Debug)] struct EnumType { /// Name of this [`EnumType`] to generate it with. ident: syn::Ident, @@ -1441,7 +1444,6 @@ impl EnumType { /// /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces /// [2]: https://doc.rust-lang.org/reference/types/trait-object.html -#[derive(Debug)] struct TraitObjectType { /// Name of this [`TraitObjectType`] to generate it with. ident: syn::Ident, @@ -1674,7 +1676,6 @@ impl ToTokens for TraitObjectType { /// type for code generation. /// /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces -#[derive(Debug)] enum Type { /// [GraphQL interface][1] type implementation as Rust enum. /// diff --git a/juniper_codegen/src/graphql_interface/new.rs b/juniper_codegen/src/graphql_interface/new.rs index f6d10c81e..a7621f7ab 100644 --- a/juniper_codegen/src/graphql_interface/new.rs +++ b/juniper_codegen/src/graphql_interface/new.rs @@ -573,15 +573,17 @@ impl Definition { let (_, ty_generics, _) = self.trait_generics.split_for_impl(); let fields_resolvers = self.fields.iter().filter_map(|f| { - (!f.is_async).then(|| { - let name = &f.name; - quote! { - #name => { - ::juniper::macros::reflection::Field::< - #scalar, - { ::juniper::macros::reflection::fnv1a128(#name) } - >::call(self, info, args, executor) - } + if f.is_async { + return None; + } + + let name = &f.name; + Some(quote! { + #name => { + ::juniper::macros::reflection::Field::< + #scalar, + { ::juniper::macros::reflection::fnv1a128(#name) } + >::call(self, info, args, executor) } }) }); @@ -715,11 +717,12 @@ impl Definition { } } - /// Returns generated code implementing [`BaseType`], [`BaseSubTypes`] and - /// [`WrappedType`] traits for this [GraphQL interface][1]. + /// Returns generated code implementing [`BaseType`], [`BaseSubTypes`], + /// [`WrappedType`] and [`Fields`] traits for this [GraphQL interface][1]. /// /// [`BaseSubTypes`]: juniper::macros::reflection::BaseSubTypes /// [`BaseType`]: juniper::macros::reflection::BaseType + /// [`Fields`]: juniper::macros::reflection::Fields /// [`WrappedType`]: juniper::macros::reflection::WrappedType /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces #[must_use] @@ -772,7 +775,7 @@ impl Definition { } } - /// Returns generated code implementing [`FieldMeta`] for this + /// Returns generated code implementing [`FieldMeta`] for each field of this /// [GraphQL interface][1]. /// /// [`FieldMeta`]: juniper::macros::reflection::FieldMeta @@ -835,7 +838,6 @@ impl Definition { /// Returns generated code implementing [`Field`] trait for each field of /// this [GraphQL interface][1]. /// - /// [`AsyncField`]: juniper::macros::reflection::AsyncField /// [`Field`]: juniper::macros::reflection::Field /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces fn impl_field_tokens(&self) -> TokenStream { diff --git a/juniper_codegen/src/graphql_object/mod.rs b/juniper_codegen/src/graphql_object/mod.rs index 48d4a9f30..507e8a363 100644 --- a/juniper_codegen/src/graphql_object/mod.rs +++ b/juniper_codegen/src/graphql_object/mod.rs @@ -361,11 +361,12 @@ impl Definition { } } - /// Returns generated code implementing [`BaseType`], [`BaseSubTypes`] and - /// [`WrappedType`] traits for this [GraphQL object][1]. + /// Returns generated code implementing [`BaseType`], [`BaseSubTypes`], + /// [`WrappedType`] and [`Fields`] traits for this [GraphQL object][1]. /// /// [`BaseSubTypes`]: juniper::macros::reflection::BaseSubTypes /// [`BaseType`]: juniper::macros::reflection::BaseType + /// [`Fields`]: juniper::macros::reflection::Fields /// [`WrappedType`]: juniper::macros::reflection::WrappedType /// [1]: https://spec.graphql.org/June2018/#sec-Objects #[must_use] @@ -735,15 +736,17 @@ impl Definition { let name = &self.name; let fields_resolvers = self.fields.iter().filter_map(|f| { - (!f.is_async).then(|| { - let name = &f.name; - quote! { - #name => { - ::juniper::macros::reflection::Field::< - #scalar, - { ::juniper::macros::reflection::fnv1a128(#name) } - >::call(self, info, args, executor) - } + if f.is_async { + return None; + } + + let name = &f.name; + Some(quote! { + #name => { + ::juniper::macros::reflection::Field::< + #scalar, + { ::juniper::macros::reflection::fnv1a128(#name) } + >::call(self, info, args, executor) } }) }); diff --git a/juniper_codegen/src/graphql_union/mod.rs b/juniper_codegen/src/graphql_union/mod.rs index 14048cd0c..42eb6123b 100644 --- a/juniper_codegen/src/graphql_union/mod.rs +++ b/juniper_codegen/src/graphql_union/mod.rs @@ -611,7 +611,13 @@ impl Definition { } } - /// TODO + /// Returns generated code implementing [`BaseType`], [`BaseSubTypes`] and + /// [`WrappedType`] traits for this [GraphQL union][1]. + /// + /// [`BaseSubTypes`]: juniper::macros::reflection::BaseSubTypes + /// [`BaseType`]: juniper::macros::reflection::BaseType + /// [`WrappedType`]: juniper::macros::reflection::WrappedType + /// [1]: https://spec.graphql.org/June2018/#sec-Unions #[must_use] pub(crate) fn impl_traits_for_const_assertions(&self) -> TokenStream { let scalar = &self.scalar; From f5340a72f05516273e28c652a903154fa5401ca8 Mon Sep 17 00:00:00 2001 From: ilslv Date: Tue, 4 Jan 2022 15:35:16 +0300 Subject: [PATCH 35/53] WIP (Tests corrections) --- .../src/codegen/interface_attr.rs | 306 ++++-------------- .../juniper_tests/src/codegen/mod.rs | 1 - .../src/codegen/new_interface.rs | 89 ----- 3 files changed, 70 insertions(+), 326 deletions(-) delete mode 100644 integration_tests/juniper_tests/src/codegen/new_interface.rs diff --git a/integration_tests/juniper_tests/src/codegen/interface_attr.rs b/integration_tests/juniper_tests/src/codegen/interface_attr.rs index 8dbc45920..75d4a024f 100644 --- a/integration_tests/juniper_tests/src/codegen/interface_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/interface_attr.rs @@ -112,237 +112,19 @@ mod trivial { home_planet: String, } - #[derive(GraphQLObject)] - #[graphql(impl = CharacterValue)] struct Droid { id: String, primary_function: String, } - #[derive(Clone, Copy)] - enum QueryRoot { - Human, - Droid, - } - - #[graphql_object(scalar = S: ScalarValue + Send + Sync)] - impl QueryRoot { - fn character(&self) -> CharacterValue { - match self { - Self::Human => Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), - } - .into(), - Self::Droid => Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), - } - .into(), - } - } - } - - #[tokio::test] - async fn enum_resolves_human() { - const DOC: &str = r#"{ - character { - ... on Human { - humanId: id - homePlanet - } - } - }"#; - - let schema = schema(QueryRoot::Human); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"character": {"humanId": "human-32", "homePlanet": "earth"}}), - vec![], - )), - ); - } - - #[tokio::test] - async fn enum_resolves_droid() { - const DOC: &str = r#"{ - character { - ... on Droid { - droidId: id - primaryFunction - } - } - }"#; - - let schema = schema(QueryRoot::Droid); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}), - vec![], - )), - ); - } - - #[tokio::test] - async fn enum_resolves_id_field() { - const DOC: &str = r#"{ - character { - id - } - }"#; - - for (root, expected_id) in &[ - (QueryRoot::Human, "human-32"), - (QueryRoot::Droid, "droid-99"), - ] { - let schema = schema(*root); - - let expected_id: &str = *expected_id; - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"character": {"id": expected_id}}), vec![])), - ); - } - } - - #[tokio::test] - async fn is_graphql_interface() { - let schema = schema(QueryRoot::Human); - - let doc = r#"{ - __type(name: "Character") { - kind - } - }"#; - - assert_eq!( - execute(&doc, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"__type": {"kind": "INTERFACE"}}), vec![])), - ); - } - - #[tokio::test] - async fn registers_all_implementers() { - let schema = schema(QueryRoot::Human); - - let doc = r#"{ - __type(name: "Character") { - possibleTypes { - kind - name - } - } - }"#; - - assert_eq!( - execute(&doc, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"__type": {"possibleTypes": [ - {"kind": "OBJECT", "name": "Droid"}, - {"kind": "OBJECT", "name": "Human"}, - ]}}), - vec![], - )), - ); - } - - #[tokio::test] - async fn registers_itself_in_implementers() { - let schema = schema(QueryRoot::Human); - - for object in &["Human", "Droid"] { - let doc = format!( - r#"{{ - __type(name: "{}") {{ - interfaces {{ - kind - name - }} - }} - }}"#, - object, - ); - - assert_eq!( - execute(&doc, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"__type": {"interfaces": [ - {"kind": "INTERFACE", "name": "Character"}, - ]}}), - vec![], - )), - ); - } - } - - #[tokio::test] - async fn uses_trait_name() { - let schema = schema(QueryRoot::Human); - - let doc = r#"{ - __type(name: "Character") { - name - } - }"#; - - assert_eq!( - execute(&doc, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"__type": {"name": "Character"}}), vec![])), - ); - } - - #[tokio::test] - async fn has_no_description() { - let schema = schema(QueryRoot::Human); - - let doc = r#"{ - __type(name: "Character") { - description - } - }"#; - - assert_eq!( - execute(&doc, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"__type": {"description": null}}), vec![])), - ); - } -} - -mod trivial_with_trait_imp { - use super::*; - - #[graphql_interface_new(for = [Human, Droid])] - trait Character { - fn id(&self) -> &str; - } - - #[derive(GraphQLObject)] - #[graphql(impl = CharacterValue)] - struct Human { - id: String, - home_planet: String, - } - - impl Character for Human { + #[graphql_object(impl = CharacterValue)] + impl Droid { fn id(&self) -> &str { &self.id } - } - - #[derive(GraphQLObject)] - #[graphql(impl = CharacterValue)] - struct Droid { - id: String, - primary_function: String, - } - impl Character for Droid { - fn id(&self) -> &str { - &self.id + fn primary_function(&self) -> &str { + &self.primary_function } } @@ -554,13 +336,22 @@ mod explicit_alias { home_planet: String, } - #[derive(GraphQLObject)] - #[graphql(impl = CharacterEnum)] struct Droid { id: String, primary_function: String, } + #[graphql_object(impl = CharacterEnum)] + impl Droid { + fn id(&self) -> &str { + &self.id + } + + fn primary_function(&self) -> &str { + &self.primary_function + } + } + #[derive(Clone, Copy)] enum QueryRoot { Human, @@ -715,13 +506,22 @@ mod trivial_async { home_planet: String, } - #[derive(GraphQLObject)] - #[graphql(impl = CharacterValue)] struct Droid { id: String, primary_function: String, } + #[graphql_object(impl = CharacterValue)] + impl Droid { + fn id(&self) -> &str { + &self.id + } + + fn primary_function(&self) -> &str { + &self.primary_function + } + } + #[derive(Clone, Copy)] enum QueryRoot { Human, @@ -1643,8 +1443,6 @@ mod argument { async fn id_wide2(&self, is_number: bool, r#async: Option) -> &str; } - // #[derive(GraphQLObject)] - // #[graphql(impl = CharacterValue)] struct Human { id: String, home_planet: String, @@ -2439,13 +2237,22 @@ mod explicit_scalar { home_planet: String, } - #[derive(GraphQLObject)] - #[graphql(impl = CharacterValue, scalar = DefaultScalarValue)] struct Droid { id: String, primary_function: String, } + #[graphql_object(impl = CharacterValue, scalar = DefaultScalarValue)] + impl Droid { + fn id(&self) -> &str { + &self.id + } + + fn primary_function(&self) -> &str { + &self.primary_function + } + } + #[derive(Clone, Copy)] enum QueryRoot { Human, @@ -2554,13 +2361,22 @@ mod custom_scalar { home_planet: String, } - #[derive(GraphQLObject)] - #[graphql(impl = CharacterValue, scalar = MyScalarValue)] struct Droid { id: String, primary_function: String, } + #[graphql_object(impl = CharacterValue, scalar = MyScalarValue)] + impl Droid { + fn id(&self) -> &str { + &self.id + } + + fn primary_function(&self) -> &str { + &self.primary_function + } + } + #[derive(Clone, Copy)] enum QueryRoot { Human, @@ -2667,13 +2483,22 @@ mod explicit_generic_scalar { home_planet: String, } - #[derive(GraphQLObject)] - #[graphql(impl = CharacterValue<__S>)] struct Droid { id: String, primary_function: String, } + #[graphql_object(impl = CharacterValue<__S>)] + impl Droid { + fn id(&self) -> &str { + &self.id + } + + fn primary_function(&self) -> &str { + &self.primary_function + } + } + #[derive(Clone, Copy)] enum QueryRoot { Human, @@ -2780,13 +2605,22 @@ mod bounded_generic_scalar { home_planet: String, } - #[derive(GraphQLObject)] - #[graphql(impl = CharacterValue)] struct Droid { id: String, primary_function: String, } + #[graphql_object(impl = CharacterValue, scalar = S: ScalarValue + Clone)] + impl Droid { + fn id(&self) -> &str { + &self.id + } + + fn primary_function(&self) -> &str { + &self.primary_function + } + } + #[derive(Clone, Copy)] enum QueryRoot { Human, diff --git a/integration_tests/juniper_tests/src/codegen/mod.rs b/integration_tests/juniper_tests/src/codegen/mod.rs index 1abc91e4d..6348a66c8 100644 --- a/integration_tests/juniper_tests/src/codegen/mod.rs +++ b/integration_tests/juniper_tests/src/codegen/mod.rs @@ -4,7 +4,6 @@ mod derive_object_with_raw_idents; mod derive_scalar; mod impl_scalar; mod interface_attr; -mod new_interface; mod object_attr; mod object_derive; mod scalar_value_transparent; diff --git a/integration_tests/juniper_tests/src/codegen/new_interface.rs b/integration_tests/juniper_tests/src/codegen/new_interface.rs deleted file mode 100644 index ca3a92922..000000000 --- a/integration_tests/juniper_tests/src/codegen/new_interface.rs +++ /dev/null @@ -1,89 +0,0 @@ -use juniper::{ - execute, graphql_interface, graphql_interface_new, graphql_object, graphql_value, graphql_vars, - DefaultScalarValue, EmptyMutation, EmptySubscription, Executor, FieldError, FieldResult, - GraphQLInputObject, GraphQLObject, GraphQLType, IntoFieldError, LookAheadMethods, RootNode, - ScalarValue, -}; - -// -------------------------- - -#[graphql_interface_new(for = [Human, Droid], scalar = S)] -trait Character { - async fn id<'a>(&self, executor: &'a Executor<'_, '_, (), S>) -> &'a str - where - S: Send + Sync, - { - executor.look_ahead().field_name() - } - - async fn info<'b>( - &'b self, - arg: Option, - #[graphql(executor)] another: &Executor<'_, '_, (), S>, - ) -> &'b str - where - S: Send + Sync; -} - -struct Human { - id: String, - home_planet: String, -} - -#[graphql_object(impl = CharacterValue<__S>)] -impl Human { - async fn id<'a, S: ScalarValue>(&self, executor: &'a Executor<'_, '_, (), S>) -> &'a str { - executor.look_ahead().field_name() - } - - async fn info<'b>(&'b self, _arg: Option) -> &'b str { - &self.home_planet - } -} - -struct Droid { - id: String, - primary_function: String, -} - -#[graphql_object(impl = CharacterValue<__S>)] -impl Droid { - fn id<'a, S: ScalarValue>(&self, executor: &'a Executor<'_, '_, (), S>) -> &'a str { - executor.look_ahead().field_name() - } - - async fn info<'b, S: ScalarValue>( - &'b self, - _arg: Option, - _executor: &Executor<'_, '_, (), S>, - ) -> &'b str { - &self.primary_function - } -} - -// -------------- - -fn schema<'q, C, Q>(query_root: Q) -> RootNode<'q, Q, EmptyMutation, EmptySubscription> -where - Q: GraphQLType + 'q, -{ - RootNode::new( - query_root, - EmptyMutation::::new(), - EmptySubscription::::new(), - ) -} - -fn schema_with_scalar<'q, S, C, Q>( - query_root: Q, -) -> RootNode<'q, Q, EmptyMutation, EmptySubscription, S> -where - Q: GraphQLType + 'q, - S: ScalarValue + 'q, -{ - RootNode::new_with_scalar_value( - query_root, - EmptyMutation::::new(), - EmptySubscription::::new(), - ) -} From 4c31c13cd462b841c12359f342457056799d3836 Mon Sep 17 00:00:00 2001 From: ilslv Date: Tue, 4 Jan 2022 16:00:37 +0300 Subject: [PATCH 36/53] WIP (More tests vol. 1) --- .../src/codegen/interface_attr.rs | 294 +++++++++++++++++- .../juniper_tests/src/codegen/object_attr.rs | 2 +- 2 files changed, 294 insertions(+), 2 deletions(-) diff --git a/integration_tests/juniper_tests/src/codegen/interface_attr.rs b/integration_tests/juniper_tests/src/codegen/interface_attr.rs index 75d4a024f..7afcfe047 100644 --- a/integration_tests/juniper_tests/src/codegen/interface_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/interface_attr.rs @@ -3,7 +3,8 @@ use juniper::{ execute, graphql_interface_new, graphql_object, graphql_value, graphql_vars, DefaultScalarValue, EmptyMutation, EmptySubscription, Executor, FieldError, FieldResult, - GraphQLInputObject, GraphQLObject, GraphQLType, IntoFieldError, RootNode, ScalarValue, + GraphQLInputObject, GraphQLObject, GraphQLType, GraphQLUnion, IntoFieldError, RootNode, + ScalarValue, }; fn schema<'q, C, Q>(query_root: Q) -> RootNode<'q, Q, EmptyMutation, EmptySubscription> @@ -3307,3 +3308,294 @@ mod ignored_method { ); } } + +mod field_return_subtyping { + use super::*; + + #[graphql_interface_new(for = [Human, Droid])] + trait Character { + fn id(&self) -> Option; + } + + #[derive(GraphQLObject)] + #[graphql(impl = CharacterValue)] + struct Human { + id: String, + home_planet: String, + } + + struct Droid { + id: String, + primary_function: String, + } + + #[graphql_object(impl = CharacterValue)] + impl Droid { + fn id(&self) -> &str { + &self.id + } + + fn primary_function(&self) -> &str { + &self.primary_function + } + } + + #[derive(Clone, Copy)] + enum QueryRoot { + Human, + Droid, + } + + #[graphql_object(scalar = S: ScalarValue + Send + Sync)] + impl QueryRoot { + fn character(&self) -> CharacterValue { + match self { + Self::Human => Human { + id: "human-32".to_string(), + home_planet: "earth".to_string(), + } + .into(), + Self::Droid => Droid { + id: "droid-99".to_string(), + primary_function: "run".to_string(), + } + .into(), + } + } + } + + #[tokio::test] + async fn enum_resolves_human() { + const DOC: &str = r#"{ + character { + ... on Human { + humanId: id + homePlanet + } + } + }"#; + + let schema = schema(QueryRoot::Human); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"character": {"humanId": "human-32", "homePlanet": "earth"}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn enum_resolves_droid() { + const DOC: &str = r#"{ + character { + ... on Droid { + droidId: id + primaryFunction + } + } + }"#; + + let schema = schema(QueryRoot::Droid); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn enum_resolves_id_field() { + const DOC: &str = r#"{ + character { + id + } + }"#; + + for (root, expected_id) in &[ + (QueryRoot::Human, "human-32"), + (QueryRoot::Droid, "droid-99"), + ] { + let schema = schema(*root); + + let expected_id: &str = *expected_id; + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"character": {"id": expected_id}}), vec![])), + ); + } + } +} + +mod field_return_union_subtyping { + use super::*; + + #[derive(GraphQLObject)] + struct Strength { + value: i32, + } + + #[derive(GraphQLObject)] + struct Knowledge { + value: i32, + } + + #[allow(dead_code)] + #[derive(GraphQLUnion)] + enum KeyFeature { + Strength(Strength), + Knowledge(Knowledge), + } + + #[graphql_interface_new(for = [Human, Droid])] + trait Character { + fn id(&self) -> Option; + + fn key_feature(&self) -> KeyFeature; + } + + #[derive(GraphQLObject)] + #[graphql(impl = CharacterValue)] + struct Human { + id: String, + home_planet: String, + key_feature: Knowledge, + } + + struct Droid { + id: String, + primary_function: String, + strength: i32, + } + + #[graphql_object(impl = CharacterValue)] + impl Droid { + fn id(&self) -> &str { + &self.id + } + + fn primary_function(&self) -> &str { + &self.primary_function + } + + fn key_feature(&self) -> Strength { + Strength { + value: self.strength, + } + } + } + + #[derive(Clone, Copy)] + enum QueryRoot { + Human, + Droid, + } + + #[graphql_object(scalar = S: ScalarValue + Send + Sync)] + impl QueryRoot { + fn character(&self) -> CharacterValue { + match self { + Self::Human => Human { + id: "human-32".to_string(), + home_planet: "earth".to_string(), + key_feature: Knowledge { value: 10 }, + } + .into(), + Self::Droid => Droid { + id: "droid-99".to_string(), + primary_function: "run".to_string(), + strength: 42, + } + .into(), + } + } + } + + #[tokio::test] + async fn enum_resolves_human() { + const DOC: &str = r#"{ + character { + ... on Human { + humanId: id + homePlanet + keyFeature { + value + } + } + } + }"#; + + let schema = schema(QueryRoot::Human); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"character": {"humanId": "human-32", "homePlanet": "earth", "keyFeature": {"value": 10}}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn enum_resolves_droid() { + const DOC: &str = r#"{ + character { + ... on Droid { + droidId: id + primaryFunction + keyFeature { + value + } + } + } + }"#; + + let schema = schema(QueryRoot::Droid); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run", "keyFeature": {"value": 42}}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn enum_resolves_id_field() { + const DOC: &str = r#"{ + character { + id + keyFeature { + ...on Strength { + value + } + ... on Knowledge { + value + } + } + } + }"#; + + for (root, expected_id, expected_val) in &[ + (QueryRoot::Human, "human-32", 10), + (QueryRoot::Droid, "droid-99", 42), + ] { + let schema = schema(*root); + + let expected_id: &str = *expected_id; + let expected_val = *expected_val; + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"character": {"id": expected_id, "keyFeature": {"value": expected_val}}}), + vec![] + )), + ); + } + } +} diff --git a/integration_tests/juniper_tests/src/codegen/object_attr.rs b/integration_tests/juniper_tests/src/codegen/object_attr.rs index 9f2f7d528..9161cc55d 100644 --- a/integration_tests/juniper_tests/src/codegen/object_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/object_attr.rs @@ -2022,7 +2022,7 @@ mod switched_context { async fn switch_res_opt<'e, S: ScalarValue>( &self, - executor: &'e Executor<'e, 'e, CustomContext, S>, + executor: &'e Executor<'_, '_, CustomContext, S>, ) -> FieldResult> { Ok(Some((executor.context(), Droid { id: 3 }))) } From 7bd09d05bf6c481da69307dbed4ed7cd192b02af Mon Sep 17 00:00:00 2001 From: ilslv Date: Wed, 5 Jan 2022 14:47:58 +0300 Subject: [PATCH 37/53] WIP (Move everything to the new graphql_interface macro) --- .../interface/argument_double_underscored.rs | 11 +- .../argument_double_underscored.stderr | 22 +- .../fail/interface/argument_non_input_type.rs | 8 - .../interface/argument_non_input_type.stderr | 26 +- .../interface/argument_wrong_default_array.rs | 17 +- .../argument_wrong_default_array.stderr | 20 +- ...hod_conflicts_with_external_downcast_fn.rs | 29 - ...conflicts_with_external_downcast_fn.stderr | 20 - .../downcast_method_wrong_input_args.rs | 24 - .../downcast_method_wrong_input_args.stderr | 19 - .../downcast_method_wrong_return_type.rs | 24 - .../downcast_method_wrong_return_type.stderr | 19 - .../interface/field_double_underscored.rs | 11 +- .../interface/field_double_underscored.stderr | 22 +- .../interface/field_non_output_return_type.rs | 15 +- .../field_non_output_return_type.stderr | 12 +- .../fail/interface/fields_duplicate.rs | 19 +- .../fail/interface/fields_duplicate.stderr | 33 +- .../interface/implementer_non_object_type.rs | 20 - .../implementer_non_object_type.stderr | 15 - .../implementers_duplicate_pretty.rs | 9 +- .../implementers_duplicate_pretty.stderr | 16 +- .../interface/implementers_duplicate_ugly.rs | 9 +- .../implementers_duplicate_ugly.stderr | 14 +- .../fail/interface/name_double_underscored.rs | 4 +- .../codegen_fail/fail/interface/no_fields.rs | 13 +- .../fail/interface/no_fields.stderr | 22 +- .../{wrong_item.rs => wrong_item_enum.rs} | 0 ...ong_item.stderr => wrong_item_enum.stderr} | 4 +- .../fail/interface/wrong_item_impl_block.rs | 11 + .../interface/wrong_item_impl_block.stderr | 7 + .../src/codegen/interface_attr.rs | 59 +- .../juniper_tests/src/issue_407.rs | 14 - .../juniper_tests/src/issue_798.rs | 14 - .../juniper_tests/src/issue_922.rs | 22 - .../src/executor_tests/interfaces_unions.rs | 54 +- .../src/executor_tests/introspection/mod.rs | 7 +- juniper/src/lib.rs | 5 +- juniper/src/tests/fixtures/starwars/schema.rs | 2 - juniper_codegen/src/common/field/mod.rs | 139 +- juniper_codegen/src/common/scalar.rs | 16 - juniper_codegen/src/graphql_interface/attr.rs | 571 +----- juniper_codegen/src/graphql_interface/mod.rs | 1673 ++++++----------- juniper_codegen/src/graphql_interface/new.rs | 1204 ------------ juniper_codegen/src/lib.rs | 8 - 45 files changed, 758 insertions(+), 3525 deletions(-) delete mode 100644 integration_tests/codegen_fail/fail/interface/downcast_method_conflicts_with_external_downcast_fn.rs delete mode 100644 integration_tests/codegen_fail/fail/interface/downcast_method_conflicts_with_external_downcast_fn.stderr delete mode 100644 integration_tests/codegen_fail/fail/interface/downcast_method_wrong_input_args.rs delete mode 100644 integration_tests/codegen_fail/fail/interface/downcast_method_wrong_input_args.stderr delete mode 100644 integration_tests/codegen_fail/fail/interface/downcast_method_wrong_return_type.rs delete mode 100644 integration_tests/codegen_fail/fail/interface/downcast_method_wrong_return_type.stderr delete mode 100644 integration_tests/codegen_fail/fail/interface/implementer_non_object_type.rs delete mode 100644 integration_tests/codegen_fail/fail/interface/implementer_non_object_type.stderr rename integration_tests/codegen_fail/fail/interface/{wrong_item.rs => wrong_item_enum.rs} (100%) rename integration_tests/codegen_fail/fail/interface/{wrong_item.stderr => wrong_item_enum.stderr} (80%) create mode 100644 integration_tests/codegen_fail/fail/interface/wrong_item_impl_block.rs create mode 100644 integration_tests/codegen_fail/fail/interface/wrong_item_impl_block.stderr delete mode 100644 juniper_codegen/src/graphql_interface/new.rs diff --git a/integration_tests/codegen_fail/fail/interface/argument_double_underscored.rs b/integration_tests/codegen_fail/fail/interface/argument_double_underscored.rs index d7227c381..b842c7ce4 100644 --- a/integration_tests/codegen_fail/fail/interface/argument_double_underscored.rs +++ b/integration_tests/codegen_fail/fail/interface/argument_double_underscored.rs @@ -1,15 +1,6 @@ -use juniper::{graphql_interface, GraphQLObject}; - -#[derive(GraphQLObject)] -#[graphql(impl = CharacterValue)] -pub struct ObjA { - test: String, -} +use juniper::graphql_interface; #[graphql_interface] -impl Character for ObjA {} - -#[graphql_interface(for = ObjA)] trait Character { fn id(&self, __num: i32) -> &str { "funA" diff --git a/integration_tests/codegen_fail/fail/interface/argument_double_underscored.stderr b/integration_tests/codegen_fail/fail/interface/argument_double_underscored.stderr index 0c47e6158..a75859459 100644 --- a/integration_tests/codegen_fail/fail/interface/argument_double_underscored.stderr +++ b/integration_tests/codegen_fail/fail/interface/argument_double_underscored.stderr @@ -1,19 +1,7 @@ error: All types and directives defined within a schema must not have a name which begins with `__` (two underscores), as this is used exclusively by GraphQL’s introspection system. - --> fail/interface/argument_double_underscored.rs:14:18 - | -14 | fn id(&self, __num: i32) -> &str { - | ^^^^^ - | - = note: https://spec.graphql.org/June2018/#sec-Schema - -error[E0412]: cannot find type `CharacterValue` in this scope - --> fail/interface/argument_double_underscored.rs:4:18 + --> fail/interface/argument_double_underscored.rs:5:18 | -4 | #[graphql(impl = CharacterValue)] - | ^^^^^^^^^^^^^^ not found in this scope - -error[E0405]: cannot find trait `Character` in this scope - --> fail/interface/argument_double_underscored.rs:10:6 - | -10 | impl Character for ObjA {} - | ^^^^^^^^^ not found in this scope +5 | fn id(&self, __num: i32) -> &str { + | ^^^^^ + | + = note: https://spec.graphql.org/June2018/#sec-Schema diff --git a/integration_tests/codegen_fail/fail/interface/argument_non_input_type.rs b/integration_tests/codegen_fail/fail/interface/argument_non_input_type.rs index db6ad3002..abca82751 100644 --- a/integration_tests/codegen_fail/fail/interface/argument_non_input_type.rs +++ b/integration_tests/codegen_fail/fail/interface/argument_non_input_type.rs @@ -1,19 +1,11 @@ use juniper::{graphql_interface, GraphQLObject}; #[derive(GraphQLObject)] -#[graphql(impl = CharacterValue)] pub struct ObjA { test: String, } #[graphql_interface] -impl Character for ObjA { - fn id(&self, obj: Self) -> &str { - "funA" - } -} - -#[graphql_interface(for = ObjA)] trait Character { fn id(&self, obj: ObjA) -> &str; } diff --git a/integration_tests/codegen_fail/fail/interface/argument_non_input_type.stderr b/integration_tests/codegen_fail/fail/interface/argument_non_input_type.stderr index 8b87b4023..e20698e5b 100644 --- a/integration_tests/codegen_fail/fail/interface/argument_non_input_type.stderr +++ b/integration_tests/codegen_fail/fail/interface/argument_non_input_type.stderr @@ -1,16 +1,16 @@ error[E0277]: the trait bound `ObjA: IsInputType<__S>` is not satisfied - --> fail/interface/argument_non_input_type.rs:16:1 - | -16 | #[graphql_interface(for = ObjA)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `IsInputType<__S>` is not implemented for `ObjA` - | - = note: this error originates in the attribute macro `graphql_interface` (in Nightly builds, run with -Z macro-backtrace for more info) + --> fail/interface/argument_non_input_type.rs:8:1 + | +8 | #[graphql_interface] + | ^^^^^^^^^^^^^^^^^^^^ the trait `IsInputType<__S>` is not implemented for `ObjA` + | + = note: this error originates in the attribute macro `graphql_interface` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: the trait bound `ObjA: FromInputValue<__S>` is not satisfied - --> fail/interface/argument_non_input_type.rs:16:1 + --> fail/interface/argument_non_input_type.rs:8:1 | -16 | #[graphql_interface(for = ObjA)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `FromInputValue<__S>` is not implemented for `ObjA` +8 | #[graphql_interface] + | ^^^^^^^^^^^^^^^^^^^^ the trait `FromInputValue<__S>` is not implemented for `ObjA` | note: required by a bound in `Registry::<'r, S>::arg` --> $WORKSPACE/juniper/src/executor/mod.rs @@ -18,11 +18,3 @@ note: required by a bound in `Registry::<'r, S>::arg` | T: GraphQLType + FromInputValue, | ^^^^^^^^^^^^^^^^^ required by this bound in `Registry::<'r, S>::arg` = note: this error originates in the attribute macro `graphql_interface` (in Nightly builds, run with -Z macro-backtrace for more info) - -error[E0277]: the trait bound `ObjA: FromInputValue<__S>` is not satisfied - --> fail/interface/argument_non_input_type.rs:16:1 - | -16 | #[graphql_interface(for = ObjA)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `FromInputValue<__S>` is not implemented for `ObjA` - | - = note: this error originates in the attribute macro `graphql_interface` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/integration_tests/codegen_fail/fail/interface/argument_wrong_default_array.rs b/integration_tests/codegen_fail/fail/interface/argument_wrong_default_array.rs index 85156a3eb..48ce93738 100644 --- a/integration_tests/codegen_fail/fail/interface/argument_wrong_default_array.rs +++ b/integration_tests/codegen_fail/fail/interface/argument_wrong_default_array.rs @@ -1,21 +1,8 @@ -use juniper::{graphql_interface, GraphQLObject}; - -#[derive(GraphQLObject)] -#[graphql(impl = CharacterValue)] -pub struct ObjA { - test: String, -} +use juniper::graphql_interface; #[graphql_interface] -impl Character for ObjA {} - -#[graphql_interface(for = ObjA)] trait Character { - fn wrong( - &self, - #[graphql(default = [true, false, false])] - input: [bool; 2], - ) -> bool { + fn wrong(&self, #[graphql(default = [true, false, false])] input: [bool; 2]) -> bool { input[0] } } diff --git a/integration_tests/codegen_fail/fail/interface/argument_wrong_default_array.stderr b/integration_tests/codegen_fail/fail/interface/argument_wrong_default_array.stderr index 0b149ee54..b745a82d0 100644 --- a/integration_tests/codegen_fail/fail/interface/argument_wrong_default_array.stderr +++ b/integration_tests/codegen_fail/fail/interface/argument_wrong_default_array.stderr @@ -1,11 +1,11 @@ error[E0277]: the trait bound `[bool; 2]: From<[bool; 3]>` is not satisfied - --> fail/interface/argument_wrong_default_array.rs:12:1 - | -12 | #[graphql_interface(for = ObjA)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `From<[bool; 3]>` is not implemented for `[bool; 2]` - | - = help: the following implementations were found: - <[T; LANES] as From>> - <[bool; LANES] as From>> - = note: required because of the requirements on the impl of `Into<[bool; 2]>` for `[bool; 3]` - = note: this error originates in the attribute macro `graphql_interface` (in Nightly builds, run with -Z macro-backtrace for more info) + --> fail/interface/argument_wrong_default_array.rs:3:1 + | +3 | #[graphql_interface] + | ^^^^^^^^^^^^^^^^^^^^ the trait `From<[bool; 3]>` is not implemented for `[bool; 2]` + | + = help: the following implementations were found: + <[T; LANES] as From>> + <[bool; LANES] as From>> + = note: required because of the requirements on the impl of `Into<[bool; 2]>` for `[bool; 3]` + = note: this error originates in the attribute macro `graphql_interface` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/integration_tests/codegen_fail/fail/interface/downcast_method_conflicts_with_external_downcast_fn.rs b/integration_tests/codegen_fail/fail/interface/downcast_method_conflicts_with_external_downcast_fn.rs deleted file mode 100644 index fdc9dfb08..000000000 --- a/integration_tests/codegen_fail/fail/interface/downcast_method_conflicts_with_external_downcast_fn.rs +++ /dev/null @@ -1,29 +0,0 @@ -use juniper::{graphql_interface, GraphQLObject}; - -#[derive(GraphQLObject)] -#[graphql(impl = CharacterValue)] -pub struct ObjA { - test: String, -} - -#[graphql_interface] -impl Character for ObjA { - fn id(&self, _: i32) -> &str { - "funA" - } - - fn as_obja(&self) -> Option<&ObjA> { - Some(self) - } -} - -#[graphql_interface(for = ObjA)] -#[graphql_interface(on ObjA = downcast_obja)] -trait Character { - fn id(&self, num: i32) -> &str; - - #[graphql(downcast)] - fn as_obja(&self) -> Option<&ObjA>; -} - -fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/downcast_method_conflicts_with_external_downcast_fn.stderr b/integration_tests/codegen_fail/fail/interface/downcast_method_conflicts_with_external_downcast_fn.stderr deleted file mode 100644 index 1aede59e0..000000000 --- a/integration_tests/codegen_fail/fail/interface/downcast_method_conflicts_with_external_downcast_fn.stderr +++ /dev/null @@ -1,20 +0,0 @@ -error: GraphQL interface trait method `as_obja` conflicts with the external downcast function `downcast_obja` declared on the trait to downcast into the implementer type `ObjA` - --> fail/interface/downcast_method_conflicts_with_external_downcast_fn.rs:26:5 - | -26 | fn as_obja(&self) -> Option<&ObjA>; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = note: https://spec.graphql.org/June2018/#sec-Interfaces - = note: use `#[graphql(ignore)]` attribute argument to ignore this trait method for interface implementers downcasting - -error[E0412]: cannot find type `CharacterValue` in this scope - --> fail/interface/downcast_method_conflicts_with_external_downcast_fn.rs:4:18 - | -4 | #[graphql(impl = CharacterValue)] - | ^^^^^^^^^^^^^^ not found in this scope - -error[E0405]: cannot find trait `Character` in this scope - --> fail/interface/downcast_method_conflicts_with_external_downcast_fn.rs:10:6 - | -10 | impl Character for ObjA { - | ^^^^^^^^^ not found in this scope diff --git a/integration_tests/codegen_fail/fail/interface/downcast_method_wrong_input_args.rs b/integration_tests/codegen_fail/fail/interface/downcast_method_wrong_input_args.rs deleted file mode 100644 index 29cfc225a..000000000 --- a/integration_tests/codegen_fail/fail/interface/downcast_method_wrong_input_args.rs +++ /dev/null @@ -1,24 +0,0 @@ -use juniper::{graphql_interface, GraphQLObject}; - -#[graphql_interface(for = Human)] -trait Character { - fn id(&self) -> i32 { - 0 - } - - #[graphql(downcast)] - fn a(&self, ctx: &(), rand: u8) -> Option<&Human> { - None - } -} - -#[derive(GraphQLObject)] -#[graphql(impl = CharacterValue)] -pub struct Human { - id: String, -} - -#[graphql_interface] -impl Character for Human {} - -fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/downcast_method_wrong_input_args.stderr b/integration_tests/codegen_fail/fail/interface/downcast_method_wrong_input_args.stderr deleted file mode 100644 index ee9e48343..000000000 --- a/integration_tests/codegen_fail/fail/interface/downcast_method_wrong_input_args.stderr +++ /dev/null @@ -1,19 +0,0 @@ -error: GraphQL interface expects trait method to accept `&self` only and, optionally, `&Context` - --> fail/interface/downcast_method_wrong_input_args.rs:10:10 - | -10 | fn a(&self, ctx: &(), rand: u8) -> Option<&Human> { - | ^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = note: https://spec.graphql.org/June2018/#sec-Interfaces - -error[E0412]: cannot find type `CharacterValue` in this scope - --> fail/interface/downcast_method_wrong_input_args.rs:16:18 - | -16 | #[graphql(impl = CharacterValue)] - | ^^^^^^^^^^^^^^ not found in this scope - -error[E0405]: cannot find trait `Character` in this scope - --> fail/interface/downcast_method_wrong_input_args.rs:22:6 - | -22 | impl Character for Human {} - | ^^^^^^^^^ not found in this scope diff --git a/integration_tests/codegen_fail/fail/interface/downcast_method_wrong_return_type.rs b/integration_tests/codegen_fail/fail/interface/downcast_method_wrong_return_type.rs deleted file mode 100644 index 2fd26790e..000000000 --- a/integration_tests/codegen_fail/fail/interface/downcast_method_wrong_return_type.rs +++ /dev/null @@ -1,24 +0,0 @@ -use juniper::{graphql_interface, GraphQLObject}; - -#[graphql_interface(for = Human)] -trait Character { - fn id(&self) -> i32 { - 0 - } - - #[graphql(downcast)] - fn a(&self, ctx: &(), rand: u8) -> &Human { - unimplemented!() - } -} - -#[derive(GraphQLObject)] -#[graphql(impl = CharacterValue)] -pub struct Human { - id: String, -} - -#[graphql_interface] -impl Character for Human {} - -fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/downcast_method_wrong_return_type.stderr b/integration_tests/codegen_fail/fail/interface/downcast_method_wrong_return_type.stderr deleted file mode 100644 index 22aa7aba2..000000000 --- a/integration_tests/codegen_fail/fail/interface/downcast_method_wrong_return_type.stderr +++ /dev/null @@ -1,19 +0,0 @@ -error: GraphQL interface expects trait method return type to be `Option<&ImplementerType>` only - --> fail/interface/downcast_method_wrong_return_type.rs:10:40 - | -10 | fn a(&self, ctx: &(), rand: u8) -> &Human { - | ^^^^^^ - | - = note: https://spec.graphql.org/June2018/#sec-Interfaces - -error[E0412]: cannot find type `CharacterValue` in this scope - --> fail/interface/downcast_method_wrong_return_type.rs:16:18 - | -16 | #[graphql(impl = CharacterValue)] - | ^^^^^^^^^^^^^^ not found in this scope - -error[E0405]: cannot find trait `Character` in this scope - --> fail/interface/downcast_method_wrong_return_type.rs:22:6 - | -22 | impl Character for Human {} - | ^^^^^^^^^ not found in this scope diff --git a/integration_tests/codegen_fail/fail/interface/field_double_underscored.rs b/integration_tests/codegen_fail/fail/interface/field_double_underscored.rs index 43115d327..3096cd73a 100644 --- a/integration_tests/codegen_fail/fail/interface/field_double_underscored.rs +++ b/integration_tests/codegen_fail/fail/interface/field_double_underscored.rs @@ -1,15 +1,6 @@ -use juniper::{graphql_interface, GraphQLObject}; - -#[derive(GraphQLObject)] -#[graphql(impl = CharacterValue)] -pub struct ObjA { - test: String, -} +use juniper::graphql_interface; #[graphql_interface] -impl Character for ObjA {} - -#[graphql_interface(for = ObjA)] trait Character { fn __id(&self) -> &str { "funA" diff --git a/integration_tests/codegen_fail/fail/interface/field_double_underscored.stderr b/integration_tests/codegen_fail/fail/interface/field_double_underscored.stderr index 2cdfafa6d..ef662d93d 100644 --- a/integration_tests/codegen_fail/fail/interface/field_double_underscored.stderr +++ b/integration_tests/codegen_fail/fail/interface/field_double_underscored.stderr @@ -1,19 +1,7 @@ error: All types and directives defined within a schema must not have a name which begins with `__` (two underscores), as this is used exclusively by GraphQL’s introspection system. - --> fail/interface/field_double_underscored.rs:14:8 - | -14 | fn __id(&self) -> &str { - | ^^^^ - | - = note: https://spec.graphql.org/June2018/#sec-Schema - -error[E0412]: cannot find type `CharacterValue` in this scope - --> fail/interface/field_double_underscored.rs:4:18 + --> fail/interface/field_double_underscored.rs:5:8 | -4 | #[graphql(impl = CharacterValue)] - | ^^^^^^^^^^^^^^ not found in this scope - -error[E0405]: cannot find trait `Character` in this scope - --> fail/interface/field_double_underscored.rs:10:6 - | -10 | impl Character for ObjA {} - | ^^^^^^^^^ not found in this scope +5 | fn __id(&self) -> &str { + | ^^^^ + | + = note: https://spec.graphql.org/June2018/#sec-Schema diff --git a/integration_tests/codegen_fail/fail/interface/field_non_output_return_type.rs b/integration_tests/codegen_fail/fail/interface/field_non_output_return_type.rs index a860ace35..ad802cf59 100644 --- a/integration_tests/codegen_fail/fail/interface/field_non_output_return_type.rs +++ b/integration_tests/codegen_fail/fail/interface/field_non_output_return_type.rs @@ -1,24 +1,13 @@ -use juniper::{graphql_interface, GraphQLInputObject, GraphQLObject}; +use juniper::{graphql_interface, GraphQLInputObject}; #[derive(GraphQLInputObject)] pub struct ObjB { id: i32, } -#[derive(GraphQLObject)] -#[graphql(impl = CharacterValue)] -pub struct ObjA { - test: String, -} - #[graphql_interface] -impl Character for ObjA {} - -#[graphql_interface(for = ObjA)] trait Character { - fn id(&self) -> ObjB { - ObjB { id: 34 } - } + fn id(&self) -> ObjB; } fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/field_non_output_return_type.stderr b/integration_tests/codegen_fail/fail/interface/field_non_output_return_type.stderr index 1491540eb..5bd7c8c9c 100644 --- a/integration_tests/codegen_fail/fail/interface/field_non_output_return_type.stderr +++ b/integration_tests/codegen_fail/fail/interface/field_non_output_return_type.stderr @@ -1,7 +1,7 @@ error[E0277]: the trait bound `ObjB: IsOutputType<__S>` is not satisfied - --> fail/interface/field_non_output_return_type.rs:17:1 - | -17 | #[graphql_interface(for = ObjA)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `IsOutputType<__S>` is not implemented for `ObjB` - | - = note: this error originates in the attribute macro `graphql_interface` (in Nightly builds, run with -Z macro-backtrace for more info) + --> fail/interface/field_non_output_return_type.rs:8:1 + | +8 | #[graphql_interface] + | ^^^^^^^^^^^^^^^^^^^^ the trait `IsOutputType<__S>` is not implemented for `ObjB` + | + = note: this error originates in the attribute macro `graphql_interface` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/integration_tests/codegen_fail/fail/interface/fields_duplicate.rs b/integration_tests/codegen_fail/fail/interface/fields_duplicate.rs index aba568b36..b69704d27 100644 --- a/integration_tests/codegen_fail/fail/interface/fields_duplicate.rs +++ b/integration_tests/codegen_fail/fail/interface/fields_duplicate.rs @@ -1,24 +1,11 @@ -use juniper::{graphql_interface, GraphQLObject}; - -#[derive(GraphQLObject)] -#[graphql(impl = CharacterValue)] -pub struct ObjA { - test: String, -} +use juniper::graphql_interface; #[graphql_interface] -impl Character for ObjA {} - -#[graphql_interface(for = ObjA)] trait Character { - fn id(&self) -> &str { - "funA" - } + fn id(&self) -> &str; #[graphql(name = "id")] - fn id2(&self) -> &str { - "funB" - } + fn id2(&self) -> &str; } fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/fields_duplicate.stderr b/integration_tests/codegen_fail/fail/interface/fields_duplicate.stderr index 284a3e1d5..143f85dab 100644 --- a/integration_tests/codegen_fail/fail/interface/fields_duplicate.stderr +++ b/integration_tests/codegen_fail/fail/interface/fields_duplicate.stderr @@ -1,25 +1,12 @@ error: GraphQL interface must have a different name for each field - --> fail/interface/fields_duplicate.rs:13:1 - | -13 | / trait Character { -14 | | fn id(&self) -> &str { -15 | | "funA" -16 | | } -... | -21 | | } -22 | | } - | |_^ - | - = note: https://spec.graphql.org/June2018/#sec-Interfaces - -error[E0412]: cannot find type `CharacterValue` in this scope - --> fail/interface/fields_duplicate.rs:4:18 + --> fail/interface/fields_duplicate.rs:4:1 | -4 | #[graphql(impl = CharacterValue)] - | ^^^^^^^^^^^^^^ not found in this scope - -error[E0405]: cannot find trait `Character` in this scope - --> fail/interface/fields_duplicate.rs:10:6 - | -10 | impl Character for ObjA {} - | ^^^^^^^^^ not found in this scope +4 | / trait Character { +5 | | fn id(&self) -> &str; +6 | | +7 | | #[graphql(name = "id")] +8 | | fn id2(&self) -> &str; +9 | | } + | |_^ + | + = note: https://spec.graphql.org/June2018/#sec-Interfaces diff --git a/integration_tests/codegen_fail/fail/interface/implementer_non_object_type.rs b/integration_tests/codegen_fail/fail/interface/implementer_non_object_type.rs deleted file mode 100644 index 0aba2508d..000000000 --- a/integration_tests/codegen_fail/fail/interface/implementer_non_object_type.rs +++ /dev/null @@ -1,20 +0,0 @@ -use juniper::{graphql_interface, GraphQLInputObject}; - -#[derive(GraphQLInputObject)] -pub struct ObjA { - test: String, -} - -#[graphql_interface] -impl Character for ObjA { - fn id(&self) -> &str { - "funA" - } -} - -#[graphql_interface(for = ObjA)] -trait Character { - fn id(&self) -> &str; -} - -fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/implementer_non_object_type.stderr b/integration_tests/codegen_fail/fail/interface/implementer_non_object_type.stderr deleted file mode 100644 index 35c2dd0ca..000000000 --- a/integration_tests/codegen_fail/fail/interface/implementer_non_object_type.stderr +++ /dev/null @@ -1,15 +0,0 @@ -error[E0277]: the trait bound `ObjA: GraphQLObject<__S>` is not satisfied - --> fail/interface/implementer_non_object_type.rs:15:1 - | -15 | #[graphql_interface(for = ObjA)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `GraphQLObject<__S>` is not implemented for `ObjA` - | - = note: this error originates in the attribute macro `graphql_interface` (in Nightly builds, run with -Z macro-backtrace for more info) - -error[E0277]: the trait bound `ObjA: IsOutputType<__S>` is not satisfied - --> fail/interface/implementer_non_object_type.rs:15:1 - | -15 | #[graphql_interface(for = ObjA)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `IsOutputType<__S>` is not implemented for `ObjA` - | - = note: this error originates in the attribute macro `graphql_interface` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/integration_tests/codegen_fail/fail/interface/implementers_duplicate_pretty.rs b/integration_tests/codegen_fail/fail/interface/implementers_duplicate_pretty.rs index fbdc8f12e..889fbd016 100644 --- a/integration_tests/codegen_fail/fail/interface/implementers_duplicate_pretty.rs +++ b/integration_tests/codegen_fail/fail/interface/implementers_duplicate_pretty.rs @@ -3,14 +3,7 @@ use juniper::{graphql_interface, GraphQLObject}; #[derive(GraphQLObject)] #[graphql(impl = CharacterValue)] pub struct ObjA { - test: String, -} - -#[graphql_interface] -impl Character for ObjA { - fn id(&self) -> &str { - "funA" - } + id: String, } #[graphql_interface(for = [ObjA, ObjA])] diff --git a/integration_tests/codegen_fail/fail/interface/implementers_duplicate_pretty.stderr b/integration_tests/codegen_fail/fail/interface/implementers_duplicate_pretty.stderr index 40db273e9..ce72fa2d7 100644 --- a/integration_tests/codegen_fail/fail/interface/implementers_duplicate_pretty.stderr +++ b/integration_tests/codegen_fail/fail/interface/implementers_duplicate_pretty.stderr @@ -1,17 +1,11 @@ error: duplicated attribute argument found - --> $DIR/implementers_duplicate_pretty.rs:16:34 - | -16 | #[graphql_interface(for = [ObjA, ObjA])] - | ^^^^ + --> fail/interface/implementers_duplicate_pretty.rs:9:34 + | +9 | #[graphql_interface(for = [ObjA, ObjA])] + | ^^^^ error[E0412]: cannot find type `CharacterValue` in this scope - --> $DIR/implementers_duplicate_pretty.rs:4:18 + --> fail/interface/implementers_duplicate_pretty.rs:4:18 | 4 | #[graphql(impl = CharacterValue)] | ^^^^^^^^^^^^^^ not found in this scope - -error[E0405]: cannot find trait `Character` in this scope - --> $DIR/implementers_duplicate_pretty.rs:10:6 - | -10 | impl Character for ObjA { - | ^^^^^^^^^ not found in this scope diff --git a/integration_tests/codegen_fail/fail/interface/implementers_duplicate_ugly.rs b/integration_tests/codegen_fail/fail/interface/implementers_duplicate_ugly.rs index 1acb56c01..a0b7c7ed0 100644 --- a/integration_tests/codegen_fail/fail/interface/implementers_duplicate_ugly.rs +++ b/integration_tests/codegen_fail/fail/interface/implementers_duplicate_ugly.rs @@ -3,18 +3,11 @@ use juniper::{graphql_interface, GraphQLObject}; #[derive(GraphQLObject)] #[graphql(impl = CharacterValue)] pub struct ObjA { - test: String, + id: String, } type ObjAlias = ObjA; -#[graphql_interface] -impl Character for ObjA { - fn id(&self) -> &str { - "funA" - } -} - #[graphql_interface(for = [ObjA, ObjAlias])] trait Character { fn id(&self) -> &str; diff --git a/integration_tests/codegen_fail/fail/interface/implementers_duplicate_ugly.stderr b/integration_tests/codegen_fail/fail/interface/implementers_duplicate_ugly.stderr index d06dda364..61340f732 100644 --- a/integration_tests/codegen_fail/fail/interface/implementers_duplicate_ugly.stderr +++ b/integration_tests/codegen_fail/fail/interface/implementers_duplicate_ugly.stderr @@ -1,7 +1,7 @@ -error[E0119]: conflicting implementations of trait `>::mark::_::{closure#0}::MutuallyExclusive` for type `ObjA` - --> $DIR/implementers_duplicate_ugly.rs:18:1 +error[E0119]: conflicting implementations of trait ` as juniper::GraphQLInterface<__S>>::mark::_::{closure#0}::MutuallyExclusive` for type `ObjA` + --> fail/interface/implementers_duplicate_ugly.rs:11:1 | -18 | #[graphql_interface(for = [ObjA, ObjAlias])] +11 | #[graphql_interface(for = [ObjA, ObjAlias])] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | | | first implementation here @@ -9,13 +9,13 @@ error[E0119]: conflicting implementations of trait `` for type `CharacterValue` - --> $DIR/implementers_duplicate_ugly.rs:18:1 +error[E0119]: conflicting implementations of trait `std::convert::From` for type `CharacterValueEnum` + --> fail/interface/implementers_duplicate_ugly.rs:11:1 | -18 | #[graphql_interface(for = [ObjA, ObjAlias])] +11 | #[graphql_interface(for = [ObjA, ObjAlias])] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | | | first implementation here - | conflicting implementation for `CharacterValue` + | conflicting implementation for `CharacterValueEnum` | = note: this error originates in the attribute macro `graphql_interface` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/integration_tests/codegen_fail/fail/interface/name_double_underscored.rs b/integration_tests/codegen_fail/fail/interface/name_double_underscored.rs index b233119f1..77343a973 100644 --- a/integration_tests/codegen_fail/fail/interface/name_double_underscored.rs +++ b/integration_tests/codegen_fail/fail/interface/name_double_underscored.rs @@ -2,9 +2,7 @@ use juniper::graphql_interface; #[graphql_interface] trait __Character { - fn id(&self) -> &str { - "funA" - } + fn id(&self) -> &str; } fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/no_fields.rs b/integration_tests/codegen_fail/fail/interface/no_fields.rs index 115960359..1308cdde8 100644 --- a/integration_tests/codegen_fail/fail/interface/no_fields.rs +++ b/integration_tests/codegen_fail/fail/interface/no_fields.rs @@ -1,15 +1,6 @@ -use juniper::{graphql_interface, GraphQLObject}; - -#[derive(GraphQLObject)] -#[graphql(impl = CharacterValue)] -pub struct ObjA { - test: String, -} +use juniper::graphql_interface; #[graphql_interface] -impl Character for ObjA {} - -#[graphql_interface(for = ObjA)] trait Character {} -fn main() {} \ No newline at end of file +fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/no_fields.stderr b/integration_tests/codegen_fail/fail/interface/no_fields.stderr index ddab0d4cc..70ab57452 100644 --- a/integration_tests/codegen_fail/fail/interface/no_fields.stderr +++ b/integration_tests/codegen_fail/fail/interface/no_fields.stderr @@ -1,19 +1,7 @@ error: GraphQL interface must have at least one field - --> fail/interface/no_fields.rs:13:1 - | -13 | trait Character {} - | ^^^^^^^^^^^^^^^^^^ - | - = note: https://spec.graphql.org/June2018/#sec-Interfaces - -error[E0412]: cannot find type `CharacterValue` in this scope - --> fail/interface/no_fields.rs:4:18 + --> fail/interface/no_fields.rs:4:1 | -4 | #[graphql(impl = CharacterValue)] - | ^^^^^^^^^^^^^^ not found in this scope - -error[E0405]: cannot find trait `Character` in this scope - --> fail/interface/no_fields.rs:10:6 - | -10 | impl Character for ObjA {} - | ^^^^^^^^^ not found in this scope +4 | trait Character {} + | ^^^^^^^^^^^^^^^^^^ + | + = note: https://spec.graphql.org/June2018/#sec-Interfaces diff --git a/integration_tests/codegen_fail/fail/interface/wrong_item.rs b/integration_tests/codegen_fail/fail/interface/wrong_item_enum.rs similarity index 100% rename from integration_tests/codegen_fail/fail/interface/wrong_item.rs rename to integration_tests/codegen_fail/fail/interface/wrong_item_enum.rs diff --git a/integration_tests/codegen_fail/fail/interface/wrong_item.stderr b/integration_tests/codegen_fail/fail/interface/wrong_item_enum.stderr similarity index 80% rename from integration_tests/codegen_fail/fail/interface/wrong_item.stderr rename to integration_tests/codegen_fail/fail/interface/wrong_item_enum.stderr index a86b15c20..9c2607dd4 100644 --- a/integration_tests/codegen_fail/fail/interface/wrong_item.stderr +++ b/integration_tests/codegen_fail/fail/interface/wrong_item_enum.stderr @@ -1,5 +1,5 @@ -error: #[graphql_interface] attribute is applicable to trait definitions and trait implementations only - --> $DIR/wrong_item.rs:8:1 +error: #[graphql_interface] attribute is applicable to trait definitions only + --> fail/interface/wrong_item_enum.rs:8:1 | 8 | #[graphql_interface(for = ObjA)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/integration_tests/codegen_fail/fail/interface/wrong_item_impl_block.rs b/integration_tests/codegen_fail/fail/interface/wrong_item_impl_block.rs new file mode 100644 index 000000000..61e142410 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/wrong_item_impl_block.rs @@ -0,0 +1,11 @@ +use juniper::{graphql_interface, GraphQLObject}; + +#[derive(GraphQLObject)] +pub struct ObjA { + test: String, +} + +#[graphql_interface(for = ObjA)] +impl ObjA {} + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/wrong_item_impl_block.stderr b/integration_tests/codegen_fail/fail/interface/wrong_item_impl_block.stderr new file mode 100644 index 000000000..712206c0b --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/wrong_item_impl_block.stderr @@ -0,0 +1,7 @@ +error: #[graphql_interface] attribute is applicable to trait definitions only + --> fail/interface/wrong_item_impl_block.rs:8:1 + | +8 | #[graphql_interface(for = ObjA)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this error originates in the attribute macro `graphql_interface` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/integration_tests/juniper_tests/src/codegen/interface_attr.rs b/integration_tests/juniper_tests/src/codegen/interface_attr.rs index 7afcfe047..c3b453804 100644 --- a/integration_tests/juniper_tests/src/codegen/interface_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/interface_attr.rs @@ -1,10 +1,9 @@ //! Tests for `#[graphql_interface]` macro. use juniper::{ - execute, graphql_interface_new, graphql_object, graphql_value, graphql_vars, - DefaultScalarValue, EmptyMutation, EmptySubscription, Executor, FieldError, FieldResult, - GraphQLInputObject, GraphQLObject, GraphQLType, GraphQLUnion, IntoFieldError, RootNode, - ScalarValue, + execute, graphql_interface, graphql_object, graphql_value, graphql_vars, DefaultScalarValue, + EmptyMutation, EmptySubscription, Executor, FieldError, FieldResult, GraphQLInputObject, + GraphQLObject, GraphQLType, GraphQLUnion, IntoFieldError, RootNode, ScalarValue, }; fn schema<'q, C, Q>(query_root: Q) -> RootNode<'q, Q, EmptyMutation, EmptySubscription> @@ -35,7 +34,7 @@ where mod no_implers { use super::*; - #[graphql_interface_new] + #[graphql_interface] trait Character { fn id(&self) -> &str; } @@ -101,7 +100,7 @@ mod no_implers { mod trivial { use super::*; - #[graphql_interface_new(for = [Human, Droid])] + #[graphql_interface(for = [Human, Droid])] trait Character { fn id(&self) -> &str; } @@ -325,7 +324,7 @@ mod trivial { mod explicit_alias { use super::*; - #[graphql_interface_new(enum = CharacterEnum, for = [Human, Droid])] + #[graphql_interface(enum = CharacterEnum, for = [Human, Droid])] trait Character { fn id(&self) -> &str; } @@ -495,7 +494,7 @@ mod explicit_alias { mod trivial_async { use super::*; - #[graphql_interface_new(for = [Human, Droid])] + #[graphql_interface(for = [Human, Droid])] trait Character { async fn id(&self) -> &str; } @@ -719,7 +718,7 @@ mod trivial_async { mod explicit_async { use super::*; - #[graphql_interface_new(for = [Human, Droid])] + #[graphql_interface(for = [Human, Droid])] trait Character { fn id(&self) -> &str; @@ -865,7 +864,7 @@ mod fallible_field { } } - #[graphql_interface_new(for = [Human, Droid])] + #[graphql_interface(for = [Human, Droid])] trait Character { fn id(&self) -> Result<&str, CustomError>; } @@ -1023,7 +1022,7 @@ mod fallible_field { mod generic { use super::*; - #[graphql_interface_new(for = [Human, Droid])] + #[graphql_interface(for = [Human, Droid])] trait Character { fn id(&self) -> &str; } @@ -1161,7 +1160,7 @@ mod generic { mod generic_async { use super::*; - #[graphql_interface_new(for = [Human, Droid])] + #[graphql_interface(for = [Human, Droid])] trait Character { async fn id(&self) -> &str; } @@ -1299,7 +1298,7 @@ mod generic_async { mod generic_lifetime_async { use super::*; - #[graphql_interface_new(for = [Human, Droid])] + #[graphql_interface(for = [Human, Droid])] trait Character<'me, A> { async fn id<'a>(&'a self) -> &'a str; } @@ -1437,7 +1436,7 @@ mod generic_lifetime_async { mod argument { use super::*; - #[graphql_interface_new(for = Human)] + #[graphql_interface(for = Human)] trait Character { fn id_wide(&self, is_number: bool) -> &str; @@ -1613,7 +1612,7 @@ mod default_argument { x: i32, } - #[graphql_interface_new(for = Human)] + #[graphql_interface(for = Human)] trait Character { async fn id( &self, @@ -1747,7 +1746,7 @@ mod description_from_doc_comment { use super::*; /// Rust docs. - #[graphql_interface_new(for = Human)] + #[graphql_interface(for = Human)] trait Character { /// Rust `id` docs. /// Long. @@ -1803,7 +1802,7 @@ mod description_from_doc_comment { mod deprecation_from_attr { use super::*; - #[graphql_interface_new(for = Human)] + #[graphql_interface(for = Human)] trait Character { fn id(&self) -> &str; @@ -1941,7 +1940,7 @@ mod explicit_name_description_and_deprecation { use super::*; /// Rust docs. - #[graphql_interface_new(name = "MyChar", desc = "My character.", for = Human)] + #[graphql_interface(name = "MyChar", desc = "My character.", for = Human)] trait Character { /// Rust `id` docs. #[graphql(name = "myId", desc = "My character ID.", deprecated = "Not used.")] @@ -2127,7 +2126,7 @@ mod explicit_name_description_and_deprecation { mod renamed_all_fields_and_args { use super::*; - #[graphql_interface_new(rename_all = "none", for = Human)] + #[graphql_interface(rename_all = "none", for = Human)] trait Character { fn id(&self) -> &str { "human-32" @@ -2225,8 +2224,8 @@ mod renamed_all_fields_and_args { mod explicit_scalar { use super::*; - #[graphql_interface_new(for = [Human, Droid])] - #[graphql_interface_new(scalar = DefaultScalarValue)] + #[graphql_interface(for = [Human, Droid])] + #[graphql_interface(scalar = DefaultScalarValue)] trait Character { fn id(&self) -> &str; } @@ -2350,7 +2349,7 @@ mod custom_scalar { use super::*; - #[graphql_interface_new(for = [Human, Droid], scalar = MyScalarValue)] + #[graphql_interface(for = [Human, Droid], scalar = MyScalarValue)] trait Character { async fn id(&self) -> &str; } @@ -2472,7 +2471,7 @@ mod custom_scalar { mod explicit_generic_scalar { use super::*; - #[graphql_interface_new(for = [Human, Droid], scalar = S)] + #[graphql_interface(for = [Human, Droid], scalar = S)] trait Character { fn id(&self) -> FieldResult<&str, S>; } @@ -2594,7 +2593,7 @@ mod explicit_generic_scalar { mod bounded_generic_scalar { use super::*; - #[graphql_interface_new(for = [Human, Droid], scalar = S: ScalarValue + Clone)] + #[graphql_interface(for = [Human, Droid], scalar = S: ScalarValue + Clone)] trait Character { fn id(&self) -> &str; } @@ -2720,7 +2719,7 @@ mod explicit_custom_context { impl juniper::Context for CustomContext {} - #[graphql_interface_new(for = [Human, Droid], context = CustomContext)] + #[graphql_interface(for = [Human, Droid], context = CustomContext)] trait Character { async fn id<'a>(&'a self, context: &CustomContext) -> &'a str; @@ -2886,7 +2885,7 @@ mod inferred_custom_context_from_field { impl juniper::Context for CustomContext {} - #[graphql_interface_new(for = [Human, Droid])] + #[graphql_interface(for = [Human, Droid])] trait Character { fn id<'a>(&self, context: &'a CustomContext) -> &'a str; @@ -3036,7 +3035,7 @@ mod executor { use super::*; - #[graphql_interface_new(for = [Human, Droid], scalar = S)] + #[graphql_interface(for = [Human, Droid], scalar = S)] trait Character { async fn id<'a>(&self, executor: &'a Executor<'_, '_, (), S>) -> &'a str where @@ -3216,7 +3215,7 @@ mod executor { mod ignored_method { use super::*; - #[graphql_interface_new(for = Human)] + #[graphql_interface(for = Human)] trait Character { fn id(&self) -> &str; @@ -3312,7 +3311,7 @@ mod ignored_method { mod field_return_subtyping { use super::*; - #[graphql_interface_new(for = [Human, Droid])] + #[graphql_interface(for = [Human, Droid])] trait Character { fn id(&self) -> Option; } @@ -3451,7 +3450,7 @@ mod field_return_union_subtyping { Knowledge(Knowledge), } - #[graphql_interface_new(for = [Human, Droid])] + #[graphql_interface(for = [Human, Droid])] trait Character { fn id(&self) -> Option; diff --git a/integration_tests/juniper_tests/src/issue_407.rs b/integration_tests/juniper_tests/src/issue_407.rs index 51bf4211a..245398c36 100644 --- a/integration_tests/juniper_tests/src/issue_407.rs +++ b/integration_tests/juniper_tests/src/issue_407.rs @@ -20,13 +20,6 @@ struct Human { name: String, } -#[graphql_interface] -impl Character for Human { - fn id(&self) -> &str { - &self.id - } -} - #[derive(GraphQLObject)] #[graphql(impl = CharacterValue)] struct Droid { @@ -34,13 +27,6 @@ struct Droid { serial_number: String, } -#[graphql_interface] -impl Character for Droid { - fn id(&self) -> &str { - &self.id - } -} - #[graphql_object] impl Query { fn characters() -> Vec { diff --git a/integration_tests/juniper_tests/src/issue_798.rs b/integration_tests/juniper_tests/src/issue_798.rs index fc23bbd6e..fed322fc3 100644 --- a/integration_tests/juniper_tests/src/issue_798.rs +++ b/integration_tests/juniper_tests/src/issue_798.rs @@ -18,13 +18,6 @@ struct Human { home_planet: String, } -#[graphql_interface] -impl Character for Human { - fn id(&self) -> &str { - &self.id - } -} - #[derive(GraphQLObject)] #[graphql(impl = CharacterValue)] struct Droid { @@ -32,13 +25,6 @@ struct Droid { primary_function: String, } -#[graphql_interface] -impl Character for Droid { - fn id(&self) -> &str { - &self.id - } -} - #[derive(GraphQLUnion)] enum FieldResult { Human(Human), diff --git a/integration_tests/juniper_tests/src/issue_922.rs b/integration_tests/juniper_tests/src/issue_922.rs index 60418de4a..fd28b45f9 100644 --- a/integration_tests/juniper_tests/src/issue_922.rs +++ b/integration_tests/juniper_tests/src/issue_922.rs @@ -38,17 +38,6 @@ struct Human { pub name: String, } -#[graphql_interface] -impl Character for Human { - fn id(&self) -> i32 { - self.id - } - - fn name(&self) -> String { - self.name.clone() - } -} - #[derive(GraphQLObject)] #[graphql(impl = CharacterValue)] struct Droid { @@ -56,17 +45,6 @@ struct Droid { pub name: String, } -#[graphql_interface] -impl Character for Droid { - fn id(&self) -> i32 { - self.id - } - - fn name(&self) -> String { - self.name.clone() - } -} - type Schema = juniper::RootNode<'static, Query, EmptyMutation, EmptySubscription>; #[tokio::test] diff --git a/juniper/src/executor_tests/interfaces_unions.rs b/juniper/src/executor_tests/interfaces_unions.rs index 02787ce58..1044baafd 100644 --- a/juniper/src/executor_tests/interfaces_unions.rs +++ b/juniper/src/executor_tests/interfaces_unions.rs @@ -3,72 +3,28 @@ mod interface { graphql_interface, graphql_object, schema::model::RootNode, types::scalars::{EmptyMutation, EmptySubscription}, + GraphQLObject, }; #[graphql_interface(for = [Cat, Dog])] trait Pet { fn name(&self) -> &str; - - #[graphql(downcast)] - fn as_dog(&self) -> Option<&Dog> { - None - } - #[graphql(downcast)] - fn as_cat(&self) -> Option<&Cat> { - None - } } + #[derive(GraphQLObject)] + #[graphql(impl = PetValue)] struct Dog { name: String, woofs: bool, } - #[graphql_interface] - impl Pet for Dog { - fn name(&self) -> &str { - &self.name - } - fn as_dog(&self) -> Option<&Dog> { - Some(self) - } - } - - #[graphql_object(impl = PetValue)] - impl Dog { - fn name(&self) -> &str { - &self.name - } - fn woofs(&self) -> bool { - self.woofs - } - } - + #[derive(GraphQLObject)] + #[graphql(impl = PetValue)] struct Cat { name: String, meows: bool, } - #[graphql_interface] - impl Pet for Cat { - fn name(&self) -> &str { - &self.name - } - fn as_cat(&self) -> Option<&Cat> { - Some(self) - } - } - - #[graphql_object(impl = PetValue)] - impl Cat { - fn name(&self) -> &str { - &self.name - } - fn meows(&self) -> bool { - self.meows - } - } - struct Schema { pets: Vec, } diff --git a/juniper/src/executor_tests/introspection/mod.rs b/juniper/src/executor_tests/introspection/mod.rs index a14a0f294..a5ec22cbd 100644 --- a/juniper/src/executor_tests/introspection/mod.rs +++ b/juniper/src/executor_tests/introspection/mod.rs @@ -43,9 +43,7 @@ impl GraphQLScalar for Scalar { #[graphql_interface(name = "SampleInterface", for = Root)] trait Interface { /// A sample field in the interface - fn sample_enum(&self) -> Sample { - Sample::One - } + fn sample_enum(&self) -> Sample; } struct Root; @@ -66,9 +64,6 @@ impl Root { } } -#[graphql_interface(scalar = DefaultScalarValue)] -impl Interface for Root {} - #[tokio::test] async fn test_execution() { let doc = r#" diff --git a/juniper/src/lib.rs b/juniper/src/lib.rs index c4ce64163..406879726 100644 --- a/juniper/src/lib.rs +++ b/juniper/src/lib.rs @@ -114,9 +114,8 @@ pub use futures::future::{BoxFuture, LocalBoxFuture}; // This allows users to just depend on juniper and get the derive // functionality automatically. pub use juniper_codegen::{ - graphql_interface, graphql_interface_new, graphql_object, graphql_scalar, graphql_subscription, - graphql_union, GraphQLEnum, GraphQLInputObject, GraphQLObject, GraphQLScalarValue, - GraphQLUnion, + graphql_interface, graphql_object, graphql_scalar, graphql_subscription, graphql_union, + GraphQLEnum, GraphQLInputObject, GraphQLObject, GraphQLScalarValue, GraphQLUnion, }; #[doc(hidden)] diff --git a/juniper/src/tests/fixtures/starwars/schema.rs b/juniper/src/tests/fixtures/starwars/schema.rs index d5eebbe5c..237dc6f84 100644 --- a/juniper/src/tests/fixtures/starwars/schema.rs +++ b/juniper/src/tests/fixtures/starwars/schema.rs @@ -136,7 +136,6 @@ impl Human { } } -#[graphql_interface] impl Character for Human { fn id(&self) -> &str { &self.id @@ -223,7 +222,6 @@ impl Droid { } } -#[graphql_interface] impl Character for Droid { fn id(&self) -> &str { &self.id diff --git a/juniper_codegen/src/common/field/mod.rs b/juniper_codegen/src/common/field/mod.rs index 0437f97f3..4272bffa0 100644 --- a/juniper_codegen/src/common/field/mod.rs +++ b/juniper_codegen/src/common/field/mod.rs @@ -16,7 +16,6 @@ use syn::{ use crate::{ common::{ - gen, parse::{ attr::{err, OptionExt as _}, ParseBufferExt as _, @@ -65,20 +64,6 @@ pub(crate) struct Attr { /// /// [1]: https://spec.graphql.org/June2018/#sec-Language.Fields pub(crate) ignore: Option>, - - /// Explicitly specified marker indicating that this trait method doesn't - /// represent a [GraphQL field][1], but is a downcasting function into the - /// [GraphQL object][2] implementer type returned by this trait method. - /// - /// Once this marker is specified, the [GraphQL object][2] implementer type - /// cannot be downcast via another trait method or external downcasting - /// function. - /// - /// Omit using this field if you're generating code for [GraphQL object][2]. - /// - /// [1]: https://spec.graphql.org/June2018/#sec-Language.Fields - /// [2]: https://spec.graphql.org/June2018/#sec-Objects - pub(crate) downcast: Option>, } impl Parse for Attr { @@ -119,10 +104,6 @@ impl Parse for Attr { .ignore .replace(SpanContainer::new(ident.span(), None, ident.clone())) .none_or_else(|_| err::dup_arg(&ident))?, - "downcast" => out - .downcast - .replace(SpanContainer::new(ident.span(), None, ident.clone())) - .none_or_else(|_| err::dup_arg(&ident))?, name => { return Err(err::unknown_arg(&ident, name)); } @@ -142,7 +123,6 @@ impl Attr { description: try_merge_opt!(description: self, another), deprecated: try_merge_opt!(deprecated: self, another), ignore: try_merge_opt!(ignore: self, another), - downcast: try_merge_opt!(downcast: self, another), }) } @@ -156,11 +136,7 @@ impl Attr { .try_fold(Self::default(), |prev, curr| prev.try_merge(curr?))?; if let Some(ignore) = &attr.ignore { - if attr.name.is_some() - || attr.description.is_some() - || attr.deprecated.is_some() - || attr.downcast.is_some() - { + if attr.name.is_some() || attr.description.is_some() || attr.deprecated.is_some() { return Err(syn::Error::new( ignore.span(), "`ignore` attribute argument is not composable with any other arguments", @@ -168,19 +144,6 @@ impl Attr { } } - if let Some(downcast) = &attr.downcast { - if attr.name.is_some() - || attr.description.is_some() - || attr.deprecated.is_some() - || attr.ignore.is_some() - { - return Err(syn::Error::new( - downcast.span(), - "`downcast` attribute argument is not composable with any other arguments", - )); - } - } - if attr.description.is_none() { attr.description = get_doc_comment(attrs).map(|sc| { let span = sc.span_ident(); @@ -390,106 +353,6 @@ impl Definition { } } - /// Returns generated code for the [`GraphQLValue::resolve_field`][0] - /// method, which resolves this [GraphQL field][1] synchronously. - /// - /// Returns [`None`] if this [`Definition::is_async`]. - /// - /// [0]: juniper::GraphQLValue::resolve_field - /// [1]: https://spec.graphql.org/June2018/#sec-Language.Fields - #[must_use] - pub(crate) fn method_resolve_field_tokens( - &self, - scalar: &scalar::Type, - trait_ty: Option<&syn::Type>, - ) -> Option { - if self.is_async { - return None; - } - - let (name, mut ty, ident) = (&self.name, self.ty.clone(), &self.ident); - - let res = if self.is_method() { - let args = self - .arguments - .as_ref() - .unwrap() - .iter() - .map(|arg| arg.method_resolve_field_tokens(scalar, false)); - - let rcv = self.has_receiver.then(|| { - quote! { self, } - }); - - if trait_ty.is_some() { - quote! { ::#ident(#rcv #( #args ),*) } - } else { - quote! { Self::#ident(#rcv #( #args ),*) } - } - } else { - ty = parse_quote! { _ }; - quote! { &self.#ident } - }; - - let resolving_code = gen::sync_resolving_code(); - - Some(quote! { - #name => { - let res: #ty = #res; - #resolving_code - } - }) - } - - /// Returns generated code for the - /// [`GraphQLValueAsync::resolve_field_async`][0] method, which resolves - /// this [GraphQL field][1] asynchronously. - /// - /// [0]: juniper::GraphQLValueAsync::resolve_field_async - /// [1]: https://spec.graphql.org/June2018/#sec-Language.Fields - #[must_use] - pub(crate) fn method_resolve_field_async_tokens( - &self, - scalar: &scalar::Type, - trait_ty: Option<&syn::Type>, - ) -> TokenStream { - let (name, mut ty, ident) = (&self.name, self.ty.clone(), &self.ident); - - let mut fut = if self.is_method() { - let args = self - .arguments - .as_ref() - .unwrap() - .iter() - .map(|arg| arg.method_resolve_field_tokens(scalar, true)); - - let rcv = self.has_receiver.then(|| { - quote! { self, } - }); - - if trait_ty.is_some() { - quote! { ::#ident(#rcv #( #args ),*) } - } else { - quote! { Self::#ident(#rcv #( #args ),*) } - } - } else { - ty = parse_quote! { _ }; - quote! { &self.#ident } - }; - if !self.is_async { - fut = quote! { ::juniper::futures::future::ready(#fut) }; - } - - let resolving_code = gen::async_resolving_code(Some(&ty)); - - quote! { - #name => { - let fut = #fut; - #resolving_code - } - } - } - /// Returns generated code for the /// [`GraphQLSubscriptionValue::resolve_field_into_stream`][0] method, which /// resolves this [GraphQL field][1] as [subscription][2]. diff --git a/juniper_codegen/src/common/scalar.rs b/juniper_codegen/src/common/scalar.rs index 1569a5391..8f11833de 100644 --- a/juniper_codegen/src/common/scalar.rs +++ b/juniper_codegen/src/common/scalar.rs @@ -90,12 +90,6 @@ impl Type { matches!(self, Self::ExplicitGeneric(_) | Self::ImplicitGeneric(_)) } - /// Indicates whether this [`Type`] is [`Type::ExplicitGeneric`]. - #[must_use] - pub(crate) fn is_explicit_generic(&self) -> bool { - matches!(self, Self::ExplicitGeneric(_)) - } - /// Indicates whether this [`Type`] is [`Type::ImplicitGeneric`]. #[must_use] pub(crate) fn is_implicit_generic(&self) -> bool { @@ -123,16 +117,6 @@ impl Type { } } - /// Returns a type parameter identifier that suits this [`Type`]. - #[must_use] - pub(crate) fn generic_ty(&self) -> syn::Type { - match self { - Self::ExplicitGeneric(ty_param) => parse_quote! { #ty_param }, - Self::ImplicitGeneric(Some(pred)) => pred.bounded_ty.clone(), - Self::ImplicitGeneric(None) | Self::Concrete(_) => parse_quote! { __S }, - } - } - /// Returns a default [`ScalarValue`] type that is compatible with this [`Type`]. /// /// [`ScalarValue`]: juniper::ScalarValue diff --git a/juniper_codegen/src/graphql_interface/attr.rs b/juniper_codegen/src/graphql_interface/attr.rs index 511a2d175..47a3fc3a4 100644 --- a/juniper_codegen/src/graphql_interface/attr.rs +++ b/juniper_codegen/src/graphql_interface/attr.rs @@ -3,7 +3,7 @@ use std::mem; use proc_macro2::{Span, TokenStream}; -use quote::{format_ident, quote, ToTokens as _}; +use quote::{format_ident, quote}; use syn::{ext::IdentExt as _, parse_quote, spanned::Spanned}; use crate::{ @@ -16,10 +16,7 @@ use crate::{ util::{path_eq_single, span_container::SpanContainer, RenameRule}, }; -use super::{ - inject_async_trait, new, Definition, EnumType, ImplAttr, Implementer, ImplementerDowncast, - TraitAttr, TraitObjectType, Type, -}; +use super::{inject_async_trait, Definition, TraitAttr}; /// [`GraphQLScope`] of errors for `#[graphql_interface]` macro. const ERR: GraphQLScope = GraphQLScope::InterfaceAttr; @@ -30,33 +27,11 @@ pub fn expand(attr_args: TokenStream, body: TokenStream) -> syn::Result(body) { - if ast.trait_.is_some() { - let impl_attrs = parse::attr::unite(("graphql_interface", &attr_args), &ast.attrs); - ast.attrs = parse::attr::strip("graphql_interface", ast.attrs); - return expand_on_impl(impl_attrs, ast); - } } Err(syn::Error::new( Span::call_site(), - "#[graphql_interface] attribute is applicable to trait definitions and trait \ - implementations only", - )) -} - -/// Expands `#[graphql_interface_new]` macro into generated code. -pub fn expand_new(attr_args: TokenStream, body: TokenStream) -> syn::Result { - if let Ok(mut ast) = syn::parse2::(body.clone()) { - let trait_attrs = parse::attr::unite(("graphql_interface_new", &attr_args), &ast.attrs); - ast.attrs = parse::attr::strip("graphql_interface_new", ast.attrs); - return expand_on_trait_new(trait_attrs, ast); - } - - Err(syn::Error::new( - Span::call_site(), - "#[graphql_interface_new] attribute is applicable to trait definitions and trait \ - implementations only", + "#[graphql_interface] attribute is applicable to trait definitions only", )) } @@ -86,27 +61,6 @@ fn expand_on_trait( let scalar = scalar::Type::parse(attr.scalar.as_deref(), &ast.generics); - let mut implementers: Vec<_> = attr - .implementers - .iter() - .map(|ty| Implementer { - ty: ty.as_ref().clone(), - downcast: None, - context: None, - scalar: scalar.clone(), - }) - .collect(); - for (ty, downcast) in &attr.external_downcasts { - match implementers.iter_mut().find(|i| &i.ty == ty) { - Some(impler) => { - impler.downcast = Some(ImplementerDowncast::External { - path: downcast.inner().clone(), - }); - } - None => err_only_implementer_downcast(&downcast.span_joined()), - } - } - proc_macro_error::abort_if_dirty(); let renaming = attr @@ -118,191 +72,8 @@ fn expand_on_trait( let mut fields = vec![]; for item in &mut ast.items { if let syn::TraitItem::Method(m) = item { - match TraitMethod::parse(m, &renaming) { - Some(TraitMethod::Field(f)) => fields.push(f), - Some(TraitMethod::Downcast(d)) => { - match implementers.iter_mut().find(|i| i.ty == d.ty) { - Some(impler) => { - if let Some(external) = &impler.downcast { - err_duplicate_downcast(m, external, &impler.ty); - } else { - impler.downcast = d.downcast; - impler.context = d.context; - } - } - None => err_only_implementer_downcast(&m.sig), - } - } - _ => {} - } - } - } - - proc_macro_error::abort_if_dirty(); - - if fields.is_empty() { - ERR.emit_custom(trait_span, "must have at least one field"); - } - if !field::all_different(&fields) { - ERR.emit_custom(trait_span, "must have a different name for each field"); - } - - proc_macro_error::abort_if_dirty(); - - let context = attr - .context - .as_deref() - .cloned() - .or_else(|| { - fields.iter().find_map(|f| { - f.arguments.as_ref().and_then(|f| { - f.iter() - .find_map(field::MethodArgument::context_ty) - .cloned() - }) - }) - }) - .or_else(|| { - implementers - .iter() - .find_map(|impler| impler.context.as_ref()) - .cloned() - }) - .unwrap_or_else(|| parse_quote! { () }); - - let is_trait_object = attr.r#dyn.is_some(); - - let is_async_trait = attr.asyncness.is_some() - || ast - .items - .iter() - .find_map(|item| match item { - syn::TraitItem::Method(m) => m.sig.asyncness, - _ => None, - }) - .is_some(); - let has_default_async_methods = ast.items.iter().any(|item| match item { - syn::TraitItem::Method(m) => m.sig.asyncness.and(m.default.as_ref()).is_some(), - _ => false, - }); - - let ty = if is_trait_object { - Type::TraitObject(Box::new(TraitObjectType::new( - &ast, - &attr, - scalar.clone(), - context.clone(), - ))) - } else { - Type::Enum(Box::new(EnumType::new( - &ast, - &attr, - &implementers, - scalar.clone(), - ))) - }; - - let generated_code = Definition { - ty, - - name, - description: attr.description.map(SpanContainer::into_inner), - - context, - scalar: scalar.clone(), - - fields, - implementers, - }; - - // Attach the `juniper::AsDynGraphQLValue` on top of the trait if dynamic dispatch is used. - if is_trait_object { - ast.attrs.push(parse_quote! { - #[allow(unused_qualifications, clippy::type_repetition_in_bounds)] - }); - - let scalar_ty = scalar.generic_ty(); - if !scalar.is_explicit_generic() { - let default_ty = scalar.default_ty(); - ast.generics - .params - .push(parse_quote! { #scalar_ty = #default_ty }); - } - ast.generics - .make_where_clause() - .predicates - .push(parse_quote! { #scalar_ty: ::juniper::ScalarValue }); - ast.supertraits - .push(parse_quote! { ::juniper::AsDynGraphQLValue<#scalar_ty> }); - } - - if is_async_trait { - if has_default_async_methods { - // Hack for object safety. See details: https://docs.rs/async-trait/#dyn-traits - ast.supertraits.push(parse_quote! { Sync }); - } - inject_async_trait( - &mut ast.attrs, - ast.items.iter_mut().filter_map(|i| { - if let syn::TraitItem::Method(m) = i { - Some(&mut m.sig) - } else { - None - } - }), - &ast.generics, - ); - } - - Ok(quote! { - #ast - #generated_code - }) -} - -/// Expands `#[graphql_interface_new]` macro placed on trait definition. -fn expand_on_trait_new( - attrs: Vec, - mut ast: syn::ItemTrait, -) -> syn::Result { - let attr = new::TraitAttr::from_attrs("graphql_interface_new", &attrs)?; - - let trait_ident = &ast.ident; - let trait_span = ast.span(); - - let name = attr - .name - .clone() - .map(SpanContainer::into_inner) - .unwrap_or_else(|| trait_ident.unraw().to_string()); - if !attr.is_internal && name.starts_with("__") { - ERR.no_double_underscore( - attr.name - .as_ref() - .map(SpanContainer::span_ident) - .unwrap_or_else(|| trait_ident.span()), - ); - } - - let scalar = scalar::Type::parse(attr.scalar.as_deref(), &ast.generics); - - proc_macro_error::abort_if_dirty(); - - let renaming = attr - .rename_fields - .as_deref() - .copied() - .unwrap_or(RenameRule::CamelCase); - - let mut fields = vec![]; - for item in &mut ast.items { - if let syn::TraitItem::Method(m) = item { - match TraitMethod::parse(m, &renaming) { - Some(TraitMethod::Field(f)) => fields.push(f), - Some(TraitMethod::Downcast(_d)) => { - unimplemented!(); - } - _ => {} + if let Some(f) = parse_field(m, &renaming) { + fields.push(f) } } } @@ -358,7 +129,7 @@ fn expand_on_trait_new( ); let description = attr.description.as_ref().map(|c| c.inner().clone()); - let generated_code = new::Definition { + let generated_code = Definition { trait_generics: ast.generics.clone(), vis: ast.vis.clone(), enum_ident, @@ -399,233 +170,96 @@ fn expand_on_trait_new( }) } -/// Expands `#[graphql_interface]` macro placed on a trait implementation block. -fn expand_on_impl(attrs: Vec, mut ast: syn::ItemImpl) -> syn::Result { - let attr = ImplAttr::from_attrs("graphql_interface", &attrs)?; - - let is_async_trait = attr.asyncness.is_some() - || ast - .items - .iter() - .find_map(|item| match item { - syn::ImplItem::Method(m) => m.sig.asyncness, - _ => None, - }) - .is_some(); - - let is_trait_object = attr.r#dyn.is_some(); - - if is_trait_object { - let scalar = scalar::Type::parse(attr.scalar.as_deref(), &ast.generics); - - ast.attrs.push(parse_quote! { - #[allow(unused_qualifications, clippy::type_repetition_in_bounds)] - }); +/// Parses [`field::Definition`] from the given trait method definition. +/// +/// Returns [`None`] if parsing fails, or the method field is ignored. +#[must_use] +fn parse_field( + method: &mut syn::TraitItemMethod, + renaming: &RenameRule, +) -> Option { + let method_ident = &method.sig.ident; + let method_attrs = method.attrs.clone(); + + // Remove repeated attributes from the method, to omit incorrect expansion. + method.attrs = mem::take(&mut method.attrs) + .into_iter() + .filter(|attr| !path_eq_single(&attr.path, "graphql")) + .collect(); - if scalar.is_implicit_generic() { - ast.generics.params.push(parse_quote! { #scalar }); - } - if scalar.is_generic() { - ast.generics - .make_where_clause() - .predicates - .push(parse_quote! { #scalar: ::juniper::ScalarValue + Send + Sync }); - } + let attr = field::Attr::from_attrs("graphql", &method_attrs) + .map_err(|e| proc_macro_error::emit_error!(e)) + .ok()?; - if !scalar.is_explicit_generic() { - let (_, trait_path, _) = ast.trait_.as_mut().unwrap(); - let trait_params = &mut trait_path.segments.last_mut().unwrap().arguments; - if let syn::PathArguments::None = trait_params { - *trait_params = syn::PathArguments::AngleBracketed(parse_quote! { <> }); - } - if let syn::PathArguments::AngleBracketed(a) = trait_params { - a.args.push(parse_quote! { #scalar }); - } - } + if attr.ignore.is_some() { + return None; } - if is_async_trait { - inject_async_trait( - &mut ast.attrs, - ast.items.iter_mut().filter_map(|i| { - if let syn::ImplItem::Method(m) = i { - Some(&mut m.sig) - } else { - None - } - }), - &ast.generics, + let name = attr + .name + .as_ref() + .map(|m| m.as_ref().value()) + .unwrap_or_else(|| renaming.apply(&method_ident.unraw().to_string())); + if name.starts_with("__") { + ERR.no_double_underscore( + attr.name + .as_ref() + .map(SpanContainer::span_ident) + .unwrap_or_else(|| method_ident.span()), ); + return None; } - Ok(quote! { #ast }) -} - -/// Representation of parsed Rust trait method for `#[graphql_interface]` macro code generation. -enum TraitMethod { - /// Method represents a [`Field`] of [GraphQL interface][1]. - /// - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - Field(field::Definition), - - /// Method represents a custom downcasting function into the [`Implementer`] of - /// [GraphQL interface][1]. - /// - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - Downcast(Box), -} - -impl TraitMethod { - /// Parses this [`TraitMethod`] from the given trait method definition. - /// - /// Returns [`None`] if the trait method marked with `#[graphql(ignore)]` attribute, - /// or parsing fails. - #[must_use] - fn parse(method: &mut syn::TraitItemMethod, renaming: &RenameRule) -> Option { - let method_attrs = method.attrs.clone(); - - // Remove repeated attributes from the method, to omit incorrect expansion. - method.attrs = mem::take(&mut method.attrs) - .into_iter() - .filter(|attr| !path_eq_single(&attr.path, "graphql")) - .collect(); - - let attr = field::Attr::from_attrs("graphql", &method_attrs) - .map_err(|e| proc_macro_error::emit_error!(e)) - .ok()?; - - if attr.ignore.is_some() { - return None; - } - - if attr.downcast.is_some() { - return Some(Self::Downcast(Box::new(Self::parse_downcast(method)?))); - } - - Some(Self::Field(Self::parse_field(method, attr, renaming)?)) - } - - /// Parses [`TraitMethod::Downcast`] from the given trait method definition. - /// - /// Returns [`None`] if parsing fails. - #[must_use] - fn parse_downcast(method: &mut syn::TraitItemMethod) -> Option { - let method_ident = &method.sig.ident; - - let ty = parse::downcaster::output_type(&method.sig.output) - .map_err(|span| { - ERR.emit_custom( - span, - "expects trait method return type to be `Option<&ImplementerType>` only", - ) - }) - .ok()?; - let context_ty = parse::downcaster::context_ty(&method.sig) - .map_err(|span| { - ERR.emit_custom( - span, - "expects trait method to accept `&self` only and, optionally, `&Context`", - ) - }) - .ok()?; - if let Some(is_async) = &method.sig.asyncness { - ERR.emit_custom( - is_async.span(), - "async downcast to interface implementer is not supported", - ); - return None; - } - - let downcast = ImplementerDowncast::Method { - name: method_ident.clone(), - with_context: context_ty.is_some(), - }; - - Some(Implementer { - ty, - downcast: Some(downcast), - context: context_ty, - scalar: scalar::Type::ImplicitGeneric(None), - }) - } - - /// Parses [`TraitMethod::Field`] from the given trait method definition. - /// - /// Returns [`None`] if parsing fails. - #[must_use] - fn parse_field( - method: &mut syn::TraitItemMethod, - attr: field::Attr, - renaming: &RenameRule, - ) -> Option { - let method_ident = &method.sig.ident; - - let name = attr - .name - .as_ref() - .map(|m| m.as_ref().value()) - .unwrap_or_else(|| renaming.apply(&method_ident.unraw().to_string())); - if name.starts_with("__") { - ERR.no_double_underscore( - attr.name - .as_ref() - .map(SpanContainer::span_ident) - .unwrap_or_else(|| method_ident.span()), - ); - return None; + let arguments = { + if method.sig.inputs.is_empty() { + return err_no_method_receiver(&method.sig.inputs); } - - let arguments = { - if method.sig.inputs.is_empty() { - return err_no_method_receiver(&method.sig.inputs); - } - let mut args_iter = method.sig.inputs.iter_mut(); - match args_iter.next().unwrap() { - syn::FnArg::Receiver(rcv) => { - if rcv.reference.is_none() || rcv.mutability.is_some() { - return err_invalid_method_receiver(rcv); - } + let mut args_iter = method.sig.inputs.iter_mut(); + match args_iter.next().unwrap() { + syn::FnArg::Receiver(rcv) => { + if rcv.reference.is_none() || rcv.mutability.is_some() { + return err_invalid_method_receiver(rcv); } - syn::FnArg::Typed(arg) => { - if let syn::Pat::Ident(a) = &*arg.pat { - if a.ident.to_string().as_str() != "self" { - return err_invalid_method_receiver(arg); - } + } + syn::FnArg::Typed(arg) => { + if let syn::Pat::Ident(a) = &*arg.pat { + if a.ident.to_string().as_str() != "self" { + return err_invalid_method_receiver(arg); } - return err_no_method_receiver(arg); } - }; - args_iter - .filter_map(|arg| match arg { - syn::FnArg::Receiver(_) => None, - syn::FnArg::Typed(arg) => field::MethodArgument::parse(arg, renaming, &ERR), - }) - .collect() + return err_no_method_receiver(arg); + } }; + args_iter + .filter_map(|arg| match arg { + syn::FnArg::Receiver(_) => None, + syn::FnArg::Typed(arg) => field::MethodArgument::parse(arg, renaming, &ERR), + }) + .collect() + }; - let mut ty = match &method.sig.output { - syn::ReturnType::Default => parse_quote! { () }, - syn::ReturnType::Type(_, ty) => ty.unparenthesized().clone(), - }; - ty.lifetimes_anonymized(); - - let description = attr.description.as_ref().map(|d| d.as_ref().value()); - let deprecated = attr - .deprecated - .as_deref() - .map(|d| d.as_ref().map(syn::LitStr::value)); - - Some(field::Definition { - name, - ty, - description, - deprecated, - ident: method_ident.clone(), - arguments: Some(arguments), - has_receiver: method.sig.receiver().is_some(), - is_async: method.sig.asyncness.is_some(), - }) - } + let mut ty = match &method.sig.output { + syn::ReturnType::Default => parse_quote! { () }, + syn::ReturnType::Type(_, ty) => ty.unparenthesized().clone(), + }; + ty.lifetimes_anonymized(); + + let description = attr.description.as_ref().map(|d| d.as_ref().value()); + let deprecated = attr + .deprecated + .as_deref() + .map(|d| d.as_ref().map(syn::LitStr::value)); + + Some(field::Definition { + name, + ty, + description, + deprecated, + ident: method_ident.clone(), + arguments: Some(arguments), + has_receiver: method.sig.receiver().is_some(), + is_async: method.sig.asyncness.is_some(), + }) } /// Emits "invalid trait method receiver" [`syn::Error`] pointing to the given @@ -649,42 +283,3 @@ fn err_no_method_receiver(span: &S) -> Option { ); None } - -/// Emits "non-implementer downcast target" [`syn::Error`] pointing to the given -/// `span`. -fn err_only_implementer_downcast(span: &S) { - ERR.emit_custom( - span.span(), - "downcasting is possible only to interface implementers", - ); -} - -/// Emits "duplicate downcast" [`syn::Error`] for the given `method` and -/// `external` [`ImplementerDowncast`] function. -fn err_duplicate_downcast( - method: &syn::TraitItemMethod, - external: &ImplementerDowncast, - impler_ty: &syn::Type, -) { - let external = match external { - ImplementerDowncast::External { path } => path, - _ => unreachable!(), - }; - - ERR.custom( - method.span(), - format!( - "trait method `{}` conflicts with the external downcast function \ - `{}` declared on the trait to downcast into the implementer type \ - `{}`", - method.sig.ident, - external.to_token_stream(), - impler_ty.to_token_stream(), - ), - ) - .note(String::from( - "use `#[graphql(ignore)]` attribute argument to ignore this trait \ - method for interface implementers downcasting", - )) - .emit() -} diff --git a/juniper_codegen/src/graphql_interface/mod.rs b/juniper_codegen/src/graphql_interface/mod.rs index 8b3deefaa..6c67f465d 100644 --- a/juniper_codegen/src/graphql_interface/mod.rs +++ b/juniper_codegen/src/graphql_interface/mod.rs @@ -3,21 +3,19 @@ //! [1]: https://spec.graphql.org/June2018/#sec-Interfaces pub mod attr; -pub mod new; -use std::{ - collections::{HashMap, HashSet}, - convert::TryInto as _, -}; +use std::{collections::HashSet, convert::TryInto as _}; use proc_macro2::TokenStream; -use quote::{format_ident, quote, ToTokens, TokenStreamExt as _}; +use quote::{format_ident, quote, ToTokens}; use syn::{ ext::IdentExt as _, parse::{Parse, ParseStream}, parse_quote, + punctuated::Punctuated, spanned::Spanned as _, token, + visit::Visit, }; use crate::{ @@ -53,32 +51,21 @@ struct TraitAttr { /// [2]: https://spec.graphql.org/June2018/#sec-Descriptions description: Option>, - /// Explicitly specified identifier of the enum Rust type behind the trait, - /// being an actual implementation of a [GraphQL interface][1] type. + /// Explicitly specified identifier of the type alias of Rust enum type + /// behind the trait, being an actual implementation of a + /// [GraphQL interface][1] type. /// /// If [`None`], then `{trait_name}Value` identifier will be used. /// /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces r#enum: Option>, - /// Explicitly specified identifier of the Rust type alias of the - /// [trait object][2], being an actual implementation of a - /// [GraphQL interface][1] type. - /// - /// Effectively makes code generation to use a [trait object][2] as a - /// [GraphQL interface][1] type rather than an enum. If [`None`], then enum - /// is used by default. - /// - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - /// [2]: https://doc.rust-lang.org/reference/types/trait-object.html - r#dyn: Option>, - /// Explicitly specified Rust types of [GraphQL objects][2] implementing /// this [GraphQL interface][1] type. /// /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces /// [2]: https://spec.graphql.org/June2018/#sec-Objects - implementers: HashSet>, + implementers: HashSet>, /// Explicitly specified type of [`Context`] to use for resolving this /// [GraphQL interface][1] type with. @@ -110,22 +97,6 @@ struct TraitAttr { /// it contains async methods. asyncness: Option>, - /// Explicitly specified external downcasting functions for - /// [GraphQL interface][1] implementers. - /// - /// If [`None`], then macro will downcast to the implementers via enum - /// dispatch or dynamic dispatch (if the one is chosen). That's why - /// specifying an external resolver function has sense, when some custom - /// [interface][1] implementer resolving logic is involved. - /// - /// Once the downcasting function is specified for some [GraphQL object][2] - /// implementer type, it cannot be downcast another such function or trait - /// method marked with a [`MethodMeta::downcast`] marker. - /// - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - /// [2]: https://spec.graphql.org/June2018/#sec-Objects - external_downcasts: HashMap>, - /// Explicitly specified [`RenameRule`] for all fields of this /// [GraphQL interface][1] type. /// @@ -184,7 +155,7 @@ impl Parse for TraitAttr { "for" | "implementers" => { input.parse::()?; for impler in input.parse_maybe_wrapped_and_punctuated::< - syn::Type, token::Bracket, token::Comma, + syn::TypePath, token::Bracket, token::Comma, >()? { let impler_span = impler.span(); out @@ -193,13 +164,6 @@ impl Parse for TraitAttr { .none_or_else(|_| err::dup_arg(impler_span))?; } } - "dyn" => { - input.parse::()?; - let alias = input.parse::()?; - out.r#dyn - .replace(SpanContainer::new(ident.span(), Some(alias.span()), alias)) - .none_or_else(|_| err::dup_arg(&ident))? - } "enum" => { input.parse::()?; let alias = input.parse::()?; @@ -213,16 +177,6 @@ impl Parse for TraitAttr { .replace(SpanContainer::new(span, Some(span), ident)) .none_or_else(|_| err::dup_arg(span))?; } - "on" => { - let ty = input.parse::()?; - input.parse::()?; - let dwncst = input.parse::()?; - let dwncst_spanned = SpanContainer::new(ident.span(), Some(ty.span()), dwncst); - let dwncst_span = dwncst_spanned.span_joined(); - out.external_downcasts - .insert(ty, dwncst_spanned) - .none_or_else(|_| err::dup_arg(dwncst_span))? - } "rename_all" => { input.parse::()?; let val = input.parse::()?; @@ -257,12 +211,8 @@ impl TraitAttr { context: try_merge_opt!(context: self, another), scalar: try_merge_opt!(scalar: self, another), implementers: try_merge_hashset!(implementers: self, another => span_joined), - r#dyn: try_merge_opt!(r#dyn: self, another), r#enum: try_merge_opt!(r#enum: self, another), asyncness: try_merge_opt!(asyncness: self, another), - external_downcasts: try_merge_hashmap!( - external_downcasts: self, another => span_joined - ), rename_fields: try_merge_opt!(rename_fields: self, another), is_internal: self.is_internal || another.is_internal, }) @@ -275,15 +225,6 @@ impl TraitAttr { .map(|attr| attr.parse_args()) .try_fold(Self::default(), |prev, curr| prev.try_merge(curr?))?; - if let Some(as_dyn) = &attr.r#dyn { - if attr.r#enum.is_some() { - return Err(syn::Error::new( - as_dyn.span(), - "`dyn` attribute argument is not composable with `enum` attribute argument", - )); - } - } - if attr.description.is_none() { attr.description = get_doc_comment(attrs); } @@ -292,109 +233,32 @@ impl TraitAttr { } } -/// Available arguments behind `#[graphql_interface]` attribute placed on a -/// trait implementation block, when generating code for [GraphQL interface][1] -/// type. +/// Definition of [GraphQL interface][1] for code generation. /// /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces -#[derive(Debug, Default)] -struct ImplAttr { - /// Explicitly specified type (or type parameter with its bounds) of - /// [`ScalarValue`] to implementing the [GraphQL interface][1] type with. - /// - /// If absent, then generated code will be generic over any [`ScalarValue`] - /// type, which, in turn, requires all [interface][1] implementers to be - /// generic over any [`ScalarValue`] type too. That's why this type should - /// be specified only if the implementer itself implements [`GraphQLType`] - /// in a non-generic way over [`ScalarValue`] type. +struct Definition { + /// [`syn::Generics`] of the trait describing the [GraphQL interface][1]. /// - /// [`GraphQLType`]: juniper::GraphQLType - /// [`ScalarValue`]: juniper::ScalarValue /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - scalar: Option>, + trait_generics: syn::Generics, - /// Explicitly specified marker indicating that the trait implementation - /// block should be transformed with applying [`async_trait`]. - /// - /// If absent, then trait will be transformed with applying [`async_trait`] - /// only if it contains async methods. + /// [`syn::Visibility`] of the trait describing the [GraphQL interface][1]. /// - /// This marker is especially useful when Rust trait contains async default - /// methods, while the implementation block doesn't. - asyncness: Option>, + /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces + vis: syn::Visibility, - /// Explicitly specified marker indicating that the implemented - /// [GraphQL interface][1] type is represented as a [trait object][2] in - /// Rust type system rather then an enum (default mode, when the marker is - /// absent). + /// Name of the generic enum describing all [`implementers`]. It's generic + /// to derive [`Clone`], [`Copy`] and [`Debug`] on it. /// - /// [2]: https://doc.rust-lang.org/reference/types/trait-object.html - r#dyn: Option>, -} - -impl Parse for ImplAttr { - fn parse(input: ParseStream<'_>) -> syn::Result { - let mut out = Self::default(); - while !input.is_empty() { - let ident = input.parse_any_ident()?; - match ident.to_string().as_str() { - "scalar" | "Scalar" | "ScalarValue" => { - input.parse::()?; - let scl = input.parse::()?; - out.scalar - .replace(SpanContainer::new(ident.span(), Some(scl.span()), scl)) - .none_or_else(|_| err::dup_arg(&ident))? - } - "dyn" => { - let span = ident.span(); - out.r#dyn - .replace(SpanContainer::new(span, Some(span), ident)) - .none_or_else(|_| err::dup_arg(span))?; - } - "async" => { - let span = ident.span(); - out.asyncness - .replace(SpanContainer::new(span, Some(span), ident)) - .none_or_else(|_| err::dup_arg(span))?; - } - name => { - return Err(err::unknown_arg(&ident, name)); - } - } - input.try_parse::()?; - } - Ok(out) - } -} - -impl ImplAttr { - /// Tries to merge two [`ImplAttr`]s into a single one, reporting about - /// duplicates, if any. - fn try_merge(self, mut another: Self) -> syn::Result { - Ok(Self { - scalar: try_merge_opt!(scalar: self, another), - r#dyn: try_merge_opt!(r#dyn: self, another), - asyncness: try_merge_opt!(asyncness: self, another), - }) - } - - /// Parses [`ImplAttr`] from the given multiple `name`d [`syn::Attribute`]s - /// placed on a trait implementation block. - pub fn from_attrs(name: &str, attrs: &[syn::Attribute]) -> syn::Result { - filter_attrs(name, attrs) - .map(|attr| attr.parse_args()) - .try_fold(Self::default(), |prev, curr| prev.try_merge(curr?)) - } -} + /// [`implementers`]: Self::implementers + /// [`Debug`]: std::fmt::Debug + enum_ident: syn::Ident, -/// Definition of [GraphQL interface][1] for code generation. -/// -/// [1]: https://spec.graphql.org/June2018/#sec-Interfaces -struct Definition { - /// Rust type that this [GraphQL interface][1] is represented with. + /// Name of the type alias for [`enum_ident`] with [`implementers`]. /// - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - ty: Type, + /// [`enum_ident`]: Self::enum_ident + /// [`implementers`]: Self::implementers + enum_alias_ident: syn::Ident, /// Name of this [GraphQL interface][1] in GraphQL schema. /// @@ -431,22 +295,142 @@ struct Definition { /// Defined [`Implementer`]s of this [GraphQL interface][1]. /// /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - implementers: Vec, + implementers: Vec, } impl ToTokens for Definition { fn to_tokens(&self, into: &mut TokenStream) { - self.ty.to_token_stream().to_tokens(into); + self.generate_enum_tokens().to_tokens(into); self.impl_graphql_interface_tokens().to_tokens(into); self.impl_output_type_tokens().to_tokens(into); self.impl_graphql_type_tokens().to_tokens(into); self.impl_graphql_value_tokens().to_tokens(into); self.impl_graphql_value_async_tokens().to_tokens(into); self.impl_traits_for_reflection_tokens().to_tokens(into); + self.impl_field_meta_tokens().to_tokens(into); + self.impl_field_tokens().to_tokens(into); + self.impl_async_field_tokens().to_tokens(into); } } impl Definition { + /// Generates enum describing all [`implementers`]. + /// + /// [`implementers`]: Self::implementers + #[must_use] + fn generate_enum_tokens(&self) -> TokenStream { + let vis = &self.vis; + let enum_ident = &self.enum_ident; + let alias_ident = &self.enum_alias_ident; + + let variant_gens_pars = self + .implementers + .iter() + .enumerate() + .map::(|(id, _)| { + let par = format_ident!("__I{}", id); + parse_quote!( #par ) + }); + + let variants_idents = self + .implementers + .iter() + .filter_map(|ty| ty.path.segments.last().map(|seg| &seg.ident)); + + let trait_gens = &self.trait_generics; + let (trait_impl_gens, trait_ty_gens, trait_where_clause) = + self.trait_generics.split_for_impl(); + + let (trait_gens_lifetimes, trait_gens_tys) = trait_gens + .params + .clone() + .into_iter() + .partition::, _>(|par| { + matches!(par, syn::GenericParam::Lifetime(_)) + }); + + let enum_gens = { + let mut enum_gens = trait_gens.clone(); + enum_gens.params = trait_gens_lifetimes.clone(); + enum_gens.params.extend(variant_gens_pars.clone()); + enum_gens.params.extend(trait_gens_tys.clone()); + enum_gens + }; + + let enum_alias_gens = { + let mut enum_alias_gens = trait_gens.clone(); + enum_alias_gens.move_bounds_to_where_clause(); + enum_alias_gens + }; + + let enum_to_alias_gens = { + trait_gens_lifetimes + .into_iter() + .map(|par| match par { + syn::GenericParam::Lifetime(def) => { + let lifetime = &def.lifetime; + quote! { #lifetime } + } + rest => quote! { #rest }, + }) + .chain(self.implementers.iter().map(ToTokens::to_token_stream)) + .chain(trait_gens_tys.into_iter().map(|par| match par { + syn::GenericParam::Type(ty) => { + let par_ident = &ty.ident; + quote! { #par_ident } + } + rest => quote! { #rest }, + })) + }; + + let phantom_variant = self.has_phantom_variant().then(|| { + let phantom_params = trait_gens.params.iter().filter_map(|p| { + let ty = match p { + syn::GenericParam::Type(ty) => { + let ident = &ty.ident; + quote! { #ident } + } + syn::GenericParam::Lifetime(lt) => { + let lifetime = <.lifetime; + quote! { &#lifetime () } + } + syn::GenericParam::Const(_) => return None, + }; + Some(quote! { + ::std::marker::PhantomData<::std::sync::atomic::AtomicPtr>> + }) + }); + quote! { __Phantom(#(#phantom_params),*) } + }); + + let from_impls = self.implementers.iter().zip(variants_idents.clone()).map( + |(ty, ident)| { + quote! { + impl#trait_impl_gens ::std::convert::From<#ty> for #alias_ident#trait_ty_gens + #trait_where_clause + { + fn from(v: #ty) -> Self { + Self::#ident(v) + } + } + } + }, + ); + + quote! { + #[derive(Clone, Copy, Debug)] + #vis enum #enum_ident#enum_gens { + #(#variants_idents(#variant_gens_pars),)* + #phantom_variant + } + + #vis type #alias_ident#enum_alias_gens = + #enum_ident<#(#enum_to_alias_gens),*>; + + #(#from_impls)* + } + } + /// Returns generated code implementing [`GraphQLInterface`] trait for this /// [GraphQL interface][1]. /// @@ -454,19 +438,23 @@ impl Definition { /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces #[must_use] fn impl_graphql_interface_tokens(&self) -> TokenStream { + let ty = &self.enum_alias_ident; let scalar = &self.scalar; - let (impl_generics, where_clause) = self.ty.impl_generics(false); - let ty = self.ty.ty_tokens(); + let gens = self.impl_generics(false); + let (impl_generics, _, where_clause) = gens.split_for_impl(); + let (_, ty_generics, _) = self.trait_generics.split_for_impl(); - let impler_tys: Vec<_> = self.implementers.iter().map(|impler| &impler.ty).collect(); + let impler_tys = &self.implementers; let all_implers_unique = (impler_tys.len() > 1).then(|| { quote! { ::juniper::sa::assert_type_ne_all!(#( #impler_tys ),*); } }); quote! { #[automatically_derived] - impl#impl_generics ::juniper::marker::GraphQLInterface<#scalar> for #ty #where_clause + impl#impl_generics ::juniper::marker::GraphQLInterface<#scalar> for + #ty#ty_generics + #where_clause { fn mark() { #all_implers_unique @@ -483,21 +471,25 @@ impl Definition { /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces #[must_use] fn impl_output_type_tokens(&self) -> TokenStream { + let ty = &self.enum_alias_ident; let scalar = &self.scalar; - let (impl_generics, where_clause) = self.ty.impl_generics(false); - let ty = self.ty.ty_tokens(); + let generics = self.impl_generics(false); + let (impl_generics, _, where_clause) = generics.split_for_impl(); + let (_, ty_generics, _) = self.trait_generics.split_for_impl(); let fields_marks = self .fields .iter() .map(|f| f.method_mark_tokens(false, scalar)); - let impler_tys = self.implementers.iter().map(|impler| &impler.ty); + let impler_tys = self.implementers.iter(); quote! { #[automatically_derived] - impl#impl_generics ::juniper::marker::IsOutputType<#scalar> for #ty #where_clause + impl#impl_generics ::juniper::marker::IsOutputType<#scalar> for + #ty#ty_generics + #where_clause { fn mark() { #( #fields_marks )* @@ -514,10 +506,12 @@ impl Definition { /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces #[must_use] fn impl_graphql_type_tokens(&self) -> TokenStream { + let ty = &self.enum_alias_ident; let scalar = &self.scalar; - let (impl_generics, where_clause) = self.ty.impl_generics(false); - let ty = self.ty.ty_tokens(); + let generics = self.impl_generics(false); + let (impl_generics, _, where_clause) = generics.split_for_impl(); + let (_, ty_generics, _) = self.trait_generics.split_for_impl(); let name = &self.name; let description = self @@ -526,7 +520,7 @@ impl Definition { .map(|desc| quote! { .description(#desc) }); // Sorting is required to preserve/guarantee the order of implementers registered in schema. - let mut impler_tys: Vec<_> = self.implementers.iter().map(|impler| &impler.ty).collect(); + let mut impler_tys = self.implementers.clone(); impler_tys.sort_unstable_by(|a, b| { let (a, b) = (quote!(#a).to_string(), quote!(#b).to_string()); a.cmp(&b) @@ -536,7 +530,9 @@ impl Definition { quote! { #[automatically_derived] - impl#impl_generics ::juniper::GraphQLType<#scalar> for #ty #where_clause + impl#impl_generics ::juniper::GraphQLType<#scalar> + for #ty#ty_generics + #where_clause { fn name(_ : &Self::TypeInfo) -> Option<&'static str> { Some(#name) @@ -554,7 +550,7 @@ impl Definition { let fields = [ #( #fields_meta, )* ]; - registry.build_interface_type::<#ty>(info, &fields) + registry.build_interface_type::<#ty#ty_generics>(info, &fields) #description .into_meta() } @@ -569,18 +565,30 @@ impl Definition { /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces #[must_use] fn impl_graphql_value_tokens(&self) -> TokenStream { + let ty = &self.enum_alias_ident; + let trait_name = &self.name; let scalar = &self.scalar; let context = &self.context; - let (impl_generics, where_clause) = self.ty.impl_generics(false); - let ty = self.ty.ty_tokens(); - let ty_name = ty.to_string(); - let trait_ty = self.ty.trait_ty(); + let generics = self.impl_generics(false); + let (impl_generics, _, where_clause) = generics.split_for_impl(); + let (_, ty_generics, _) = self.trait_generics.split_for_impl(); - let fields_resolvers = self - .fields - .iter() - .filter_map(|f| f.method_resolve_field_tokens(scalar, Some(&trait_ty))); + let fields_resolvers = self.fields.iter().filter_map(|f| { + if f.is_async { + return None; + } + + let name = &f.name; + Some(quote! { + #name => { + ::juniper::macros::reflection::Field::< + #scalar, + { ::juniper::macros::reflection::fnv1a128(#name) } + >::call(self, info, args, executor) + } + }) + }); let async_fields_err = { let names = self .fields @@ -589,29 +597,22 @@ impl Definition { .collect::>(); (!names.is_empty()).then(|| { field::Definition::method_resolve_field_err_async_field_tokens( - &names, scalar, &ty_name, + &names, scalar, trait_name, ) }) }; let no_field_err = - field::Definition::method_resolve_field_err_no_field_tokens(scalar, &ty_name); + field::Definition::method_resolve_field_err_no_field_tokens(scalar, trait_name); - let custom_downcast_checks = self - .implementers - .iter() - .filter_map(|i| i.method_concrete_type_name_tokens(&trait_ty)); - let regular_downcast_check = self.ty.method_concrete_type_name_tokens(); + let downcast_check = self.method_concrete_type_name_tokens(); - let custom_downcasts = self - .implementers - .iter() - .filter_map(|i| i.method_resolve_into_type_tokens(&trait_ty)); - let regular_downcast = self.ty.method_resolve_into_type_tokens(); + let downcast = self.method_resolve_into_type_tokens(); quote! { #[allow(deprecated)] #[automatically_derived] - impl#impl_generics ::juniper::GraphQLValue<#scalar> for #ty #where_clause + impl#impl_generics ::juniper::GraphQLValue<#scalar> for #ty#ty_generics + #where_clause { type Context = #context; type TypeInfo = (); @@ -639,8 +640,7 @@ impl Definition { context: &Self::Context, info: &Self::TypeInfo, ) -> String { - #( #custom_downcast_checks )* - #regular_downcast_check + #downcast_check } fn resolve_into_type( @@ -650,8 +650,7 @@ impl Definition { _: Option<&[::juniper::Selection<#scalar>]>, executor: &::juniper::Executor, ) -> ::juniper::ExecutionResult<#scalar> { - #( #custom_downcasts )* - #regular_downcast + #downcast } } } @@ -664,30 +663,35 @@ impl Definition { /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces #[must_use] fn impl_graphql_value_async_tokens(&self) -> TokenStream { + let ty = &self.enum_alias_ident; + let trait_name = &self.name; let scalar = &self.scalar; - let (impl_generics, where_clause) = self.ty.impl_generics(true); - let ty = self.ty.ty_tokens(); - let ty_name = ty.to_string(); - let trait_ty = self.ty.trait_ty(); + let generics = self.impl_generics(true); + let (impl_generics, _, where_clause) = generics.split_for_impl(); + let (_, ty_generics, _) = self.trait_generics.split_for_impl(); - let fields_resolvers = self - .fields - .iter() - .map(|f| f.method_resolve_field_async_tokens(scalar, Some(&trait_ty))); + let fields_resolvers = self.fields.iter().map(|f| { + let name = &f.name; + quote! { + #name => { + ::juniper::macros::reflection::AsyncField::< + #scalar, + { ::juniper::macros::reflection::fnv1a128(#name) } + >::call(self, info, args, executor) + } + } + }); let no_field_err = - field::Definition::method_resolve_field_err_no_field_tokens(scalar, &ty_name); + field::Definition::method_resolve_field_err_no_field_tokens(scalar, trait_name); - let custom_downcasts = self - .implementers - .iter() - .filter_map(|i| i.method_resolve_into_type_async_tokens(&trait_ty)); - let regular_downcast = self.ty.method_resolve_into_type_async_tokens(); + let downcast = self.method_resolve_into_type_async_tokens(); quote! { #[allow(deprecated, non_snake_case)] #[automatically_derived] - impl#impl_generics ::juniper::GraphQLValueAsync<#scalar> for #ty #where_clause + impl#impl_generics ::juniper::GraphQLValueAsync<#scalar> for #ty#ty_generics + #where_clause { fn resolve_field_async<'b>( &'b self, @@ -709,670 +713,334 @@ impl Definition { _: Option<&'b [::juniper::Selection<'b, #scalar>]>, executor: &'b ::juniper::Executor<'b, 'b, Self::Context, #scalar> ) -> ::juniper::BoxFuture<'b, ::juniper::ExecutionResult<#scalar>> { - #( #custom_downcasts )* - #regular_downcast + #downcast } } } } - /// Returns generated code implementing [`BaseType`], [`BaseSubTypes`] and - /// [`WrappedType`] traits for this [GraphQL interface][1]. + /// Returns generated code implementing [`BaseType`], [`BaseSubTypes`], + /// [`WrappedType`] and [`Fields`] traits for this [GraphQL interface][1]. /// - /// [`GraphQLValueAsync`]: juniper::GraphQLValueAsync + /// [`BaseSubTypes`]: juniper::macros::reflection::BaseSubTypes + /// [`BaseType`]: juniper::macros::reflection::BaseType + /// [`Fields`]: juniper::macros::reflection::Fields + /// [`WrappedType`]: juniper::macros::reflection::WrappedType /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces #[must_use] - pub(crate) fn impl_traits_for_reflection_tokens(&self) -> TokenStream { + fn impl_traits_for_reflection_tokens(&self) -> TokenStream { + let ty = &self.enum_alias_ident; + let implementers = &self.implementers; let scalar = &self.scalar; let name = &self.name; - let (impl_generics, where_clause) = self.ty.impl_generics(false); - let ty = self.ty.ty_tokens(); - let implementors = self.implementers.iter().map(|i| &i.ty); + let fields = self.fields.iter().map(|f| &f.name); + + let generics = self.impl_generics(false); + let (impl_generics, _, where_clause) = generics.split_for_impl(); + let (_, ty_generics, _) = self.trait_generics.split_for_impl(); quote! { - impl #impl_generics ::juniper::macros::reflection::BaseType<#scalar> for #ty + #[automatically_derived] + impl#impl_generics ::juniper::macros::reflection::BaseType<#scalar> + for #ty#ty_generics #where_clause { const NAME: ::juniper::macros::reflection::Type = #name; } - impl #impl_generics ::juniper::macros::reflection::BaseSubTypes<#scalar> for #ty + #[automatically_derived] + impl#impl_generics ::juniper::macros::reflection::BaseSubTypes<#scalar> + for #ty#ty_generics #where_clause { const NAMES: ::juniper::macros::reflection::Types = &[ >::NAME, - #(<#implementors as ::juniper::macros::reflection::BaseType<#scalar>>::NAME),* + #(<#implementers as ::juniper::macros::reflection::BaseType<#scalar>>::NAME),* ]; } - impl #impl_generics ::juniper::macros::reflection::WrappedType<#scalar> for #ty + #[automatically_derived] + impl#impl_generics ::juniper::macros::reflection::WrappedType<#scalar> + for #ty#ty_generics #where_clause { const VALUE: ::juniper::macros::reflection::WrappedValue = 1; } + + #[automatically_derived] + impl#impl_generics ::juniper::macros::reflection::Fields<#scalar> + for #ty#ty_generics + #where_clause + { + const NAMES: ::juniper::macros::reflection::Names = &[#(#fields),*]; + } } } -} -/// Representation of custom downcast into an [`Implementer`] from a -/// [GraphQL interface][1] type for code generation. -/// -/// [1]: https://spec.graphql.org/June2018/#sec-Interfaces -#[derive(Clone, Debug)] -enum ImplementerDowncast { - /// Downcast is performed via a method of trait describing a + /// Returns generated code implementing [`FieldMeta`] for each field of this /// [GraphQL interface][1]. /// + /// [`FieldMeta`]: juniper::macros::reflection::FieldMeta /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - Method { - /// Name of trait method which performs this [`ImplementerDowncast`]. - name: syn::Ident, - - /// Indicator whether the trait method accepts a [`Context`] as its - /// second argument. - /// - /// [`Context`]: juniper::Context - with_context: bool, - }, + fn impl_field_meta_tokens(&self) -> TokenStream { + let ty = &self.enum_alias_ident; + let context = &self.context; + let scalar = &self.scalar; - /// Downcast is performed via some external function. - External { - /// Path of the external function to be called with. - path: syn::ExprPath, - }, -} + let generics = self.impl_generics(false); + let (impl_generics, _, where_clause) = generics.split_for_impl(); + let (_, ty_generics, _) = self.trait_generics.split_for_impl(); -/// Representation of [GraphQL interface][1] implementer for code generation. -/// -/// [1]: https://spec.graphql.org/June2018/#sec-Interfaces -#[derive(Clone, Debug)] -struct Implementer { - /// Rust type that this [GraphQL interface][1] [`Implementer`] is - /// represented by. - /// - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - ty: syn::Type, + self.fields + .iter() + .map(|field| { + let field_name = &field.name; + let mut return_ty = field.ty.clone(); + generics.replace_type_with_defaults(&mut return_ty); + + let (args_tys, args_names): (Vec<_>, Vec<_>) = field + .arguments + .iter() + .flat_map(|vec| vec.iter()) + .filter_map(|arg| match arg { + field::MethodArgument::Regular(arg) => Some((&arg.ty, &arg.name)), + _ => None, + }) + .unzip(); - /// Custom [`ImplementerDowncast`] for this [`Implementer`]. - /// - /// If absent, then [`Implementer`] is downcast from an enum variant or a - /// trait object. - downcast: Option, + quote! { + #[allow(non_snake_case)] + impl#impl_generics ::juniper::macros::reflection::FieldMeta< + #scalar, + { ::juniper::macros::reflection::fnv1a128(#field_name) } + > for #ty#ty_generics #where_clause { + type Context = #context; + type TypeInfo = (); + const TYPE: ::juniper::macros::reflection::Type = + <#return_ty as ::juniper::macros::reflection::BaseType<#scalar>>::NAME; + const SUB_TYPES: ::juniper::macros::reflection::Types = + <#return_ty as ::juniper::macros::reflection::BaseSubTypes<#scalar>>::NAMES; + const WRAPPED_VALUE: ::juniper::macros::reflection::WrappedValue = + <#return_ty as ::juniper::macros::reflection::WrappedType<#scalar>>::VALUE; + const ARGUMENTS: &'static [( + ::juniper::macros::reflection::Name, + ::juniper::macros::reflection::Type, + ::juniper::macros::reflection::WrappedValue, + )] = &[#(( + #args_names, + <#args_tys as ::juniper::macros::reflection::BaseType<#scalar>>::NAME, + <#args_tys as ::juniper::macros::reflection::WrappedType<#scalar>>::VALUE, + )),*]; + } + } + }) + .collect() + } - /// Rust type of [`Context`] that this [GraphQL interface][1] - /// [`Implementer`] requires for downcasting. - /// - /// It's available only when code generation happens for Rust traits and a - /// trait method contains context argument. + /// Returns generated code implementing [`Field`] trait for each field of + /// this [GraphQL interface][1]. /// - /// [`Context`]: juniper::Context + /// [`Field`]: juniper::macros::reflection::Field /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - context: Option, + fn impl_field_tokens(&self) -> TokenStream { + let ty = &self.enum_alias_ident; + let scalar = &self.scalar; + let const_scalar = self.scalar.default_ty(); - /// [`ScalarValue`] parametrization of this [`Implementer`]. - /// - /// [`ScalarValue`]: juniper::ScalarValue - scalar: scalar::Type, -} + let impl_tys = self.implementers.iter().collect::>(); + let impl_idents = self + .implementers + .iter() + .filter_map(|ty| ty.path.segments.last().map(|seg| &seg.ident)) + .collect::>(); -impl Implementer { - /// Returns generated code of downcasting this [`Implementer`] via custom - /// [`ImplementerDowncast`]. - /// - /// Returns [`None`] if there is no custom [`Implementer::downcast`]. - #[must_use] - fn downcast_call_tokens( - &self, - trait_ty: &syn::Type, - ctx: Option, - ) -> Option { - let ctx = ctx.unwrap_or_else(|| parse_quote! { executor.context() }); - let mut ctx_arg = Some(quote! { , ::juniper::FromContext::from(#ctx) }); - - let fn_path = match self.downcast.as_ref()? { - ImplementerDowncast::Method { name, with_context } => { - if !with_context { - ctx_arg = None; + let generics = self.impl_generics(false); + let (impl_generics, _, where_clause) = generics.split_for_impl(); + let (_, ty_generics, _) = self.trait_generics.split_for_impl(); + + self.fields + .iter() + .filter_map(|field| { + if field.is_async { + return None; } - quote! { ::#name } - } - ImplementerDowncast::External { path } => { - quote! { #path } - } - }; - Some(quote! { - #fn_path(self #ctx_arg) - }) + let field_name = &field.name; + let mut return_ty = field.ty.clone(); + generics.replace_type_with_defaults(&mut return_ty); + + let const_ty_generics = self.const_trait_generics(); + + let unreachable_arm = (self.implementers.is_empty() + || !self.trait_generics.params.is_empty()) + .then(|| { + quote! { _ => unreachable!() } + }); + + Some(quote! { + #[allow(non_snake_case)] + impl#impl_generics ::juniper::macros::reflection::Field< + #scalar, + { ::juniper::macros::reflection::fnv1a128(#field_name) } + > for #ty#ty_generics #where_clause { + fn call( + &self, + info: &Self::TypeInfo, + args: &::juniper::Arguments<#scalar>, + executor: &::juniper::Executor, + ) -> ::juniper::ExecutionResult<#scalar> { + match self { + #(#ty::#impl_idents(v) => { + ::juniper::assert_field!( + #ty#const_ty_generics, + #impl_tys, + #const_scalar, + #field_name, + ); + + <_ as ::juniper::macros::reflection::Field< + #scalar, + { ::juniper::macros::reflection::fnv1a128(#field_name) }, + >>::call(v, info, args, executor) + })* + #unreachable_arm + } + } + } + }) + }) + .collect() } - /// Returns generated code for the [`GraphQLValue::concrete_type_name`] - /// method, which returns name of the GraphQL type represented by this - /// [`Implementer`]. - /// - /// Returns [`None`] if there is no custom [`Implementer::downcast`]. + /// Returns generated code implementing [`AsyncField`] trait for each field + /// of this [GraphQL interface][1]. /// - /// [`GraphQLValue::concrete_type_name`]: juniper::GraphQLValue::concrete_type_name - #[must_use] - fn method_concrete_type_name_tokens(&self, trait_ty: &syn::Type) -> Option { - self.downcast.as_ref()?; - - let ty = &self.ty; + /// [`AsyncField`]: juniper::macros::reflection::AsyncField + /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces + fn impl_async_field_tokens(&self) -> TokenStream { + let ty = &self.enum_alias_ident; let scalar = &self.scalar; + let const_scalar = self.scalar.default_ty(); + + let impl_tys = self.implementers.iter().collect::>(); + let impl_idents = self + .implementers + .iter() + .filter_map(|ty| ty.path.segments.last().map(|seg| &seg.ident)) + .collect::>(); - let downcast = self.downcast_call_tokens(trait_ty, Some(parse_quote! { context })); + let generics = self.impl_generics(true); + let (impl_generics, _, where_clause) = generics.split_for_impl(); + let (_, ty_generics, _) = self.trait_generics.split_for_impl(); - // Doing this may be quite an expensive, because resolving may contain some heavy - // computation, so we're preforming it twice. Unfortunately, we have no other options here, - // until the `juniper::GraphQLType` itself will allow to do it in some cleverer way. - Some(quote! { - if (#downcast as ::std::option::Option<&#ty>).is_some() { - return <#ty as ::juniper::GraphQLType<#scalar>>::name(info).unwrap().to_string(); - } - }) + self.fields + .iter() + .map(|field| { + let field_name = &field.name; + let mut return_ty = field.ty.clone(); + generics.replace_type_with_defaults(&mut return_ty); + + let const_ty_generics = self.const_trait_generics(); + + let unreachable_arm = (self.implementers.is_empty() + || !self.trait_generics.params.is_empty()) + .then(|| { + quote! { _ => unreachable!() } + }); + + quote! { + #[allow(non_snake_case)] + impl#impl_generics ::juniper::macros::reflection::AsyncField< + #scalar, + { ::juniper::macros::reflection::fnv1a128(#field_name) } + > for #ty#ty_generics #where_clause { + fn call<'b>( + &'b self, + info: &'b Self::TypeInfo, + args: &'b ::juniper::Arguments<#scalar>, + executor: &'b ::juniper::Executor, + ) -> ::juniper::BoxFuture<'b, ::juniper::ExecutionResult<#scalar>> { + match self { + #(#ty::#impl_idents(v) => { + ::juniper::assert_field!( + #ty#const_ty_generics, + #impl_tys, + #const_scalar, + #field_name, + ); + + <_ as ::juniper::macros::reflection::AsyncField< + #scalar, + { ::juniper::macros::reflection::fnv1a128(#field_name) }, + >>::call(v, info, args, executor) + })* + #unreachable_arm + } + } + } + } + }) + .collect() } - /// Returns generated code for the [`GraphQLValue::resolve_into_type`][0] - /// method, which downcasts the [GraphQL interface][1] type into this - /// [`Implementer`] synchronously. - /// - /// Returns [`None`] if there is no custom [`Implementer::downcast`]. + /// Returns generated code for the [`GraphQLValue::concrete_type_name`][0] + /// method, which returns name of the underlying [`Implementer`] GraphQL + /// type contained in this [`EnumType`]. /// - /// [0]: juniper::GraphQLValue::resolve_into_type - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces + /// [0]: juniper::GraphQLValue::concrete_type_name #[must_use] - fn method_resolve_into_type_tokens(&self, trait_ty: &syn::Type) -> Option { - self.downcast.as_ref()?; - - let ty = &self.ty; - let ty_name = ty.to_token_stream().to_string(); + fn method_concrete_type_name_tokens(&self) -> TokenStream { let scalar = &self.scalar; - let downcast = self.downcast_call_tokens(trait_ty, None); + let match_arms = self + .implementers + .iter() + .filter_map(|ty| ty.path.segments.last().map(|seg| (&seg.ident, ty))) + .map(|(ident, ty)| { + quote! { + Self::#ident(v) => < + #ty as ::juniper::GraphQLValue<#scalar> + >::concrete_type_name(v, context, info), + } + }); - let resolving_code = gen::sync_resolving_code(); + let non_exhaustive_match_arm = + (!self.trait_generics.params.is_empty() || self.implementers.is_empty()).then(|| { + quote! { _ => unreachable!(), } + }); - Some(quote! { - if type_name == <#ty as ::juniper::GraphQLType<#scalar>>::name(info) - .ok_or_else(|| ::juniper::macros::helper::err_unnamed_type(#ty_name))? - { - let res = #downcast; - return #resolving_code; + quote! { + match self { + #( #match_arms )* + #non_exhaustive_match_arm } - }) + } } /// Returns generated code for the /// [`GraphQLValueAsync::resolve_into_type_async`][0] method, which - /// downcasts the [GraphQL interface][1] type into this [`Implementer`] + /// downcasts this [`EnumType`] into its underlying [`Implementer`] type /// asynchronously. /// - /// Returns [`None`] if there is no custom [`Implementer::downcast`]. - /// /// [0]: juniper::GraphQLValueAsync::resolve_into_type_async - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces #[must_use] - fn method_resolve_into_type_async_tokens(&self, trait_ty: &syn::Type) -> Option { - self.downcast.as_ref()?; - - let ty = &self.ty; - let ty_name = ty.to_token_stream().to_string(); - let scalar = &self.scalar; - - let downcast = self.downcast_call_tokens(trait_ty, None); - + fn method_resolve_into_type_async_tokens(&self) -> TokenStream { let resolving_code = gen::async_resolving_code(None); - Some(quote! { - match <#ty as ::juniper::GraphQLType<#scalar>>::name(info) { - Some(name) => { - if type_name == name { - let fut = ::juniper::futures::future::ready(#downcast); - return #resolving_code; - } - } - None => return ::juniper::macros::helper::err_unnamed_type_fut(#ty_name), - } - }) - } -} - -/// Representation of Rust enum implementing [GraphQL interface][1] type for -/// code generation. -/// -/// [1]: https://spec.graphql.org/June2018/#sec-Interfaces -struct EnumType { - /// Name of this [`EnumType`] to generate it with. - ident: syn::Ident, - - /// [`syn::Visibility`] of this [`EnumType`] to generate it with. - visibility: syn::Visibility, - - /// Rust types of all [GraphQL interface][1] implements to represent - /// variants of this [`EnumType`]. - /// - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - variants: Vec, - - /// Name of the trait describing the [GraphQL interface][1] represented by - /// this [`EnumType`]. - /// - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - trait_ident: syn::Ident, - - /// [`syn::Generics`] of the trait describing the [GraphQL interface][1] - /// represented by this [`EnumType`]. - /// - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - trait_generics: syn::Generics, - - /// Associated types of the trait describing the [GraphQL interface][1] - /// represented by this [`EnumType`]. - trait_types: Vec<(syn::Ident, syn::Generics)>, - - /// Associated constants of the trait describing the [GraphQL interface][1] - /// represented by this [`EnumType`]. - /// - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - trait_consts: Vec<(syn::Ident, syn::Type)>, - - /// Methods of the trait describing the [GraphQL interface][1] represented - /// by this [`EnumType`]. - /// - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - trait_methods: Vec, - - /// [`ScalarValue`] parametrization to generate [`GraphQLType`] - /// implementation with for this [`EnumType`]. - /// - /// [`GraphQLType`]: juniper::GraphQLType - /// [`ScalarValue`]: juniper::ScalarValue - scalar: scalar::Type, -} - -impl ToTokens for EnumType { - fn to_tokens(&self, into: &mut TokenStream) { - self.type_definition_tokens().to_tokens(into); - into.append_all(self.impl_from_tokens()); - self.impl_trait_tokens().to_tokens(into); - } -} - -impl EnumType { - /// Constructs a new [`EnumType`] out of the given parameters. - #[must_use] - fn new( - r#trait: &syn::ItemTrait, - meta: &TraitAttr, - implers: &[Implementer], - scalar: scalar::Type, - ) -> Self { - Self { - ident: meta - .r#enum - .as_ref() - .map(SpanContainer::as_ref) - .cloned() - .unwrap_or_else(|| format_ident!("{}Value", r#trait.ident)), - visibility: r#trait.vis.clone(), - variants: implers.iter().map(|impler| impler.ty.clone()).collect(), - trait_ident: r#trait.ident.clone(), - trait_generics: r#trait.generics.clone(), - trait_types: r#trait - .items - .iter() - .filter_map(|i| { - if let syn::TraitItem::Type(ty) = i { - Some((ty.ident.clone(), ty.generics.clone())) - } else { - None - } - }) - .collect(), - trait_consts: r#trait - .items - .iter() - .filter_map(|i| { - if let syn::TraitItem::Const(cnst) = i { - Some((cnst.ident.clone(), cnst.ty.clone())) - } else { - None - } - }) - .collect(), - trait_methods: r#trait - .items - .iter() - .filter_map(|i| { - if let syn::TraitItem::Method(m) = i { - Some(m.sig.clone()) - } else { - None - } - }) - .collect(), - scalar, - } - } - - /// Returns name of a single variant of this [`EnumType`] by the given - /// underlying [`syn::Type`] of the variant. - #[must_use] - fn variant_ident(ty: &syn::Type) -> &syn::Ident { - if let syn::Type::Path(p) = ty { - &p.path.segments.last().unwrap().ident - } else { - unreachable!("GraphQL object has unexpected type `{}`", quote! { #ty }) - } - } - - /// Indicates whether this [`EnumType`] has non-exhaustive phantom variant - /// to hold type parameters. - #[must_use] - fn has_phantom_variant(&self) -> bool { - !self.trait_generics.params.is_empty() - } - - /// Returns generate code for dispatching non-exhaustive phantom variant of - /// this [`EnumType`] in `match` expressions. - /// - /// Returns [`None`] if this [`EnumType`] is exhaustive. - #[must_use] - fn non_exhaustive_match_arm_tokens(&self) -> Option { - if self.has_phantom_variant() || self.variants.is_empty() { - Some(quote! { _ => unreachable!(), }) - } else { - None - } - } - - /// Returns prepared [`syn::Generics`] for [`GraphQLType`] trait (and - /// similar) implementation of this [`EnumType`]. - /// - /// If `for_async` is `true`, then additional predicates are added to suit - /// the [`GraphQLAsyncValue`] trait (and similar) requirements. - /// - /// [`GraphQLAsyncValue`]: juniper::GraphQLAsyncValue - /// [`GraphQLType`]: juniper::GraphQLType - #[must_use] - fn impl_generics(&self, for_async: bool) -> syn::Generics { - let mut generics = self.trait_generics.clone(); - - let scalar = &self.scalar; - if scalar.is_implicit_generic() { - generics.params.push(parse_quote! { #scalar }); - } - if scalar.is_generic() { - generics - .make_where_clause() - .predicates - .push(parse_quote! { #scalar: ::juniper::ScalarValue }); - } - if let Some(bound) = scalar.bounds() { - generics.make_where_clause().predicates.push(bound); - } - - if for_async { - let self_ty = if self.trait_generics.lifetimes().next().is_some() { - // Modify lifetime names to omit "lifetime name `'a` shadows a - // lifetime name that is already in scope" error. - let mut generics = self.trait_generics.clone(); - for lt in generics.lifetimes_mut() { - let ident = lt.lifetime.ident.unraw(); - lt.lifetime.ident = format_ident!("__fa__{}", ident); - } - - let lifetimes = generics.lifetimes().map(|lt| <.lifetime); - let ty = &self.ident; - let (_, ty_generics, _) = generics.split_for_impl(); - - quote! { for<#( #lifetimes ),*> #ty#ty_generics } - } else { - quote! { Self } - }; - generics - .make_where_clause() - .predicates - .push(parse_quote! { #self_ty: Sync }); - - if scalar.is_generic() { - generics - .make_where_clause() - .predicates - .push(parse_quote! { #scalar: Send + Sync }); - } - } - - generics - } - - /// Returns full type signature of the original trait describing the - /// [GraphQL interface][1] for this [`EnumType`]. - /// - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - #[must_use] - fn trait_ty(&self) -> syn::Type { - let ty = &self.trait_ident; - let (_, generics, _) = self.trait_generics.split_for_impl(); - - parse_quote! { #ty#generics } - } - - /// Returns generated code of the full type signature of this [`EnumType`]. - #[must_use] - fn ty_tokens(&self) -> TokenStream { - let ty = &self.ident; - let (_, generics, _) = self.trait_generics.split_for_impl(); - - quote! { #ty#generics } - } - - /// Returns generate code of the Rust type definitions of this [`EnumType`]. - /// - /// If the [`EnumType::trait_generics`] are not empty, then they are - /// contained in the generated enum too. - #[must_use] - fn type_definition_tokens(&self) -> TokenStream { - let enum_ty = &self.ident; - let generics = &self.trait_generics; - let vis = &self.visibility; - - let doc = format!( - "Type implementing [GraphQL interface][1] represented by `{}` trait.\ - \n\n\ - [1]: https://spec.graphql.org/June2018/#sec-Interfaces", - self.trait_ident, - ); - - let variants = self.variants.iter().map(|ty| { - let variant = Self::variant_ident(ty); - let doc = format!( - "`{}` implementer of this GraphQL interface.", - quote! { #ty }, - ); - - quote! { - #[doc = #doc] - #variant(#ty), - } - }); - - let phantom_variant = if self.has_phantom_variant() { - let ty_params = generics.params.iter().map(|p| { - let ty = match p { - syn::GenericParam::Type(ty) => { - let ident = &ty.ident; - quote! { #ident } - } - syn::GenericParam::Lifetime(lt) => { - let lifetime = <.lifetime; - quote! { &#lifetime () } - } - syn::GenericParam::Const(_) => unimplemented!(), - }; + let match_arms = self.implementers.iter().filter_map(|ty| { + ty.path.segments.last().map(|ident| { quote! { - ::std::marker::PhantomData<::std::sync::atomic::AtomicPtr>> - } - }); - - Some(quote! { - #[doc(hidden)] - __Phantom(#( #ty_params ),*), - }) - } else { - None - }; - - quote! { - #[automatically_derived] - #[doc = #doc] - #vis enum #enum_ty#generics { - #( #variants )* - #phantom_variant - } - } - } - - /// Returns generated code implementing [`From`] trait for this [`EnumType`] - /// from its [`EnumType::variants`]. - fn impl_from_tokens(&self) -> impl Iterator + '_ { - let enum_ty = &self.ident; - let (impl_generics, generics, where_clause) = self.trait_generics.split_for_impl(); - - self.variants.iter().map(move |ty| { - let variant = Self::variant_ident(ty); - - quote! { - #[automatically_derived] - impl#impl_generics From<#ty> for #enum_ty#generics #where_clause { - fn from(v: #ty) -> Self { - Self::#variant(v) + Self::#ident(v) => { + let fut = ::juniper::futures::future::ready(v); + #resolving_code } } - } - }) - } - - /// Returns generated code implementing the original trait describing the - /// [GraphQL interface][1] for this [`EnumType`]. - /// - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - #[must_use] - fn impl_trait_tokens(&self) -> TokenStream { - let enum_ty = &self.ident; - - let trait_ident = &self.trait_ident; - let (impl_generics, generics, where_clause) = self.trait_generics.split_for_impl(); - - let var_ty = self.variants.first(); - - let assoc_types = self.trait_types.iter().map(|(ty, ty_gen)| { - quote! { - type #ty#ty_gen = <#var_ty as #trait_ident#generics>::#ty#ty_gen; - } - }); - - let assoc_consts = self.trait_consts.iter().map(|(ident, ty)| { - quote! { - const #ident: #ty = <#var_ty as #trait_ident#generics>::#ident; - } + }) }); - - let methods = self.trait_methods.iter().map(|sig| { - let method = &sig.ident; - - let mut sig = sig.clone(); - let mut args = vec![]; - for (n, arg) in sig.inputs.iter_mut().enumerate() { - match arg { - syn::FnArg::Receiver(_) => {} - syn::FnArg::Typed(a) => { - if !matches!(&*a.pat, syn::Pat::Ident(_)) { - let ident = format_ident!("__arg{}", n); - a.pat = parse_quote! { #ident }; - } - args.push(a.pat.clone()); - } - } - } - - let and_await = if sig.asyncness.is_some() { - Some(quote! { .await }) - } else { - None - }; - - let match_arms = self.variants.iter().map(|ty| { - let variant = Self::variant_ident(ty); - let args = args.clone(); - - quote! { - Self::#variant(v) => - <#ty as #trait_ident#generics>::#method(v #( , #args )* )#and_await, - } + let non_exhaustive_match_arm = + (!self.trait_generics.params.is_empty() || self.implementers.is_empty()).then(|| { + quote! { _ => unreachable!(), } }); - let non_exhaustive_match_arm = self.non_exhaustive_match_arm_tokens(); - - quote! { - #sig { - match self { - #( #match_arms )* - #non_exhaustive_match_arm - } - } - } - }); - - let mut impl_tokens = quote! { - #[allow(deprecated)] - #[automatically_derived] - impl#impl_generics #trait_ident#generics for #enum_ty#generics #where_clause { - #( #assoc_types )* - - #( #assoc_consts )* - - #( #methods )* - } - }; - - if self.trait_methods.iter().any(|sig| sig.asyncness.is_some()) { - let mut ast: syn::ItemImpl = parse_quote! { #impl_tokens }; - inject_async_trait( - &mut ast.attrs, - ast.items.iter_mut().filter_map(|i| { - if let syn::ImplItem::Method(m) = i { - Some(&mut m.sig) - } else { - None - } - }), - &ast.generics, - ); - impl_tokens = quote! { #ast }; - } - - impl_tokens - } - - /// Returns generated code for the [`GraphQLValue::concrete_type_name`][0] - /// method, which returns name of the underlying [`Implementer`] GraphQL - /// type contained in this [`EnumType`]. - /// - /// [0]: juniper::GraphQLValue::concrete_type_name - #[must_use] - fn method_concrete_type_name_tokens(&self) -> TokenStream { - let scalar = &self.scalar; - - let match_arms = self.variants.iter().map(|ty| { - let variant = Self::variant_ident(ty); - - quote! { - Self::#variant(v) => < - #ty as ::juniper::GraphQLValue<#scalar> - >::concrete_type_name(v, context, info), - } - }); - let non_exhaustive_match_arm = self.non_exhaustive_match_arm_tokens(); quote! { match self { @@ -1391,14 +1059,18 @@ impl EnumType { fn method_resolve_into_type_tokens(&self) -> TokenStream { let resolving_code = gen::sync_resolving_code(); - let match_arms = self.variants.iter().map(|ty| { - let variant = Self::variant_ident(ty); - - quote! { - Self::#variant(res) => #resolving_code, - } + let match_arms = self.implementers.iter().filter_map(|ty| { + ty.path.segments.last().map(|ident| { + quote! { + Self::#ident(res) => #resolving_code, + } + }) }); - let non_exhaustive_match_arm = self.non_exhaustive_match_arm_tokens(); + + let non_exhaustive_match_arm = + (!self.trait_generics.params.is_empty() || self.implementers.is_empty()).then(|| { + quote! { _ => unreachable!(), } + }); quote! { match self { @@ -1408,94 +1080,47 @@ impl EnumType { } } - /// Returns generated code for the - /// [`GraphQLValueAsync::resolve_into_type_async`][0] method, which - /// downcasts this [`EnumType`] into its underlying [`Implementer`] type - /// asynchronously. - /// - /// [0]: juniper::GraphQLValueAsync::resolve_into_type_async + /// Returns trait generics replaced with default values for usage in `const` + /// context. #[must_use] - fn method_resolve_into_type_async_tokens(&self) -> TokenStream { - let resolving_code = gen::async_resolving_code(None); - - let match_arms = self.variants.iter().map(|ty| { - let variant = Self::variant_ident(ty); + fn const_trait_generics(&self) -> syn::PathArguments { + struct GenericsForConst(syn::AngleBracketedGenericArguments); - quote! { - Self::#variant(v) => { - let fut = ::juniper::futures::future::ready(v); - #resolving_code - } - } - }); - let non_exhaustive_match_arm = self.non_exhaustive_match_arm_tokens(); - - quote! { - match self { - #( #match_arms )* - #non_exhaustive_match_arm + impl Visit<'_> for GenericsForConst { + fn visit_generic_param(&mut self, param: &syn::GenericParam) { + let arg = match param { + syn::GenericParam::Lifetime(_) => parse_quote!( 'static ), + syn::GenericParam::Type(ty) => { + if ty.default.is_none() { + parse_quote!(::juniper::DefaultScalarValue) + } else { + return; + } + } + syn::GenericParam::Const(c) => { + if c.default.is_none() { + // This hack works because only `min_const_generics` + // are enabled for now. + // TODO: replace this once full `const_generics` are + // available. + // Maybe with `<_ as Default>::default()`? + parse_quote!({ 0_u8 as _ }) + } else { + return; + } + } + }; + self.0.args.push(arg) } } - } -} - -/// Representation of Rust [trait object][2] implementing [GraphQL interface][1] -/// type for code generation. -/// -/// [1]: https://spec.graphql.org/June2018/#sec-Interfaces -/// [2]: https://doc.rust-lang.org/reference/types/trait-object.html -struct TraitObjectType { - /// Name of this [`TraitObjectType`] to generate it with. - ident: syn::Ident, - - /// [`syn::Visibility`] of this [`TraitObjectType`] to generate it with. - visibility: syn::Visibility, - - /// Name of the trait describing the [GraphQL interface][1] represented by - /// this [`TraitObjectType`]. - /// - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - trait_ident: syn::Ident, - - /// [`syn::Generics`] of the trait describing the [GraphQL interface][1] - /// represented by this [`TraitObjectType`]. - /// - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - trait_generics: syn::Generics, - - /// [`ScalarValue`] parametrization of this [`TraitObjectType`] to generate - /// it with. - /// - /// [`ScalarValue`]: juniper::ScalarValue - scalar: scalar::Type, - /// Rust type of [`Context`] to generate this [`TraitObjectType`] with. - /// - /// [`Context`]: juniper::Context - context: syn::Type, -} - -impl TraitObjectType { - /// Constructs a new [`TraitObjectType`] out of the given parameters. - #[must_use] - fn new( - r#trait: &syn::ItemTrait, - meta: &TraitAttr, - scalar: scalar::Type, - context: syn::Type, - ) -> Self { - Self { - ident: meta.r#dyn.as_ref().unwrap().as_ref().clone(), - visibility: r#trait.vis.clone(), - trait_ident: r#trait.ident.clone(), - trait_generics: r#trait.generics.clone(), - scalar, - context, - } + let mut visitor = GenericsForConst(parse_quote!( <> )); + visitor.visit_generics(&self.trait_generics); + syn::PathArguments::AngleBracketed(visitor.0) } /// Returns prepared [`syn::Generics`] for [`GraphQLType`] trait (and - /// similar) implementation of this [`TraitObjectType`]. + /// similar) implementation of this [`EnumType`]. /// /// If `for_async` is `true`, then additional predicates are added to suit /// the [`GraphQLAsyncValue`] trait (and similar) requirements. @@ -1506,8 +1131,6 @@ impl TraitObjectType { fn impl_generics(&self, for_async: bool) -> syn::Generics { let mut generics = self.trait_generics.clone(); - generics.params.push(parse_quote! { '__obj }); - let scalar = &self.scalar; if scalar.is_implicit_generic() { generics.params.push(parse_quote! { #scalar }); @@ -1523,10 +1146,28 @@ impl TraitObjectType { } if for_async { + let self_ty = if self.trait_generics.lifetimes().next().is_some() { + // Modify lifetime names to omit "lifetime name `'a` shadows a + // lifetime name that is already in scope" error. + let mut generics = self.trait_generics.clone(); + for lt in generics.lifetimes_mut() { + let ident = lt.lifetime.ident.unraw(); + lt.lifetime.ident = format_ident!("__fa__{}", ident); + } + + let lifetimes = generics.lifetimes().map(|lt| <.lifetime); + let ty = &self.enum_alias_ident; + let (_, ty_generics, _) = generics.split_for_impl(); + + quote! { for<#( #lifetimes ),*> #ty#ty_generics } + } else { + quote! { Self } + }; generics .make_where_clause() .predicates - .push(parse_quote! { Self: Sync }); + .push(parse_quote! { #self_ty: Sync }); + if scalar.is_generic() { generics .make_where_clause() @@ -1538,243 +1179,11 @@ impl TraitObjectType { generics } - /// Returns full type signature of the original trait describing the - /// [GraphQL interface][1] for this [`TraitObjectType`]. - /// - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - #[must_use] - fn trait_ty(&self) -> syn::Type { - let ty = &self.trait_ident; - - let mut generics = self.trait_generics.clone(); - if !self.scalar.is_explicit_generic() { - let scalar = &self.scalar; - generics.params.push(parse_quote! { #scalar }); - } - let (_, generics, _) = generics.split_for_impl(); - - parse_quote! { #ty#generics } - } - - /// Returns generated code of the full type signature of this - /// [`TraitObjectType`]. - #[must_use] - fn ty_tokens(&self) -> TokenStream { - let ty = &self.trait_ident; - - let mut generics = self.trait_generics.clone(); - generics.remove_defaults(); - generics.move_bounds_to_where_clause(); - if !self.scalar.is_explicit_generic() { - let scalar = &self.scalar; - generics.params.push(parse_quote! { #scalar }); - } - let ty_params = &generics.params; - - let context = &self.context; - - quote! { - dyn #ty<#ty_params, Context = #context, TypeInfo = ()> + '__obj + Send + Sync - } - } - - /// Returns generated code for the [`GraphQLValue::concrete_type_name`][0] - /// method, which returns name of the underlying [`Implementer`] GraphQL - /// type contained in this [`TraitObjectType`]. - /// - /// [0]: juniper::GraphQLValue::concrete_type_name - #[must_use] - fn method_concrete_type_name_tokens(&self) -> TokenStream { - quote! { - self.as_dyn_graphql_value().concrete_type_name(context, info) - } - } - - /// Returns generated code for the [`GraphQLValue::resolve_into_type`][0] - /// method, which downcasts this [`TraitObjectType`] into its underlying - /// [`Implementer`] type synchronously. - /// - /// [0]: juniper::GraphQLValue::resolve_into_type - #[must_use] - fn method_resolve_into_type_tokens(&self) -> TokenStream { - let resolving_code = gen::sync_resolving_code(); - - quote! { - let res = self.as_dyn_graphql_value(); - #resolving_code - } - } - - /// Returns generated code for the - /// [`GraphQLValueAsync::resolve_into_type_async`][0] method, which - /// downcasts this [`TraitObjectType`] into its underlying [`Implementer`] - /// type asynchronously. - /// - /// [0]: juniper::GraphQLValueAsync::resolve_into_type_async - #[must_use] - fn method_resolve_into_type_async_tokens(&self) -> TokenStream { - let resolving_code = gen::async_resolving_code(None); - - quote! { - let fut = ::juniper::futures::future::ready(self.as_dyn_graphql_value_async()); - #resolving_code - } - } -} - -impl ToTokens for TraitObjectType { - fn to_tokens(&self, into: &mut TokenStream) { - let dyn_ty = &self.ident; - let vis = &self.visibility; - - let doc = format!( - "Helper alias for the `{}` [trait object][2] implementing [GraphQL interface][1].\ - \n\n\ - [1]: https://spec.graphql.org/June2018/#sec-Interfaces\n\ - [2]: https://doc.rust-lang.org/reference/types/trait-object.html", - self.trait_ident, - ); - - let trait_ident = &self.trait_ident; - - let mut generics = self.trait_generics.clone(); - if !self.scalar.is_explicit_generic() { - let scalar_ty = self.scalar.generic_ty(); - let default_ty = self.scalar.default_ty(); - generics - .params - .push(parse_quote! { #scalar_ty = #default_ty }); - } - - let (mut ty_params_left, mut ty_params_right) = (None, None); - if !generics.params.is_empty() { - // We should preserve defaults for left side. - generics.move_bounds_to_where_clause(); - let params = &generics.params; - ty_params_left = Some(quote! { , #params }); - - generics.remove_defaults(); - let params = &generics.params; - ty_params_right = Some(quote! { #params, }); - }; - - let context = &self.context; - - let dyn_alias = quote! { - #[automatically_derived] - #[doc = #doc] - #vis type #dyn_ty<'a #ty_params_left> = - dyn #trait_ident<#ty_params_right Context = #context, TypeInfo = ()> + - 'a + Send + Sync; - }; - - into.append_all(&[dyn_alias]); - } -} - -/// Representation of possible Rust types implementing [GraphQL interface][1] -/// type for code generation. -/// -/// [1]: https://spec.graphql.org/June2018/#sec-Interfaces -enum Type { - /// [GraphQL interface][1] type implementation as Rust enum. - /// - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - Enum(Box), - - /// [GraphQL interface][1] type implementation as Rust [trait object][2]. - /// - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - /// [2]: https://doc.rust-lang.org/reference/types/trait-object.html - TraitObject(Box), -} - -impl ToTokens for Type { - fn to_tokens(&self, into: &mut TokenStream) { - match self { - Self::Enum(e) => e.to_tokens(into), - Self::TraitObject(o) => o.to_tokens(into), - } - } -} - -impl Type { - /// Returns prepared [`syn::Generics`] for [`GraphQLType`] trait (and - /// similar) implementation of this [`Type`]. - /// - /// If `for_async` is `true`, then additional predicates are added to suit - /// the [`GraphQLAsyncValue`] trait (and similar) requirements. - /// - /// [`GraphQLAsyncValue`]: juniper::GraphQLAsyncValue - /// [`GraphQLType`]: juniper::GraphQLType - #[must_use] - fn impl_generics(&self, for_async: bool) -> (TokenStream, Option) { - let generics = match self { - Self::Enum(e) => e.impl_generics(for_async), - Self::TraitObject(o) => o.impl_generics(for_async), - }; - let (impl_generics, _, where_clause) = generics.split_for_impl(); - (quote! { #impl_generics }, where_clause.cloned()) - } - - /// Returns full type signature of the original trait describing the - /// [GraphQL interface][1] for this [`Type`]. - /// - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - #[must_use] - fn trait_ty(&self) -> syn::Type { - match self { - Self::Enum(e) => e.trait_ty(), - Self::TraitObject(o) => o.trait_ty(), - } - } - - /// Returns generated code of the full type signature of this [`Type`]. - #[must_use] - fn ty_tokens(&self) -> TokenStream { - match self { - Self::Enum(e) => e.ty_tokens(), - Self::TraitObject(o) => o.ty_tokens(), - } - } - - /// Returns generated code for the [`GraphQLValue::concrete_type_name`][0] - /// method, which returns name of the underlying [`Implementer`] GraphQL - /// type contained in this [`Type`]. - /// - /// [0]: juniper::GraphQLValue::concrete_type_name - #[must_use] - fn method_concrete_type_name_tokens(&self) -> TokenStream { - match self { - Self::Enum(e) => e.method_concrete_type_name_tokens(), - Self::TraitObject(o) => o.method_concrete_type_name_tokens(), - } - } - - /// Returns generated code for the [`GraphQLValue::resolve_into_type`][0] - /// method, which downcasts this [`Type`] into its underlying - /// [`Implementer`] type synchronously. - /// - /// [0]: juniper::GraphQLValue::resolve_into_type + /// Indicates whether this [`EnumType`] has non-exhaustive phantom variant + /// to hold type parameters. #[must_use] - fn method_resolve_into_type_tokens(&self) -> TokenStream { - match self { - Self::Enum(e) => e.method_resolve_into_type_tokens(), - Self::TraitObject(o) => o.method_resolve_into_type_tokens(), - } - } - - /// Returns generated code for the - /// [`GraphQLValueAsync::resolve_into_type_async`][0] method, which - /// downcasts this [`Type`] into its underlying [`Implementer`] type - /// asynchronously. - /// - /// [0]: juniper::GraphQLValueAsync::resolve_into_type_async - fn method_resolve_into_type_async_tokens(&self) -> TokenStream { - match self { - Self::Enum(e) => e.method_resolve_into_type_async_tokens(), - Self::TraitObject(o) => o.method_resolve_into_type_async_tokens(), - } + fn has_phantom_variant(&self) -> bool { + !self.trait_generics.params.is_empty() } } diff --git a/juniper_codegen/src/graphql_interface/new.rs b/juniper_codegen/src/graphql_interface/new.rs deleted file mode 100644 index a7621f7ab..000000000 --- a/juniper_codegen/src/graphql_interface/new.rs +++ /dev/null @@ -1,1204 +0,0 @@ -//! Code generation for [GraphQL interface][1]. -//! -//! [1]: https://spec.graphql.org/June2018/#sec-Interfaces - -use std::{collections::HashSet, convert::TryInto as _}; - -use proc_macro2::TokenStream; -use quote::{format_ident, quote, ToTokens}; -use syn::{ - ext::IdentExt as _, - parse::{Parse, ParseStream}, - parse_quote, - punctuated::Punctuated, - spanned::Spanned as _, - token, - visit::Visit, -}; - -use crate::{ - common::{ - field, gen, - parse::{ - attr::{err, OptionExt as _}, - GenericsExt as _, ParseBufferExt as _, - }, - scalar, - }, - util::{filter_attrs, get_doc_comment, span_container::SpanContainer, RenameRule}, -}; - -/// Available arguments behind `#[graphql_interface]` attribute placed on a -/// trait definition, when generating code for [GraphQL interface][1] type. -/// -/// [1]: https://spec.graphql.org/June2018/#sec-Interfaces -#[derive(Debug, Default)] -pub(crate) struct TraitAttr { - /// Explicitly specified name of [GraphQL interface][1] type. - /// - /// If [`None`], then Rust trait name is used by default. - /// - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - pub(crate) name: Option>, - - /// Explicitly specified [description][2] of [GraphQL interface][1] type. - /// - /// If [`None`], then Rust doc comment is used as [description][2], if any. - /// - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - /// [2]: https://spec.graphql.org/June2018/#sec-Descriptions - pub(crate) description: Option>, - - /// Explicitly specified identifier of the type alias of Rust enum type - /// behind the trait, being an actual implementation of a - /// [GraphQL interface][1] type. - /// - /// If [`None`], then `{trait_name}Value` identifier will be used. - /// - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - pub(crate) r#enum: Option>, - - /// Explicitly specified Rust types of [GraphQL objects][2] implementing - /// this [GraphQL interface][1] type. - /// - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - /// [2]: https://spec.graphql.org/June2018/#sec-Objects - pub(crate) implementers: HashSet>, - - /// Explicitly specified type of [`Context`] to use for resolving this - /// [GraphQL interface][1] type with. - /// - /// If [`None`], then unit type `()` is assumed as a type of [`Context`]. - /// - /// [`Context`]: juniper::Context - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - pub(crate) context: Option>, - - /// Explicitly specified type (or type parameter with its bounds) of - /// [`ScalarValue`] to resolve this [GraphQL interface][1] type with. - /// - /// If [`None`], then generated code will be generic over any - /// [`ScalarValue`] type, which, in turn, requires all [interface][1] - /// implementers to be generic over any [`ScalarValue`] type too. That's why - /// this type should be specified only if one of the implementers implements - /// [`GraphQLType`] in a non-generic way over [`ScalarValue`] type. - /// - /// [`GraphQLType`]: juniper::GraphQLType - /// [`ScalarValue`]: juniper::ScalarValue - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - pub(crate) scalar: Option>, - - /// Explicitly specified marker indicating that the Rust trait should be - /// transformed into [`async_trait`]. - /// - /// If [`None`], then trait will be transformed into [`async_trait`] only if - /// it contains async methods. - pub(crate) asyncness: Option>, - - /// Explicitly specified [`RenameRule`] for all fields of this - /// [GraphQL interface][1] type. - /// - /// If [`None`] then the default rule will be [`RenameRule::CamelCase`]. - /// - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - pub(crate) rename_fields: Option>, - - /// Indicator whether the generated code is intended to be used only inside - /// the [`juniper`] library. - pub(crate) is_internal: bool, -} - -impl Parse for TraitAttr { - fn parse(input: ParseStream<'_>) -> syn::Result { - let mut out = Self::default(); - while !input.is_empty() { - let ident = input.parse_any_ident()?; - match ident.to_string().as_str() { - "name" => { - input.parse::()?; - let name = input.parse::()?; - out.name - .replace(SpanContainer::new( - ident.span(), - Some(name.span()), - name.value(), - )) - .none_or_else(|_| err::dup_arg(&ident))? - } - "desc" | "description" => { - input.parse::()?; - let desc = input.parse::()?; - out.description - .replace(SpanContainer::new( - ident.span(), - Some(desc.span()), - desc.value(), - )) - .none_or_else(|_| err::dup_arg(&ident))? - } - "ctx" | "context" | "Context" => { - input.parse::()?; - let ctx = input.parse::()?; - out.context - .replace(SpanContainer::new(ident.span(), Some(ctx.span()), ctx)) - .none_or_else(|_| err::dup_arg(&ident))? - } - "scalar" | "Scalar" | "ScalarValue" => { - input.parse::()?; - let scl = input.parse::()?; - out.scalar - .replace(SpanContainer::new(ident.span(), Some(scl.span()), scl)) - .none_or_else(|_| err::dup_arg(&ident))? - } - "for" | "implementers" => { - input.parse::()?; - for impler in input.parse_maybe_wrapped_and_punctuated::< - syn::TypePath, token::Bracket, token::Comma, - >()? { - let impler_span = impler.span(); - out - .implementers - .replace(SpanContainer::new(ident.span(), Some(impler_span), impler)) - .none_or_else(|_| err::dup_arg(impler_span))?; - } - } - "enum" => { - input.parse::()?; - let alias = input.parse::()?; - out.r#enum - .replace(SpanContainer::new(ident.span(), Some(alias.span()), alias)) - .none_or_else(|_| err::dup_arg(&ident))? - } - "async" => { - let span = ident.span(); - out.asyncness - .replace(SpanContainer::new(span, Some(span), ident)) - .none_or_else(|_| err::dup_arg(span))?; - } - "rename_all" => { - input.parse::()?; - let val = input.parse::()?; - out.rename_fields - .replace(SpanContainer::new( - ident.span(), - Some(val.span()), - val.try_into()?, - )) - .none_or_else(|_| err::dup_arg(&ident))?; - } - "internal" => { - out.is_internal = true; - } - name => { - return Err(err::unknown_arg(&ident, name)); - } - } - input.try_parse::()?; - } - Ok(out) - } -} - -impl TraitAttr { - /// Tries to merge two [`TraitAttr`]s into a single one, reporting about - /// duplicates, if any. - fn try_merge(self, mut another: Self) -> syn::Result { - Ok(Self { - name: try_merge_opt!(name: self, another), - description: try_merge_opt!(description: self, another), - context: try_merge_opt!(context: self, another), - scalar: try_merge_opt!(scalar: self, another), - implementers: try_merge_hashset!(implementers: self, another => span_joined), - r#enum: try_merge_opt!(r#enum: self, another), - asyncness: try_merge_opt!(asyncness: self, another), - rename_fields: try_merge_opt!(rename_fields: self, another), - is_internal: self.is_internal || another.is_internal, - }) - } - - /// Parses [`TraitAttr`] from the given multiple `name`d [`syn::Attribute`]s - /// placed on a trait definition. - pub(crate) fn from_attrs(name: &str, attrs: &[syn::Attribute]) -> syn::Result { - let mut attr = filter_attrs(name, attrs) - .map(|attr| attr.parse_args()) - .try_fold(Self::default(), |prev, curr| prev.try_merge(curr?))?; - - if attr.description.is_none() { - attr.description = get_doc_comment(attrs); - } - - Ok(attr) - } -} - -/// Definition of [GraphQL interface][1] for code generation. -/// -/// [1]: https://spec.graphql.org/June2018/#sec-Interfaces -pub(crate) struct Definition { - /// [`syn::Generics`] of the trait describing the [GraphQL interface][1]. - /// - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - pub(crate) trait_generics: syn::Generics, - - /// [`syn::Visibility`] of the trait describing the [GraphQL interface][1]. - /// - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - pub(crate) vis: syn::Visibility, - - /// Name of the generic enum describing all [`implementers`]. It's generic - /// to derive [`Clone`], [`Copy`] and [`Debug`] on it. - /// - /// [`implementers`]: Self::implementers - /// [`Debug`]: std::fmt::Debug - pub(crate) enum_ident: syn::Ident, - - /// Name of the type alias for [`enum_ident`] with [`implementers`]. - /// - /// [`enum_ident`]: Self::enum_ident - /// [`implementers`]: Self::implementers - pub(crate) enum_alias_ident: syn::Ident, - - /// Name of this [GraphQL interface][1] in GraphQL schema. - /// - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - pub(crate) name: String, - - /// Description of this [GraphQL interface][1] to put into GraphQL schema. - /// - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - pub(crate) description: Option, - - /// Rust type of [`Context`] to generate [`GraphQLType`] implementation with - /// for this [GraphQL interface][1]. - /// - /// [`GraphQLType`]: juniper::GraphQLType - /// [`Context`]: juniper::Context - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - pub(crate) context: syn::Type, - - /// [`ScalarValue`] parametrization to generate [`GraphQLType`] - /// implementation with for this [GraphQL interface][1]. - /// - /// [`GraphQLType`]: juniper::GraphQLType - /// [`ScalarValue`]: juniper::ScalarValue - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - pub(crate) scalar: scalar::Type, - - /// Defined [GraphQL fields][2] of this [GraphQL interface][1]. - /// - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - /// [2]: https://spec.graphql.org/June2018/#sec-Language.Fields - pub(crate) fields: Vec, - - /// Defined [`Implementer`]s of this [GraphQL interface][1]. - /// - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - pub(crate) implementers: Vec, -} - -impl ToTokens for Definition { - fn to_tokens(&self, into: &mut TokenStream) { - self.generate_enum_tokens().to_tokens(into); - self.impl_graphql_interface_tokens().to_tokens(into); - self.impl_output_type_tokens().to_tokens(into); - self.impl_graphql_type_tokens().to_tokens(into); - self.impl_graphql_value_tokens().to_tokens(into); - self.impl_graphql_value_async_tokens().to_tokens(into); - self.impl_traits_for_reflection_tokens().to_tokens(into); - self.impl_field_meta_tokens().to_tokens(into); - self.impl_field_tokens().to_tokens(into); - self.impl_async_field_tokens().to_tokens(into); - } -} - -impl Definition { - /// Generates enum describing all [`implementers`]. - /// - /// [`implementers`]: Self::implementers - #[must_use] - fn generate_enum_tokens(&self) -> TokenStream { - let vis = &self.vis; - let enum_ident = &self.enum_ident; - let alias_ident = &self.enum_alias_ident; - - let variant_gens_pars = self - .implementers - .iter() - .enumerate() - .map::(|(id, _)| { - let par = format_ident!("__I{}", id); - parse_quote!( #par ) - }); - - let variants_idents = self - .implementers - .iter() - .filter_map(|ty| ty.path.segments.last().map(|seg| &seg.ident)); - - let trait_gens = &self.trait_generics; - let (trait_impl_gens, trait_ty_gens, trait_where_clause) = - self.trait_generics.split_for_impl(); - - let (trait_gens_lifetimes, trait_gens_tys) = trait_gens - .params - .clone() - .into_iter() - .partition::, _>(|par| { - matches!(par, syn::GenericParam::Lifetime(_)) - }); - - let enum_gens = { - let mut enum_gens = trait_gens.clone(); - enum_gens.params = trait_gens_lifetimes.clone(); - enum_gens.params.extend(variant_gens_pars.clone()); - enum_gens.params.extend(trait_gens_tys.clone()); - enum_gens - }; - - let enum_alias_gens = { - let mut enum_alias_gens = trait_gens.clone(); - enum_alias_gens.move_bounds_to_where_clause(); - enum_alias_gens - }; - - let enum_to_alias_gens = { - trait_gens_lifetimes - .into_iter() - .map(|par| match par { - syn::GenericParam::Lifetime(def) => { - let lifetime = &def.lifetime; - quote! { #lifetime } - } - rest => quote! { #rest }, - }) - .chain(self.implementers.iter().map(ToTokens::to_token_stream)) - .chain(trait_gens_tys.into_iter().map(|par| match par { - syn::GenericParam::Type(ty) => { - let par_ident = &ty.ident; - quote! { #par_ident } - } - rest => quote! { #rest }, - })) - }; - - let phantom_variant = self.has_phantom_variant().then(|| { - let phantom_params = trait_gens.params.iter().filter_map(|p| { - let ty = match p { - syn::GenericParam::Type(ty) => { - let ident = &ty.ident; - quote! { #ident } - } - syn::GenericParam::Lifetime(lt) => { - let lifetime = <.lifetime; - quote! { &#lifetime () } - } - syn::GenericParam::Const(_) => return None, - }; - Some(quote! { - ::std::marker::PhantomData<::std::sync::atomic::AtomicPtr>> - }) - }); - quote! { __Phantom(#(#phantom_params),*) } - }); - - let from_impls = self.implementers.iter().zip(variants_idents.clone()).map( - |(ty, ident)| { - quote! { - impl#trait_impl_gens ::std::convert::From<#ty> for #alias_ident#trait_ty_gens - #trait_where_clause - { - fn from(v: #ty) -> Self { - Self::#ident(v) - } - } - } - }, - ); - - quote! { - #[derive(Clone, Copy, Debug)] - #vis enum #enum_ident#enum_gens { - #(#variants_idents(#variant_gens_pars),)* - #phantom_variant - } - - #vis type #alias_ident#enum_alias_gens = - #enum_ident<#(#enum_to_alias_gens),*>; - - #(#from_impls)* - } - } - - /// Returns generated code implementing [`GraphQLInterface`] trait for this - /// [GraphQL interface][1]. - /// - /// [`GraphQLInterface`]: juniper::GraphQLInterface - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - #[must_use] - fn impl_graphql_interface_tokens(&self) -> TokenStream { - let ty = &self.enum_alias_ident; - let scalar = &self.scalar; - - let gens = self.impl_generics(false); - let (impl_generics, _, where_clause) = gens.split_for_impl(); - let (_, ty_generics, _) = self.trait_generics.split_for_impl(); - - let impler_tys = &self.implementers; - let all_implers_unique = (impler_tys.len() > 1).then(|| { - quote! { ::juniper::sa::assert_type_ne_all!(#( #impler_tys ),*); } - }); - - quote! { - #[automatically_derived] - impl#impl_generics ::juniper::marker::GraphQLInterface<#scalar> for - #ty#ty_generics - #where_clause - { - fn mark() { - #all_implers_unique - #( <#impler_tys as ::juniper::marker::GraphQLObject<#scalar>>::mark(); )* - } - } - } - } - - /// Returns generated code implementing [`marker::IsOutputType`] trait for - /// this [GraphQL interface][1]. - /// - /// [`marker::IsOutputType`]: juniper::marker::IsOutputType - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - #[must_use] - fn impl_output_type_tokens(&self) -> TokenStream { - let ty = &self.enum_alias_ident; - let scalar = &self.scalar; - - let generics = self.impl_generics(false); - let (impl_generics, _, where_clause) = generics.split_for_impl(); - let (_, ty_generics, _) = self.trait_generics.split_for_impl(); - - let fields_marks = self - .fields - .iter() - .map(|f| f.method_mark_tokens(false, scalar)); - - let impler_tys = self.implementers.iter(); - - quote! { - #[automatically_derived] - impl#impl_generics ::juniper::marker::IsOutputType<#scalar> for - #ty#ty_generics - #where_clause - { - fn mark() { - #( #fields_marks )* - #( <#impler_tys as ::juniper::marker::IsOutputType<#scalar>>::mark(); )* - } - } - } - } - - /// Returns generated code implementing [`GraphQLType`] trait for this - /// [GraphQL interface][1]. - /// - /// [`GraphQLType`]: juniper::GraphQLType - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - #[must_use] - fn impl_graphql_type_tokens(&self) -> TokenStream { - let ty = &self.enum_alias_ident; - let scalar = &self.scalar; - - let generics = self.impl_generics(false); - let (impl_generics, _, where_clause) = generics.split_for_impl(); - let (_, ty_generics, _) = self.trait_generics.split_for_impl(); - - let name = &self.name; - let description = self - .description - .as_ref() - .map(|desc| quote! { .description(#desc) }); - - // Sorting is required to preserve/guarantee the order of implementers registered in schema. - let mut impler_tys = self.implementers.clone(); - impler_tys.sort_unstable_by(|a, b| { - let (a, b) = (quote!(#a).to_string(), quote!(#b).to_string()); - a.cmp(&b) - }); - - let fields_meta = self.fields.iter().map(|f| f.method_meta_tokens(None)); - - quote! { - #[automatically_derived] - impl#impl_generics ::juniper::GraphQLType<#scalar> - for #ty#ty_generics - #where_clause - { - fn name(_ : &Self::TypeInfo) -> Option<&'static str> { - Some(#name) - } - - fn meta<'r>( - info: &Self::TypeInfo, - registry: &mut ::juniper::Registry<'r, #scalar> - ) -> ::juniper::meta::MetaType<'r, #scalar> - where #scalar: 'r, - { - // Ensure all implementer types are registered. - #( let _ = registry.get_type::<#impler_tys>(info); )* - - let fields = [ - #( #fields_meta, )* - ]; - registry.build_interface_type::<#ty#ty_generics>(info, &fields) - #description - .into_meta() - } - } - } - } - - /// Returns generated code implementing [`GraphQLValue`] trait for this - /// [GraphQL interface][1]. - /// - /// [`GraphQLValue`]: juniper::GraphQLValue - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - #[must_use] - fn impl_graphql_value_tokens(&self) -> TokenStream { - let ty = &self.enum_alias_ident; - let trait_name = &self.name; - let scalar = &self.scalar; - let context = &self.context; - - let generics = self.impl_generics(false); - let (impl_generics, _, where_clause) = generics.split_for_impl(); - let (_, ty_generics, _) = self.trait_generics.split_for_impl(); - - let fields_resolvers = self.fields.iter().filter_map(|f| { - if f.is_async { - return None; - } - - let name = &f.name; - Some(quote! { - #name => { - ::juniper::macros::reflection::Field::< - #scalar, - { ::juniper::macros::reflection::fnv1a128(#name) } - >::call(self, info, args, executor) - } - }) - }); - let async_fields_err = { - let names = self - .fields - .iter() - .filter_map(|f| f.is_async.then(|| f.name.as_str())) - .collect::>(); - (!names.is_empty()).then(|| { - field::Definition::method_resolve_field_err_async_field_tokens( - &names, scalar, trait_name, - ) - }) - }; - let no_field_err = - field::Definition::method_resolve_field_err_no_field_tokens(scalar, trait_name); - - let downcast_check = self.method_concrete_type_name_tokens(); - - let downcast = self.method_resolve_into_type_tokens(); - - quote! { - #[allow(deprecated)] - #[automatically_derived] - impl#impl_generics ::juniper::GraphQLValue<#scalar> for #ty#ty_generics - #where_clause - { - type Context = #context; - type TypeInfo = (); - - fn type_name<'__i>(&self, info: &'__i Self::TypeInfo) -> Option<&'__i str> { - >::name(info) - } - - fn resolve_field( - &self, - info: &Self::TypeInfo, - field: &str, - args: &::juniper::Arguments<#scalar>, - executor: &::juniper::Executor, - ) -> ::juniper::ExecutionResult<#scalar> { - match field { - #( #fields_resolvers )* - #async_fields_err - _ => #no_field_err, - } - } - - fn concrete_type_name( - &self, - context: &Self::Context, - info: &Self::TypeInfo, - ) -> String { - #downcast_check - } - - fn resolve_into_type( - &self, - info: &Self::TypeInfo, - type_name: &str, - _: Option<&[::juniper::Selection<#scalar>]>, - executor: &::juniper::Executor, - ) -> ::juniper::ExecutionResult<#scalar> { - #downcast - } - } - } - } - - /// Returns generated code implementing [`GraphQLValueAsync`] trait for this - /// [GraphQL interface][1]. - /// - /// [`GraphQLValueAsync`]: juniper::GraphQLValueAsync - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - #[must_use] - fn impl_graphql_value_async_tokens(&self) -> TokenStream { - let ty = &self.enum_alias_ident; - let trait_name = &self.name; - let scalar = &self.scalar; - - let generics = self.impl_generics(true); - let (impl_generics, _, where_clause) = generics.split_for_impl(); - let (_, ty_generics, _) = self.trait_generics.split_for_impl(); - - let fields_resolvers = self.fields.iter().map(|f| { - let name = &f.name; - quote! { - #name => { - ::juniper::macros::reflection::AsyncField::< - #scalar, - { ::juniper::macros::reflection::fnv1a128(#name) } - >::call(self, info, args, executor) - } - } - }); - let no_field_err = - field::Definition::method_resolve_field_err_no_field_tokens(scalar, trait_name); - - let downcast = self.method_resolve_into_type_async_tokens(); - - quote! { - #[allow(deprecated, non_snake_case)] - #[automatically_derived] - impl#impl_generics ::juniper::GraphQLValueAsync<#scalar> for #ty#ty_generics - #where_clause - { - fn resolve_field_async<'b>( - &'b self, - info: &'b Self::TypeInfo, - field: &'b str, - args: &'b ::juniper::Arguments<#scalar>, - executor: &'b ::juniper::Executor, - ) -> ::juniper::BoxFuture<'b, ::juniper::ExecutionResult<#scalar>> { - match field { - #( #fields_resolvers )* - _ => Box::pin(async move { #no_field_err }), - } - } - - fn resolve_into_type_async<'b>( - &'b self, - info: &'b Self::TypeInfo, - type_name: &str, - _: Option<&'b [::juniper::Selection<'b, #scalar>]>, - executor: &'b ::juniper::Executor<'b, 'b, Self::Context, #scalar> - ) -> ::juniper::BoxFuture<'b, ::juniper::ExecutionResult<#scalar>> { - #downcast - } - } - } - } - - /// Returns generated code implementing [`BaseType`], [`BaseSubTypes`], - /// [`WrappedType`] and [`Fields`] traits for this [GraphQL interface][1]. - /// - /// [`BaseSubTypes`]: juniper::macros::reflection::BaseSubTypes - /// [`BaseType`]: juniper::macros::reflection::BaseType - /// [`Fields`]: juniper::macros::reflection::Fields - /// [`WrappedType`]: juniper::macros::reflection::WrappedType - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - #[must_use] - pub(crate) fn impl_traits_for_reflection_tokens(&self) -> TokenStream { - let ty = &self.enum_alias_ident; - let implementers = &self.implementers; - let scalar = &self.scalar; - let name = &self.name; - let fields = self.fields.iter().map(|f| &f.name); - - let generics = self.impl_generics(false); - let (impl_generics, _, where_clause) = generics.split_for_impl(); - let (_, ty_generics, _) = self.trait_generics.split_for_impl(); - - quote! { - #[automatically_derived] - impl#impl_generics ::juniper::macros::reflection::BaseType<#scalar> - for #ty#ty_generics - #where_clause - { - const NAME: ::juniper::macros::reflection::Type = #name; - } - - #[automatically_derived] - impl#impl_generics ::juniper::macros::reflection::BaseSubTypes<#scalar> - for #ty#ty_generics - #where_clause - { - const NAMES: ::juniper::macros::reflection::Types = &[ - >::NAME, - #(<#implementers as ::juniper::macros::reflection::BaseType<#scalar>>::NAME),* - ]; - } - - #[automatically_derived] - impl#impl_generics ::juniper::macros::reflection::WrappedType<#scalar> - for #ty#ty_generics - #where_clause - { - const VALUE: ::juniper::macros::reflection::WrappedValue = 1; - } - - #[automatically_derived] - impl#impl_generics ::juniper::macros::reflection::Fields<#scalar> - for #ty#ty_generics - #where_clause - { - const NAMES: ::juniper::macros::reflection::Names = &[#(#fields),*]; - } - } - } - - /// Returns generated code implementing [`FieldMeta`] for each field of this - /// [GraphQL interface][1]. - /// - /// [`FieldMeta`]: juniper::macros::reflection::FieldMeta - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - fn impl_field_meta_tokens(&self) -> TokenStream { - let ty = &self.enum_alias_ident; - let context = &self.context; - let scalar = &self.scalar; - - let generics = self.impl_generics(false); - let (impl_generics, _, where_clause) = generics.split_for_impl(); - let (_, ty_generics, _) = self.trait_generics.split_for_impl(); - - self.fields - .iter() - .map(|field| { - let field_name = &field.name; - let mut return_ty = field.ty.clone(); - generics.replace_type_with_defaults(&mut return_ty); - - let (args_tys, args_names): (Vec<_>, Vec<_>) = field - .arguments - .iter() - .flat_map(|vec| vec.iter()) - .filter_map(|arg| match arg { - field::MethodArgument::Regular(arg) => Some((&arg.ty, &arg.name)), - _ => None, - }) - .unzip(); - - quote! { - #[allow(non_snake_case)] - impl#impl_generics ::juniper::macros::reflection::FieldMeta< - #scalar, - { ::juniper::macros::reflection::fnv1a128(#field_name) } - > for #ty#ty_generics #where_clause { - type Context = #context; - type TypeInfo = (); - const TYPE: ::juniper::macros::reflection::Type = - <#return_ty as ::juniper::macros::reflection::BaseType<#scalar>>::NAME; - const SUB_TYPES: ::juniper::macros::reflection::Types = - <#return_ty as ::juniper::macros::reflection::BaseSubTypes<#scalar>>::NAMES; - const WRAPPED_VALUE: ::juniper::macros::reflection::WrappedValue = - <#return_ty as ::juniper::macros::reflection::WrappedType<#scalar>>::VALUE; - const ARGUMENTS: &'static [( - ::juniper::macros::reflection::Name, - ::juniper::macros::reflection::Type, - ::juniper::macros::reflection::WrappedValue, - )] = &[#(( - #args_names, - <#args_tys as ::juniper::macros::reflection::BaseType<#scalar>>::NAME, - <#args_tys as ::juniper::macros::reflection::WrappedType<#scalar>>::VALUE, - )),*]; - } - } - }) - .collect() - } - - /// Returns generated code implementing [`Field`] trait for each field of - /// this [GraphQL interface][1]. - /// - /// [`Field`]: juniper::macros::reflection::Field - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - fn impl_field_tokens(&self) -> TokenStream { - let ty = &self.enum_alias_ident; - let scalar = &self.scalar; - let const_scalar = self.const_scalar(); - - let impl_tys = self.implementers.iter().collect::>(); - let impl_idents = self - .implementers - .iter() - .filter_map(|ty| ty.path.segments.last().map(|seg| &seg.ident)) - .collect::>(); - - let generics = self.impl_generics(false); - let (impl_generics, _, where_clause) = generics.split_for_impl(); - let (_, ty_generics, _) = self.trait_generics.split_for_impl(); - - self.fields - .iter() - .filter_map(|field| { - if field.is_async { - return None; - } - - let field_name = &field.name; - let mut return_ty = field.ty.clone(); - generics.replace_type_with_defaults(&mut return_ty); - - let const_ty_generics = self.const_trait_generics(); - - let unreachable_arm = (self.implementers.is_empty() - || !self.trait_generics.params.is_empty()) - .then(|| { - quote! { _ => unreachable!() } - }); - - Some(quote! { - #[allow(non_snake_case)] - impl#impl_generics ::juniper::macros::reflection::Field< - #scalar, - { ::juniper::macros::reflection::fnv1a128(#field_name) } - > for #ty#ty_generics #where_clause { - fn call( - &self, - info: &Self::TypeInfo, - args: &::juniper::Arguments<#scalar>, - executor: &::juniper::Executor, - ) -> ::juniper::ExecutionResult<#scalar> { - match self { - #(#ty::#impl_idents(v) => { - ::juniper::assert_field!( - #ty#const_ty_generics, - #impl_tys, - #const_scalar, - #field_name, - ); - - <_ as ::juniper::macros::reflection::Field< - #scalar, - { ::juniper::macros::reflection::fnv1a128(#field_name) }, - >>::call(v, info, args, executor) - })* - #unreachable_arm - } - } - } - }) - }) - .collect() - } - - /// Returns generated code implementing [`AsyncField`] trait for each field - /// of this [GraphQL interface][1]. - /// - /// [`AsyncField`]: juniper::macros::reflection::AsyncField - /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces - fn impl_async_field_tokens(&self) -> TokenStream { - let ty = &self.enum_alias_ident; - let scalar = &self.scalar; - let const_scalar = self.const_scalar(); - - let impl_tys = self.implementers.iter().collect::>(); - let impl_idents = self - .implementers - .iter() - .filter_map(|ty| ty.path.segments.last().map(|seg| &seg.ident)) - .collect::>(); - - let generics = self.impl_generics(true); - let (impl_generics, _, where_clause) = generics.split_for_impl(); - let (_, ty_generics, _) = self.trait_generics.split_for_impl(); - - self.fields - .iter() - .map(|field| { - let field_name = &field.name; - let mut return_ty = field.ty.clone(); - generics.replace_type_with_defaults(&mut return_ty); - - let const_ty_generics = self.const_trait_generics(); - - let unreachable_arm = (self.implementers.is_empty() - || !self.trait_generics.params.is_empty()) - .then(|| { - quote! { _ => unreachable!() } - }); - - quote! { - #[allow(non_snake_case)] - impl#impl_generics ::juniper::macros::reflection::AsyncField< - #scalar, - { ::juniper::macros::reflection::fnv1a128(#field_name) } - > for #ty#ty_generics #where_clause { - fn call<'b>( - &'b self, - info: &'b Self::TypeInfo, - args: &'b ::juniper::Arguments<#scalar>, - executor: &'b ::juniper::Executor, - ) -> ::juniper::BoxFuture<'b, ::juniper::ExecutionResult<#scalar>> { - match self { - #(#ty::#impl_idents(v) => { - ::juniper::assert_field!( - #ty#const_ty_generics, - #impl_tys, - #const_scalar, - #field_name, - ); - - <_ as ::juniper::macros::reflection::AsyncField< - #scalar, - { ::juniper::macros::reflection::fnv1a128(#field_name) }, - >>::call(v, info, args, executor) - })* - #unreachable_arm - } - } - } - } - }) - .collect() - } - - /// Returns generated code for the [`GraphQLValue::concrete_type_name`][0] - /// method, which returns name of the underlying [`Implementer`] GraphQL - /// type contained in this [`EnumType`]. - /// - /// [0]: juniper::GraphQLValue::concrete_type_name - #[must_use] - fn method_concrete_type_name_tokens(&self) -> TokenStream { - let scalar = &self.scalar; - - let match_arms = self - .implementers - .iter() - .filter_map(|ty| ty.path.segments.last().map(|seg| (&seg.ident, ty))) - .map(|(ident, ty)| { - quote! { - Self::#ident(v) => < - #ty as ::juniper::GraphQLValue<#scalar> - >::concrete_type_name(v, context, info), - } - }); - - let non_exhaustive_match_arm = - (!self.trait_generics.params.is_empty() || self.implementers.is_empty()).then(|| { - quote! { _ => unreachable!(), } - }); - - quote! { - match self { - #( #match_arms )* - #non_exhaustive_match_arm - } - } - } - - /// Returns generated code for the - /// [`GraphQLValueAsync::resolve_into_type_async`][0] method, which - /// downcasts this [`EnumType`] into its underlying [`Implementer`] type - /// asynchronously. - /// - /// [0]: juniper::GraphQLValueAsync::resolve_into_type_async - #[must_use] - fn method_resolve_into_type_async_tokens(&self) -> TokenStream { - let resolving_code = gen::async_resolving_code(None); - - let match_arms = self.implementers.iter().filter_map(|ty| { - ty.path.segments.last().map(|ident| { - quote! { - Self::#ident(v) => { - let fut = ::juniper::futures::future::ready(v); - #resolving_code - } - } - }) - }); - let non_exhaustive_match_arm = - (!self.trait_generics.params.is_empty() || self.implementers.is_empty()).then(|| { - quote! { _ => unreachable!(), } - }); - - quote! { - match self { - #( #match_arms )* - #non_exhaustive_match_arm - } - } - } - - /// Returns generated code for the [`GraphQLValue::resolve_into_type`][0] - /// method, which downcasts this [`EnumType`] into its underlying - /// [`Implementer`] type synchronously. - /// - /// [0]: juniper::GraphQLValue::resolve_into_type - #[must_use] - fn method_resolve_into_type_tokens(&self) -> TokenStream { - let resolving_code = gen::sync_resolving_code(); - - let match_arms = self.implementers.iter().filter_map(|ty| { - ty.path.segments.last().map(|ident| { - quote! { - Self::#ident(res) => #resolving_code, - } - }) - }); - - let non_exhaustive_match_arm = - (!self.trait_generics.params.is_empty() || self.implementers.is_empty()).then(|| { - quote! { _ => unreachable!(), } - }); - - quote! { - match self { - #( #match_arms )* - #non_exhaustive_match_arm - } - } - } - - /// Returns trait generics replaced with default values for usage in `const` - /// context. - #[must_use] - fn const_trait_generics(&self) -> syn::PathArguments { - struct GenericsForConst(syn::AngleBracketedGenericArguments); - - impl Visit<'_> for GenericsForConst { - fn visit_generic_param(&mut self, param: &syn::GenericParam) { - let arg = match param { - syn::GenericParam::Lifetime(_) => parse_quote!( 'static ), - syn::GenericParam::Type(ty) => { - if ty.default.is_none() { - parse_quote!(::juniper::DefaultScalarValue) - } else { - return; - } - } - syn::GenericParam::Const(c) => { - if c.default.is_none() { - // This hack works because only `min_const_generics` - // are enabled for now. - // TODO: replace this once full `const_generics` are - // available. - // Maybe with `<_ as Default>::default()`? - parse_quote!({ 0_u8 as _ }) - } else { - return; - } - } - }; - self.0.args.push(arg) - } - } - - let mut visitor = GenericsForConst(parse_quote!( <> )); - visitor.visit_generics(&self.trait_generics); - syn::PathArguments::AngleBracketed(visitor.0) - } - - /// Returns [`scalar`] replaced with [`DefaultScalarValue`] in case it's - /// [`ExplicitGeneric`] or [`ImplicitGeneric`] for using [`scalar`] in - /// `const` context. - /// - /// [`scalar`]: Self::scalar - /// [`DefaultScalarValue`]: juniper::DefaultScalarValue - /// [`ExplicitGeneric`]: scalar::Type::ExplicitGeneric - /// [`ImplicitGeneric`]: scalar::Type::ImplicitGeneric - #[must_use] - fn const_scalar(&self) -> syn::Type { - match &self.scalar { - scalar::Type::Concrete(ty) => ty.clone(), - scalar::Type::ExplicitGeneric(_) | scalar::Type::ImplicitGeneric(_) => { - parse_quote! { ::juniper::DefaultScalarValue } - } - } - } - - /// Returns prepared [`syn::Generics`] for [`GraphQLType`] trait (and - /// similar) implementation of this [`EnumType`]. - /// - /// If `for_async` is `true`, then additional predicates are added to suit - /// the [`GraphQLAsyncValue`] trait (and similar) requirements. - /// - /// [`GraphQLAsyncValue`]: juniper::GraphQLAsyncValue - /// [`GraphQLType`]: juniper::GraphQLType - #[must_use] - fn impl_generics(&self, for_async: bool) -> syn::Generics { - let mut generics = self.trait_generics.clone(); - - let scalar = &self.scalar; - if scalar.is_implicit_generic() { - generics.params.push(parse_quote! { #scalar }); - } - if scalar.is_generic() { - generics - .make_where_clause() - .predicates - .push(parse_quote! { #scalar: ::juniper::ScalarValue }); - } - if let Some(bound) = scalar.bounds() { - generics.make_where_clause().predicates.push(bound); - } - - if for_async { - let self_ty = if self.trait_generics.lifetimes().next().is_some() { - // Modify lifetime names to omit "lifetime name `'a` shadows a - // lifetime name that is already in scope" error. - let mut generics = self.trait_generics.clone(); - for lt in generics.lifetimes_mut() { - let ident = lt.lifetime.ident.unraw(); - lt.lifetime.ident = format_ident!("__fa__{}", ident); - } - - let lifetimes = generics.lifetimes().map(|lt| <.lifetime); - let ty = &self.enum_alias_ident; - let (_, ty_generics, _) = generics.split_for_impl(); - - quote! { for<#( #lifetimes ),*> #ty#ty_generics } - } else { - quote! { Self } - }; - generics - .make_where_clause() - .predicates - .push(parse_quote! { #self_ty: Sync }); - - if scalar.is_generic() { - generics - .make_where_clause() - .predicates - .push(parse_quote! { #scalar: Send + Sync }); - } - } - - generics - } - - /// Indicates whether this [`EnumType`] has non-exhaustive phantom variant - /// to hold type parameters. - #[must_use] - fn has_phantom_variant(&self) -> bool { - !self.trait_generics.params.is_empty() - } -} diff --git a/juniper_codegen/src/lib.rs b/juniper_codegen/src/lib.rs index 0b30d10c0..1e426af3d 100644 --- a/juniper_codegen/src/lib.rs +++ b/juniper_codegen/src/lib.rs @@ -673,14 +673,6 @@ pub fn graphql_interface(attr: TokenStream, body: TokenStream) -> TokenStream { .into() } -#[proc_macro_error] -#[proc_macro_attribute] -pub fn graphql_interface_new(attr: TokenStream, body: TokenStream) -> TokenStream { - self::graphql_interface::attr::expand_new(attr.into(), body.into()) - .unwrap_or_abort() - .into() -} - /// `#[derive(GraphQLObject)]` macro for deriving a [GraphQL object][1] /// implementation for structs. /// From b5f552a7ce68ec62c52b1616c2c2b6e2bd0b6321 Mon Sep 17 00:00:00 2001 From: ilslv Date: Wed, 5 Jan 2022 15:39:42 +0300 Subject: [PATCH 38/53] WIP (More codegen_fail tests) --- .../additional_non_nullable_argument.rs | 19 +++++++++++++++++++ .../additional_non_nullable_argument.stderr | 7 +++++++ .../fail/interface/missing_field.rs | 14 ++++++++++++++ .../fail/interface/missing_field.stderr | 7 +++++++ .../fail/interface/missing_field_argument.rs | 19 +++++++++++++++++++ .../interface/missing_field_argument.stderr | 7 +++++++ .../fail/interface/non_subtype_return.rs | 14 ++++++++++++++ .../fail/interface/non_subtype_return.stderr | 7 +++++++ .../fail/interface/wrong_argument_type.rs | 19 +++++++++++++++++++ .../fail/interface/wrong_argument_type.stderr | 7 +++++++ 10 files changed, 120 insertions(+) create mode 100644 integration_tests/codegen_fail/fail/interface/additional_non_nullable_argument.rs create mode 100644 integration_tests/codegen_fail/fail/interface/additional_non_nullable_argument.stderr create mode 100644 integration_tests/codegen_fail/fail/interface/missing_field.rs create mode 100644 integration_tests/codegen_fail/fail/interface/missing_field.stderr create mode 100644 integration_tests/codegen_fail/fail/interface/missing_field_argument.rs create mode 100644 integration_tests/codegen_fail/fail/interface/missing_field_argument.stderr create mode 100644 integration_tests/codegen_fail/fail/interface/non_subtype_return.rs create mode 100644 integration_tests/codegen_fail/fail/interface/non_subtype_return.stderr create mode 100644 integration_tests/codegen_fail/fail/interface/wrong_argument_type.rs create mode 100644 integration_tests/codegen_fail/fail/interface/wrong_argument_type.stderr diff --git a/integration_tests/codegen_fail/fail/interface/additional_non_nullable_argument.rs b/integration_tests/codegen_fail/fail/interface/additional_non_nullable_argument.rs new file mode 100644 index 000000000..c69f0f664 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/additional_non_nullable_argument.rs @@ -0,0 +1,19 @@ +use juniper::{graphql_interface, graphql_object}; + +pub struct ObjA { + id: String, +} + +#[graphql_object(impl = CharacterValue)] +impl ObjA { + fn id(&self, is_present: bool) -> &str { + is_present.then(|| self.id.as_str()).unwrap_or("missing") + } +} + +#[graphql_interface(for = ObjA)] +trait Character { + fn id(&self) -> &str; +} + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/additional_non_nullable_argument.stderr b/integration_tests/codegen_fail/fail/interface/additional_non_nullable_argument.stderr new file mode 100644 index 000000000..0805ac203 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/additional_non_nullable_argument.stderr @@ -0,0 +1,7 @@ +error[E0080]: evaluation of constant value failed + --> fail/interface/additional_non_nullable_argument.rs:14:1 + | +14 | #[graphql_interface(for = ObjA)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: Field `id`: Argument `isPresent` of type `Boolean!` not present on the interface and so has to be nullable.', $DIR/fail/interface/additional_non_nullable_argument.rs:14:1 + | + = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/integration_tests/codegen_fail/fail/interface/missing_field.rs b/integration_tests/codegen_fail/fail/interface/missing_field.rs new file mode 100644 index 000000000..641556939 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/missing_field.rs @@ -0,0 +1,14 @@ +use juniper::{graphql_interface, GraphQLObject}; + +#[derive(GraphQLObject)] +#[graphql(impl = CharacterValue)] +pub struct ObjA { + test: String, +} + +#[graphql_interface(for = ObjA)] +trait Character { + fn id(&self) -> &str; +} + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/missing_field.stderr b/integration_tests/codegen_fail/fail/interface/missing_field.stderr new file mode 100644 index 000000000..d0efa3518 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/missing_field.stderr @@ -0,0 +1,7 @@ +error[E0080]: evaluation of constant value failed + --> fail/interface/missing_field.rs:9:1 + | +9 | #[graphql_interface(for = ObjA)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: Field `id` isn't implemented on `ObjA`.', $DIR/fail/interface/missing_field.rs:9:1 + | + = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/integration_tests/codegen_fail/fail/interface/missing_field_argument.rs b/integration_tests/codegen_fail/fail/interface/missing_field_argument.rs new file mode 100644 index 000000000..b71a38d6b --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/missing_field_argument.rs @@ -0,0 +1,19 @@ +use juniper::{graphql_interface, graphql_object}; + +pub struct ObjA { + id: String, +} + +#[graphql_object(impl = CharacterValue)] +impl ObjA { + fn id(&self) -> &String { + &self.id + } +} + +#[graphql_interface(for = ObjA)] +trait Character { + fn id(&self, is_present: bool) -> &str; +} + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/missing_field_argument.stderr b/integration_tests/codegen_fail/fail/interface/missing_field_argument.stderr new file mode 100644 index 000000000..719cfd1a8 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/missing_field_argument.stderr @@ -0,0 +1,7 @@ +error[E0080]: evaluation of constant value failed + --> fail/interface/missing_field_argument.rs:14:1 + | +14 | #[graphql_interface(for = ObjA)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: Field `id`: Argument `isPresent` of type `Boolean!` was expected, but not found.', $DIR/fail/interface/missing_field_argument.rs:14:1 + | + = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/integration_tests/codegen_fail/fail/interface/non_subtype_return.rs b/integration_tests/codegen_fail/fail/interface/non_subtype_return.rs new file mode 100644 index 000000000..8eab508d3 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/non_subtype_return.rs @@ -0,0 +1,14 @@ +use juniper::{graphql_interface, GraphQLObject}; + +#[derive(GraphQLObject)] +#[graphql(impl = CharacterValue)] +pub struct ObjA { + id: Vec, +} + +#[graphql_interface(for = ObjA)] +trait Character { + fn id(&self) -> &str; +} + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/non_subtype_return.stderr b/integration_tests/codegen_fail/fail/interface/non_subtype_return.stderr new file mode 100644 index 000000000..ca48f8464 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/non_subtype_return.stderr @@ -0,0 +1,7 @@ +error[E0080]: evaluation of constant value failed + --> fail/interface/non_subtype_return.rs:9:1 + | +9 | #[graphql_interface(for = ObjA)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: Field `id`: implementor is expected to return a subtype of interface's return object: `[String!]!` is not a subtype of `String!`.', $DIR/fail/interface/non_subtype_return.rs:9:1 + | + = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/integration_tests/codegen_fail/fail/interface/wrong_argument_type.rs b/integration_tests/codegen_fail/fail/interface/wrong_argument_type.rs new file mode 100644 index 000000000..388aea1ed --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/wrong_argument_type.rs @@ -0,0 +1,19 @@ +use juniper::{graphql_interface, graphql_object}; + +pub struct ObjA { + id: String, +} + +#[graphql_object(impl = CharacterValue)] +impl ObjA { + fn id(&self, _is_present: i32) -> &str { + &self.id + } +} + +#[graphql_interface(for = ObjA)] +trait Character { + fn id(&self, is_present: bool) -> &str; +} + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/wrong_argument_type.stderr b/integration_tests/codegen_fail/fail/interface/wrong_argument_type.stderr new file mode 100644 index 000000000..204bd1234 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/wrong_argument_type.stderr @@ -0,0 +1,7 @@ +error[E0080]: evaluation of constant value failed + --> fail/interface/wrong_argument_type.rs:14:1 + | +14 | #[graphql_interface(for = ObjA)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: Field `id`: Argument `isPresent`: expected type `Boolean!`, found: `Int!`.', $DIR/fail/interface/wrong_argument_type.rs:14:1 + | + = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) From 66819e932bfacd9881bc136084ae91426c78d27c Mon Sep 17 00:00:00 2001 From: ilslv Date: Wed, 5 Jan 2022 17:05:59 +0300 Subject: [PATCH 39/53] WIP (prettify missing references in `impl` and `for` attributes) --- .../fail/interface/missing_for_attr.rs | 14 +++++ .../fail/interface/missing_for_attr.stderr | 7 +++ .../fail/interface/missing_impl_attr.rs | 13 +++++ .../fail/interface/missing_impl_attr.stderr | 7 +++ juniper/src/macros/reflection.rs | 52 +++++++++++++++++++ juniper_codegen/src/graphql_interface/mod.rs | 6 ++- juniper_codegen/src/graphql_object/mod.rs | 16 +++++- 7 files changed, 113 insertions(+), 2 deletions(-) create mode 100644 integration_tests/codegen_fail/fail/interface/missing_for_attr.rs create mode 100644 integration_tests/codegen_fail/fail/interface/missing_for_attr.stderr create mode 100644 integration_tests/codegen_fail/fail/interface/missing_impl_attr.rs create mode 100644 integration_tests/codegen_fail/fail/interface/missing_impl_attr.stderr diff --git a/integration_tests/codegen_fail/fail/interface/missing_for_attr.rs b/integration_tests/codegen_fail/fail/interface/missing_for_attr.rs new file mode 100644 index 000000000..77a3043af --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/missing_for_attr.rs @@ -0,0 +1,14 @@ +use juniper::{graphql_interface, GraphQLObject}; + +#[derive(GraphQLObject)] +#[graphql(impl = CharacterValue)] +pub struct ObjA { + id: String, +} + +#[graphql_interface] +trait Character { + fn id(&self) -> &str; +} + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/missing_for_attr.stderr b/integration_tests/codegen_fail/fail/interface/missing_for_attr.stderr new file mode 100644 index 000000000..84072d87d --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/missing_for_attr.stderr @@ -0,0 +1,7 @@ +error[E0080]: evaluation of constant value failed + --> fail/interface/missing_for_attr.rs:3:10 + | +3 | #[derive(GraphQLObject)] + | ^^^^^^^^^^^^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: missing implementer reference in interface's `for` attribute.', $DIR/fail/interface/missing_for_attr.rs:3:10 + | + = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/integration_tests/codegen_fail/fail/interface/missing_impl_attr.rs b/integration_tests/codegen_fail/fail/interface/missing_impl_attr.rs new file mode 100644 index 000000000..c35b23b33 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/missing_impl_attr.rs @@ -0,0 +1,13 @@ +use juniper::{graphql_interface, GraphQLObject}; + +#[derive(GraphQLObject)] +pub struct ObjA { + id: String, +} + +#[graphql_interface(for = ObjA)] +trait Character { + fn id(&self) -> &str; +} + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/missing_impl_attr.stderr b/integration_tests/codegen_fail/fail/interface/missing_impl_attr.stderr new file mode 100644 index 000000000..767a53203 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/missing_impl_attr.stderr @@ -0,0 +1,7 @@ +error[E0080]: evaluation of constant value failed + --> fail/interface/missing_impl_attr.rs:8:1 + | +8 | #[graphql_interface(for = ObjA)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: missing interface reference in implementer's `impl` attribute.', $DIR/fail/interface/missing_impl_attr.rs:8:1 + | + = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/juniper/src/macros/reflection.rs b/juniper/src/macros/reflection.rs index d0b304d8d..bd14b8586 100644 --- a/juniper/src/macros/reflection.rs +++ b/juniper/src/macros/reflection.rs @@ -281,6 +281,10 @@ pub trait Fields { const NAMES: Names; } +pub trait Implements { + const NAMES: Types; +} + /// Stores meta information of [GraphQL fields][1]: /// - [`Context`] and [`TypeInfo`]. /// - Return type's [`TYPE`], [`SUB_TYPES`] and [`WRAPPED_VALUE`]. @@ -467,6 +471,54 @@ pub const fn str_eq(l: &str, r: &str) -> bool { true } +#[macro_export] +macro_rules! assert_implemented_for { + ($scalar: ty, $implementor: ty $(, $interfaces: ty)* $(,)?) => { + const _: () = { + $({ + let is_present = $crate::macros::reflection::str_exists_in_arr( + <$implementor as ::juniper::macros::reflection::BaseType<$scalar>>::NAME, + <$interfaces as ::juniper::macros::reflection::BaseSubTypes<$scalar>>::NAMES, + ); + if !is_present { + const MSG: &str = $crate::const_concat!( + "Failed to implement interface `", + <$interfaces as $crate::macros::reflection::BaseType<$scalar>>::NAME, + "` on `", + <$implementor as $crate::macros::reflection::BaseType<$scalar>>::NAME, + "`: missing implementer reference in interface's `for` attribute.", + ); + ::std::panic!("{}", MSG); + } + })* + }; + }; +} + +#[macro_export] +macro_rules! assert_interfaces_impls { + ($scalar: ty, $interface: ty $(, $implementers: ty)* $(,)?) => { + const _: () = { + $({ + let is_present = $crate::macros::reflection::str_exists_in_arr( + <$interface as ::juniper::macros::reflection::BaseType<$scalar>>::NAME, + <$implementers as ::juniper::macros::reflection::Implements<$scalar>>::NAMES, + ); + if !is_present { + const MSG: &str = $crate::const_concat!( + "Failed to implement interface `", + <$interface as $crate::macros::reflection::BaseType<$scalar>>::NAME, + "` on `", + <$implementers as $crate::macros::reflection::BaseType<$scalar>>::NAME, + "`: missing interface reference in implementer's `impl` attribute.", + ); + ::std::panic!("{}", MSG); + } + })* + }; + }; +} + /// Asserts validness of the [`Field`] [`Arguments`] and return [`Type`]. This /// assertion is a combination of [`assert_subtype`] and [`assert_field_args`]. /// See [spec][1] for more info. diff --git a/juniper_codegen/src/graphql_interface/mod.rs b/juniper_codegen/src/graphql_interface/mod.rs index 6c67f465d..499e90fcc 100644 --- a/juniper_codegen/src/graphql_interface/mod.rs +++ b/juniper_codegen/src/graphql_interface/mod.rs @@ -473,6 +473,7 @@ impl Definition { fn impl_output_type_tokens(&self) -> TokenStream { let ty = &self.enum_alias_ident; let scalar = &self.scalar; + let const_scalar = &self.scalar.default_ty(); let generics = self.impl_generics(false); let (impl_generics, _, where_clause) = generics.split_for_impl(); @@ -483,7 +484,7 @@ impl Definition { .iter() .map(|f| f.method_mark_tokens(false, scalar)); - let impler_tys = self.implementers.iter(); + let impler_tys = self.implementers.iter().collect::>(); quote! { #[automatically_derived] @@ -494,6 +495,9 @@ impl Definition { fn mark() { #( #fields_marks )* #( <#impler_tys as ::juniper::marker::IsOutputType<#scalar>>::mark(); )* + ::juniper::assert_interfaces_impls!( + #const_scalar, #ty, #(#impler_tys),* + ); } } } diff --git a/juniper_codegen/src/graphql_object/mod.rs b/juniper_codegen/src/graphql_object/mod.rs index 507e8a363..901e81d12 100644 --- a/juniper_codegen/src/graphql_object/mod.rs +++ b/juniper_codegen/src/graphql_object/mod.rs @@ -376,6 +376,7 @@ impl Definition { let (impl_generics, where_clause) = self.impl_generics(false); let ty = &self.ty; let fields = self.fields.iter().map(|f| &f.name); + let interfaces = self.interfaces.iter(); quote! { #[automatically_derived] @@ -395,6 +396,15 @@ impl Definition { &[>::NAME]; } + #[automatically_derived] + impl#impl_generics ::juniper::macros::reflection::Implements<#scalar> + for #ty + #where_clause + { + const NAMES: ::juniper::macros::reflection::Types = + &[#(<#interfaces as ::juniper::macros::reflection::BaseType<#scalar>>::NAME),*]; + } + #[automatically_derived] impl#impl_generics ::juniper::macros::reflection::WrappedType<#scalar> for #ty @@ -507,11 +517,12 @@ impl Definition { #[must_use] fn impl_graphql_object_tokens(&self) -> TokenStream { let scalar = &self.scalar; + let const_scalar = self.scalar.default_ty(); let (impl_generics, where_clause) = self.impl_generics(false); let ty = &self.ty; - let interface_tys = self.interfaces.iter(); + let interface_tys = self.interfaces.iter().collect::>(); // TODO: Make it work by repeating `sa::assert_type_ne_all!` expansion, // but considering generics. //let interface_tys: Vec<_> = self.interfaces.iter().collect(); @@ -525,6 +536,9 @@ impl Definition { { fn mark() { #( <#interface_tys as ::juniper::marker::GraphQLInterface<#scalar>>::mark(); )* + ::juniper::assert_implemented_for!( + #const_scalar, #ty, #(#interface_tys),* + ); } } } From 3d53dc10d56e69a622e8bdd4ee9f52d88a477401 Mon Sep 17 00:00:00 2001 From: ilslv Date: Wed, 5 Jan 2022 17:33:28 +0300 Subject: [PATCH 40/53] WIP (more tests) --- .../src/codegen/interface_attr.rs | 125 ++++++++++++++++++ juniper_codegen/src/common/parse/mod.rs | 2 +- juniper_codegen/src/graphql_interface/mod.rs | 3 +- juniper_codegen/src/graphql_object/mod.rs | 19 ++- 4 files changed, 144 insertions(+), 5 deletions(-) diff --git a/integration_tests/juniper_tests/src/codegen/interface_attr.rs b/integration_tests/juniper_tests/src/codegen/interface_attr.rs index c3b453804..5646a5d9b 100644 --- a/integration_tests/juniper_tests/src/codegen/interface_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/interface_attr.rs @@ -3598,3 +3598,128 @@ mod field_return_union_subtyping { } } } + +mod additional_nullable_argument { + use super::*; + + #[graphql_interface(for = [Human, Droid])] + trait Character { + fn id(&self) -> Option; + } + + #[derive(GraphQLObject)] + #[graphql(impl = CharacterValue)] + struct Human { + id: String, + home_planet: String, + } + + struct Droid { + id: String, + primary_function: String, + } + + #[graphql_object(impl = CharacterValue)] + impl Droid { + fn id(&self, is_present: Option) -> &str { + is_present + .unwrap_or_default() + .then(|| self.id.as_str()) + .unwrap_or("missing") + } + + fn primary_function(&self) -> &str { + &self.primary_function + } + } + + #[derive(Clone, Copy)] + enum QueryRoot { + Human, + Droid, + } + + #[graphql_object(scalar = S: ScalarValue + Send + Sync)] + impl QueryRoot { + fn character(&self) -> CharacterValue { + match self { + Self::Human => Human { + id: "human-32".to_string(), + home_planet: "earth".to_string(), + } + .into(), + Self::Droid => Droid { + id: "droid-99".to_string(), + primary_function: "run".to_string(), + } + .into(), + } + } + } + + #[tokio::test] + async fn enum_resolves_human() { + const DOC: &str = r#"{ + character { + ... on Human { + humanId: id + homePlanet + } + } + }"#; + + let schema = schema(QueryRoot::Human); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"character": {"humanId": "human-32", "homePlanet": "earth"}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn enum_resolves_droid() { + const DOC: &str = r#"{ + character { + ... on Droid { + droidId: id(isPresent: true) + primaryFunction + } + } + }"#; + + let schema = schema(QueryRoot::Droid); + + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok(( + graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}), + vec![], + )), + ); + } + + #[tokio::test] + async fn enum_resolves_id_field() { + const DOC: &str = r#"{ + character { + id + } + }"#; + + for (root, expected_id) in &[ + (QueryRoot::Human, "human-32"), + (QueryRoot::Droid, "missing"), + ] { + let schema = schema(*root); + + let expected_id: &str = *expected_id; + assert_eq!( + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, + Ok((graphql_value!({"character": {"id": expected_id}}), vec![])), + ); + } + } +} diff --git a/juniper_codegen/src/common/parse/mod.rs b/juniper_codegen/src/common/parse/mod.rs index f74e75b12..7f5cda6d5 100644 --- a/juniper_codegen/src/common/parse/mod.rs +++ b/juniper_codegen/src/common/parse/mod.rs @@ -330,7 +330,7 @@ impl GenericsExt for syn::Generics { }); if is_generic { - *ty = parse_quote!(()); + *ty = parse_quote!(::juniper::DefaultScalarValue); } } _ => {} diff --git a/juniper_codegen/src/graphql_interface/mod.rs b/juniper_codegen/src/graphql_interface/mod.rs index 499e90fcc..a26645d54 100644 --- a/juniper_codegen/src/graphql_interface/mod.rs +++ b/juniper_codegen/src/graphql_interface/mod.rs @@ -478,6 +478,7 @@ impl Definition { let generics = self.impl_generics(false); let (impl_generics, _, where_clause) = generics.split_for_impl(); let (_, ty_generics, _) = self.trait_generics.split_for_impl(); + let ty_const_generics = self.const_trait_generics(); let fields_marks = self .fields @@ -496,7 +497,7 @@ impl Definition { #( #fields_marks )* #( <#impler_tys as ::juniper::marker::IsOutputType<#scalar>>::mark(); )* ::juniper::assert_interfaces_impls!( - #const_scalar, #ty, #(#impler_tys),* + #const_scalar, #ty#ty_const_generics, #(#impler_tys),* ); } } diff --git a/juniper_codegen/src/graphql_object/mod.rs b/juniper_codegen/src/graphql_object/mod.rs index 901e81d12..d306d37a4 100644 --- a/juniper_codegen/src/graphql_object/mod.rs +++ b/juniper_codegen/src/graphql_object/mod.rs @@ -21,7 +21,7 @@ use crate::{ field, gen, parse::{ attr::{err, OptionExt as _}, - ParseBufferExt as _, TypeExt, + GenericsExt as _, ParseBufferExt as _, TypeExt, }, scalar, }, @@ -522,7 +522,20 @@ impl Definition { let (impl_generics, where_clause) = self.impl_generics(false); let ty = &self.ty; - let interface_tys = self.interfaces.iter().collect::>(); + let interface_tys = self.interfaces.iter(); + + let generics = { + let mut generics = self.generics.clone(); + if scalar.is_implicit_generic() { + generics.params.push(parse_quote!(#scalar)) + } + generics + }; + let const_interface_tys = interface_tys.clone().cloned().map(|mut ty| { + generics.replace_type_with_defaults(&mut ty); + ty + }); + // TODO: Make it work by repeating `sa::assert_type_ne_all!` expansion, // but considering generics. //let interface_tys: Vec<_> = self.interfaces.iter().collect(); @@ -537,7 +550,7 @@ impl Definition { fn mark() { #( <#interface_tys as ::juniper::marker::GraphQLInterface<#scalar>>::mark(); )* ::juniper::assert_implemented_for!( - #const_scalar, #ty, #(#interface_tys),* + #const_scalar, #ty, #(#const_interface_tys),* ); } } From 732bc131ea1b5550b93338caa0f9a6b75a7698c8 Mon Sep 17 00:00:00 2001 From: ilslv Date: Thu, 6 Jan 2022 08:33:17 +0300 Subject: [PATCH 41/53] WIP (Correct docs) --- juniper_codegen/src/lib.rs | 211 +++++++++---------------------------- 1 file changed, 47 insertions(+), 164 deletions(-) diff --git a/juniper_codegen/src/lib.rs b/juniper_codegen/src/lib.rs index 1e426af3d..409d29757 100644 --- a/juniper_codegen/src/lib.rs +++ b/juniper_codegen/src/lib.rs @@ -271,70 +271,33 @@ pub fn graphql_scalar(args: TokenStream, input: TokenStream) -> TokenStream { /// Specifying multiple `#[graphql_interface]` attributes on the same definition /// is totally okay. They all will be treated as a single attribute. /// -/// The main difference between [GraphQL interface][1] type and Rust trait is +/// [GraphQL interfaces][1] are more like Go's structural interfaces, while +/// Rust's traits are more like typeclasses. So using `impl Trait` isn't an +/// option. +/// +/// Another difference between [GraphQL interface][1] type and Rust trait is /// that the former serves both as an _abstraction_ and a _value downcastable to /// concrete implementers_, while in Rust, a trait is an _abstraction only_ and /// you need a separate type to downcast into a concrete implementer, like enum /// or [trait object][3], because trait doesn't represent a type itself. -/// Macro uses Rust enum to represent a value type of [GraphQL interface][1] by -/// default, however [trait object][3] may be used too (use `dyn` attribute -/// argument for that). -/// -/// A __trait has to be [object safe][2]__ if its values are represented by -/// [trait object][3], because schema resolvers will need to return that -/// [trait object][3]. The [trait object][3] has to be [`Send`] and [`Sync`], -/// and the macro automatically generate a convenien type alias for such -/// [trait object][3]. +/// Macro uses Rust enum to represent a value type of [GraphQL interface][1]. /// /// ``` /// use juniper::{graphql_interface, GraphQLObject}; /// /// // NOTICE: By default a `CharacterValue` enum is generated by macro to represent values of this /// // GraphQL interface. -/// #[graphql_interface(for = [Human, Droid])] // enumerating all implementers is mandatory +/// #[graphql_interface(for = Human)] // enumerating all implementers is mandatory /// trait Character { /// fn id(&self) -> &str; /// } /// -/// // NOTICE: `dyn` attribute argument enables trait object usage to represent values of this -/// // GraphQL interface. Also, for trait objects a trait is slightly modified -/// // under-the-hood by adding a `ScalarValue` type parameter. -/// #[graphql_interface(dyn = DynSerial, for = Droid)] -/// trait Serial { -/// fn number(&self) -> i32; -/// } -/// /// #[derive(GraphQLObject)] /// #[graphql(impl = CharacterValue)] // notice the enum type name, not trait name /// struct Human { -/// id: String, +/// id: String, // this field is used to resolve Character::id /// home_planet: String, /// } -/// #[graphql_interface] -/// impl Character for Human { -/// fn id(&self) -> &str { -/// &self.id -/// } -/// } -/// -/// #[derive(GraphQLObject)] -/// #[graphql(impl = [CharacterValue, DynSerial<__S>])] // notice the trait object referred by alias -/// struct Droid { // and its parametrization by generic -/// id: String, // `ScalarValue` -/// primary_function: String, -/// } -/// #[graphql_interface] -/// impl Character for Droid { -/// fn id(&self) -> &str { -/// &self.id -/// } -/// } -/// #[graphql_interface(dyn)] // implementing requires to know about dynamic dispatch too -/// impl Serial for Droid { -/// fn number(&self) -> i32 { -/// 78953 -/// } -/// } /// ``` /// /// # Custom name, description, deprecation and argument defaults @@ -390,7 +353,7 @@ pub fn graphql_scalar(args: TokenStream, input: TokenStream) -> TokenStream { /// `none` (disables any renaming). /// /// ``` -/// # use juniper::{graphql_interface, GraphQLObject}; +/// # use juniper::{graphql_interface, graphql_object}; /// # /// #[graphql_interface(for = Human, rename_all = "none")] // disables renaming /// trait Character { @@ -399,19 +362,26 @@ pub fn graphql_scalar(args: TokenStream, input: TokenStream) -> TokenStream { /// fn detailed_info(&self, info_kind: String) -> String; /// } /// -/// #[derive(GraphQLObject)] -/// #[graphql(impl = CharacterValue)] /// struct Human { /// id: String, /// home_planet: String, /// } -/// #[graphql_interface] -/// impl Character for Human { -/// fn detailed_info(&self, info_kind: String) -> String { +/// +/// #[graphql_object(impl = CharacterValue, rename_all = "none")] +/// impl Human { +/// fn id(&self) -> &str { +/// &self.id +/// } +/// +/// fn home_planet(&self) -> &str { +/// &self.home_planet +/// } +/// +/// // You can return `&str` even if trait definition returns `String`. +/// fn detailed_info(&self, info_kind: String) -> &str { /// (info_kind == "planet") /// .then(|| &self.home_planet) /// .unwrap_or(&self.id) -/// .clone() /// } /// } /// ``` @@ -447,7 +417,7 @@ pub fn graphql_scalar(args: TokenStream, input: TokenStream) -> TokenStream { /// /// ``` /// # use std::collections::HashMap; -/// # use juniper::{graphql_interface, GraphQLObject}; +/// # use juniper::{graphql_interface, graphql_object}; /// # /// struct Database { /// humans: HashMap, @@ -461,36 +431,38 @@ pub fn graphql_scalar(args: TokenStream, input: TokenStream) -> TokenStream { /// fn info<'db>(&self, #[graphql(context)] db: &'db Database) -> Option<&'db str>; /// } /// -/// #[derive(GraphQLObject)] -/// #[graphql(impl = CharacterValue, Context = Database)] /// struct Human { /// id: String, /// home_planet: String, /// } -/// #[graphql_interface] -/// impl Character for Human { -/// fn id<'db>(&self, db: &'db Database) -> Option<&'db str> { -/// db.humans.get(&self.id).map(|h| h.id.as_str()) +/// #[graphql_object(impl = CharacterValue, Context = Database)] +/// impl Human { +/// fn id<'db>(&self, context: &'db Database) -> Option<&'db str> { +/// context.humans.get(&self.id).map(|h| h.id.as_str()) /// } -/// fn info<'db>(&self, db: &'db Database) -> Option<&'db str> { +/// fn info<'db>(&self, #[graphql(context)] db: &'db Database) -> Option<&'db str> { /// db.humans.get(&self.id).map(|h| h.home_planet.as_str()) /// } +/// fn home_planet(&self) -> &str { +/// &self.home_planet +/// } /// } /// -/// #[derive(GraphQLObject)] -/// #[graphql(impl = CharacterValue, Context = Database)] /// struct Droid { /// id: String, /// primary_function: String, /// } -/// #[graphql_interface] -/// impl Character for Droid { -/// fn id<'db>(&self, db: &'db Database) -> Option<&'db str> { -/// db.droids.get(&self.id).map(|h| h.id.as_str()) +/// #[graphql_object(impl = CharacterValue, Context = Database)] +/// impl Droid { +/// fn id<'db>(&self, ctx: &'db Database) -> Option<&'db str> { +/// ctx.droids.get(&self.id).map(|h| h.id.as_str()) /// } -/// fn info<'db>(&self, db: &'db Database) -> Option<&'db str> { +/// fn info<'db>(&self, #[graphql(context)] db: &'db Database) -> Option<&'db str> { /// db.droids.get(&self.id).map(|h| h.primary_function.as_str()) /// } +/// fn primary_function(&self) -> &str { +/// &self.primary_function +/// } /// } /// ``` /// @@ -503,7 +475,7 @@ pub fn graphql_scalar(args: TokenStream, input: TokenStream) -> TokenStream { /// However, this requires to explicitly parametrize over [`ScalarValue`], as [`Executor`] does so. /// /// ``` -/// # use juniper::{graphql_interface, Executor, GraphQLObject, LookAheadMethods as _, ScalarValue}; +/// # use juniper::{graphql_interface, graphql_object, Executor, LookAheadMethods as _, ScalarValue}; /// # /// // NOTICE: Specifying `ScalarValue` as existing type parameter. /// #[graphql_interface(for = Human, scalar = S)] @@ -520,24 +492,22 @@ pub fn graphql_scalar(args: TokenStream, input: TokenStream) -> TokenStream { /// S: Send + Sync; /// } /// -/// #[derive(GraphQLObject)] -/// #[graphql(impl = CharacterValue<__S>)] /// struct Human { /// id: String, /// name: String, /// } -/// #[graphql_interface(scalar = S)] -/// impl Character for Human { -/// async fn id<'a>(&self, executor: &'a Executor<'_, '_, (), S>) -> &'a str +/// #[graphql_object(impl = CharacterValue<__S>)] +/// impl Human { +/// async fn id<'a, S>(&self, executor: &'a Executor<'_, '_, (), S>) -> &'a str /// where -/// S: Send + Sync, +/// S: ScalarValue + Send + Sync, /// { /// executor.look_ahead().field_name() /// } /// -/// async fn name<'b>(&'b self, _: &Executor<'_, '_, (), S>) -> &'b str +/// async fn name<'b, S>(&'b self, _executor: &Executor<'_, '_, (), S>) -> &'b str /// where -/// S: Send + Sync, +/// S: ScalarValue + Send + Sync, /// { /// &self.name /// } @@ -557,7 +527,7 @@ pub fn graphql_scalar(args: TokenStream, input: TokenStream) -> TokenStream { /// # use juniper::{graphql_interface, DefaultScalarValue, GraphQLObject}; /// # /// // NOTICE: Removing `Scalar` argument will fail compilation. -/// #[graphql_interface(for = [Human, Droid], scalar = DefaultScalarValue)] +/// #[graphql_interface(for = Human, scalar = DefaultScalarValue)] /// trait Character { /// fn id(&self) -> &str; /// } @@ -568,93 +538,6 @@ pub fn graphql_scalar(args: TokenStream, input: TokenStream) -> TokenStream { /// id: String, /// home_planet: String, /// } -/// #[graphql_interface(scalar = DefaultScalarValue)] -/// impl Character for Human { -/// fn id(&self) -> &str{ -/// &self.id -/// } -/// } -/// -/// #[derive(GraphQLObject)] -/// #[graphql(impl = CharacterValue, scalar = DefaultScalarValue)] -/// struct Droid { -/// id: String, -/// primary_function: String, -/// } -/// #[graphql_interface(scalar = DefaultScalarValue)] -/// impl Character for Droid { -/// fn id(&self) -> &str { -/// &self.id -/// } -/// } -/// ``` -/// -/// # Downcasting -/// -/// By default, the [GraphQL interface][1] value is downcast to one of its implementer types via -/// matching the enum variant or downcasting the trait object (if `dyn` attribute's argument is -/// used). -/// -/// To use a custom logic for downcasting a [GraphQL interface][1] into its implementer, there may -/// be specified: -/// - either a `downcast` attribute's argument directly on a trait method; -/// - or an `on` attribute's argument on aa trait definition referring an exteranl function. -/// -/// ``` -/// # use std::collections::HashMap; -/// # use juniper::{graphql_interface, GraphQLObject}; -/// # -/// struct Database { -/// humans: HashMap, -/// droids: HashMap, -/// } -/// impl juniper::Context for Database {} -/// -/// #[graphql_interface(for = [Human, Droid], Context = Database)] -/// #[graphql_interface(on Droid = get_droid)] // enables downcasting `Droid` via `get_droid()` -/// trait Character { -/// fn id(&self) -> &str; -/// -/// #[graphql(downcast)] // makes method a downcast to `Human`, not a field -/// // NOTICE: The method signature may optionally contain `&Database` context argument. -/// fn as_human(&self) -> Option<&Human> { -/// None -/// } -/// } -/// -/// #[derive(GraphQLObject)] -/// #[graphql(impl = CharacterValue, Context = Database)] -/// struct Human { -/// id: String, -/// } -/// #[graphql_interface] -/// impl Character for Human { -/// fn id(&self) -> &str { -/// &self.id -/// } -/// -/// fn as_human(&self) -> Option<&Self> { -/// Some(self) -/// } -/// } -/// -/// #[derive(GraphQLObject)] -/// #[graphql(impl = CharacterValue, Context = Database)] -/// struct Droid { -/// id: String, -/// } -/// #[graphql_interface] -/// impl Character for Droid { -/// fn id(&self) -> &str { -/// &self.id -/// } -/// } -/// -/// // External downcast function doesn't have to be a method of a type. -/// // It's only a matter of the function signature to match the requirements. -/// fn get_droid<'db>(ch: &CharacterValue, db: &'db Database) -> Option<&'db Droid> { -/// db.droids.get(ch.id()) -/// } /// ``` /// /// [`Context`]: juniper::Context From 81d96cefcea5aa43a57831095b69ee7ebfc760ca Mon Sep 17 00:00:00 2001 From: ilslv Date: Thu, 6 Jan 2022 08:52:34 +0300 Subject: [PATCH 42/53] WIP (Forbid default trait method impls) --- .../fail/interface/argument_double_underscored.rs | 4 +--- .../fail/interface/argument_double_underscored.stderr | 2 +- .../fail/interface/argument_wrong_default_array.rs | 10 ---------- .../interface/argument_wrong_default_array.stderr | 11 ----------- .../fail/interface/field_double_underscored.rs | 4 +--- .../fail/interface/field_double_underscored.stderr | 2 +- .../fail/interface/method_default_impl.rs | 10 ++++++++++ .../fail/interface/method_default_impl.stderr | 10 ++++++++++ juniper_codegen/src/graphql_interface/attr.rs | 11 +++++++++++ 9 files changed, 35 insertions(+), 29 deletions(-) delete mode 100644 integration_tests/codegen_fail/fail/interface/argument_wrong_default_array.rs delete mode 100644 integration_tests/codegen_fail/fail/interface/argument_wrong_default_array.stderr create mode 100644 integration_tests/codegen_fail/fail/interface/method_default_impl.rs create mode 100644 integration_tests/codegen_fail/fail/interface/method_default_impl.stderr diff --git a/integration_tests/codegen_fail/fail/interface/argument_double_underscored.rs b/integration_tests/codegen_fail/fail/interface/argument_double_underscored.rs index b842c7ce4..449d67485 100644 --- a/integration_tests/codegen_fail/fail/interface/argument_double_underscored.rs +++ b/integration_tests/codegen_fail/fail/interface/argument_double_underscored.rs @@ -2,9 +2,7 @@ use juniper::graphql_interface; #[graphql_interface] trait Character { - fn id(&self, __num: i32) -> &str { - "funA" - } + fn id(&self, __num: i32) -> &str; } fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/argument_double_underscored.stderr b/integration_tests/codegen_fail/fail/interface/argument_double_underscored.stderr index a75859459..0a6e79eba 100644 --- a/integration_tests/codegen_fail/fail/interface/argument_double_underscored.stderr +++ b/integration_tests/codegen_fail/fail/interface/argument_double_underscored.stderr @@ -1,7 +1,7 @@ error: All types and directives defined within a schema must not have a name which begins with `__` (two underscores), as this is used exclusively by GraphQL’s introspection system. --> fail/interface/argument_double_underscored.rs:5:18 | -5 | fn id(&self, __num: i32) -> &str { +5 | fn id(&self, __num: i32) -> &str; | ^^^^^ | = note: https://spec.graphql.org/June2018/#sec-Schema diff --git a/integration_tests/codegen_fail/fail/interface/argument_wrong_default_array.rs b/integration_tests/codegen_fail/fail/interface/argument_wrong_default_array.rs deleted file mode 100644 index 48ce93738..000000000 --- a/integration_tests/codegen_fail/fail/interface/argument_wrong_default_array.rs +++ /dev/null @@ -1,10 +0,0 @@ -use juniper::graphql_interface; - -#[graphql_interface] -trait Character { - fn wrong(&self, #[graphql(default = [true, false, false])] input: [bool; 2]) -> bool { - input[0] - } -} - -fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/argument_wrong_default_array.stderr b/integration_tests/codegen_fail/fail/interface/argument_wrong_default_array.stderr deleted file mode 100644 index b745a82d0..000000000 --- a/integration_tests/codegen_fail/fail/interface/argument_wrong_default_array.stderr +++ /dev/null @@ -1,11 +0,0 @@ -error[E0277]: the trait bound `[bool; 2]: From<[bool; 3]>` is not satisfied - --> fail/interface/argument_wrong_default_array.rs:3:1 - | -3 | #[graphql_interface] - | ^^^^^^^^^^^^^^^^^^^^ the trait `From<[bool; 3]>` is not implemented for `[bool; 2]` - | - = help: the following implementations were found: - <[T; LANES] as From>> - <[bool; LANES] as From>> - = note: required because of the requirements on the impl of `Into<[bool; 2]>` for `[bool; 3]` - = note: this error originates in the attribute macro `graphql_interface` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/integration_tests/codegen_fail/fail/interface/field_double_underscored.rs b/integration_tests/codegen_fail/fail/interface/field_double_underscored.rs index 3096cd73a..10d001198 100644 --- a/integration_tests/codegen_fail/fail/interface/field_double_underscored.rs +++ b/integration_tests/codegen_fail/fail/interface/field_double_underscored.rs @@ -2,9 +2,7 @@ use juniper::graphql_interface; #[graphql_interface] trait Character { - fn __id(&self) -> &str { - "funA" - } + fn __id(&self) -> &str; } fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/field_double_underscored.stderr b/integration_tests/codegen_fail/fail/interface/field_double_underscored.stderr index ef662d93d..6c683ed51 100644 --- a/integration_tests/codegen_fail/fail/interface/field_double_underscored.stderr +++ b/integration_tests/codegen_fail/fail/interface/field_double_underscored.stderr @@ -1,7 +1,7 @@ error: All types and directives defined within a schema must not have a name which begins with `__` (two underscores), as this is used exclusively by GraphQL’s introspection system. --> fail/interface/field_double_underscored.rs:5:8 | -5 | fn __id(&self) -> &str { +5 | fn __id(&self) -> &str; | ^^^^ | = note: https://spec.graphql.org/June2018/#sec-Schema diff --git a/integration_tests/codegen_fail/fail/interface/method_default_impl.rs b/integration_tests/codegen_fail/fail/interface/method_default_impl.rs new file mode 100644 index 000000000..e7ac7832e --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/method_default_impl.rs @@ -0,0 +1,10 @@ +use juniper::graphql_interface; + +#[graphql_interface] +trait Character { + fn id(&self) -> &str { + "default" + } +} + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/method_default_impl.stderr b/integration_tests/codegen_fail/fail/interface/method_default_impl.stderr new file mode 100644 index 000000000..a5a728b90 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/method_default_impl.stderr @@ -0,0 +1,10 @@ +error: GraphQL interface trait method can't have default impl block + --> fail/interface/method_default_impl.rs:5:26 + | +5 | fn id(&self) -> &str { + | __________________________^ +6 | | "default" +7 | | } + | |_____^ + | + = note: https://spec.graphql.org/June2018/#sec-Interfaces diff --git a/juniper_codegen/src/graphql_interface/attr.rs b/juniper_codegen/src/graphql_interface/attr.rs index 47a3fc3a4..a5659274e 100644 --- a/juniper_codegen/src/graphql_interface/attr.rs +++ b/juniper_codegen/src/graphql_interface/attr.rs @@ -178,6 +178,10 @@ fn parse_field( method: &mut syn::TraitItemMethod, renaming: &RenameRule, ) -> Option { + if method.default.is_some() { + return err_default_impl_block(&method.default); + } + let method_ident = &method.sig.ident; let method_attrs = method.attrs.clone(); @@ -262,6 +266,13 @@ fn parse_field( }) } +/// Emits "trait method can't have default impl block" [`syn::Error`] pointing +/// to the given `span`. +fn err_default_impl_block(span: &S) -> Option { + ERR.emit_custom(span.span(), "trait method can't have default impl block"); + None +} + /// Emits "invalid trait method receiver" [`syn::Error`] pointing to the given /// `span`. #[must_use] From 94164eb0b442f5577390b5512f745fbf124c8cd8 Mon Sep 17 00:00:00 2001 From: ilslv Date: Thu, 6 Jan 2022 09:50:50 +0300 Subject: [PATCH 43/53] WIP (Corrections) --- docs/book/content/types/interfaces.md | 208 ++---------------- .../src/codegen/interface_attr.rs | 21 +- juniper/src/ast.rs | 1 + juniper/src/executor/mod.rs | 3 +- juniper/src/executor/owned_executor.rs | 2 + juniper/src/macros/reflection.rs | 5 +- juniper/src/parser/value.rs | 4 +- juniper/src/schema/meta.rs | 15 ++ juniper/src/tests/fixtures/starwars/schema.rs | 12 +- juniper/src/types/nullable.rs | 3 + juniper_codegen/src/graphql_interface/attr.rs | 12 +- juniper_codegen/src/lib.rs | 6 +- juniper_graphql_ws/src/lib.rs | 2 + juniper_hyper/src/lib.rs | 12 +- juniper_iron/src/lib.rs | 16 +- juniper_subscriptions/src/lib.rs | 4 +- 16 files changed, 77 insertions(+), 249 deletions(-) diff --git a/docs/book/content/types/interfaces.md b/docs/book/content/types/interfaces.md index 414d2f299..a7d723ae1 100644 --- a/docs/book/content/types/interfaces.md +++ b/docs/book/content/types/interfaces.md @@ -45,32 +45,14 @@ trait Character { struct Human { id: String, } -#[graphql_interface] // implementing requires macro attribute too, (°o°)! -impl Character for Human { - fn id(&self) -> &str { - &self.id - } -} #[derive(GraphQLObject)] #[graphql(impl = CharacterValue)] struct Droid { id: String, } -#[graphql_interface] -impl Character for Droid { - fn id(&self) -> &str { - &self.id - } -} - -# fn main() { -let human = Human { id: "human-32".to_owned() }; -// Values type for interface has `From` implementations for all its implementers, -// so we don't need to bother with enum variant names. -let character: CharacterValue = human.into(); -assert_eq!(character.id(), "human-32"); -# } +# +# fn main() {} ``` Also, enum name can be specified explicitly, if desired. @@ -90,71 +72,11 @@ struct Human { id: String, home_planet: String, } -#[graphql_interface] -impl Character for Human { - fn id(&self) -> &str { - &self.id - } -} # # fn main() {} ``` -### Trait object values - -If, for some reason, we would like to use [trait objects][2] for representing [interface][1] values incorporating dynamic dispatch, then it should be specified explicitly in the trait definition. - -Downcasting [trait objects][2] in Rust is not that trivial, that's why macro transforms the trait definition slightly, imposing some additional type parameters under-the-hood. - -> __NOTICE__: -> A __trait has to be [object safe](https://doc.rust-lang.org/stable/reference/items/traits.html#object-safety)__, because schema resolvers will need to return a [trait object][2] to specify a [GraphQL interface][1] behind it. - -```rust -# extern crate juniper; -# extern crate tokio; -use juniper::{graphql_interface, GraphQLObject}; - -// `dyn` argument accepts the name of type alias for the required trait object, -// and macro generates this alias automatically. -#[graphql_interface(dyn = DynCharacter, for = Human)] -trait Character { - async fn id(&self) -> &str; // async fields are supported natively -} - -#[derive(GraphQLObject)] -#[graphql(impl = DynCharacter<__S>)] // macro adds `ScalarValue` type parameter to trait, -struct Human { // so it may be specified explicitly when required - id: String, -} -#[graphql_interface(dyn)] // implementing requires to know about dynamic dispatch too -impl Character for Human { - async fn id(&self) -> &str { - &self.id - } -} - -#[derive(GraphQLObject)] -#[graphql(impl = DynCharacter<__S>)] -struct Droid { - id: String, -} -#[graphql_interface] -impl Character for Droid { - async fn id(&self) -> &str { - &self.id - } -} - -# #[tokio::main] -# async fn main() { -let human = Human { id: "human-32".to_owned() }; -let character: Box = Box::new(human); -assert_eq!(character.id().await, "human-32"); -# } -``` - - ### Ignoring trait methods We may want to omit some trait methods to be assumed as [GraphQL interface][1] fields and ignore them. @@ -176,12 +98,6 @@ trait Character { struct Human { id: String, } -#[graphql_interface] -impl Character for Human { - fn id(&self) -> &str { - &self.id - } -} # # fn main() {} ``` @@ -278,24 +194,6 @@ struct Human { id: String, name: String, } -#[graphql_interface] -impl Character for Human { - fn id(&self, db: &Database) -> Option<&str> { - if db.humans.contains_key(&self.id) { - Some(&self.id) - } else { - None - } - } - - fn name(&self, db: &Database) -> Option<&str> { - if db.humans.contains_key(&self.id) { - Some(&self.name) - } else { - None - } - } -} # # fn main() {} ``` @@ -309,7 +207,7 @@ This requires to explicitly parametrize over [`ScalarValue`][3], as [`Executor`] ```rust # extern crate juniper; -use juniper::{graphql_interface, Executor, GraphQLObject, LookAheadMethods as _, ScalarValue}; +use juniper::{graphql_interface, graphql_object, Executor, LookAheadMethods as _, ScalarValue}; #[graphql_interface(for = Human, Scalar = S)] // notice specifying `ScalarValue` as existing type parameter trait Character { @@ -326,102 +224,40 @@ trait Character { ) -> &'b str where S: Send + Sync; + + fn home_planet(&self) -> &str; } -#[derive(GraphQLObject)] -#[graphql(impl = CharacterValue<__S>)] struct Human { id: String, name: String, + home_planet: String, } -#[graphql_interface(scalar = S)] -impl Character for Human { - async fn id<'a>(&self, executor: &'a Executor<'_, '_, (), S>) -> &'a str +#[graphql_object(impl = CharacterValue<__S>)] +impl Human { + async fn id<'a, S>(&self, executor: &'a Executor<'_, '_, (), S>) -> &'a str where - S: Send + Sync, + S: ScalarValue + Send + Sync, { executor.look_ahead().field_name() } - async fn name<'b>(&'b self, _: &Executor<'_, '_, (), S>) -> &'b str + async fn name<'b, S>(&'b self, #[graphql(executor)] _: &Executor<'_, '_, (), S>) -> &'b str where - S: Send + Sync, + S: ScalarValue + Send + Sync, { &self.name } -} -# -# fn main() {} -``` - - -### Downcasting - -By default, the [GraphQL interface][1] value is downcast to one of its implementer types via matching the enum variant or downcasting the trait object (if `dyn` macro argument is used). - -However, if some custom logic is needed to downcast a [GraphQL interface][1] implementer, you may specify either an external function or a trait method to do so. - -```rust -# extern crate juniper; -# use std::collections::HashMap; -use juniper::{graphql_interface, GraphQLObject}; - -struct Database { - droids: HashMap, -} -impl juniper::Context for Database {} - -#[graphql_interface(for = [Human, Droid], context = Database)] -#[graphql_interface(on Droid = get_droid)] // enables downcasting `Droid` via `get_droid()` function -trait Character { - fn id(&self) -> &str; - - #[graphql(downcast)] // makes method a downcast to `Human`, not a field - // NOTICE: The method signature may optionally contain `&Database` context argument. - fn as_human(&self) -> Option<&Human> { - None + + fn home_planet<'c, S>(&'c self, #[graphql(executor)] _: &Executor<'_, '_, (), S>) -> &'c str { + // Executor may not be present on the trait method ^^^^^^^^^^^^^^^^^^^^^^^^ + &self.home_planet } } - -#[derive(GraphQLObject)] -#[graphql(impl = CharacterValue, Context = Database)] -struct Human { - id: String, -} -#[graphql_interface] -impl Character for Human { - fn id(&self) -> &str { - &self.id - } - - fn as_human(&self) -> Option<&Self> { - Some(self) - } -} - -#[derive(GraphQLObject)] -#[graphql(impl = CharacterValue, Context = Database)] -struct Droid { - id: String, -} -#[graphql_interface] -impl Character for Droid { - fn id(&self) -> &str { - &self.id - } -} - -// External downcast function doesn't have to be a method of a type. -// It's only a matter of the function signature to match the requirements. -fn get_droid<'db>(ch: &CharacterValue, db: &'db Database) -> Option<&'db Droid> { - db.droids.get(ch.id()) -} # # fn main() {} ``` -The attribute syntax `#[graphql_interface(on ImplementerType = resolver_fn)]` follows the [GraphQL syntax for downcasting interface implementer](https://spec.graphql.org/June2018/#example-5cc55). - @@ -445,12 +281,6 @@ struct Human { id: String, home_planet: String, } -#[graphql_interface(scalar = DefaultScalarValue)] -impl Character for Human { - fn id(&self) -> &str { - &self.id - } -} #[derive(GraphQLObject)] #[graphql(impl = CharacterValue, Scalar = DefaultScalarValue)] @@ -458,12 +288,6 @@ struct Droid { id: String, primary_function: String, } -#[graphql_interface(scalar = DefaultScalarValue)] -impl Character for Droid { - fn id(&self) -> &str { - &self.id - } -} # # fn main() {} ``` diff --git a/integration_tests/juniper_tests/src/codegen/interface_attr.rs b/integration_tests/juniper_tests/src/codegen/interface_attr.rs index 5646a5d9b..9666f430c 100644 --- a/integration_tests/juniper_tests/src/codegen/interface_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/interface_attr.rs @@ -1621,9 +1621,7 @@ mod default_argument { #[graphql(default = "t")] third: String, ) -> String; - fn info(&self, #[graphql(default = Point { x: 1 })] coord: Point) -> i32 { - coord.x - } + fn info(&self, #[graphql(default = Point { x: 1 })] coord: Point) -> i32; } struct Human; @@ -2128,17 +2126,11 @@ mod renamed_all_fields_and_args { #[graphql_interface(rename_all = "none", for = Human)] trait Character { - fn id(&self) -> &str { - "human-32" - } + fn id(&self) -> &str; - async fn home_planet(&self, planet_name: String) -> String { - planet_name - } + async fn home_planet(&self, planet_name: String) -> String; - async fn r#async_info(&self, r#my_num: i32) -> i32 { - r#my_num - } + async fn r#async_info(&self, r#my_num: i32) -> i32; } struct Human; @@ -3039,10 +3031,7 @@ mod executor { trait Character { async fn id<'a>(&self, executor: &'a Executor<'_, '_, (), S>) -> &'a str where - S: Send + Sync, - { - executor.look_ahead().field_name() - } + S: Send + Sync; async fn info<'b>( &'b self, diff --git a/juniper/src/ast.rs b/juniper/src/ast.rs index f7805eeb3..ea2c0fec2 100644 --- a/juniper/src/ast.rs +++ b/juniper/src/ast.rs @@ -296,6 +296,7 @@ impl InputValue { } /// Resolve all variables to their values. + #[must_use] pub fn into_const(self, vars: &Variables) -> Self where S: Clone, diff --git a/juniper/src/executor/mod.rs b/juniper/src/executor/mod.rs index 553af48ea..9b9de9497 100644 --- a/juniper/src/executor/mod.rs +++ b/juniper/src/executor/mod.rs @@ -701,7 +701,7 @@ where FieldPath::Root(_) => unreachable!(), }; self.parent_selection_set - .map(|p| { + .and_then(|p| { // Search the parent's fields to find this field within the set let found_field = p.iter().find(|&x| { match *x { @@ -721,7 +721,6 @@ where None } }) - .flatten() .unwrap_or_else(|| { // We didn't find a field in the parent's selection matching // this field, which means we're inside a FragmentSpread diff --git a/juniper/src/executor/owned_executor.rs b/juniper/src/executor/owned_executor.rs index 28b03c3a7..cce1a208e 100644 --- a/juniper/src/executor/owned_executor.rs +++ b/juniper/src/executor/owned_executor.rs @@ -49,6 +49,7 @@ where S: Clone, { #[doc(hidden)] + #[must_use] pub fn type_sub_executor( &self, type_name: Option<&str>, @@ -76,6 +77,7 @@ where } #[doc(hidden)] + #[must_use] pub fn field_sub_executor( &self, field_alias: &'a str, diff --git a/juniper/src/macros/reflection.rs b/juniper/src/macros/reflection.rs index bd14b8586..de324e674 100644 --- a/juniper/src/macros/reflection.rs +++ b/juniper/src/macros/reflection.rs @@ -820,7 +820,7 @@ macro_rules! const_concat { // SAFETY: this is safe, as we concatenate multiple UTF-8 strings one // after the other byte by byte. #[allow(unsafe_code)] - unsafe { std::str::from_utf8_unchecked(&CON) } + unsafe { ::std::str::from_utf8_unchecked(&CON) } }}; } @@ -941,7 +941,8 @@ macro_rules! format_type { // SAFETY: this is safe, as we concatenate multiple UTF-8 strings one // after the other byte by byte. #[allow(unsafe_code)] - const TYPE_FORMATTED: &str = unsafe { ::std::mem::transmute(TYPE_ARR.as_slice()) }; + const TYPE_FORMATTED: &str = + unsafe { ::std::str::from_utf8_unchecked(TYPE_ARR.as_slice()) }; TYPE_FORMATTED }}; diff --git a/juniper/src/parser/value.rs b/juniper/src/parser/value.rs index 8d66661c9..3ff6d5efc 100644 --- a/juniper/src/parser/value.rs +++ b/juniper/src/parser/value.rs @@ -112,9 +112,7 @@ where .. }, _, - ) => Ok(parser - .next_token()? - .map(|_| InputValue::enum_value(name.to_owned()))), + ) => Ok(parser.next_token()?.map(|_| InputValue::enum_value(name))), _ => Err(parser.next_token()?.map(ParseError::UnexpectedToken)), } } diff --git a/juniper/src/schema/meta.rs b/juniper/src/schema/meta.rs index d4f350842..ccd43a99a 100644 --- a/juniper/src/schema/meta.rs +++ b/juniper/src/schema/meta.rs @@ -447,6 +447,7 @@ impl<'a, S> ScalarMeta<'a, S> { /// Sets the `description` of this [`ScalarMeta`] type. /// /// Overwrites any previously set description. + #[must_use] pub fn description(mut self, description: &str) -> Self { self.description = Some(description.to_owned()); self @@ -457,6 +458,7 @@ impl<'a, S> ScalarMeta<'a, S> { /// Overwrites any previously set [specification URL][0]. /// /// [0]: https://spec.graphql.org/October2021#sec--specifiedBy + #[must_use] pub fn specified_by_url(mut self, url: impl Into>) -> Self { self.specified_by_url = Some(url.into()); self @@ -515,6 +517,7 @@ impl<'a, S> ObjectMeta<'a, S> { /// Sets the `description` of this [`ObjectMeta`] type. /// /// Overwrites any previously set description. + #[must_use] pub fn description(mut self, description: &str) -> Self { self.description = Some(description.to_owned()); self @@ -523,6 +526,7 @@ impl<'a, S> ObjectMeta<'a, S> { /// Set the `interfaces` this [`ObjectMeta`] type implements. /// /// Overwrites any previously set list of interfaces. + #[must_use] pub fn interfaces(mut self, interfaces: &[Type<'a>]) -> Self { self.interface_names = interfaces .iter() @@ -556,6 +560,7 @@ impl<'a, S> EnumMeta<'a, S> { /// Sets the `description` of this [`EnumMeta`] type. /// /// Overwrites any previously set description. + #[must_use] pub fn description(mut self, description: &str) -> Self { self.description = Some(description.to_owned()); self @@ -584,6 +589,7 @@ impl<'a, S> InterfaceMeta<'a, S> { /// Sets the `description` of this [`InterfaceMeta`] type. /// /// Overwrites any previously set description. + #[must_use] pub fn description(mut self, description: &str) -> Self { self.description = Some(description.to_owned()); self @@ -612,6 +618,7 @@ impl<'a> UnionMeta<'a> { /// Sets the `description` of this [`UnionMeta`] type. /// /// Overwrites any previously set description. + #[must_use] pub fn description(mut self, description: &str) -> Self { self.description = Some(description.to_owned()); self @@ -643,6 +650,7 @@ impl<'a, S> InputObjectMeta<'a, S> { /// Set the `description` of this [`InputObjectMeta`] type. /// /// Overwrites any previously set description. + #[must_use] pub fn description(mut self, description: &str) -> Self { self.description = Some(description.to_owned()); self @@ -658,6 +666,7 @@ impl<'a, S> Field<'a, S> { /// Set the `description` of this [`Field`]. /// /// Overwrites any previously set description. + #[must_use] pub fn description(mut self, description: &str) -> Self { self.description = Some(description.to_owned()); self @@ -666,6 +675,7 @@ impl<'a, S> Field<'a, S> { /// Adds an `argument` to this [`Field`]. /// /// Arguments are unordered and can't contain duplicates by name. + #[must_use] pub fn argument(mut self, argument: Argument<'a, S>) -> Self { match self.arguments { None => { @@ -681,6 +691,7 @@ impl<'a, S> Field<'a, S> { /// Sets this [`Field`] as deprecated with an optional `reason`. /// /// Overwrites any previously set deprecation reason. + #[must_use] pub fn deprecated(mut self, reason: Option<&str>) -> Self { self.deprecation_status = DeprecationStatus::Deprecated(reason.map(ToOwned::to_owned)); self @@ -701,6 +712,7 @@ impl<'a, S> Argument<'a, S> { /// Sets the `description` of this [`Argument`]. /// /// Overwrites any previously set description. + #[must_use] pub fn description(mut self, description: &str) -> Self { self.description = Some(description.to_owned()); self @@ -709,6 +721,7 @@ impl<'a, S> Argument<'a, S> { /// Set the default value of this [`Argument`]. /// /// Overwrites any previously set default value. + #[must_use] pub fn default_value(mut self, val: InputValue) -> Self { self.default_value = Some(val); self @@ -728,6 +741,7 @@ impl EnumValue { /// Sets the `description` of this [`EnumValue`]. /// /// Overwrites any previously set description. + #[must_use] pub fn description(mut self, description: &str) -> Self { self.description = Some(description.to_owned()); self @@ -736,6 +750,7 @@ impl EnumValue { /// Sets this [`EnumValue`] as deprecated with an optional `reason`. /// /// Overwrites any previously set deprecation reason. + #[must_use] pub fn deprecated(mut self, reason: Option<&str>) -> Self { self.deprecation_status = DeprecationStatus::Deprecated(reason.map(ToOwned::to_owned)); self diff --git a/juniper/src/tests/fixtures/starwars/schema.rs b/juniper/src/tests/fixtures/starwars/schema.rs index 237dc6f84..233e2f347 100644 --- a/juniper/src/tests/fixtures/starwars/schema.rs +++ b/juniper/src/tests/fixtures/starwars/schema.rs @@ -95,11 +95,7 @@ impl Human { Self { id: id.to_owned(), name: name.to_owned(), - friend_ids: friend_ids - .to_owned() - .into_iter() - .map(ToOwned::to_owned) - .collect(), + friend_ids: friend_ids.iter().copied().map(ToOwned::to_owned).collect(), appears_in: appears_in.to_vec(), secret_backstory: secret_backstory.map(ToOwned::to_owned), home_planet: home_planet.map(|p| p.to_owned()), @@ -181,11 +177,7 @@ impl Droid { Self { id: id.to_owned(), name: name.to_owned(), - friend_ids: friend_ids - .to_owned() - .into_iter() - .map(ToOwned::to_owned) - .collect(), + friend_ids: friend_ids.iter().copied().map(ToOwned::to_owned).collect(), appears_in: appears_in.to_vec(), secret_backstory: secret_backstory.map(ToOwned::to_owned), primary_function: primary_function.map(ToOwned::to_owned), diff --git a/juniper/src/types/nullable.rs b/juniper/src/types/nullable.rs index 81341c597..7e8d01cc1 100644 --- a/juniper/src/types/nullable.rs +++ b/juniper/src/types/nullable.rs @@ -141,6 +141,7 @@ impl Nullable { /// Returns the nullable if it contains a value, otherwise returns `b`. #[inline] + #[must_use] pub fn or(self, b: Self) -> Self { match self { Self::Some(_) => self, @@ -151,6 +152,7 @@ impl Nullable { /// Returns the nullable if it contains a value, otherwise calls `f` and /// returns the result. #[inline] + #[must_use] pub fn or_else Nullable>(self, f: F) -> Nullable { match self { Self::Some(_) => self, @@ -161,6 +163,7 @@ impl Nullable { /// Replaces the actual value in the nullable by the value given in parameter, returning the /// old value if present, leaving a `Some` in its place without deinitializing either one. #[inline] + #[must_use] pub fn replace(&mut self, value: T) -> Self { std::mem::replace(self, Self::Some(value)) } diff --git a/juniper_codegen/src/graphql_interface/attr.rs b/juniper_codegen/src/graphql_interface/attr.rs index a5659274e..c2a504db4 100644 --- a/juniper_codegen/src/graphql_interface/attr.rs +++ b/juniper_codegen/src/graphql_interface/attr.rs @@ -23,7 +23,7 @@ const ERR: GraphQLScope = GraphQLScope::InterfaceAttr; /// Expands `#[graphql_interface]` macro into generated code. pub fn expand(attr_args: TokenStream, body: TokenStream) -> syn::Result { - if let Ok(mut ast) = syn::parse2::(body.clone()) { + if let Ok(mut ast) = syn::parse2::(body) { let trait_attrs = parse::attr::unite(("graphql_interface", &attr_args), &ast.attrs); ast.attrs = parse::attr::strip("graphql_interface", ast.attrs); return expand_on_trait(trait_attrs, ast); @@ -137,7 +137,7 @@ fn expand_on_trait( name, description, context, - scalar: scalar.clone(), + scalar, fields, implementers: attr .implementers @@ -178,10 +178,6 @@ fn parse_field( method: &mut syn::TraitItemMethod, renaming: &RenameRule, ) -> Option { - if method.default.is_some() { - return err_default_impl_block(&method.default); - } - let method_ident = &method.sig.ident; let method_attrs = method.attrs.clone(); @@ -199,6 +195,10 @@ fn parse_field( return None; } + if method.default.is_some() { + return err_default_impl_block(&method.default); + } + let name = attr .name .as_ref() diff --git a/juniper_codegen/src/lib.rs b/juniper_codegen/src/lib.rs index 409d29757..1c27cf356 100644 --- a/juniper_codegen/src/lib.rs +++ b/juniper_codegen/src/lib.rs @@ -272,8 +272,10 @@ pub fn graphql_scalar(args: TokenStream, input: TokenStream) -> TokenStream { /// is totally okay. They all will be treated as a single attribute. /// /// [GraphQL interfaces][1] are more like Go's structural interfaces, while -/// Rust's traits are more like typeclasses. So using `impl Trait` isn't an -/// option. +/// Rust's traits are more like typeclasses. Using `impl Trait` isn't an +/// option, so you have to cover all trait's methods with type's fields or +/// impl block. But no one is stopping you from additionally implementing trait +/// manually. /// /// Another difference between [GraphQL interface][1] type and Rust trait is /// that the former serves both as an _abstraction_ and a _value downcastable to diff --git a/juniper_graphql_ws/src/lib.rs b/juniper_graphql_ws/src/lib.rs index f1fe9bd3b..51c2e914f 100644 --- a/juniper_graphql_ws/src/lib.rs +++ b/juniper_graphql_ws/src/lib.rs @@ -68,6 +68,7 @@ impl ConnectionConfig { /// Specifies the maximum number of in-flight operations that a connection can have. If this /// number is exceeded, attempting to start more will result in an error. By default, there is /// no limit to in-flight operations. + #[must_use] pub fn with_max_in_flight_operations(mut self, max: usize) -> Self { self.max_in_flight_operations = max; self @@ -75,6 +76,7 @@ impl ConnectionConfig { /// Specifies the interval at which to send keep-alives. Specifying a zero duration will /// disable keep-alives. By default, keep-alives are sent every 15 seconds. + #[must_use] pub fn with_keep_alive_interval(mut self, interval: Duration) -> Self { self.keep_alive_interval = interval; self diff --git a/juniper_hyper/src/lib.rs b/juniper_hyper/src/lib.rs index 52e67a929..50ccff197 100644 --- a/juniper_hyper/src/lib.rs +++ b/juniper_hyper/src/lib.rs @@ -286,13 +286,13 @@ enum GraphQLRequestError { } impl fmt::Display for GraphQLRequestError { - fn fmt(&self, mut f: &mut fmt::Formatter) -> fmt::Result { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { - GraphQLRequestError::BodyHyper(ref err) => fmt::Display::fmt(err, &mut f), - GraphQLRequestError::BodyUtf8(ref err) => fmt::Display::fmt(err, &mut f), - GraphQLRequestError::BodyJSONError(ref err) => fmt::Display::fmt(err, &mut f), - GraphQLRequestError::Variables(ref err) => fmt::Display::fmt(err, &mut f), - GraphQLRequestError::Invalid(ref err) => fmt::Display::fmt(err, &mut f), + GraphQLRequestError::BodyHyper(ref err) => fmt::Display::fmt(err, f), + GraphQLRequestError::BodyUtf8(ref err) => fmt::Display::fmt(err, f), + GraphQLRequestError::BodyJSONError(ref err) => fmt::Display::fmt(err, f), + GraphQLRequestError::Variables(ref err) => fmt::Display::fmt(err, f), + GraphQLRequestError::Invalid(ref err) => fmt::Display::fmt(err, f), } } } diff --git a/juniper_iron/src/lib.rs b/juniper_iron/src/lib.rs index 5a6651878..40d1f8039 100644 --- a/juniper_iron/src/lib.rs +++ b/juniper_iron/src/lib.rs @@ -311,15 +311,15 @@ where Subscription: GraphQLType + Send + Sync + 'static, 'a: 'static, { - fn handle(&self, mut req: &mut Request) -> IronResult { + fn handle(&self, req: &mut Request) -> IronResult { let context = (self.context_factory)(req)?; let graphql_request = match req.method { - method::Get => self.handle_get(&mut req)?, + method::Get => self.handle_get(req)?, method::Post => match req.headers.get::().map(ContentType::deref) { Some(Mime(TopLevel::Application, sub_lvl, _)) => match sub_lvl.as_str() { - "json" => self.handle_post_json(&mut req)?, - "graphql" => self.handle_post_graphql(&mut req)?, + "json" => self.handle_post_json(req)?, + "graphql" => self.handle_post_graphql(req)?, _ => return Ok(Response::with(status::BadRequest)), }, _ => return Ok(Response::with(status::BadRequest)), @@ -369,11 +369,11 @@ enum GraphQLIronError { } impl fmt::Display for GraphQLIronError { - fn fmt(&self, mut f: &mut fmt::Formatter) -> fmt::Result { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { - GraphQLIronError::Serde(ref err) => fmt::Display::fmt(err, &mut f), - GraphQLIronError::Url(ref err) => fmt::Display::fmt(err, &mut f), - GraphQLIronError::InvalidData(err) => fmt::Display::fmt(err, &mut f), + GraphQLIronError::Serde(ref err) => fmt::Display::fmt(err, f), + GraphQLIronError::Url(ref err) => fmt::Display::fmt(err, f), + GraphQLIronError::InvalidData(err) => fmt::Display::fmt(err, f), } } } diff --git a/juniper_subscriptions/src/lib.rs b/juniper_subscriptions/src/lib.rs index 5e890b630..e09e11279 100644 --- a/juniper_subscriptions/src/lib.rs +++ b/juniper_subscriptions/src/lib.rs @@ -183,7 +183,7 @@ where ready_vec.push(None); } - let stream = stream::poll_fn(move |mut ctx| -> Poll>> { + let stream = stream::poll_fn(move |ctx| -> Poll>> { let mut obj_iterator = object.iter_mut(); // Due to having to modify `ready_vec` contents (by-move pattern) @@ -204,7 +204,7 @@ where match val { Value::Scalar(stream) => { - match Pin::new(stream).poll_next(&mut ctx) { + match Pin::new(stream).poll_next(ctx) { Poll::Ready(None) => return Poll::Ready(None), Poll::Ready(Some(value)) => { *ready = Some((field_name.clone(), value)); From 1345bffcd8533126e350511411b1d0fed4faffab Mon Sep 17 00:00:00 2001 From: ilslv Date: Thu, 6 Jan 2022 10:19:16 +0300 Subject: [PATCH 44/53] WIP (Corrections) --- juniper/src/macros/reflection.rs | 9 ++++++++- juniper_codegen/src/common/parse/mod.rs | 2 ++ juniper_codegen/src/graphql_union/mod.rs | 4 ++-- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/juniper/src/macros/reflection.rs b/juniper/src/macros/reflection.rs index de324e674..f6fb31bb9 100644 --- a/juniper/src/macros/reflection.rs +++ b/juniper/src/macros/reflection.rs @@ -281,11 +281,14 @@ pub trait Fields { const NAMES: Names; } +/// [`Types`] of the [GraphQL interfaces][1] implemented by this type. +/// +/// [1]: https://spec.graphql.org/October2021/#sec-Interfaces pub trait Implements { const NAMES: Types; } -/// Stores meta information of [GraphQL fields][1]: +/// Stores meta information of [GraphQL field][1]: /// - [`Context`] and [`TypeInfo`]. /// - Return type's [`TYPE`], [`SUB_TYPES`] and [`WRAPPED_VALUE`]. /// - [`ARGUMENTS`]. @@ -471,6 +474,8 @@ pub const fn str_eq(l: &str, r: &str) -> bool { true } +/// Asserts that `#[graphql_interface(for = ...)]` has all types referencing +/// this interface in `impl = ...` attribute section. #[macro_export] macro_rules! assert_implemented_for { ($scalar: ty, $implementor: ty $(, $interfaces: ty)* $(,)?) => { @@ -495,6 +500,8 @@ macro_rules! assert_implemented_for { }; } +/// Asserts that `impl = ...` attribute section has all types referencing this +/// type in `#[graphql_interface(for = ...)]`. #[macro_export] macro_rules! assert_interfaces_impls { ($scalar: ty, $interface: ty $(, $implementers: ty)* $(,)?) => { diff --git a/juniper_codegen/src/common/parse/mod.rs b/juniper_codegen/src/common/parse/mod.rs index 7f5cda6d5..0ed3c0dfb 100644 --- a/juniper_codegen/src/common/parse/mod.rs +++ b/juniper_codegen/src/common/parse/mod.rs @@ -330,6 +330,8 @@ impl GenericsExt for syn::Generics { }); if is_generic { + // Replacing with `DefaultScalarValue` instead of + // `()` because generic parameter may be scalar. *ty = parse_quote!(::juniper::DefaultScalarValue); } } diff --git a/juniper_codegen/src/graphql_union/mod.rs b/juniper_codegen/src/graphql_union/mod.rs index 42eb6123b..eb882961d 100644 --- a/juniper_codegen/src/graphql_union/mod.rs +++ b/juniper_codegen/src/graphql_union/mod.rs @@ -320,7 +320,7 @@ impl ToTokens for Definition { self.impl_graphql_type_tokens().to_tokens(into); self.impl_graphql_value_tokens().to_tokens(into); self.impl_graphql_value_async_tokens().to_tokens(into); - self.impl_traits_for_const_assertions().to_tokens(into); + self.impl_traits_for_reflection_tokens().to_tokens(into); } } @@ -619,7 +619,7 @@ impl Definition { /// [`WrappedType`]: juniper::macros::reflection::WrappedType /// [1]: https://spec.graphql.org/June2018/#sec-Unions #[must_use] - pub(crate) fn impl_traits_for_const_assertions(&self) -> TokenStream { + pub(crate) fn impl_traits_for_reflection_tokens(&self) -> TokenStream { let scalar = &self.scalar; let name = &self.name; let variants = self.variants.iter().map(|var| &var.ty); From 6cb5cef71ada1e7beb6cd8194a463ecf581c49be Mon Sep 17 00:00:00 2001 From: ilslv Date: Thu, 6 Jan 2022 12:27:38 +0300 Subject: [PATCH 45/53] WIP (Corrections) --- .../src/codegen/interface_attr.rs | 4 ++-- juniper/src/macros/reflection.rs | 5 ++++- juniper/src/types/scalars.rs | 6 +++--- juniper_codegen/src/derive_scalar_value.rs | 6 +++--- juniper_codegen/src/graphql_interface/attr.rs | 2 +- juniper_codegen/src/graphql_interface/mod.rs | 19 +++++++++++-------- juniper_codegen/src/impl_scalar.rs | 6 +++--- juniper_codegen/src/util/mod.rs | 12 ++++++------ 8 files changed, 33 insertions(+), 27 deletions(-) diff --git a/integration_tests/juniper_tests/src/codegen/interface_attr.rs b/integration_tests/juniper_tests/src/codegen/interface_attr.rs index 9666f430c..8303aeb50 100644 --- a/integration_tests/juniper_tests/src/codegen/interface_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/interface_attr.rs @@ -883,8 +883,8 @@ mod fallible_field { #[graphql_object(impl = CharacterValue)] impl Droid { - fn id(&self) -> String { - self.id.clone() + fn id(&self) -> Result { + Ok(self.id.clone()) } fn primary_function(&self) -> &str { diff --git a/juniper/src/macros/reflection.rs b/juniper/src/macros/reflection.rs index f6fb31bb9..709bbd865 100644 --- a/juniper/src/macros/reflection.rs +++ b/juniper/src/macros/reflection.rs @@ -210,9 +210,9 @@ impl + ?Sized> BaseSubTypes for Rc { /// assert_eq!(format_type!(TYPE_STRING, WRAP_VAL_STRING), "[String]"); /// ``` /// +/// [`VALUE`]: Self::VALUE /// [1]: https://spec.graphql.org/October2021/#sec-Objects /// [2]: https://spec.graphql.org/October2021/#sec-Wrapping-Types -/// [`VALUE`]: Self::VALUE pub trait WrappedType { /// [`WrappedValue`] of this type. const VALUE: WrappedValue; @@ -285,6 +285,9 @@ pub trait Fields { /// /// [1]: https://spec.graphql.org/October2021/#sec-Interfaces pub trait Implements { + /// [`Types`] of the [GraphQL interfaces][1] implemented by this type. + /// + /// [1]: https://spec.graphql.org/October2021/#sec-Interfaces const NAMES: Types; } diff --git a/juniper/src/types/scalars.rs b/juniper/src/types/scalars.rs index 87c293d7b..7e7603e79 100644 --- a/juniper/src/types/scalars.rs +++ b/juniper/src/types/scalars.rs @@ -204,15 +204,15 @@ where } impl reflection::WrappedType for str { - const VALUE: u128 = 1; + const VALUE: reflection::WrappedValue = 1; } impl reflection::BaseType for str { - const NAME: &'static str = "String"; + const NAME: reflection::Type = "String"; } impl reflection::BaseSubTypes for str { - const NAMES: &'static [&'static str] = &[>::NAME]; + const NAMES: reflection::Types = &[>::NAME]; } impl GraphQLType for str diff --git a/juniper_codegen/src/derive_scalar_value.rs b/juniper_codegen/src/derive_scalar_value.rs index 9e2698d74..1e25ce913 100644 --- a/juniper_codegen/src/derive_scalar_value.rs +++ b/juniper_codegen/src/derive_scalar_value.rs @@ -241,20 +241,20 @@ fn impl_scalar_struct( impl#impl_generics ::juniper::macros::reflection::BaseType<#scalar> for #ident where #scalar: ::juniper::ScalarValue, { - const NAME: &'static str = #name; + const NAME: ::juniper::macros::reflection::Type = #name; } impl#impl_generics ::juniper::macros::reflection::BaseSubTypes<#scalar> for #ident where #scalar: ::juniper::ScalarValue, { - const NAMES: &'static [&'static str] = + const NAMES: ::juniper::macros::reflection::Types = &[>::NAME]; } impl#impl_generics ::juniper::macros::reflection::WrappedType<#scalar> for #ident where #scalar: ::juniper::ScalarValue, { - const VALUE: u128 = 1; + const VALUE: ::juniper::macros::reflection::WrappedValue = 1; } ); diff --git a/juniper_codegen/src/graphql_interface/attr.rs b/juniper_codegen/src/graphql_interface/attr.rs index c2a504db4..a34704490 100644 --- a/juniper_codegen/src/graphql_interface/attr.rs +++ b/juniper_codegen/src/graphql_interface/attr.rs @@ -128,7 +128,7 @@ fn expand_on_trait( |c| format_ident!("{}Enum", c.inner().to_string()), ); - let description = attr.description.as_ref().map(|c| c.inner().clone()); + let description = attr.description.as_deref().cloned(); let generated_code = Definition { trait_generics: ast.generics.clone(), vis: ast.vis.clone(), diff --git a/juniper_codegen/src/graphql_interface/mod.rs b/juniper_codegen/src/graphql_interface/mod.rs index a26645d54..9c3ceef10 100644 --- a/juniper_codegen/src/graphql_interface/mod.rs +++ b/juniper_codegen/src/graphql_interface/mod.rs @@ -989,10 +989,11 @@ impl Definition { } /// Returns generated code for the [`GraphQLValue::concrete_type_name`][0] - /// method, which returns name of the underlying [`Implementer`] GraphQL - /// type contained in this [`EnumType`]. + /// method, which returns name of the underlying [`implementers`][1] GraphQL + /// type contained in this enum. /// /// [0]: juniper::GraphQLValue::concrete_type_name + /// [1]: Self::implementers #[must_use] fn method_concrete_type_name_tokens(&self) -> TokenStream { let scalar = &self.scalar; @@ -1024,10 +1025,11 @@ impl Definition { /// Returns generated code for the /// [`GraphQLValueAsync::resolve_into_type_async`][0] method, which - /// downcasts this [`EnumType`] into its underlying [`Implementer`] type + /// downcasts this enum into its underlying [`implementers`][1] type /// asynchronously. /// /// [0]: juniper::GraphQLValueAsync::resolve_into_type_async + /// [1]: Self::implementers #[must_use] fn method_resolve_into_type_async_tokens(&self) -> TokenStream { let resolving_code = gen::async_resolving_code(None); @@ -1056,10 +1058,11 @@ impl Definition { } /// Returns generated code for the [`GraphQLValue::resolve_into_type`][0] - /// method, which downcasts this [`EnumType`] into its underlying - /// [`Implementer`] type synchronously. + /// method, which downcasts this enum into its underlying + /// [`implementers`][1] type synchronously. /// /// [0]: juniper::GraphQLValue::resolve_into_type + /// [1]: Self::implementers #[must_use] fn method_resolve_into_type_tokens(&self) -> TokenStream { let resolving_code = gen::sync_resolving_code(); @@ -1125,7 +1128,7 @@ impl Definition { } /// Returns prepared [`syn::Generics`] for [`GraphQLType`] trait (and - /// similar) implementation of this [`EnumType`]. + /// similar) implementation of this enum. /// /// If `for_async` is `true`, then additional predicates are added to suit /// the [`GraphQLAsyncValue`] trait (and similar) requirements. @@ -1184,8 +1187,8 @@ impl Definition { generics } - /// Indicates whether this [`EnumType`] has non-exhaustive phantom variant - /// to hold type parameters. + /// Indicates whether this enum has non-exhaustive phantom variant to hold + /// type parameters. #[must_use] fn has_phantom_variant(&self) -> bool { !self.trait_generics.params.is_empty() diff --git a/juniper_codegen/src/impl_scalar.rs b/juniper_codegen/src/impl_scalar.rs index b28aabedc..880bbd246 100644 --- a/juniper_codegen/src/impl_scalar.rs +++ b/juniper_codegen/src/impl_scalar.rs @@ -331,20 +331,20 @@ pub fn build_scalar( impl#generic_type_decl ::juniper::macros::reflection::BaseType<#generic_type> for #impl_for_type #generic_type_bound { - const NAME: &'static str = #name; + const NAME:::juniper::macros::reflection::Type = #name; } impl#generic_type_decl ::juniper::macros::reflection::BaseSubTypes<#generic_type> for #impl_for_type #generic_type_bound { - const NAMES: &'static [&'static str] = + const NAMES: ::juniper::macros::reflection::Types = &[>::NAME]; } impl#generic_type_decl ::juniper::macros::reflection::WrappedType<#generic_type> for #impl_for_type #generic_type_bound { - const VALUE: u128 = 1; + const VALUE: ::juniper::macros::reflection::WrappedValue = 1; } ); diff --git a/juniper_codegen/src/util/mod.rs b/juniper_codegen/src/util/mod.rs index 1a301717c..38722918f 100644 --- a/juniper_codegen/src/util/mod.rs +++ b/juniper_codegen/src/util/mod.rs @@ -910,20 +910,20 @@ impl GraphQLTypeDefiniton { impl#impl_generics ::juniper::macros::reflection::BaseType<#scalar> for #ty #where_clause { - const NAME: &'static str = #name; + const NAME: ::juniper::macros::reflection::Type = #name; } impl#impl_generics ::juniper::macros::reflection::BaseSubTypes<#scalar> for #ty #where_clause { - const NAMES: &'static [&'static str] = + const NAMES: ::juniper::macros::reflection::Types = &[>::NAME]; } impl#impl_generics ::juniper::macros::reflection::WrappedType<#scalar> for #ty #where_clause { - const VALUE: u128 = 1; + const VALUE: ::juniper::macros::reflection::WrappedValue = 1; } ); @@ -1177,14 +1177,14 @@ impl GraphQLTypeDefiniton { for #ty #type_generics_tokens #where_clause { - const NAME: &'static str = #name; + const NAME: ::juniper::macros::reflection::Type = #name; } impl#impl_generics ::juniper::macros::reflection::BaseSubTypes<#scalar> for #ty #type_generics_tokens #where_clause { - const NAMES: &'static [&'static str] = + const NAMES: ::juniper::macros::reflection::Types = &[>::NAME]; } @@ -1192,7 +1192,7 @@ impl GraphQLTypeDefiniton { for #ty #type_generics_tokens #where_clause { - const VALUE: u128 = 1; + const VALUE: ::juniper::macros::reflection::WrappedValue = 1; } ); From 41a2dcf7faeefddd208991025fbee45968100be1 Mon Sep 17 00:00:00 2001 From: ilslv Date: Thu, 6 Jan 2022 12:46:11 +0300 Subject: [PATCH 46/53] WIP (CHANGELOG) --- juniper/CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/juniper/CHANGELOG.md b/juniper/CHANGELOG.md index 836472fb0..e85c18174 100644 --- a/juniper/CHANGELOG.md +++ b/juniper/CHANGELOG.md @@ -10,6 +10,12 @@ - Make `FromInputValue` methods fallible to allow post-validation. ([#987](https://github.com/graphql-rust/juniper/pull/987)) - Change `Option` to `Result` in `from_input_value()` return type of `#[graphql_scalar]` macro. ([#987](https://github.com/graphql-rust/juniper/pull/987)) - Forbid `__typename` field on `subscription` operations [accordingly to October 2021 spec](https://spec.graphql.org/October2021/#note-bc213). ([#1001](https://github.com/graphql-rust/juniper/pull/1001), [#1000](https://github.com/graphql-rust/juniper/pull/1000)) +- Redesign `#[graphql_interface]` macro. ([#1009](https://github.com/graphql-rust/juniper/pull/1009)): + - Remove support for `#[graphql_interface(dyn)]`. + - Describe all interface trait methods with type's fields or impl block instead of `#[graphql_interface]` attribute on `impl Trait`. + - Forbid default impls on non-skipped trait methods. + - Add support for additional nullable arguments on implementer. + - Add support for returning sub-type on implementer. ## Features From eb8c56d538cbfade79fb6750c9543d2c8b78b3a2 Mon Sep 17 00:00:00 2001 From: ilslv Date: Thu, 6 Jan 2022 12:57:12 +0300 Subject: [PATCH 47/53] WIP (Correction) --- juniper_codegen/src/impl_scalar.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/juniper_codegen/src/impl_scalar.rs b/juniper_codegen/src/impl_scalar.rs index 880bbd246..523e99e96 100644 --- a/juniper_codegen/src/impl_scalar.rs +++ b/juniper_codegen/src/impl_scalar.rs @@ -331,7 +331,7 @@ pub fn build_scalar( impl#generic_type_decl ::juniper::macros::reflection::BaseType<#generic_type> for #impl_for_type #generic_type_bound { - const NAME:::juniper::macros::reflection::Type = #name; + const NAME: ::juniper::macros::reflection::Type = #name; } impl#generic_type_decl ::juniper::macros::reflection::BaseSubTypes<#generic_type> for #impl_for_type From f5310379325a73e00d455d213b865a4c35347a3b Mon Sep 17 00:00:00 2001 From: tyranron Date: Tue, 11 Jan 2022 16:07:08 +0100 Subject: [PATCH 48/53] Some corrections [skip ci] --- .../src/codegen/interface_attr.rs | 129 +++++----- juniper/src/macros/mod.rs | 1 - juniper/src/macros/reflection.rs | 231 ++++++++++-------- juniper/src/tests/fixtures/starwars/schema.rs | 55 +---- juniper_hyper/src/lib.rs | 22 +- juniper_iron/src/lib.rs | 6 +- 6 files changed, 216 insertions(+), 228 deletions(-) diff --git a/integration_tests/juniper_tests/src/codegen/interface_attr.rs b/integration_tests/juniper_tests/src/codegen/interface_attr.rs index 8303aeb50..f7ea0a21b 100644 --- a/integration_tests/juniper_tests/src/codegen/interface_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/interface_attr.rs @@ -41,7 +41,7 @@ mod no_implers { struct QueryRoot; - #[graphql_object(scalar = S: ScalarValue + Send + Sync)] + #[graphql_object] impl QueryRoot { fn character(&self) -> CharacterValue { unimplemented!() @@ -50,48 +50,48 @@ mod no_implers { #[tokio::test] async fn is_graphql_interface() { - let schema = schema(QueryRoot); - - let doc = r#"{ + const DOC: &str = r#"{ __type(name: "Character") { kind } }"#; + let schema = schema(QueryRoot); + assert_eq!( - execute(&doc, None, &schema, &graphql_vars! {}, &()).await, + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok((graphql_value!({"__type": {"kind": "INTERFACE"}}), vec![])), ); } #[tokio::test] async fn uses_trait_name() { - let schema = schema(QueryRoot); - - let doc = r#"{ + const DOC: &str = r#"{ __type(name: "Character") { name } }"#; + let schema = schema(QueryRoot); + assert_eq!( - execute(&doc, None, &schema, &graphql_vars! {}, &()).await, + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok((graphql_value!({"__type": {"name": "Character"}}), vec![])), ); } #[tokio::test] async fn has_no_description() { - let schema = schema(QueryRoot); - - let doc = r#"{ + const DOC: &str = r#"{ __type(name: "Character") { description } }"#; + let schema = schema(QueryRoot); + assert_eq!( - execute(&doc, None, &schema, &graphql_vars! {}, &()).await, + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok((graphql_value!({"__type": {"description": null}}), vec![])), ); } @@ -134,7 +134,7 @@ mod trivial { Droid, } - #[graphql_object(scalar = S: ScalarValue + Send + Sync)] + #[graphql_object] impl QueryRoot { fn character(&self) -> CharacterValue { match self { @@ -153,7 +153,7 @@ mod trivial { } #[tokio::test] - async fn enum_resolves_human() { + async fn resolves_human() { const DOC: &str = r#"{ character { ... on Human { @@ -168,14 +168,17 @@ mod trivial { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( - graphql_value!({"character": {"humanId": "human-32", "homePlanet": "earth"}}), + graphql_value!({"character": { + "humanId": "human-32", + "homePlanet": "earth", + }}), vec![], )), ); } #[tokio::test] - async fn enum_resolves_droid() { + async fn resolves_droid() { const DOC: &str = r#"{ character { ... on Droid { @@ -190,14 +193,17 @@ mod trivial { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( - graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}), + graphql_value!({"character": { + "droidId": "droid-99", + "primaryFunction": "run", + }}), vec![], )), ); } #[tokio::test] - async fn enum_resolves_id_field() { + async fn resolves_id_field() { const DOC: &str = r#"{ character { id @@ -220,25 +226,23 @@ mod trivial { #[tokio::test] async fn is_graphql_interface() { - let schema = schema(QueryRoot::Human); - - let doc = r#"{ + const DOC: &str = r#"{ __type(name: "Character") { kind } }"#; + let schema = schema(QueryRoot::Human); + assert_eq!( - execute(&doc, None, &schema, &graphql_vars! {}, &()).await, + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok((graphql_value!({"__type": {"kind": "INTERFACE"}}), vec![])), ); } #[tokio::test] async fn registers_all_implementers() { - let schema = schema(QueryRoot::Human); - - let doc = r#"{ + const DOC: &str = r#"{ __type(name: "Character") { possibleTypes { kind @@ -247,8 +251,10 @@ mod trivial { } }"#; + let schema = schema(QueryRoot::Human); + assert_eq!( - execute(&doc, None, &schema, &graphql_vars! {}, &()).await, + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( graphql_value!({"__type": {"possibleTypes": [ {"kind": "OBJECT", "name": "Droid"}, @@ -290,32 +296,32 @@ mod trivial { #[tokio::test] async fn uses_trait_name() { - let schema = schema(QueryRoot::Human); - - let doc = r#"{ + const DOC: &str = r#"{ __type(name: "Character") { name } }"#; + let schema = schema(QueryRoot::Human); + assert_eq!( - execute(&doc, None, &schema, &graphql_vars! {}, &()).await, + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok((graphql_value!({"__type": {"name": "Character"}}), vec![])), ); } #[tokio::test] async fn has_no_description() { - let schema = schema(QueryRoot::Human); - - let doc = r#"{ + const DOC: &str = r#"{ __type(name: "Character") { description } }"#; + let schema = schema(QueryRoot::Human); + assert_eq!( - execute(&doc, None, &schema, &graphql_vars! {}, &()).await, + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok((graphql_value!({"__type": {"description": null}}), vec![])), ); } @@ -392,7 +398,10 @@ mod explicit_alias { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( - graphql_value!({"character": {"humanId": "human-32", "homePlanet": "earth"}}), + graphql_value!({"character": { + "humanId": "human-32", + "homePlanet": "earth", + }}), vec![], )), ); @@ -414,7 +423,10 @@ mod explicit_alias { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( - graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}), + graphql_value!({"character": { + "droidId": "droid-99", + "primaryFunction": "run", + }}), vec![], )), ); @@ -513,6 +525,7 @@ mod trivial_async { #[graphql_object(impl = CharacterValue)] impl Droid { + // FIXME: use async fn id(&self) -> &str { &self.id } @@ -528,7 +541,7 @@ mod trivial_async { Droid, } - #[graphql_object(scalar = S: ScalarValue + Send + Sync)] + #[graphql_object] impl QueryRoot { fn character(&self) -> CharacterValue { match self { @@ -547,7 +560,7 @@ mod trivial_async { } #[tokio::test] - async fn enum_resolves_human() { + async fn resolves_human() { const DOC: &str = r#"{ character { ... on Human { @@ -569,7 +582,7 @@ mod trivial_async { } #[tokio::test] - async fn enum_resolves_droid() { + async fn resolves_droid() { const DOC: &str = r#"{ character { ... on Droid { @@ -591,7 +604,7 @@ mod trivial_async { } #[tokio::test] - async fn enum_resolves_id_field() { + async fn resolves_id_field() { const DOC: &str = r#"{ character { id @@ -614,25 +627,23 @@ mod trivial_async { #[tokio::test] async fn is_graphql_interface() { - let schema = schema(QueryRoot::Human); - - let doc = r#"{ + const DOC: &str = r#"{ __type(name: "Character") { kind } }"#; + let schema = schema(QueryRoot::Human); + assert_eq!( - execute(&doc, None, &schema, &graphql_vars! {}, &()).await, + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok((graphql_value!({"__type": {"kind": "INTERFACE"}}), vec![])), ); } #[tokio::test] async fn registers_all_implementers() { - let schema = schema(QueryRoot::Human); - - let doc = r#"{ + const DOC: &str = r#"{ __type(name: "Character") { possibleTypes { kind @@ -641,8 +652,10 @@ mod trivial_async { } }"#; + let schema = schema(QueryRoot::Human); + assert_eq!( - execute(&doc, None, &schema, &graphql_vars! {}, &()).await, + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( graphql_value!({"__type": {"possibleTypes": [ {"kind": "OBJECT", "name": "Droid"}, @@ -684,32 +697,32 @@ mod trivial_async { #[tokio::test] async fn uses_trait_name() { - let schema = schema(QueryRoot::Human); - - let doc = r#"{ + const DOC: &str = r#"{ __type(name: "Character") { name } }"#; + let schema = schema(QueryRoot::Human); + assert_eq!( - execute(&doc, None, &schema, &graphql_vars! {}, &()).await, + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok((graphql_value!({"__type": {"name": "Character"}}), vec![])), ); } #[tokio::test] async fn has_no_description() { - let schema = schema(QueryRoot::Human); - - let doc = r#"{ + const DOC: &str = r#"{ __type(name: "Character") { description } }"#; + let schema = schema(QueryRoot::Human); + assert_eq!( - execute(&doc, None, &schema, &graphql_vars! {}, &()).await, + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok((graphql_value!({"__type": {"description": null}}), vec![])), ); } @@ -3588,7 +3601,7 @@ mod field_return_union_subtyping { } } -mod additional_nullable_argument { +mod nullable_argument_subtyping { use super::*; #[graphql_interface(for = [Human, Droid])] diff --git a/juniper/src/macros/mod.rs b/juniper/src/macros/mod.rs index 182bd2055..f9aa80771 100644 --- a/juniper/src/macros/mod.rs +++ b/juniper/src/macros/mod.rs @@ -1,7 +1,6 @@ //! Declarative macros and helper definitions for procedural macros. #[doc(hidden)] -#[macro_use] pub mod helper; #[doc(hidden)] #[macro_use] diff --git a/juniper/src/macros/reflection.rs b/juniper/src/macros/reflection.rs index 709bbd865..d91355a33 100644 --- a/juniper/src/macros/reflection.rs +++ b/juniper/src/macros/reflection.rs @@ -1,4 +1,5 @@ -//! Helper traits and macros for compile-time reflection. +//! Helper traits and macros for compile-time reflection of Rust types into +//! GraphQL types. use std::{rc::Rc, sync::Arc}; @@ -8,62 +9,39 @@ use crate::{ Arguments as FieldArguments, ExecutionResult, Executor, GraphQLValue, Nullable, ScalarValue, }; -/// Type alias for [GraphQL object][1], [scalar][2] or [interface][3] type name. +/// Alias for a [GraphQL object][1], [scalar][2] or [interface][3] type's name +/// in a GraphQL schema. +/// /// See [`BaseType`] for more info. /// -/// [1]: https://spec.graphql.org/October2021/#sec-Objects -/// [2]: https://spec.graphql.org/October2021/#sec-Scalars -/// [3]: https://spec.graphql.org/October2021/#sec-Interfaces +/// [1]: https://spec.graphql.org/October2021#sec-Objects +/// [2]: https://spec.graphql.org/October2021#sec-Scalars +/// [3]: https://spec.graphql.org/October2021#sec-Interfaces pub type Type = &'static str; -/// Type alias for slice of [`Type`]s. See [`BaseSubTypes`] for more info. -pub type Types = &'static [Type]; - -/// Type alias for [GraphQL object][1] or [interface][2] [field argument][3] -/// name. -/// -/// [1]: https://spec.graphql.org/October2021/#sec-Objects -/// [2]: https://spec.graphql.org/October2021/#sec-Interfaces -/// [3]: https://spec.graphql.org/October2021/#sec-Language.Arguments -pub type Name = &'static str; - -/// Type alias for slice of [`Name`]s. See [`Fields`] for more info. -pub type Names = &'static [Name]; - -/// Type alias for value of [`WrappedType`]. -pub type WrappedValue = u128; - -/// Type alias for [field argument][1]s [`Name`], [`Type`] and [`WrappedValue`]. +/// Alias for a slice of [`Type`]s. /// -/// [1]: https://spec.graphql.org/October2021/#sec-Language.Arguments -pub type Argument = (Name, Type, WrappedValue); +/// See [`BaseSubTypes`] for more info. +pub type Types = &'static [Type]; -/// Type alias for [field argument][1]s [`Name`], [`Type`] and -/// [`WrappedValue`]. +/// Naming of a [GraphQL object][1], [scalar][2] or [interface][3] [`Type`]. /// -/// [1]: https://spec.graphql.org/October2021/#sec-Language.Arguments -pub type Arguments = &'static [(Name, Type, WrappedValue)]; - -/// Type alias for constantly hashed [`Name`] for usage in const context. -pub type FieldName = u128; - -/// [GraphQL object][1], [scalar][2] or [interface][3] [`Type`] name. This trait -/// is transparent to the [`Option`], [`Vec`] and other containers, so to fully -/// represent [GraphQL object][1] we additionally use [`WrappedType`]. +/// This trait is transparent to [`Option`], [`Vec`] and other containers, so to +/// fully represent [GraphQL object][1] we additionally use [`WrappedType`]. /// -/// Different Rust type may have the same [`NAME`]. For example [`String`] and -/// &[`str`](prim@str). +/// Different Rust type may have the same [`NAME`]. For example, [`String`] and +/// `&`[`str`](prim@str) share `String!` GraphQL type. /// /// [`NAME`]: Self::NAME -/// [1]: https://spec.graphql.org/October2021/#sec-Objects -/// [2]: https://spec.graphql.org/October2021/#sec-Scalars -/// [3]: https://spec.graphql.org/October2021/#sec-Interfaces +/// [1]: https://spec.graphql.org/October2021#sec-Objects +/// [2]: https://spec.graphql.org/October2021#sec-Scalars +/// [3]: https://spec.graphql.org/October2021#sec-Interfaces pub trait BaseType { /// [`Type`] of the [GraphQL object][1], [scalar][2] or [interface][3]. /// - /// [1]: https://spec.graphql.org/October2021/#sec-Objects - /// [2]: https://spec.graphql.org/October2021/#sec-Scalars - /// [3]: https://spec.graphql.org/October2021/#sec-Interfaces + /// [1]: https://spec.graphql.org/October2021#sec-Objects + /// [2]: https://spec.graphql.org/October2021#sec-Scalars + /// [3]: https://spec.graphql.org/October2021#sec-Interfaces const NAME: Type; } @@ -115,16 +93,16 @@ impl + ?Sized> BaseType for Rc { const NAME: Type = T::NAME; } -/// [GraphQL object][1] [sub-types][2]. This trait is transparent to the -/// [`Option`], [`Vec`] and other containers. +/// [Sub-types][2] of a [GraphQL object][1]. /// -/// [1]: https://spec.graphql.org/October2021/#sec-Objects -/// [2]: https://spec.graphql.org/October2021/#sel-JAHZhCHCDEJDAAAEEFDBtzC +/// This trait is transparent to [`Option`], [`Vec`] and other containers. +/// +/// [1]: https://spec.graphql.org/October2021#sec-Objects +/// [2]: https://spec.graphql.org/October2021#sel-JAHZhCHCDEJDAAAEEFDBtzC pub trait BaseSubTypes { - /// [`Types`] for the [GraphQL object][1]s [sub-types][2]. + /// Sub-[`Types`] of the [GraphQL object][1]. /// - /// [1]: https://spec.graphql.org/October2021/#sec-Objects - /// [2]: https://spec.graphql.org/October2021/#sel-JAHZhCHCDEJDAAAEEFDBtzC + /// [1]: https://spec.graphql.org/October2021#sec-Objects const NAMES: Types; } @@ -176,19 +154,26 @@ impl + ?Sized> BaseSubTypes for Rc { const NAMES: Types = T::NAMES; } -/// To fully represent [GraphQL object][1] it's not enough to use [`Type`], -/// because of the [wrapping types][2]. To work around this we use -/// [`WrappedValue`] which is represented with [`u128`]. +/// Alias for a value of a [`WrappedType`] (combined GraphQL type). +pub type WrappedValue = u128; + +// TODO: Just use `&str`s once they're allowed in `const` generics. +/// Encoding of a composed GraphQL type in numbers. /// -/// - In base case of non-nullable [object] [`VALUE`] is `1`. +/// To fully represent a [GraphQL object][1] it's not enough to use [`Type`], +/// because of the [wrapping types][2]. To work around this we use a +/// [`WrappedValue`] which is represented via [`u128`]. +/// +/// - In base case of non-nullable [object][1] [`VALUE`] is `1`. /// - To represent nullability we "append" `2` to the [`VALUE`], so /// [`Option`]`<`[object][1]`>` has [`VALUE`] of `12`. /// - To represent list we "append" `3` to the [`VALUE`], so /// [`Vec`]`<`[object][1]`>` has [`VALUE`] of `13`. /// /// This approach allows us to uniquely represent any [GraphQL object][1] with -/// combination of [`Type`] and [`WrappedValue`] and even constantly format it -/// with [`format_type`] macro. +/// combination of [`Type`] and [`WrappedValue`] and even format it via +/// [`format_type!`] macro in `const` context. +/// /// /// # Examples /// @@ -211,8 +196,8 @@ impl + ?Sized> BaseSubTypes for Rc { /// ``` /// /// [`VALUE`]: Self::VALUE -/// [1]: https://spec.graphql.org/October2021/#sec-Objects -/// [2]: https://spec.graphql.org/October2021/#sec-Wrapping-Types +/// [1]: https://spec.graphql.org/October2021#sec-Objects +/// [2]: https://spec.graphql.org/October2021#sec-Wrapping-Types pub trait WrappedType { /// [`WrappedValue`] of this type. const VALUE: WrappedValue; @@ -266,6 +251,34 @@ impl + ?Sized> WrappedType for Rc { const VALUE: u128 = T::VALUE; } +/// Alias for a [GraphQL object][1] or [interface][2] [field argument][3] name. +/// +/// See [`Fields`] for more info. +/// +/// [1]: https://spec.graphql.org/October2021#sec-Objects +/// [2]: https://spec.graphql.org/October2021#sec-Interfaces +/// [3]: https://spec.graphql.org/October2021#sec-Language.Arguments +pub type Name = &'static str; + +/// Alias for a slice of [`Name`]s. +/// +/// See [`Fields`] for more info. +pub type Names = &'static [Name]; + +/// Alias for [field argument][1]s [`Name`], [`Type`] and [`WrappedValue`]. +/// +/// [1]: https://spec.graphql.org/October2021#sec-Language.Arguments +pub type Argument = (Name, Type, WrappedValue); + +/// Alias for a slice of [field argument][1]s [`Name`], [`Type`] and +/// [`WrappedValue`]. +/// +/// [1]: https://spec.graphql.org/October2021#sec-Language.Arguments +pub type Arguments = &'static [(Name, Type, WrappedValue)]; + +/// Alias for a `const`-hashed [`Name`] used in `const` context. +pub type FieldName = u128; + /// [GraphQL object][1] or [interface][2] [field arguments][3] [`Names`]. /// /// [1]: https://spec.graphql.org/October2021/#sec-Objects @@ -291,7 +304,7 @@ pub trait Implements { const NAMES: Types; } -/// Stores meta information of [GraphQL field][1]: +/// Stores meta information of a [GraphQL field][1]: /// - [`Context`] and [`TypeInfo`]. /// - Return type's [`TYPE`], [`SUB_TYPES`] and [`WRAPPED_VALUE`]. /// - [`ARGUMENTS`]. @@ -379,10 +392,11 @@ pub trait AsyncField: FieldMeta { ) -> BoxFuture<'b, ExecutionResult>; } -/// Non-cryptographic hash with good dispersion to use [`str`](prim@str) in -/// const generics. See [spec] for more info. +/// Non-cryptographic hash with good dispersion to use as a [`str`](prim@str) in +/// `const` generics. See [spec] for more info. /// /// [spec]: https://datatracker.ietf.org/doc/html/draft-eastlake-fnv-17.html +#[must_use] pub const fn fnv1a128(str: Name) -> u128 { const FNV_OFFSET_BASIS: u128 = 0x6c62272e07bb014262b821756295c58d; const FNV_PRIME: u128 = 0x0000000001000000000000000000013b; @@ -398,49 +412,51 @@ pub const fn fnv1a128(str: Name) -> u128 { hash } -/// Length of the [`format_type`] macro result __in bytes__. -pub const fn type_len_with_wrapped_val(ty: Type, v: WrappedValue) -> usize { +/// Length __in bytes__ of the [`format_type`] macro result. +#[must_use] +pub const fn type_len_with_wrapped_val(ty: Type, val: WrappedValue) -> usize { let mut len = ty.as_bytes().len() + "!".as_bytes().len(); // Type! - let mut current_wrap_val = v; - while current_wrap_val % 10 != 0 { - match current_wrap_val % 10 { + let mut curr = val; + while curr % 10 != 0 { + match curr % 10 { 2 => len -= "!".as_bytes().len(), // remove ! 3 => len += "[]!".as_bytes().len(), // [Type]! _ => {} } - - current_wrap_val /= 10; + curr /= 10; } len } -/// Based on the [`WrappedValue`] checks whether GraphQL [`objects`][1] can be -/// subtypes. +/// Checks whether GraphQL [`objects`][1] can be sub-types, based on the +/// [`WrappedValue`]. /// -/// To fully determine sub-typing relation [`Type`] should be one of the +/// To fully determine the sub-typing relation [`Type`] should be one of the /// [`BaseSubTypes::NAMES`]. /// -/// [1]: https://spec.graphql.org/October2021/#sec-Objects +/// [1]: https://spec.graphql.org/October2021#sec-Objects +#[must_use] pub const fn can_be_subtype(ty: WrappedValue, subtype: WrappedValue) -> bool { - let ty_current = ty % 10; - let subtype_current = subtype % 10; + let ty_curr = ty % 10; + let sub_curr = subtype % 10; - if ty_current == subtype_current { - if ty_current == 1 { + if ty_curr == sub_curr { + if ty_curr == 1 { true } else { can_be_subtype(ty / 10, subtype / 10) } - } else if ty_current == 2 { + } else if ty_curr == 2 { can_be_subtype(ty / 10, subtype) } else { false } } -/// Checks whether `val` exists in `arr`. +/// Checks whether the given `val` exists in the given `arr`. +#[must_use] pub const fn str_exists_in_arr(val: &str, arr: &[&str]) -> bool { let mut i = 0; while i < arr.len() { @@ -452,7 +468,7 @@ pub const fn str_exists_in_arr(val: &str, arr: &[&str]) -> bool { false } -/// Compares strings in `const` context. +/// Compares strings in a `const` context. /// /// As there is no `const impl Trait` and `l == r` calls [`Eq`], we have to /// write custom comparison function. @@ -477,8 +493,8 @@ pub const fn str_eq(l: &str, r: &str) -> bool { true } -/// Asserts that `#[graphql_interface(for = ...)]` has all types referencing -/// this interface in `impl = ...` attribute section. +/// Asserts that `#[graphql_interface(for = ...)]` has all the types referencing +/// this interface in the `impl = ...` attribute argument. #[macro_export] macro_rules! assert_implemented_for { ($scalar: ty, $implementor: ty $(, $interfaces: ty)* $(,)?) => { @@ -503,8 +519,8 @@ macro_rules! assert_implemented_for { }; } -/// Asserts that `impl = ...` attribute section has all types referencing this -/// type in `#[graphql_interface(for = ...)]`. +/// Asserts that `impl = ...` attribute argument has all the types referencing +/// this GraphQL type in `#[graphql_interface(for = ...)]`. #[macro_export] macro_rules! assert_interfaces_impls { ($scalar: ty, $interface: ty $(, $implementers: ty)* $(,)?) => { @@ -529,11 +545,14 @@ macro_rules! assert_interfaces_impls { }; } -/// Asserts validness of the [`Field`] [`Arguments`] and return [`Type`]. This -/// assertion is a combination of [`assert_subtype`] and [`assert_field_args`]. +/// Asserts validness of [`Field`] [`Arguments`] and returned [`Type`]. +/// +/// This assertion is a combination of [`assert_subtype`] and +/// [`assert_field_args`]. +/// /// See [spec][1] for more info. /// -/// [1]: https://spec.graphql.org/October2021/#IsValidImplementation() +/// [1]: https://spec.graphql.org/October2021#IsValidImplementation() #[macro_export] macro_rules! assert_field { ( @@ -547,10 +566,11 @@ macro_rules! assert_field { }; } -/// Asserts validness of the [`Field`]s return type. See [spec][1] for more -/// info. +/// Asserts validness of a [`Field`] return type. /// -/// [1]: https://spec.graphql.org/October2021/#IsValidImplementationFieldType() +/// See [spec][1] for more info. +/// +/// [1]: https://spec.graphql.org/October2021#IsValidImplementationFieldType() #[macro_export] macro_rules! assert_subtype { ( @@ -805,7 +825,7 @@ macro_rules! assert_field_args { }; } -/// Concatenates const [`str`](prim@str)s in const context. +/// Concatenates `const` [`str`](prim@str)s in a `const` context. #[macro_export] macro_rules! const_concat { ($($s:expr),* $(,)?) => {{ @@ -827,15 +847,16 @@ macro_rules! const_concat { } const CON: [u8; LEN] = concat([$($s),*]); - // SAFETY: this is safe, as we concatenate multiple UTF-8 strings one - // after the other byte by byte. + // TODO: Use `str::from_utf8()` once it becomes `const`. + // SAFETY: This is safe, as we concatenate multiple UTF-8 strings one + // after another byte-by-byte. #[allow(unsafe_code)] unsafe { ::std::str::from_utf8_unchecked(&CON) } }}; } -/// Before executing [`fnv1a128`] checks whether `impl_ty` has corresponding -/// [`Field`] impl and panics with understandable message. +/// Ensures that the given `$impl_ty` implements [`Field`] and returns a +/// [`fnv1a128`] hash for it, otherwise panics with understandable message. #[macro_export] macro_rules! checked_hash { ($field_name: expr, $impl_ty: ty, $scalar: ty $(, $prefix: expr)? $(,)?) => {{ @@ -859,7 +880,8 @@ macro_rules! checked_hash { }}; } -/// Formats [`Type`] and [`WrappedValue`] into GraphQL type. +/// Formats the given [`Type`] and [`WrappedValue`] into a readable GraphQL type +/// name. /// /// # Examples /// @@ -893,9 +915,9 @@ macro_rules! format_type { let mut is_null = false; while current_wrap_val % 10 != 0 { match current_wrap_val % 10 { - 2 => is_null = true, // Skips writing BANG later. + 2 => is_null = true, // Skips writing `BANG` later. 3 => { - // Write OPENING_BRACKET at current_start. + // Write `OPENING_BRACKET` at `current_start`. let mut i = 0; while i < OPENING_BRACKET.as_bytes().len() { type_arr[current_start + i] = OPENING_BRACKET.as_bytes()[i]; @@ -903,7 +925,7 @@ macro_rules! format_type { } current_start += i; if !is_null { - // Write BANG at current_end. + // Write `BANG` at `current_end`. i = 0; while i < BANG.as_bytes().len() { type_arr[current_end - BANG.as_bytes().len() + i + 1] = @@ -912,7 +934,7 @@ macro_rules! format_type { } current_end -= i; } - // Write CLOSING_BRACKET at current_end. + // Write `CLOSING_BRACKET` at `current_end`. i = 0; while i < CLOSING_BRACKET.as_bytes().len() { type_arr[current_end - CLOSING_BRACKET.as_bytes().len() + i + 1] = @@ -928,7 +950,7 @@ macro_rules! format_type { current_wrap_val /= 10; } - // Writes Type at current_start. + // Writes `Type` at `current_start`. let mut i = 0; while i < ty.as_bytes().len() { type_arr[current_start + i] = ty.as_bytes()[i]; @@ -936,7 +958,7 @@ macro_rules! format_type { } i = 0; if !is_null { - // Writes BANG at current_end. + // Writes `BANG` at `current_end`. while i < BANG.as_bytes().len() { type_arr[current_end - BANG.as_bytes().len() + i + 1] = BANG.as_bytes()[i]; i += 1; @@ -948,8 +970,9 @@ macro_rules! format_type { const TYPE_ARR: [u8; RES_LEN] = format_type_arr(); - // SAFETY: this is safe, as we concatenate multiple UTF-8 strings one - // after the other byte by byte. + // TODO: Use `str::from_utf8()` once it becomes `const`. + // SAFETY: This is safe, as we concatenate multiple UTF-8 strings one + // after another byte-by-byte. #[allow(unsafe_code)] const TYPE_FORMATTED: &str = unsafe { ::std::str::from_utf8_unchecked(TYPE_ARR.as_slice()) }; diff --git a/juniper/src/tests/fixtures/starwars/schema.rs b/juniper/src/tests/fixtures/starwars/schema.rs index 233e2f347..9e18ea268 100644 --- a/juniper/src/tests/fixtures/starwars/schema.rs +++ b/juniper/src/tests/fixtures/starwars/schema.rs @@ -118,7 +118,7 @@ impl Human { /// The friends of the human fn friends(&self, ctx: &Database) -> Vec { - ctx.get_friends(self) + ctx.get_friends(&self.friend_ids) } /// Which movies they appear in @@ -132,28 +132,6 @@ impl Human { } } -impl Character for Human { - fn id(&self) -> &str { - &self.id - } - - fn name(&self) -> Option<&str> { - Some(&self.name) - } - - fn friends(&self, ctx: &Database) -> Vec { - ctx.get_friends(self) - } - - fn appears_in(&self) -> &[Episode] { - &self.appears_in - } - - fn friends_ids(&self) -> &[String] { - &self.friend_ids - } -} - #[derive(Clone)] pub struct Droid { id: String, @@ -200,7 +178,7 @@ impl Droid { /// The friends of the droid fn friends(&self, ctx: &Database) -> Vec { - ctx.get_friends(self) + ctx.get_friends(&self.friend_ids) } /// Which movies they appear in @@ -214,28 +192,6 @@ impl Droid { } } -impl Character for Droid { - fn id(&self) -> &str { - &self.id - } - - fn name(&self) -> Option<&str> { - Some(&self.name) - } - - fn friends(&self, ctx: &Database) -> Vec { - ctx.get_friends(self) - } - - fn appears_in(&self) -> &[Episode] { - &self.appears_in - } - - fn friends_ids(&self) -> &[String] { - &self.friend_ids - } -} - #[derive(Default, Clone)] pub struct Database { humans: HashMap, @@ -363,10 +319,7 @@ impl Database { } } - pub fn get_friends(&self, c: &dyn Character) -> Vec { - c.friends_ids() - .iter() - .flat_map(|id| self.get_character(id)) - .collect() + pub fn get_friends(&self, ids: &[String]) -> Vec { + ids.iter().flat_map(|id| self.get_character(id)).collect() } } diff --git a/juniper_hyper/src/lib.rs b/juniper_hyper/src/lib.rs index 50ccff197..d46df4527 100644 --- a/juniper_hyper/src/lib.rs +++ b/juniper_hyper/src/lib.rs @@ -287,23 +287,23 @@ enum GraphQLRequestError { impl fmt::Display for GraphQLRequestError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - GraphQLRequestError::BodyHyper(ref err) => fmt::Display::fmt(err, f), - GraphQLRequestError::BodyUtf8(ref err) => fmt::Display::fmt(err, f), - GraphQLRequestError::BodyJSONError(ref err) => fmt::Display::fmt(err, f), - GraphQLRequestError::Variables(ref err) => fmt::Display::fmt(err, f), - GraphQLRequestError::Invalid(ref err) => fmt::Display::fmt(err, f), + match self { + GraphQLRequestError::BodyHyper(err) => fmt::Display::fmt(err, f), + GraphQLRequestError::BodyUtf8(err) => fmt::Display::fmt(err, f), + GraphQLRequestError::BodyJSONError(err) => fmt::Display::fmt(err, f), + GraphQLRequestError::Variables(err) => fmt::Display::fmt(err, f), + GraphQLRequestError::Invalid(err) => fmt::Display::fmt(err, f), } } } impl Error for GraphQLRequestError { fn source(&self) -> Option<&(dyn Error + 'static)> { - match *self { - GraphQLRequestError::BodyHyper(ref err) => Some(err), - GraphQLRequestError::BodyUtf8(ref err) => Some(err), - GraphQLRequestError::BodyJSONError(ref err) => Some(err), - GraphQLRequestError::Variables(ref err) => Some(err), + match self { + GraphQLRequestError::BodyHyper(err) => Some(err), + GraphQLRequestError::BodyUtf8(err) => Some(err), + GraphQLRequestError::BodyJSONError(err) => Some(err), + GraphQLRequestError::Variables(err) => Some(err), GraphQLRequestError::Invalid(_) => None, } } diff --git a/juniper_iron/src/lib.rs b/juniper_iron/src/lib.rs index 40d1f8039..cbef5ace0 100644 --- a/juniper_iron/src/lib.rs +++ b/juniper_iron/src/lib.rs @@ -370,9 +370,9 @@ enum GraphQLIronError { impl fmt::Display for GraphQLIronError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - GraphQLIronError::Serde(ref err) => fmt::Display::fmt(err, f), - GraphQLIronError::Url(ref err) => fmt::Display::fmt(err, f), + match self { + GraphQLIronError::Serde(err) => fmt::Display::fmt(err, f), + GraphQLIronError::Url(err) => fmt::Display::fmt(err, f), GraphQLIronError::InvalidData(err) => fmt::Display::fmt(err, f), } } From a2287dc6853e008ebe7e0ae131a476a4004c797a Mon Sep 17 00:00:00 2001 From: ilslv Date: Wed, 12 Jan 2022 12:24:57 +0300 Subject: [PATCH 49/53] Minor tests corrections --- .../src/codegen/interface_attr.rs | 82 +++++++++---------- 1 file changed, 39 insertions(+), 43 deletions(-) diff --git a/integration_tests/juniper_tests/src/codegen/interface_attr.rs b/integration_tests/juniper_tests/src/codegen/interface_attr.rs index f7ea0a21b..0f31b1aa6 100644 --- a/integration_tests/juniper_tests/src/codegen/interface_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/interface_attr.rs @@ -772,7 +772,7 @@ mod explicit_async { Droid, } - #[graphql_object(scalar = S: ScalarValue + Send + Sync)] + #[graphql_object] impl QueryRoot { fn character(&self) -> CharacterValue { match self { @@ -911,7 +911,7 @@ mod fallible_field { Droid, } - #[graphql_object(scalar = S: ScalarValue + Send + Sync)] + #[graphql_object] impl QueryRoot { fn character(&self) -> CharacterValue { match self { @@ -999,7 +999,7 @@ mod fallible_field { async fn has_correct_graphql_type() { let schema = schema(QueryRoot::Human); - let doc = r#"{ + const DOC: &str = r#"{ __type(name: "Character") { name kind @@ -1016,7 +1016,7 @@ mod fallible_field { }"#; assert_eq!( - execute(&doc, None, &schema, &graphql_vars! {}, &()).await, + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( graphql_value!({"__type": { "name": "Character", @@ -1069,7 +1069,7 @@ mod generic { Droid, } - #[graphql_object(scalar = S: ScalarValue + Send + Sync)] + #[graphql_object] impl QueryRoot { fn character(&self) -> CharacterValue { match self { @@ -1155,7 +1155,7 @@ mod generic { #[tokio::test] async fn uses_trait_name_without_type_params() { - let doc = r#"{ + const DOC: &str = r#"{ __type(name: "Character") { name } @@ -1164,7 +1164,7 @@ mod generic { let schema = schema(QueryRoot::Human); assert_eq!( - execute(&doc, None, &schema, &graphql_vars! {}, &()).await, + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok((graphql_value!({"__type": {"name": "Character"}}), vec![])), ); } @@ -1207,7 +1207,7 @@ mod generic_async { Droid, } - #[graphql_object(scalar = S: ScalarValue + Send + Sync)] + #[graphql_object] impl QueryRoot { fn character(&self) -> CharacterValue { match self { @@ -1293,7 +1293,7 @@ mod generic_async { #[tokio::test] async fn uses_trait_name_without_type_params() { - let doc = r#"{ + const DOC: &str = r#"{ __type(name: "Character") { name } @@ -1302,7 +1302,7 @@ mod generic_async { let schema = schema(QueryRoot::Human); assert_eq!( - execute(&doc, None, &schema, &graphql_vars! {}, &()).await, + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok((graphql_value!({"__type": {"name": "Character"}}), vec![])), ); } @@ -1345,7 +1345,7 @@ mod generic_lifetime_async { Droid, } - #[graphql_object(scalar = S: ScalarValue + Send + Sync)] + #[graphql_object] impl QueryRoot { fn character(&self) -> CharacterValue<'_, ()> { match self { @@ -1431,7 +1431,7 @@ mod generic_lifetime_async { #[tokio::test] async fn uses_trait_name_without_type_params() { - let doc = r#"{ + const DOC: &str = r#"{ __type(name: "Character") { name } @@ -1440,7 +1440,7 @@ mod generic_lifetime_async { let schema = schema(QueryRoot::Human); assert_eq!( - execute(&doc, None, &schema, &graphql_vars! {}, &()).await, + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok((graphql_value!({"__type": {"name": "Character"}}), vec![])), ); } @@ -1490,7 +1490,7 @@ mod argument { struct QueryRoot; - #[graphql_object(scalar = S: ScalarValue + Send + Sync)] + #[graphql_object] impl QueryRoot { fn character(&self) -> CharacterValue { Human { @@ -1534,7 +1534,7 @@ mod argument { async fn camelcases_name() { let schema = schema(QueryRoot); - let doc = r#"{ + const DOC: &str = r#"{ __type(name: "Character") { fields { name @@ -1545,7 +1545,7 @@ mod argument { } }"#; assert_eq!( - execute(&doc, None, &schema, &graphql_vars! {}, &()).await, + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( graphql_value!({"__type": {"fields": [{ "name": "idWide", @@ -1568,7 +1568,7 @@ mod argument { async fn has_no_description() { let schema = schema(QueryRoot); - let doc = r#"{ + const DOC: &str = r#"{ __type(name: "Character") { fields { args { @@ -1579,7 +1579,7 @@ mod argument { }"#; assert_eq!( - execute(&doc, None, &schema, &graphql_vars! {}, &()).await, + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( graphql_value!({"__type": {"fields": [ {"args": [{"description": null}]}, @@ -1594,7 +1594,7 @@ mod argument { async fn has_no_defaults() { let schema = schema(QueryRoot); - let doc = r#"{ + const DOC: &str = r#"{ __type(name: "Character") { fields { args { @@ -1605,7 +1605,7 @@ mod argument { }"#; assert_eq!( - execute(&doc, None, &schema, &graphql_vars! {}, &()).await, + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( graphql_value!({"__type": {"fields": [ {"args": [{"defaultValue": null}]}, @@ -2482,7 +2482,7 @@ mod explicit_generic_scalar { } #[derive(GraphQLObject)] - #[graphql(impl = CharacterValue<__S>)] + #[graphql(scalar = S: ScalarValue, impl = CharacterValue)] struct Human { id: String, home_planet: String, @@ -2510,7 +2510,7 @@ mod explicit_generic_scalar { Droid, } - #[graphql_object(scalar = S: ScalarValue + Send + Sync)] + #[graphql_object(scalar = S: ScalarValue)] impl QueryRoot { fn character(&self) -> CharacterValue { match self { @@ -2632,7 +2632,7 @@ mod bounded_generic_scalar { Droid, } - #[graphql_object(scalar = S: ScalarValue + Clone + Send + Sync)] + #[graphql_object] impl QueryRoot { fn character(&self) -> CharacterValue { match self { @@ -2787,7 +2787,7 @@ mod explicit_custom_context { Droid, } - #[graphql_object(context = CustomContext, scalar = S: ScalarValue + Send + Sync)] + #[graphql_object(context = CustomContext)] impl QueryRoot { fn character(&self) -> CharacterValue { match self { @@ -2851,7 +2851,7 @@ mod explicit_custom_context { #[tokio::test] async fn resolves_fields() { - let doc = r#"{ + const DOC: &str = r#"{ character { id info @@ -2869,7 +2869,7 @@ mod explicit_custom_context { let expected_info: &str = *expected_info; let expexted_more: &str = *expexted_more; assert_eq!( - execute(&doc, None, &schema, &graphql_vars! {}, &CustomContext).await, + execute(DOC, None, &schema, &graphql_vars! {}, &CustomContext).await, Ok(( graphql_value!({"character": { "id": expected_id, @@ -2941,7 +2941,7 @@ mod inferred_custom_context_from_field { Droid, } - #[graphql_object(context = CustomContext, scalar = S: ScalarValue + Send + Sync)] + #[graphql_object(context = CustomContext)] impl QueryRoot { fn character(&self) -> CharacterValue { match self { @@ -3005,7 +3005,7 @@ mod inferred_custom_context_from_field { #[tokio::test] async fn resolves_fields() { - let doc = r#"{ + const DOC: &str = r#"{ character { id info @@ -3022,7 +3022,7 @@ mod inferred_custom_context_from_field { let expected_id: &str = *expected_id; let expected_info: &str = *expected_info; assert_eq!( - execute(&doc, None, &schema, &graphql_vars! {}, &ctx).await, + execute(DOC, None, &schema, &graphql_vars! {}, &ctx).await, Ok(( graphql_value!({"character": { "id": expected_id, @@ -3042,24 +3042,20 @@ mod executor { #[graphql_interface(for = [Human, Droid], scalar = S)] trait Character { - async fn id<'a>(&self, executor: &'a Executor<'_, '_, (), S>) -> &'a str - where - S: Send + Sync; + async fn id<'a>(&self, executor: &'a Executor<'_, '_, (), S>) -> &'a str; async fn info<'b>( &'b self, arg: Option, #[graphql(executor)] another: &Executor<'_, '_, (), S>, - ) -> &'b str - where - S: Send + Sync; + ) -> &'b str; } struct Human { home_planet: String, } - #[graphql_object(impl = CharacterValue<__S>)] + #[graphql_object(scalar = S: ScalarValue, impl = CharacterValue)] impl Human { async fn id<'a, S: ScalarValue>(&self, executor: &'a Executor<'_, '_, (), S>) -> &'a str { executor.look_ahead().field_name() @@ -3165,7 +3161,7 @@ mod executor { #[tokio::test] async fn resolves_fields() { - let doc = r#"{ + const DOC: &str = r#"{ character { id info @@ -3177,7 +3173,7 @@ mod executor { let expected_info: &str = *expected_info; assert_eq!( - execute(&doc, None, &schema, &graphql_vars! {}, &()).await, + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( graphql_value!({"character": {"id": "id", "info": expected_info}}), vec![], @@ -3188,7 +3184,7 @@ mod executor { #[tokio::test] async fn not_arg() { - let doc = r#"{ + const DOC: &str = r#"{ __type(name: "Character") { fields { name @@ -3202,7 +3198,7 @@ mod executor { let schema = schema(QueryRoot::Human); assert_eq!( - execute(&doc, None, &schema, &graphql_vars! {}, &()).await, + execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( graphql_value!({"__type": {"fields": [ {"name": "id", "args": []}, @@ -3347,7 +3343,7 @@ mod field_return_subtyping { Droid, } - #[graphql_object(scalar = S: ScalarValue + Send + Sync)] + #[graphql_object] impl QueryRoot { fn character(&self) -> CharacterValue { match self { @@ -3496,7 +3492,7 @@ mod field_return_union_subtyping { Droid, } - #[graphql_object(scalar = S: ScalarValue + Send + Sync)] + #[graphql_object] impl QueryRoot { fn character(&self) -> CharacterValue { match self { @@ -3641,7 +3637,7 @@ mod nullable_argument_subtyping { Droid, } - #[graphql_object(scalar = S: ScalarValue + Send + Sync)] + #[graphql_object] impl QueryRoot { fn character(&self) -> CharacterValue { match self { From ceb2cf6e94ed04106e986c233e2faf12a2864818 Mon Sep 17 00:00:00 2001 From: ilslv Date: Wed, 12 Jan 2022 15:31:10 +0300 Subject: [PATCH 50/53] Don't inject `#[async_trait]` in case some trait methods are async --- docs/book/content/types/interfaces.md | 10 +- .../src/codegen/interface_attr.rs | 453 +----------------- juniper/src/macros/reflection.rs | 117 +++++ juniper_codegen/src/graphql_interface/attr.rs | 34 +- juniper_codegen/src/graphql_interface/mod.rs | 41 +- juniper_codegen/src/lib.rs | 10 +- 6 files changed, 150 insertions(+), 515 deletions(-) diff --git a/docs/book/content/types/interfaces.md b/docs/book/content/types/interfaces.md index a7d723ae1..3b0698880 100644 --- a/docs/book/content/types/interfaces.md +++ b/docs/book/content/types/interfaces.md @@ -213,17 +213,13 @@ use juniper::{graphql_interface, graphql_object, Executor, LookAheadMethods as _ trait Character { // If a field argument is named `executor`, it's automatically assumed // as an executor argument. - async fn id<'a>(&self, executor: &'a Executor<'_, '_, (), S>) -> &'a str - where - S: Send + Sync; // required by `#[async_trait]` transformation ¯\_(ツ)_/¯ + fn id<'a>(&self, executor: &'a Executor<'_, '_, (), S>) -> &'a str; // Otherwise, you may mark it explicitly as an executor argument. - async fn name<'b>( + fn name<'b>( &'b self, #[graphql(executor)] another: &Executor<'_, '_, (), S>, - ) -> &'b str - where - S: Send + Sync; + ) -> &'b str; fn home_planet(&self) -> &str; } diff --git a/integration_tests/juniper_tests/src/codegen/interface_attr.rs b/integration_tests/juniper_tests/src/codegen/interface_attr.rs index 0f31b1aa6..a1a11c195 100644 --- a/integration_tests/juniper_tests/src/codegen/interface_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/interface_attr.rs @@ -119,7 +119,7 @@ mod trivial { #[graphql_object(impl = CharacterValue)] impl Droid { - fn id(&self) -> &str { + async fn id(&self) -> &str { &self.id } @@ -508,7 +508,7 @@ mod trivial_async { #[graphql_interface(for = [Human, Droid])] trait Character { - async fn id(&self) -> &str; + fn id(&self) -> &str; } #[derive(GraphQLObject)] @@ -525,8 +525,7 @@ mod trivial_async { #[graphql_object(impl = CharacterValue)] impl Droid { - // FIXME: use async - fn id(&self) -> &str { + async fn id(&self) -> &str { &self.id } @@ -728,144 +727,6 @@ mod trivial_async { } } -mod explicit_async { - use super::*; - - #[graphql_interface(for = [Human, Droid])] - trait Character { - fn id(&self) -> &str; - - async fn info(&self) -> String; - } - - #[derive(GraphQLObject)] - #[graphql(impl = CharacterValue)] - struct Human { - id: String, - info: String, - home_planet: String, - } - - struct Droid { - id: String, - primary_function: String, - } - - #[graphql_object(impl = CharacterValue)] - impl Droid { - fn id(&self) -> &str { - &self.id - } - - async fn info(&self) -> String { - format!("Primary function is {}", &self.primary_function) - } - - fn primary_function(&self) -> &str { - &self.primary_function - } - } - - #[derive(Clone, Copy)] - enum QueryRoot { - Human, - Droid, - } - - #[graphql_object] - impl QueryRoot { - fn character(&self) -> CharacterValue { - match self { - Self::Human => Human { - id: "human-32".to_string(), - info: "Home planet is earth".to_string(), - home_planet: "earth".to_string(), - } - .into(), - Self::Droid => Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), - } - .into(), - } - } - } - - #[tokio::test] - async fn enum_resolves_human() { - const DOC: &str = r#"{ - character { - ... on Human { - humanId: id - homePlanet - } - } - }"#; - - let schema = schema(QueryRoot::Human); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"character": {"humanId": "human-32", "homePlanet": "earth"}}), - vec![], - )), - ); - } - - #[tokio::test] - async fn enum_resolves_droid() { - const DOC: &str = r#"{ - character { - ... on Droid { - droidId: id - primaryFunction - } - } - }"#; - - let schema = schema(QueryRoot::Droid); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}), - vec![], - )), - ); - } - - #[tokio::test] - async fn enum_resolves_fields() { - const DOC: &str = r#"{ - character { - id - info - } - }"#; - - for (root, expected_id, expected_info) in &[ - (QueryRoot::Human, "human-32", "Home planet is earth"), - (QueryRoot::Droid, "droid-99", "Primary function is run"), - ] { - let schema = schema(*root); - - let expected_id: &str = *expected_id; - let expected_info: &str = *expected_info; - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"character": { - "id": expected_id, - "info": expected_info, - }}), - vec![], - )), - ); - } - } -} - mod fallible_field { use super::*; @@ -1170,282 +1031,6 @@ mod generic { } } -mod generic_async { - use super::*; - - #[graphql_interface(for = [Human, Droid])] - trait Character { - async fn id(&self) -> &str; - } - - #[derive(GraphQLObject)] - #[graphql(impl = CharacterValue)] - struct Human { - id: String, - home_planet: String, - } - - struct Droid { - id: String, - primary_function: String, - } - - #[graphql_object(impl = CharacterValue<(), u8>)] - impl Droid { - fn id(&self) -> &str { - &self.id - } - - fn primary_function(&self) -> &str { - &self.primary_function - } - } - - #[derive(Clone, Copy)] - enum QueryRoot { - Human, - Droid, - } - - #[graphql_object] - impl QueryRoot { - fn character(&self) -> CharacterValue { - match self { - Self::Human => Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), - } - .into(), - Self::Droid => Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), - } - .into(), - } - } - } - - #[tokio::test] - async fn enum_resolves_human() { - const DOC: &str = r#"{ - character { - ... on Human { - humanId: id - homePlanet - } - } - }"#; - - let schema = schema(QueryRoot::Human); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"character": {"humanId": "human-32", "homePlanet": "earth"}}), - vec![], - )), - ); - } - - #[tokio::test] - async fn enum_resolves_droid() { - const DOC: &str = r#"{ - character { - ... on Droid { - droidId: id - primaryFunction - } - } - }"#; - - let schema = schema(QueryRoot::Droid); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}), - vec![], - )), - ); - } - - #[tokio::test] - async fn enum_resolves_id_field() { - const DOC: &str = r#"{ - character { - id - } - }"#; - - for (root, expected_id) in &[ - (QueryRoot::Human, "human-32"), - (QueryRoot::Droid, "droid-99"), - ] { - let schema = schema(*root); - - let expected_id: &str = *expected_id; - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"character": {"id": expected_id}}), vec![])), - ); - } - } - - #[tokio::test] - async fn uses_trait_name_without_type_params() { - const DOC: &str = r#"{ - __type(name: "Character") { - name - } - }"#; - - let schema = schema(QueryRoot::Human); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"__type": {"name": "Character"}}), vec![])), - ); - } -} - -mod generic_lifetime_async { - use super::*; - - #[graphql_interface(for = [Human, Droid])] - trait Character<'me, A> { - async fn id<'a>(&'a self) -> &'a str; - } - - #[derive(GraphQLObject)] - #[graphql(impl = CharacterValue<()>)] - struct Human { - id: String, - home_planet: String, - } - - struct Droid { - id: String, - primary_function: String, - } - - #[graphql_object(impl = CharacterValue<()>)] - impl Droid { - fn id(&self) -> &str { - &self.id - } - - fn primary_function(&self) -> &str { - &self.primary_function - } - } - - #[derive(Clone, Copy)] - enum QueryRoot { - Human, - Droid, - } - - #[graphql_object] - impl QueryRoot { - fn character(&self) -> CharacterValue<'_, ()> { - match self { - Self::Human => Human { - id: "human-32".to_string(), - home_planet: "earth".to_string(), - } - .into(), - Self::Droid => Droid { - id: "droid-99".to_string(), - primary_function: "run".to_string(), - } - .into(), - } - } - } - - #[tokio::test] - async fn enum_resolves_human() { - const DOC: &str = r#"{ - character { - ... on Human { - humanId: id - homePlanet - } - } - }"#; - - let schema = schema(QueryRoot::Human); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"character": {"humanId": "human-32", "homePlanet": "earth"}}), - vec![], - )), - ); - } - - #[tokio::test] - async fn enum_resolves_droid() { - const DOC: &str = r#"{ - character { - ... on Droid { - droidId: id - primaryFunction - } - } - }"#; - - let schema = schema(QueryRoot::Droid); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok(( - graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run"}}), - vec![], - )), - ); - } - - #[tokio::test] - async fn enum_resolves_id_field() { - const DOC: &str = r#"{ - character { - id - } - }"#; - - for (root, expected_id) in &[ - (QueryRoot::Human, "human-32"), - (QueryRoot::Droid, "droid-99"), - ] { - let schema = schema(*root); - - let expected_id: &str = *expected_id; - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"character": {"id": expected_id}}), vec![])), - ); - } - } - - #[tokio::test] - async fn uses_trait_name_without_type_params() { - const DOC: &str = r#"{ - __type(name: "Character") { - name - } - }"#; - - let schema = schema(QueryRoot::Human); - - assert_eq!( - execute(DOC, None, &schema, &graphql_vars! {}, &()).await, - Ok((graphql_value!({"__type": {"name": "Character"}}), vec![])), - ); - } -} - mod argument { use super::*; @@ -1453,7 +1038,7 @@ mod argument { trait Character { fn id_wide(&self, is_number: bool) -> &str; - async fn id_wide2(&self, is_number: bool, r#async: Option) -> &str; + fn id_wide2(&self, is_number: bool, r#async: Option) -> &str; } struct Human { @@ -1471,7 +1056,7 @@ mod argument { &self.home_planet } - fn id_wide(&self, is_number: bool) -> &str { + async fn id_wide(&self, is_number: bool) -> &str { if is_number { &self.id } else { @@ -1627,7 +1212,7 @@ mod default_argument { #[graphql_interface(for = Human)] trait Character { - async fn id( + fn id( &self, #[graphql(default)] first: String, #[graphql(default = "second".to_string())] second: String, @@ -1641,7 +1226,7 @@ mod default_argument { #[graphql_object(impl = CharacterValue)] impl Human { - fn info(&self, coord: Point) -> i32 { + async fn info(&self, coord: Point) -> i32 { coord.x } @@ -2141,9 +1726,9 @@ mod renamed_all_fields_and_args { trait Character { fn id(&self) -> &str; - async fn home_planet(&self, planet_name: String) -> String; + fn home_planet(&self, planet_name: String) -> String; - async fn r#async_info(&self, r#my_num: i32) -> i32; + fn r#async_info(&self, r#my_num: i32) -> i32; } struct Human; @@ -2154,11 +1739,11 @@ mod renamed_all_fields_and_args { "human-32" } - fn home_planet(planet_name: String) -> String { + async fn home_planet(planet_name: String) -> String { planet_name } - fn r#async_info(r#my_num: i32) -> i32 { + async fn r#async_info(r#my_num: i32) -> i32 { r#my_num } } @@ -2356,7 +1941,7 @@ mod custom_scalar { #[graphql_interface(for = [Human, Droid], scalar = MyScalarValue)] trait Character { - async fn id(&self) -> &str; + fn id(&self) -> &str; } #[derive(GraphQLObject)] @@ -2726,9 +2311,9 @@ mod explicit_custom_context { #[graphql_interface(for = [Human, Droid], context = CustomContext)] trait Character { - async fn id<'a>(&'a self, context: &CustomContext) -> &'a str; + fn id<'a>(&'a self, context: &CustomContext) -> &'a str; - async fn info<'b>(&'b self, ctx: &()) -> &'b str; + fn info<'b>(&'b self, ctx: &()) -> &'b str; fn more<'c>(&'c self, #[graphql(context)] custom: &CustomContext) -> &'c str; } @@ -2740,11 +2325,11 @@ mod explicit_custom_context { #[graphql_object(impl = CharacterValue, context = CustomContext)] impl Human { - fn id<'a>(&'a self, _context: &CustomContext) -> &'a str { + async fn id<'a>(&'a self, _context: &CustomContext) -> &'a str { &self.id } - fn home_planet(&self) -> &str { + async fn home_planet(&self) -> &str { &self.home_planet } @@ -2894,7 +2479,7 @@ mod inferred_custom_context_from_field { trait Character { fn id<'a>(&self, context: &'a CustomContext) -> &'a str; - fn info<'b>(&'b self, context: &()) -> &'b str; + fn info(&self, context: &()) -> &str; } struct Human { @@ -3042,9 +2627,9 @@ mod executor { #[graphql_interface(for = [Human, Droid], scalar = S)] trait Character { - async fn id<'a>(&self, executor: &'a Executor<'_, '_, (), S>) -> &'a str; + fn id<'a>(&self, executor: &'a Executor<'_, '_, (), S>) -> &'a str; - async fn info<'b>( + fn info<'b>( &'b self, arg: Option, #[graphql(executor)] another: &Executor<'_, '_, (), S>, diff --git a/juniper/src/macros/reflection.rs b/juniper/src/macros/reflection.rs index d91355a33..7467b22b0 100644 --- a/juniper/src/macros/reflection.rs +++ b/juniper/src/macros/reflection.rs @@ -493,6 +493,123 @@ pub const fn str_eq(l: &str, r: &str) -> bool { true } +/// Tried to call [`Field`] implementation of `set_ty` with `info`, `args` and +/// `executor` as arguments if present or panics otherwise. +/// +/// This macro uses [autoref-based specialisation][1]. +/// +/// [1]: http://lukaskalbertodt.github.io/2019/12/05/generalized-autoref-based-specialization.html +#[macro_export] +macro_rules! call_field_or_panic { + ( + $field_name: expr, + $self_: expr, + $info: expr, + $args: expr, + $executor: expr $(,)? + ) => {{ + const FIELD_NAME: $crate::macros::reflection::FieldName = + $crate::macros::reflection::fnv1a128($field_name); + + struct Wrap(T); + + impl $crate::macros::reflection::FieldMeta for Wrap<&T> + where + T: $crate::macros::reflection::FieldMeta, + { + type Context = T::Context; + + type TypeInfo = T::TypeInfo; + + const TYPE: $crate::macros::reflection::Type = T::TYPE; + + const SUB_TYPES: $crate::macros::reflection::Types = T::SUB_TYPES; + + const WRAPPED_VALUE: $crate::macros::reflection::WrappedValue = T::WRAPPED_VALUE; + + const ARGUMENTS: $crate::macros::reflection::Arguments = T::ARGUMENTS; + } + + impl $crate::macros::reflection::FieldMeta for &Wrap<&T> + where + T: $crate::macros::reflection::FieldMeta, + { + type Context = T::Context; + + type TypeInfo = T::TypeInfo; + + const TYPE: $crate::macros::reflection::Type = T::TYPE; + + const SUB_TYPES: $crate::macros::reflection::Types = T::SUB_TYPES; + + const WRAPPED_VALUE: $crate::macros::reflection::WrappedValue = T::WRAPPED_VALUE; + + const ARGUMENTS: $crate::macros::reflection::Arguments = T::ARGUMENTS; + } + + /// First, we'll try to call this trait in case [`Field`] impl is present. + trait ViaField: $crate::macros::reflection::FieldMeta { + fn __call( + &self, + info: &Self::TypeInfo, + args: &$crate::Arguments, + executor: &$crate::Executor, + ) -> $crate::ExecutionResult; + } + + impl ViaField for &Wrap<&T> + where + T: $crate::macros::reflection::Field< + S, + { $crate::macros::reflection::fnv1a128($field_name) }, + >, + { + fn __call( + &self, + info: &Self::TypeInfo, + args: &$crate::Arguments, + executor: &$crate::Executor, + ) -> $crate::ExecutionResult { + self.0.call(info, args, executor) + } + } + + /// If [`Field`] impl wasn't found, we'll fallback to [`BasePanic`] + /// trait, which simply panics. + trait BasePanic: $crate::macros::reflection::FieldMeta { + fn __call( + &self, + info: &Self::TypeInfo, + args: &$crate::Arguments, + executor: &$crate::Executor, + ) -> $crate::ExecutionResult; + } + + impl BasePanic for Wrap<&T> + where + T: $crate::macros::reflection::FieldMeta< + S, + { $crate::macros::reflection::fnv1a128($field_name) }, + > + $crate::macros::reflection::BaseType, + { + fn __call( + &self, + _: &Self::TypeInfo, + _: &$crate::Arguments, + _: &$crate::Executor, + ) -> $crate::ExecutionResult { + ::std::panic!( + "Tried to resolve async field `{}` on type `{}` with a sync resolver", + $field_name, + T::NAME, + ); + } + } + + (&&Wrap($self_)).__call($info, $args, $executor) + }}; +} + /// Asserts that `#[graphql_interface(for = ...)]` has all the types referencing /// this interface in the `impl = ...` attribute argument. #[macro_export] diff --git a/juniper_codegen/src/graphql_interface/attr.rs b/juniper_codegen/src/graphql_interface/attr.rs index a34704490..5696c4aa9 100644 --- a/juniper_codegen/src/graphql_interface/attr.rs +++ b/juniper_codegen/src/graphql_interface/attr.rs @@ -16,7 +16,7 @@ use crate::{ util::{path_eq_single, span_container::SpanContainer, RenameRule}, }; -use super::{inject_async_trait, Definition, TraitAttr}; +use super::{Definition, TraitAttr}; /// [`GraphQLScope`] of errors for `#[graphql_interface]` macro. const ERR: GraphQLScope = GraphQLScope::InterfaceAttr; @@ -104,20 +104,6 @@ fn expand_on_trait( }) .unwrap_or_else(|| parse_quote! { () }); - let is_async_trait = attr.asyncness.is_some() - || ast - .items - .iter() - .find_map(|item| match item { - syn::TraitItem::Method(m) => m.sig.asyncness, - _ => None, - }) - .is_some(); - let has_default_async_methods = ast.items.iter().any(|item| match item { - syn::TraitItem::Method(m) => m.sig.asyncness.and(m.default.as_ref()).is_some(), - _ => false, - }); - let enum_alias_ident = attr .r#enum .as_deref() @@ -146,24 +132,6 @@ fn expand_on_trait( .collect(), }; - if is_async_trait { - if has_default_async_methods { - // Hack for object safety. See details: https://docs.rs/async-trait/#dyn-traits - ast.supertraits.push(parse_quote! { Sync }); - } - inject_async_trait( - &mut ast.attrs, - ast.items.iter_mut().filter_map(|i| { - if let syn::TraitItem::Method(m) = i { - Some(&mut m.sig) - } else { - None - } - }), - &ast.generics, - ); - } - Ok(quote! { #ast #generated_code diff --git a/juniper_codegen/src/graphql_interface/mod.rs b/juniper_codegen/src/graphql_interface/mod.rs index 9c3ceef10..ff2a4cb56 100644 --- a/juniper_codegen/src/graphql_interface/mod.rs +++ b/juniper_codegen/src/graphql_interface/mod.rs @@ -903,10 +903,13 @@ impl Definition { #field_name, ); - <_ as ::juniper::macros::reflection::Field< - #scalar, - { ::juniper::macros::reflection::fnv1a128(#field_name) }, - >>::call(v, info, args, executor) + ::juniper::call_field_or_panic!( + #field_name, + v, + info, + args, + executor, + ) })* #unreachable_arm } @@ -1194,33 +1197,3 @@ impl Definition { !self.trait_generics.params.is_empty() } } - -/// Injects [`async_trait`] implementation into the given trait definition or -/// trait implementation block, correctly restricting type and lifetime -/// parameters with `'async_trait` lifetime, if required. -fn inject_async_trait<'m, M>(attrs: &mut Vec, methods: M, generics: &syn::Generics) -where - M: IntoIterator, -{ - attrs.push(parse_quote! { #[::juniper::async_trait] }); - - for method in methods.into_iter() { - if method.asyncness.is_some() { - let where_clause = &mut method.generics.make_where_clause().predicates; - for p in &generics.params { - let ty_param = match p { - syn::GenericParam::Type(t) => { - let ty_param = &t.ident; - quote! { #ty_param } - } - syn::GenericParam::Lifetime(l) => { - let ty_param = &l.lifetime; - quote! { #ty_param } - } - syn::GenericParam::Const(_) => continue, - }; - where_clause.push(parse_quote! { #ty_param: 'async_trait }); - } - } - } -} diff --git a/juniper_codegen/src/lib.rs b/juniper_codegen/src/lib.rs index 1c27cf356..f31e33748 100644 --- a/juniper_codegen/src/lib.rs +++ b/juniper_codegen/src/lib.rs @@ -482,16 +482,12 @@ pub fn graphql_scalar(args: TokenStream, input: TokenStream) -> TokenStream { /// // NOTICE: Specifying `ScalarValue` as existing type parameter. /// #[graphql_interface(for = Human, scalar = S)] /// trait Character { -/// async fn id<'a>(&self, executor: &'a Executor<'_, '_, (), S>) -> &'a str -/// where -/// S: Send + Sync; // required by `#[async_trait]` transformation ¯\_(ツ)_/¯ +/// fn id<'a>(&self, executor: &'a Executor<'_, '_, (), S>) -> &'a str; /// -/// async fn name<'b>( +/// fn name<'b>( /// &'b self, /// #[graphql(executor)] another: &Executor<'_, '_, (), S>, -/// ) -> &'b str -/// where -/// S: Send + Sync; +/// ) -> &'b str; /// } /// /// struct Human { From d77a1abf49ba62e3ef161dfba6e7366db2692db8 Mon Sep 17 00:00:00 2001 From: ilslv Date: Thu, 13 Jan 2022 08:24:36 +0300 Subject: [PATCH 51/53] Corrections --- docs/book/content/types/interfaces.md | 11 ++++------- .../interface/additional_non_nullable_argument.stderr | 2 +- .../fail/interface/argument_wrong_default_array.rs | 8 ++++++++ .../interface/argument_wrong_default_array.stderr | 11 +++++++++++ juniper/CHANGELOG.md | 2 ++ juniper/src/macros/reflection.rs | 2 +- juniper_codegen/src/lib.rs | 9 +++------ 7 files changed, 30 insertions(+), 15 deletions(-) create mode 100644 integration_tests/codegen_fail/fail/interface/argument_wrong_default_array.rs create mode 100644 integration_tests/codegen_fail/fail/interface/argument_wrong_default_array.stderr diff --git a/docs/book/content/types/interfaces.md b/docs/book/content/types/interfaces.md index 3b0698880..aa9c543e7 100644 --- a/docs/book/content/types/interfaces.md +++ b/docs/book/content/types/interfaces.md @@ -229,19 +229,16 @@ struct Human { name: String, home_planet: String, } -#[graphql_object(impl = CharacterValue<__S>)] +#[graphql_object(scalar = S: ScalarValue, impl = CharacterValue)] impl Human { - async fn id<'a, S>(&self, executor: &'a Executor<'_, '_, (), S>) -> &'a str + async fn id<'a, S>(&self, executor: &'a Executor<'_, '_, (), S>) -> &'a str where - S: ScalarValue + Send + Sync, + S: ScalarValue, { executor.look_ahead().field_name() } - async fn name<'b, S>(&'b self, #[graphql(executor)] _: &Executor<'_, '_, (), S>) -> &'b str - where - S: ScalarValue + Send + Sync, - { + async fn name<'b, S>(&'b self, #[graphql(executor)] _: &Executor<'_, '_, (), S>) -> &'b str { &self.name } diff --git a/integration_tests/codegen_fail/fail/interface/additional_non_nullable_argument.stderr b/integration_tests/codegen_fail/fail/interface/additional_non_nullable_argument.stderr index 0805ac203..589ed21b5 100644 --- a/integration_tests/codegen_fail/fail/interface/additional_non_nullable_argument.stderr +++ b/integration_tests/codegen_fail/fail/interface/additional_non_nullable_argument.stderr @@ -2,6 +2,6 @@ error[E0080]: evaluation of constant value failed --> fail/interface/additional_non_nullable_argument.rs:14:1 | 14 | #[graphql_interface(for = ObjA)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: Field `id`: Argument `isPresent` of type `Boolean!` not present on the interface and so has to be nullable.', $DIR/fail/interface/additional_non_nullable_argument.rs:14:1 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the evaluated program panicked at 'Failed to implement interface `Character` on `ObjA`: Field `id`: Argument `isPresent` of type `Boolean!` isn't present on the interface and so has to be nullable.', $DIR/fail/interface/additional_non_nullable_argument.rs:14:1 | = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/integration_tests/codegen_fail/fail/interface/argument_wrong_default_array.rs b/integration_tests/codegen_fail/fail/interface/argument_wrong_default_array.rs new file mode 100644 index 000000000..00bec5f4c --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/argument_wrong_default_array.rs @@ -0,0 +1,8 @@ +use juniper::graphql_interface; + +#[graphql_interface] +trait Character { + fn wrong(&self, #[graphql(default = [true, false, false])] input: [bool; 2]) -> bool; +} + +fn main() {} diff --git a/integration_tests/codegen_fail/fail/interface/argument_wrong_default_array.stderr b/integration_tests/codegen_fail/fail/interface/argument_wrong_default_array.stderr new file mode 100644 index 000000000..b745a82d0 --- /dev/null +++ b/integration_tests/codegen_fail/fail/interface/argument_wrong_default_array.stderr @@ -0,0 +1,11 @@ +error[E0277]: the trait bound `[bool; 2]: From<[bool; 3]>` is not satisfied + --> fail/interface/argument_wrong_default_array.rs:3:1 + | +3 | #[graphql_interface] + | ^^^^^^^^^^^^^^^^^^^^ the trait `From<[bool; 3]>` is not implemented for `[bool; 2]` + | + = help: the following implementations were found: + <[T; LANES] as From>> + <[bool; LANES] as From>> + = note: required because of the requirements on the impl of `Into<[bool; 2]>` for `[bool; 3]` + = note: this error originates in the attribute macro `graphql_interface` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/juniper/CHANGELOG.md b/juniper/CHANGELOG.md index e85c18174..631233216 100644 --- a/juniper/CHANGELOG.md +++ b/juniper/CHANGELOG.md @@ -12,6 +12,8 @@ - Forbid `__typename` field on `subscription` operations [accordingly to October 2021 spec](https://spec.graphql.org/October2021/#note-bc213). ([#1001](https://github.com/graphql-rust/juniper/pull/1001), [#1000](https://github.com/graphql-rust/juniper/pull/1000)) - Redesign `#[graphql_interface]` macro. ([#1009](https://github.com/graphql-rust/juniper/pull/1009)): - Remove support for `#[graphql_interface(dyn)]`. + - Remove support for `downcast`. + - Remove support for `async` trait methods. - Describe all interface trait methods with type's fields or impl block instead of `#[graphql_interface]` attribute on `impl Trait`. - Forbid default impls on non-skipped trait methods. - Add support for additional nullable arguments on implementer. diff --git a/juniper/src/macros/reflection.rs b/juniper/src/macros/reflection.rs index 7467b22b0..4c2da2a17 100644 --- a/juniper/src/macros/reflection.rs +++ b/juniper/src/macros/reflection.rs @@ -930,7 +930,7 @@ macro_rules! assert_field_args { IMPL_ARG_NAME, "` of type `", IMPL_TYPE_FORMATTED, - "` not present on the interface and so has to be nullable." + "` isn't present on the interface and so has to be nullable." ) } }; diff --git a/juniper_codegen/src/lib.rs b/juniper_codegen/src/lib.rs index f31e33748..90e59a318 100644 --- a/juniper_codegen/src/lib.rs +++ b/juniper_codegen/src/lib.rs @@ -494,19 +494,16 @@ pub fn graphql_scalar(args: TokenStream, input: TokenStream) -> TokenStream { /// id: String, /// name: String, /// } -/// #[graphql_object(impl = CharacterValue<__S>)] +/// #[graphql_object(scalar = S: ScalarValue, impl = CharacterValue)] /// impl Human { /// async fn id<'a, S>(&self, executor: &'a Executor<'_, '_, (), S>) -> &'a str /// where -/// S: ScalarValue + Send + Sync, +/// S: ScalarValue, /// { /// executor.look_ahead().field_name() /// } /// -/// async fn name<'b, S>(&'b self, _executor: &Executor<'_, '_, (), S>) -> &'b str -/// where -/// S: ScalarValue + Send + Sync, -/// { +/// async fn name<'b, S>(&'b self, _executor: &Executor<'_, '_, (), S>) -> &'b str { /// &self.name /// } /// } From 0cb101d38e99a29b9f527f7536e8412ad558c76b Mon Sep 17 00:00:00 2001 From: ilslv Date: Fri, 14 Jan 2022 09:44:19 +0300 Subject: [PATCH 52/53] Corrections --- examples/actix_subscriptions/src/main.rs | 2 +- juniper/src/macros/reflection.rs | 117 ------------------ juniper/src/tests/fixtures/starwars/schema.rs | 20 +-- juniper_codegen/src/common/field/mod.rs | 22 ---- juniper_codegen/src/graphql_interface/mod.rs | 41 ++---- juniper_codegen/src/graphql_object/mod.rs | 85 ++++++------- 6 files changed, 59 insertions(+), 228 deletions(-) diff --git a/examples/actix_subscriptions/src/main.rs b/examples/actix_subscriptions/src/main.rs index 6da5ad562..d36c34d5a 100644 --- a/examples/actix_subscriptions/src/main.rs +++ b/examples/actix_subscriptions/src/main.rs @@ -10,7 +10,7 @@ use actix_web::{ use juniper::{ graphql_object, graphql_subscription, graphql_value, - tests::fixtures::starwars::schema::{Character as _, Database, Query}, + tests::fixtures::starwars::schema::{Database, Query}, EmptyMutation, FieldError, RootNode, }; use juniper_actix::{graphql_handler, playground_handler, subscriptions::subscriptions_handler}; diff --git a/juniper/src/macros/reflection.rs b/juniper/src/macros/reflection.rs index 4c2da2a17..30e4f74f4 100644 --- a/juniper/src/macros/reflection.rs +++ b/juniper/src/macros/reflection.rs @@ -493,123 +493,6 @@ pub const fn str_eq(l: &str, r: &str) -> bool { true } -/// Tried to call [`Field`] implementation of `set_ty` with `info`, `args` and -/// `executor` as arguments if present or panics otherwise. -/// -/// This macro uses [autoref-based specialisation][1]. -/// -/// [1]: http://lukaskalbertodt.github.io/2019/12/05/generalized-autoref-based-specialization.html -#[macro_export] -macro_rules! call_field_or_panic { - ( - $field_name: expr, - $self_: expr, - $info: expr, - $args: expr, - $executor: expr $(,)? - ) => {{ - const FIELD_NAME: $crate::macros::reflection::FieldName = - $crate::macros::reflection::fnv1a128($field_name); - - struct Wrap(T); - - impl $crate::macros::reflection::FieldMeta for Wrap<&T> - where - T: $crate::macros::reflection::FieldMeta, - { - type Context = T::Context; - - type TypeInfo = T::TypeInfo; - - const TYPE: $crate::macros::reflection::Type = T::TYPE; - - const SUB_TYPES: $crate::macros::reflection::Types = T::SUB_TYPES; - - const WRAPPED_VALUE: $crate::macros::reflection::WrappedValue = T::WRAPPED_VALUE; - - const ARGUMENTS: $crate::macros::reflection::Arguments = T::ARGUMENTS; - } - - impl $crate::macros::reflection::FieldMeta for &Wrap<&T> - where - T: $crate::macros::reflection::FieldMeta, - { - type Context = T::Context; - - type TypeInfo = T::TypeInfo; - - const TYPE: $crate::macros::reflection::Type = T::TYPE; - - const SUB_TYPES: $crate::macros::reflection::Types = T::SUB_TYPES; - - const WRAPPED_VALUE: $crate::macros::reflection::WrappedValue = T::WRAPPED_VALUE; - - const ARGUMENTS: $crate::macros::reflection::Arguments = T::ARGUMENTS; - } - - /// First, we'll try to call this trait in case [`Field`] impl is present. - trait ViaField: $crate::macros::reflection::FieldMeta { - fn __call( - &self, - info: &Self::TypeInfo, - args: &$crate::Arguments, - executor: &$crate::Executor, - ) -> $crate::ExecutionResult; - } - - impl ViaField for &Wrap<&T> - where - T: $crate::macros::reflection::Field< - S, - { $crate::macros::reflection::fnv1a128($field_name) }, - >, - { - fn __call( - &self, - info: &Self::TypeInfo, - args: &$crate::Arguments, - executor: &$crate::Executor, - ) -> $crate::ExecutionResult { - self.0.call(info, args, executor) - } - } - - /// If [`Field`] impl wasn't found, we'll fallback to [`BasePanic`] - /// trait, which simply panics. - trait BasePanic: $crate::macros::reflection::FieldMeta { - fn __call( - &self, - info: &Self::TypeInfo, - args: &$crate::Arguments, - executor: &$crate::Executor, - ) -> $crate::ExecutionResult; - } - - impl BasePanic for Wrap<&T> - where - T: $crate::macros::reflection::FieldMeta< - S, - { $crate::macros::reflection::fnv1a128($field_name) }, - > + $crate::macros::reflection::BaseType, - { - fn __call( - &self, - _: &Self::TypeInfo, - _: &$crate::Arguments, - _: &$crate::Executor, - ) -> $crate::ExecutionResult { - ::std::panic!( - "Tried to resolve async field `{}` on type `{}` with a sync resolver", - $field_name, - T::NAME, - ); - } - } - - (&&Wrap($self_)).__call($info, $args, $executor) - }}; -} - /// Asserts that `#[graphql_interface(for = ...)]` has all the types referencing /// this interface in the `impl = ...` attribute argument. #[macro_export] diff --git a/juniper/src/tests/fixtures/starwars/schema.rs b/juniper/src/tests/fixtures/starwars/schema.rs index 9e18ea268..d9c795de6 100644 --- a/juniper/src/tests/fixtures/starwars/schema.rs +++ b/juniper/src/tests/fixtures/starwars/schema.rs @@ -107,27 +107,27 @@ impl Human { #[graphql_object(context = Database, impl = CharacterValue)] impl Human { /// The id of the human - fn id(&self) -> &str { + pub fn id(&self) -> &str { &self.id } /// The name of the human - fn name(&self) -> Option<&str> { + pub fn name(&self) -> Option<&str> { Some(self.name.as_str()) } /// The friends of the human - fn friends(&self, ctx: &Database) -> Vec { + pub fn friends(&self, ctx: &Database) -> Vec { ctx.get_friends(&self.friend_ids) } /// Which movies they appear in - fn appears_in(&self) -> &[Episode] { + pub fn appears_in(&self) -> &[Episode] { &self.appears_in } /// The home planet of the human - fn home_planet(&self) -> &Option { + pub fn home_planet(&self) -> &Option { &self.home_planet } } @@ -167,27 +167,27 @@ impl Droid { #[graphql_object(context = Database, impl = CharacterValue)] impl Droid { /// The id of the droid - fn id(&self) -> &str { + pub fn id(&self) -> &str { &self.id } /// The name of the droid - fn name(&self) -> Option<&str> { + pub fn name(&self) -> Option<&str> { Some(self.name.as_str()) } /// The friends of the droid - fn friends(&self, ctx: &Database) -> Vec { + pub fn friends(&self, ctx: &Database) -> Vec { ctx.get_friends(&self.friend_ids) } /// Which movies they appear in - fn appears_in(&self) -> &[Episode] { + pub fn appears_in(&self) -> &[Episode] { &self.appears_in } /// The primary function of the droid - fn primary_function(&self) -> &Option { + pub fn primary_function(&self) -> &Option { &self.primary_function } } diff --git a/juniper_codegen/src/common/field/mod.rs b/juniper_codegen/src/common/field/mod.rs index 4272bffa0..bf299b458 100644 --- a/juniper_codegen/src/common/field/mod.rs +++ b/juniper_codegen/src/common/field/mod.rs @@ -251,28 +251,6 @@ impl Definition { } } - /// Returns generated code that errors about [GraphQL fields][1] tried to be - /// resolved asynchronously in the [`GraphQLValue::resolve_field`] method - /// (which is synchronous itself). - /// - /// [`GraphQLValue::resolve_field`]: juniper::GraphQLValue::resolve_field - /// [1]: https://spec.graphql.org/June2018/#sec-Language.Fields - #[must_use] - pub(crate) fn method_resolve_field_err_async_field_tokens( - field_names: &[&str], - scalar: &scalar::Type, - ty_name: &str, - ) -> TokenStream { - quote! { - #( #field_names )|* => return Err(::juniper::FieldError::from(format!( - "Tried to resolve async field `{}` on type `{}` with a sync resolver", - field, - >::name(info) - .ok_or_else(|| ::juniper::macros::helper::err_unnamed_type(#ty_name))?, - ))), - } - } - /// Returns generated code for the [`marker::IsOutputType::mark`] method, /// which performs static checks for this [GraphQL field][1]. /// diff --git a/juniper_codegen/src/graphql_interface/mod.rs b/juniper_codegen/src/graphql_interface/mod.rs index ff2a4cb56..6f0b956e5 100644 --- a/juniper_codegen/src/graphql_interface/mod.rs +++ b/juniper_codegen/src/graphql_interface/mod.rs @@ -579,11 +579,7 @@ impl Definition { let (impl_generics, _, where_clause) = generics.split_for_impl(); let (_, ty_generics, _) = self.trait_generics.split_for_impl(); - let fields_resolvers = self.fields.iter().filter_map(|f| { - if f.is_async { - return None; - } - + let fields_resolvers = self.fields.iter().map(|f| { let name = &f.name; Some(quote! { #name => { @@ -594,18 +590,7 @@ impl Definition { } }) }); - let async_fields_err = { - let names = self - .fields - .iter() - .filter_map(|f| f.is_async.then(|| f.name.as_str())) - .collect::>(); - (!names.is_empty()).then(|| { - field::Definition::method_resolve_field_err_async_field_tokens( - &names, scalar, trait_name, - ) - }) - }; + let no_field_err = field::Definition::method_resolve_field_err_no_field_tokens(scalar, trait_name); @@ -635,7 +620,6 @@ impl Definition { ) -> ::juniper::ExecutionResult<#scalar> { match field { #( #fields_resolvers )* - #async_fields_err _ => #no_field_err, } } @@ -865,11 +849,7 @@ impl Definition { self.fields .iter() - .filter_map(|field| { - if field.is_async { - return None; - } - + .map(|field| { let field_name = &field.name; let mut return_ty = field.ty.clone(); generics.replace_type_with_defaults(&mut return_ty); @@ -882,7 +862,7 @@ impl Definition { quote! { _ => unreachable!() } }); - Some(quote! { + quote! { #[allow(non_snake_case)] impl#impl_generics ::juniper::macros::reflection::Field< #scalar, @@ -903,19 +883,16 @@ impl Definition { #field_name, ); - ::juniper::call_field_or_panic!( - #field_name, - v, - info, - args, - executor, - ) + <_ as ::juniper::macros::reflection::Field::< + #scalar, + { ::juniper::macros::reflection::fnv1a128(#field_name) }, + >>::call(v, info, args, executor) })* #unreachable_arm } } } - }) + } }) .collect() } diff --git a/juniper_codegen/src/graphql_object/mod.rs b/juniper_codegen/src/graphql_object/mod.rs index d306d37a4..e2d752f8b 100644 --- a/juniper_codegen/src/graphql_object/mod.rs +++ b/juniper_codegen/src/graphql_object/mod.rs @@ -633,34 +633,45 @@ impl Definition { self.fields .iter() - .filter_map(|field| { - if field.is_async { - return None; - } - + .map(|field| { let (name, mut res_ty, ident) = (&field.name, field.ty.clone(), &field.ident); - let res = if field.is_method() { - let args = field - .arguments - .as_ref() - .unwrap() - .iter() - .map(|arg| arg.method_resolve_field_tokens(scalar, false)); - - let rcv = field.has_receiver.then(|| { - quote! { self, } - }); - - quote! { Self::#ident(#rcv #( #args ),*) } + let resolve = if field.is_async { + quote! { + ::std::panic!( + "Tried to resolve async field `{}` on type `{}` with a sync resolver", + #name, + >::NAME, + ); + } } else { - res_ty = parse_quote! { _ }; - quote! { &self.#ident } + let res = if field.is_method() { + let args = field + .arguments + .as_ref() + .unwrap() + .iter() + .map(|arg| arg.method_resolve_field_tokens(scalar, false)); + + let rcv = field.has_receiver.then(|| { + quote! { self, } + }); + + quote! { Self::#ident(#rcv #( #args ),*) } + } else { + res_ty = parse_quote! { _ }; + quote! { &self.#ident } + }; + + let resolving_code = gen::sync_resolving_code(); + + quote! { + let res: #res_ty = #res; + #resolving_code + } }; - let resolving_code = gen::sync_resolving_code(); - - Some(quote! { + quote! { #[allow(deprecated, non_snake_case)] #[automatically_derived] impl #impl_generics ::juniper::macros::reflection::Field< @@ -675,11 +686,10 @@ impl Definition { args: &::juniper::Arguments<#scalar>, executor: &::juniper::Executor, ) -> ::juniper::ExecutionResult<#scalar> { - let res: #res_ty = #res; - #resolving_code + #resolve } } - }) + } }) .collect() } @@ -762,34 +772,18 @@ impl Definition { let name = &self.name; - let fields_resolvers = self.fields.iter().filter_map(|f| { - if f.is_async { - return None; - } - + let fields_resolvers = self.fields.iter().map(|f| { let name = &f.name; - Some(quote! { + quote! { #name => { ::juniper::macros::reflection::Field::< #scalar, { ::juniper::macros::reflection::fnv1a128(#name) } >::call(self, info, args, executor) } - }) + } }); - let async_fields_err = { - let names = self - .fields - .iter() - .filter_map(|f| f.is_async.then(|| f.name.as_str())) - .collect::>(); - (!names.is_empty()).then(|| { - field::Definition::method_resolve_field_err_async_field_tokens( - &names, scalar, &ty_name, - ) - }) - }; let no_field_err = field::Definition::method_resolve_field_err_no_field_tokens(scalar, &ty_name); @@ -814,7 +808,6 @@ impl Definition { ) -> ::juniper::ExecutionResult<#scalar> { match field { #( #fields_resolvers )* - #async_fields_err _ => #no_field_err, } } From a411cd03f0e1b9625ed148f825455a73940a309d Mon Sep 17 00:00:00 2001 From: tyranron Date: Wed, 26 Jan 2022 18:43:15 +0200 Subject: [PATCH 53/53] Corrections --- .../fail/interface/method_default_impl.stderr | 2 +- .../src/codegen/interface_attr.rs | 110 ++++++----- juniper/CHANGELOG.md | 15 +- juniper/src/macros/mod.rs | 2 +- .../src/macros/{reflection.rs => reflect.rs} | 187 +++++++++--------- juniper/src/types/scalars.rs | 14 +- juniper_codegen/src/common/parse/mod.rs | 9 +- juniper_codegen/src/derive_scalar_value.rs | 14 +- juniper_codegen/src/graphql_interface/attr.rs | 16 +- juniper_codegen/src/graphql_interface/mod.rs | 143 +++++++------- juniper_codegen/src/graphql_object/mod.rs | 87 ++++---- juniper_codegen/src/graphql_union/mod.rs | 26 +-- juniper_codegen/src/impl_scalar.rs | 14 +- juniper_codegen/src/lib.rs | 7 +- juniper_codegen/src/util/mod.rs | 28 +-- 15 files changed, 348 insertions(+), 326 deletions(-) rename juniper/src/macros/{reflection.rs => reflect.rs} (83%) diff --git a/integration_tests/codegen_fail/fail/interface/method_default_impl.stderr b/integration_tests/codegen_fail/fail/interface/method_default_impl.stderr index a5a728b90..887107a68 100644 --- a/integration_tests/codegen_fail/fail/interface/method_default_impl.stderr +++ b/integration_tests/codegen_fail/fail/interface/method_default_impl.stderr @@ -1,4 +1,4 @@ -error: GraphQL interface trait method can't have default impl block +error: GraphQL interface trait method can't have default implementation --> fail/interface/method_default_impl.rs:5:26 | 5 | fn id(&self) -> &str { diff --git a/integration_tests/juniper_tests/src/codegen/interface_attr.rs b/integration_tests/juniper_tests/src/codegen/interface_attr.rs index a1a11c195..7750a1501 100644 --- a/integration_tests/juniper_tests/src/codegen/interface_attr.rs +++ b/integration_tests/juniper_tests/src/codegen/interface_attr.rs @@ -791,7 +791,7 @@ mod fallible_field { } #[tokio::test] - async fn enum_resolves_human() { + async fn resolves_human() { const DOC: &str = r#"{ character { ... on Human { @@ -813,7 +813,7 @@ mod fallible_field { } #[tokio::test] - async fn enum_resolves_droid() { + async fn resolves_droid() { const DOC: &str = r#"{ character { ... on Droid { @@ -835,7 +835,7 @@ mod fallible_field { } #[tokio::test] - async fn enum_resolves_id_field() { + async fn resolves_id_field() { const DOC: &str = r#"{ character { id @@ -858,8 +858,6 @@ mod fallible_field { #[tokio::test] async fn has_correct_graphql_type() { - let schema = schema(QueryRoot::Human); - const DOC: &str = r#"{ __type(name: "Character") { name @@ -876,6 +874,8 @@ mod fallible_field { } }"#; + let schema = schema(QueryRoot::Human); + assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( @@ -885,7 +885,7 @@ mod fallible_field { "fields": [{ "name": "id", "type": {"kind": "NON_NULL", "ofType": {"name": "String"}}, - }] + }], }}), vec![], )), @@ -949,7 +949,7 @@ mod generic { } #[tokio::test] - async fn enum_resolves_human() { + async fn resolves_human() { const DOC: &str = r#"{ character { ... on Human { @@ -971,7 +971,7 @@ mod generic { } #[tokio::test] - async fn enum_resolves_droid() { + async fn resolves_droid() { const DOC: &str = r#"{ character { ... on Droid { @@ -993,7 +993,7 @@ mod generic { } #[tokio::test] - async fn enum_resolves_id_field() { + async fn resolves_id_field() { const DOC: &str = r#"{ character { id @@ -1087,7 +1087,7 @@ mod argument { } #[tokio::test] - async fn enum_resolves_id_field() { + async fn resolves_id_field() { let schema = schema(QueryRoot); for (input, expected) in &[ @@ -1117,8 +1117,6 @@ mod argument { #[tokio::test] async fn camelcases_name() { - let schema = schema(QueryRoot); - const DOC: &str = r#"{ __type(name: "Character") { fields { @@ -1129,6 +1127,9 @@ mod argument { } } }"#; + + let schema = schema(QueryRoot); + assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( @@ -1137,11 +1138,11 @@ mod argument { "args": [ {"name": "isNumber"}, ], - },{ + }, { "name": "idWide2", "args": [ {"name": "isNumber"}, - {"name": "async"} + {"name": "async"}, ], }]}}), vec![], @@ -1151,8 +1152,6 @@ mod argument { #[tokio::test] async fn has_no_description() { - let schema = schema(QueryRoot); - const DOC: &str = r#"{ __type(name: "Character") { fields { @@ -1163,6 +1162,8 @@ mod argument { } }"#; + let schema = schema(QueryRoot); + assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( @@ -1177,8 +1178,6 @@ mod argument { #[tokio::test] async fn has_no_defaults() { - let schema = schema(QueryRoot); - const DOC: &str = r#"{ __type(name: "Character") { fields { @@ -1189,6 +1188,8 @@ mod argument { } }"#; + let schema = schema(QueryRoot); + assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( @@ -1868,7 +1869,7 @@ mod explicit_scalar { } #[tokio::test] - async fn enum_resolves_human() { + async fn resolves_human() { const DOC: &str = r#"{ character { ... on Human { @@ -1890,7 +1891,7 @@ mod explicit_scalar { } #[tokio::test] - async fn enum_resolves_droid() { + async fn resolves_droid() { const DOC: &str = r#"{ character { ... on Droid { @@ -1912,7 +1913,7 @@ mod explicit_scalar { } #[tokio::test] - async fn enum_resolves_id_field() { + async fn resolves_id_field() { const DOC: &str = r#"{ character { id @@ -1992,7 +1993,7 @@ mod custom_scalar { } #[tokio::test] - async fn enum_resolves_human() { + async fn resolves_human() { const DOC: &str = r#"{ character { ... on Human { @@ -2014,7 +2015,7 @@ mod custom_scalar { } #[tokio::test] - async fn enum_resolves_droid() { + async fn resolves_droid() { const DOC: &str = r#"{ character { ... on Droid { @@ -2036,7 +2037,7 @@ mod custom_scalar { } #[tokio::test] - async fn enum_resolves_id_field() { + async fn resolves_id_field() { const DOC: &str = r#"{ character { id @@ -2114,7 +2115,7 @@ mod explicit_generic_scalar { } #[tokio::test] - async fn enum_resolves_human() { + async fn resolves_human() { const DOC: &str = r#"{ character { ... on Human { @@ -2136,7 +2137,7 @@ mod explicit_generic_scalar { } #[tokio::test] - async fn enum_resolves_droid() { + async fn resolves_droid() { const DOC: &str = r#"{ character { ... on Droid { @@ -2158,7 +2159,7 @@ mod explicit_generic_scalar { } #[tokio::test] - async fn enum_resolves_id_field() { + async fn resolves_id_field() { const DOC: &str = r#"{ character { id @@ -2236,7 +2237,7 @@ mod bounded_generic_scalar { } #[tokio::test] - async fn enum_resolves_human() { + async fn resolves_human() { const DOC: &str = r#"{ character { ... on Human { @@ -2258,7 +2259,7 @@ mod bounded_generic_scalar { } #[tokio::test] - async fn enum_resolves_droid() { + async fn resolves_droid() { const DOC: &str = r#"{ character { ... on Droid { @@ -2280,7 +2281,7 @@ mod bounded_generic_scalar { } #[tokio::test] - async fn enum_resolves_id_field() { + async fn resolves_id_field() { const DOC: &str = r#"{ character { id @@ -2391,7 +2392,7 @@ mod explicit_custom_context { } #[tokio::test] - async fn enum_resolves_human() { + async fn resolves_human() { const DOC: &str = r#"{ character { ... on Human { @@ -2413,7 +2414,7 @@ mod explicit_custom_context { } #[tokio::test] - async fn enum_resolves_droid() { + async fn resolves_droid() { const DOC: &str = r#"{ character { ... on Droid { @@ -2543,7 +2544,7 @@ mod inferred_custom_context_from_field { } #[tokio::test] - async fn enum_resolves_human() { + async fn resolves_human() { const DOC: &str = r#"{ character { ... on Human { @@ -2566,7 +2567,7 @@ mod inferred_custom_context_from_field { } #[tokio::test] - async fn enum_resolves_droid() { + async fn resolves_droid() { const DOC: &str = r#"{ character { ... on Droid { @@ -2701,7 +2702,7 @@ mod executor { } #[tokio::test] - async fn enum_resolves_human() { + async fn resolves_human() { const DOC: &str = r#"{ character { ... on Human { @@ -2723,7 +2724,7 @@ mod executor { } #[tokio::test] - async fn enum_resolves_droid() { + async fn resolves_droid() { const DOC: &str = r#"{ character { ... on Droid { @@ -2947,7 +2948,7 @@ mod field_return_subtyping { } #[tokio::test] - async fn enum_resolves_human() { + async fn resolves_human() { const DOC: &str = r#"{ character { ... on Human { @@ -2969,7 +2970,7 @@ mod field_return_subtyping { } #[tokio::test] - async fn enum_resolves_droid() { + async fn resolves_droid() { const DOC: &str = r#"{ character { ... on Droid { @@ -2991,7 +2992,7 @@ mod field_return_subtyping { } #[tokio::test] - async fn enum_resolves_id_field() { + async fn resolves_id_field() { const DOC: &str = r#"{ character { id @@ -3098,7 +3099,7 @@ mod field_return_union_subtyping { } #[tokio::test] - async fn enum_resolves_human() { + async fn resolves_human() { const DOC: &str = r#"{ character { ... on Human { @@ -3116,14 +3117,18 @@ mod field_return_union_subtyping { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( - graphql_value!({"character": {"humanId": "human-32", "homePlanet": "earth", "keyFeature": {"value": 10}}}), + graphql_value!({"character": { + "humanId": "human-32", + "homePlanet": "earth", + "keyFeature": {"value": 10}, + }}), vec![], )), ); } #[tokio::test] - async fn enum_resolves_droid() { + async fn resolves_droid() { const DOC: &str = r#"{ character { ... on Droid { @@ -3141,14 +3146,18 @@ mod field_return_union_subtyping { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( - graphql_value!({"character": {"droidId": "droid-99", "primaryFunction": "run", "keyFeature": {"value": 42}}}), + graphql_value!({"character": { + "droidId": "droid-99", + "primaryFunction": "run", + "keyFeature": {"value": 42}, + }}), vec![], )), ); } #[tokio::test] - async fn enum_resolves_id_field() { + async fn resolves_fields() { const DOC: &str = r#"{ character { id @@ -3174,8 +3183,11 @@ mod field_return_union_subtyping { assert_eq!( execute(DOC, None, &schema, &graphql_vars! {}, &()).await, Ok(( - graphql_value!({"character": {"id": expected_id, "keyFeature": {"value": expected_val}}}), - vec![] + graphql_value!({"character": { + "id": expected_id, + "keyFeature": {"value": expected_val}, + }}), + vec![], )), ); } @@ -3241,7 +3253,7 @@ mod nullable_argument_subtyping { } #[tokio::test] - async fn enum_resolves_human() { + async fn resolves_human() { const DOC: &str = r#"{ character { ... on Human { @@ -3263,7 +3275,7 @@ mod nullable_argument_subtyping { } #[tokio::test] - async fn enum_resolves_droid() { + async fn resolves_droid() { const DOC: &str = r#"{ character { ... on Droid { @@ -3285,7 +3297,7 @@ mod nullable_argument_subtyping { } #[tokio::test] - async fn enum_resolves_id_field() { + async fn resolves_id_field() { const DOC: &str = r#"{ character { id diff --git a/juniper/CHANGELOG.md b/juniper/CHANGELOG.md index 631233216..411413396 100644 --- a/juniper/CHANGELOG.md +++ b/juniper/CHANGELOG.md @@ -10,14 +10,13 @@ - Make `FromInputValue` methods fallible to allow post-validation. ([#987](https://github.com/graphql-rust/juniper/pull/987)) - Change `Option` to `Result` in `from_input_value()` return type of `#[graphql_scalar]` macro. ([#987](https://github.com/graphql-rust/juniper/pull/987)) - Forbid `__typename` field on `subscription` operations [accordingly to October 2021 spec](https://spec.graphql.org/October2021/#note-bc213). ([#1001](https://github.com/graphql-rust/juniper/pull/1001), [#1000](https://github.com/graphql-rust/juniper/pull/1000)) -- Redesign `#[graphql_interface]` macro. ([#1009](https://github.com/graphql-rust/juniper/pull/1009)): - - Remove support for `#[graphql_interface(dyn)]`. - - Remove support for `downcast`. - - Remove support for `async` trait methods. - - Describe all interface trait methods with type's fields or impl block instead of `#[graphql_interface]` attribute on `impl Trait`. - - Forbid default impls on non-skipped trait methods. - - Add support for additional nullable arguments on implementer. - - Add support for returning sub-type on implementer. +- Redesign `#[graphql_interface]` macro: ([#1009](https://github.com/graphql-rust/juniper/pull/1009)) + - Remove support for `#[graphql_interface(dyn)]` (interface values as trait objects). + - Remove support for `downcast` (custom resolution into implementer types). + - Remove support for `async` trait methods (not required anymore). + - Remove necessity of writing `impl Trait for Type` blocks (interfaces are implemented just by matching its fields). + - Forbid default impls on non-ignored trait methods. + - Support coercion of additional nullable arguments and return sub-typing on implementer. ## Features diff --git a/juniper/src/macros/mod.rs b/juniper/src/macros/mod.rs index f9aa80771..4bbcfb5f6 100644 --- a/juniper/src/macros/mod.rs +++ b/juniper/src/macros/mod.rs @@ -4,7 +4,7 @@ pub mod helper; #[doc(hidden)] #[macro_use] -pub mod reflection; +pub mod reflect; #[macro_use] mod graphql_input_value; diff --git a/juniper/src/macros/reflection.rs b/juniper/src/macros/reflect.rs similarity index 83% rename from juniper/src/macros/reflection.rs rename to juniper/src/macros/reflect.rs index 30e4f74f4..64b04c2c2 100644 --- a/juniper/src/macros/reflection.rs +++ b/juniper/src/macros/reflect.rs @@ -1,5 +1,4 @@ -//! Helper traits and macros for compile-time reflection of Rust types into -//! GraphQL types. +//! Compile-time reflection of Rust types into GraphQL types. use std::{rc::Rc, sync::Arc}; @@ -27,9 +26,9 @@ pub type Types = &'static [Type]; /// Naming of a [GraphQL object][1], [scalar][2] or [interface][3] [`Type`]. /// /// This trait is transparent to [`Option`], [`Vec`] and other containers, so to -/// fully represent [GraphQL object][1] we additionally use [`WrappedType`]. +/// fully represent a [GraphQL object][1] we additionally use [`WrappedType`]. /// -/// Different Rust type may have the same [`NAME`]. For example, [`String`] and +/// Different Rust types may have the same [`NAME`]. For example, [`String`] and /// `&`[`str`](prim@str) share `String!` GraphQL type. /// /// [`NAME`]: Self::NAME @@ -154,7 +153,7 @@ impl + ?Sized> BaseSubTypes for Rc { const NAMES: Types = T::NAMES; } -/// Alias for a value of a [`WrappedType`] (combined GraphQL type). +/// Alias for a value of a [`WrappedType`] (composed GraphQL type). pub type WrappedValue = u128; // TODO: Just use `&str`s once they're allowed in `const` generics. @@ -162,23 +161,26 @@ pub type WrappedValue = u128; /// /// To fully represent a [GraphQL object][1] it's not enough to use [`Type`], /// because of the [wrapping types][2]. To work around this we use a -/// [`WrappedValue`] which is represented via [`u128`]. -/// +/// [`WrappedValue`] which is represented via [`u128`] number in the following +/// encoding: /// - In base case of non-nullable [object][1] [`VALUE`] is `1`. /// - To represent nullability we "append" `2` to the [`VALUE`], so /// [`Option`]`<`[object][1]`>` has [`VALUE`] of `12`. /// - To represent list we "append" `3` to the [`VALUE`], so /// [`Vec`]`<`[object][1]`>` has [`VALUE`] of `13`. /// -/// This approach allows us to uniquely represent any [GraphQL object][1] with +/// This approach allows us to uniquely represent any [GraphQL object][1] with a /// combination of [`Type`] and [`WrappedValue`] and even format it via -/// [`format_type!`] macro in `const` context. -/// +/// [`format_type!`] macro in a `const` context. /// /// # Examples /// /// ```rust -/// # use juniper::{macros::reflection::{WrappedType, BaseType, WrappedValue, Type}, DefaultScalarValue, format_type}; +/// # use juniper::{ +/// # format_type, +/// # macros::reflect::{WrappedType, BaseType, WrappedValue, Type}, +/// # DefaultScalarValue, +/// # }; /// # /// assert_eq!( as WrappedType>::VALUE, 12); /// assert_eq!( as WrappedType>::VALUE, 13); @@ -276,31 +278,31 @@ pub type Argument = (Name, Type, WrappedValue); /// [1]: https://spec.graphql.org/October2021#sec-Language.Arguments pub type Arguments = &'static [(Name, Type, WrappedValue)]; -/// Alias for a `const`-hashed [`Name`] used in `const` context. +/// Alias for a `const`-hashed [`Name`] used in a `const` context. pub type FieldName = u128; /// [GraphQL object][1] or [interface][2] [field arguments][3] [`Names`]. /// -/// [1]: https://spec.graphql.org/October2021/#sec-Objects -/// [2]: https://spec.graphql.org/October2021/#sec-Interfaces -/// [3]: https://spec.graphql.org/October2021/#sec-Language.Arguments +/// [1]: https://spec.graphql.org/October2021#sec-Objects +/// [2]: https://spec.graphql.org/October2021#sec-Interfaces +/// [3]: https://spec.graphql.org/October2021#sec-Language.Arguments pub trait Fields { /// [`Names`] of the [GraphQL object][1] or [interface][2] /// [field arguments][3]. /// - /// [1]: https://spec.graphql.org/October2021/#sec-Objects - /// [2]: https://spec.graphql.org/October2021/#sec-Interfaces - /// [3]: https://spec.graphql.org/October2021/#sec-Language.Arguments + /// [1]: https://spec.graphql.org/October2021#sec-Objects + /// [2]: https://spec.graphql.org/October2021#sec-Interfaces + /// [3]: https://spec.graphql.org/October2021#sec-Language.Arguments const NAMES: Names; } /// [`Types`] of the [GraphQL interfaces][1] implemented by this type. /// -/// [1]: https://spec.graphql.org/October2021/#sec-Interfaces +/// [1]: https://spec.graphql.org/October2021#sec-Interfaces pub trait Implements { /// [`Types`] of the [GraphQL interfaces][1] implemented by this type. /// - /// [1]: https://spec.graphql.org/October2021/#sec-Interfaces + /// [1]: https://spec.graphql.org/October2021#sec-Interfaces const NAMES: Types; } @@ -315,54 +317,54 @@ pub trait Implements { /// [`TYPE`]: Self::TYPE /// [`TypeInfo`]: Self::TypeInfo /// [`WRAPPED_VALUE`]: Self::WRAPPED_VALUE -/// [1]: https://spec.graphql.org/October2021/#sec-Language.Fields +/// [1]: https://spec.graphql.org/October2021#sec-Language.Fields pub trait FieldMeta { - /// [`GraphQLValue::Context`] of this [`Field`][1]. + /// [`GraphQLValue::Context`] of this [field][1]. /// - /// [1]: https://spec.graphql.org/October2021/#sec-Language.Fields + /// [1]: https://spec.graphql.org/October2021#sec-Language.Fields type Context; /// [`GraphQLValue::TypeInfo`] of this [GraphQL field][1]. /// - /// [1]: https://spec.graphql.org/October2021/#sec-Language.Fields + /// [1]: https://spec.graphql.org/October2021#sec-Language.Fields type TypeInfo; /// [`Types`] of [GraphQL field's][1] return type. /// - /// [1]: https://spec.graphql.org/October2021/#sec-Language.Fields + /// [1]: https://spec.graphql.org/October2021#sec-Language.Fields const TYPE: Type; - /// [Sub-Types][1] of [GraphQL field's][2] return type. + /// [Sub-types][1] of [GraphQL field's][2] return type. /// /// [1]: BaseSubTypes - /// [2]: https://spec.graphql.org/October2021/#sec-Language.Fields + /// [2]: https://spec.graphql.org/October2021#sec-Language.Fields const SUB_TYPES: Types; /// [`WrappedValue`] of [GraphQL field's][1] return type. /// - /// [1]: https://spec.graphql.org/October2021/#sec-Language.Fields + /// [1]: https://spec.graphql.org/October2021#sec-Language.Fields const WRAPPED_VALUE: WrappedValue; /// [GraphQL field's][1] [`Arguments`]. /// - /// [1]: https://spec.graphql.org/October2021/#sec-Language.Fields + /// [1]: https://spec.graphql.org/October2021#sec-Language.Fields const ARGUMENTS: Arguments; } /// Synchronous field of a [GraphQL object][1] or [interface][2]. /// -/// [1]: https://spec.graphql.org/October2021/#sec-Objects -/// [2]: https://spec.graphql.org/October2021/#sec-Interfaces +/// [1]: https://spec.graphql.org/October2021#sec-Objects +/// [2]: https://spec.graphql.org/October2021#sec-Interfaces pub trait Field: FieldMeta { /// Resolves the [`Value`] of this synchronous [`Field`]. /// - /// The `arguments` object contains all the specified arguments, with + /// The `arguments` object contains all the specified arguments, with the /// default values being substituted for the ones not provided by the query. /// /// The `executor` can be used to drive selections into sub-[objects][1]. /// /// [`Value`]: crate::Value - /// [1]: https://spec.graphql.org/October2021/#sec-Objects + /// [1]: https://spec.graphql.org/October2021#sec-Objects fn call( &self, info: &Self::TypeInfo, @@ -371,19 +373,19 @@ pub trait Field: FieldMeta { ) -> ExecutionResult; } -/// Asynchronous field of a GraphQL [`object`][1] or [`interface`][2]. +/// Asynchronous field of a GraphQL [object][1] or [interface][2]. /// -/// [1]: https://spec.graphql.org/October2021/#sec-Objects -/// [2]: https://spec.graphql.org/October2021/#sec-Interfaces +/// [1]: https://spec.graphql.org/October2021#sec-Objects +/// [2]: https://spec.graphql.org/October2021#sec-Interfaces pub trait AsyncField: FieldMeta { /// Resolves the [`Value`] of this asynchronous [`AsyncField`]. /// - /// The `arguments` object contains all the specified arguments, with + /// The `arguments` object contains all the specified arguments, with the /// default values being substituted for the ones not provided by the query. /// /// The `executor` can be used to drive selections into sub-[objects][1]. /// - /// [1]: https://spec.graphql.org/October2021/#sec-Objects + /// [1]: https://spec.graphql.org/October2021#sec-Objects fn call<'b>( &'b self, info: &'b Self::TypeInfo, @@ -412,7 +414,7 @@ pub const fn fnv1a128(str: Name) -> u128 { hash } -/// Length __in bytes__ of the [`format_type`] macro result. +/// Length __in bytes__ of the [`format_type!`] macro result. #[must_use] pub const fn type_len_with_wrapped_val(ty: Type, val: WrappedValue) -> usize { let mut len = ty.as_bytes().len() + "!".as_bytes().len(); // Type! @@ -430,10 +432,10 @@ pub const fn type_len_with_wrapped_val(ty: Type, val: WrappedValue) -> usize { len } -/// Checks whether GraphQL [`objects`][1] can be sub-types, based on the -/// [`WrappedValue`]. +/// Checks whether the given GraphQL [object][1] represents a `subtype` of the +/// given GraphQL `ty`pe, basing on the [`WrappedType`] encoding. /// -/// To fully determine the sub-typing relation [`Type`] should be one of the +/// To fully determine the sub-typing relation the [`Type`] should be one of the /// [`BaseSubTypes::NAMES`]. /// /// [1]: https://spec.graphql.org/October2021#sec-Objects @@ -495,21 +497,23 @@ pub const fn str_eq(l: &str, r: &str) -> bool { /// Asserts that `#[graphql_interface(for = ...)]` has all the types referencing /// this interface in the `impl = ...` attribute argument. +/// +/// Symmetrical to [`assert_interfaces_impls!`]. #[macro_export] macro_rules! assert_implemented_for { ($scalar: ty, $implementor: ty $(, $interfaces: ty)* $(,)?) => { const _: () = { $({ - let is_present = $crate::macros::reflection::str_exists_in_arr( - <$implementor as ::juniper::macros::reflection::BaseType<$scalar>>::NAME, - <$interfaces as ::juniper::macros::reflection::BaseSubTypes<$scalar>>::NAMES, + let is_present = $crate::macros::reflect::str_exists_in_arr( + <$implementor as ::juniper::macros::reflect::BaseType<$scalar>>::NAME, + <$interfaces as ::juniper::macros::reflect::BaseSubTypes<$scalar>>::NAMES, ); if !is_present { const MSG: &str = $crate::const_concat!( "Failed to implement interface `", - <$interfaces as $crate::macros::reflection::BaseType<$scalar>>::NAME, + <$interfaces as $crate::macros::reflect::BaseType<$scalar>>::NAME, "` on `", - <$implementor as $crate::macros::reflection::BaseType<$scalar>>::NAME, + <$implementor as $crate::macros::reflect::BaseType<$scalar>>::NAME, "`: missing implementer reference in interface's `for` attribute.", ); ::std::panic!("{}", MSG); @@ -521,21 +525,23 @@ macro_rules! assert_implemented_for { /// Asserts that `impl = ...` attribute argument has all the types referencing /// this GraphQL type in `#[graphql_interface(for = ...)]`. +/// +/// Symmetrical to [`assert_implemented_for!`]. #[macro_export] macro_rules! assert_interfaces_impls { ($scalar: ty, $interface: ty $(, $implementers: ty)* $(,)?) => { const _: () = { $({ - let is_present = $crate::macros::reflection::str_exists_in_arr( - <$interface as ::juniper::macros::reflection::BaseType<$scalar>>::NAME, - <$implementers as ::juniper::macros::reflection::Implements<$scalar>>::NAMES, + let is_present = $crate::macros::reflect::str_exists_in_arr( + <$interface as ::juniper::macros::reflect::BaseType<$scalar>>::NAME, + <$implementers as ::juniper::macros::reflect::Implements<$scalar>>::NAMES, ); if !is_present { const MSG: &str = $crate::const_concat!( "Failed to implement interface `", - <$interface as $crate::macros::reflection::BaseType<$scalar>>::NAME, + <$interface as $crate::macros::reflect::BaseType<$scalar>>::NAME, "` on `", - <$implementers as $crate::macros::reflection::BaseType<$scalar>>::NAME, + <$implementers as $crate::macros::reflect::BaseType<$scalar>>::NAME, "`: missing interface reference in implementer's `impl` attribute.", ); ::std::panic!("{}", MSG); @@ -580,10 +586,10 @@ macro_rules! assert_subtype { $field_name: expr $(,)? ) => { const _: () = { - const BASE_TY: $crate::macros::reflection::Type = - <$base_ty as $crate::macros::reflection::BaseType<$scalar>>::NAME; - const IMPL_TY: $crate::macros::reflection::Type = - <$impl_ty as $crate::macros::reflection::BaseType<$scalar>>::NAME; + const BASE_TY: $crate::macros::reflect::Type = + <$base_ty as $crate::macros::reflect::BaseType<$scalar>>::NAME; + const IMPL_TY: $crate::macros::reflect::Type = + <$impl_ty as $crate::macros::reflect::BaseType<$scalar>>::NAME; const ERR_PREFIX: &str = $crate::const_concat!( "Failed to implement interface `", BASE_TY, @@ -592,39 +598,39 @@ macro_rules! assert_subtype { "`: ", ); - const FIELD_NAME: $crate::macros::reflection::Name = + const FIELD_NAME: $crate::macros::reflect::Name = $field_name; - const BASE_RETURN_WRAPPED_VAL: $crate::macros::reflection::WrappedValue = - <$base_ty as $crate::macros::reflection::FieldMeta< + const BASE_RETURN_WRAPPED_VAL: $crate::macros::reflect::WrappedValue = + <$base_ty as $crate::macros::reflect::FieldMeta< $scalar, { $crate::checked_hash!(FIELD_NAME, $base_ty, $scalar, ERR_PREFIX) }, >>::WRAPPED_VALUE; - const IMPL_RETURN_WRAPPED_VAL: $crate::macros::reflection::WrappedValue = - <$impl_ty as $crate::macros::reflection::FieldMeta< + const IMPL_RETURN_WRAPPED_VAL: $crate::macros::reflect::WrappedValue = + <$impl_ty as $crate::macros::reflect::FieldMeta< $scalar, { $crate::checked_hash!(FIELD_NAME, $impl_ty, $scalar, ERR_PREFIX) }, >>::WRAPPED_VALUE; - const BASE_RETURN_TY: $crate::macros::reflection::Type = - <$base_ty as $crate::macros::reflection::FieldMeta< + const BASE_RETURN_TY: $crate::macros::reflect::Type = + <$base_ty as $crate::macros::reflect::FieldMeta< $scalar, { $crate::checked_hash!(FIELD_NAME, $base_ty, $scalar, ERR_PREFIX) }, >>::TYPE; - const IMPL_RETURN_TY: $crate::macros::reflection::Type = - <$impl_ty as $crate::macros::reflection::FieldMeta< + const IMPL_RETURN_TY: $crate::macros::reflect::Type = + <$impl_ty as $crate::macros::reflect::FieldMeta< $scalar, { $crate::checked_hash!(FIELD_NAME, $impl_ty, $scalar, ERR_PREFIX) }, >>::TYPE; - const BASE_RETURN_SUB_TYPES: $crate::macros::reflection::Types = - <$base_ty as $crate::macros::reflection::FieldMeta< + const BASE_RETURN_SUB_TYPES: $crate::macros::reflect::Types = + <$base_ty as $crate::macros::reflect::FieldMeta< $scalar, { $crate::checked_hash!(FIELD_NAME, $base_ty, $scalar, ERR_PREFIX) }, >>::SUB_TYPES; - let is_subtype = $crate::macros::reflection::str_exists_in_arr(IMPL_RETURN_TY, BASE_RETURN_SUB_TYPES) - && $crate::macros::reflection::can_be_subtype(BASE_RETURN_WRAPPED_VAL, IMPL_RETURN_WRAPPED_VAL); + let is_subtype = $crate::macros::reflect::str_exists_in_arr(IMPL_RETURN_TY, BASE_RETURN_SUB_TYPES) + && $crate::macros::reflect::can_be_subtype(BASE_RETURN_WRAPPED_VAL, IMPL_RETURN_WRAPPED_VAL); if !is_subtype { const MSG: &str = $crate::const_concat!( ERR_PREFIX, @@ -645,7 +651,7 @@ macro_rules! assert_subtype { /// Asserts validness of the [`Field`]s arguments. See [spec][1] for more /// info. /// -/// [1]: https://spec.graphql.org/October2021/#sel-IAHZhCHCDEEFAAADHD8Cxob +/// [1]: https://spec.graphql.org/October2021#sel-IAHZhCHCDEEFAAADHD8Cxob #[macro_export] macro_rules! assert_field_args { ( @@ -655,10 +661,8 @@ macro_rules! assert_field_args { $field_name: expr $(,)? ) => { const _: () = { - const BASE_NAME: &str = - <$base_ty as $crate::macros::reflection::BaseType<$scalar>>::NAME; - const IMPL_NAME: &str = - <$impl_ty as $crate::macros::reflection::BaseType<$scalar>>::NAME; + const BASE_NAME: &str = <$base_ty as $crate::macros::reflect::BaseType<$scalar>>::NAME; + const IMPL_NAME: &str = <$impl_ty as $crate::macros::reflect::BaseType<$scalar>>::NAME; const ERR_PREFIX: &str = $crate::const_concat!( "Failed to implement interface `", BASE_NAME, @@ -669,21 +673,21 @@ macro_rules! assert_field_args { const FIELD_NAME: &str = $field_name; - const BASE_ARGS: ::juniper::macros::reflection::Arguments = - <$base_ty as $crate::macros::reflection::FieldMeta< + const BASE_ARGS: ::juniper::macros::reflect::Arguments = + <$base_ty as $crate::macros::reflect::FieldMeta< $scalar, { $crate::checked_hash!(FIELD_NAME, $base_ty, $scalar, ERR_PREFIX) }, >>::ARGUMENTS; - const IMPL_ARGS: ::juniper::macros::reflection::Arguments = - <$impl_ty as $crate::macros::reflection::FieldMeta< + const IMPL_ARGS: ::juniper::macros::reflect::Arguments = + <$impl_ty as $crate::macros::reflect::FieldMeta< $scalar, { $crate::checked_hash!(FIELD_NAME, $impl_ty, $scalar, ERR_PREFIX) }, >>::ARGUMENTS; struct Error { cause: Cause, - base: ::juniper::macros::reflection::Argument, - implementation: ::juniper::macros::reflection::Argument, + base: ::juniper::macros::reflect::Argument, + implementation: ::juniper::macros::reflect::Argument, } enum Cause { @@ -715,8 +719,8 @@ macro_rules! assert_field_args { while impl_i < IMPL_ARGS.len() { let (impl_name, impl_type, impl_wrap_val) = IMPL_ARGS[impl_i]; - if $crate::macros::reflection::str_eq(base_name, impl_name) { - if $crate::macros::reflection::str_eq(base_type, impl_type) + if $crate::macros::reflect::str_eq(base_name, impl_name) { + if $crate::macros::reflect::str_eq(base_type, impl_type) && base_wrap_val == impl_wrap_val { was_found = true; @@ -757,7 +761,7 @@ macro_rules! assert_field_args { let mut was_found = false; while base_i < BASE_ARGS.len() { let (base_name, _, _) = BASE_ARGS[base_i]; - if $crate::macros::reflection::str_eq(base_name, impl_name) { + if $crate::macros::reflect::str_eq(base_name, impl_name) { was_found = true; break; } @@ -860,19 +864,19 @@ macro_rules! const_concat { #[macro_export] macro_rules! checked_hash { ($field_name: expr, $impl_ty: ty, $scalar: ty $(, $prefix: expr)? $(,)?) => {{ - let exists = $crate::macros::reflection::str_exists_in_arr( + let exists = $crate::macros::reflect::str_exists_in_arr( $field_name, - <$impl_ty as $crate::macros::reflection::Fields<$scalar>>::NAMES, + <$impl_ty as $crate::macros::reflect::Fields<$scalar>>::NAMES, ); if exists { - $crate::macros::reflection::fnv1a128(FIELD_NAME) + $crate::macros::reflect::fnv1a128(FIELD_NAME) } else { const MSG: &str = $crate::const_concat!( $($prefix,)? "Field `", $field_name, "` isn't implemented on `", - <$impl_ty as $crate::macros::reflection::BaseType<$scalar>>::NAME, + <$impl_ty as $crate::macros::reflect::BaseType<$scalar>>::NAME, "`." ); ::std::panic!("{}", MSG) @@ -885,7 +889,7 @@ macro_rules! checked_hash { /// /// # Examples /// -/// ``` +/// ```rust /// # use juniper::format_type; /// # /// assert_eq!(format_type!("String", 123), "[String]!"); @@ -895,11 +899,10 @@ macro_rules! checked_hash { macro_rules! format_type { ($ty: expr, $wrapped_value: expr $(,)?) => {{ const TYPE: ( - $crate::macros::reflection::Type, - $crate::macros::reflection::WrappedValue, + $crate::macros::reflect::Type, + $crate::macros::reflect::WrappedValue, ) = ($ty, $wrapped_value); - const RES_LEN: usize = - $crate::macros::reflection::type_len_with_wrapped_val(TYPE.0, TYPE.1); + const RES_LEN: usize = $crate::macros::reflect::type_len_with_wrapped_val(TYPE.0, TYPE.1); const OPENING_BRACKET: &str = "["; const CLOSING_BRACKET: &str = "]"; diff --git a/juniper/src/types/scalars.rs b/juniper/src/types/scalars.rs index 7e7603e79..72b559398 100644 --- a/juniper/src/types/scalars.rs +++ b/juniper/src/types/scalars.rs @@ -7,7 +7,7 @@ use serde::{Deserialize, Serialize}; use crate::{ ast::{InputValue, Selection, ToInputValue}, executor::{ExecutionResult, Executor, Registry}, - macros::reflection, + macros::reflect, parser::{LexerError, ParseError, ScalarToken, Token}, schema::meta::MetaType, types::{ @@ -203,16 +203,16 @@ where }) } -impl reflection::WrappedType for str { - const VALUE: reflection::WrappedValue = 1; +impl reflect::WrappedType for str { + const VALUE: reflect::WrappedValue = 1; } -impl reflection::BaseType for str { - const NAME: reflection::Type = "String"; +impl reflect::BaseType for str { + const NAME: reflect::Type = "String"; } -impl reflection::BaseSubTypes for str { - const NAMES: reflection::Types = &[>::NAME]; +impl reflect::BaseSubTypes for str { + const NAMES: reflect::Types = &[>::NAME]; } impl GraphQLType for str diff --git a/juniper_codegen/src/common/parse/mod.rs b/juniper_codegen/src/common/parse/mod.rs index 0ed3c0dfb..5f541eaa3 100644 --- a/juniper_codegen/src/common/parse/mod.rs +++ b/juniper_codegen/src/common/parse/mod.rs @@ -253,7 +253,8 @@ pub(crate) trait GenericsExt { /// Moves all trait and lifetime bounds of these [`syn::Generics`] to its [`syn::WhereClause`]. fn move_bounds_to_where_clause(&mut self); - /// Replaces generic parameters in `ty` found in `self` with default ones. + /// Replaces generic parameters in the given [`syn::Type`] with default + /// ones, provided by these [`syn::Generics`]. fn replace_type_with_defaults(&self, ty: &mut syn::Type); } @@ -312,7 +313,7 @@ impl GenericsExt for syn::Generics { fn visit_generic_argument_mut(&mut self, arg: &mut syn::GenericArgument) { match arg { syn::GenericArgument::Lifetime(lf) => { - *lf = parse_quote!( 'static ); + *lf = parse_quote! { 'static }; } syn::GenericArgument::Type(ty) => { let is_generic = self @@ -330,8 +331,8 @@ impl GenericsExt for syn::Generics { }); if is_generic { - // Replacing with `DefaultScalarValue` instead of - // `()` because generic parameter may be scalar. + // Replace with `DefaultScalarValue` instead of `()` + // because generic parameter may be scalar. *ty = parse_quote!(::juniper::DefaultScalarValue); } } diff --git a/juniper_codegen/src/derive_scalar_value.rs b/juniper_codegen/src/derive_scalar_value.rs index 1e25ce913..3888d757b 100644 --- a/juniper_codegen/src/derive_scalar_value.rs +++ b/juniper_codegen/src/derive_scalar_value.rs @@ -238,23 +238,23 @@ fn impl_scalar_struct( where #scalar: ::juniper::ScalarValue, { } - impl#impl_generics ::juniper::macros::reflection::BaseType<#scalar> for #ident + impl#impl_generics ::juniper::macros::reflect::BaseType<#scalar> for #ident where #scalar: ::juniper::ScalarValue, { - const NAME: ::juniper::macros::reflection::Type = #name; + const NAME: ::juniper::macros::reflect::Type = #name; } - impl#impl_generics ::juniper::macros::reflection::BaseSubTypes<#scalar> for #ident + impl#impl_generics ::juniper::macros::reflect::BaseSubTypes<#scalar> for #ident where #scalar: ::juniper::ScalarValue, { - const NAMES: ::juniper::macros::reflection::Types = - &[>::NAME]; + const NAMES: ::juniper::macros::reflect::Types = + &[>::NAME]; } - impl#impl_generics ::juniper::macros::reflection::WrappedType<#scalar> for #ident + impl#impl_generics ::juniper::macros::reflect::WrappedType<#scalar> for #ident where #scalar: ::juniper::ScalarValue, { - const VALUE: ::juniper::macros::reflection::WrappedValue = 1; + const VALUE: ::juniper::macros::reflect::WrappedValue = 1; } ); diff --git a/juniper_codegen/src/graphql_interface/attr.rs b/juniper_codegen/src/graphql_interface/attr.rs index 5696c4aa9..88f8b0d61 100644 --- a/juniper_codegen/src/graphql_interface/attr.rs +++ b/juniper_codegen/src/graphql_interface/attr.rs @@ -114,14 +114,13 @@ fn expand_on_trait( |c| format_ident!("{}Enum", c.inner().to_string()), ); - let description = attr.description.as_deref().cloned(); let generated_code = Definition { trait_generics: ast.generics.clone(), vis: ast.vis.clone(), enum_ident, enum_alias_ident, name, - description, + description: attr.description.as_deref().cloned(), context, scalar, fields, @@ -138,7 +137,7 @@ fn expand_on_trait( }) } -/// Parses [`field::Definition`] from the given trait method definition. +/// Parses a [`field::Definition`] from the given trait method definition. /// /// Returns [`None`] if parsing fails, or the method field is ignored. #[must_use] @@ -234,16 +233,18 @@ fn parse_field( }) } -/// Emits "trait method can't have default impl block" [`syn::Error`] pointing -/// to the given `span`. +/// Emits "trait method can't have default implementation" [`syn::Error`] +/// pointing to the given `span`. fn err_default_impl_block(span: &S) -> Option { - ERR.emit_custom(span.span(), "trait method can't have default impl block"); + ERR.emit_custom( + span.span(), + "trait method can't have default implementation", + ); None } /// Emits "invalid trait method receiver" [`syn::Error`] pointing to the given /// `span`. -#[must_use] fn err_invalid_method_receiver(span: &S) -> Option { ERR.emit_custom( span.span(), @@ -254,7 +255,6 @@ fn err_invalid_method_receiver(span: &S) -> Option { /// Emits "no trait method receiver" [`syn::Error`] pointing to the given /// `span`. -#[must_use] fn err_no_method_receiver(span: &S) -> Option { ERR.emit_custom( span.span(), diff --git a/juniper_codegen/src/graphql_interface/mod.rs b/juniper_codegen/src/graphql_interface/mod.rs index 6f0b956e5..263d1c5f7 100644 --- a/juniper_codegen/src/graphql_interface/mod.rs +++ b/juniper_codegen/src/graphql_interface/mod.rs @@ -306,7 +306,7 @@ impl ToTokens for Definition { self.impl_graphql_type_tokens().to_tokens(into); self.impl_graphql_value_tokens().to_tokens(into); self.impl_graphql_value_async_tokens().to_tokens(into); - self.impl_traits_for_reflection_tokens().to_tokens(into); + self.impl_reflection_traits_tokens().to_tokens(into); self.impl_field_meta_tokens().to_tokens(into); self.impl_field_tokens().to_tokens(into); self.impl_async_field_tokens().to_tokens(into); @@ -314,7 +314,7 @@ impl ToTokens for Definition { } impl Definition { - /// Generates enum describing all [`implementers`]. + /// Generates enum describing all the [`implementers`]. /// /// [`implementers`]: Self::implementers #[must_use] @@ -329,7 +329,7 @@ impl Definition { .enumerate() .map::(|(id, _)| { let par = format_ident!("__I{}", id); - parse_quote!( #par ) + parse_quote! { #par } }); let variants_idents = self @@ -403,27 +403,33 @@ impl Definition { quote! { __Phantom(#(#phantom_params),*) } }); - let from_impls = self.implementers.iter().zip(variants_idents.clone()).map( - |(ty, ident)| { - quote! { - impl#trait_impl_gens ::std::convert::From<#ty> for #alias_ident#trait_ty_gens - #trait_where_clause - { - fn from(v: #ty) -> Self { - Self::#ident(v) + let from_impls = + self.implementers + .iter() + .zip(variants_idents.clone()) + .map(|(ty, ident)| { + quote! { + #[automatically_derived] + impl#trait_impl_gens ::std::convert::From<#ty> + for #alias_ident#trait_ty_gens + #trait_where_clause + { + fn from(v: #ty) -> Self { + Self::#ident(v) + } } } - } - }, - ); + }); quote! { + #[automatically_derived] #[derive(Clone, Copy, Debug)] #vis enum #enum_ident#enum_gens { #(#variants_idents(#variant_gens_pars),)* #phantom_variant } + #[automatically_derived] #vis type #alias_ident#enum_alias_gens = #enum_ident<#(#enum_to_alias_gens),*>; @@ -452,8 +458,8 @@ impl Definition { quote! { #[automatically_derived] - impl#impl_generics ::juniper::marker::GraphQLInterface<#scalar> for - #ty#ty_generics + impl#impl_generics ::juniper::marker::GraphQLInterface<#scalar> + for #ty#ty_generics #where_clause { fn mark() { @@ -489,8 +495,8 @@ impl Definition { quote! { #[automatically_derived] - impl#impl_generics ::juniper::marker::IsOutputType<#scalar> for - #ty#ty_generics + impl#impl_generics ::juniper::marker::IsOutputType<#scalar> + for #ty#ty_generics #where_clause { fn mark() { @@ -583,9 +589,9 @@ impl Definition { let name = &f.name; Some(quote! { #name => { - ::juniper::macros::reflection::Field::< + ::juniper::macros::reflect::Field::< #scalar, - { ::juniper::macros::reflection::fnv1a128(#name) } + { ::juniper::macros::reflect::fnv1a128(#name) } >::call(self, info, args, executor) } }) @@ -664,9 +670,9 @@ impl Definition { let name = &f.name; quote! { #name => { - ::juniper::macros::reflection::AsyncField::< + ::juniper::macros::reflect::AsyncField::< #scalar, - { ::juniper::macros::reflection::fnv1a128(#name) } + { ::juniper::macros::reflect::fnv1a128(#name) } >::call(self, info, args, executor) } } @@ -711,13 +717,13 @@ impl Definition { /// Returns generated code implementing [`BaseType`], [`BaseSubTypes`], /// [`WrappedType`] and [`Fields`] traits for this [GraphQL interface][1]. /// - /// [`BaseSubTypes`]: juniper::macros::reflection::BaseSubTypes - /// [`BaseType`]: juniper::macros::reflection::BaseType - /// [`Fields`]: juniper::macros::reflection::Fields - /// [`WrappedType`]: juniper::macros::reflection::WrappedType + /// [`BaseSubTypes`]: juniper::macros::reflect::BaseSubTypes + /// [`BaseType`]: juniper::macros::reflect::BaseType + /// [`Fields`]: juniper::macros::reflect::Fields + /// [`WrappedType`]: juniper::macros::reflect::WrappedType /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces #[must_use] - fn impl_traits_for_reflection_tokens(&self) -> TokenStream { + fn impl_reflection_traits_tokens(&self) -> TokenStream { let ty = &self.enum_alias_ident; let implementers = &self.implementers; let scalar = &self.scalar; @@ -730,38 +736,38 @@ impl Definition { quote! { #[automatically_derived] - impl#impl_generics ::juniper::macros::reflection::BaseType<#scalar> + impl#impl_generics ::juniper::macros::reflect::BaseType<#scalar> for #ty#ty_generics #where_clause { - const NAME: ::juniper::macros::reflection::Type = #name; + const NAME: ::juniper::macros::reflect::Type = #name; } #[automatically_derived] - impl#impl_generics ::juniper::macros::reflection::BaseSubTypes<#scalar> + impl#impl_generics ::juniper::macros::reflect::BaseSubTypes<#scalar> for #ty#ty_generics #where_clause { - const NAMES: ::juniper::macros::reflection::Types = &[ - >::NAME, - #(<#implementers as ::juniper::macros::reflection::BaseType<#scalar>>::NAME),* + const NAMES: ::juniper::macros::reflect::Types = &[ + >::NAME, + #(<#implementers as ::juniper::macros::reflect::BaseType<#scalar>>::NAME),* ]; } #[automatically_derived] - impl#impl_generics ::juniper::macros::reflection::WrappedType<#scalar> + impl#impl_generics ::juniper::macros::reflect::WrappedType<#scalar> for #ty#ty_generics #where_clause { - const VALUE: ::juniper::macros::reflection::WrappedValue = 1; + const VALUE: ::juniper::macros::reflect::WrappedValue = 1; } #[automatically_derived] - impl#impl_generics ::juniper::macros::reflection::Fields<#scalar> + impl#impl_generics ::juniper::macros::reflect::Fields<#scalar> for #ty#ty_generics #where_clause { - const NAMES: ::juniper::macros::reflection::Names = &[#(#fields),*]; + const NAMES: ::juniper::macros::reflect::Names = &[#(#fields),*]; } } } @@ -769,7 +775,7 @@ impl Definition { /// Returns generated code implementing [`FieldMeta`] for each field of this /// [GraphQL interface][1]. /// - /// [`FieldMeta`]: juniper::macros::reflection::FieldMeta + /// [`FieldMeta`]: juniper::macros::reflect::FieldMeta /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces fn impl_field_meta_tokens(&self) -> TokenStream { let ty = &self.enum_alias_ident; @@ -799,26 +805,27 @@ impl Definition { quote! { #[allow(non_snake_case)] - impl#impl_generics ::juniper::macros::reflection::FieldMeta< + #[automatically_derived] + impl#impl_generics ::juniper::macros::reflect::FieldMeta< #scalar, - { ::juniper::macros::reflection::fnv1a128(#field_name) } + { ::juniper::macros::reflect::fnv1a128(#field_name) } > for #ty#ty_generics #where_clause { type Context = #context; type TypeInfo = (); - const TYPE: ::juniper::macros::reflection::Type = - <#return_ty as ::juniper::macros::reflection::BaseType<#scalar>>::NAME; - const SUB_TYPES: ::juniper::macros::reflection::Types = - <#return_ty as ::juniper::macros::reflection::BaseSubTypes<#scalar>>::NAMES; - const WRAPPED_VALUE: ::juniper::macros::reflection::WrappedValue = - <#return_ty as ::juniper::macros::reflection::WrappedType<#scalar>>::VALUE; + const TYPE: ::juniper::macros::reflect::Type = + <#return_ty as ::juniper::macros::reflect::BaseType<#scalar>>::NAME; + const SUB_TYPES: ::juniper::macros::reflect::Types = + <#return_ty as ::juniper::macros::reflect::BaseSubTypes<#scalar>>::NAMES; + const WRAPPED_VALUE: ::juniper::macros::reflect::WrappedValue = + <#return_ty as ::juniper::macros::reflect::WrappedType<#scalar>>::VALUE; const ARGUMENTS: &'static [( - ::juniper::macros::reflection::Name, - ::juniper::macros::reflection::Type, - ::juniper::macros::reflection::WrappedValue, + ::juniper::macros::reflect::Name, + ::juniper::macros::reflect::Type, + ::juniper::macros::reflect::WrappedValue, )] = &[#(( #args_names, - <#args_tys as ::juniper::macros::reflection::BaseType<#scalar>>::NAME, - <#args_tys as ::juniper::macros::reflection::WrappedType<#scalar>>::VALUE, + <#args_tys as ::juniper::macros::reflect::BaseType<#scalar>>::NAME, + <#args_tys as ::juniper::macros::reflect::WrappedType<#scalar>>::VALUE, )),*]; } } @@ -829,7 +836,7 @@ impl Definition { /// Returns generated code implementing [`Field`] trait for each field of /// this [GraphQL interface][1]. /// - /// [`Field`]: juniper::macros::reflection::Field + /// [`Field`]: juniper::macros::reflect::Field /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces fn impl_field_tokens(&self) -> TokenStream { let ty = &self.enum_alias_ident; @@ -864,9 +871,10 @@ impl Definition { quote! { #[allow(non_snake_case)] - impl#impl_generics ::juniper::macros::reflection::Field< + #[automatically_derived] + impl#impl_generics ::juniper::macros::reflect::Field< #scalar, - { ::juniper::macros::reflection::fnv1a128(#field_name) } + { ::juniper::macros::reflect::fnv1a128(#field_name) } > for #ty#ty_generics #where_clause { fn call( &self, @@ -883,9 +891,9 @@ impl Definition { #field_name, ); - <_ as ::juniper::macros::reflection::Field::< + <_ as ::juniper::macros::reflect::Field::< #scalar, - { ::juniper::macros::reflection::fnv1a128(#field_name) }, + { ::juniper::macros::reflect::fnv1a128(#field_name) }, >>::call(v, info, args, executor) })* #unreachable_arm @@ -900,7 +908,7 @@ impl Definition { /// Returns generated code implementing [`AsyncField`] trait for each field /// of this [GraphQL interface][1]. /// - /// [`AsyncField`]: juniper::macros::reflection::AsyncField + /// [`AsyncField`]: juniper::macros::reflect::AsyncField /// [1]: https://spec.graphql.org/June2018/#sec-Interfaces fn impl_async_field_tokens(&self) -> TokenStream { let ty = &self.enum_alias_ident; @@ -935,9 +943,10 @@ impl Definition { quote! { #[allow(non_snake_case)] - impl#impl_generics ::juniper::macros::reflection::AsyncField< + #[automatically_derived] + impl#impl_generics ::juniper::macros::reflect::AsyncField< #scalar, - { ::juniper::macros::reflection::fnv1a128(#field_name) } + { ::juniper::macros::reflect::fnv1a128(#field_name) } > for #ty#ty_generics #where_clause { fn call<'b>( &'b self, @@ -954,9 +963,9 @@ impl Definition { #field_name, ); - <_ as ::juniper::macros::reflection::AsyncField< + <_ as ::juniper::macros::reflect::AsyncField< #scalar, - { ::juniper::macros::reflection::fnv1a128(#field_name) }, + { ::juniper::macros::reflect::fnv1a128(#field_name) }, >>::call(v, info, args, executor) })* #unreachable_arm @@ -1038,7 +1047,7 @@ impl Definition { } /// Returns generated code for the [`GraphQLValue::resolve_into_type`][0] - /// method, which downcasts this enum into its underlying + /// method, which resolves this enum into its underlying /// [`implementers`][1] type synchronously. /// /// [0]: juniper::GraphQLValue::resolve_into_type @@ -1068,8 +1077,8 @@ impl Definition { } } - /// Returns trait generics replaced with default values for usage in `const` - /// context. + /// Returns trait generics replaced with the default values for usage in a + /// `const` context. #[must_use] fn const_trait_generics(&self) -> syn::PathArguments { struct GenericsForConst(syn::AngleBracketedGenericArguments); @@ -1077,10 +1086,10 @@ impl Definition { impl Visit<'_> for GenericsForConst { fn visit_generic_param(&mut self, param: &syn::GenericParam) { let arg = match param { - syn::GenericParam::Lifetime(_) => parse_quote!( 'static ), + syn::GenericParam::Lifetime(_) => parse_quote! { 'static }, syn::GenericParam::Type(ty) => { if ty.default.is_none() { - parse_quote!(::juniper::DefaultScalarValue) + parse_quote! { ::juniper::DefaultScalarValue } } else { return; } @@ -1089,7 +1098,7 @@ impl Definition { if c.default.is_none() { // This hack works because only `min_const_generics` // are enabled for now. - // TODO: replace this once full `const_generics` are + // TODO: Replace this once full `const_generics` are // available. // Maybe with `<_ as Default>::default()`? parse_quote!({ 0_u8 as _ }) diff --git a/juniper_codegen/src/graphql_object/mod.rs b/juniper_codegen/src/graphql_object/mod.rs index e2d752f8b..49459737b 100644 --- a/juniper_codegen/src/graphql_object/mod.rs +++ b/juniper_codegen/src/graphql_object/mod.rs @@ -364,13 +364,13 @@ impl Definition { /// Returns generated code implementing [`BaseType`], [`BaseSubTypes`], /// [`WrappedType`] and [`Fields`] traits for this [GraphQL object][1]. /// - /// [`BaseSubTypes`]: juniper::macros::reflection::BaseSubTypes - /// [`BaseType`]: juniper::macros::reflection::BaseType - /// [`Fields`]: juniper::macros::reflection::Fields - /// [`WrappedType`]: juniper::macros::reflection::WrappedType + /// [`BaseSubTypes`]: juniper::macros::reflect::BaseSubTypes + /// [`BaseType`]: juniper::macros::reflect::BaseType + /// [`Fields`]: juniper::macros::reflect::Fields + /// [`WrappedType`]: juniper::macros::reflect::WrappedType /// [1]: https://spec.graphql.org/June2018/#sec-Objects #[must_use] - pub(crate) fn impl_traits_for_reflection_tokens(&self) -> TokenStream { + pub(crate) fn impl_reflection_traits_tokens(&self) -> TokenStream { let scalar = &self.scalar; let name = &self.name; let (impl_generics, where_clause) = self.impl_generics(false); @@ -380,45 +380,45 @@ impl Definition { quote! { #[automatically_derived] - impl#impl_generics ::juniper::macros::reflection::BaseType<#scalar> + impl#impl_generics ::juniper::macros::reflect::BaseType<#scalar> for #ty #where_clause { - const NAME: ::juniper::macros::reflection::Type = #name; + const NAME: ::juniper::macros::reflect::Type = #name; } #[automatically_derived] - impl#impl_generics ::juniper::macros::reflection::BaseSubTypes<#scalar> + impl#impl_generics ::juniper::macros::reflect::BaseSubTypes<#scalar> for #ty #where_clause { - const NAMES: ::juniper::macros::reflection::Types = - &[>::NAME]; + const NAMES: ::juniper::macros::reflect::Types = + &[>::NAME]; } #[automatically_derived] - impl#impl_generics ::juniper::macros::reflection::Implements<#scalar> + impl#impl_generics ::juniper::macros::reflect::Implements<#scalar> for #ty #where_clause { - const NAMES: ::juniper::macros::reflection::Types = - &[#(<#interfaces as ::juniper::macros::reflection::BaseType<#scalar>>::NAME),*]; + const NAMES: ::juniper::macros::reflect::Types = + &[#(<#interfaces as ::juniper::macros::reflect::BaseType<#scalar>>::NAME),*]; } #[automatically_derived] - impl#impl_generics ::juniper::macros::reflection::WrappedType<#scalar> + impl#impl_generics ::juniper::macros::reflect::WrappedType<#scalar> for #ty #where_clause { - const VALUE: ::juniper::macros::reflection::WrappedValue = 1; + const VALUE: ::juniper::macros::reflect::WrappedValue = 1; } #[automatically_derived] - impl#impl_generics ::juniper::macros::reflection::Fields<#scalar> + impl#impl_generics ::juniper::macros::reflect::Fields<#scalar> for #ty #where_clause { - const NAMES: ::juniper::macros::reflection::Names = &[#(#fields),*]; + const NAMES: ::juniper::macros::reflect::Names = &[#(#fields),*]; } } } @@ -501,7 +501,7 @@ impl ToTokens for Definition { self.impl_graphql_value_tokens().to_tokens(into); self.impl_graphql_value_async_tokens().to_tokens(into); self.impl_as_dyn_graphql_value_tokens().to_tokens(into); - self.impl_traits_for_reflection_tokens().to_tokens(into); + self.impl_reflection_traits_tokens().to_tokens(into); self.impl_field_meta_tokens().to_tokens(into); self.impl_field_tokens().to_tokens(into); self.impl_async_field_tokens().to_tokens(into); @@ -527,7 +527,7 @@ impl Definition { let generics = { let mut generics = self.generics.clone(); if scalar.is_implicit_generic() { - generics.params.push(parse_quote!(#scalar)) + generics.params.push(parse_quote! { #scalar }) } generics }; @@ -582,11 +582,10 @@ impl Definition { .filter_map(|arg| match arg { field::MethodArgument::Regular(arg) => { let (name, ty) = (&arg.name, &arg.ty); - Some(quote! {( #name, - <#ty as ::juniper::macros::reflection::BaseType<#scalar>>::NAME, - <#ty as ::juniper::macros::reflection::WrappedType<#scalar>>::VALUE, + <#ty as ::juniper::macros::reflect::BaseType<#scalar>>::NAME, + <#ty as ::juniper::macros::reflect::WrappedType<#scalar>>::VALUE, )}) } field::MethodArgument::Executor | field::MethodArgument::Context(_) => None, @@ -596,24 +595,22 @@ impl Definition { quote! { #[allow(deprecated, non_snake_case)] #[automatically_derived] - impl #impl_generics ::juniper::macros::reflection::FieldMeta< + impl #impl_generics ::juniper::macros::reflect::FieldMeta< #scalar, - { ::juniper::macros::reflection::fnv1a128(#name) } - > for #impl_ty - #where_clause - { + { ::juniper::macros::reflect::fnv1a128(#name) } + > for #impl_ty #where_clause { type Context = #context; type TypeInfo = (); - const TYPE: ::juniper::macros::reflection::Type = - <#ty as ::juniper::macros::reflection::BaseType<#scalar>>::NAME; - const SUB_TYPES: ::juniper::macros::reflection::Types = - <#ty as ::juniper::macros::reflection::BaseSubTypes<#scalar>>::NAMES; - const WRAPPED_VALUE: juniper::macros::reflection::WrappedValue = - <#ty as ::juniper::macros::reflection::WrappedType<#scalar>>::VALUE; + const TYPE: ::juniper::macros::reflect::Type = + <#ty as ::juniper::macros::reflect::BaseType<#scalar>>::NAME; + const SUB_TYPES: ::juniper::macros::reflect::Types = + <#ty as ::juniper::macros::reflect::BaseSubTypes<#scalar>>::NAMES; + const WRAPPED_VALUE: juniper::macros::reflect::WrappedValue = + <#ty as ::juniper::macros::reflect::WrappedType<#scalar>>::VALUE; const ARGUMENTS: &'static [( - ::juniper::macros::reflection::Name, - ::juniper::macros::reflection::Type, - ::juniper::macros::reflection::WrappedValue, + ::juniper::macros::reflect::Name, + ::juniper::macros::reflect::Type, + ::juniper::macros::reflect::WrappedValue, )] = &[#(#arguments,)*]; } } @@ -641,7 +638,7 @@ impl Definition { ::std::panic!( "Tried to resolve async field `{}` on type `{}` with a sync resolver", #name, - >::NAME, + >::NAME, ); } } else { @@ -674,9 +671,9 @@ impl Definition { quote! { #[allow(deprecated, non_snake_case)] #[automatically_derived] - impl #impl_generics ::juniper::macros::reflection::Field< + impl #impl_generics ::juniper::macros::reflect::Field< #scalar, - { ::juniper::macros::reflection::fnv1a128(#name) } + { ::juniper::macros::reflect::fnv1a128(#name) } > for #impl_ty #where_clause { @@ -735,9 +732,9 @@ impl Definition { quote! { #[allow(deprecated, non_snake_case)] #[automatically_derived] - impl #impl_generics ::juniper::macros::reflection::AsyncField< + impl #impl_generics ::juniper::macros::reflect::AsyncField< #scalar, - { ::juniper::macros::reflection::fnv1a128(#name) } + { ::juniper::macros::reflect::fnv1a128(#name) } > for #impl_ty #where_clause { @@ -776,9 +773,9 @@ impl Definition { let name = &f.name; quote! { #name => { - ::juniper::macros::reflection::Field::< + ::juniper::macros::reflect::Field::< #scalar, - { ::juniper::macros::reflection::fnv1a128(#name) } + { ::juniper::macros::reflect::fnv1a128(#name) } >::call(self, info, args, executor) } } @@ -840,9 +837,9 @@ impl Definition { let name = &f.name; quote! { #name => { - ::juniper::macros::reflection::AsyncField::< + ::juniper::macros::reflect::AsyncField::< #scalar, - { ::juniper::macros::reflection::fnv1a128(#name) } + { ::juniper::macros::reflect::fnv1a128(#name) } >::call(self, info, args, executor) } } diff --git a/juniper_codegen/src/graphql_union/mod.rs b/juniper_codegen/src/graphql_union/mod.rs index eb882961d..4c3bafca5 100644 --- a/juniper_codegen/src/graphql_union/mod.rs +++ b/juniper_codegen/src/graphql_union/mod.rs @@ -320,7 +320,7 @@ impl ToTokens for Definition { self.impl_graphql_type_tokens().to_tokens(into); self.impl_graphql_value_tokens().to_tokens(into); self.impl_graphql_value_async_tokens().to_tokens(into); - self.impl_traits_for_reflection_tokens().to_tokens(into); + self.impl_reflection_traits_tokens().to_tokens(into); } } @@ -614,12 +614,12 @@ impl Definition { /// Returns generated code implementing [`BaseType`], [`BaseSubTypes`] and /// [`WrappedType`] traits for this [GraphQL union][1]. /// - /// [`BaseSubTypes`]: juniper::macros::reflection::BaseSubTypes - /// [`BaseType`]: juniper::macros::reflection::BaseType - /// [`WrappedType`]: juniper::macros::reflection::WrappedType + /// [`BaseSubTypes`]: juniper::macros::reflect::BaseSubTypes + /// [`BaseType`]: juniper::macros::reflect::BaseType + /// [`WrappedType`]: juniper::macros::reflect::WrappedType /// [1]: https://spec.graphql.org/June2018/#sec-Unions #[must_use] - pub(crate) fn impl_traits_for_reflection_tokens(&self) -> TokenStream { + pub(crate) fn impl_reflection_traits_tokens(&self) -> TokenStream { let scalar = &self.scalar; let name = &self.name; let variants = self.variants.iter().map(|var| &var.ty); @@ -627,30 +627,30 @@ impl Definition { quote! { #[automatically_derived] - impl#impl_generics ::juniper::macros::reflection::BaseType<#scalar> + impl#impl_generics ::juniper::macros::reflect::BaseType<#scalar> for #ty #where_clause { - const NAME: ::juniper::macros::reflection::Type = #name; + const NAME: ::juniper::macros::reflect::Type = #name; } #[automatically_derived] - impl#impl_generics ::juniper::macros::reflection::BaseSubTypes<#scalar> + impl#impl_generics ::juniper::macros::reflect::BaseSubTypes<#scalar> for #ty #where_clause { - const NAMES: ::juniper::macros::reflection::Types = &[ - >::NAME, - #(<#variants as ::juniper::macros::reflection::BaseType<#scalar>>::NAME),* + const NAMES: ::juniper::macros::reflect::Types = &[ + >::NAME, + #(<#variants as ::juniper::macros::reflect::BaseType<#scalar>>::NAME),* ]; } #[automatically_derived] - impl#impl_generics ::juniper::macros::reflection::WrappedType<#scalar> + impl#impl_generics ::juniper::macros::reflect::WrappedType<#scalar> for #ty #where_clause { - const VALUE: ::juniper::macros::reflection::WrappedValue = 1; + const VALUE: ::juniper::macros::reflect::WrappedValue = 1; } } } diff --git a/juniper_codegen/src/impl_scalar.rs b/juniper_codegen/src/impl_scalar.rs index 523e99e96..e40aa41de 100644 --- a/juniper_codegen/src/impl_scalar.rs +++ b/juniper_codegen/src/impl_scalar.rs @@ -328,23 +328,23 @@ pub fn build_scalar( } } - impl#generic_type_decl ::juniper::macros::reflection::BaseType<#generic_type> for #impl_for_type + impl#generic_type_decl ::juniper::macros::reflect::BaseType<#generic_type> for #impl_for_type #generic_type_bound { - const NAME: ::juniper::macros::reflection::Type = #name; + const NAME: ::juniper::macros::reflect::Type = #name; } - impl#generic_type_decl ::juniper::macros::reflection::BaseSubTypes<#generic_type> for #impl_for_type + impl#generic_type_decl ::juniper::macros::reflect::BaseSubTypes<#generic_type> for #impl_for_type #generic_type_bound { - const NAMES: ::juniper::macros::reflection::Types = - &[>::NAME]; + const NAMES: ::juniper::macros::reflect::Types = + &[>::NAME]; } - impl#generic_type_decl ::juniper::macros::reflection::WrappedType<#generic_type> for #impl_for_type + impl#generic_type_decl ::juniper::macros::reflect::WrappedType<#generic_type> for #impl_for_type #generic_type_bound { - const VALUE: ::juniper::macros::reflection::WrappedValue = 1; + const VALUE: ::juniper::macros::reflect::WrappedValue = 1; } ); diff --git a/juniper_codegen/src/lib.rs b/juniper_codegen/src/lib.rs index 90e59a318..4aff89d1e 100644 --- a/juniper_codegen/src/lib.rs +++ b/juniper_codegen/src/lib.rs @@ -271,8 +271,8 @@ pub fn graphql_scalar(args: TokenStream, input: TokenStream) -> TokenStream { /// Specifying multiple `#[graphql_interface]` attributes on the same definition /// is totally okay. They all will be treated as a single attribute. /// -/// [GraphQL interfaces][1] are more like Go's structural interfaces, while -/// Rust's traits are more like typeclasses. Using `impl Trait` isn't an +/// [GraphQL interfaces][1] are more like structurally-typed interfaces, while +/// Rust's traits are more like type classes. Using `impl Trait` isn't an /// option, so you have to cover all trait's methods with type's fields or /// impl block. But no one is stopping you from additionally implementing trait /// manually. @@ -282,7 +282,8 @@ pub fn graphql_scalar(args: TokenStream, input: TokenStream) -> TokenStream { /// concrete implementers_, while in Rust, a trait is an _abstraction only_ and /// you need a separate type to downcast into a concrete implementer, like enum /// or [trait object][3], because trait doesn't represent a type itself. -/// Macro uses Rust enum to represent a value type of [GraphQL interface][1]. +/// Macro uses Rust enums only to represent a value type of a +/// [GraphQL interface][1]. /// /// ``` /// use juniper::{graphql_interface, GraphQLObject}; diff --git a/juniper_codegen/src/util/mod.rs b/juniper_codegen/src/util/mod.rs index 38722918f..726fce44c 100644 --- a/juniper_codegen/src/util/mod.rs +++ b/juniper_codegen/src/util/mod.rs @@ -907,23 +907,23 @@ impl GraphQLTypeDefiniton { } } - impl#impl_generics ::juniper::macros::reflection::BaseType<#scalar> for #ty + impl#impl_generics ::juniper::macros::reflect::BaseType<#scalar> for #ty #where_clause { - const NAME: ::juniper::macros::reflection::Type = #name; + const NAME: ::juniper::macros::reflect::Type = #name; } - impl#impl_generics ::juniper::macros::reflection::BaseSubTypes<#scalar> for #ty + impl#impl_generics ::juniper::macros::reflect::BaseSubTypes<#scalar> for #ty #where_clause { - const NAMES: ::juniper::macros::reflection::Types = - &[>::NAME]; + const NAMES: ::juniper::macros::reflect::Types = + &[>::NAME]; } - impl#impl_generics ::juniper::macros::reflection::WrappedType<#scalar> for #ty + impl#impl_generics ::juniper::macros::reflect::WrappedType<#scalar> for #ty #where_clause { - const VALUE: ::juniper::macros::reflection::WrappedValue = 1; + const VALUE: ::juniper::macros::reflect::WrappedValue = 1; } ); @@ -1173,26 +1173,26 @@ impl GraphQLTypeDefiniton { } } - impl#impl_generics ::juniper::macros::reflection::BaseType<#scalar> + impl#impl_generics ::juniper::macros::reflect::BaseType<#scalar> for #ty #type_generics_tokens #where_clause { - const NAME: ::juniper::macros::reflection::Type = #name; + const NAME: ::juniper::macros::reflect::Type = #name; } - impl#impl_generics ::juniper::macros::reflection::BaseSubTypes<#scalar> + impl#impl_generics ::juniper::macros::reflect::BaseSubTypes<#scalar> for #ty #type_generics_tokens #where_clause { - const NAMES: ::juniper::macros::reflection::Types = - &[>::NAME]; + const NAMES: ::juniper::macros::reflect::Types = + &[>::NAME]; } - impl#impl_generics ::juniper::macros::reflection::WrappedType<#scalar> + impl#impl_generics ::juniper::macros::reflect::WrappedType<#scalar> for #ty #type_generics_tokens #where_clause { - const VALUE: ::juniper::macros::reflection::WrappedValue = 1; + const VALUE: ::juniper::macros::reflect::WrappedValue = 1; } );