-
Notifications
You must be signed in to change notification settings - Fork 423
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Rework core traits #1072
base: master
Are you sure you want to change the base?
Rework core traits #1072
Conversation
Regarding "Further investigation" section...
Well, this is simple. Once we decouple the orphan rules omitting responsibility from
This one is trickier as hits something quite fundamental regarding the Rust type system:
We cannot have 1 and 2 at the same time, as the requirements are clear contradiction. To have them both, we need to introduce some transparent wrapper type holding our type parameter struct Owned<X, By>(X, PhatomData<By>); Now our impl<A, B, T> Foo<A> for Owned<Box<T>, B> where T: Foo<B> {} Hooray? Not yet! The problem has shifted to the place where we should or should not wrap the type. Unfortunately, we cannot do that seamlessly. trait IntoOwned {
type Owned;
}
struct Mine;
impl juniper::IntoOwned for u8 { // oops! orphan rules again
type Owned = juniper::Owned<Self, Mine>;
} Without seamless conversion, we cannot use this implementation just like that: #[derive(graphql::Object)]
struct Human {
id: u8,
} This shows very well our fundamental limitation of unconstrained type parameter, described above. Who should guess which implementation should be used here? Different crates may provide different Ok, then: #[derive(graphql::Object)]
struct Human {
id: Owned<u8, Mine>,
} Hmm, not quite ergonomic. We don't want to mess with wrappers in the user code. #[derive(graphql::Object)]
struct Human {
#[graphql(owner = Mine)]
id: u8, // and we do wrapping under-the-hood in the macro expansion only
} Naming still requires bikeshedding to be better, though. Conclusions? With this small ergonomic hit (in a form of additional attribute argument in places where foreign types and our types are connected) we solve the problem of "polymorphism" for a type parameter for omitting orphan rules. This way a custom type won't pollute all the schema types above it, just a field which refers it directly. And now we're able to use |
@ilslv thoughts? ☝️ |
@tyranron this sounds a lot like From what i can tell, there is no way we can exactly replicate #[derive(graphql::Object)]
struct Human {
#[graphql(with = "custom")]
id: u8, // and we do wrapping under-the-hood in the macro expansion only
}
mod custom {
pub(super) enum Custom {}
// functions ...
} |
Nota bene At the moment we have a problem:
Meaning that Seems to be resolvable by quantifying all the type lifetimes into HTRB: |
Do GATs change any of the design thinking here? |
@LegNeato at the moment I don't see any need of them here, but it may appear eventually to solve some problems. |
Are we going to complete this rework eventually? This is a lot of work indeed, we should not leave them around. |
Requires #1028, #1047, #1052
Required for #975
Related to #1091
Eliminates #1097
This PR represents an attempt to make a fundamental rework of Juniper resolving machinery (
GraphQLType
,GraphQLValue
, etc).Motivation
At the moment, the core traits like
GraphQLType
andGraphQLValue
impose the following problems to codebase:Context
andTypeInfo
types, as they're defined as associative types. So, once some leaf type in schema uses a different type forContext
, we're fucked, and should either make the whole schema work with a singleContext
type, or doesn't compile at all. The same applies toTypeInfo
, which, in result, makes impossible to combine static and dynamic GraphQL schemas at all.Solution
This PR tries to remodel the core traits machinery in the following ways:
impl
blocks prevents redudant bounds pollution of the code base.impl
blocks. Require only the minimum to makeimpl
work. For thetransparent
behavior the bounds should bubble-up structurally (structure implements trait if all the required fields implement trait).TypeInfo
andContext
as generic type parameters for all possible implementations, and requireBorrow<ConcreteContextType>
for the implementation needs. Not a single implemetation should put a concrete type into these type parameters, otherwise the whole polymorphism will be broken (as we have now with associative types). This allows us to fully remove thecontext =
arguments in macros, and to use different context types in different methods of a single type. As for type info, it allows to combine dynamic and static schemas naturally.FromInputValue
trait, likeserde::Deserialize<'de>
does. This allows to pass references as arguments (and so parse unsized types) to fields in the similar degree as supported byserde
deserialization.ScalarValue
type parameter into a separate type parameter. RemoveScalarValue
type parameter in places where it's not really required.Example
Meet a shiny new Juniper:
Further investigation
The solution for the following additional capabilities is not figured out yet and requires further investigation:
ScalarValue
. At the moment the concreteScalarValue
type in a leaf type implementation polutes all the schema the same way theContext
/TypeInfo
does. But we cannot justBorrow<CustomScalar>
from generic like we're doing forContext
. Still it would be nice to figure out the way for combining types using differentScalarValue
s in their implementations.ScalarValue
type in implementations for this purpose. But it's not the responsibility of theScalarValue
notion, and usingScalarValue
for that makes to put it into places where it's not required at all (likereflect::BaseType
trait). Having a separate mechanic for omitting orphan rules would be much more intuitive. However, to work this polymorphically, we need to resolve the same implications imposed by previous question.The main problem with polymorphism in both questions is that we cannot express
forall
quantifier for types in Rust:Solved: See #1072 (comment)
Progress
This is a huge work, as requires an accurate re-implementation of the whole Juniper resolving and type registration machinery.
&T
(ref)&mut T
(mut ref)Box<T>
Rc<T>
Arc<T>
Cow<'_, T>
Option<T>
Nullable<T>
Vec<T>
[T]
(slice)[T; N]
(array)HashSet
(requires Implement HashSet support the same way as Vec #1070)BTreeSet
str
#[graphql::Scalar]
macro#[graphql::Enum]
macro (requires Refactor#[derive(GraphQLEnum)]
macro #1047)#[graphql::InputObject]
macro (requires Refactor#[derive(GraphQLInputObject)]
#1052)#[graphql::Object]
macro#[graphql::Union]
macro#[graphql::Interface]
macro (requires Allow interfaces to implement other interfaces (#1000) #1028)#[graphql::Subscription]
macro